@oh-my-pi/pi-tui 18.4.2 → 18.4.4
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 +56 -0
- package/dist/types/apps/cleanse-board.d.ts +17 -0
- package/dist/types/apps/git/help.d.ts +16 -0
- package/dist/types/apps/git/sidebar.d.ts +18 -0
- package/dist/types/apps/live-visualizer.d.ts +10 -0
- package/dist/types/apps/ps-top.d.ts +15 -0
- package/dist/types/apps/standalone-picker.d.ts +3 -0
- package/dist/types/autocomplete.d.ts +8 -0
- package/dist/types/chat/advisor-message.d.ts +12 -2
- package/dist/types/chat/assistant-message.d.ts +20 -0
- package/dist/types/chat/bash-execution.d.ts +9 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +6 -1
- package/dist/types/chat/collab-prompt-message.d.ts +4 -0
- package/dist/types/chat/compaction-summary-message.d.ts +15 -0
- package/dist/types/chat/eval-execution.d.ts +8 -0
- package/dist/types/chat/execution-shared.d.ts +58 -0
- package/dist/types/chat/late-diagnostics-message.d.ts +15 -1
- package/dist/types/chat/read-tool-group.d.ts +15 -0
- package/dist/types/chat/skill-message.d.ts +9 -0
- package/dist/types/chat/stripped-tool-calls-placeholder.d.ts +3 -0
- package/dist/types/chat/thinking-display.d.ts +2 -0
- package/dist/types/chat/todo-reminder.d.ts +4 -1
- package/dist/types/chat/tool-execution.d.ts +25 -1
- package/dist/types/chat/transcript-actions.d.ts +37 -0
- package/dist/types/chat/transcript-entry.d.ts +3 -1
- package/dist/types/chat/user-message.d.ts +25 -0
- package/dist/types/chrome/collab-qrcode.d.ts +7 -0
- package/dist/types/chrome/context-thresholds.d.ts +11 -0
- package/dist/types/chrome/countdown-timer.d.ts +9 -3
- package/dist/types/chrome/diff.d.ts +12 -0
- package/dist/types/chrome/dynamic-border.d.ts +3 -0
- package/dist/types/chrome/format.d.ts +6 -0
- package/dist/types/chrome/message-divider.d.ts +13 -0
- package/dist/types/chrome/message-frame.d.ts +10 -0
- package/dist/types/chrome/message-notice.d.ts +23 -0
- package/dist/types/chrome/overlay-box.d.ts +11 -2
- package/dist/types/chrome/qrcode.d.ts +7 -0
- package/dist/types/chrome/segment-track.d.ts +7 -0
- package/dist/types/chrome/selector-helpers.d.ts +0 -6
- package/dist/types/chrome/status-notice.d.ts +14 -0
- package/dist/types/chrome/streaming-panel.d.ts +3 -0
- package/dist/types/chrome/tool-activity.d.ts +3 -0
- package/dist/types/chrome/transcript-container.d.ts +13 -0
- package/dist/types/chrome/transcript-status.d.ts +15 -3
- package/dist/types/components/box.d.ts +7 -0
- package/dist/types/components/disclosure.d.ts +9 -0
- package/dist/types/components/editor.d.ts +42 -0
- package/dist/types/components/form.d.ts +14 -0
- package/dist/types/components/image.d.ts +7 -0
- package/dist/types/components/input.d.ts +20 -1
- package/dist/types/components/key-value-list.d.ts +3 -0
- package/dist/types/components/layout/geometry.d.ts +16 -0
- package/dist/types/components/layout/row.d.ts +7 -0
- package/dist/types/components/layout/split-pane.d.ts +8 -0
- package/dist/types/components/layout/stack.d.ts +6 -0
- package/dist/types/components/loader.d.ts +48 -0
- package/dist/types/components/markdown.d.ts +8 -0
- package/dist/types/components/metric.d.ts +7 -0
- package/dist/types/components/progress-bar.d.ts +3 -0
- package/dist/types/components/scroll-view.d.ts +6 -0
- package/dist/types/components/section.d.ts +3 -0
- package/dist/types/components/select-list.d.ts +33 -0
- package/dist/types/components/settings-list.d.ts +68 -0
- package/dist/types/components/spacer.d.ts +4 -0
- package/dist/types/components/tab-bar.d.ts +9 -0
- package/dist/types/components/table.d.ts +7 -0
- package/dist/types/components/text.d.ts +6 -0
- package/dist/types/components/tree-view.d.ts +9 -0
- package/dist/types/components/truncated-text.d.ts +3 -0
- package/dist/types/components/wizard-step.d.ts +6 -0
- package/dist/types/hotkeys-markdown.d.ts +24 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/latex-block.d.ts +7 -0
- package/dist/types/loop-watchdog.d.ts +9 -0
- package/dist/types/native/apply.d.ts +39 -0
- package/dist/types/native/backend.d.ts +94 -0
- package/dist/types/native/blobs.d.ts +29 -0
- package/dist/types/native/describe.d.ts +56 -0
- package/dist/types/native/encode.d.ts +50 -0
- package/dist/types/native/icons.d.ts +18 -0
- package/dist/types/native/memo.d.ts +27 -0
- package/dist/types/native/node.d.ts +107 -0
- package/dist/types/native/overlay.d.ts +64 -0
- package/dist/types/native/picker.d.ts +124 -0
- package/dist/types/native/reconcile.d.ts +41 -0
- package/dist/types/native/settle.d.ts +11 -0
- package/dist/types/native/spans.d.ts +21 -0
- package/dist/types/native/state.d.ts +6 -0
- package/dist/types/native/tone.d.ts +24 -0
- package/dist/types/overlays/advisor-config.d.ts +20 -0
- package/dist/types/overlays/agent-hub-renderer.d.ts +27 -11
- package/dist/types/overlays/agent-hub.d.ts +10 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +7 -0
- package/dist/types/overlays/agents-hub.d.ts +8 -0
- package/dist/types/overlays/annotation-overlay.d.ts +3 -0
- package/dist/types/overlays/ask-dialog.d.ts +23 -0
- package/dist/types/overlays/bordered-loader.d.ts +3 -0
- package/dist/types/overlays/btw-history-panel.d.ts +9 -0
- package/dist/types/overlays/btw-panel.d.ts +3 -0
- package/dist/types/overlays/cleanse-panel.d.ts +5 -2
- package/dist/types/overlays/composer-shape-preview.d.ts +7 -1
- package/dist/types/overlays/copy-selector.d.ts +38 -0
- package/dist/types/overlays/error-banner.d.ts +9 -0
- package/dist/types/overlays/extensions/extension-list.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +18 -0
- package/dist/types/overlays/hook-editor.d.ts +28 -1
- package/dist/types/overlays/hook-input.d.ts +3 -0
- package/dist/types/overlays/hook-selector.d.ts +10 -0
- package/dist/types/overlays/hub-frame.d.ts +18 -0
- package/dist/types/overlays/jobs-panel.d.ts +24 -0
- package/dist/types/overlays/login-dialog.d.ts +10 -0
- package/dist/types/overlays/logout-account-selector.d.ts +4 -0
- package/dist/types/overlays/mcp-add-wizard.d.ts +4 -0
- package/dist/types/overlays/model-browser.d.ts +69 -0
- package/dist/types/overlays/model-hub.d.ts +7 -0
- package/dist/types/overlays/model-picker.d.ts +9 -1
- package/dist/types/overlays/move-overlay.d.ts +3 -0
- package/dist/types/overlays/oauth-selector.d.ts +4 -0
- package/dist/types/overlays/omfg-panel.d.ts +3 -0
- package/dist/types/overlays/pause-screen.d.ts +12 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +5 -0
- package/dist/types/overlays/plan-save-overlay.d.ts +10 -0
- package/dist/types/overlays/plugin-selector.d.ts +4 -0
- package/dist/types/overlays/plugin-settings.d.ts +30 -0
- package/dist/types/overlays/queue-mode-selector.d.ts +4 -0
- package/dist/types/overlays/reset-usage-selector.d.ts +8 -0
- package/dist/types/overlays/rewind-selector.d.ts +5 -0
- package/dist/types/overlays/session-info-overlay.d.ts +19 -1
- package/dist/types/overlays/session-selector.d.ts +64 -1
- package/dist/types/overlays/settings-defs.d.ts +14 -0
- package/dist/types/overlays/settings-selector.d.ts +21 -0
- package/dist/types/overlays/show-images-selector.d.ts +4 -0
- package/dist/types/overlays/snapcompact-shape-preview.d.ts +2 -0
- package/dist/types/overlays/stats-notice.d.ts +8 -0
- package/dist/types/overlays/theme-selector.d.ts +11 -0
- package/dist/types/overlays/thinking-selector.d.ts +4 -0
- package/dist/types/overlays/tree-selector.d.ts +60 -2
- package/dist/types/overlays/usage-dashboard.d.ts +26 -3
- package/dist/types/overlays/usage-row.d.ts +36 -2
- package/dist/types/prompt/attachment-chips.d.ts +8 -0
- package/dist/types/prompt/composer-attachments.d.ts +15 -3
- package/dist/types/prompt/composer.d.ts +18 -1
- package/dist/types/prompt/custom-editor.d.ts +48 -2
- package/dist/types/prompt/editor-top-gap.d.ts +2 -0
- package/dist/types/prompt/macos-spelling.d.ts +6 -0
- package/dist/types/prompt/magic-keywords.d.ts +9 -0
- package/dist/types/prompt/queued-messages.d.ts +23 -0
- package/dist/types/prompt/skill-tokens.d.ts +4 -2
- package/dist/types/prompt/welcome.d.ts +16 -0
- package/dist/types/render/code-cell.d.ts +10 -0
- package/dist/types/render/file-list.d.ts +8 -0
- package/dist/types/render/hyperlink.d.ts +14 -0
- package/dist/types/render/output-block.d.ts +43 -0
- package/dist/types/render/output-pane.d.ts +17 -0
- package/dist/types/render/tool-card.d.ts +8 -0
- package/dist/types/render/tree-list.d.ts +8 -0
- package/dist/types/render/width-aware-text.d.ts +7 -0
- package/dist/types/setup/scenes/outro.d.ts +7 -0
- package/dist/types/setup/scenes/sign-in.d.ts +16 -9
- package/dist/types/setup/scenes/splash.d.ts +8 -0
- package/dist/types/setup/scenes/types.d.ts +6 -27
- package/dist/types/setup/wizard-overlay.d.ts +9 -0
- package/dist/types/status-line/component.d.ts +49 -5
- package/dist/types/status-line/context-usage.d.ts +20 -0
- package/dist/types/status-line/footer.d.ts +8 -0
- package/dist/types/status-line/metrics.d.ts +1 -2
- package/dist/types/status-line/segments.d.ts +10 -1
- package/dist/types/status-line/types.d.ts +40 -1
- package/dist/types/stdin-buffer.d.ts +10 -7
- package/dist/types/terminal-capabilities.d.ts +1 -1
- package/dist/types/terminal.d.ts +36 -0
- package/dist/types/theme/defaults/index.d.ts +100 -0
- package/dist/types/theme/schema.d.ts +2 -0
- package/dist/types/theme/shimmer.d.ts +8 -0
- package/dist/types/theme/theme-class.d.ts +2 -0
- package/dist/types/theme/theme.d.ts +33 -1
- package/dist/types/tools/ask.d.ts +3 -0
- package/dist/types/tools/ast-edit.d.ts +10 -1
- package/dist/types/tools/ast-grep.d.ts +10 -1
- package/dist/types/tools/autoresearch.d.ts +21 -4
- package/dist/types/tools/bash.d.ts +30 -1
- package/dist/types/tools/cfg-render.d.ts +5 -1
- package/dist/types/tools/debug.d.ts +3 -1
- package/dist/types/tools/default-renderer.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +14 -1
- package/dist/types/tools/eval.d.ts +12 -1
- package/dist/types/tools/fetch.d.ts +16 -1
- package/dist/types/tools/find.d.ts +15 -1
- package/dist/types/tools/github.d.ts +3 -0
- package/dist/types/tools/glob.d.ts +14 -1
- package/dist/types/tools/goal.d.ts +3 -1
- package/dist/types/tools/grep.d.ts +14 -1
- package/dist/types/tools/grouped-file-output.d.ts +58 -0
- package/dist/types/tools/json-tree.d.ts +20 -0
- package/dist/types/tools/lsp.d.ts +3 -10
- package/dist/types/tools/mcp.d.ts +16 -1
- package/dist/types/tools/memory.d.ts +26 -0
- package/dist/types/tools/native-view.d.ts +90 -0
- package/dist/types/tools/proc-render.d.ts +7 -1
- package/dist/types/tools/read.d.ts +23 -1
- package/dist/types/tools/renderer.d.ts +57 -0
- package/dist/types/tools/report-tool-issue.d.ts +3 -0
- package/dist/types/tools/resolve.d.ts +14 -1
- package/dist/types/tools/task.d.ts +3 -0
- package/dist/types/tools/think.d.ts +3 -0
- package/dist/types/tools/todo.d.ts +12 -0
- package/dist/types/tools/vibe.d.ts +3 -1
- package/dist/types/tools/wait.d.ts +3 -0
- package/dist/types/tools/web-search-types.d.ts +90 -0
- package/dist/types/tools/web-search.d.ts +9 -168
- package/dist/types/tools/write.d.ts +14 -1
- package/dist/types/tools/xdev.d.ts +13 -0
- package/dist/types/tui.d.ts +49 -0
- package/package.json +9 -9
- package/src/apps/autoresearch-dashboard.ts +362 -4
- package/src/apps/cleanse-board.ts +219 -1
- package/src/apps/cleanse-picker.ts +9 -1
- package/src/apps/debug/log-viewer.ts +15 -15
- package/src/apps/debug/protocol-probe.ts +2 -20
- package/src/apps/git/avatar.ts +2 -22
- package/src/apps/git/git-tui.ts +406 -59
- package/src/apps/git/help.ts +130 -0
- package/src/apps/git/sidebar.ts +491 -0
- package/src/apps/live-visualizer.ts +65 -0
- package/src/apps/ps-top.ts +448 -17
- package/src/apps/session-picker.ts +1 -0
- package/src/apps/setup-model-picker.ts +5 -1
- package/src/apps/standalone-picker.ts +55 -1
- package/src/autocomplete.ts +44 -1
- package/src/chat/advisor-message.ts +72 -3
- package/src/chat/assistant-message.ts +538 -51
- package/src/chat/background-tan-message.ts +4 -4
- package/src/chat/bash-execution.ts +81 -0
- package/src/chat/cache-invalidation-marker.ts +8 -0
- package/src/chat/chat-transcript-builder.ts +37 -6
- package/src/chat/collab-prompt-message.ts +15 -3
- package/src/chat/compaction-summary-message.ts +74 -1
- package/src/chat/custom-message.ts +3 -1
- package/src/chat/eval-execution.ts +57 -0
- package/src/chat/execution-shared.ts +153 -1
- package/src/chat/hook-message.ts +1 -0
- package/src/chat/late-diagnostics-message.ts +85 -2
- package/src/chat/read-tool-group.ts +341 -1
- package/src/chat/served-model-marker.ts +8 -0
- package/src/chat/skill-message.ts +63 -0
- package/src/chat/stripped-tool-calls-placeholder.ts +18 -10
- package/src/chat/thinking-display.ts +73 -11
- package/src/chat/todo-reminder.ts +34 -4
- package/src/chat/tool-execution.ts +385 -10
- package/src/chat/transcript-actions.ts +38 -0
- package/src/chat/transcript-browser.ts +2 -9
- package/src/chat/transcript-entry.ts +10 -1
- package/src/chat/transcript-render-helpers.ts +16 -15
- package/src/chat/ttsr-notification.ts +38 -1
- package/src/chat/user-message.ts +167 -4
- package/src/chrome/collab-qrcode.ts +27 -2
- package/src/chrome/context-thresholds.ts +34 -0
- package/src/chrome/countdown-timer.ts +24 -1
- package/src/chrome/diff.ts +60 -1
- package/src/chrome/dynamic-border.ts +7 -0
- package/src/chrome/format.ts +12 -0
- package/src/chrome/message-divider.ts +33 -1
- package/src/chrome/message-frame.ts +53 -10
- package/src/chrome/message-notice.ts +71 -0
- package/src/chrome/overlay-box.ts +45 -2
- package/src/chrome/qrcode.ts +28 -0
- package/src/chrome/segment-track.ts +26 -0
- package/src/chrome/selector-helpers.ts +1 -17
- package/src/chrome/status-notice.ts +46 -0
- package/src/chrome/streaming-panel.ts +25 -1
- package/src/chrome/tool-activity.ts +20 -0
- package/src/chrome/transcript-container.ts +139 -17
- package/src/chrome/transcript-status.ts +40 -11
- package/src/components/box.ts +51 -0
- package/src/components/disclosure.ts +33 -0
- package/src/components/editor.ts +239 -36
- package/src/components/form.ts +93 -9
- package/src/components/image.ts +30 -0
- package/src/components/input.ts +77 -26
- package/src/components/key-value-list.ts +18 -0
- package/src/components/layout/geometry.ts +44 -0
- package/src/components/layout/row.ts +30 -0
- package/src/components/layout/split-pane.ts +65 -0
- package/src/components/layout/stack.ts +27 -0
- package/src/components/loader.ts +183 -1
- package/src/components/markdown.ts +131 -56
- package/src/components/metric.ts +39 -0
- package/src/components/progress-bar.ts +28 -0
- package/src/components/scroll-view.ts +23 -0
- package/src/components/section.ts +16 -0
- package/src/components/select-list.ts +174 -25
- package/src/components/settings-list.ts +264 -7
- package/src/components/spacer.ts +17 -0
- package/src/components/tab-bar.ts +42 -0
- package/src/components/table.ts +34 -0
- package/src/components/text.ts +23 -0
- package/src/components/tree-view.ts +65 -0
- package/src/components/truncated-text.ts +14 -0
- package/src/components/wizard-step.ts +18 -9
- package/src/debug-server.ts +14 -7
- package/src/hotkeys-markdown.ts +251 -69
- package/src/index.ts +6 -0
- package/src/latex-block.ts +11 -0
- package/src/loop-watchdog.ts +38 -10
- package/src/native/apply.ts +275 -0
- package/src/native/backend.ts +682 -0
- package/src/native/blobs.ts +70 -0
- package/src/native/describe.ts +124 -0
- package/src/native/encode.ts +233 -0
- package/src/native/icons.ts +184 -0
- package/src/native/memo.ts +64 -0
- package/src/native/node.ts +112 -0
- package/src/native/overlay.ts +131 -0
- package/src/native/picker.ts +333 -0
- package/src/native/reconcile.ts +1063 -0
- package/src/native/settle.ts +23 -0
- package/src/native/spans.ts +308 -0
- package/src/native/state.ts +28 -0
- package/src/native/tone.ts +79 -0
- package/src/overlays/advisor-config.ts +420 -0
- package/src/overlays/agent-hub-renderer.ts +155 -18
- package/src/overlays/agent-hub.ts +1037 -44
- package/src/overlays/agent-transcript-viewer.ts +129 -0
- package/src/overlays/agents-hub.ts +688 -53
- package/src/overlays/annotation-overlay.ts +391 -52
- package/src/overlays/ask-dialog.ts +435 -24
- package/src/overlays/bordered-loader.ts +17 -0
- package/src/overlays/btw-history-panel.ts +182 -0
- package/src/overlays/btw-panel.ts +68 -2
- package/src/overlays/cleanse-panel.ts +105 -15
- package/src/overlays/codex-reset-fireworks.ts +65 -5
- package/src/overlays/composer-shape-preview.ts +26 -1
- package/src/overlays/copy-selector.ts +545 -23
- package/src/overlays/error-banner.ts +44 -0
- package/src/overlays/extensions/extension-list.ts +23 -17
- package/src/overlays/history-search.ts +175 -33
- package/src/overlays/hook-editor.ts +113 -6
- package/src/overlays/hook-input.ts +22 -1
- package/src/overlays/hook-selector.ts +311 -22
- package/src/overlays/hub-frame.ts +80 -0
- package/src/overlays/jobs-panel.ts +112 -0
- package/src/overlays/login-dialog.ts +183 -1
- package/src/overlays/logout-account-selector.ts +102 -4
- package/src/overlays/mcp-add-wizard.ts +232 -53
- package/src/overlays/model-browser.ts +681 -22
- package/src/overlays/model-hub.ts +1402 -78
- package/src/overlays/model-picker.ts +184 -7
- package/src/overlays/move-overlay.ts +45 -0
- package/src/overlays/oauth-selector.ts +227 -23
- package/src/overlays/omfg-panel.ts +86 -2
- package/src/overlays/pause-screen.ts +55 -4
- package/src/overlays/plan-review-overlay.ts +352 -0
- package/src/overlays/plan-save-overlay.ts +45 -0
- package/src/overlays/plugin-selector.ts +21 -1
- package/src/overlays/plugin-settings.ts +195 -5
- package/src/overlays/queue-mode-selector.ts +19 -1
- package/src/overlays/reset-usage-selector.ts +110 -40
- package/src/overlays/rewind-selector.ts +458 -44
- package/src/overlays/session-account-selector.ts +1 -1
- package/src/overlays/session-info-overlay.ts +137 -1
- package/src/overlays/session-selector.ts +807 -46
- package/src/overlays/settings-defs.ts +41 -0
- package/src/overlays/settings-selector.ts +585 -33
- package/src/overlays/show-images-selector.ts +18 -1
- package/src/overlays/snapcompact-shape-preview.ts +48 -3
- package/src/overlays/stats-notice.ts +31 -0
- package/src/overlays/theme-selector.ts +31 -1
- package/src/overlays/thinking-selector.ts +22 -1
- package/src/overlays/tree-selector.ts +570 -110
- package/src/overlays/usage-dashboard.ts +587 -12
- package/src/overlays/usage-row.ts +161 -25
- package/src/prompt/attachment-chips.ts +60 -0
- package/src/prompt/composer-attachments.ts +31 -9
- package/src/prompt/composer.ts +55 -2
- package/src/prompt/custom-editor.ts +302 -4
- package/src/prompt/editor-top-gap.ts +8 -0
- package/src/prompt/macos-spelling.ts +24 -9
- package/src/prompt/magic-keywords.ts +22 -1
- package/src/prompt/model-mention-autocomplete.ts +1 -0
- package/src/prompt/queued-messages.ts +101 -0
- package/src/prompt/skill-tokens.ts +5 -3
- package/src/prompt/welcome.ts +213 -6
- package/src/render/code-cell.ts +103 -2
- package/src/render/file-list.ts +29 -0
- package/src/render/hyperlink.ts +32 -0
- package/src/render/output-block.ts +110 -0
- package/src/render/output-pane.ts +37 -0
- package/src/render/tool-card.ts +137 -0
- package/src/render/tree-list.ts +39 -0
- package/src/render/width-aware-text.ts +20 -0
- package/src/setup/scenes/composer.ts +29 -0
- package/src/setup/scenes/glyph.ts +15 -0
- package/src/setup/scenes/model.ts +36 -8
- package/src/setup/scenes/outro.ts +28 -1
- package/src/setup/scenes/sign-in.ts +138 -24
- package/src/setup/scenes/splash.ts +28 -1
- package/src/setup/scenes/theme.ts +88 -3
- package/src/setup/scenes/types.ts +7 -28
- package/src/setup/startup-splash.ts +46 -9
- package/src/setup/wizard-overlay.ts +140 -23
- package/src/setup/wizard.ts +1 -1
- package/src/status-line/component.ts +414 -18
- package/src/status-line/context-usage.ts +279 -50
- package/src/status-line/footer.ts +94 -1
- package/src/status-line/metrics.ts +3 -6
- package/src/status-line/segments.ts +463 -97
- package/src/status-line/separators.ts +3 -16
- package/src/status-line/types.ts +43 -1
- package/src/stdin-buffer.ts +27 -21
- package/src/terminal-capabilities.ts +7 -0
- package/src/terminal.ts +153 -11
- package/src/theme/defaults/dark-neon-noir.json +100 -0
- package/src/theme/defaults/index.ts +2 -0
- package/src/theme/defaults/light-prism.json +5 -5
- package/src/theme/defaults/onyx.json +62 -62
- package/src/theme/mermaid-cache.ts +10 -1
- package/src/theme/schema.ts +15 -0
- package/src/theme/shimmer.ts +33 -0
- package/src/theme/theme-class.ts +24 -19
- package/src/theme/theme.ts +111 -9
- package/src/theme/tui-adapters.ts +14 -1
- package/src/tools/ask.ts +157 -1
- package/src/tools/ast-edit.ts +174 -1
- package/src/tools/ast-grep.ts +82 -2
- package/src/tools/autoresearch.ts +149 -1
- package/src/tools/bash.ts +233 -62
- package/src/tools/cfg-render.ts +66 -1
- package/src/tools/debug.ts +78 -1
- package/src/tools/default-renderer.ts +35 -1
- package/src/tools/edit.ts +357 -31
- package/src/tools/eval.ts +257 -1
- package/src/tools/fetch.ts +102 -3
- package/src/tools/find.ts +127 -1
- package/src/tools/github.ts +119 -10
- package/src/tools/glob.ts +116 -1
- package/src/tools/goal.ts +56 -1
- package/src/tools/grep.ts +83 -2
- package/src/tools/grouped-file-output.ts +205 -0
- package/src/tools/json-tree.ts +116 -1
- package/src/tools/lsp.ts +318 -1
- package/src/tools/mcp.ts +80 -1
- package/src/tools/memory.ts +93 -11
- package/src/tools/native-view.ts +292 -0
- package/src/tools/proc-render.ts +221 -1
- package/src/tools/read.ts +199 -4
- package/src/tools/renderer.ts +61 -0
- package/src/tools/report-tool-issue.ts +7 -1
- package/src/tools/resolve.ts +63 -1
- package/src/tools/streaming-output.ts +79 -16
- package/src/tools/task.ts +406 -4
- package/src/tools/think.ts +19 -1
- package/src/tools/todo.ts +89 -0
- package/src/tools/vibe.ts +230 -2
- package/src/tools/wait.ts +181 -1
- package/src/tools/web-search-types.ts +99 -0
- package/src/tools/web-search.ts +111 -170
- package/src/tools/write.ts +144 -5
- package/src/tools/xdev.ts +64 -1
- package/src/tui.ts +295 -4
- package/dist/types/setup/scenes/providers.d.ts +0 -3
- package/dist/types/setup/scenes/web-search.d.ts +0 -22
- package/src/setup/scenes/providers.ts +0 -110
- package/src/setup/scenes/web-search.ts +0 -165
|
@@ -13,7 +13,22 @@ import {
|
|
|
13
13
|
truncateToWidth,
|
|
14
14
|
visibleWidth,
|
|
15
15
|
} from "../index";
|
|
16
|
-
import
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { formatBytes, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
18
|
+
import type { TspPickerGroup, TspPickerItem, TspSpan, TspText, TspTone } from "@oh-my-pi/pi-wire";
|
|
19
|
+
import { compact, kv, md, node, span } from "../native/describe";
|
|
20
|
+
import {
|
|
21
|
+
picker,
|
|
22
|
+
pickerAction,
|
|
23
|
+
pickerAge,
|
|
24
|
+
pickerDate,
|
|
25
|
+
type PickerEvent,
|
|
26
|
+
pickerEvent,
|
|
27
|
+
pickerHits,
|
|
28
|
+
} from "../native/picker";
|
|
29
|
+
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
30
|
+
import { hintsRow, type NativeHint, overlayCard } from "../native/overlay";
|
|
31
|
+
import { plainText } from "../native/spans";
|
|
17
32
|
import { theme } from "../theme/theme";
|
|
18
33
|
import { contentRowWidth } from "../chrome/selector-helpers";
|
|
19
34
|
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
|
|
@@ -27,6 +42,7 @@ export interface SessionSelectorEntry {
|
|
|
27
42
|
cwd: string;
|
|
28
43
|
title?: string;
|
|
29
44
|
modified: Date;
|
|
45
|
+
created?: Date;
|
|
30
46
|
size: number;
|
|
31
47
|
firstMessage: string;
|
|
32
48
|
allMessagesText: string;
|
|
@@ -39,7 +55,7 @@ import { HookSelectorComponent } from "./hook-selector";
|
|
|
39
55
|
import { bottomBorder, OverlayPanel, row, topBorder } from "../chrome/overlay-box";
|
|
40
56
|
import { MenuSelection, getMenuWindow } from "../components/menu-selection";
|
|
41
57
|
import { formatKeyHint, formatKeyHints } from "../app-keybindings";
|
|
42
|
-
import { interruptKey } from "../chrome/keybinding-hints";
|
|
58
|
+
import { boundKeys, interruptKey } from "../chrome/keybinding-hints";
|
|
43
59
|
|
|
44
60
|
/**
|
|
45
61
|
* Themed glyph + colored label for a session's lifecycle status, or `undefined`
|
|
@@ -64,6 +80,181 @@ function formatSessionStatus(status: SessionSelectorStatus | undefined): string
|
|
|
64
80
|
}
|
|
65
81
|
}
|
|
66
82
|
|
|
83
|
+
/** Lifecycle status as one theme-colored span (native picker), mirroring {@link formatSessionStatus}. */
|
|
84
|
+
function sessionStatusSpan(status: SessionSelectorStatus | undefined): TspSpan | undefined {
|
|
85
|
+
switch (status) {
|
|
86
|
+
case "complete":
|
|
87
|
+
return span("done", "success");
|
|
88
|
+
case "interrupted":
|
|
89
|
+
return span("interrupted", "warning");
|
|
90
|
+
case "aborted":
|
|
91
|
+
return span("aborted", "muted");
|
|
92
|
+
case "error":
|
|
93
|
+
return span("error", "error");
|
|
94
|
+
case "pending":
|
|
95
|
+
return span("pending", "accent");
|
|
96
|
+
default:
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Status dot tone of a session row in the native picker; none when the status is unknown. */
|
|
102
|
+
function sessionStatusTone(status: SessionSelectorStatus | undefined): TspTone | undefined {
|
|
103
|
+
switch (status) {
|
|
104
|
+
case "complete":
|
|
105
|
+
return "success";
|
|
106
|
+
case "interrupted":
|
|
107
|
+
return "warning";
|
|
108
|
+
case "aborted":
|
|
109
|
+
case "error":
|
|
110
|
+
return "error";
|
|
111
|
+
case "pending":
|
|
112
|
+
return "pending";
|
|
113
|
+
default:
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The name a session goes by: its title, else its first message on one line, else its id. */
|
|
119
|
+
function sessionLabel(session: SessionSelectorEntry): string {
|
|
120
|
+
const title = session.title ? plainText(session.title).trim() : "";
|
|
121
|
+
return title || plainText(session.firstMessage).replace(/\s+/g, " ").trim() || session.id;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Longest prompt or answer excerpt the native preview shows. */
|
|
125
|
+
const PREVIEW_EXCERPT_CHARS = 1200;
|
|
126
|
+
/** The preview follows the selection once it rests this long (holding ↓ doesn't flood the wire). */
|
|
127
|
+
const PREVIEW_SETTLE_MS = 60;
|
|
128
|
+
const DAY_MS = 86_400_000;
|
|
129
|
+
const PINNED_GROUP = { id: "pinned", label: "Pinned" };
|
|
130
|
+
|
|
131
|
+
/** Day group of a session's last modification, relative to local midnight today. */
|
|
132
|
+
function sessionDayGroup(modified: Date, startOfToday: number): { id: string; label: string } {
|
|
133
|
+
const at = modified.getTime();
|
|
134
|
+
if (at >= startOfToday) return { id: "today", label: "Today" };
|
|
135
|
+
if (at >= startOfToday - DAY_MS) return { id: "yesterday", label: "Yesterday" };
|
|
136
|
+
if (at >= startOfToday - 6 * DAY_MS) return { id: "week", label: "This week" };
|
|
137
|
+
return { id: "earlier", label: "Earlier" };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** A cwd as dim-prefix / strong-tail path spans. */
|
|
141
|
+
function cwdSpans(cwd: string): TspSpan[] {
|
|
142
|
+
const short = shortenPath(cwd);
|
|
143
|
+
const cut = short.lastIndexOf("/") + 1;
|
|
144
|
+
return cut > 0 && cut < short.length
|
|
145
|
+
? [span(short.slice(0, cut), "path dim"), span(short.slice(cut), "path")]
|
|
146
|
+
: [span(short, "path")];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The native preview of a session: its title, a fact grid, and the
|
|
151
|
+
* conversation's first prompt (user tint) plus the tail of the rest as an
|
|
152
|
+
* excerpt of the latest answer, both clipped to {@link PREVIEW_EXCERPT_CHARS}.
|
|
153
|
+
*/
|
|
154
|
+
function sessionPreview(session: SessionSelectorEntry, forkedFrom: string | undefined): NativeChild[] {
|
|
155
|
+
const status = sessionStatusSpan(session.status);
|
|
156
|
+
const created = session.created && !Number.isNaN(session.created.getTime()) ? session.created : undefined;
|
|
157
|
+
const first = session.firstMessage.trim();
|
|
158
|
+
const rest = session.allMessagesText.startsWith(session.firstMessage)
|
|
159
|
+
? session.allMessagesText.slice(session.firstMessage.length).trim()
|
|
160
|
+
: "";
|
|
161
|
+
const conversation = compact([
|
|
162
|
+
first.length > 0 &&
|
|
163
|
+
md(first.length > PREVIEW_EXCERPT_CHARS ? `${first.slice(0, PREVIEW_EXCERPT_CHARS)}…` : first, {
|
|
164
|
+
tone: "user",
|
|
165
|
+
}),
|
|
166
|
+
rest.length > 0 && md(rest.length > PREVIEW_EXCERPT_CHARS ? `…${rest.slice(-PREVIEW_EXCERPT_CHARS)}` : rest),
|
|
167
|
+
]);
|
|
168
|
+
return compact([
|
|
169
|
+
node("text", { text: sessionLabel(session), role: "omp.picker.title" }),
|
|
170
|
+
kv([
|
|
171
|
+
["Folder", session.cwd ? cwdSpans(session.cwd) : undefined],
|
|
172
|
+
["Created", created && pickerDate(created)],
|
|
173
|
+
["Modified", pickerDate(session.modified)],
|
|
174
|
+
["Size", formatBytes(session.size)],
|
|
175
|
+
["Status", status && [status]],
|
|
176
|
+
["Forked from", forkedFrom],
|
|
177
|
+
]),
|
|
178
|
+
conversation.length > 0 && node("section", { head: "Conversation" }, conversation),
|
|
179
|
+
]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Relative age of a session's last modification (`"3 hours ago"`), falling back to the date after a week. */
|
|
183
|
+
function formatSessionDate(date: Date): string {
|
|
184
|
+
const diffMs = Date.now() - date.getTime();
|
|
185
|
+
const diffMins = Math.floor(diffMs / 60000);
|
|
186
|
+
const diffHours = Math.floor(diffMs / 3600000);
|
|
187
|
+
const diffDays = Math.floor(diffMs / 86400000);
|
|
188
|
+
|
|
189
|
+
if (diffMins < 1) return "just now";
|
|
190
|
+
if (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
|
|
191
|
+
if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
|
|
192
|
+
if (diffDays === 1) return "1 day ago";
|
|
193
|
+
if (diffDays < 7) return `${diffDays} days ago`;
|
|
194
|
+
|
|
195
|
+
return date.toLocaleDateString();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** A cached native session item and the inputs it was built from. */
|
|
199
|
+
interface SessionItemMemo {
|
|
200
|
+
node: NativeNode;
|
|
201
|
+
date: string;
|
|
202
|
+
pinned: boolean;
|
|
203
|
+
current: boolean;
|
|
204
|
+
showCwd: boolean;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Cached native item node on the session entry, rebuilt when its inputs change. */
|
|
208
|
+
const kNativeItem = Symbol("session.nativeItem");
|
|
209
|
+
|
|
210
|
+
interface NativeSessionInfo extends SessionSelectorEntry {
|
|
211
|
+
[kNativeItem]?: SessionItemMemo;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** The session picker's catalogue: one item per session in scope, and the inputs it was built from. */
|
|
215
|
+
interface SessionCatalogue {
|
|
216
|
+
version: number;
|
|
217
|
+
showCwd: boolean;
|
|
218
|
+
currentPath: string | undefined;
|
|
219
|
+
pinnedIds: ReadonlySet<string>;
|
|
220
|
+
minute: number;
|
|
221
|
+
items: readonly TspPickerItem[];
|
|
222
|
+
byPath: ReadonlyMap<string, TspPickerItem>;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** The session list's part of the picker props, plus the memo keys it was built from. */
|
|
226
|
+
interface SessionPickerView {
|
|
227
|
+
items: readonly TspPickerItem[];
|
|
228
|
+
itemsAdd?: readonly TspPickerItem[];
|
|
229
|
+
version: number;
|
|
230
|
+
query: string;
|
|
231
|
+
/** Search caret (UTF-16 offset into `query`). */
|
|
232
|
+
cursor: number;
|
|
233
|
+
order: readonly (string | TspPickerGroup)[];
|
|
234
|
+
hits?: Readonly<Record<string, readonly (readonly [number, number])[]>>;
|
|
235
|
+
selected: string | null;
|
|
236
|
+
current: readonly string[];
|
|
237
|
+
total: number;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** An open delete confirmation: the session and the dialog's two answers. */
|
|
241
|
+
interface DeleteChoice<T extends SessionSelectorEntry> {
|
|
242
|
+
session: T;
|
|
243
|
+
confirm(): void;
|
|
244
|
+
cancel(): void;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Transient status line above the list: scope loading or an error. */
|
|
248
|
+
interface SessionPickerMessage {
|
|
249
|
+
kind: "loading" | "error";
|
|
250
|
+
text: string;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const SCOPE_TABS: readonly { id: "folder" | "all"; label: string }[] = [
|
|
254
|
+
{ id: "folder", label: "Current folder" },
|
|
255
|
+
{ id: "all", label: "All projects" },
|
|
256
|
+
];
|
|
257
|
+
|
|
67
258
|
/** Returns the IDs of sessions whose recorded prompts match a query, best first. */
|
|
68
259
|
export type SessionHistoryMatcher = (query: string) => string[];
|
|
69
260
|
|
|
@@ -352,6 +543,22 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
352
543
|
#hadFilterQuery = false;
|
|
353
544
|
/** Last query passed to {@link #filterSessions}; same-query refilter keeps the index. */
|
|
354
545
|
#lastFilterQuery = "";
|
|
546
|
+
/** Bumped whenever the visible session set may have changed (native memo key). */
|
|
547
|
+
#itemsVersion = 0;
|
|
548
|
+
#itemsNative:
|
|
549
|
+
| { version: number; showCwd: boolean; currentPath: string | undefined; items: NativeNode[] }
|
|
550
|
+
| undefined;
|
|
551
|
+
#listNative:
|
|
552
|
+
| { items: readonly NativeNode[]; selected: string | undefined; query: string; node: NativeNode }
|
|
553
|
+
| undefined;
|
|
554
|
+
/** Bumped when the session set itself changes (scope switch, delete): the picker catalogue's memo key. */
|
|
555
|
+
#datasetVersion = 0;
|
|
556
|
+
/** The picker catalogue (every session in scope), rebuilt only when the set, markers or minute change. */
|
|
557
|
+
#pickerCatalogue: SessionCatalogue | undefined;
|
|
558
|
+
/** Paths upserted through `itemsAdd` (history badges) since the catalogue was last sent. */
|
|
559
|
+
#pickerPatched = new Set<string>();
|
|
560
|
+
#pickerPatch: { catalogue: readonly TspPickerItem[]; flagged: string; add: readonly TspPickerItem[] } | undefined;
|
|
561
|
+
#pickerView: SessionPickerView | undefined;
|
|
355
562
|
|
|
356
563
|
constructor(
|
|
357
564
|
sessions: T[],
|
|
@@ -417,6 +624,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
417
624
|
|
|
418
625
|
/** Replace the visible dataset, e.g. when toggling folder/all-projects scope. */
|
|
419
626
|
setSessions(sessions: T[], showCwd: boolean, pinnedIds?: ReadonlySet<string>): void {
|
|
627
|
+
this.#datasetVersion++;
|
|
420
628
|
this.#allSessions = sessions;
|
|
421
629
|
this.#showCwd = showCwd;
|
|
422
630
|
if (pinnedIds !== undefined) this.#pinnedIds = pinnedIds;
|
|
@@ -427,6 +635,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
427
635
|
|
|
428
636
|
#filterSessions(query: string): void {
|
|
429
637
|
this.#scanGeneration++;
|
|
638
|
+
this.#itemsVersion++;
|
|
430
639
|
if (this.#scanTimer !== undefined) {
|
|
431
640
|
clearTimeout(this.#scanTimer);
|
|
432
641
|
this.#scanTimer = undefined;
|
|
@@ -511,6 +720,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
511
720
|
* prompt-history hits, literal matches (recency), and fuzzy-only hits (score).
|
|
512
721
|
*/
|
|
513
722
|
#composeFiltered(): void {
|
|
723
|
+
this.#itemsVersion++;
|
|
514
724
|
this.#fuzzyRanked.sort(compareFuzzyRank);
|
|
515
725
|
const base: T[] = [];
|
|
516
726
|
for (const match of this.#literalRanked) base.push(match.session);
|
|
@@ -570,6 +780,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
570
780
|
removeSession(sessionPath: string): void {
|
|
571
781
|
const index = this.#allSessions.findIndex(s => s.path === sessionPath);
|
|
572
782
|
if (index === -1) return;
|
|
783
|
+
this.#datasetVersion++;
|
|
573
784
|
this.#allSessions.splice(index, 1);
|
|
574
785
|
// Re-filter to update the composed result set
|
|
575
786
|
this.#filterSessions(this.#searchInput.getValue());
|
|
@@ -599,6 +810,293 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
599
810
|
this.onSelect?.(session);
|
|
600
811
|
}
|
|
601
812
|
|
|
813
|
+
/** Native click on a session item: the same outcome as a mouse click (select + resume). */
|
|
814
|
+
confirmSession(path: string): void {
|
|
815
|
+
const index = this.#menu.visibleItems.findIndex(s => s.path === path);
|
|
816
|
+
if (index >= 0) this.selectAndConfirm(index);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/** Native row click: move the selection there, as the arrow keys would. */
|
|
820
|
+
selectSession(path: string): void {
|
|
821
|
+
if (this.#menu.setSelectedKey(path)) this.#selectionMoved = true;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/** Enter: resume the selected session. */
|
|
825
|
+
resumeSelected(): void {
|
|
826
|
+
const selected = this.#menu.selectedItem;
|
|
827
|
+
if (selected) this.onSelect?.(selected);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/** Delete / Backspace on an empty query: ask the parent to confirm deleting the selected session. */
|
|
831
|
+
requestDelete(): void {
|
|
832
|
+
const selected = this.#menu.selectedItem;
|
|
833
|
+
if (selected) this.onDeleteRequest?.(selected);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
/** Empty the search query (the empty state's "Clear search"). */
|
|
837
|
+
clearSearch(): void {
|
|
838
|
+
this.#searchInput.setValue("");
|
|
839
|
+
this.#filterSessions("");
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
get selectedSession(): T | undefined {
|
|
843
|
+
return this.#menu.selectedItem;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** Label of the session at `sessionPath` in this listing, else its file name. */
|
|
847
|
+
parentLabel(sessionPath: string): string {
|
|
848
|
+
const parent = this.#allSessions.find(s => s.path === sessionPath);
|
|
849
|
+
return parent ? sessionLabel(parent) : path.basename(sessionPath, ".jsonl");
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
/**
|
|
853
|
+
* The session list's picker data. `items` is the whole scope and keeps its
|
|
854
|
+
* identity while typing (rebuilt when the set, pins, the live session or
|
|
855
|
+
* the minute change); a query changes only `order`, `hits` and
|
|
856
|
+
* `selected`, plus `itemsAdd` upserts for prompt-history badges. Without
|
|
857
|
+
* a query `order` groups pinned sessions, then by day (Today, Yesterday,
|
|
858
|
+
* This week, Earlier); with one it is the flat ranking.
|
|
859
|
+
*/
|
|
860
|
+
pickerView(): SessionPickerView {
|
|
861
|
+
const catalogue = this.#catalogue();
|
|
862
|
+
const query = this.#searchInput.getValue();
|
|
863
|
+
const cursor = this.#searchInput.getCursor();
|
|
864
|
+
const selected = this.#menu.selectedKey ?? null;
|
|
865
|
+
const flagged = this.#historyFlagged();
|
|
866
|
+
const itemsAdd = this.#historyPatch(catalogue.items, catalogue.byPath, flagged);
|
|
867
|
+
const view = this.#pickerView;
|
|
868
|
+
if (
|
|
869
|
+
view?.items === catalogue.items &&
|
|
870
|
+
view.version === this.#itemsVersion &&
|
|
871
|
+
view.query === query &&
|
|
872
|
+
view.cursor === cursor &&
|
|
873
|
+
view.selected === selected &&
|
|
874
|
+
view.itemsAdd === itemsAdd
|
|
875
|
+
) {
|
|
876
|
+
return view;
|
|
877
|
+
}
|
|
878
|
+
const visible = this.#menu.visibleItems;
|
|
879
|
+
const tokens = tokenizeSessionQuery(query);
|
|
880
|
+
const order: (string | TspPickerGroup)[] = [];
|
|
881
|
+
let hits: Record<string, (readonly [number, number])[]> | undefined;
|
|
882
|
+
if (tokens.length === 0) {
|
|
883
|
+
const now = new Date();
|
|
884
|
+
const startOfToday = new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
|
|
885
|
+
// Pins lead the listing (`sortPinnedFirst`), so they get their own group ahead of the days.
|
|
886
|
+
const groups = visible.map(session =>
|
|
887
|
+
this.#pinnedIds.has(session.id) ? PINNED_GROUP : sessionDayGroup(session.modified, startOfToday),
|
|
888
|
+
);
|
|
889
|
+
for (let i = 0; i < visible.length; i++) {
|
|
890
|
+
const group = groups[i]!;
|
|
891
|
+
if (i === 0 || groups[i - 1]!.id !== group.id) {
|
|
892
|
+
let count = 1;
|
|
893
|
+
while (i + count < visible.length && groups[i + count]!.id === group.id) count++;
|
|
894
|
+
order.push({ group: `${group.id}-${i}`, label: group.label, count });
|
|
895
|
+
}
|
|
896
|
+
order.push(visible[i]!.path);
|
|
897
|
+
}
|
|
898
|
+
} else {
|
|
899
|
+
for (const session of visible) {
|
|
900
|
+
order.push(session.path);
|
|
901
|
+
const label = catalogue.byPath.get(session.path)?.label;
|
|
902
|
+
if (typeof label !== "string") continue;
|
|
903
|
+
const ranges = pickerHits(label, tokens);
|
|
904
|
+
if (ranges.length > 0) (hits ??= {})[session.path] = ranges;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
const next: SessionPickerView = {
|
|
908
|
+
items: catalogue.items,
|
|
909
|
+
...(itemsAdd.length > 0 ? { itemsAdd } : {}),
|
|
910
|
+
version: this.#itemsVersion,
|
|
911
|
+
query,
|
|
912
|
+
cursor,
|
|
913
|
+
order,
|
|
914
|
+
...(hits ? { hits } : {}),
|
|
915
|
+
selected,
|
|
916
|
+
current: catalogue.currentPath !== undefined ? [catalogue.currentPath] : [],
|
|
917
|
+
total: this.#allSessions.length,
|
|
918
|
+
};
|
|
919
|
+
this.#pickerView = next;
|
|
920
|
+
return next;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
#catalogue(): SessionCatalogue {
|
|
924
|
+
const currentPath = this.#getCurrentSessionPath();
|
|
925
|
+
const minute = Math.floor(Date.now() / 60_000);
|
|
926
|
+
const cached = this.#pickerCatalogue;
|
|
927
|
+
if (
|
|
928
|
+
cached?.version === this.#datasetVersion &&
|
|
929
|
+
cached.showCwd === this.#showCwd &&
|
|
930
|
+
cached.currentPath === currentPath &&
|
|
931
|
+
cached.pinnedIds === this.#pinnedIds &&
|
|
932
|
+
cached.minute === minute
|
|
933
|
+
) {
|
|
934
|
+
return cached;
|
|
935
|
+
}
|
|
936
|
+
const now = Date.now();
|
|
937
|
+
const byPath = new Map<string, TspPickerItem>();
|
|
938
|
+
const items = this.#allSessions.map(session => {
|
|
939
|
+
const item = this.#pickerItem(session, currentPath, now);
|
|
940
|
+
byPath.set(session.path, item);
|
|
941
|
+
return item;
|
|
942
|
+
});
|
|
943
|
+
this.#pickerPatched.clear();
|
|
944
|
+
const catalogue: SessionCatalogue = {
|
|
945
|
+
version: this.#datasetVersion,
|
|
946
|
+
showCwd: this.#showCwd,
|
|
947
|
+
currentPath,
|
|
948
|
+
pinnedIds: this.#pinnedIds,
|
|
949
|
+
minute,
|
|
950
|
+
items,
|
|
951
|
+
byPath,
|
|
952
|
+
};
|
|
953
|
+
this.#pickerCatalogue = catalogue;
|
|
954
|
+
return catalogue;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
#pickerItem(session: T, currentPath: string | undefined, now: number): TspPickerItem {
|
|
958
|
+
const message = plainText(session.firstMessage).replace(/\s+/g, " ").trim();
|
|
959
|
+
const title = session.title ? plainText(session.title).trim() : "";
|
|
960
|
+
const badges: { text: string; tone?: TspTone; title?: string }[] = [];
|
|
961
|
+
if (session.parentSessionPath) {
|
|
962
|
+
badges.push({ text: "fork", title: `Forked from “${this.parentLabel(session.parentSessionPath)}”` });
|
|
963
|
+
}
|
|
964
|
+
if (session.path === currentPath) badges.push({ text: "current", tone: "accent" });
|
|
965
|
+
const when = pickerDate(session.modified);
|
|
966
|
+
const detail = this.#showCwd ? (session.cwd ? cwdSpans(session.cwd) : undefined) : title ? message : undefined;
|
|
967
|
+
const dot = sessionStatusTone(session.status);
|
|
968
|
+
return {
|
|
969
|
+
id: session.path,
|
|
970
|
+
label: title || message || session.id,
|
|
971
|
+
...(detail ? { detail } : {}),
|
|
972
|
+
facts: { when: pickerAge(session.modified, now), size: formatBytes(session.size) },
|
|
973
|
+
...(dot ? { dot } : {}),
|
|
974
|
+
...(this.#pinnedIds.has(session.id) ? { icon: "pin" } : {}),
|
|
975
|
+
...(badges.length > 0 ? { badges } : {}),
|
|
976
|
+
title: this.#showCwd && message ? `${message}\n${when}` : when,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/** Paths of the visible sessions the prompt-history merge surfaced for the current query. */
|
|
981
|
+
#historyFlagged(): string {
|
|
982
|
+
if (this.#historyIds.length === 0 || tokenizeSessionQuery(this.#searchInput.getValue()).length === 0) return "";
|
|
983
|
+
const ids = new Set(this.#historyIds);
|
|
984
|
+
const paths: string[] = [];
|
|
985
|
+
for (const session of this.#menu.visibleItems) if (ids.has(session.id)) paths.push(session.path);
|
|
986
|
+
return paths.join("\n");
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/**
|
|
990
|
+
* `itemsAdd` for the history badges: every session flagged now gets the
|
|
991
|
+
* badge, and every one flagged since the catalogue was sent but not now
|
|
992
|
+
* gets its plain item back (upserts stick until `items` is replaced).
|
|
993
|
+
*/
|
|
994
|
+
#historyPatch(
|
|
995
|
+
catalogue: readonly TspPickerItem[],
|
|
996
|
+
byPath: ReadonlyMap<string, TspPickerItem>,
|
|
997
|
+
flagged: string,
|
|
998
|
+
): readonly TspPickerItem[] {
|
|
999
|
+
const memo = this.#pickerPatch;
|
|
1000
|
+
if (memo?.catalogue === catalogue && memo.flagged === flagged) return memo.add;
|
|
1001
|
+
const now = new Set(flagged ? flagged.split("\n") : []);
|
|
1002
|
+
for (const key of now) this.#pickerPatched.add(key);
|
|
1003
|
+
const add: TspPickerItem[] = [];
|
|
1004
|
+
for (const key of this.#pickerPatched) {
|
|
1005
|
+
const base = byPath.get(key);
|
|
1006
|
+
if (!base) continue;
|
|
1007
|
+
add.push(
|
|
1008
|
+
now.has(key)
|
|
1009
|
+
? { ...base, badges: [...(base.badges ?? []), { text: "history", title: "Matched in prompt history" }] }
|
|
1010
|
+
: base,
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
this.#pickerPatch = { catalogue, flagged, add };
|
|
1014
|
+
return add;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
get searchInput(): Input {
|
|
1018
|
+
return this.#searchInput;
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* The session list as a native `list` keyed `"list"`, items keyed by
|
|
1023
|
+
* session path. The terminal owns scrolling and virtualization; item nodes
|
|
1024
|
+
* are cached per session and the list node while nothing visible changed.
|
|
1025
|
+
*/
|
|
1026
|
+
describeList(): NativeNode {
|
|
1027
|
+
const currentPath = this.#getCurrentSessionPath();
|
|
1028
|
+
let items = this.#itemsNative;
|
|
1029
|
+
if (
|
|
1030
|
+
items?.version !== this.#itemsVersion ||
|
|
1031
|
+
items.showCwd !== this.#showCwd ||
|
|
1032
|
+
items.currentPath !== currentPath
|
|
1033
|
+
) {
|
|
1034
|
+
items = {
|
|
1035
|
+
version: this.#itemsVersion,
|
|
1036
|
+
showCwd: this.#showCwd,
|
|
1037
|
+
currentPath,
|
|
1038
|
+
items: this.#menu.visibleItems.map(session => this.#describeSession(session, currentPath)),
|
|
1039
|
+
};
|
|
1040
|
+
this.#itemsNative = items;
|
|
1041
|
+
}
|
|
1042
|
+
const selected = this.#menu.selectedKey;
|
|
1043
|
+
const query = this.#searchInput.getValue();
|
|
1044
|
+
const memo = this.#listNative;
|
|
1045
|
+
if (memo?.items === items.items && memo.selected === selected && memo.query === query) return memo.node;
|
|
1046
|
+
const empty: TspText = this.#showCwd
|
|
1047
|
+
? [span("No sessions found", "muted")]
|
|
1048
|
+
: [span("No sessions in current folder. Press ", "muted"), span("tab", "key"), span(" to view all.", "muted")];
|
|
1049
|
+
const list = node(
|
|
1050
|
+
"list",
|
|
1051
|
+
{ selected: selected ?? null, filter: query.trim() || undefined, empty, virtual: true },
|
|
1052
|
+
items.items,
|
|
1053
|
+
"list",
|
|
1054
|
+
);
|
|
1055
|
+
this.#listNative = { items: items.items, selected, query, node: list };
|
|
1056
|
+
return list;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
#describeSession(session: T, currentPath: string | undefined): NativeNode {
|
|
1060
|
+
const date = formatSessionDate(session.modified);
|
|
1061
|
+
const pinned = this.#pinnedIds.has(session.id);
|
|
1062
|
+
const current = currentPath !== undefined && session.path === currentPath;
|
|
1063
|
+
const tagged = session as NativeSessionInfo;
|
|
1064
|
+
const cached = tagged[kNativeItem];
|
|
1065
|
+
if (
|
|
1066
|
+
cached?.date === date &&
|
|
1067
|
+
cached.pinned === pinned &&
|
|
1068
|
+
cached.current === current &&
|
|
1069
|
+
cached.showCwd === this.#showCwd
|
|
1070
|
+
) {
|
|
1071
|
+
return cached.node;
|
|
1072
|
+
}
|
|
1073
|
+
const message = plainText(session.firstMessage).replace(/\s+/g, " ").trim();
|
|
1074
|
+
const detail: TspSpan[] = [];
|
|
1075
|
+
const push = (part: TspSpan): void => {
|
|
1076
|
+
if (detail.length > 0) detail.push(span(` ${theme.sep.dot} `, "dim"));
|
|
1077
|
+
detail.push(part);
|
|
1078
|
+
};
|
|
1079
|
+
if (session.title && message) push(span(message, "dim"));
|
|
1080
|
+
if (current) push(span("current", "accent"));
|
|
1081
|
+
const status = sessionStatusSpan(session.status);
|
|
1082
|
+
if (status) push(status);
|
|
1083
|
+
if (session.parentSessionPath) push(span("fork", "dim"));
|
|
1084
|
+
if (this.#showCwd && session.cwd) push(span(shortenPath(session.cwd), "path"));
|
|
1085
|
+
const item = node(
|
|
1086
|
+
"item",
|
|
1087
|
+
{
|
|
1088
|
+
label: (session.title && plainText(session.title)) || message || session.id,
|
|
1089
|
+
detail: detail.length > 0 ? detail : undefined,
|
|
1090
|
+
value: [span(`${date} ${theme.sep.dot} ${formatBytes(session.size)}`, "dim")],
|
|
1091
|
+
icon: pinned ? "pin" : undefined,
|
|
1092
|
+
},
|
|
1093
|
+
undefined,
|
|
1094
|
+
session.path,
|
|
1095
|
+
);
|
|
1096
|
+
tagged[kNativeItem] = { node: item, date, pinned, current, showCwd: this.#showCwd };
|
|
1097
|
+
return item;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
602
1100
|
invalidate(): void {
|
|
603
1101
|
// No cached state to invalidate currently
|
|
604
1102
|
}
|
|
@@ -626,23 +1124,6 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
626
1124
|
return lines;
|
|
627
1125
|
}
|
|
628
1126
|
|
|
629
|
-
// Format dates
|
|
630
|
-
const formatDate = (date: Date): string => {
|
|
631
|
-
const now = new Date();
|
|
632
|
-
const diffMs = now.getTime() - date.getTime();
|
|
633
|
-
const diffMins = Math.floor(diffMs / 60000);
|
|
634
|
-
const diffHours = Math.floor(diffMs / 3600000);
|
|
635
|
-
const diffDays = Math.floor(diffMs / 86400000);
|
|
636
|
-
|
|
637
|
-
if (diffMins < 1) return "just now";
|
|
638
|
-
if (diffMins < 60) return `${diffMins} minute${diffMins !== 1 ? "s" : ""} ago`;
|
|
639
|
-
if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? "s" : ""} ago`;
|
|
640
|
-
if (diffDays === 1) return "1 day ago";
|
|
641
|
-
if (diffDays < 7) return `${diffDays} days ago`;
|
|
642
|
-
|
|
643
|
-
return date.toLocaleDateString();
|
|
644
|
-
};
|
|
645
|
-
|
|
646
1127
|
// Window the list around the selection by actual line height (3 lines
|
|
647
1128
|
// per session, 4 when a title adds a preview line) until the viewport
|
|
648
1129
|
// budget is spent, so short sessions never strand blank rows a
|
|
@@ -706,7 +1187,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
706
1187
|
// wrapping the whole line.
|
|
707
1188
|
const dim = (s: string) => theme.fg("dim", s);
|
|
708
1189
|
const dot = dim(theme.sep.dot);
|
|
709
|
-
const modified =
|
|
1190
|
+
const modified = formatSessionDate(session.modified);
|
|
710
1191
|
let metadata = ` ${dim(modified)} ${dot} ${dim(formatBytes(session.size))}`;
|
|
711
1192
|
if (currentPath !== undefined && session.path === currentPath) {
|
|
712
1193
|
metadata += ` ${dot} ${theme.fg("accent", "current")}`;
|
|
@@ -763,10 +1244,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
763
1244
|
matchesKey(keyData, "delete") ||
|
|
764
1245
|
(matchesKey(keyData, "backspace") && this.#searchInput.getValue().length === 0)
|
|
765
1246
|
) {
|
|
766
|
-
|
|
767
|
-
if (selected && this.onDeleteRequest) {
|
|
768
|
-
this.onDeleteRequest(selected);
|
|
769
|
-
}
|
|
1247
|
+
this.requestDelete();
|
|
770
1248
|
return;
|
|
771
1249
|
}
|
|
772
1250
|
// Up arrow
|
|
@@ -795,10 +1273,7 @@ class SessionList<T extends SessionSelectorEntry> implements Component {
|
|
|
795
1273
|
}
|
|
796
1274
|
// Enter
|
|
797
1275
|
if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
798
|
-
|
|
799
|
-
if (selected && this.onSelect) {
|
|
800
|
-
this.onSelect(selected);
|
|
801
|
-
}
|
|
1276
|
+
this.resumeSelected();
|
|
802
1277
|
return;
|
|
803
1278
|
}
|
|
804
1279
|
// Escape - cancel
|
|
@@ -853,6 +1328,12 @@ export interface SessionSelectorOptions<T extends SessionSelectorEntry = Session
|
|
|
853
1328
|
pinnedIds?: ReadonlySet<string>;
|
|
854
1329
|
/** Path of the live session, or a getter so detach/newSession stays accurate. */
|
|
855
1330
|
currentSessionPath?: string | (() => string | undefined);
|
|
1331
|
+
/**
|
|
1332
|
+
* The picker is the whole program (`omp --resume`): the native picker
|
|
1333
|
+
* fills the screen surface (`size:"screen"`) instead of floating as a
|
|
1334
|
+
* sheet over the transcript.
|
|
1335
|
+
*/
|
|
1336
|
+
standalone?: boolean;
|
|
856
1337
|
}
|
|
857
1338
|
|
|
858
1339
|
/**
|
|
@@ -890,6 +1371,40 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
890
1371
|
readonly #fillHeight: boolean;
|
|
891
1372
|
readonly #title: string;
|
|
892
1373
|
readonly #scopeLabel: string | false | undefined;
|
|
1374
|
+
/** What `#messageContainer` shows, for the native description. */
|
|
1375
|
+
#message: SessionPickerMessage | undefined;
|
|
1376
|
+
/** The native picker shows `#message`'s error until the next key or pointer event. */
|
|
1377
|
+
#pickerErrorOpen = false;
|
|
1378
|
+
readonly #standalone: boolean;
|
|
1379
|
+
readonly #pickerTitle: string;
|
|
1380
|
+
/** The open delete confirmation's two answers, for the picker's confirm strip. */
|
|
1381
|
+
#deleteChoice: DeleteChoice<T> | null = null;
|
|
1382
|
+
/** The preview pane's content and the session it shows; follows the selection once it settles. */
|
|
1383
|
+
#preview: { session: T | undefined; nodes: readonly NativeChild[] } | undefined;
|
|
1384
|
+
#previewPending: T | undefined;
|
|
1385
|
+
#previewSettled: T | undefined;
|
|
1386
|
+
#previewTimer: NodeJS.Timeout | undefined;
|
|
1387
|
+
#pickerMemo:
|
|
1388
|
+
| {
|
|
1389
|
+
view: SessionPickerView;
|
|
1390
|
+
preview: readonly NativeChild[];
|
|
1391
|
+
scope: "folder" | "all";
|
|
1392
|
+
message: SessionPickerMessage | undefined;
|
|
1393
|
+
errorOpen: boolean;
|
|
1394
|
+
choice: DeleteChoice<T> | null;
|
|
1395
|
+
node: NativeNode;
|
|
1396
|
+
}
|
|
1397
|
+
| undefined;
|
|
1398
|
+
#nativeMemo:
|
|
1399
|
+
| {
|
|
1400
|
+
title: string;
|
|
1401
|
+
scope: "folder" | "all";
|
|
1402
|
+
message: SessionPickerMessage | undefined;
|
|
1403
|
+
dialog: HookSelectorComponent | null;
|
|
1404
|
+
list: NativeNode;
|
|
1405
|
+
node: NativeNode;
|
|
1406
|
+
}
|
|
1407
|
+
| undefined;
|
|
893
1408
|
|
|
894
1409
|
constructor(
|
|
895
1410
|
sessions: T[],
|
|
@@ -908,6 +1423,8 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
908
1423
|
this.#getTerminalRows = options.getTerminalRows ?? (() => 24);
|
|
909
1424
|
this.#fillHeight = options.fillHeight ?? false;
|
|
910
1425
|
this.#title = options.title ?? "Resume Session";
|
|
1426
|
+
this.#pickerTitle = options.title ?? "Resume session";
|
|
1427
|
+
this.#standalone = options.standalone ?? false;
|
|
911
1428
|
this.#scopeLabel = options.scopeLabel;
|
|
912
1429
|
this.title = this.#headerLabel();
|
|
913
1430
|
// One spacer of breathing room; OverlayPanel supplies the two outer
|
|
@@ -973,6 +1490,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
973
1490
|
this.#toggling = true;
|
|
974
1491
|
this.#messageContainer.clear();
|
|
975
1492
|
this.#messageContainer.addChild(new Text(theme.fg("muted", "Loading all projects…"), 0, 0));
|
|
1493
|
+
this.#message = { kind: "loading", text: "Loading all projects…" };
|
|
976
1494
|
this.#onRequestRender?.();
|
|
977
1495
|
try {
|
|
978
1496
|
global = await this.#loadAllSessions();
|
|
@@ -984,6 +1502,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
984
1502
|
}
|
|
985
1503
|
this.#globalSessions = global;
|
|
986
1504
|
this.#messageContainer.clear();
|
|
1505
|
+
this.#message = undefined;
|
|
987
1506
|
this.#toggling = false;
|
|
988
1507
|
}
|
|
989
1508
|
this.#scope = "all";
|
|
@@ -1015,15 +1534,19 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1015
1534
|
*/
|
|
1016
1535
|
override dispose(): void {
|
|
1017
1536
|
this.#sessionList.dispose();
|
|
1537
|
+
clearTimeout(this.#previewTimer);
|
|
1018
1538
|
super.dispose();
|
|
1019
1539
|
}
|
|
1020
1540
|
|
|
1021
1541
|
#clearError(): void {
|
|
1022
1542
|
this.#messageContainer.clear();
|
|
1543
|
+
this.#message = undefined;
|
|
1023
1544
|
}
|
|
1024
1545
|
|
|
1025
1546
|
#showError(message: string): void {
|
|
1026
1547
|
this.#messageContainer.clear();
|
|
1548
|
+
this.#message = { kind: "error", text: `Error: ${plainText(message)}` };
|
|
1549
|
+
this.#pickerErrorOpen = true;
|
|
1027
1550
|
this.#messageContainer.addChild(new Text(theme.fg("error", `Error: ${replaceTabs(message)}`), 0, 0));
|
|
1028
1551
|
this.#messageContainer.addChild(new Spacer(1));
|
|
1029
1552
|
}
|
|
@@ -1032,6 +1555,7 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1032
1555
|
const displayName = session.title || session.firstMessage.slice(0, 40) || session.id;
|
|
1033
1556
|
const closeDialog = () => {
|
|
1034
1557
|
this.#confirmationDialog = null;
|
|
1558
|
+
this.#deleteChoice = null;
|
|
1035
1559
|
// Restore the SessionList into the content slot so the picker is back
|
|
1036
1560
|
// to its normal layout on the very next render — the same frame the
|
|
1037
1561
|
// dialog disappears.
|
|
@@ -1039,29 +1563,39 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1039
1563
|
this.#contentSlot.addChild(this.#sessionList);
|
|
1040
1564
|
this.#onRequestRender?.();
|
|
1041
1565
|
};
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
if (
|
|
1051
|
-
this.#
|
|
1052
|
-
this.#folderSessions = this.#folderSessions.filter(s => s.path !== session.path);
|
|
1053
|
-
if (this.#globalSessions) {
|
|
1054
|
-
this.#globalSessions = this.#globalSessions.filter(s => s.path !== session.path);
|
|
1055
|
-
}
|
|
1566
|
+
const answer = async (option: string) => {
|
|
1567
|
+
if (option === "Yes" && this.#onDelete) {
|
|
1568
|
+
this.#clearError();
|
|
1569
|
+
try {
|
|
1570
|
+
const deleted = await this.#onDelete(session);
|
|
1571
|
+
if (deleted) {
|
|
1572
|
+
this.#sessionList.removeSession(session.path);
|
|
1573
|
+
this.#folderSessions = this.#folderSessions.filter(s => s.path !== session.path);
|
|
1574
|
+
if (this.#globalSessions) {
|
|
1575
|
+
this.#globalSessions = this.#globalSessions.filter(s => s.path !== session.path);
|
|
1056
1576
|
}
|
|
1057
|
-
} catch (err) {
|
|
1058
|
-
this.#showError(err instanceof Error ? err.message : String(err));
|
|
1059
1577
|
}
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
this.#showError(err instanceof Error ? err.message : String(err));
|
|
1060
1580
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1581
|
+
}
|
|
1582
|
+
closeDialog();
|
|
1583
|
+
};
|
|
1584
|
+
this.#confirmationDialog = new HookSelectorComponent(
|
|
1585
|
+
`Delete session?\n${displayName}`,
|
|
1586
|
+
["Yes", "No"],
|
|
1587
|
+
answer,
|
|
1063
1588
|
closeDialog,
|
|
1064
1589
|
);
|
|
1590
|
+
this.#deleteChoice = {
|
|
1591
|
+
session,
|
|
1592
|
+
confirm: () => {
|
|
1593
|
+
// The strip goes at once; the dialog stays until the delete settles, as after Enter on "Yes".
|
|
1594
|
+
this.#deleteChoice = null;
|
|
1595
|
+
void answer("Yes");
|
|
1596
|
+
},
|
|
1597
|
+
cancel: closeDialog,
|
|
1598
|
+
};
|
|
1065
1599
|
// Swap the SessionList out of the content slot and mount the dialog in its
|
|
1066
1600
|
// place: the dialog competes only with the SessionList's rendered budget,
|
|
1067
1601
|
// never the SessionList AND the picker chrome, so the picker frame stays
|
|
@@ -1108,8 +1642,235 @@ export class SessionSelectorComponent<T extends SessionSelectorEntry = SessionSe
|
|
|
1108
1642
|
return [row("", width), row(hint, width), row("", width), bottomBorder(width)];
|
|
1109
1643
|
}
|
|
1110
1644
|
|
|
1645
|
+
/** Scope tabs replace the "(current folder)" title suffix when the scope can toggle. */
|
|
1646
|
+
#hasScopeTabs(): boolean {
|
|
1647
|
+
return this.#scopeLabel === undefined && this.#sessionList.onToggleScope !== undefined;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
/** Over the transcript the picker is its own sheet (`layer`); the standalone app fills the screen surface. */
|
|
1651
|
+
nativeSheet(cx: DescribeContext): boolean {
|
|
1652
|
+
return cx.supports("picker") && !this.#standalone;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
* With the `picker` kind: the data-first session picker (§4.7). Otherwise
|
|
1657
|
+
* scope `tabs`, loading/error message, then the search `input` and session
|
|
1658
|
+
* `list` — or the delete-confirmation dialog in their place, as on the ANSI
|
|
1659
|
+
* path — and the key-hint footer.
|
|
1660
|
+
*/
|
|
1661
|
+
override describe(cx: DescribeContext): NativeNode {
|
|
1662
|
+
if (cx.supports("picker")) return this.#describePicker();
|
|
1663
|
+
const list = this.#sessionList.describeList();
|
|
1664
|
+
const memo = this.#nativeMemo;
|
|
1665
|
+
if (
|
|
1666
|
+
memo?.title === this.title &&
|
|
1667
|
+
memo.scope === this.#scope &&
|
|
1668
|
+
memo.message === this.#message &&
|
|
1669
|
+
memo.dialog === this.#confirmationDialog &&
|
|
1670
|
+
memo.list === list
|
|
1671
|
+
) {
|
|
1672
|
+
return memo.node;
|
|
1673
|
+
}
|
|
1674
|
+
const tabs = this.#hasScopeTabs();
|
|
1675
|
+
const children: NativeChild[] = [];
|
|
1676
|
+
if (tabs) children.push(node("tabs", { items: SCOPE_TABS, active: this.#scope }, undefined, "scope"));
|
|
1677
|
+
const message = this.#message;
|
|
1678
|
+
if (message?.kind === "loading") {
|
|
1679
|
+
children.push(node("spinner", { label: [span(message.text, "muted")] }, undefined, "message"));
|
|
1680
|
+
} else if (message) {
|
|
1681
|
+
children.push(node("text", { spans: [span(message.text, "error")] }, undefined, "message"));
|
|
1682
|
+
}
|
|
1683
|
+
if (this.#confirmationDialog) {
|
|
1684
|
+
children.push(this.#confirmationDialog);
|
|
1685
|
+
} else {
|
|
1686
|
+
children.push(this.#sessionList.searchInput, list);
|
|
1687
|
+
}
|
|
1688
|
+
const hints: (NativeHint | undefined)[] = [
|
|
1689
|
+
{ keys: ["delete", "backspace"], label: "delete" },
|
|
1690
|
+
{ keys: ["enter"], label: "select" },
|
|
1691
|
+
];
|
|
1692
|
+
if (this.#sessionList.onToggleScope) {
|
|
1693
|
+
hints.push({ keys: ["tab"], label: this.#scope === "all" ? "current folder" : "all projects" });
|
|
1694
|
+
}
|
|
1695
|
+
hints.push({ keys: [boundKeys("app.interrupt", ["escape"])[0] ?? "escape"], label: "cancel" });
|
|
1696
|
+
const result = overlayCard("omp.overlay.sessions", tabs ? this.#title : this.title, [
|
|
1697
|
+
...children,
|
|
1698
|
+
hintsRow(hints),
|
|
1699
|
+
]);
|
|
1700
|
+
this.#nativeMemo = {
|
|
1701
|
+
title: this.title,
|
|
1702
|
+
scope: this.#scope,
|
|
1703
|
+
message,
|
|
1704
|
+
dialog: this.#confirmationDialog,
|
|
1705
|
+
list,
|
|
1706
|
+
node: result,
|
|
1707
|
+
};
|
|
1708
|
+
return result;
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
/**
|
|
1712
|
+
* `lg` cards sheet (`screen` for the standalone app): This folder / All
|
|
1713
|
+
* projects tabs, sessions grouped by day (ranked flat while searching),
|
|
1714
|
+
* the selected session's preview, the delete confirm strip, and the
|
|
1715
|
+
* actions of the keys Enter, Delete/Backspace, Tab and Esc.
|
|
1716
|
+
*/
|
|
1717
|
+
#describePicker(): NativeNode {
|
|
1718
|
+
const list = this.#sessionList;
|
|
1719
|
+
const view = list.pickerView();
|
|
1720
|
+
const preview = this.#previewFor(list.selectedSession);
|
|
1721
|
+
const message = this.#message;
|
|
1722
|
+
const errorOpen = this.#pickerErrorOpen && message?.kind === "error";
|
|
1723
|
+
const choice = this.#deleteChoice;
|
|
1724
|
+
const memo = this.#pickerMemo;
|
|
1725
|
+
if (
|
|
1726
|
+
memo?.view === view &&
|
|
1727
|
+
memo.preview === preview &&
|
|
1728
|
+
memo.scope === this.#scope &&
|
|
1729
|
+
memo.message === message &&
|
|
1730
|
+
memo.errorOpen === errorOpen &&
|
|
1731
|
+
memo.choice === choice
|
|
1732
|
+
) {
|
|
1733
|
+
return memo.node;
|
|
1734
|
+
}
|
|
1735
|
+
const loading = message?.kind === "loading";
|
|
1736
|
+
const toggle = list.onToggleScope !== undefined;
|
|
1737
|
+
const actions = [pickerAction("resume", "Resume", "enter", { primary: true })];
|
|
1738
|
+
if (this.#onDelete) actions.push(pickerAction("delete", "Delete", "backspace"));
|
|
1739
|
+
if (toggle) {
|
|
1740
|
+
actions.push(pickerAction("scope", this.#scope === "all" ? "This folder" : "All projects", "tab"));
|
|
1741
|
+
}
|
|
1742
|
+
actions.push(
|
|
1743
|
+
pickerAction("close", "Close", boundKeys("app.interrupt", ["escape"])[0] ?? "escape", { end: true }),
|
|
1744
|
+
);
|
|
1745
|
+
const subtitle = this.#scopeLabel === false ? undefined : (this.#scopeLabel ?? path.basename(getProjectDir()));
|
|
1746
|
+
const result = picker(
|
|
1747
|
+
{
|
|
1748
|
+
title: this.#pickerTitle,
|
|
1749
|
+
...(subtitle ? { subtitle } : {}),
|
|
1750
|
+
icon: "history",
|
|
1751
|
+
noun: "sessions",
|
|
1752
|
+
size: this.#standalone ? "screen" : "lg",
|
|
1753
|
+
layout: "cards",
|
|
1754
|
+
preview: "side",
|
|
1755
|
+
query: view.query,
|
|
1756
|
+
cursor: view.cursor,
|
|
1757
|
+
placeholder: "Search sessions…",
|
|
1758
|
+
...(this.#hasScopeTabs()
|
|
1759
|
+
? {
|
|
1760
|
+
tabs: [
|
|
1761
|
+
{ id: "folder", label: "This folder" },
|
|
1762
|
+
{ id: "all", label: "All projects" },
|
|
1763
|
+
],
|
|
1764
|
+
tab: loading ? "all" : this.#scope,
|
|
1765
|
+
}
|
|
1766
|
+
: {}),
|
|
1767
|
+
columns: [
|
|
1768
|
+
{ id: "when", format: "time" },
|
|
1769
|
+
{ id: "size", format: "dim" },
|
|
1770
|
+
],
|
|
1771
|
+
items: view.items,
|
|
1772
|
+
...(view.itemsAdd ? { itemsAdd: view.itemsAdd } : {}),
|
|
1773
|
+
order: view.order,
|
|
1774
|
+
...(view.hits ? { hits: view.hits } : {}),
|
|
1775
|
+
selected: view.selected,
|
|
1776
|
+
current: view.current,
|
|
1777
|
+
total: view.total,
|
|
1778
|
+
actions,
|
|
1779
|
+
state: loading ? "loading" : errorOpen ? "error" : "ready",
|
|
1780
|
+
...(loading || errorOpen ? { message: message!.text } : {}),
|
|
1781
|
+
empty: this.#scope === "all" || !toggle ? "No sessions yet" : "No sessions in this folder yet",
|
|
1782
|
+
confirm: choice
|
|
1783
|
+
? {
|
|
1784
|
+
text: `Delete “${sessionLabel(choice.session)}”? This removes the session file.`,
|
|
1785
|
+
act: "delete-confirm",
|
|
1786
|
+
label: "Delete",
|
|
1787
|
+
}
|
|
1788
|
+
: null,
|
|
1789
|
+
},
|
|
1790
|
+
preview,
|
|
1791
|
+
);
|
|
1792
|
+
this.#pickerMemo = { view, preview, scope: this.#scope, message, errorOpen, choice, node: result };
|
|
1793
|
+
return result;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
/**
|
|
1797
|
+
* The preview children for `target`: built at once for the first
|
|
1798
|
+
* selection, then only after the selection has rested
|
|
1799
|
+
* {@link PREVIEW_SETTLE_MS} (the old preview stays meanwhile).
|
|
1800
|
+
*/
|
|
1801
|
+
#previewFor(target: T | undefined): readonly NativeChild[] {
|
|
1802
|
+
const shown = this.#preview;
|
|
1803
|
+
if (shown && shown.session === target) return shown.nodes;
|
|
1804
|
+
if (shown && target !== this.#previewSettled) {
|
|
1805
|
+
if (this.#previewPending !== target || this.#previewTimer === undefined) {
|
|
1806
|
+
clearTimeout(this.#previewTimer);
|
|
1807
|
+
this.#previewPending = target;
|
|
1808
|
+
this.#previewTimer = setTimeout(() => {
|
|
1809
|
+
this.#previewTimer = undefined;
|
|
1810
|
+
this.#previewSettled = this.#previewPending;
|
|
1811
|
+
this.#onRequestRender?.();
|
|
1812
|
+
}, PREVIEW_SETTLE_MS);
|
|
1813
|
+
}
|
|
1814
|
+
return shown.nodes;
|
|
1815
|
+
}
|
|
1816
|
+
const nodes = target
|
|
1817
|
+
? sessionPreview(
|
|
1818
|
+
target,
|
|
1819
|
+
target.parentSessionPath ? this.#sessionList.parentLabel(target.parentSessionPath) : undefined,
|
|
1820
|
+
)
|
|
1821
|
+
: [];
|
|
1822
|
+
this.#preview = { session: target, nodes };
|
|
1823
|
+
return nodes;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* Picker pointer events run the keys' paths: a row click selects (arrows),
|
|
1828
|
+
* a second click or `Resume` resumes (Enter), `Delete` asks (Delete), the
|
|
1829
|
+
* tab or `All projects` toggles scope (Tab), `Close` cancels (Esc), and
|
|
1830
|
+
* the confirm strip answers the delete dialog (Yes / Esc).
|
|
1831
|
+
*/
|
|
1832
|
+
#handlePickerEvent(ev: PickerEvent): void {
|
|
1833
|
+
const list = this.#sessionList;
|
|
1834
|
+
const choice = this.#deleteChoice;
|
|
1835
|
+
if (ev.kind !== "action") {
|
|
1836
|
+
if (choice) return;
|
|
1837
|
+
if (ev.kind === "select") list.selectSession(ev.item);
|
|
1838
|
+
else list.confirmSession(ev.item);
|
|
1839
|
+
return;
|
|
1840
|
+
}
|
|
1841
|
+
if (ev.act === "delete-confirm") choice?.confirm();
|
|
1842
|
+
else if (ev.act === "cancel") choice?.cancel();
|
|
1843
|
+
else if (ev.act === "close") {
|
|
1844
|
+
if (choice) choice.cancel();
|
|
1845
|
+
else list.onCancel?.();
|
|
1846
|
+
} else if (choice) return;
|
|
1847
|
+
else if (ev.act === "resume") list.resumeSelected();
|
|
1848
|
+
else if (ev.act === "delete") list.requestDelete();
|
|
1849
|
+
else if (ev.act === "scope" || (ev.act === "tab" && ev.value !== undefined && ev.value !== this.#scope)) {
|
|
1850
|
+
list.onToggleScope?.();
|
|
1851
|
+
} else if (ev.act === "clear") list.clearSearch();
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
/** Picker events (root keypath); fallback: scope tab → toggle scope, click on a session → resume it, exactly like a mouse click. */
|
|
1855
|
+
handleNativeEvent(event: NativeUiEvent): void {
|
|
1856
|
+
if (this.#inputLocked) return;
|
|
1857
|
+
const ev = pickerEvent(event);
|
|
1858
|
+
if (ev) {
|
|
1859
|
+
this.#pickerErrorOpen = false;
|
|
1860
|
+
this.#handlePickerEvent(ev);
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
if (event.type !== "select" && event.type !== "activate") return;
|
|
1864
|
+
if (event.key === "scope") {
|
|
1865
|
+
if (event.type === "select" && event.item !== this.#scope) void this.#toggleScope();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1868
|
+
if (event.key === "list" && !this.#confirmationDialog) this.#sessionList.confirmSession(event.item);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1111
1871
|
handleInput(keyData: string): void {
|
|
1112
1872
|
if (this.#inputLocked) return;
|
|
1873
|
+
this.#pickerErrorOpen = false;
|
|
1113
1874
|
if (keyData.startsWith("\x1b[<")) {
|
|
1114
1875
|
this.#handleMouse(keyData);
|
|
1115
1876
|
return;
|