@oh-my-pi/pi-tui 18.2.4 → 18.2.6
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 +23 -0
- package/README.md +43 -0
- package/dist/types/app-keybindings.d.ts +395 -0
- package/dist/types/apps/autoresearch-dashboard.d.ts +43 -0
- package/dist/types/apps/autoresearch-data.d.ts +15 -0
- package/dist/types/apps/cleanse-board.d.ts +66 -0
- package/dist/types/apps/cleanse-picker.d.ts +23 -0
- package/dist/types/apps/debug/log-formatting.d.ts +6 -0
- package/dist/types/apps/debug/log-viewer.d.ts +81 -0
- package/dist/types/apps/debug/protocol-probe.d.ts +42 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +58 -0
- package/dist/types/apps/debug/raw-sse.d.ts +26 -0
- package/dist/types/apps/debug/terminal-info.d.ts +34 -0
- package/dist/types/apps/debug/viewer-frame.d.ts +52 -0
- package/dist/types/apps/git/avatar.d.ts +9 -0
- package/dist/types/apps/git/colors.d.ts +49 -0
- package/dist/types/apps/git/diff-pane.d.ts +188 -0
- package/dist/types/apps/git/git-tui.d.ts +66 -0
- package/dist/types/apps/git/sidebar.d.ts +149 -0
- package/dist/types/apps/git/state.d.ts +117 -0
- package/dist/types/apps/if-bench-board.d.ts +75 -0
- package/dist/types/apps/live-visualizer.d.ts +33 -0
- package/dist/types/apps/ps-data.d.ts +51 -0
- package/dist/types/apps/ps-top.d.ts +33 -0
- package/dist/types/apps/session-picker.d.ts +26 -0
- package/dist/types/apps/setup-model-picker.d.ts +13 -0
- package/dist/types/apps/standalone-picker.d.ts +42 -0
- package/dist/types/autocomplete.d.ts +4 -0
- package/dist/types/chat/advisor-message.d.ts +11 -0
- package/dist/types/chat/assistant-message.d.ts +106 -0
- package/dist/types/chat/background-tan-message.d.ts +9 -0
- package/dist/types/chat/bash-execution.d.ts +47 -0
- package/dist/types/chat/cache-invalidation-marker.d.ts +50 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +50 -0
- package/dist/types/chat/collab-prompt-message.d.ts +10 -0
- package/dist/types/chat/compaction-summary-message.d.ts +54 -0
- package/dist/types/chat/custom-message.d.ts +10 -0
- package/dist/types/chat/display-preferences.d.ts +13 -0
- package/dist/types/chat/eval-execution.d.ts +29 -0
- package/dist/types/chat/execution-shared.d.ts +51 -0
- package/dist/types/chat/extension-types.d.ts +28 -0
- package/dist/types/chat/hook-message.d.ts +10 -0
- package/dist/types/chat/image-loading.d.ts +46 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +21 -0
- package/dist/types/chat/messages.d.ts +189 -0
- package/dist/types/chat/reaction.d.ts +30 -0
- package/dist/types/chat/read-target.d.ts +4 -0
- package/dist/types/chat/read-tool-group.d.ts +67 -0
- package/dist/types/chat/served-model-marker.d.ts +47 -0
- package/dist/types/chat/skill-message.d.ts +33 -0
- package/dist/types/chat/skill-title-input.d.ts +14 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +12 -0
- package/dist/types/chat/thinking-display.d.ts +12 -0
- package/dist/types/chat/todo-reminder.d.ts +13 -0
- package/dist/types/chat/tool-execution.d.ts +138 -0
- package/dist/types/chat/transcript-browser.d.ts +93 -0
- package/dist/types/chat/transcript-entry.d.ts +45 -0
- package/dist/types/chat/transcript-outline.d.ts +86 -0
- package/dist/types/chat/transcript-render-helpers.d.ts +92 -0
- package/dist/types/chat/ttsr-notification.d.ts +23 -0
- package/dist/types/chat/user-message.d.ts +52 -0
- package/dist/types/chrome/chat-block.d.ts +64 -0
- package/dist/types/chrome/collab-qrcode.d.ts +28 -0
- package/dist/types/chrome/context-thresholds.d.ts +10 -0
- package/dist/types/chrome/countdown-timer.d.ts +14 -0
- package/dist/types/chrome/diff.d.ts +14 -0
- package/dist/types/chrome/dynamic-border.d.ts +16 -0
- package/dist/types/chrome/error-block.d.ts +17 -0
- package/dist/types/chrome/form-theme.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +12 -0
- package/dist/types/chrome/index.d.ts +20 -0
- package/dist/types/chrome/keybinding-hints.d.ts +40 -0
- package/dist/types/chrome/live-board.d.ts +23 -0
- package/dist/types/chrome/local-date.d.ts +4 -0
- package/dist/types/chrome/message-divider.d.ts +22 -0
- package/dist/types/chrome/message-frame.d.ts +52 -0
- package/dist/types/chrome/message-notice.d.ts +35 -0
- package/dist/types/chrome/overlay-box.d.ts +74 -0
- package/dist/types/chrome/qrcode.d.ts +48 -0
- package/dist/types/chrome/segment-track.d.ts +27 -0
- package/dist/types/chrome/select-list-mouse-routing.d.ts +4 -0
- package/dist/types/chrome/selector-helpers.d.ts +52 -0
- package/dist/types/chrome/shared.d.ts +5 -0
- package/dist/types/chrome/streaming-panel.d.ts +19 -0
- package/dist/types/chrome/tool-activity.d.ts +17 -0
- package/dist/types/chrome/transcript-container.d.ts +129 -0
- package/dist/types/chrome/transcript-status.d.ts +12 -0
- package/dist/types/chrome/visual-truncate.d.ts +19 -0
- package/dist/types/components/disclosure.d.ts +42 -0
- package/dist/types/components/form.d.ts +130 -0
- package/dist/types/components/key-value-list.d.ts +24 -0
- package/dist/types/components/layout/geometry.d.ts +73 -0
- package/dist/types/components/layout/row.d.ts +55 -0
- package/dist/types/components/layout/split-pane.d.ts +80 -0
- package/dist/types/components/layout/stack.d.ts +50 -0
- package/dist/types/components/menu-selection.d.ts +71 -0
- package/dist/types/components/metric.d.ts +44 -0
- package/dist/types/components/progress-bar.d.ts +36 -0
- package/dist/types/components/scroll-view.d.ts +53 -19
- package/dist/types/components/scroll-viewport.d.ts +46 -0
- package/dist/types/components/section.d.ts +19 -0
- package/dist/types/components/select-list.d.ts +46 -2
- package/dist/types/components/settings-list.d.ts +20 -0
- package/dist/types/components/table.d.ts +32 -0
- package/dist/types/components/tree-view.d.ts +130 -0
- package/dist/types/components/wizard-step.d.ts +60 -0
- package/dist/types/hotkeys-markdown.d.ts +7 -0
- package/dist/types/index.d.ts +17 -0
- package/dist/types/keybinding-matchers.d.ts +35 -0
- package/dist/types/keys.d.ts +2 -2
- package/dist/types/lang-from-path.d.ts +9 -0
- package/dist/types/overlays/advisor-config.d.ts +114 -0
- package/dist/types/overlays/agent-activity.d.ts +41 -0
- package/dist/types/overlays/agent-hub-projection.d.ts +33 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +53 -0
- package/dist/types/overlays/agent-hub-types.d.ts +99 -0
- package/dist/types/overlays/agent-hub.d.ts +126 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +71 -0
- package/dist/types/overlays/agents-hub.d.ts +72 -0
- package/dist/types/overlays/ask-dialog.d.ts +88 -0
- package/dist/types/overlays/bordered-loader.d.ts +11 -0
- package/dist/types/overlays/btw-history-panel.d.ts +29 -0
- package/dist/types/overlays/btw-history.d.ts +17 -0
- package/dist/types/overlays/btw-panel.d.ts +26 -0
- package/dist/types/overlays/cleanse-panel.d.ts +36 -0
- package/dist/types/overlays/codex-reset-fireworks.d.ts +52 -0
- package/dist/types/overlays/composer-shape-preview.d.ts +47 -0
- package/dist/types/overlays/composer-shape-registry.d.ts +50 -0
- package/dist/types/overlays/copy-selector.d.ts +48 -0
- package/dist/types/overlays/copy-targets.d.ts +63 -0
- package/dist/types/overlays/error-banner.d.ts +13 -0
- package/dist/types/overlays/extensions/display-text.d.ts +10 -0
- package/dist/types/overlays/extensions/extension-dashboard.d.ts +63 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +54 -0
- package/dist/types/overlays/extensions/inspector-model.d.ts +109 -0
- package/dist/types/overlays/extensions/inspector-panel.d.ts +17 -0
- package/dist/types/overlays/extensions/live-tool-session.d.ts +28 -0
- package/dist/types/overlays/extensions/mcp-runtime.d.ts +107 -0
- package/dist/types/overlays/extensions/state-manager.d.ts +42 -0
- package/dist/types/overlays/extensions/types.d.ts +165 -0
- package/dist/types/overlays/history-search.d.ts +16 -0
- package/dist/types/overlays/hook-editor.d.ts +44 -0
- package/dist/types/overlays/hook-input.d.ts +20 -0
- package/dist/types/overlays/hook-selector.d.ts +71 -0
- package/dist/types/overlays/hub-frame.d.ts +77 -0
- package/dist/types/overlays/login-dialog.d.ts +43 -0
- package/dist/types/overlays/logout-account-selector.d.ts +15 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +87 -0
- package/dist/types/overlays/model-browser.d.ts +213 -0
- package/dist/types/overlays/model-hub.d.ts +72 -0
- package/dist/types/overlays/model-picker.d.ts +74 -0
- package/dist/types/overlays/model-selector.d.ts +52 -0
- package/dist/types/overlays/move-overlay.d.ts +39 -0
- package/dist/types/overlays/oauth-selector.d.ts +41 -0
- package/dist/types/overlays/omfg-panel.d.ts +20 -0
- package/dist/types/overlays/pause-screen.d.ts +43 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +89 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +19 -0
- package/dist/types/overlays/plan-toc.d.ts +41 -0
- package/dist/types/overlays/plugin-selector.d.ts +28 -0
- package/dist/types/overlays/plugin-settings.d.ts +180 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +11 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +23 -0
- package/dist/types/overlays/rewind-selector.d.ts +61 -0
- package/dist/types/overlays/running-subagent-badge.d.ts +6 -0
- package/dist/types/overlays/session-account-selector.d.ts +24 -0
- package/dist/types/overlays/session-info-overlay.d.ts +17 -0
- package/dist/types/overlays/session-observer-registry.d.ts +77 -0
- package/dist/types/overlays/session-selector.d.ts +131 -0
- package/dist/types/overlays/settings-defs.d.ts +119 -0
- package/dist/types/overlays/settings-selector.d.ts +73 -0
- package/dist/types/overlays/show-images-selector.d.ts +11 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +31 -0
- package/dist/types/overlays/theme-selector.d.ts +12 -0
- package/dist/types/overlays/thinking-selector.d.ts +12 -0
- package/dist/types/overlays/tiny-title-download-progress.d.ts +21 -0
- package/dist/types/overlays/tree-selector.d.ts +92 -0
- package/dist/types/overlays/usage-dashboard.d.ts +94 -0
- package/dist/types/overlays/usage-display.d.ts +5 -0
- package/dist/types/overlays/usage-row.d.ts +17 -0
- package/dist/types/prompt/attachment-chips.d.ts +19 -0
- package/dist/types/prompt/composer-attachments.d.ts +71 -0
- package/dist/types/prompt/composer-cache.d.ts +37 -0
- package/dist/types/prompt/composer.d.ts +159 -0
- package/dist/types/prompt/custom-editor.d.ts +261 -0
- package/dist/types/prompt/editor-top-gap.d.ts +18 -0
- package/dist/types/prompt/emoji-autocomplete.d.ts +16 -0
- package/dist/types/prompt/github-ref-autocomplete.d.ts +35 -0
- package/dist/types/prompt/gradient-highlight.d.ts +32 -0
- package/dist/types/prompt/image-format.d.ts +4 -0
- package/dist/types/prompt/image-references.d.ts +26 -0
- package/dist/types/prompt/interactive-context-helpers.d.ts +43 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +56 -0
- package/dist/types/prompt/macos-spelling.d.ts +44 -0
- package/dist/types/prompt/magic-keyword-boundary.d.ts +9 -0
- package/dist/types/prompt/magic-keywords.d.ts +26 -0
- package/dist/types/prompt/markdown-prose.d.ts +15 -0
- package/dist/types/prompt/model-mention-autocomplete.d.ts +25 -0
- package/dist/types/prompt/model-mention-syntax.d.ts +24 -0
- package/dist/types/prompt/orchestrate.d.ts +13 -0
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +56 -0
- package/dist/types/prompt/queue-input.d.ts +8 -0
- package/dist/types/prompt/skill-tokens.d.ts +13 -0
- package/dist/types/prompt/tools-markdown.d.ts +12 -0
- package/dist/types/prompt/ultrathink.d.ts +13 -0
- package/dist/types/prompt/usage-amounts.d.ts +26 -0
- package/dist/types/prompt/video.d.ts +18 -0
- package/dist/types/prompt/welcome.d.ts +83 -0
- package/dist/types/prompt/workflow.d.ts +13 -0
- package/dist/types/render/code-cell.d.ts +51 -0
- package/dist/types/render/file-list.d.ts +22 -0
- package/dist/types/render/hyperlink.d.ts +80 -0
- package/dist/types/render/index.d.ts +16 -0
- package/dist/types/render/output-block.d.ts +54 -0
- package/dist/types/render/output-pane.d.ts +80 -0
- package/dist/types/render/render-utils.d.ts +312 -0
- package/dist/types/render/sixel.d.ts +21 -0
- package/dist/types/render/status-line.d.ts +23 -0
- package/dist/types/render/tool-card.d.ts +71 -0
- package/dist/types/render/tree-list.d.ts +29 -0
- package/dist/types/render/types.d.ts +17 -0
- package/dist/types/render/utils.d.ts +43 -0
- package/dist/types/render/width-aware-text.d.ts +23 -0
- package/dist/types/setup/lazy.d.ts +3 -0
- package/dist/types/setup/scenes/composer.d.ts +3 -0
- package/dist/types/setup/scenes/glyph.d.ts +3 -0
- package/dist/types/setup/scenes/model.d.ts +3 -0
- package/dist/types/setup/scenes/outro.d.ts +2 -0
- package/dist/types/setup/scenes/providers.d.ts +3 -0
- package/dist/types/setup/scenes/sign-in.d.ts +21 -0
- package/dist/types/setup/scenes/splash.d.ts +11 -0
- package/dist/types/setup/scenes/theme.d.ts +3 -0
- package/dist/types/setup/scenes/types.d.ts +110 -0
- package/dist/types/setup/scenes/web-search.d.ts +22 -0
- package/dist/types/setup/setup-version.d.ts +11 -0
- package/dist/types/setup/startup-splash.d.ts +9 -0
- package/dist/types/setup/wizard-overlay.d.ts +16 -0
- package/dist/types/setup/wizard.d.ts +25 -0
- package/dist/types/status-line/component.d.ts +182 -0
- package/dist/types/status-line/context-usage.d.ts +156 -0
- package/dist/types/status-line/footer.d.ts +30 -0
- package/dist/types/status-line/git-utils.d.ts +22 -0
- package/dist/types/status-line/host.d.ts +129 -0
- package/dist/types/status-line/index.d.ts +6 -0
- package/dist/types/status-line/loop.d.ts +18 -0
- package/dist/types/status-line/metrics.d.ts +21 -0
- package/dist/types/status-line/presets.d.ts +3 -0
- package/dist/types/status-line/schema.d.ts +15 -0
- package/dist/types/status-line/segments.d.ts +5 -0
- package/dist/types/status-line/separators.d.ts +3 -0
- package/dist/types/status-line/types.d.ts +236 -0
- package/dist/types/stdin-buffer.d.ts +4 -0
- package/dist/types/theme/color.d.ts +24 -0
- package/dist/types/theme/defaults/index.d.ts +9489 -0
- package/dist/types/theme/index.d.ts +4 -0
- package/dist/types/theme/loader.d.ts +23 -0
- package/dist/types/theme/mermaid-cache.d.ts +26 -0
- package/dist/types/theme/schema-validation.d.ts +3 -0
- package/dist/types/theme/schema.d.ts +25 -0
- package/dist/types/theme/session-color.d.ts +43 -0
- package/dist/types/theme/shimmer.d.ts +44 -0
- package/dist/types/theme/symbols.d.ts +34 -0
- package/dist/types/theme/theme-class.d.ts +299 -0
- package/dist/types/theme/theme.d.ts +111 -0
- package/dist/types/theme/tui-adapters.d.ts +30 -0
- package/dist/types/thinking.d.ts +165 -0
- package/dist/types/tools/agent-tree.d.ts +36 -0
- package/dist/types/tools/ask.d.ts +67 -0
- package/dist/types/tools/ast-edit.d.ts +53 -0
- package/dist/types/tools/ast-grep.d.ts +52 -0
- package/dist/types/tools/autoresearch.d.ts +156 -0
- package/dist/types/tools/bash-interactive.d.ts +28 -0
- package/dist/types/tools/bash.d.ts +103 -0
- package/dist/types/tools/conflict-detect.d.ts +43 -0
- package/dist/types/tools/debug.d.ts +60 -0
- package/dist/types/tools/default-renderer.d.ts +24 -0
- package/dist/types/tools/edit.d.ts +158 -0
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +5 -0
- package/dist/types/tools/eval-format/index.d.ts +5 -0
- package/dist/types/tools/eval-format/javascript.d.ts +2 -0
- package/dist/types/tools/eval-format/python.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +94 -0
- package/dist/types/tools/fetch.d.ts +42 -0
- package/dist/types/tools/gh-format.d.ts +8 -0
- package/dist/types/tools/github.d.ts +144 -0
- package/dist/types/tools/glob.d.ts +44 -0
- package/dist/types/tools/goal.d.ts +42 -0
- package/dist/types/tools/grep.d.ts +64 -0
- package/dist/types/tools/grouped-file-output.d.ts +71 -0
- package/dist/types/tools/hashline-format.d.ts +28 -0
- package/dist/types/tools/hub.d.ts +340 -0
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/json-tree.d.ts +59 -0
- package/dist/types/tools/line-ranges.d.ts +29 -0
- package/dist/types/tools/list-limit.d.ts +15 -0
- package/dist/types/tools/lsp.d.ts +72 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/mcp.d.ts +81 -0
- package/dist/types/tools/memory.d.ts +67 -0
- package/dist/types/tools/output-meta.d.ts +151 -0
- package/dist/types/tools/read.d.ts +139 -0
- package/dist/types/tools/renderer.d.ts +98 -0
- package/dist/types/tools/report-tool-issue.d.ts +8 -0
- package/dist/types/tools/resolve.d.ts +47 -0
- package/dist/types/tools/streaming-output.d.ts +282 -0
- package/dist/types/tools/subprocess.d.ts +24 -0
- package/dist/types/tools/task-repair-args.d.ts +53 -0
- package/dist/types/tools/task-yield-assembly.d.ts +20 -0
- package/dist/types/tools/task.d.ts +425 -0
- package/dist/types/tools/terminal-output.d.ts +8 -0
- package/dist/types/tools/think.d.ts +13 -0
- package/dist/types/tools/todo.d.ts +118 -0
- package/dist/types/tools/tool-errors.d.ts +7 -0
- package/dist/types/tools/vibe.d.ts +120 -0
- package/dist/types/tools/web-search.d.ts +195 -0
- package/dist/types/tools/write.d.ts +76 -0
- package/dist/types/tools/xd-url.d.ts +13 -0
- package/dist/types/tools/xdev.d.ts +52 -0
- package/dist/types/tui.d.ts +18 -0
- package/dist/types/utils.d.ts +2 -0
- package/package.json +37 -3
- package/src/app-keybindings.ts +760 -0
- package/src/apps/autoresearch-dashboard.ts +500 -0
- package/src/apps/autoresearch-data.ts +64 -0
- package/src/apps/cleanse-board.ts +341 -0
- package/src/apps/cleanse-picker.ts +57 -0
- package/src/apps/debug/log-formatting.ts +53 -0
- package/src/apps/debug/log-viewer.ts +868 -0
- package/src/apps/debug/protocol-probe.ts +267 -0
- package/src/apps/debug/raw-sse-buffer.ts +449 -0
- package/src/apps/debug/raw-sse.ts +282 -0
- package/src/apps/debug/terminal-info.ts +115 -0
- package/src/apps/debug/viewer-frame.ts +174 -0
- package/src/apps/git/avatar.ts +52 -0
- package/src/apps/git/colors.ts +109 -0
- package/src/apps/git/diff-pane.ts +1512 -0
- package/src/apps/git/git-tui.ts +889 -0
- package/src/apps/git/sidebar.ts +1204 -0
- package/src/apps/git/state.ts +113 -0
- package/src/apps/if-bench-board.ts +324 -0
- package/src/apps/live-visualizer.ts +238 -0
- package/src/apps/ps-data.ts +116 -0
- package/src/apps/ps-top.ts +422 -0
- package/src/apps/session-picker.ts +95 -0
- package/src/apps/setup-model-picker.ts +21 -0
- package/src/apps/standalone-picker.ts +111 -0
- package/src/autocomplete.ts +9 -13
- package/src/chat/advisor-message.ts +175 -0
- package/src/chat/assistant-message.ts +1214 -0
- package/src/chat/background-tan-message.ts +35 -0
- package/src/chat/bash-execution.ts +394 -0
- package/src/chat/cache-invalidation-marker.ts +94 -0
- package/src/chat/chat-transcript-builder.ts +593 -0
- package/src/chat/collab-prompt-message.ts +34 -0
- package/src/chat/compaction-summary-message.ts +261 -0
- package/src/chat/custom-message.ts +23 -0
- package/src/chat/display-preferences.ts +24 -0
- package/src/chat/eval-execution.ts +172 -0
- package/src/chat/execution-shared.ts +113 -0
- package/src/chat/extension-types.ts +46 -0
- package/src/chat/hook-message.ts +20 -0
- package/src/chat/image-loading.ts +122 -0
- package/src/chat/late-diagnostics-message.ts +92 -0
- package/src/chat/messages.ts +305 -0
- package/src/chat/reaction.ts +65 -0
- package/src/chat/read-target.ts +7 -0
- package/src/chat/read-tool-group.ts +873 -0
- package/src/chat/served-model-marker.ts +91 -0
- package/src/chat/skill-message.ts +220 -0
- package/src/chat/skill-title-input.ts +41 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +35 -0
- package/src/chat/thinking-display.ts +312 -0
- package/src/chat/todo-reminder.ts +33 -0
- package/src/chat/tool-execution.ts +1395 -0
- package/src/chat/transcript-browser.ts +268 -0
- package/src/chat/transcript-entry.ts +96 -0
- package/src/chat/transcript-outline.ts +260 -0
- package/src/chat/transcript-render-helpers.ts +315 -0
- package/src/chat/ttsr-notification.ts +123 -0
- package/src/chat/user-message.ts +287 -0
- package/src/chrome/chat-block.ts +110 -0
- package/src/chrome/collab-qrcode.ts +74 -0
- package/src/chrome/context-thresholds.ts +85 -0
- package/src/chrome/countdown-timer.ts +74 -0
- package/src/chrome/diff.ts +266 -0
- package/src/chrome/dynamic-border.ts +36 -0
- package/src/chrome/error-block.ts +63 -0
- package/src/chrome/form-theme.ts +10 -0
- package/src/chrome/format.ts +66 -0
- package/src/chrome/index.ts +21 -0
- package/src/chrome/keybinding-hints.ts +55 -0
- package/src/chrome/live-board.ts +123 -0
- package/src/chrome/local-date.ts +20 -0
- package/src/chrome/message-divider.ts +51 -0
- package/src/chrome/message-frame.ts +164 -0
- package/src/chrome/message-notice.ts +103 -0
- package/src/chrome/overlay-box.ts +301 -0
- package/src/chrome/qrcode.ts +539 -0
- package/src/chrome/segment-track.ts +86 -0
- package/src/chrome/select-list-mouse-routing.ts +11 -0
- package/src/chrome/selector-helpers.ts +119 -0
- package/src/chrome/shared.ts +27 -0
- package/src/chrome/streaming-panel.ts +72 -0
- package/src/chrome/tool-activity.ts +44 -0
- package/src/chrome/transcript-container.ts +902 -0
- package/src/chrome/transcript-status.ts +27 -0
- package/src/chrome/visual-truncate.ts +70 -0
- package/src/components/disclosure.ts +235 -0
- package/src/components/editor.ts +10 -12
- package/src/components/form.ts +566 -0
- package/src/components/input.ts +6 -27
- package/src/components/key-value-list.ts +88 -0
- package/src/components/layout/geometry.ts +202 -0
- package/src/components/layout/row.ts +283 -0
- package/src/components/layout/split-pane.ts +349 -0
- package/src/components/layout/stack.ts +261 -0
- package/src/components/markdown.ts +16 -12
- package/src/components/menu-selection.ts +248 -0
- package/src/components/metric.ts +132 -0
- package/src/components/progress-bar.ts +140 -0
- package/src/components/scroll-view.ts +372 -70
- package/src/components/scroll-viewport.ts +154 -0
- package/src/components/section.ts +77 -0
- package/src/components/select-list.ts +238 -159
- package/src/components/settings-list.ts +103 -80
- package/src/components/table.ts +127 -0
- package/src/components/tree-view.ts +481 -0
- package/src/components/wizard-step.ts +229 -0
- package/src/deccara.ts +1 -3
- package/src/hotkeys-markdown.ts +100 -0
- package/src/index.ts +18 -0
- package/src/keybinding-matchers.ts +99 -0
- package/src/keys.ts +2 -2
- package/src/lang-from-path.ts +252 -0
- package/src/overlays/advisor-config.ts +761 -0
- package/src/overlays/agent-activity.ts +113 -0
- package/src/overlays/agent-hub-projection.ts +250 -0
- package/src/overlays/agent-hub-renderer.ts +281 -0
- package/src/overlays/agent-hub-types.ts +74 -0
- package/src/overlays/agent-hub.ts +1562 -0
- package/src/overlays/agent-transcript-viewer.ts +669 -0
- package/src/overlays/agents-hub.ts +1203 -0
- package/src/overlays/ask-dialog.ts +1202 -0
- package/src/overlays/bordered-loader.ts +41 -0
- package/src/overlays/btw-history-panel.ts +591 -0
- package/src/overlays/btw-history.ts +33 -0
- package/src/overlays/btw-panel.ts +167 -0
- package/src/overlays/cleanse-panel.ts +167 -0
- package/src/overlays/codex-reset-fireworks.ts +375 -0
- package/src/overlays/composer-shape-preview.ts +137 -0
- package/src/overlays/composer-shape-registry.ts +82 -0
- package/src/overlays/copy-selector.ts +618 -0
- package/src/overlays/copy-targets.ts +210 -0
- package/src/overlays/error-banner.ts +39 -0
- package/src/overlays/extensions/display-text.ts +39 -0
- package/src/overlays/extensions/extension-dashboard.ts +645 -0
- package/src/overlays/extensions/extension-list.ts +667 -0
- package/src/overlays/extensions/inspector-model.ts +484 -0
- package/src/overlays/extensions/inspector-panel.ts +669 -0
- package/src/overlays/extensions/live-tool-session.ts +67 -0
- package/src/overlays/extensions/mcp-runtime.ts +289 -0
- package/src/overlays/extensions/state-manager.ts +337 -0
- package/src/overlays/extensions/types.ts +208 -0
- package/src/overlays/history-search.ts +268 -0
- package/src/overlays/hook-editor.ts +272 -0
- package/src/overlays/hook-input.ts +84 -0
- package/src/overlays/hook-selector.ts +635 -0
- package/src/overlays/hub-frame.ts +262 -0
- package/src/overlays/login-dialog.ts +231 -0
- package/src/overlays/logout-account-selector.ts +115 -0
- package/src/overlays/mcp-add-wizard.ts +1436 -0
- package/src/overlays/model-browser.ts +1176 -0
- package/src/overlays/model-hub.ts +2279 -0
- package/src/overlays/model-picker.ts +285 -0
- package/src/overlays/model-selector.ts +107 -0
- package/src/overlays/move-overlay.ts +197 -0
- package/src/overlays/oauth-selector.ts +446 -0
- package/src/overlays/omfg-panel.ts +136 -0
- package/src/overlays/pause-screen.ts +198 -0
- package/src/overlays/plan-review-overlay.ts +1227 -0
- package/src/overlays/plan-save-overlay.ts +74 -0
- package/src/overlays/plan-toc.ts +139 -0
- package/src/overlays/plugin-selector.ts +97 -0
- package/src/overlays/plugin-settings.ts +839 -0
- package/src/overlays/queue-mode-selector.ts +55 -0
- package/src/overlays/reset-usage-selector.ts +170 -0
- package/src/overlays/rewind-selector.ts +499 -0
- package/src/overlays/running-subagent-badge.ts +19 -0
- package/src/overlays/session-account-selector.ts +68 -0
- package/src/overlays/session-info-overlay.ts +96 -0
- package/src/overlays/session-observer-registry.ts +289 -0
- package/src/overlays/session-selector.ts +1137 -0
- package/src/overlays/settings-defs.ts +315 -0
- package/src/overlays/settings-selector.ts +1415 -0
- package/src/overlays/show-images-selector.ts +44 -0
- package/src/overlays/snapcompact-shape-preview-doc.md +14 -0
- package/src/overlays/snapcompact-shape-preview.ts +192 -0
- package/src/overlays/theme-selector.ts +62 -0
- package/src/overlays/thinking-selector.ts +51 -0
- package/src/overlays/tiny-title-download-progress.ts +102 -0
- package/src/overlays/tree-selector.ts +1121 -0
- package/src/overlays/usage-dashboard.ts +629 -0
- package/src/overlays/usage-display.ts +45 -0
- package/src/overlays/usage-row.ts +118 -0
- package/src/prompt/attachment-chips.ts +201 -0
- package/src/prompt/composer-attachments.ts +280 -0
- package/src/prompt/composer-cache.ts +304 -0
- package/src/prompt/composer.ts +934 -0
- package/src/prompt/custom-editor.ts +1392 -0
- package/src/prompt/data/emojis.json +1 -0
- package/src/prompt/editor-top-gap.ts +28 -0
- package/src/prompt/emoji-autocomplete.ts +285 -0
- package/src/prompt/github-ref-autocomplete.ts +75 -0
- package/src/prompt/gradient-highlight.ts +101 -0
- package/src/prompt/image-format.ts +28 -0
- package/src/prompt/image-references.ts +102 -0
- package/src/prompt/interactive-context-helpers.ts +126 -0
- package/src/prompt/internal-url-autocomplete.ts +155 -0
- package/src/prompt/macos-spelling.ts +456 -0
- package/src/prompt/magic-keyword-boundary.ts +23 -0
- package/src/prompt/magic-keywords.ts +42 -0
- package/src/prompt/markdown-prose.ts +245 -0
- package/src/prompt/model-mention-autocomplete.ts +94 -0
- package/src/prompt/model-mention-syntax.ts +41 -0
- package/src/prompt/orchestrate.ts +26 -0
- package/src/prompt/prompt-action-autocomplete.ts +337 -0
- package/src/prompt/queue-input.ts +132 -0
- package/src/prompt/skill-tokens.ts +36 -0
- package/src/prompt/tips.txt +27 -0
- package/src/prompt/tools-markdown.ts +29 -0
- package/src/prompt/ultrathink.ts +26 -0
- package/src/prompt/usage-amounts.ts +99 -0
- package/src/prompt/video.ts +47 -0
- package/src/prompt/welcome.ts +605 -0
- package/src/prompt/workflow.ts +26 -0
- package/src/render/code-cell.ts +244 -0
- package/src/render/file-list.ts +58 -0
- package/src/render/hyperlink.ts +192 -0
- package/src/render/index.ts +17 -0
- package/src/render/output-block.ts +267 -0
- package/src/render/output-pane.ts +299 -0
- package/src/render/render-utils.ts +1182 -0
- package/src/render/sixel.ts +69 -0
- package/src/render/status-line.ts +55 -0
- package/src/render/tool-card.ts +272 -0
- package/src/render/tree-list.ts +188 -0
- package/src/render/types.ts +19 -0
- package/src/render/utils.ts +109 -0
- package/src/render/width-aware-text.ts +60 -0
- package/src/setup/lazy.ts +17 -0
- package/src/setup/scenes/composer.ts +127 -0
- package/src/setup/scenes/glyph.ts +120 -0
- package/src/setup/scenes/model.ts +138 -0
- package/src/setup/scenes/outro.ts +24 -0
- package/src/setup/scenes/providers.ts +107 -0
- package/src/setup/scenes/sign-in.ts +373 -0
- package/src/setup/scenes/splash.ts +192 -0
- package/src/setup/scenes/theme.ts +343 -0
- package/src/setup/scenes/types.ts +111 -0
- package/src/setup/scenes/web-search.ts +165 -0
- package/src/setup/setup-version.ts +11 -0
- package/src/setup/startup-splash.ts +107 -0
- package/src/setup/wizard-overlay.ts +319 -0
- package/src/setup/wizard.ts +101 -0
- package/src/status-line/component.ts +3003 -0
- package/src/status-line/context-usage.ts +688 -0
- package/src/status-line/footer.ts +323 -0
- package/src/status-line/git-utils.ts +42 -0
- package/src/status-line/host.ts +97 -0
- package/src/status-line/index.ts +6 -0
- package/src/status-line/loop.ts +28 -0
- package/src/status-line/metrics.ts +94 -0
- package/src/status-line/presets.ts +107 -0
- package/src/status-line/schema.ts +57 -0
- package/src/status-line/segments.ts +934 -0
- package/src/status-line/separators.ts +55 -0
- package/src/status-line/types.ts +227 -0
- package/src/stdin-buffer.ts +4 -2
- package/src/theme/color.ts +95 -0
- package/src/theme/dark.json +95 -0
- package/src/theme/defaults/alabaster.json +93 -0
- package/src/theme/defaults/amethyst.json +96 -0
- package/src/theme/defaults/anthracite.json +93 -0
- package/src/theme/defaults/basalt.json +91 -0
- package/src/theme/defaults/birch.json +95 -0
- package/src/theme/defaults/dark-abyss.json +91 -0
- package/src/theme/defaults/dark-arctic.json +104 -0
- package/src/theme/defaults/dark-aurora.json +95 -0
- package/src/theme/defaults/dark-catppuccin.json +107 -0
- package/src/theme/defaults/dark-cavern.json +91 -0
- package/src/theme/defaults/dark-celestial.json +106 -0
- package/src/theme/defaults/dark-copper.json +95 -0
- package/src/theme/defaults/dark-cosmos.json +90 -0
- package/src/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/theme/defaults/dark-dracula.json +98 -0
- package/src/theme/defaults/dark-eclipse.json +91 -0
- package/src/theme/defaults/dark-ember.json +95 -0
- package/src/theme/defaults/dark-equinox.json +90 -0
- package/src/theme/defaults/dark-forest.json +96 -0
- package/src/theme/defaults/dark-github.json +105 -0
- package/src/theme/defaults/dark-gruvbox.json +112 -0
- package/src/theme/defaults/dark-lavender.json +95 -0
- package/src/theme/defaults/dark-lunar.json +89 -0
- package/src/theme/defaults/dark-midnight.json +95 -0
- package/src/theme/defaults/dark-monochrome.json +94 -0
- package/src/theme/defaults/dark-monokai.json +98 -0
- package/src/theme/defaults/dark-nebula.json +90 -0
- package/src/theme/defaults/dark-nord.json +98 -0
- package/src/theme/defaults/dark-ocean.json +101 -0
- package/src/theme/defaults/dark-one.json +100 -0
- package/src/theme/defaults/dark-poimandres.json +115 -0
- package/src/theme/defaults/dark-rainforest.json +91 -0
- package/src/theme/defaults/dark-reef.json +91 -0
- package/src/theme/defaults/dark-retro.json +92 -0
- package/src/theme/defaults/dark-rose-pine.json +96 -0
- package/src/theme/defaults/dark-sakura.json +95 -0
- package/src/theme/defaults/dark-slate.json +95 -0
- package/src/theme/defaults/dark-solarized.json +97 -0
- package/src/theme/defaults/dark-solstice.json +90 -0
- package/src/theme/defaults/dark-starfall.json +91 -0
- package/src/theme/defaults/dark-sunset.json +99 -0
- package/src/theme/defaults/dark-swamp.json +90 -0
- package/src/theme/defaults/dark-synthwave.json +103 -0
- package/src/theme/defaults/dark-taiga.json +91 -0
- package/src/theme/defaults/dark-terminal.json +95 -0
- package/src/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/theme/defaults/dark-tundra.json +91 -0
- package/src/theme/defaults/dark-twilight.json +91 -0
- package/src/theme/defaults/dark-volcanic.json +91 -0
- package/src/theme/defaults/graphite.json +92 -0
- package/src/theme/defaults/index.ts +201 -0
- package/src/theme/defaults/light-arctic.json +107 -0
- package/src/theme/defaults/light-aurora-day.json +91 -0
- package/src/theme/defaults/light-canyon.json +91 -0
- package/src/theme/defaults/light-catppuccin.json +106 -0
- package/src/theme/defaults/light-cirrus.json +90 -0
- package/src/theme/defaults/light-coral.json +95 -0
- package/src/theme/defaults/light-cyberpunk.json +96 -0
- package/src/theme/defaults/light-dawn.json +90 -0
- package/src/theme/defaults/light-dunes.json +91 -0
- package/src/theme/defaults/light-eucalyptus.json +95 -0
- package/src/theme/defaults/light-forest.json +100 -0
- package/src/theme/defaults/light-frost.json +95 -0
- package/src/theme/defaults/light-github.json +115 -0
- package/src/theme/defaults/light-glacier.json +91 -0
- package/src/theme/defaults/light-gruvbox.json +108 -0
- package/src/theme/defaults/light-haze.json +90 -0
- package/src/theme/defaults/light-honeycomb.json +95 -0
- package/src/theme/defaults/light-lagoon.json +91 -0
- package/src/theme/defaults/light-lavender.json +95 -0
- package/src/theme/defaults/light-meadow.json +91 -0
- package/src/theme/defaults/light-mint.json +95 -0
- package/src/theme/defaults/light-monochrome.json +101 -0
- package/src/theme/defaults/light-ocean.json +99 -0
- package/src/theme/defaults/light-one.json +99 -0
- package/src/theme/defaults/light-opal.json +91 -0
- package/src/theme/defaults/light-orchard.json +91 -0
- package/src/theme/defaults/light-paper.json +95 -0
- package/src/theme/defaults/light-poimandres.json +115 -0
- package/src/theme/defaults/light-prism.json +90 -0
- package/src/theme/defaults/light-retro.json +98 -0
- package/src/theme/defaults/light-sand.json +95 -0
- package/src/theme/defaults/light-savanna.json +91 -0
- package/src/theme/defaults/light-solarized.json +102 -0
- package/src/theme/defaults/light-soleil.json +90 -0
- package/src/theme/defaults/light-sunset.json +99 -0
- package/src/theme/defaults/light-synthwave.json +98 -0
- package/src/theme/defaults/light-tokyo-night.json +111 -0
- package/src/theme/defaults/light-wetland.json +91 -0
- package/src/theme/defaults/light-zenith.json +89 -0
- package/src/theme/defaults/limestone.json +94 -0
- package/src/theme/defaults/mahogany.json +97 -0
- package/src/theme/defaults/marble.json +93 -0
- package/src/theme/defaults/obsidian.json +91 -0
- package/src/theme/defaults/onyx.json +91 -0
- package/src/theme/defaults/pearl.json +93 -0
- package/src/theme/defaults/porcelain.json +91 -0
- package/src/theme/defaults/quartz.json +96 -0
- package/src/theme/defaults/sandstone.json +95 -0
- package/src/theme/defaults/titanium.json +90 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/light.json +93 -0
- package/src/theme/loader.ts +200 -0
- package/src/theme/mermaid-cache.ts +92 -0
- package/src/theme/schema-validation.ts +119 -0
- package/src/theme/schema.ts +168 -0
- package/src/theme/session-color.ts +253 -0
- package/src/theme/shimmer.ts +307 -0
- package/src/theme/symbols.ts +1433 -0
- package/src/theme/theme-class.ts +787 -0
- package/src/theme/theme-schema.json +463 -0
- package/src/theme/theme.ts +810 -0
- package/src/theme/tui-adapters.ts +339 -0
- package/src/thinking.ts +380 -0
- package/src/tools/agent-tree.ts +128 -0
- package/src/tools/ask.ts +482 -0
- package/src/tools/ast-edit.ts +210 -0
- package/src/tools/ast-grep.ts +171 -0
- package/src/tools/autoresearch.ts +287 -0
- package/src/tools/bash-interactive.ts +295 -0
- package/src/tools/bash.ts +468 -0
- package/src/tools/conflict-detect.ts +77 -0
- package/src/tools/debug.ts +167 -0
- package/src/tools/default-renderer.ts +172 -0
- package/src/tools/edit.ts +1258 -0
- package/src/tools/eval-format/code-mode-declarations.ts +61 -0
- package/src/tools/eval-format/index.ts +16 -0
- package/src/tools/eval-format/javascript.ts +952 -0
- package/src/tools/eval-format/python.ts +544 -0
- package/src/tools/eval.ts +835 -0
- package/src/tools/fetch.ts +226 -0
- package/src/tools/gh-format.ts +18 -0
- package/src/tools/github.ts +686 -0
- package/src/tools/glob.ts +213 -0
- package/src/tools/goal.ts +153 -0
- package/src/tools/grep.ts +385 -0
- package/src/tools/grouped-file-output.ts +211 -0
- package/src/tools/hashline-format.ts +59 -0
- package/src/tools/hub.ts +1488 -0
- package/src/tools/index.ts +71 -0
- package/src/tools/json-tree.ts +388 -0
- package/src/tools/line-ranges.ts +98 -0
- package/src/tools/list-limit.ts +43 -0
- package/src/tools/lsp.ts +702 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/mcp.ts +309 -0
- package/src/tools/memory.ts +220 -0
- package/src/tools/output-meta.ts +367 -0
- package/src/tools/read.ts +628 -0
- package/src/tools/renderer.ts +107 -0
- package/src/tools/report-tool-issue.ts +13 -0
- package/src/tools/resolve.ts +139 -0
- package/src/tools/streaming-output.ts +1576 -0
- package/src/tools/subprocess.ts +39 -0
- package/src/tools/task-repair-args.ts +118 -0
- package/src/tools/task-yield-assembly.ts +147 -0
- package/src/tools/task.ts +2110 -0
- package/src/tools/terminal-output.ts +153 -0
- package/src/tools/think.ts +28 -0
- package/src/tools/todo.ts +580 -0
- package/src/tools/tool-errors.ts +15 -0
- package/src/tools/vibe.ts +420 -0
- package/src/tools/web-search.ts +409 -0
- package/src/tools/write.ts +511 -0
- package/src/tools/xd-url.ts +28 -0
- package/src/tools/xdev.ts +201 -0
- package/src/tui.ts +57 -4
- package/src/utils.ts +8 -0
|
@@ -0,0 +1,1121 @@
|
|
|
1
|
+
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import {
|
|
3
|
+
type Component,
|
|
4
|
+
Container,
|
|
5
|
+
extractPrintableText,
|
|
6
|
+
fuzzyMatch,
|
|
7
|
+
Input,
|
|
8
|
+
matchesKey,
|
|
9
|
+
Spacer,
|
|
10
|
+
TruncatedText,
|
|
11
|
+
truncateToWidth,
|
|
12
|
+
} from "../index";
|
|
13
|
+
import { isRecord, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
14
|
+
/** Available session-tree display filters. */
|
|
15
|
+
export const TREE_FILTER_MODES = ["default", "no-tools", "user-only", "labeled-only", "all"] as const;
|
|
16
|
+
/** Session-tree display filter. */
|
|
17
|
+
export type TreeFilterMode = (typeof TREE_FILTER_MODES)[number];
|
|
18
|
+
import { theme } from "../theme/theme";
|
|
19
|
+
import {
|
|
20
|
+
matchesAppInterrupt,
|
|
21
|
+
matchesSelectDown,
|
|
22
|
+
matchesSelectPageDown,
|
|
23
|
+
matchesSelectPageUp,
|
|
24
|
+
matchesSelectUp,
|
|
25
|
+
} from "../keybinding-matchers";
|
|
26
|
+
import { isUserRequestEntry, type TranscriptEntryLike } from "../chat/transcript-entry";
|
|
27
|
+
|
|
28
|
+
/** Fields consumed when displaying persisted entries in the session tree. */
|
|
29
|
+
export type SessionTreeEntry = { id: string; parentId: string | null } & (
|
|
30
|
+
| TranscriptEntryLike
|
|
31
|
+
| { type: "compaction"; tokensBefore: number }
|
|
32
|
+
| { type: "branch_summary"; summary: string }
|
|
33
|
+
| { type: "model_change"; model: string }
|
|
34
|
+
| { type: "model_usage"; purpose: string; role?: string; provider: string; model: string }
|
|
35
|
+
| { type: "thinking_level_change"; thinkingLevel?: string | null }
|
|
36
|
+
| { type: "custom"; customType: string }
|
|
37
|
+
| { type: "label"; label?: string }
|
|
38
|
+
| { type: "service_tier_change"; serviceTier: Partial<Record<string, string>> | null }
|
|
39
|
+
| { type: "title_change"; title: string }
|
|
40
|
+
| { type: "mode_change"; mode: string }
|
|
41
|
+
| { type: "credential_pin"; provider: string }
|
|
42
|
+
| { type: "ttsr_injection"; injectedRules: string[] }
|
|
43
|
+
| { type: "session_init" | "reset_boundary" }
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
/** Session tree shape consumed by the selector. */
|
|
47
|
+
export interface TreeSelectorNode {
|
|
48
|
+
entry: SessionTreeEntry;
|
|
49
|
+
children: TreeSelectorNode[];
|
|
50
|
+
label?: string;
|
|
51
|
+
}
|
|
52
|
+
import { toPathList } from "../render/render-utils";
|
|
53
|
+
import { shortenPath } from "../render/render-utils";
|
|
54
|
+
import { canonicalizeMessage } from "../chat/thinking-display";
|
|
55
|
+
import { resolveAssistantErrorPresentation } from "../chat/transcript-render-helpers";
|
|
56
|
+
import { OverlayPanel, PanelDivider } from "../chrome/overlay-box";
|
|
57
|
+
import { TreeView, type TreeRow } from "../components/tree-view";
|
|
58
|
+
|
|
59
|
+
/** Filter mode for tree display */
|
|
60
|
+
type FilterMode = TreeFilterMode;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Tree list component with selection and ASCII art visualization
|
|
64
|
+
*/
|
|
65
|
+
/** Tool call info for lookup */
|
|
66
|
+
interface ToolCallInfo {
|
|
67
|
+
name: string;
|
|
68
|
+
arguments: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Advisor note metadata surfaced on a single session-tree row. */
|
|
72
|
+
interface AdvisorTreeDisplay {
|
|
73
|
+
/** Non-default advisor names then severities, comma-joined (e.g. `sec, blocker`). */
|
|
74
|
+
qualifier: string;
|
|
75
|
+
/** Note bodies joined into one line. */
|
|
76
|
+
text: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Collapse untrusted session metadata to one safe tree-row field: strip
|
|
81
|
+
* ANSI/control characters, then fold preserved tabs/newlines into spaces.
|
|
82
|
+
*/
|
|
83
|
+
function sanitizeTreeField(value: string): string {
|
|
84
|
+
return sanitizeText(value)
|
|
85
|
+
.replace(/[\n\t]/g, " ")
|
|
86
|
+
.trim();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Extract display metadata from an advisor custom-message's `details.notes`,
|
|
91
|
+
* ignoring the model-facing `<advisory>` wrapper stored in `content`. Collects
|
|
92
|
+
* distinct non-default advisor names and severities so the tree row can tag the
|
|
93
|
+
* note the way its transcript card does.
|
|
94
|
+
*/
|
|
95
|
+
function advisorTreeDisplay(details: unknown): AdvisorTreeDisplay {
|
|
96
|
+
if (!isRecord(details) || !Array.isArray(details.notes)) return { qualifier: "", text: "" };
|
|
97
|
+
const notes: string[] = [];
|
|
98
|
+
const advisors: string[] = [];
|
|
99
|
+
const severities: string[] = [];
|
|
100
|
+
for (const note of details.notes) {
|
|
101
|
+
if (!isRecord(note)) continue;
|
|
102
|
+
if (typeof note.note === "string") notes.push(note.note);
|
|
103
|
+
if (typeof note.advisor === "string") {
|
|
104
|
+
const name = sanitizeTreeField(note.advisor);
|
|
105
|
+
if (name && name !== "default" && !advisors.includes(name)) advisors.push(name);
|
|
106
|
+
}
|
|
107
|
+
if (typeof note.severity === "string") {
|
|
108
|
+
const severity = sanitizeTreeField(note.severity);
|
|
109
|
+
if (severity && !severities.includes(severity)) severities.push(severity);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { qualifier: [...advisors, ...severities].join(", "), text: notes.join(" ") };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Strip one model-facing `<system-*>` envelope from custom-message content.
|
|
117
|
+
* Nested system tags belong to the recorded payload and remain visible.
|
|
118
|
+
*/
|
|
119
|
+
function stripSystemWrapperTags(content: string): string {
|
|
120
|
+
const trimmed = content.trim();
|
|
121
|
+
const opening = /^<(system-[\w-]+)/i.exec(trimmed);
|
|
122
|
+
if (!opening) return content;
|
|
123
|
+
|
|
124
|
+
const attributeStart = opening[0].length;
|
|
125
|
+
const firstAttributeCharacter = trimmed[attributeStart];
|
|
126
|
+
if (firstAttributeCharacter !== ">" && !/\s/.test(firstAttributeCharacter ?? "")) return content;
|
|
127
|
+
|
|
128
|
+
let quote: '"' | "'" | undefined;
|
|
129
|
+
let openingEnd = -1;
|
|
130
|
+
for (let index = attributeStart; index < trimmed.length; index++) {
|
|
131
|
+
const character = trimmed[index];
|
|
132
|
+
if (quote) {
|
|
133
|
+
if (character === quote) quote = undefined;
|
|
134
|
+
} else if (character === '"' || character === "'") {
|
|
135
|
+
quote = character;
|
|
136
|
+
} else if (character === "<") {
|
|
137
|
+
return content;
|
|
138
|
+
} else if (character === ">") {
|
|
139
|
+
openingEnd = index;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (openingEnd === -1 || quote) return content;
|
|
144
|
+
|
|
145
|
+
const closingTag = `</${opening[1]}>`;
|
|
146
|
+
const closingStart = trimmed.length - closingTag.length;
|
|
147
|
+
if (closingStart <= openingEnd || trimmed.slice(closingStart).toLowerCase() !== closingTag.toLowerCase()) {
|
|
148
|
+
return content;
|
|
149
|
+
}
|
|
150
|
+
return trimmed.slice(openingEnd + 1, closingStart).trim();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Per-message cap on text folded into the tree search index. */
|
|
154
|
+
const SEARCH_TEXT_LIMIT = 200;
|
|
155
|
+
|
|
156
|
+
class TreeList implements Component {
|
|
157
|
+
#tree: TreeView<TreeSelectorNode, string>;
|
|
158
|
+
#roots: TreeSelectorNode[] = [];
|
|
159
|
+
#rootIds: Set<string> = new Set();
|
|
160
|
+
#nodeById: Map<string, TreeSelectorNode> = new Map();
|
|
161
|
+
#filterMode: FilterMode;
|
|
162
|
+
#searchQuery = "";
|
|
163
|
+
#toolCallMap: Map<string, ToolCallInfo> = new Map();
|
|
164
|
+
#multipleRoots = false;
|
|
165
|
+
#activePathIds: Set<string> = new Set();
|
|
166
|
+
#containsActive: Map<TreeSelectorNode, boolean> = new Map();
|
|
167
|
+
|
|
168
|
+
onSelect?: (entryId: string, options: { summarize: boolean }) => void;
|
|
169
|
+
onCancel?: () => void;
|
|
170
|
+
onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
|
|
171
|
+
|
|
172
|
+
constructor(
|
|
173
|
+
tree: TreeSelectorNode[],
|
|
174
|
+
private readonly currentLeafId: string | null,
|
|
175
|
+
private readonly maxVisibleLines: number,
|
|
176
|
+
initialFilterMode: FilterMode = "default",
|
|
177
|
+
initialSelectedId?: string,
|
|
178
|
+
) {
|
|
179
|
+
this.#filterMode = initialFilterMode;
|
|
180
|
+
this.#multipleRoots = tree.length > 1;
|
|
181
|
+
this.#indexSession(tree);
|
|
182
|
+
this.#tree = new TreeView<TreeSelectorNode, string>({
|
|
183
|
+
roots: this.#roots,
|
|
184
|
+
getKey: node => node.entry.id,
|
|
185
|
+
getChildren: node => this.#orderedChildren(node),
|
|
186
|
+
getChildDepth: (_node, row, children) =>
|
|
187
|
+
children.length > 1 || (this.#multipleRoots && row.parentKey === undefined) ? row.depth + 1 : row.depth,
|
|
188
|
+
theme,
|
|
189
|
+
filter: this.#buildFilter(),
|
|
190
|
+
maxRows: maxVisibleLines,
|
|
191
|
+
scrollbar: true,
|
|
192
|
+
scrollbarTheme: {
|
|
193
|
+
track: text => theme.fg("muted", text),
|
|
194
|
+
thumb: text => theme.fg("accent", text),
|
|
195
|
+
},
|
|
196
|
+
renderPrefix: (row, context) => this.#renderGutter(row, context.width, context.windowRows),
|
|
197
|
+
renderLeading: (_item, context) => (context.selected ? theme.fg("accent", "› ") : " "),
|
|
198
|
+
renderRow: (node, context) => this.#renderTreeRow(node, context.selected),
|
|
199
|
+
styleSelected: line => theme.bg("selectedBg", line),
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Start with initialSelectedId if provided, otherwise current leaf.
|
|
203
|
+
// A null target selects the last row; unknown ids resolve to their
|
|
204
|
+
// nearest visible ancestor inside TreeView.
|
|
205
|
+
const targetId = initialSelectedId ?? currentLeafId;
|
|
206
|
+
if (targetId === null || targetId === undefined) {
|
|
207
|
+
this.#tree.setSelectionIndex(this.#tree.rows.length - 1);
|
|
208
|
+
} else {
|
|
209
|
+
this.#tree.setSelectedKey(targetId);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Session-specific projection index. Iteratively collects tool calls for
|
|
215
|
+
* later lookup, maps ids to nodes/parents, marks active-descendant
|
|
216
|
+
* membership, orders the current branch first, and records the active
|
|
217
|
+
* path. Hierarchy shape, filtering, and selection live in TreeView.
|
|
218
|
+
*/
|
|
219
|
+
#indexSession(roots: TreeSelectorNode[]): void {
|
|
220
|
+
this.#toolCallMap.clear();
|
|
221
|
+
this.#containsActive.clear();
|
|
222
|
+
this.#nodeById.clear();
|
|
223
|
+
this.#activePathIds.clear();
|
|
224
|
+
|
|
225
|
+
const parentIds = new Map<string, string | null>();
|
|
226
|
+
const allNodes: TreeSelectorNode[] = [];
|
|
227
|
+
const visitStack: TreeSelectorNode[] = [...roots];
|
|
228
|
+
while (visitStack.length > 0) {
|
|
229
|
+
const node = visitStack.pop()!;
|
|
230
|
+
allNodes.push(node);
|
|
231
|
+
this.#nodeById.set(node.entry.id, node);
|
|
232
|
+
const entry = node.entry;
|
|
233
|
+
if (entry.type === "message" && entry.message.role === "assistant" && "content" in entry.message) {
|
|
234
|
+
const content = entry.message.content;
|
|
235
|
+
if (Array.isArray(content)) {
|
|
236
|
+
for (const block of content) {
|
|
237
|
+
if (typeof block === "object" && block !== null && "type" in block && block.type === "toolCall") {
|
|
238
|
+
const tc = block as { id: string; name: string; arguments: Record<string, unknown> };
|
|
239
|
+
this.#toolCallMap.set(tc.id, { name: tc.name, arguments: tc.arguments });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (let i = node.children.length - 1; i >= 0; i--) {
|
|
245
|
+
const child = node.children[i];
|
|
246
|
+
parentIds.set(child.entry.id, node.entry.id);
|
|
247
|
+
visitStack.push(child);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
for (const root of roots) {
|
|
251
|
+
if (!parentIds.has(root.entry.id)) parentIds.set(root.entry.id, null);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Post-order active-descendant membership (children before parents).
|
|
255
|
+
const leafId = this.currentLeafId;
|
|
256
|
+
for (let i = allNodes.length - 1; i >= 0; i--) {
|
|
257
|
+
const node = allNodes[i];
|
|
258
|
+
let has = leafId !== null && node.entry.id === leafId;
|
|
259
|
+
for (const child of node.children) {
|
|
260
|
+
if (this.#containsActive.get(child)) {
|
|
261
|
+
has = true;
|
|
262
|
+
break;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
this.#containsActive.set(node, has);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Current branch first; stable sort preserves domain order otherwise.
|
|
269
|
+
this.#roots = [...roots].sort(
|
|
270
|
+
(a, b) => Number(this.#containsActive.get(b)) - Number(this.#containsActive.get(a)),
|
|
271
|
+
);
|
|
272
|
+
this.#rootIds = new Set(this.#roots.map(root => root.entry.id));
|
|
273
|
+
|
|
274
|
+
// Active path from the current leaf back to its root.
|
|
275
|
+
let currentId: string | null = this.currentLeafId;
|
|
276
|
+
while (currentId) {
|
|
277
|
+
this.#activePathIds.add(currentId);
|
|
278
|
+
const parentId = parentIds.get(currentId);
|
|
279
|
+
if (parentId === undefined || parentId === null) break;
|
|
280
|
+
currentId = parentId;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** Children with the branch containing the active leaf first. */
|
|
285
|
+
#orderedChildren(node: TreeSelectorNode): readonly TreeSelectorNode[] {
|
|
286
|
+
const children = node.children;
|
|
287
|
+
let hasActive = false;
|
|
288
|
+
for (const child of children) {
|
|
289
|
+
if (this.#containsActive.get(child)) {
|
|
290
|
+
hasActive = true;
|
|
291
|
+
break;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!hasActive) return children;
|
|
295
|
+
return [...children].sort((a, b) => Number(this.#containsActive.get(b)) - Number(this.#containsActive.get(a)));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
#applyFilter(): void {
|
|
299
|
+
// TreeView retains the nearest visible selection (including an anchor
|
|
300
|
+
// through empty filter results) when the predicate is replaced.
|
|
301
|
+
this.#tree.setFilter(this.#buildFilter());
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Visible-row predicate combining the assistant-text rule, filter mode, and fuzzy query. */
|
|
305
|
+
#buildFilter(): (node: TreeSelectorNode, row: TreeRow<TreeSelectorNode, string>) => boolean {
|
|
306
|
+
const filterMode = this.#filterMode;
|
|
307
|
+
const leafId = this.currentLeafId;
|
|
308
|
+
const searchTokens = this.#searchQuery.toLowerCase().split(/\s+/).filter(Boolean);
|
|
309
|
+
return node => {
|
|
310
|
+
const entry = node.entry;
|
|
311
|
+
const isCurrentLeaf = entry.id === leafId;
|
|
312
|
+
|
|
313
|
+
// Skip assistant messages with only tool calls (no text) unless error/aborted
|
|
314
|
+
// Always show current leaf so active position is visible
|
|
315
|
+
if (entry.type === "message" && entry.message.role === "assistant" && !isCurrentLeaf) {
|
|
316
|
+
const msg = entry.message as { stopReason?: string; content?: unknown };
|
|
317
|
+
const hasText = this.#hasTextContent(msg.content);
|
|
318
|
+
const isErrorOrAborted = msg.stopReason && msg.stopReason !== "stop" && msg.stopReason !== "toolUse";
|
|
319
|
+
// Only hide if no text AND not an error/aborted message
|
|
320
|
+
if (!hasText && !isErrorOrAborted) {
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Apply filter mode
|
|
326
|
+
let passesFilter = true;
|
|
327
|
+
// Entry types hidden in default view (settings/bookkeeping). These carry
|
|
328
|
+
// no conversation content, so the tree only shows them in "all" mode.
|
|
329
|
+
const isSettingsEntry =
|
|
330
|
+
entry.type === "label" ||
|
|
331
|
+
entry.type === "custom" ||
|
|
332
|
+
entry.type === "model_change" ||
|
|
333
|
+
entry.type === "model_usage" ||
|
|
334
|
+
entry.type === "thinking_level_change" ||
|
|
335
|
+
entry.type === "service_tier_change" ||
|
|
336
|
+
entry.type === "title_change" ||
|
|
337
|
+
entry.type === "credential_pin" ||
|
|
338
|
+
entry.type === "session_init" ||
|
|
339
|
+
entry.type === "ttsr_injection" ||
|
|
340
|
+
entry.type === "mode_change" ||
|
|
341
|
+
entry.type === "reset_boundary";
|
|
342
|
+
|
|
343
|
+
switch (filterMode) {
|
|
344
|
+
case "user-only":
|
|
345
|
+
// Just user requests (plain prompts and user-invoked skill/collab prompts)
|
|
346
|
+
passesFilter = isUserRequestEntry(entry);
|
|
347
|
+
break;
|
|
348
|
+
case "no-tools":
|
|
349
|
+
// Default minus tool results
|
|
350
|
+
passesFilter = !isSettingsEntry && !(entry.type === "message" && entry.message.role === "toolResult");
|
|
351
|
+
break;
|
|
352
|
+
case "labeled-only":
|
|
353
|
+
// Just labeled entries
|
|
354
|
+
passesFilter = node.label !== undefined;
|
|
355
|
+
break;
|
|
356
|
+
case "all":
|
|
357
|
+
// Show everything
|
|
358
|
+
passesFilter = true;
|
|
359
|
+
break;
|
|
360
|
+
default:
|
|
361
|
+
// Default mode: hide settings/bookkeeping entries
|
|
362
|
+
passesFilter = !isSettingsEntry;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!passesFilter) return false;
|
|
367
|
+
|
|
368
|
+
// Apply fuzzy search filter
|
|
369
|
+
if (searchTokens.length > 0) {
|
|
370
|
+
const nodeText = this.#getSearchableText(node);
|
|
371
|
+
return searchTokens.every(token => fuzzyMatch(token, nodeText).matches);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return true;
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Get searchable text content from a node */
|
|
379
|
+
#getSearchableText(node: TreeSelectorNode): string {
|
|
380
|
+
const entry = node.entry;
|
|
381
|
+
const parts: string[] = [];
|
|
382
|
+
|
|
383
|
+
if (node.label) {
|
|
384
|
+
parts.push(node.label);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
switch (entry.type) {
|
|
388
|
+
case "message": {
|
|
389
|
+
const msg = entry.message;
|
|
390
|
+
parts.push(msg.role);
|
|
391
|
+
if ("content" in msg && msg.content) {
|
|
392
|
+
parts.push(this.#extractContent(msg.content));
|
|
393
|
+
}
|
|
394
|
+
if (msg.role === "bashExecution") {
|
|
395
|
+
const bashMsg = msg as { command?: string };
|
|
396
|
+
if (bashMsg.command) parts.push(bashMsg.command);
|
|
397
|
+
}
|
|
398
|
+
break;
|
|
399
|
+
}
|
|
400
|
+
case "custom_message": {
|
|
401
|
+
parts.push(entry.customType);
|
|
402
|
+
if (entry.customType === "advisor") {
|
|
403
|
+
const { qualifier, text } = advisorTreeDisplay(entry.details);
|
|
404
|
+
if (qualifier) parts.push(qualifier);
|
|
405
|
+
if (text) parts.push(text);
|
|
406
|
+
} else {
|
|
407
|
+
const content = stripSystemWrapperTags(this.#joinTextContent(entry.content)).slice(0, SEARCH_TEXT_LIMIT);
|
|
408
|
+
if (content) parts.push(content);
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
case "compaction":
|
|
413
|
+
parts.push("compaction");
|
|
414
|
+
break;
|
|
415
|
+
case "branch_summary":
|
|
416
|
+
parts.push("branch summary", entry.summary);
|
|
417
|
+
break;
|
|
418
|
+
case "model_change":
|
|
419
|
+
parts.push("model", entry.model);
|
|
420
|
+
break;
|
|
421
|
+
case "model_usage":
|
|
422
|
+
parts.push(
|
|
423
|
+
"model usage",
|
|
424
|
+
sanitizeTreeField(entry.purpose),
|
|
425
|
+
sanitizeTreeField(entry.role ?? ""),
|
|
426
|
+
sanitizeTreeField(entry.provider),
|
|
427
|
+
sanitizeTreeField(entry.model),
|
|
428
|
+
);
|
|
429
|
+
break;
|
|
430
|
+
case "thinking_level_change":
|
|
431
|
+
parts.push("thinking", entry.thinkingLevel ?? ThinkingLevel.Off);
|
|
432
|
+
break;
|
|
433
|
+
case "custom":
|
|
434
|
+
parts.push("custom", entry.customType);
|
|
435
|
+
break;
|
|
436
|
+
case "label":
|
|
437
|
+
parts.push("label", entry.label ?? "");
|
|
438
|
+
break;
|
|
439
|
+
case "service_tier_change":
|
|
440
|
+
parts.push("service tier");
|
|
441
|
+
if (entry.serviceTier) {
|
|
442
|
+
const serviceTier = entry.serviceTier;
|
|
443
|
+
for (const family in serviceTier) {
|
|
444
|
+
const tier = serviceTier[family as keyof typeof serviceTier];
|
|
445
|
+
if (tier) parts.push(family, tier);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
case "title_change":
|
|
450
|
+
parts.push("title", entry.title);
|
|
451
|
+
break;
|
|
452
|
+
case "mode_change":
|
|
453
|
+
parts.push("mode", entry.mode);
|
|
454
|
+
break;
|
|
455
|
+
case "credential_pin":
|
|
456
|
+
parts.push("credential pin", entry.provider);
|
|
457
|
+
break;
|
|
458
|
+
case "ttsr_injection":
|
|
459
|
+
parts.push("ttsr injection", ...entry.injectedRules);
|
|
460
|
+
break;
|
|
461
|
+
case "reset_boundary":
|
|
462
|
+
parts.push("reset boundary");
|
|
463
|
+
break;
|
|
464
|
+
case "session_init":
|
|
465
|
+
parts.push("session init");
|
|
466
|
+
break;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return parts.join(" ");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
invalidate(): void {
|
|
473
|
+
this.#tree.invalidate();
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
dispose(): void {
|
|
477
|
+
this.#tree.dispose();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
getSearchQuery(): string {
|
|
481
|
+
return this.#searchQuery;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
getSelectedNode(): TreeSelectorNode | undefined {
|
|
485
|
+
return this.#tree.selectedItem;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
updateNodeLabel(entryId: string, label: string | undefined): void {
|
|
489
|
+
const node = this.#nodeById.get(entryId);
|
|
490
|
+
if (node) node.label = label;
|
|
491
|
+
this.#tree.invalidate();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
#getFilterLabel(): string {
|
|
495
|
+
switch (this.#filterMode) {
|
|
496
|
+
case "no-tools":
|
|
497
|
+
return " [no-tools]";
|
|
498
|
+
case "user-only":
|
|
499
|
+
return " [user]";
|
|
500
|
+
case "labeled-only":
|
|
501
|
+
return " [labeled]";
|
|
502
|
+
case "all":
|
|
503
|
+
return " [all]";
|
|
504
|
+
default:
|
|
505
|
+
return "";
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
render(width: number): readonly string[] {
|
|
510
|
+
const lines: string[] = [];
|
|
511
|
+
const totalCount = this.#tree.allRows.length;
|
|
512
|
+
|
|
513
|
+
if (this.#tree.rows.length === 0) {
|
|
514
|
+
// Three empty-state shapes:
|
|
515
|
+
// - no rows at all → no entries at all (truly fresh session).
|
|
516
|
+
// - search query rejects everything → tell the user the search is the cause.
|
|
517
|
+
// - filter mode rejects everything → tell the user the filter is the cause and
|
|
518
|
+
// how to widen it. Otherwise fresh sessions whose only persisted entries are
|
|
519
|
+
// `model_change` + `thinking_level_change` (both hidden by the default filter)
|
|
520
|
+
// read as "broken /tree" — see #1909.
|
|
521
|
+
if (totalCount === 0) {
|
|
522
|
+
lines.push(truncateToWidth(theme.fg("muted", "No entries found"), width));
|
|
523
|
+
lines.push(truncateToWidth(theme.fg("muted", `(0/0)${this.#getFilterLabel()}`), width));
|
|
524
|
+
} else if (this.#searchQuery.length > 0) {
|
|
525
|
+
lines.push(truncateToWidth(theme.fg("muted", `No entries match search "${this.#searchQuery}"`), width));
|
|
526
|
+
lines.push(truncateToWidth(theme.fg("muted", "Press Backspace to clear the search"), width));
|
|
527
|
+
lines.push(truncateToWidth(theme.fg("muted", `(0/${totalCount})${this.#getFilterLabel()}`), width));
|
|
528
|
+
} else {
|
|
529
|
+
const filterLabel = this.#getFilterLabel().trim() || "[default]";
|
|
530
|
+
lines.push(
|
|
531
|
+
truncateToWidth(
|
|
532
|
+
theme.fg("muted", `${totalCount} entries hidden by the current filter ${filterLabel}`),
|
|
533
|
+
width,
|
|
534
|
+
),
|
|
535
|
+
);
|
|
536
|
+
lines.push(truncateToWidth(theme.fg("muted", "Press Alt+A to show all, Alt+D for default"), width));
|
|
537
|
+
lines.push(truncateToWidth(theme.fg("muted", `(0/${totalCount})${this.#getFilterLabel()}`), width));
|
|
538
|
+
}
|
|
539
|
+
return lines;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Hierarchy, selection-centered windowing, and the scrollbar all live
|
|
543
|
+
// in TreeView; the gutter/row builders below only shape its lines.
|
|
544
|
+
lines.push(...this.#tree.render(width));
|
|
545
|
+
|
|
546
|
+
const filterLabel = this.#getFilterLabel();
|
|
547
|
+
if (filterLabel) {
|
|
548
|
+
lines.push(truncateToWidth(theme.fg("muted", filterLabel.trim()), width));
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
return lines;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
/** Entry text after the tree gutter: active-path marker, label, and display row. */
|
|
555
|
+
#renderTreeRow(node: TreeSelectorNode, selected: boolean): string {
|
|
556
|
+
// Active path marker - shown right before the entry text
|
|
557
|
+
const isOnActivePath = this.#activePathIds.has(node.entry.id);
|
|
558
|
+
const pathMarker = isOnActivePath ? theme.fg("accent", `${theme.md.bullet} `) : "";
|
|
559
|
+
const label = node.label ? theme.fg("warning", `[${node.label}] `) : "";
|
|
560
|
+
return `${pathMarker}${label}${this.#getEntryDisplayText(node, selected)}`;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* Custom gutter geometry over TreeView rows. Connectors draw only at real
|
|
565
|
+
* branch points (`siblingCount > 1`); session roots under the virtual
|
|
566
|
+
* multi-root stay connector-free, and linear chains keep their branch
|
|
567
|
+
* head's depth. Gutter depth is capped like before (issue #1144): each
|
|
568
|
+
* level renders as 3 cells, older levels compress behind a leading `…`,
|
|
569
|
+
* and one shared horizontal offset keeps every row describing one shape.
|
|
570
|
+
*/
|
|
571
|
+
#renderGutter(
|
|
572
|
+
row: TreeRow<TreeSelectorNode, string>,
|
|
573
|
+
width: number,
|
|
574
|
+
windowRows: readonly TreeRow<TreeSelectorNode, string>[],
|
|
575
|
+
): { first: string; continuation: string } {
|
|
576
|
+
// Cap the per-row gutter prefix so a content budget is always preserved.
|
|
577
|
+
// Reserve at least MIN_CONTENT_COLS for entry text — or half the
|
|
578
|
+
// viewport, whichever is larger — and compress older gutter levels
|
|
579
|
+
// off-screen behind a leading ellipsis when the row would exceed budget.
|
|
580
|
+
const MIN_CONTENT_COLS = 24;
|
|
581
|
+
const OVERHEAD_COLS = 4; // cursor (2) + a touch of breathing room
|
|
582
|
+
const contentReserve = Math.max(MIN_CONTENT_COLS, Math.floor(width / 2));
|
|
583
|
+
const maxIndentLevels = Math.max(1, Math.floor((width - contentReserve - OVERHEAD_COLS) / 3));
|
|
584
|
+
|
|
585
|
+
// One horizontal scroll position for the whole window: a per-row offset
|
|
586
|
+
// would put a different tree depth in the same column on each line, so
|
|
587
|
+
// connectors and gutters would stop describing one shape.
|
|
588
|
+
let deepest = row.depth;
|
|
589
|
+
for (const windowRow of windowRows) {
|
|
590
|
+
deepest = Math.max(deepest, windowRow.depth);
|
|
591
|
+
}
|
|
592
|
+
const windowOffset = Math.max(0, deepest - maxIndentLevels);
|
|
593
|
+
|
|
594
|
+
const displayIndent = row.depth;
|
|
595
|
+
const hasConnector = row.parentKey !== undefined && row.siblingCount > 1;
|
|
596
|
+
const connectorSymbol = hasConnector ? (row.isLast ? theme.tree.last : theme.tree.branch) : "";
|
|
597
|
+
const connectorChars = hasConnector ? Array.from(connectorSymbol) : [];
|
|
598
|
+
const scrollOffset = Math.min(windowOffset, displayIndent);
|
|
599
|
+
const renderedIndent = displayIndent - scrollOffset;
|
|
600
|
+
const connectorPositionDisplay = hasConnector ? renderedIndent - 1 : -1;
|
|
601
|
+
// Linear rows reuse their branch head's depth. Existing sibling
|
|
602
|
+
// gutters remain visible; terminal gutters remain terminated.
|
|
603
|
+
|
|
604
|
+
// Build prefix char by char, placing gutters and connector at their positions
|
|
605
|
+
const totalChars = renderedIndent * 3;
|
|
606
|
+
const prefixChars: string[] = [];
|
|
607
|
+
for (let i = 0; i < totalChars; i++) {
|
|
608
|
+
const level = Math.floor(i / 3);
|
|
609
|
+
const originalDepth = level + scrollOffset;
|
|
610
|
+
const posInLevel = i % 3;
|
|
611
|
+
|
|
612
|
+
// An ancestor branch point draws its gutter at its own depth minus
|
|
613
|
+
// one — the level its connector occupied. Session roots never emit
|
|
614
|
+
// gutters; their connectors are suppressed.
|
|
615
|
+
const gutterAncestor = row.ancestors.find(
|
|
616
|
+
ancestor =>
|
|
617
|
+
ancestor.depth - 1 === originalDepth &&
|
|
618
|
+
ancestor.siblingCount > 1 &&
|
|
619
|
+
!(this.#multipleRoots && this.#rootIds.has(ancestor.key)),
|
|
620
|
+
);
|
|
621
|
+
if (gutterAncestor) {
|
|
622
|
+
// Gutters follow standard tree semantics: `│` only while more
|
|
623
|
+
// siblings continue below, space below a `└─`.
|
|
624
|
+
if (posInLevel === 0) {
|
|
625
|
+
prefixChars.push(gutterAncestor.isLast ? " " : theme.tree.vertical);
|
|
626
|
+
} else {
|
|
627
|
+
prefixChars.push(" ");
|
|
628
|
+
}
|
|
629
|
+
} else if (hasConnector && level === connectorPositionDisplay) {
|
|
630
|
+
// Connector at this level
|
|
631
|
+
if (posInLevel === 0) {
|
|
632
|
+
prefixChars.push(connectorChars[0] ?? " ");
|
|
633
|
+
} else if (posInLevel === 1) {
|
|
634
|
+
prefixChars.push(connectorChars[1] ?? theme.tree.horizontal);
|
|
635
|
+
} else {
|
|
636
|
+
prefixChars.push(connectorChars[2] ?? " ");
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
prefixChars.push(" ");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
// Mark the leftmost cell when ancestors were compressed off-screen.
|
|
643
|
+
if (scrollOffset > 0 && prefixChars.length > 0) {
|
|
644
|
+
prefixChars[0] = "…";
|
|
645
|
+
}
|
|
646
|
+
const prefix = theme.fg("dim", prefixChars.join(""));
|
|
647
|
+
return { first: prefix, continuation: prefix };
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
#getEntryDisplayText(node: TreeSelectorNode, isSelected: boolean): string {
|
|
651
|
+
const entry = node.entry;
|
|
652
|
+
let result: string;
|
|
653
|
+
|
|
654
|
+
const normalize = (s: string) => s.replace(/[\n\t]/g, " ").trim();
|
|
655
|
+
|
|
656
|
+
switch (entry.type) {
|
|
657
|
+
case "message": {
|
|
658
|
+
const msg = entry.message;
|
|
659
|
+
const role = msg.role;
|
|
660
|
+
if (role === "user") {
|
|
661
|
+
const msgWithContent = msg as { content?: unknown };
|
|
662
|
+
const content = normalize(this.#extractContent(msgWithContent.content));
|
|
663
|
+
result = theme.fg("accent", "user: ") + content;
|
|
664
|
+
} else if (role === "developer") {
|
|
665
|
+
const msgWithContent = msg as { content?: unknown };
|
|
666
|
+
const content = normalize(this.#extractContent(msgWithContent.content));
|
|
667
|
+
result = theme.fg("dim", "developer: ") + theme.fg("muted", content);
|
|
668
|
+
} else if (role === "assistant") {
|
|
669
|
+
const presentation = resolveAssistantErrorPresentation(msg);
|
|
670
|
+
if (presentation.kind === "compact-recovered") {
|
|
671
|
+
result = theme.fg("success", "assistant: ") + theme.fg("dim", presentation.text);
|
|
672
|
+
break;
|
|
673
|
+
}
|
|
674
|
+
const msgWithContent = msg as { content?: unknown; stopReason?: string; errorMessage?: string };
|
|
675
|
+
const textContent = normalize(this.#extractContent(msgWithContent.content));
|
|
676
|
+
if (textContent) {
|
|
677
|
+
result = theme.fg("success", "assistant: ") + textContent;
|
|
678
|
+
} else if (presentation.kind === "full") {
|
|
679
|
+
result =
|
|
680
|
+
theme.fg("success", "assistant: ") + theme.fg("error", normalize(presentation.text).slice(0, 80));
|
|
681
|
+
} else if (msgWithContent.stopReason === "aborted") {
|
|
682
|
+
result = theme.fg("success", "assistant: ") + theme.fg("muted", "(aborted)");
|
|
683
|
+
} else {
|
|
684
|
+
result = theme.fg("success", "assistant: ") + theme.fg("muted", "(no content)");
|
|
685
|
+
}
|
|
686
|
+
} else if (role === "toolResult") {
|
|
687
|
+
const toolMsg = msg as { toolCallId?: string; toolName?: string };
|
|
688
|
+
const toolCall = toolMsg.toolCallId ? this.#toolCallMap.get(toolMsg.toolCallId) : undefined;
|
|
689
|
+
if (toolCall) {
|
|
690
|
+
result = theme.fg("muted", this.#formatToolCall(toolCall.name, toolCall.arguments));
|
|
691
|
+
} else {
|
|
692
|
+
result = theme.fg("muted", `[${toolMsg.toolName ?? "tool"}]`);
|
|
693
|
+
}
|
|
694
|
+
} else if (role === "bashExecution") {
|
|
695
|
+
const bashMsg = msg as { command?: string };
|
|
696
|
+
result = theme.fg("dim", `[bash]: ${normalize(bashMsg.command ?? "")}`);
|
|
697
|
+
} else {
|
|
698
|
+
result = theme.fg("dim", `[${role}]`);
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
case "custom_message": {
|
|
703
|
+
if (entry.customType === "advisor") {
|
|
704
|
+
const { qualifier, text } = advisorTreeDisplay(entry.details);
|
|
705
|
+
const label = qualifier ? `advisor (${qualifier}): ` : "advisor: ";
|
|
706
|
+
result = theme.fg("customMessageLabel", label) + normalize(text);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
const content = stripSystemWrapperTags(this.#joinTextContent(entry.content));
|
|
710
|
+
result = theme.fg("customMessageLabel", `[${entry.customType}]: `) + normalize(content);
|
|
711
|
+
break;
|
|
712
|
+
}
|
|
713
|
+
case "compaction": {
|
|
714
|
+
const tokens = Math.round(entry.tokensBefore / 1000);
|
|
715
|
+
result = theme.fg("borderAccent", `[compaction: ${tokens}k tokens]`);
|
|
716
|
+
break;
|
|
717
|
+
}
|
|
718
|
+
case "branch_summary":
|
|
719
|
+
result = theme.fg("warning", `[branch summary]: `) + normalize(entry.summary);
|
|
720
|
+
break;
|
|
721
|
+
case "model_change":
|
|
722
|
+
result = theme.fg("dim", `[model: ${entry.model}]`);
|
|
723
|
+
break;
|
|
724
|
+
case "model_usage": {
|
|
725
|
+
const purpose = sanitizeTreeField(entry.purpose);
|
|
726
|
+
const role = sanitizeTreeField(entry.role ?? "");
|
|
727
|
+
const provider = sanitizeTreeField(entry.provider);
|
|
728
|
+
const model = sanitizeTreeField(entry.model);
|
|
729
|
+
result = theme.fg("dim", `[model usage: ${purpose} ${role ? `${role} ` : ""}${provider}/${model}]`);
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
case "thinking_level_change":
|
|
733
|
+
result = theme.fg("dim", `[thinking: ${entry.thinkingLevel ?? ThinkingLevel.Off}]`);
|
|
734
|
+
break;
|
|
735
|
+
case "custom":
|
|
736
|
+
result = theme.fg("dim", `[custom: ${entry.customType}]`);
|
|
737
|
+
break;
|
|
738
|
+
case "label":
|
|
739
|
+
result = theme.fg("dim", `[label: ${entry.label ?? "(cleared)"}]`);
|
|
740
|
+
break;
|
|
741
|
+
case "service_tier_change": {
|
|
742
|
+
// Per-family map, or null when the session went back to the default.
|
|
743
|
+
const tiers = entry.serviceTier
|
|
744
|
+
? Object.entries(entry.serviceTier)
|
|
745
|
+
.map(([family, tier]) => `${family}:${tier}`)
|
|
746
|
+
.join(" ")
|
|
747
|
+
: "(default)";
|
|
748
|
+
result = theme.fg("dim", `[service tier: ${tiers}]`);
|
|
749
|
+
break;
|
|
750
|
+
}
|
|
751
|
+
case "title_change":
|
|
752
|
+
result = theme.fg("dim", `[title: ${normalize(entry.title)}]`);
|
|
753
|
+
break;
|
|
754
|
+
case "mode_change":
|
|
755
|
+
result = theme.fg("dim", `[mode: ${entry.mode}]`);
|
|
756
|
+
break;
|
|
757
|
+
case "credential_pin":
|
|
758
|
+
result = theme.fg("dim", `[credential pin: ${entry.provider}]`);
|
|
759
|
+
break;
|
|
760
|
+
default:
|
|
761
|
+
// Bookkeeping entries with nothing worth spelling out still get their
|
|
762
|
+
// type. A row that renders to the empty string is worse than a
|
|
763
|
+
// useless one: it draws as a bare bullet with no way to tell what it
|
|
764
|
+
// is or why the tree has a gap in it.
|
|
765
|
+
result = theme.fg("dim", `[${entry.type.replaceAll("_", " ")}]`);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
return isSelected ? theme.bold(result) : result;
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
#extractContent(content: unknown): string {
|
|
772
|
+
return this.#joinTextContent(content).slice(0, SEARCH_TEXT_LIMIT);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Concatenate every text block (or return a string as-is) with no length cap. */
|
|
776
|
+
#joinTextContent(content: unknown): string {
|
|
777
|
+
if (typeof content === "string") return content;
|
|
778
|
+
if (Array.isArray(content)) {
|
|
779
|
+
let result = "";
|
|
780
|
+
for (const c of content) {
|
|
781
|
+
if (
|
|
782
|
+
typeof c === "object" &&
|
|
783
|
+
c !== null &&
|
|
784
|
+
"type" in c &&
|
|
785
|
+
c.type === "text" &&
|
|
786
|
+
"text" in c &&
|
|
787
|
+
typeof c.text === "string"
|
|
788
|
+
) {
|
|
789
|
+
result += c.text;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return result;
|
|
793
|
+
}
|
|
794
|
+
return "";
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
#hasTextContent(content: unknown): boolean {
|
|
798
|
+
if (typeof content === "string") return Boolean(canonicalizeMessage(content));
|
|
799
|
+
if (Array.isArray(content)) {
|
|
800
|
+
for (const c of content) {
|
|
801
|
+
if (typeof c === "object" && c !== null && "type" in c && c.type === "text") {
|
|
802
|
+
const text = (c as { text?: string }).text;
|
|
803
|
+
if (text && canonicalizeMessage(text)) return true;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return false;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
#formatToolCall(name: string, args: Record<string, unknown>): string {
|
|
811
|
+
switch (name) {
|
|
812
|
+
case "read": {
|
|
813
|
+
const path = shortenPath(String(args.path || args.file_path || ""));
|
|
814
|
+
const offset = args.offset as number | undefined;
|
|
815
|
+
const limit = args.limit as number | undefined;
|
|
816
|
+
let display = path;
|
|
817
|
+
if (offset !== undefined || limit !== undefined) {
|
|
818
|
+
const start = offset ?? 1;
|
|
819
|
+
const end = limit !== undefined ? start + limit - 1 : "";
|
|
820
|
+
display += `:${start}${end ? `-${end}` : ""}`;
|
|
821
|
+
}
|
|
822
|
+
return `[read: ${display}]`;
|
|
823
|
+
}
|
|
824
|
+
case "write": {
|
|
825
|
+
const path = shortenPath(String(args.path || args.file_path || ""));
|
|
826
|
+
return `[write: ${path}]`;
|
|
827
|
+
}
|
|
828
|
+
case "edit": {
|
|
829
|
+
const path = shortenPath(String(args.path || args.file_path || ""));
|
|
830
|
+
return `[edit: ${path}]`;
|
|
831
|
+
}
|
|
832
|
+
case "bash": {
|
|
833
|
+
const rawCmd = String(args.command || "");
|
|
834
|
+
const cmd = rawCmd
|
|
835
|
+
.replace(/[\n\t]/g, " ")
|
|
836
|
+
.trim()
|
|
837
|
+
.slice(0, 50);
|
|
838
|
+
return `[bash: ${cmd}${rawCmd.length > 50 ? "..." : ""}]`;
|
|
839
|
+
}
|
|
840
|
+
case "grep": {
|
|
841
|
+
const pattern = String(args.pattern || "");
|
|
842
|
+
const searchPathsInput =
|
|
843
|
+
typeof args.paths === "string" || Array.isArray(args.paths)
|
|
844
|
+
? args.paths
|
|
845
|
+
: typeof args.path === "string"
|
|
846
|
+
? args.path
|
|
847
|
+
: undefined;
|
|
848
|
+
const paths = toPathList(searchPathsInput);
|
|
849
|
+
const scope = paths.length > 0 ? paths.join(", ") : ".";
|
|
850
|
+
return `[grep: /${pattern}/ in ${shortenPath(scope)}]`;
|
|
851
|
+
}
|
|
852
|
+
case "glob": {
|
|
853
|
+
const globInput =
|
|
854
|
+
typeof args.path === "string"
|
|
855
|
+
? args.path
|
|
856
|
+
: typeof args.paths === "string" || Array.isArray(args.paths)
|
|
857
|
+
? args.paths
|
|
858
|
+
: undefined;
|
|
859
|
+
const paths = toPathList(globInput);
|
|
860
|
+
const scope = paths.length > 0 ? paths.join(", ") : ".";
|
|
861
|
+
return `[glob: ${shortenPath(scope)}]`;
|
|
862
|
+
}
|
|
863
|
+
case "ls": {
|
|
864
|
+
const path = shortenPath(String(args.path || "."));
|
|
865
|
+
return `[ls: ${path}]`;
|
|
866
|
+
}
|
|
867
|
+
default: {
|
|
868
|
+
// Custom tool - show name and truncated JSON args
|
|
869
|
+
const argsStr = JSON.stringify(args).slice(0, 40);
|
|
870
|
+
return `[${name}: ${argsStr}${JSON.stringify(args).length > 40 ? "..." : ""}]`;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
handleInput(keyData: string): void {
|
|
876
|
+
if (matchesSelectUp(keyData)) {
|
|
877
|
+
this.#tree.moveSelection(-1, true);
|
|
878
|
+
} else if (matchesSelectDown(keyData)) {
|
|
879
|
+
this.#tree.moveSelection(1, true);
|
|
880
|
+
} else if (matchesKey(keyData, "alt+up")) {
|
|
881
|
+
this.#tree.moveSelectionWhere(
|
|
882
|
+
node =>
|
|
883
|
+
node.entry.type === "message" &&
|
|
884
|
+
(node.entry.message.role === "user" || node.entry.message.role === "assistant"),
|
|
885
|
+
-1,
|
|
886
|
+
);
|
|
887
|
+
} else if (matchesKey(keyData, "alt+down")) {
|
|
888
|
+
this.#tree.moveSelectionWhere(
|
|
889
|
+
node =>
|
|
890
|
+
node.entry.type === "message" &&
|
|
891
|
+
(node.entry.message.role === "user" || node.entry.message.role === "assistant"),
|
|
892
|
+
1,
|
|
893
|
+
);
|
|
894
|
+
} else if (matchesKey(keyData, "home")) {
|
|
895
|
+
this.#tree.setSelectionIndex(0);
|
|
896
|
+
} else if (matchesKey(keyData, "end")) {
|
|
897
|
+
this.#tree.setSelectionIndex(this.#tree.rows.length - 1);
|
|
898
|
+
} else if (matchesSelectPageUp(keyData) || matchesKey(keyData, "left")) {
|
|
899
|
+
this.#tree.moveSelection(-this.maxVisibleLines);
|
|
900
|
+
} else if (matchesSelectPageDown(keyData) || matchesKey(keyData, "right")) {
|
|
901
|
+
this.#tree.moveSelection(this.maxVisibleLines);
|
|
902
|
+
} else if (
|
|
903
|
+
matchesKey(keyData, "shift+enter") ||
|
|
904
|
+
matchesKey(keyData, "shift+return") ||
|
|
905
|
+
keyData === "\n" || // Shift+Enter delivered as bare LF (iTerm2 legacy mapping) — matches the composer (issue #8821)
|
|
906
|
+
keyData === "\x1b[13;2~" // Shift+Enter legacy CSI ~ form — also accepted by the composer (editor.ts:1466)
|
|
907
|
+
) {
|
|
908
|
+
// Summarize-and-switch: fork with a branch summary without the extra prompt.
|
|
909
|
+
const selected = this.#tree.selectedItem;
|
|
910
|
+
if (selected && this.onSelect) {
|
|
911
|
+
this.onSelect(selected.entry.id, { summarize: true });
|
|
912
|
+
}
|
|
913
|
+
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return")) {
|
|
914
|
+
const selected = this.#tree.selectedItem;
|
|
915
|
+
if (selected && this.onSelect) {
|
|
916
|
+
this.onSelect(selected.entry.id, { summarize: false });
|
|
917
|
+
}
|
|
918
|
+
} else if (matchesAppInterrupt(keyData)) {
|
|
919
|
+
if (this.#searchQuery) {
|
|
920
|
+
this.#searchQuery = "";
|
|
921
|
+
this.#applyFilter();
|
|
922
|
+
} else {
|
|
923
|
+
this.onCancel?.();
|
|
924
|
+
}
|
|
925
|
+
} else if (matchesKey(keyData, "ctrl+c")) {
|
|
926
|
+
this.onCancel?.();
|
|
927
|
+
} else if (matchesKey(keyData, "shift+ctrl+o") || matchesKey(keyData, "ctrl+shift+o")) {
|
|
928
|
+
// Cycle filter backwards
|
|
929
|
+
const modes = TREE_FILTER_MODES;
|
|
930
|
+
const currentIndex = modes.indexOf(this.#filterMode);
|
|
931
|
+
this.#filterMode = modes[(currentIndex - 1 + modes.length) % modes.length];
|
|
932
|
+
this.#applyFilter();
|
|
933
|
+
} else if (matchesKey(keyData, "ctrl+o")) {
|
|
934
|
+
// Cycle filter forwards: default → no-tools → user-only → labeled-only → all → default
|
|
935
|
+
const modes = TREE_FILTER_MODES;
|
|
936
|
+
const currentIndex = modes.indexOf(this.#filterMode);
|
|
937
|
+
this.#filterMode = modes[(currentIndex + 1) % modes.length];
|
|
938
|
+
this.#applyFilter();
|
|
939
|
+
} else if (matchesKey(keyData, "alt+d")) {
|
|
940
|
+
this.#filterMode = "default";
|
|
941
|
+
this.#applyFilter();
|
|
942
|
+
} else if (matchesKey(keyData, "alt+t")) {
|
|
943
|
+
this.#filterMode = "no-tools";
|
|
944
|
+
this.#applyFilter();
|
|
945
|
+
} else if (matchesKey(keyData, "alt+u")) {
|
|
946
|
+
this.#filterMode = "user-only";
|
|
947
|
+
this.#applyFilter();
|
|
948
|
+
} else if (matchesKey(keyData, "alt+l")) {
|
|
949
|
+
this.#filterMode = "labeled-only";
|
|
950
|
+
this.#applyFilter();
|
|
951
|
+
} else if (matchesKey(keyData, "alt+a")) {
|
|
952
|
+
this.#filterMode = "all";
|
|
953
|
+
this.#applyFilter();
|
|
954
|
+
} else if (matchesKey(keyData, "backspace")) {
|
|
955
|
+
if (this.#searchQuery.length > 0) {
|
|
956
|
+
this.#searchQuery = this.#searchQuery.slice(0, -1);
|
|
957
|
+
this.#applyFilter();
|
|
958
|
+
}
|
|
959
|
+
} else if (matchesKey(keyData, "shift+l") && !this.#searchQuery) {
|
|
960
|
+
const selected = this.#tree.selectedItem;
|
|
961
|
+
if (selected && this.onLabelEdit) {
|
|
962
|
+
this.onLabelEdit(selected.entry.id, selected.label);
|
|
963
|
+
}
|
|
964
|
+
} else {
|
|
965
|
+
const printableText = extractPrintableText(keyData);
|
|
966
|
+
if (printableText) {
|
|
967
|
+
this.#searchQuery += printableText;
|
|
968
|
+
this.#applyFilter();
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/** Component that displays the current search query */
|
|
975
|
+
class SearchLine implements Component {
|
|
976
|
+
constructor(private treeList: TreeList) {}
|
|
977
|
+
|
|
978
|
+
invalidate(): void {}
|
|
979
|
+
|
|
980
|
+
render(width: number): readonly string[] {
|
|
981
|
+
const query = this.treeList.getSearchQuery();
|
|
982
|
+
if (query) {
|
|
983
|
+
return [truncateToWidth(`${theme.fg("muted", "Search:")} ${theme.fg("accent", query)}`, width)];
|
|
984
|
+
}
|
|
985
|
+
return [truncateToWidth(theme.fg("muted", "Search:"), width)];
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
handleInput(_keyData: string): void {}
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/** Label input component shown when editing a label */
|
|
992
|
+
class LabelInput implements Component {
|
|
993
|
+
#input: Input;
|
|
994
|
+
onSubmit?: (entryId: string, label: string | undefined) => void;
|
|
995
|
+
onCancel?: () => void;
|
|
996
|
+
|
|
997
|
+
constructor(
|
|
998
|
+
private readonly entryId: string,
|
|
999
|
+
currentLabel: string | undefined,
|
|
1000
|
+
) {
|
|
1001
|
+
this.#input = new Input();
|
|
1002
|
+
if (currentLabel) {
|
|
1003
|
+
this.#input.setValue(currentLabel);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
invalidate(): void {}
|
|
1008
|
+
|
|
1009
|
+
render(width: number): readonly string[] {
|
|
1010
|
+
const lines: string[] = [];
|
|
1011
|
+
lines.push(truncateToWidth(theme.fg("muted", "Label (empty to remove):"), width));
|
|
1012
|
+
lines.push(...this.#input.render(width));
|
|
1013
|
+
lines.push(truncateToWidth(theme.fg("dim", "enter: save esc: cancel"), width));
|
|
1014
|
+
return lines;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
handleInput(keyData: string): void {
|
|
1018
|
+
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
1019
|
+
const value = this.#input.getValue().trim();
|
|
1020
|
+
this.onSubmit?.(this.entryId, value || undefined);
|
|
1021
|
+
} else if (matchesAppInterrupt(keyData)) {
|
|
1022
|
+
this.onCancel?.();
|
|
1023
|
+
} else {
|
|
1024
|
+
this.#input.handleInput(keyData);
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* Component that renders a session tree selector for navigation
|
|
1031
|
+
*/
|
|
1032
|
+
export class TreeSelectorComponent extends OverlayPanel {
|
|
1033
|
+
#treeList: TreeList;
|
|
1034
|
+
#labelInput: LabelInput | null = null;
|
|
1035
|
+
#labelInputContainer: Container;
|
|
1036
|
+
#treeContainer: Container;
|
|
1037
|
+
|
|
1038
|
+
constructor(
|
|
1039
|
+
tree: TreeSelectorNode[],
|
|
1040
|
+
currentLeafId: string | null,
|
|
1041
|
+
terminalHeight: number,
|
|
1042
|
+
onSelect: (entryId: string, options: { summarize: boolean }) => void,
|
|
1043
|
+
onCancel: () => void,
|
|
1044
|
+
private readonly onLabelChangeCallback?: (entryId: string, label: string | undefined) => void,
|
|
1045
|
+
initialFilterMode: FilterMode = "default",
|
|
1046
|
+
) {
|
|
1047
|
+
super("Session Tree");
|
|
1048
|
+
// The outer panel has eight fixed rows around the tree list: top/bottom
|
|
1049
|
+
// borders, the two spacers, help, search, and section divider.
|
|
1050
|
+
const PANEL_CHROME_ROWS = 8;
|
|
1051
|
+
const maxVisibleLines = Math.max(
|
|
1052
|
+
1,
|
|
1053
|
+
Math.min(Math.max(5, Math.floor(terminalHeight / 2)), terminalHeight - PANEL_CHROME_ROWS),
|
|
1054
|
+
);
|
|
1055
|
+
|
|
1056
|
+
this.#treeList = new TreeList(tree, currentLeafId, maxVisibleLines, initialFilterMode);
|
|
1057
|
+
this.#treeList.onSelect = onSelect;
|
|
1058
|
+
this.#treeList.onCancel = onCancel;
|
|
1059
|
+
this.#treeList.onLabelEdit = (entryId, currentLabel) => this.#showLabelInput(entryId, currentLabel);
|
|
1060
|
+
|
|
1061
|
+
this.#treeContainer = new Container();
|
|
1062
|
+
this.#treeContainer.addChild(this.#treeList);
|
|
1063
|
+
|
|
1064
|
+
this.#labelInputContainer = new Container();
|
|
1065
|
+
|
|
1066
|
+
this.addChild(new Spacer(1));
|
|
1067
|
+
this.addChild(
|
|
1068
|
+
new TruncatedText(
|
|
1069
|
+
theme.fg(
|
|
1070
|
+
"muted",
|
|
1071
|
+
"Enter: switch. Alt+↑/↓: previous/next turn. PgUp/PgDn (←/→): page. Home/End: first/last item. Shift+Enter: summarize & switch. Shift+L: label. Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
|
|
1072
|
+
),
|
|
1073
|
+
0,
|
|
1074
|
+
0,
|
|
1075
|
+
),
|
|
1076
|
+
);
|
|
1077
|
+
this.addChild(new SearchLine(this.#treeList));
|
|
1078
|
+
this.addChild(new PanelDivider());
|
|
1079
|
+
this.addChild(new Spacer(1));
|
|
1080
|
+
this.addChild(this.#treeContainer);
|
|
1081
|
+
this.addChild(this.#labelInputContainer);
|
|
1082
|
+
this.addChild(new Spacer(1));
|
|
1083
|
+
|
|
1084
|
+
if (tree.length === 0) {
|
|
1085
|
+
setTimeout(() => onCancel(), 100);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
#showLabelInput(entryId: string, currentLabel: string | undefined): void {
|
|
1090
|
+
this.#labelInput = new LabelInput(entryId, currentLabel);
|
|
1091
|
+
this.#labelInput.onSubmit = (id, label) => {
|
|
1092
|
+
this.#treeList.updateNodeLabel(id, label);
|
|
1093
|
+
this.onLabelChangeCallback?.(id, label);
|
|
1094
|
+
this.#hideLabelInput();
|
|
1095
|
+
};
|
|
1096
|
+
this.#labelInput.onCancel = () => this.#hideLabelInput();
|
|
1097
|
+
|
|
1098
|
+
this.#treeContainer.clear();
|
|
1099
|
+
this.#labelInputContainer.clear();
|
|
1100
|
+
this.#labelInputContainer.addChild(this.#labelInput);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
#hideLabelInput(): void {
|
|
1104
|
+
this.#labelInput = null;
|
|
1105
|
+
this.#labelInputContainer.clear();
|
|
1106
|
+
this.#treeContainer.clear();
|
|
1107
|
+
this.#treeContainer.addChild(this.#treeList);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
handleInput(keyData: string): void {
|
|
1111
|
+
if (this.#labelInput) {
|
|
1112
|
+
this.#labelInput.handleInput(keyData);
|
|
1113
|
+
} else {
|
|
1114
|
+
this.#treeList.handleInput(keyData);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
getTreeList(): TreeList {
|
|
1119
|
+
return this.#treeList;
|
|
1120
|
+
}
|
|
1121
|
+
}
|