@pellux/goodvibes-tui 2.0.21 → 2.1.0
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 +58 -0
- package/README.md +16 -16
- package/docs/README.md +4 -4
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +16 -6
- 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-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/verification-ledger.ts +18 -16
- 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/{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
|
@@ -1,41 +1,20 @@
|
|
|
1
1
|
import { UIFactory } from '../renderer/ui-factory.ts';
|
|
2
|
-
import {
|
|
3
|
-
import { isDiffContent, renderExpandedToolResultLines } from '../renderer/tool-result-expanded-lines.ts';
|
|
4
|
-
import { activeTheme, activeUiTones } from '../renderer/theme.ts';
|
|
5
|
-
import { renderToolCallBlock } from '../renderer/tool-call.ts';
|
|
6
|
-
import { summarizeToolResult } from '../renderer/tool-result-summary.ts';
|
|
2
|
+
import { activeTheme, activeTokens } from '../renderer/theme.ts';
|
|
7
3
|
import type { ToolCall } from '@pellux/goodvibes-sdk/platform/types';
|
|
8
|
-
import { renderThinkingBlock } from '../renderer/thinking.ts';
|
|
9
4
|
import { renderSystemMessage } from '../renderer/system-message.ts';
|
|
10
5
|
import { createEmptyLine, type Line, type Cell } from '@pellux/goodvibes-sdk/platform/types';
|
|
11
|
-
import { getSplashLines,
|
|
6
|
+
import { getSplashLines, SPLASH_GRADIENT } from '../utils/splash-lines.ts';
|
|
12
7
|
import { interpolateColor, getDisplayWidth, wrapText } from '../utils/terminal-width.ts';
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
import { renderConversationEventLine, renderConversationFoldedRow } from '../renderer/conversation-surface.ts';
|
|
16
|
-
import { GLYPHS } from '../renderer/ui-primitives.ts';
|
|
17
|
-
import type { BlockMeta } from './conversation-types.ts';
|
|
18
|
-
import {
|
|
19
|
-
collectToolCallOutcomes,
|
|
20
|
-
isTurnCollapsed,
|
|
21
|
-
type ConversationRenderContext,
|
|
22
|
-
} from './conversation-render-context.ts';
|
|
8
|
+
import { LAYOUT } from '../renderer/layout.ts';
|
|
9
|
+
import type { ConversationRenderContext } from './conversation-render-context.ts';
|
|
23
10
|
import { renderCompactionContinuationMessage } from './conversation-compaction-render.ts';
|
|
24
|
-
// Which rows fold, and the blank separator that earns, shared verbatim with
|
|
25
|
-
// conversation-line-cache.ts so a warm cache and a cold rebuild space alike.
|
|
26
|
-
import { isToolResultFolded, trailingBlankAfter } from './conversation-fold.ts';
|
|
27
|
-
import { drawTreeRails, treeIndentCols, treeTextCol } from '@pellux/goodvibes-terminal-shell';
|
|
28
|
-
import {
|
|
29
|
-
MAX_NEST_DEPTH,
|
|
30
|
-
buildRenderPlan,
|
|
31
|
-
collapseKeyForNode,
|
|
32
|
-
computeAssistantTurns,
|
|
33
|
-
type AssistantTurnMembership,
|
|
34
|
-
type RenderNode,
|
|
35
|
-
} from './conversation-turn-structure.ts';
|
|
36
11
|
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
|
|
37
|
-
import { parseDiffForApply } from '@pellux/goodvibes-sdk/platform/core';
|
|
38
12
|
import { extractUserDisplayText, COMPACTION_HANDOFF_HEADER } from '@pellux/goodvibes-sdk/platform/core';
|
|
13
|
+
import { laneGlyphs } from '../renderer/lane-graph/glyphs.ts';
|
|
14
|
+
import { beadArgument, beadName, cellText, formatBeadTime } from '../renderer/lane-graph/bead.ts';
|
|
15
|
+
import { paintBeadRow } from '../renderer/lane-graph/paint.ts';
|
|
16
|
+
import { buildTurnModel, orphanResultBead, transcriptUnits, type TranscriptUnit, type TurnModel } from './work-tree-model.ts';
|
|
17
|
+
import { renderSingleBead, renderTurn, type TurnRender } from './work-tree-render.ts';
|
|
39
18
|
// SystemMessageKind imported from runtime directly to avoid cycle:
|
|
40
19
|
// conversation-rendering.ts → system-message-router.ts → conversation.ts → conversation-rendering.ts
|
|
41
20
|
import type { SystemMessageKind } from '@/runtime/index.ts';
|
|
@@ -67,13 +46,13 @@ type Message = ConversationMessageSnapshot;
|
|
|
67
46
|
export {
|
|
68
47
|
collectCompletedToolCallIds,
|
|
69
48
|
collectToolCallOutcomes,
|
|
70
|
-
isTurnCollapsed,
|
|
71
49
|
type ConversationRenderContext,
|
|
72
50
|
type ToolCallOutcome,
|
|
73
51
|
} from './conversation-render-context.ts';
|
|
74
52
|
|
|
75
|
-
|
|
76
|
-
|
|
53
|
+
/** Whether a system message at `msgIdx` is navigable for error navigation. */
|
|
54
|
+
export function isNavigableSystemMessage(context: Pick<ConversationRenderContext, 'messageKindRegistry'>, msgIdx: number): boolean {
|
|
55
|
+
return NAVIGABLE_KINDS.has(context.messageKindRegistry.get(msgIdx) ?? 'system');
|
|
77
56
|
}
|
|
78
57
|
|
|
79
58
|
export function renderConversationUserMessage(
|
|
@@ -85,7 +64,7 @@ export function renderConversationUserMessage(
|
|
|
85
64
|
const T = activeTheme();
|
|
86
65
|
const displayText = extractUserDisplayText(message.content);
|
|
87
66
|
if (message.cancelled) {
|
|
88
|
-
context.history.addLines(UIFactory.createMessageBar(width, displayText, T.errorBarBg,
|
|
67
|
+
context.history.addLines(UIFactory.createMessageBar(width, displayText, T.errorBarBg, activeTokens().textMuted, activeTokens().error, true));
|
|
89
68
|
return;
|
|
90
69
|
}
|
|
91
70
|
// Compaction-continuation handoff: a user-ROLE message the compactor
|
|
@@ -100,246 +79,6 @@ export function renderConversationUserMessage(
|
|
|
100
79
|
context.history.addLines(UIFactory.createMessageBar(width, displayText));
|
|
101
80
|
}
|
|
102
81
|
|
|
103
|
-
export function renderConversationAssistantMessage(
|
|
104
|
-
context: ConversationRenderContext,
|
|
105
|
-
message: Extract<Message, { role: 'assistant' }>,
|
|
106
|
-
width: number,
|
|
107
|
-
lineNumberMode: 'all' | 'code' | 'off',
|
|
108
|
-
collapseThreshold: number,
|
|
109
|
-
msgIdx: number,
|
|
110
|
-
): void {
|
|
111
|
-
const T = activeTheme();
|
|
112
|
-
const turn = context.assistantTurns?.get(msgIdx);
|
|
113
|
-
// No membership (a standalone render, e.g. a golden-frame harness) behaves
|
|
114
|
-
// exactly as it always did: the message owns its own header.
|
|
115
|
-
const isHead = turn?.isHead ?? true;
|
|
116
|
-
const turnCollapsed = isTurnCollapsed(turn, context.collapseState);
|
|
117
|
-
|
|
118
|
-
if (isHead) {
|
|
119
|
-
const assistantHeaderDetails = [];
|
|
120
|
-
if (message.model) {
|
|
121
|
-
assistantHeaderDetails.push({ text: ` ${message.model}${message.provider ? ` (${message.provider})` : ''} `, fg: T.modelNameDim, dim: true });
|
|
122
|
-
}
|
|
123
|
-
// The count spans the whole run, not just this message, that is the point
|
|
124
|
-
// of merging. `tools:1` repeated over five headers becomes one `5 tools`.
|
|
125
|
-
const toolCount = turn?.toolCallCount ?? message.toolCalls?.length ?? 0;
|
|
126
|
-
if (toolCount > 0) {
|
|
127
|
-
assistantHeaderDetails.push({ text: ` ${GLYPHS.status.pending} ${toolCount} tool${toolCount === 1 ? '' : 's'} `, fg: T.toolAccent });
|
|
128
|
-
}
|
|
129
|
-
// A label every call in the run shares belongs here, once, instead of on
|
|
130
|
-
// every branch, the branches then lead with what distinguishes them.
|
|
131
|
-
//
|
|
132
|
-
// Hoisted ONLY when it fits whole. A label chopped mid-word ("Calling the
|
|
133
|
-
// assistant serv") is worse than no label: it costs a third of the header
|
|
134
|
-
// and tells you less than the rows below it already do, since each row
|
|
135
|
-
// leads with its own distinguishing token. So it either fits or it goes,
|
|
136
|
-
// never a truncated stub. Dropping it loses nothing reachable: the rows
|
|
137
|
-
// still name every call, and omitToolName is unaffected either way.
|
|
138
|
-
if (turn?.sharedToolLabel) {
|
|
139
|
-
const usedCols = assistantHeaderDetails.reduce((sum, seg) => sum + getDisplayWidth(seg.text), 0);
|
|
140
|
-
const availCols = width - LAYOUT.RIGHT_MARGIN - (LAYOUT.LEFT_MARGIN + 1)
|
|
141
|
-
- getDisplayWidth(' assistant ') - usedCols;
|
|
142
|
-
const labelText = ` ${turn.sharedToolLabel} `;
|
|
143
|
-
if (getDisplayWidth(labelText) <= availCols) {
|
|
144
|
-
assistantHeaderDetails.push({ text: labelText, fg: T.toolNameFg });
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
// Aggregated across the run so suppressing a non-head's header never loses
|
|
148
|
-
// the signal that reasoning happened.
|
|
149
|
-
const hasReasoning = turn?.hasReasoning ?? Boolean(message.reasoningContent || message.reasoningSummary);
|
|
150
|
-
if (hasReasoning) {
|
|
151
|
-
assistantHeaderDetails.push({ text: ` ${GLYPHS.status.active} reasoning `, fg: T.reasoningAccent, dim: true });
|
|
152
|
-
}
|
|
153
|
-
if (turnCollapsed && toolCount > 0) {
|
|
154
|
-
assistantHeaderDetails.push({ text: ` ${GLYPHS.navigation.collapsed} hidden `, fg: '244', dim: true });
|
|
155
|
-
}
|
|
156
|
-
// An empty run, no model, no tools, no reasoning, emits no header rather
|
|
157
|
-
// than a bare `● assistant` with nothing under it.
|
|
158
|
-
if (assistantHeaderDetails.length > 0) {
|
|
159
|
-
const headerStartLine = context.history.getLineCount();
|
|
160
|
-
const headerBlockIdx = context.blockRegistry.length;
|
|
161
|
-
context.history.addLine(renderConversationEventLine(width, {
|
|
162
|
-
marker: GLYPHS.status.active,
|
|
163
|
-
markerFg: T.assistantHeader,
|
|
164
|
-
label: 'assistant',
|
|
165
|
-
labelFg: T.assistantHeader,
|
|
166
|
-
detailFg: '244',
|
|
167
|
-
}, assistantHeaderDetails));
|
|
168
|
-
|
|
169
|
-
// Turns are collapsible as a unit and default to EXPANDED, a turn
|
|
170
|
-
// collapsed by default would hide the activity the transcript exists to
|
|
171
|
-
// show. Registered only when there is machinery to hide.
|
|
172
|
-
if (turn && toolCount > 0) {
|
|
173
|
-
if (!context.collapseState.has(turn.turnKey)) {
|
|
174
|
-
context.collapseState.set(turn.turnKey, false);
|
|
175
|
-
}
|
|
176
|
-
context.blockRegistry.push({
|
|
177
|
-
blockIndex: headerBlockIdx,
|
|
178
|
-
collapseKey: turn.turnKey,
|
|
179
|
-
type: 'assistant_turn',
|
|
180
|
-
startLine: headerStartLine,
|
|
181
|
-
lineCount: 1,
|
|
182
|
-
rawContent: `assistant turn: ${toolCount} tool call${toolCount === 1 ? '' : 's'}${turn.sharedToolLabel ? ` (${turn.sharedToolLabel})` : ''}`,
|
|
183
|
-
groupMemberIndexes: turn.resultIndexes,
|
|
184
|
-
toolName: turn.sharedToolLabel,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// A collapsed turn hides its machinery (tool rows, reasoning) but never its
|
|
191
|
-
// prose: the prose IS the answer, and hiding it would make collapse
|
|
192
|
-
// destructive rather than tidying.
|
|
193
|
-
if (turnCollapsed) {
|
|
194
|
-
if (message.content) renderAssistantProse(context, message, width, lineNumberMode, collapseThreshold, msgIdx);
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const showThinking = context.configManager?.get('display.showThinking') ?? false;
|
|
199
|
-
const showReasoningSummary = context.configManager?.get('display.showReasoningSummary') ?? false;
|
|
200
|
-
if (showThinking && message.reasoningContent) {
|
|
201
|
-
const thinkingStartLine = context.history.getLineCount();
|
|
202
|
-
const thinkingBlockIdx = context.blockRegistry.length;
|
|
203
|
-
const thinkingCollapseKey = `msg_${msgIdx}_thinking`;
|
|
204
|
-
// Collapsed by default, like every other collapsible block, Tab (or the
|
|
205
|
-
// block-actions menu) expands it via the same collapseKey the BlockMeta
|
|
206
|
-
// below registers. Previously this key was registered but never
|
|
207
|
-
// consulted: the toggle existed but did nothing.
|
|
208
|
-
const isThinkingCollapsed = context.collapseState.has(thinkingCollapseKey)
|
|
209
|
-
? context.collapseState.get(thinkingCollapseKey)!
|
|
210
|
-
: true;
|
|
211
|
-
if (!context.collapseState.has(thinkingCollapseKey)) {
|
|
212
|
-
context.collapseState.set(thinkingCollapseKey, true);
|
|
213
|
-
}
|
|
214
|
-
if (isThinkingCollapsed) {
|
|
215
|
-
// One row: label plus size badge, no frame and no interior line.
|
|
216
|
-
//
|
|
217
|
-
// Deliberately NO preview, unlike a folded tool result. Reasoning text
|
|
218
|
-
// stays behind the toggle until it is asked for, the fold is the quiet
|
|
219
|
-
// state of an already-opt-in display, and previewing the first reasoning
|
|
220
|
-
// line here would disclose content the collapsed form has never shown.
|
|
221
|
-
const thinkingLineCount = message.reasoningContent.split('\n').length;
|
|
222
|
-
context.history.addLine(renderConversationFoldedRow(width, {
|
|
223
|
-
// The same ▌ the EXPANDED block draws (BORDERS.THINKING), so folding
|
|
224
|
-
// changes the row count without changing what the block reads as.
|
|
225
|
-
marker: BORDERS.THINKING.char,
|
|
226
|
-
markerFg: T.reasoningAccent,
|
|
227
|
-
label: 'thinking',
|
|
228
|
-
labelFg: T.reasoningAccent,
|
|
229
|
-
detailFg: '244',
|
|
230
|
-
}, [
|
|
231
|
-
{ text: ` ${GLYPHS.navigation.collapsed} ${thinkingLineCount} line${thinkingLineCount === 1 ? '' : 's'} `, fg: '244', dim: true },
|
|
232
|
-
], ''));
|
|
233
|
-
} else {
|
|
234
|
-
const thinkingLines = renderThinkingBlock(message.reasoningContent, width);
|
|
235
|
-
context.history.addLines(thinkingLines);
|
|
236
|
-
}
|
|
237
|
-
context.history.addLine(createEmptyLine(width));
|
|
238
|
-
const thinkingRenderedLines = context.history.getLineCount() - thinkingStartLine;
|
|
239
|
-
context.blockRegistry.push({
|
|
240
|
-
blockIndex: thinkingBlockIdx,
|
|
241
|
-
collapseKey: thinkingCollapseKey,
|
|
242
|
-
type: 'thinking',
|
|
243
|
-
startLine: thinkingStartLine,
|
|
244
|
-
lineCount: thinkingRenderedLines,
|
|
245
|
-
rawContent: message.reasoningContent,
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
if (showReasoningSummary && message.reasoningSummary) {
|
|
249
|
-
const summaryLines = renderThinkingBlock(message.reasoningSummary, width);
|
|
250
|
-
context.history.addLines(summaryLines);
|
|
251
|
-
context.history.addLine(createEmptyLine(width));
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (message.content) renderAssistantProse(context, message, width, lineNumberMode, collapseThreshold, msgIdx);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Assistant prose, the model's actual answer, at full prominence and full
|
|
259
|
-
* width.
|
|
260
|
-
*
|
|
261
|
-
* Prose is never drawn as a tree branch. It is what closes a group (see
|
|
262
|
-
* startsNewTurn) and the content the tree exists to surround, not a child of
|
|
263
|
-
* it; indenting it would also re-wrap markdown at a reduced width, which is
|
|
264
|
-
* the table-drops-columns hazard.
|
|
265
|
-
*/
|
|
266
|
-
function renderAssistantProse(
|
|
267
|
-
context: ConversationRenderContext,
|
|
268
|
-
message: Extract<Message, { role: 'assistant' }>,
|
|
269
|
-
width: number,
|
|
270
|
-
lineNumberMode: 'all' | 'code' | 'off',
|
|
271
|
-
collapseThreshold: number,
|
|
272
|
-
msgIdx: number,
|
|
273
|
-
): void {
|
|
274
|
-
{
|
|
275
|
-
const showAllLineNumbers = lineNumberMode === 'all';
|
|
276
|
-
const showCodeBlockLineNumbers = lineNumberMode === 'all' ? false : lineNumberMode === 'code';
|
|
277
|
-
// NOTE: The 'all' mode intentionally calls renderMarkdownTracked twice:
|
|
278
|
-
// 1. Measure pass: render at full `width` to get the total line count, which
|
|
279
|
-
// determines `numWidth` (digit count) and thus `gutterW` (gutter column width).
|
|
280
|
-
// 2. Render pass: render at `width - gutterW` with the gutter factored in.
|
|
281
|
-
//
|
|
282
|
-
// Single-pass is not pursued here. It would require either a pessimistic
|
|
283
|
-
// `numWidth=6` (fits 999,999 lines, but wastes 3-4 gutter columns on typical
|
|
284
|
-
// messages) or rendering the numbered output into a scratch buffer and trimming.
|
|
285
|
-
// Neither is clearly better than the current two-pass measurement approach.
|
|
286
|
-
// The 4α commit message claim that this "eliminates double-parse when line
|
|
287
|
-
// numbers are enabled" was inaccurate: 4α eliminated the legacy
|
|
288
|
-
// `renderMarkdown()` duplicate used for code-block line-number mode ('code').
|
|
289
|
-
// The 'all' mode double-call is a deliberate design choice and remains unchanged.
|
|
290
|
-
const measureWidth = showAllLineNumbers ? width : 0;
|
|
291
|
-
const totalLines = showAllLineNumbers
|
|
292
|
-
? renderMarkdownTracked(message.content, measureWidth, { codeBlockLineNumbers: false }).lines.length
|
|
293
|
-
: 0;
|
|
294
|
-
const numWidth = Math.max(3, String(totalLines).length);
|
|
295
|
-
const gutterW = numWidth + 3;
|
|
296
|
-
const contentWidth = showAllLineNumbers ? width - gutterW : width;
|
|
297
|
-
const renderWidth = showAllLineNumbers ? contentWidth : width;
|
|
298
|
-
|
|
299
|
-
const { lines: tracked, codeBlocks } = renderMarkdownTracked(message.content, renderWidth, {
|
|
300
|
-
codeBlockLineNumbers: showCodeBlockLineNumbers,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
const msgBaseLineOffset = context.history.getLineCount();
|
|
304
|
-
for (const cb of codeBlocks) {
|
|
305
|
-
const blockStartLine = msgBaseLineOffset + cb.startOffset;
|
|
306
|
-
const blockIdx = context.blockRegistry.length;
|
|
307
|
-
const collapseKey = `code_${msgIdx}_${blockIdx}`;
|
|
308
|
-
const isAutoCollapsed = cb.rawContent.split('\n').length > collapseThreshold;
|
|
309
|
-
if (isAutoCollapsed && !context.collapseState.has(collapseKey)) {
|
|
310
|
-
context.collapseState.set(collapseKey, true);
|
|
311
|
-
}
|
|
312
|
-
context.blockRegistry.push({
|
|
313
|
-
blockIndex: blockIdx,
|
|
314
|
-
collapseKey,
|
|
315
|
-
type: 'code',
|
|
316
|
-
startLine: blockStartLine,
|
|
317
|
-
lineCount: cb.lineCount,
|
|
318
|
-
rawContent: cb.rawContent,
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (showAllLineNumbers) {
|
|
323
|
-
const numbered = tracked.map((line, i) => {
|
|
324
|
-
const label = String(i + 1).padStart(numWidth) + ' │ ';
|
|
325
|
-
const gutterCells = UIFactory.stringToLine(label, gutterW, { fg: '238', dim: true });
|
|
326
|
-
const fullLine = createEmptyLine(width);
|
|
327
|
-
for (let ci = 0; ci < gutterW && ci < gutterCells.length; ci++) {
|
|
328
|
-
fullLine[ci] = gutterCells[ci];
|
|
329
|
-
}
|
|
330
|
-
for (let ci = 0; ci < line.length && gutterW + ci < width; ci++) {
|
|
331
|
-
fullLine[gutterW + ci] = line[ci];
|
|
332
|
-
}
|
|
333
|
-
return fullLine;
|
|
334
|
-
});
|
|
335
|
-
context.history.addLines(numbered);
|
|
336
|
-
} else {
|
|
337
|
-
context.history.addLines(tracked);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
}
|
|
342
|
-
|
|
343
82
|
export function renderConversationSystemMessage(
|
|
344
83
|
context: ConversationRenderContext,
|
|
345
84
|
message: Extract<Message, { role: 'system' }>,
|
|
@@ -357,244 +96,91 @@ export function renderConversationSystemMessage(
|
|
|
357
96
|
}
|
|
358
97
|
}
|
|
359
98
|
|
|
360
|
-
/**
|
|
361
|
-
|
|
362
|
-
*
|
|
363
|
-
* The call row is its own plan node rather than part of the assistant
|
|
364
|
-
* message's render, which is what lets a result be interleaved BETWEEN two
|
|
365
|
-
* calls of the same message, the ordering guarantee that makes a
|
|
366
|
-
* late-finishing call's result land inside its own subtree instead of after a
|
|
367
|
-
* call issued later.
|
|
368
|
-
*/
|
|
369
|
-
export function renderConversationToolCallNode(
|
|
99
|
+
/** Build one assistant turn's work-tree model from the render context. */
|
|
100
|
+
export function buildConversationTurnModel(
|
|
370
101
|
context: ConversationRenderContext,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
const ran = context.completedToolCallIds === undefined
|
|
388
|
-
|| (call.id !== undefined && context.completedToolCallIds.has(call.id));
|
|
389
|
-
const status: 'done' | 'error' | 'cancelled' | 'pending' = outcome === 'error'
|
|
390
|
-
? 'error'
|
|
391
|
-
: outcome === 'cancelled'
|
|
392
|
-
? 'cancelled'
|
|
393
|
-
: outcome === 'ok'
|
|
394
|
-
? 'done'
|
|
395
|
-
: ran ? 'done' : 'pending';
|
|
396
|
-
const indent = treeIndentCols(node.depth, width);
|
|
397
|
-
const lines = renderToolCallBlock(
|
|
398
|
-
call,
|
|
399
|
-
status,
|
|
400
|
-
undefined,
|
|
401
|
-
width,
|
|
402
|
-
undefined,
|
|
403
|
-
undefined,
|
|
404
|
-
undefined,
|
|
405
|
-
undefined,
|
|
406
|
-
{ indentCols: indent, omitToolName: turn?.sharedToolLabel !== undefined },
|
|
407
|
-
);
|
|
408
|
-
|
|
409
|
-
// Recursion stopped here, say so rather than silently showing a subtree as
|
|
410
|
-
// if it were a leaf.
|
|
411
|
-
if (node.truncated) {
|
|
412
|
-
const noteIndent = treeIndentCols(node.depth + 1, width);
|
|
413
|
-
lines.push(renderConversationEventLine(width, {
|
|
414
|
-
marker: GLYPHS.navigation.collapsed,
|
|
415
|
-
markerFg: '244',
|
|
416
|
-
label: '',
|
|
417
|
-
labelFg: '244',
|
|
418
|
-
detailFg: '244',
|
|
419
|
-
}, [{
|
|
420
|
-
text: node.truncated === 'depth'
|
|
421
|
-
? ` nested activity continues below depth ${MAX_NEST_DEPTH} `
|
|
422
|
-
: ' nested activity repeats an agent already shown above ',
|
|
423
|
-
fg: '244',
|
|
424
|
-
dim: true,
|
|
425
|
-
}], noteIndent));
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
// Rails last, over every line the row emitted, so the vertical run down to
|
|
429
|
-
// the next sibling has no gap in it.
|
|
430
|
-
drawTreeRails(lines, node.depth, node.connector, node.openAncestorDepths, width, T.toolAccent);
|
|
431
|
-
context.history.addLines(lines);
|
|
102
|
+
messages: readonly Message[],
|
|
103
|
+
offset: number,
|
|
104
|
+
unit: Extract<TranscriptUnit, { kind: 'turn' }>,
|
|
105
|
+
streamingIndex = -1,
|
|
106
|
+
): TurnModel {
|
|
107
|
+
const config = context.configManager;
|
|
108
|
+
return buildTurnModel({
|
|
109
|
+
messages,
|
|
110
|
+
offset,
|
|
111
|
+
unit,
|
|
112
|
+
sources: context.workTreeSources ?? {},
|
|
113
|
+
collapse: context.collapseState,
|
|
114
|
+
streamingIndex,
|
|
115
|
+
showThinking: config?.get('display.showThinking') ?? false,
|
|
116
|
+
showReasoningSummary: config?.get('display.showReasoningSummary') ?? false,
|
|
117
|
+
});
|
|
432
118
|
}
|
|
433
119
|
|
|
434
120
|
/**
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
121
|
+
* Draw a turn model (lines, blocks and error lines relative to the turn), then
|
|
122
|
+
* the notices that arrived during it (never in the main transcript, whose
|
|
123
|
+
* notices are toasts and notification-history entries: systemNotices). A notice is not a step of the turn, so it
|
|
124
|
+
* is drawn after the turn and outside its lanes, exactly as a notice between
|
|
125
|
+
* turns is (the concept's turns hold only the turn's own text, beads, lanes and
|
|
126
|
+
* answer; agent and WRFC state is the lanes' own spawn and merge rows).
|
|
439
127
|
*/
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
128
|
+
export function drawConversationTurn(context: ConversationRenderContext, model: TurnModel, width: number): TurnRender {
|
|
129
|
+
const render = drawTurnRows(context, model, width);
|
|
130
|
+
if (model.notices.length === 0 || context.systemNotices === 'elsewhere') return render;
|
|
131
|
+
const lines = [...render.lines];
|
|
132
|
+
const errorLines = [...render.errorLines];
|
|
133
|
+
const messageLines = new Map(render.messageLines);
|
|
134
|
+
for (const notice of model.notices) {
|
|
135
|
+
lines.push(createEmptyLine(width));
|
|
136
|
+
messageLines.set(notice.messageIndex, lines.length);
|
|
137
|
+
if (isNavigableSystemMessage(context, notice.messageIndex)) errorLines.push(lines.length);
|
|
138
|
+
lines.push(...renderSystemMessage(notice.content, width));
|
|
447
139
|
}
|
|
448
|
-
return
|
|
140
|
+
return { ...render, lines, errorLines, messageLines };
|
|
449
141
|
}
|
|
450
142
|
|
|
451
|
-
|
|
143
|
+
function drawTurnRows(context: ConversationRenderContext, model: TurnModel, width: number): TurnRender {
|
|
144
|
+
const config = context.configManager;
|
|
145
|
+
return renderTurn(model, {
|
|
146
|
+
width,
|
|
147
|
+
glyphSet: context.treeGlyphSet ?? 'rounded',
|
|
148
|
+
focusId: context.focusId ?? null,
|
|
149
|
+
lineNumberMode: config?.get('display.lineNumbers') ?? 'off',
|
|
150
|
+
collapseThreshold: config?.get('display.collapseThreshold') ?? 30,
|
|
151
|
+
collapseState: context.collapseState,
|
|
152
|
+
isNavigableSystem: (index) => isNavigableSystemMessage(context, index),
|
|
153
|
+
frame: context.frame ?? 0,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Render one assistant turn through the work tree (work-tree-model.ts builds
|
|
159
|
+
* its rows, work-tree-render.ts draws them) into the context's history.
|
|
160
|
+
*/
|
|
161
|
+
export function renderConversationTurn(
|
|
452
162
|
context: ConversationRenderContext,
|
|
453
|
-
|
|
163
|
+
messages: readonly Message[],
|
|
164
|
+
offset: number,
|
|
165
|
+
unit: Extract<TranscriptUnit, { kind: 'turn' }>,
|
|
454
166
|
width: number,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
)
|
|
458
|
-
const
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
if (isTurnCollapsed(turn, context.collapseState)) return;
|
|
464
|
-
|
|
465
|
-
const collapseKey = node ? collapseKeyForNode(node) : `msg_${msgIdx}`;
|
|
466
|
-
const blockIdx = context.blockRegistry.length;
|
|
467
|
-
const startLine = context.history.getLineCount();
|
|
468
|
-
const contentLines = message.content.split('\n');
|
|
469
|
-
const isDiff = isDiffContent(message.content);
|
|
470
|
-
const blockType: 'diff' | 'tool' = isDiff ? 'diff' : 'tool';
|
|
471
|
-
// Parsed once, ahead of the collapse check, so it's available for the
|
|
472
|
-
// block-registry meta merge below regardless of collapsed/expanded state.
|
|
473
|
-
const diffParse = isDiff ? parseDiffForApply(message.content) : undefined;
|
|
474
|
-
const inTree = depth > 0 && indent > 0;
|
|
475
|
-
// The EXPANDED render, computed unconditionally (even while collapsed) so
|
|
476
|
-
// the header's "N lines" badge always names what Tab would actually reveal
|
|
477
|
-
//, a raw JSON blob that pretty-prints to 50 lines must say 50, not 1, even
|
|
478
|
-
// while it's still folded. Reused below as the actual body when expanded,
|
|
479
|
-
// so this is never rendered twice.
|
|
480
|
-
//
|
|
481
|
-
// In the tree it is rendered at the width it will actually OCCUPY (the row's
|
|
482
|
-
// width less its indent) and then shifted into place, so the badge still
|
|
483
|
-
// counts exactly the lines expansion produces. Rendering it full-width and
|
|
484
|
-
// displaying it indented would desynchronise the two, the
|
|
485
|
-
// markdown-table-drops-columns bug class, and rendering it full-width and
|
|
486
|
-
// displaying it flush punches the row's rails out for the whole body.
|
|
487
|
-
// The body is a continuation of its row, so it starts at the row's own text
|
|
488
|
-
// column; the shift is that column expressed relative to the flush left
|
|
489
|
-
// margin the body renders with internally.
|
|
490
|
-
const bodyShift = inTree ? Math.max(0, treeTextCol(indent) - LAYOUT.LEFT_MARGIN) : 0;
|
|
491
|
-
const bodyWidth = Math.max(LAYOUT.LEFT_MARGIN + LAYOUT.RIGHT_MARGIN + 8, width - bodyShift);
|
|
492
|
-
const expandedLines = renderExpandedToolResultLines(message.content, bodyWidth);
|
|
493
|
-
const lineCount = expandedLines.length;
|
|
494
|
-
|
|
495
|
-
// Human one-line summary for tool results (write/read/exec/edit): shown as the
|
|
496
|
-
// collapsed line so the transcript reads "wrote foo.txt (532 B)" instead of a
|
|
497
|
-
// raw JSON blob; the full payload stays behind the expand toggle. Only for
|
|
498
|
-
// 'tool' blocks (diffs render their own view). (item 3.)
|
|
499
|
-
const resultSummary = blockType === 'tool'
|
|
500
|
-
? summarizeToolResult(message.toolName, message.content)
|
|
501
|
-
: null;
|
|
502
|
-
|
|
503
|
-
const isCollapsed = isToolResultFolded(message, context.collapseState, collapseKey);
|
|
504
|
-
|
|
505
|
-
// The fold decision IS the stored default: with nothing stored,
|
|
506
|
-
// isToolResultFolded() already resolved to exactly the value this key should
|
|
507
|
-
// hold. Recomputing "is it short?" here would be a second copy of the
|
|
508
|
-
// threshold that the policy module owns.
|
|
509
|
-
if (!context.collapseState.has(collapseKey)) {
|
|
510
|
-
context.collapseState.set(collapseKey, isCollapsed);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// A per-call user cancellation settles as a tool result whose content leads
|
|
514
|
-
// with "Error: cancelled by user" (the SDK's structured cancelled shape; any
|
|
515
|
-
// partial output the tool produced before it stopped follows on later lines).
|
|
516
|
-
// Render it structurally as a distinct "cancelled" block in the warn tone,
|
|
517
|
-
// not a generic tool result, and not a hard error, so the transcript reads
|
|
518
|
-
// the user's decision honestly while the partial output stays visible below.
|
|
519
|
-
const isCancelled = blockType === 'tool' && /^Error: cancelled by user\b/.test(contentLines[0] ?? '');
|
|
520
|
-
const warnTone = activeUiTones().chrome.warn;
|
|
521
|
-
|
|
522
|
-
// In the tree a result hangs under the call that produced it, so repeating
|
|
523
|
-
// the generic label ("tool result") and the tool's own name, which the
|
|
524
|
-
// parent call row already shows, is exactly the boilerplate this layout
|
|
525
|
-
// removes. The row leads with its size badge instead. `diff` and `cancelled`
|
|
526
|
-
// keep their labels: those carry information the parent row does not.
|
|
527
|
-
const label = isCancelled ? 'cancelled' : (blockType === 'diff' ? 'diff' : (inTree ? '' : 'tool result'));
|
|
528
|
-
const nameSegments = inTree
|
|
529
|
-
? []
|
|
530
|
-
: (message.toolName
|
|
531
|
-
? [{ text: ` ${message.toolName} `, fg: T.toolNameFg }]
|
|
532
|
-
: [{ text: ` ${summarizeCallId(message.callId || 'standalone')} `, fg: '244' as const, dim: true }]);
|
|
533
|
-
|
|
534
|
-
// A FOLDED result is exactly one row: this header, with its preview riding on
|
|
535
|
-
// the same line right after the `▸ N lines` badge (see
|
|
536
|
-
// renderConversationFoldedRow). The badge is the count, the old fold also
|
|
537
|
-
// carried a separate `[▸ N hidden]` marker, which restated it.
|
|
538
|
-
const headerTone = {
|
|
539
|
-
marker: isCancelled ? GLYPHS.status.blocked : (blockType === 'diff' ? GLYPHS.status.dualPane : GLYPHS.status.active),
|
|
540
|
-
markerFg: isCancelled ? warnTone : (blockType === 'diff' ? T.diffAccent : T.toolAccent),
|
|
541
|
-
label,
|
|
542
|
-
labelFg: isCancelled ? warnTone : (blockType === 'diff' ? T.diffAccent : T.toolAccent),
|
|
543
|
-
detailFg: '244',
|
|
544
|
-
};
|
|
545
|
-
const headerDetails = [
|
|
546
|
-
...nameSegments,
|
|
547
|
-
{ text: ` ${isCollapsed ? GLYPHS.navigation.collapsed : GLYPHS.navigation.expanded} ${lineCount} line${lineCount === 1 ? '' : 's'} `, fg: '244', dim: true },
|
|
548
|
-
];
|
|
549
|
-
const headerLine = isCollapsed
|
|
550
|
-
? renderConversationFoldedRow(width, headerTone, headerDetails, resultSummary ?? contentLines[0] ?? '', indent)
|
|
551
|
-
: renderConversationEventLine(width, headerTone, headerDetails, indent);
|
|
552
|
-
// Every line this row emits, gathered before anything is committed, so the
|
|
553
|
-
// rails can be drawn across all of them in one pass (see drawTreeRails). The
|
|
554
|
-
// result row deliberately carries NO status marker of its own: the call row
|
|
555
|
-
// directly above it already states the outcome in the shared bullet column,
|
|
556
|
-
// and a second marker one row down only doubles it.
|
|
557
|
-
const rows: Line[] = [headerLine];
|
|
558
|
-
|
|
559
|
-
if (!isCollapsed) {
|
|
560
|
-
// Diff or plain result, either way this is exactly `expandedLines`,
|
|
561
|
-
// already computed above (and already what the header's line count is
|
|
562
|
-
// honest about), so there is nothing left to render here.
|
|
563
|
-
//
|
|
564
|
-
// Deliberately NOT indented. renderExpandedToolResultLines is the single
|
|
565
|
-
// source of truth for the "N lines" badge above; re-wrapping it at a
|
|
566
|
-
// reduced width would desynchronise that count from what expansion
|
|
567
|
-
// actually reveals, the markdown-table-drops-columns bug class. The body
|
|
568
|
-
// is already visually bound to its row by the header directly above it.
|
|
569
|
-
// In the tree it was rendered at `bodyWidth` above and is shifted into the
|
|
570
|
-
// row's indent here, which is what leaves the rail columns to its left free
|
|
571
|
-
// for the rails to run through.
|
|
572
|
-
for (const line of expandedLines) rows.push(shiftLineRight(line, bodyShift, width));
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
if (node && inTree) {
|
|
576
|
-
drawTreeRails(rows, node.depth, node.connector, node.openAncestorDepths, width, T.toolAccent);
|
|
577
|
-
}
|
|
578
|
-
context.history.addLines(rows);
|
|
579
|
-
|
|
580
|
-
const renderedLineCount = context.history.getLineCount() - startLine;
|
|
581
|
-
let meta: BlockMeta = {
|
|
582
|
-
blockIndex: blockIdx,
|
|
583
|
-
collapseKey,
|
|
584
|
-
type: blockType,
|
|
585
|
-
startLine,
|
|
586
|
-
lineCount: renderedLineCount,
|
|
587
|
-
rawContent: message.content,
|
|
588
|
-
toolName: message.toolName,
|
|
589
|
-
};
|
|
590
|
-
|
|
591
|
-
if (isDiff && diffParse) {
|
|
592
|
-
meta = { ...meta, ...diffParse };
|
|
167
|
+
streamingIndex = -1,
|
|
168
|
+
): TurnRender {
|
|
169
|
+
const render = drawConversationTurn(context, buildConversationTurnModel(context, messages, offset, unit, streamingIndex), width);
|
|
170
|
+
const base = context.history.getLineCount();
|
|
171
|
+
const blockBase = context.blockRegistry.length;
|
|
172
|
+
context.history.addLines(render.lines);
|
|
173
|
+
for (const block of render.blocks) {
|
|
174
|
+
context.blockRegistry.push({ ...block, blockIndex: block.blockIndex + blockBase, startLine: block.startLine + base });
|
|
593
175
|
}
|
|
594
|
-
|
|
595
|
-
|
|
176
|
+
for (const line of render.errorLines) context.errorLineRegistry.push(line + base);
|
|
177
|
+
return render;
|
|
596
178
|
}
|
|
597
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Render a message slice: user and system messages as themselves, every
|
|
182
|
+
* assistant turn as its work tree, a blank row after each unit.
|
|
183
|
+
*/
|
|
598
184
|
export function appendConversationMessages(
|
|
599
185
|
context: ConversationRenderContext,
|
|
600
186
|
messages: Message[],
|
|
@@ -608,71 +194,49 @@ export function appendConversationMessages(
|
|
|
608
194
|
*/
|
|
609
195
|
msgIndexOffset = 0,
|
|
610
196
|
): void {
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
: (() => {
|
|
618
|
-
const toolCallOutcomes = collectToolCallOutcomes(messages);
|
|
619
|
-
return { ...context, toolCallOutcomes, completedToolCallIds: new Set(toolCallOutcomes.keys()) };
|
|
620
|
-
})();
|
|
621
|
-
const assistantTurns = renderContext.assistantTurns
|
|
622
|
-
?? computeAssistantTurns(messages, msgIndexOffset);
|
|
623
|
-
const turnContext: ConversationRenderContext = renderContext.assistantTurns !== undefined
|
|
624
|
-
? renderContext
|
|
625
|
-
: { ...renderContext, assistantTurns };
|
|
626
|
-
|
|
627
|
-
const plan = buildRenderPlan(messages, msgIndexOffset, {
|
|
628
|
-
resolveAgentSnapshot: context.resolveAgentSnapshot,
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
// Header line of each turn, so a row that renders nothing (hidden by a
|
|
632
|
-
// collapsed turn) still anchors transcript navigation at its turn rather
|
|
633
|
-
// than at whatever position the buffer happens to sit at.
|
|
634
|
-
const turnHeaderLines = new Map<string, number>();
|
|
635
|
-
|
|
636
|
-
for (let i = 0; i < plan.length; i++) {
|
|
637
|
-
const node = plan[i]!;
|
|
638
|
-
const before = turnContext.history.getLineCount();
|
|
639
|
-
|
|
640
|
-
if (node.kind === 'toolcall') {
|
|
641
|
-
renderConversationToolCallNode(turnContext, node, width);
|
|
197
|
+
for (const unit of transcriptUnits(messages, msgIndexOffset)) {
|
|
198
|
+
const before = context.history.getLineCount();
|
|
199
|
+
if (unit.kind === 'turn') {
|
|
200
|
+
const render = renderConversationTurn(context, messages, msgIndexOffset, unit, width);
|
|
201
|
+
for (const [index, line] of render.messageLines) messageLineRegistry[index] = before + line;
|
|
202
|
+
for (let index = unit.start; index <= unit.end; index++) messageLineRegistry[index] ??= before;
|
|
642
203
|
} else {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
// write the root transcript's line registry.
|
|
646
|
-
const isRoot = node.scope === '';
|
|
647
|
-
const turn = turnContext.assistantTurns?.get(node.absIdx);
|
|
648
|
-
if (turn?.isHead) turnHeaderLines.set(turn.turnKey, before);
|
|
649
|
-
if (isRoot) {
|
|
650
|
-
messageLineRegistry[node.absIdx] = turn && isTurnCollapsed(turn, turnContext.collapseState)
|
|
651
|
-
? (turnHeaderLines.get(turn.turnKey) ?? before)
|
|
652
|
-
: before;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
if (message.role === 'user') {
|
|
656
|
-
renderConversationUserMessage(turnContext, message, width, node.absIdx);
|
|
657
|
-
} else if (message.role === 'assistant') {
|
|
658
|
-
renderConversationAssistantMessage(turnContext, message, width, lineNumberMode, collapseThreshold, node.absIdx);
|
|
659
|
-
} else if (message.role === 'system') {
|
|
660
|
-
renderConversationSystemMessage(turnContext, message, width, node.absIdx);
|
|
661
|
-
} else if (message.role === 'tool') {
|
|
662
|
-
renderConversationToolMessage(turnContext, message, width, node.absIdx, node);
|
|
663
|
-
}
|
|
204
|
+
messageLineRegistry[unit.index] = before;
|
|
205
|
+
renderConversationMessageUnit(context, messages[unit.index - msgIndexOffset]!, width, unit.index);
|
|
664
206
|
}
|
|
207
|
+
if (context.history.getLineCount() > before) context.history.addLine(createEmptyLine(width));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
665
210
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
211
|
+
/** A standalone message unit (a user message, or a system message outside any turn). */
|
|
212
|
+
export function renderConversationMessageUnit(
|
|
213
|
+
context: ConversationRenderContext,
|
|
214
|
+
message: Message,
|
|
215
|
+
width: number,
|
|
216
|
+
msgIdx: number,
|
|
217
|
+
): void {
|
|
218
|
+
if (message.role === 'user') renderConversationUserMessage(context, message, width, msgIdx);
|
|
219
|
+
else if (message.role === 'system') {
|
|
220
|
+
// The main transcript's notices are toasts and history entries, not rows.
|
|
221
|
+
if (context.systemNotices !== 'elsewhere') renderConversationSystemMessage(context, message, width, msgIdx);
|
|
222
|
+
}
|
|
223
|
+
else if (message.role === 'tool') {
|
|
224
|
+
// A result whose call is outside the rendered slice (the display was
|
|
225
|
+
// cleared mid-turn): one bead of its own, with its block and body.
|
|
226
|
+
const render = renderSingleBead(orphanResultBead(message, msgIdx, context.collapseState), {
|
|
227
|
+
width,
|
|
228
|
+
glyphSet: context.treeGlyphSet ?? 'rounded',
|
|
229
|
+
focusId: context.focusId ?? null,
|
|
230
|
+
lineNumberMode: 'off',
|
|
231
|
+
collapseThreshold: 30,
|
|
232
|
+
collapseState: context.collapseState,
|
|
233
|
+
isNavigableSystem: () => false,
|
|
234
|
+
frame: context.frame ?? 0,
|
|
235
|
+
});
|
|
236
|
+
const base = context.history.getLineCount();
|
|
237
|
+
const blockBase = context.blockRegistry.length;
|
|
238
|
+
context.history.addLines(render.lines);
|
|
239
|
+
for (const block of render.blocks) context.blockRegistry.push({ ...block, blockIndex: blockBase + block.blockIndex, startLine: base + block.startLine });
|
|
676
240
|
}
|
|
677
241
|
}
|
|
678
242
|
|
|
@@ -681,9 +245,9 @@ export function addConversationSplashScreen(
|
|
|
681
245
|
width: number,
|
|
682
246
|
): void {
|
|
683
247
|
const splashStrings = getSplashLines(width, context.splashOptions);
|
|
684
|
-
|
|
685
|
-
const purple =
|
|
686
|
-
const
|
|
248
|
+
// The splash gradient is protected: it never follows the theme.
|
|
249
|
+
const { start: cyan, end: purple } = SPLASH_GRADIENT;
|
|
250
|
+
const versionFg = activeTokens().textFaint;
|
|
687
251
|
|
|
688
252
|
splashStrings.forEach((str, y) => {
|
|
689
253
|
const line = UIFactory.stringToLine(str, width);
|
|
@@ -695,8 +259,7 @@ export function addConversationSplashScreen(
|
|
|
695
259
|
const cell = line[x];
|
|
696
260
|
if (cell.char === ' ' && (x < startX || x >= endX)) continue;
|
|
697
261
|
if (isVersion) {
|
|
698
|
-
cell.fg =
|
|
699
|
-
cell.dim = true;
|
|
262
|
+
cell.fg = versionFg;
|
|
700
263
|
} else {
|
|
701
264
|
const factor = (x - startX) / (endX - startX || 1);
|
|
702
265
|
cell.fg = interpolateColor(cyan, purple, Math.max(0, Math.min(1, factor)));
|
|
@@ -705,9 +268,8 @@ export function addConversationSplashScreen(
|
|
|
705
268
|
}
|
|
706
269
|
context.history.addLine(line);
|
|
707
270
|
});
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
}
|
|
271
|
+
// No trailing blank rows: the shell centers the splash in whatever height
|
|
272
|
+
// the conversation area has (conversation-layout.ts centerViewportContent).
|
|
711
273
|
}
|
|
712
274
|
|
|
713
275
|
export function conversationTextToLines(
|
|
@@ -735,13 +297,12 @@ export function logConversationText(
|
|
|
735
297
|
}
|
|
736
298
|
|
|
737
299
|
/**
|
|
738
|
-
* logConversationToolResult - Append a
|
|
739
|
-
*
|
|
740
|
-
*
|
|
741
|
-
* model's own tool-call results. Display-only: never touches message history.
|
|
300
|
+
* logConversationToolResult - Append a display-only tool result (a slash
|
|
301
|
+
* command's subprocess result, e.g. /test) as one bead row, the same row a
|
|
302
|
+
* model's own call draws in the work tree. Never touches message history.
|
|
742
303
|
*/
|
|
743
304
|
export function logConversationToolResult(
|
|
744
|
-
context: Pick<ConversationRenderContext, 'history'>,
|
|
305
|
+
context: Pick<ConversationRenderContext, 'history' | 'treeGlyphSet'>,
|
|
745
306
|
width: number,
|
|
746
307
|
toolCall: ToolCall,
|
|
747
308
|
status: 'done' | 'error',
|
|
@@ -749,5 +310,26 @@ export function logConversationToolResult(
|
|
|
749
310
|
durationMs: number,
|
|
750
311
|
errorMsg?: string,
|
|
751
312
|
): void {
|
|
752
|
-
context
|
|
313
|
+
renderStandaloneBead(context, width, toolCall, status, errorMsg ? `${resultSummary ? `${resultSummary} · ` : ''}${errorMsg}` : resultSummary, durationMs);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function renderStandaloneBead(
|
|
317
|
+
context: Pick<ConversationRenderContext, 'history' | 'treeGlyphSet'>,
|
|
318
|
+
width: number,
|
|
319
|
+
toolCall: ToolCall,
|
|
320
|
+
status: 'done' | 'error',
|
|
321
|
+
summary: string,
|
|
322
|
+
durationMs: number | undefined,
|
|
323
|
+
): void {
|
|
324
|
+
const glyphs = laneGlyphs(context.treeGlyphSet ?? 'rounded');
|
|
325
|
+
context.history.addLine(paintBeadRow([{ role: 'bead', color: 0 }], {
|
|
326
|
+
status: status === 'error' ? 'err' : 'ok',
|
|
327
|
+
name: beadName(toolCall),
|
|
328
|
+
arg: beadArgument(toolCall),
|
|
329
|
+
summary: summary ? { text: cellText(summary), tone: status === 'error' ? 'bad' : 'faint' } : null,
|
|
330
|
+
time: durationMs !== undefined ? formatBeadTime(durationMs) : undefined,
|
|
331
|
+
laneColor: activeTokens().brand,
|
|
332
|
+
hasBody: false,
|
|
333
|
+
open: false,
|
|
334
|
+
}, { width, gutterWidth: 6, glyphs, frame: 0 }));
|
|
753
335
|
}
|