@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,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* work-tree-wiring.ts, connecting the conversation work tree to the live
|
|
3
|
+
* runtime: the timing store fed by tool and turn events, agent lanes read
|
|
4
|
+
* from the agent manager (WRFC chains from the chain controller, cost from
|
|
5
|
+
* the fleet read model), the call a permission prompt is holding, and the
|
|
6
|
+
* per-session fold state.
|
|
7
|
+
*
|
|
8
|
+
* One call from main.ts; everything the work tree reads at render time goes
|
|
9
|
+
* through the WorkTreeSources this installs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { AgentManager } from '@pellux/goodvibes-sdk/platform/tools';
|
|
13
|
+
import type { WrfcChain } from '@pellux/goodvibes-sdk/platform/agents';
|
|
14
|
+
import type { ProcessNode } from '@pellux/goodvibes-sdk/platform/runtime/fleet';
|
|
15
|
+
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
|
|
16
|
+
import type { UiRuntimeEvents } from '@/runtime/index.ts';
|
|
17
|
+
import type { ConversationManager } from './conversation.ts';
|
|
18
|
+
import { WorkTreeTimingStore, userMessageFingerprint, type AgentLaneInfo, type TurnOutcome, type WrfcPhaseInfo } from './work-tree-sources.ts';
|
|
19
|
+
import { loadWorkTreeFolds, loadWorkTreeTurnOutcomes, saveWorkTreeFolds, sweepOrphanWorkTreeFolds } from './work-tree-fold-store.ts';
|
|
20
|
+
import { onSemanticSummaryReady } from '../renderer/lane-graph/semantic-memo.ts';
|
|
21
|
+
import { onSyntaxHighlightReady } from '../renderer/code-block.ts';
|
|
22
|
+
|
|
23
|
+
type AgentRecord = ReturnType<AgentManager['list']>[number];
|
|
24
|
+
|
|
25
|
+
export interface WorkTreeWiringDeps {
|
|
26
|
+
readonly conversation: ConversationManager;
|
|
27
|
+
readonly events: UiRuntimeEvents;
|
|
28
|
+
readonly agentManager: Pick<AgentManager, 'getStatus' | 'getConversationSnapshot'>;
|
|
29
|
+
readonly listChains: () => readonly WrfcChain[];
|
|
30
|
+
readonly fleetNodes: () => readonly ProcessNode[];
|
|
31
|
+
/** The call id a permission prompt is holding, if any. */
|
|
32
|
+
readonly pendingCallId: () => string | undefined;
|
|
33
|
+
readonly turnActive: () => boolean;
|
|
34
|
+
readonly sessionsDir: string;
|
|
35
|
+
readonly sessionId: () => string;
|
|
36
|
+
/** Repaint (a ◈ summary of an opened edit landed). */
|
|
37
|
+
readonly requestRender: () => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function firstLines(text: string | undefined, n: number): string | undefined {
|
|
41
|
+
if (!text) return undefined;
|
|
42
|
+
const lines = text.split('\n').filter((l) => l.trim().length > 0).slice(0, n);
|
|
43
|
+
return lines.length > 0 ? lines.join('\n') : undefined;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A finished agent's transcript never changes: read it once. */
|
|
47
|
+
interface SnapshotMemo { readonly stamp: string; readonly messages: readonly ConversationMessageSnapshot[] }
|
|
48
|
+
|
|
49
|
+
export function wireWorkTree(deps: WorkTreeWiringDeps): { readonly timings: WorkTreeTimingStore; readonly unsubs: Array<() => void> } {
|
|
50
|
+
const timings = new WorkTreeTimingStore();
|
|
51
|
+
const unsubs: Array<() => void> = [];
|
|
52
|
+
const { conversation, events } = deps;
|
|
53
|
+
|
|
54
|
+
unsubs.push(events.tools.on('TOOL_EXECUTING', (ev) => timings.callStarted(ev.callId, ev.startedAt)));
|
|
55
|
+
unsubs.push(events.tools.on('TOOL_SUCCEEDED', (ev) => timings.callSettled(ev.callId, ev.durationMs, Date.now())));
|
|
56
|
+
unsubs.push(events.tools.on('TOOL_FAILED', (ev) => timings.callSettled(ev.callId, ev.durationMs, Date.now())));
|
|
57
|
+
unsubs.push(events.tools.on('TOOL_CANCELLED', (ev) => timings.callSettled(ev.callId, undefined, Date.now())));
|
|
58
|
+
// Everything the session sidecar keeps: fold decisions and failed/cancelled turn endings.
|
|
59
|
+
const persistView = (): void => saveWorkTreeFolds(deps.sessionsDir, deps.sessionId(), conversation.workTree.foldState(), conversation.workTree.turnOutcomes());
|
|
60
|
+
|
|
61
|
+
unsubs.push(events.turns.on('TURN_SUBMITTED', () => {
|
|
62
|
+
const snapshot = conversation.getMessageSnapshot();
|
|
63
|
+
for (let i = snapshot.length - 1; i >= 0; i--) {
|
|
64
|
+
if (snapshot[i]!.role === 'user') { timings.turnStarted(i, Date.now(), userMessageFingerprint(snapshot[i])); break; }
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
const endTurn = (outcome: TurnOutcome): void => {
|
|
68
|
+
const ended = timings.turnEnded(Date.now(), outcome);
|
|
69
|
+
// The header drops "working" and states a failure on the next build.
|
|
70
|
+
conversation.workTree.invalidate();
|
|
71
|
+
if (!ended || ended.timing.fingerprint === undefined) return;
|
|
72
|
+
if (conversation.workTree.recordTurnOutcome({ index: ended.index, fingerprint: ended.timing.fingerprint, outcome })) persistView();
|
|
73
|
+
};
|
|
74
|
+
unsubs.push(events.turns.on('TURN_COMPLETED', (ev) => endTurn(ev.stopReason === 'empty_response' ? 'failed' : 'completed')));
|
|
75
|
+
unsubs.push(events.turns.on('TURN_ERROR', () => endTurn('failed')));
|
|
76
|
+
unsubs.push(events.turns.on('TURN_CANCEL', () => endTurn('cancelled')));
|
|
77
|
+
unsubs.push(events.turns.on('PREFLIGHT_FAIL', () => endTurn('failed')));
|
|
78
|
+
// A new or resumed transcript: turn timings are keyed by message index and belong to the old one.
|
|
79
|
+
unsubs.push(conversation.workTree.onReset(() => timings.clear()));
|
|
80
|
+
|
|
81
|
+
const snapshots = new Map<string, SnapshotMemo>();
|
|
82
|
+
const messagesOf = (record: AgentRecord): readonly ConversationMessageSnapshot[] => {
|
|
83
|
+
const finished = record.status !== 'running' && record.status !== 'pending';
|
|
84
|
+
const stamp = `${record.status}:${record.completedAt ?? ''}:${record.toolCallCount}`;
|
|
85
|
+
const memo = snapshots.get(record.id);
|
|
86
|
+
if (finished && memo && memo.stamp === stamp) return memo.messages;
|
|
87
|
+
const messages = deps.agentManager.getConversationSnapshot(record.id);
|
|
88
|
+
if (finished) snapshots.set(record.id, { stamp, messages });
|
|
89
|
+
return messages;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const costOf = (agentId: string): number | undefined => {
|
|
93
|
+
const node = deps.fleetNodes().find((n) => n.id === agentId);
|
|
94
|
+
// Only a real reading: an unpriced node has no cost to state.
|
|
95
|
+
return node && node.costState !== 'unpriced' && typeof node.costUsd === 'number' ? node.costUsd : undefined;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const wrfcPhases = (record: AgentRecord): { phases: WrfcPhaseInfo[]; passed: boolean | undefined } | undefined => {
|
|
99
|
+
const chain = deps.listChains().find((c) => c.ownerAgentId === record.id || (c.engineerAgentId === record.id && c.ownerAgentId === c.engineerAgentId));
|
|
100
|
+
if (!chain) return undefined;
|
|
101
|
+
const phases: WrfcPhaseInfo[] = [];
|
|
102
|
+
for (const id of chain.allAgentIds) {
|
|
103
|
+
if (id === chain.ownerAgentId && id !== chain.engineerAgentId) continue;
|
|
104
|
+
const phase = deps.agentManager.getStatus(id);
|
|
105
|
+
if (!phase) continue;
|
|
106
|
+
const role = (phase.wrfcRole ?? (id === chain.engineerAgentId ? 'engineer' : id === chain.reviewerAgentId ? 'reviewer' : id === chain.fixerAgentId ? 'fixer' : 'engineer')) as WrfcPhaseInfo['role'];
|
|
107
|
+
const latestReview = role === 'reviewer' && id === chain.reviewerAgentId ? chain.reviewerReport : undefined;
|
|
108
|
+
phases.push({
|
|
109
|
+
agentId: id,
|
|
110
|
+
role,
|
|
111
|
+
task: phase.task,
|
|
112
|
+
status: phase.status,
|
|
113
|
+
startedAt: phase.startedAt,
|
|
114
|
+
completedAt: phase.completedAt,
|
|
115
|
+
findings: latestReview?.issues.map((issue) => issue.description),
|
|
116
|
+
passed: latestReview ? (chain.lastReviewVerdict?.passed ?? latestReview.passed) : undefined,
|
|
117
|
+
output: firstLines(phase.fullOutput, 6),
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
phases.sort((a, b) => (a.startedAt ?? 0) - (b.startedAt ?? 0));
|
|
121
|
+
const passed = chain.state === 'passed' ? true : chain.state === 'failed' ? false : undefined;
|
|
122
|
+
return { phases, passed };
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const agent = (agentId: string): AgentLaneInfo | null => {
|
|
126
|
+
const record = deps.agentManager.getStatus(agentId);
|
|
127
|
+
if (!record) return null;
|
|
128
|
+
const wrfc = wrfcPhases(record);
|
|
129
|
+
return {
|
|
130
|
+
id: record.id,
|
|
131
|
+
name: record.template,
|
|
132
|
+
task: record.task,
|
|
133
|
+
status: record.status,
|
|
134
|
+
startedAt: record.startedAt,
|
|
135
|
+
completedAt: record.completedAt,
|
|
136
|
+
toolCallCount: record.toolCallCount,
|
|
137
|
+
costUsd: costOf(record.id),
|
|
138
|
+
error: record.error,
|
|
139
|
+
messages: wrfc ? [] : messagesOf(record),
|
|
140
|
+
wrfcPhases: wrfc?.phases,
|
|
141
|
+
wrfcPassed: wrfc?.passed,
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
conversation.setWorkTreeSources({
|
|
146
|
+
callTiming: (callId) => timings.callTiming(callId),
|
|
147
|
+
turnTiming: (index) => timings.turnTiming(index),
|
|
148
|
+
agent,
|
|
149
|
+
waitingCallIds: () => {
|
|
150
|
+
const id = deps.pendingCallId();
|
|
151
|
+
return id ? new Set([id]) : new Set<string>();
|
|
152
|
+
},
|
|
153
|
+
turnActive: deps.turnActive,
|
|
154
|
+
turnOutcome: (index) => conversation.workTree.turnOutcome(index),
|
|
155
|
+
now: () => Date.now(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Fold decisions persist per session, beside the session file.
|
|
159
|
+
conversation.workTree.onFoldChange(persistView);
|
|
160
|
+
sweepOrphanWorkTreeFolds(deps.sessionsDir);
|
|
161
|
+
unsubs.push(onSemanticSummaryReady(() => { conversation.workTree.invalidate(); deps.requestRender(); }));
|
|
162
|
+
// A tree-sitter parse landed: code drawn with the regex placeholder is redrawn with it.
|
|
163
|
+
unsubs.push(onSyntaxHighlightReady(() => { conversation.workTree.invalidate(); deps.requestRender(); }));
|
|
164
|
+
return { timings, unsubs };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Restore a resumed session's fold decisions and failed/cancelled turn
|
|
169
|
+
* endings (called by the resume routine, after the transcript is restored).
|
|
170
|
+
*/
|
|
171
|
+
export function restoreWorkTreeFolds(conversation: ConversationManager, sessionsDir: string, sessionId: string): number {
|
|
172
|
+
const entries = loadWorkTreeFolds(sessionsDir, sessionId);
|
|
173
|
+
conversation.workTree.restoreFoldState(entries);
|
|
174
|
+
conversation.workTree.restoreTurnOutcomes(loadWorkTreeTurnOutcomes(sessionsDir, sessionId));
|
|
175
|
+
return entries.length;
|
|
176
|
+
}
|
|
@@ -18,11 +18,17 @@
|
|
|
18
18
|
* their reasons, which is the same principle the channel renderer follows: in
|
|
19
19
|
* plain words, never by an opaque identifier.
|
|
20
20
|
*
|
|
21
|
+
* The title names the work (owner ruling 2026-09-29): the workstream's task,
|
|
22
|
+
* trimmed at a word boundary, and the body adds the last review score when
|
|
23
|
+
* one was reached. behavior.notificationsMetadataOnly (default off) drops the
|
|
24
|
+
* task, the reason and the score, leaving what kind of stop it was.
|
|
25
|
+
*
|
|
21
26
|
* Split out of turn-event-wiring.ts as a pure function so the text is testable
|
|
22
27
|
* on its own, the wiring itself cannot be asserted against without mocking a
|
|
23
28
|
* process-global notifier.
|
|
24
29
|
*/
|
|
25
30
|
import { formatTurnBudgetOutcome } from './turn-budget-outcome.ts';
|
|
31
|
+
import { NOTIFICATION_TEXT_LIMITS, trimAtWordBoundary } from '@pellux/goodvibes-sdk/platform/runtime/operations';
|
|
26
32
|
|
|
27
33
|
/** The fields of WORKFLOW_CHAIN_FAILED this narration reads. */
|
|
28
34
|
export interface WorkstreamFailureNarrationInput {
|
|
@@ -32,11 +38,32 @@ export interface WorkstreamFailureNarrationInput {
|
|
|
32
38
|
readonly turnLimitSource?: 'default' | 'spawn-override' | 'policy-bound' | undefined;
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
/** What the host remembers about the workstream, and the privacy setting. */
|
|
42
|
+
export interface WorkstreamNotificationContext {
|
|
43
|
+
/** The workstream's task, from WORKFLOW_CHAIN_CREATED. */
|
|
44
|
+
readonly task?: string | null | undefined;
|
|
45
|
+
/** The last review score (out of 10), from WORKFLOW_REVIEW_COMPLETED. */
|
|
46
|
+
readonly reviewScore?: number | null | undefined;
|
|
47
|
+
/** behavior.notificationsMetadataOnly */
|
|
48
|
+
readonly metadataOnly?: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
export interface WorkstreamNotification {
|
|
36
52
|
readonly title: string;
|
|
37
53
|
readonly body: string;
|
|
38
54
|
}
|
|
39
55
|
|
|
56
|
+
function titled(outcome: string, context: WorkstreamNotificationContext): string {
|
|
57
|
+
const prefix = `Workstream ${outcome}: `;
|
|
58
|
+
const task = context.metadataOnly ? '' : trimAtWordBoundary(context.task ?? '', NOTIFICATION_TEXT_LIMITS.desktopTitle - prefix.length);
|
|
59
|
+
return task ? `${prefix}${task}` : `GoodVibes: workstream ${outcome}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function withScore(body: string, context: WorkstreamNotificationContext): string {
|
|
63
|
+
if (context.metadataOnly || typeof context.reviewScore !== 'number' || !Number.isFinite(context.reviewScore)) return body;
|
|
64
|
+
return `${body} (last review ${Math.round(context.reviewScore * 10) / 10}/10)`;
|
|
65
|
+
}
|
|
66
|
+
|
|
40
67
|
/**
|
|
41
68
|
* Title and body for a workstream that reached a terminal state.
|
|
42
69
|
*
|
|
@@ -49,21 +76,24 @@ export interface WorkstreamNotification {
|
|
|
49
76
|
*/
|
|
50
77
|
export function workstreamFailureNotification(
|
|
51
78
|
payload: WorkstreamFailureNarrationInput,
|
|
79
|
+
context: WorkstreamNotificationContext = {},
|
|
52
80
|
): WorkstreamNotification {
|
|
81
|
+
const metadataOnly = context.metadataOnly === true;
|
|
53
82
|
if (payload.failureKind === 'cancelled') {
|
|
54
83
|
return {
|
|
55
|
-
title: '
|
|
56
|
-
body: `Cancelled: ${payload.reason}`,
|
|
84
|
+
title: titled('cancelled', context),
|
|
85
|
+
body: metadataOnly ? 'Cancelled' : withScore(`Cancelled: ${payload.reason}`, context),
|
|
57
86
|
};
|
|
58
87
|
}
|
|
59
88
|
if (payload.failureKind === 'max_turns') {
|
|
60
89
|
return {
|
|
61
|
-
title: '
|
|
62
|
-
body: `The workstream ${formatTurnBudgetOutcome({ limit: payload.turnLimit, source: payload.turnLimitSource })}`,
|
|
90
|
+
title: titled('hit its turn budget', context),
|
|
91
|
+
body: withScore(`The workstream ${formatTurnBudgetOutcome({ limit: payload.turnLimit, source: payload.turnLimitSource })}`, context),
|
|
63
92
|
};
|
|
64
93
|
}
|
|
94
|
+
const reason = payload.failureKind === 'transport' ? 'transient transport error' : payload.reason;
|
|
65
95
|
return {
|
|
66
|
-
title: '
|
|
67
|
-
body:
|
|
96
|
+
title: titled('failed', context),
|
|
97
|
+
body: metadataOnly ? (payload.failureKind === 'transport' ? `Failed: ${reason}` : 'Failed') : withScore(`Failed: ${reason}`, context),
|
|
68
98
|
};
|
|
69
99
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wrfc-notice-titles.ts, the plain title of each review-chain line this
|
|
3
|
+
* terminal writes itself.
|
|
4
|
+
*
|
|
5
|
+
* runtime/bootstrap-core.ts and runtime/wrfc-persistence.ts write a few
|
|
6
|
+
* "[WRFC] …" lines of their own (the SDK's lines are titled by its
|
|
7
|
+
* runtimeEventOfNotice). The notification history keeps each such event as
|
|
8
|
+
* one entry under a plain title, with the line's detail as the body
|
|
9
|
+
* (core/notices.ts). The test feeds every producer's template through
|
|
10
|
+
* shellChainNoticeOf so the lines and this table cannot drift apart.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export interface ShellChainNotice {
|
|
14
|
+
readonly title: string;
|
|
15
|
+
readonly level: 'info' | 'warning';
|
|
16
|
+
/** The line without its bracket tag or status mark. */
|
|
17
|
+
readonly detail: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const SHELL_CHAIN_LINES: ReadonlyArray<{ readonly pattern: RegExp; readonly title: string; readonly level: ShellChainNotice['level'] }> = [
|
|
21
|
+
{ pattern: /^\[WRFC\] Engineer enumerated \d+ constraints? for chain \S+/, title: 'Review chain constraints listed', level: 'info' },
|
|
22
|
+
{ pattern: /^\[WRFC\] Fix #\d+ targeting \d+ constraints? on chain \S+/, title: 'Review chain fix started', level: 'info' },
|
|
23
|
+
{ pattern: /^\[WRFC\] ✗ Chain \S+: \d+ constraint violations? forced failure/, title: 'Review chain failed its constraints', level: 'warning' },
|
|
24
|
+
{ pattern: /^\[WRFC\] Guard: .*\(spawn-forced-wrfc\)$/s, title: 'Agent request sent through a review chain', level: 'info' },
|
|
25
|
+
{ pattern: /^\[WRFC\] Guard: .*\(spawn-suppressed-wrfc\)$/s, title: 'Agent request kept out of a review chain', level: 'info' },
|
|
26
|
+
{ pattern: /^\[WRFC\] Guard: .*\(batch-collapsed-to-wrfc\)$/s, title: 'Agent requests combined into one review chain', level: 'info' },
|
|
27
|
+
{ pattern: /^\[WRFC\] Chain \S+ \(.*\) was interrupted by a restart; /s, title: 'Review chain interrupted by a restart', level: 'warning' },
|
|
28
|
+
{ pattern: /^\[WRFC\] Pre-router buffer overflowed: /, title: 'Early review chain messages dropped', level: 'warning' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** The plain title of a review-chain line this terminal writes, or undefined for any other line. */
|
|
32
|
+
export function shellChainNoticeOf(text: string): ShellChainNotice | undefined {
|
|
33
|
+
const line = text.trim();
|
|
34
|
+
const match = SHELL_CHAIN_LINES.find((entry) => entry.pattern.test(line));
|
|
35
|
+
if (!match) return undefined;
|
|
36
|
+
return { title: match.title, level: match.level, detail: line.replace(/^\[WRFC\]\s*/, '').replace(/^✗\s*/, '') };
|
|
37
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agents-modal-text.ts, the plain-text pieces the Agents modal shows: the
|
|
3
|
+
* hosted session's header facts and rows, and a compact live tail of an
|
|
4
|
+
* agent's conversation for the side column.
|
|
5
|
+
*
|
|
6
|
+
* Every hosted fact is a field of the daemon's own record; nothing is derived
|
|
7
|
+
* here. The tail shows what an agent said and did most recently: its words,
|
|
8
|
+
* the tools it called (→) and the first line of each result (←).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ConversationMessageSnapshot } from '@pellux/goodvibes-sdk/platform/core';
|
|
12
|
+
import { createEmptyLine, type Line } from '@pellux/goodvibes-sdk/platform/types';
|
|
13
|
+
import type { HostedSessionFeedState, HostedSessionRow } from '../views/hosted-session-feed.ts';
|
|
14
|
+
import type { AgentsText } from '../renderer/agents-modal.ts';
|
|
15
|
+
import { activeTokens } from '../renderer/theme.ts';
|
|
16
|
+
import { wrapText, getDisplayWidth } from '../utils/terminal-width.ts';
|
|
17
|
+
|
|
18
|
+
/** What the hosted session is, what it is doing, and what leaving it would do. */
|
|
19
|
+
export function hostedHeaderTexts(state: HostedSessionFeedState): AgentsText[] {
|
|
20
|
+
const record = state.record;
|
|
21
|
+
if (!record) return [];
|
|
22
|
+
const lines: AgentsText[] = [
|
|
23
|
+
{ text: `${record.status} · ${record.turnCount} turn${record.turnCount === 1 ? '' : 's'} · ${record.messageCount} message${record.messageCount === 1 ? '' : 's'}`, tone: 'text' },
|
|
24
|
+
{ text: `workspace ${record.workspaceRoot}`, tone: 'faint' },
|
|
25
|
+
{
|
|
26
|
+
text: `${record.effectiveDetachPolicy === 'survive' ? 'leaving keeps it running (survive)' : 'leaving ends it (kill)'}, ${record.detachPolicy === null ? 'from the setting' : 'set for this session'}`,
|
|
27
|
+
tone: record.effectiveDetachPolicy === 'survive' ? 'success' : 'warning',
|
|
28
|
+
},
|
|
29
|
+
{ text: `attached: ${record.attachedClients.length > 0 ? record.attachedClients.join(', ') : 'nobody'}`, tone: 'muted' },
|
|
30
|
+
];
|
|
31
|
+
if (record.status === 'terminated') lines.push({ text: `ended: ${record.terminatedReason ?? 'no reason recorded'}`, tone: 'error' });
|
|
32
|
+
if (record.restoredFromDisk) lines.push({ text: 'restored from disk after a daemon restart', tone: 'warning' });
|
|
33
|
+
lines.push(state.streaming
|
|
34
|
+
? { text: 'live event stream open', tone: 'success' }
|
|
35
|
+
: { text: `no live stream: ${state.streamNote ?? 'not subscribed'}`, tone: 'warning' });
|
|
36
|
+
if (state.runningToolCalls.length > 0) {
|
|
37
|
+
lines.push({ text: `running: ${state.runningToolCalls.map((call) => call.tool).join(', ')}`, tone: 'brand' });
|
|
38
|
+
}
|
|
39
|
+
if (state.droppedRows > 0) lines.push({ text: `${state.droppedRows} earlier row(s) dropped; reattach to backfill`, tone: 'faint' });
|
|
40
|
+
return lines;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** The hosted conversation's rows as text. */
|
|
44
|
+
export function hostedRowTexts(rows: readonly HostedSessionRow[]): AgentsText[] {
|
|
45
|
+
return rows.map((row) => {
|
|
46
|
+
const prefix = row.kind === 'user' ? 'you: ' : row.kind === 'tool' ? '' : row.kind === 'error' ? 'error: ' : '';
|
|
47
|
+
const text = `${prefix}${row.text}${row.streaming ? ' …' : ''}`;
|
|
48
|
+
if (row.kind === 'tool') return { text, tone: 'brand', glyph: '→' };
|
|
49
|
+
if (row.kind === 'error') return { text, tone: 'error' };
|
|
50
|
+
if (row.kind === 'user') return { text, tone: 'text', bold: true };
|
|
51
|
+
if (row.kind === 'system') return { text, tone: 'faint' };
|
|
52
|
+
return { text, tone: 'text' };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The hosted transcript as lines at a width (text from column 5, like the transcript). */
|
|
57
|
+
export function hostedBody(rows: readonly HostedSessionRow[], width: number, height: number): Line[] {
|
|
58
|
+
const t = activeTokens();
|
|
59
|
+
const out: Line[] = [];
|
|
60
|
+
const textWidth = Math.max(8, width - 9);
|
|
61
|
+
for (const item of hostedRowTexts(rows)) {
|
|
62
|
+
const fg = item.tone === 'error' ? t.error : item.tone === 'brand' ? t.brand : item.tone === 'faint' ? t.textFaint : t.text;
|
|
63
|
+
const text = item.glyph ? `${item.glyph} ${item.text}` : item.text;
|
|
64
|
+
for (const part of wrapText(text, textWidth)) {
|
|
65
|
+
const line = createEmptyLine(width);
|
|
66
|
+
let x = 5;
|
|
67
|
+
for (const ch of part) {
|
|
68
|
+
const w = getDisplayWidth(ch);
|
|
69
|
+
if (w <= 0 || x + w > width - 4) break;
|
|
70
|
+
line[x] = { ...line[x]!, char: ch, fg, bold: item.bold === true };
|
|
71
|
+
if (w === 2) line[x + 1] = { ...line[x]!, char: '' };
|
|
72
|
+
x += w;
|
|
73
|
+
}
|
|
74
|
+
out.push(line);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out.slice(-Math.max(1, height));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function contentText(content: string | ReadonlyArray<{ type: string; text?: string }>): string {
|
|
81
|
+
if (typeof content === 'string') return content;
|
|
82
|
+
return content.map((part) => (part.type === 'text' ? part.text ?? '' : `[${part.type}]`)).join(' ');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** The one argument that says what a tool call is about (a path, a command, a query). */
|
|
86
|
+
function callSubject(args: Record<string, unknown>): string {
|
|
87
|
+
for (const key of ['path', 'file_path', 'filePath', 'command', 'cmd', 'pattern', 'query', 'url', 'name']) {
|
|
88
|
+
const value = args[key];
|
|
89
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
90
|
+
}
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function firstLine(text: string): string {
|
|
95
|
+
return text.split('\n').find((line) => line.trim().length > 0)?.trim() ?? '';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The most recent `limit` entries of an agent's conversation, as tail lines. */
|
|
99
|
+
export function transcriptTail(snapshot: readonly ConversationMessageSnapshot[], limit: number): AgentsText[] {
|
|
100
|
+
const out: AgentsText[] = [];
|
|
101
|
+
for (const message of snapshot) {
|
|
102
|
+
if (message.role === 'user') {
|
|
103
|
+
const text = firstLine(contentText(message.content as string));
|
|
104
|
+
if (text) out.push({ text: `you: ${text}`, tone: 'text', bold: true });
|
|
105
|
+
} else if (message.role === 'assistant') {
|
|
106
|
+
const text = message.content.trim();
|
|
107
|
+
if (text) for (const paragraph of text.split(/\n\s*\n/).slice(-2)) out.push({ text: paragraph.replace(/\s+/g, ' ').trim(), tone: 'text' });
|
|
108
|
+
for (const call of message.toolCalls ?? []) {
|
|
109
|
+
const subject = callSubject(call.arguments);
|
|
110
|
+
out.push({ text: subject ? `${call.name} ${subject}` : call.name, tone: 'muted', glyph: '→' });
|
|
111
|
+
}
|
|
112
|
+
} else if (message.role === 'tool') {
|
|
113
|
+
const text = firstLine(message.content);
|
|
114
|
+
out.push({ text: `${message.toolName ?? 'result'}${text ? `: ${text}` : ''}`, tone: 'faint', glyph: '←' });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (out.length === 0) out.push({ text: 'No messages yet.', tone: 'faint' });
|
|
118
|
+
return out.slice(-limit);
|
|
119
|
+
}
|