@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
|
@@ -0,0 +1,682 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Native (Tern Surface Protocol) backend: owns the surfaces a TSP terminal
|
|
3
|
+
* shows, sends reconciled frames under credit-based flow control, uploads
|
|
4
|
+
* blobs, and routes terminal events back to components.
|
|
5
|
+
*
|
|
6
|
+
* Surfaces. An inline surface (`mode:"inline"`) holds the session: its root
|
|
7
|
+
* (id = surface id) gets the fixed regions `main`, `dock` and `layer` on the
|
|
8
|
+
* first frame. While a fullscreen overlay is topmost, a `mode:"screen"`
|
|
9
|
+
* surface shows it instead and the inline surface pauses (the way the ANSI
|
|
10
|
+
* path borrows the alternate screen); closing the overlay closes that surface
|
|
11
|
+
* and the next inline frame catches up.
|
|
12
|
+
*
|
|
13
|
+
* Flow control. At most `credits` frames per surface are unacknowledged. A
|
|
14
|
+
* render while blocked only marks the surface dirty; the next `ack` reconciles
|
|
15
|
+
* once against the last sent state, so every intermediate change coalesces
|
|
16
|
+
* into one frame (`set`s merged, `text append`s joined).
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as logger from "@oh-my-pi/pi-utils/logger";
|
|
20
|
+
import {
|
|
21
|
+
TSP_DEFAULT_APC_LIMIT,
|
|
22
|
+
TSP_DEFAULT_CREDITS,
|
|
23
|
+
TSP_KINDS,
|
|
24
|
+
TSP_VERSION,
|
|
25
|
+
type TspEvent,
|
|
26
|
+
type TspFrame,
|
|
27
|
+
type TspKind,
|
|
28
|
+
type TspNode,
|
|
29
|
+
type TspOp,
|
|
30
|
+
} from "@oh-my-pi/pi-wire";
|
|
31
|
+
import type { Terminal } from "../terminal";
|
|
32
|
+
import {
|
|
33
|
+
bindTheme,
|
|
34
|
+
getCurrentThemeName,
|
|
35
|
+
getNativeThemePalette,
|
|
36
|
+
getNativeThemePaletteKey,
|
|
37
|
+
isLightTheme,
|
|
38
|
+
type NativeThemePalette,
|
|
39
|
+
setNativeSymbolPreset,
|
|
40
|
+
} from "../theme/theme";
|
|
41
|
+
import type { Component, OverlayOptions } from "../tui";
|
|
42
|
+
import { TspDocument } from "./apply";
|
|
43
|
+
import { getNativeBlob } from "./blobs";
|
|
44
|
+
import { node } from "./describe";
|
|
45
|
+
import { encodeTspJson, encodeTspMessage, type TspHello, TspReader, splitTspMessage } from "./encode";
|
|
46
|
+
import type { DescribeContext, NativeChild, NativeNode, NativeSurface, NativeUiEvent } from "./node";
|
|
47
|
+
import { nativeComponentId, Reconciler } from "./reconcile";
|
|
48
|
+
import { setNativeRendering } from "./state";
|
|
49
|
+
|
|
50
|
+
/** A visible TUI overlay, bottom to top. */
|
|
51
|
+
export interface NativeOverlay {
|
|
52
|
+
readonly component: Component;
|
|
53
|
+
readonly options: OverlayOptions | undefined;
|
|
54
|
+
/** Keyboard focus is inside this overlay. */
|
|
55
|
+
readonly focused: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** What the backend needs from the TUI. */
|
|
59
|
+
export interface NativeHost {
|
|
60
|
+
readonly terminal: Terminal;
|
|
61
|
+
/** `main`/`dock` content: the frame provider's surface, or the TUI children. */
|
|
62
|
+
describeSurface(cx: DescribeContext): NativeSurface;
|
|
63
|
+
/** Visible overlays, bottom to top. */
|
|
64
|
+
overlays(): readonly NativeOverlay[];
|
|
65
|
+
/** Component receiving keyboard input. */
|
|
66
|
+
focused(): Component | null;
|
|
67
|
+
requestRender(): void;
|
|
68
|
+
/** The terminal switched appearance. */
|
|
69
|
+
appearanceChanged(dark: boolean): void;
|
|
70
|
+
/** Reduce Motion toggled. */
|
|
71
|
+
motionChanged(reduce: boolean): void;
|
|
72
|
+
/** Theme-derived output changed (symbol preset): drop render and describe caches. */
|
|
73
|
+
invalidate(): void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface NativeBackendOptions {
|
|
77
|
+
/** Keep the reference document and recent frames for debugging. */
|
|
78
|
+
readonly mirror?: boolean;
|
|
79
|
+
/** Append every TSP message to this JSONL file. Defaults to `PI_TUI_TSP_RECORD`. */
|
|
80
|
+
readonly recordPath?: string;
|
|
81
|
+
/** Log the `rows` fallback count per frame. Defaults to `PI_TUI_NATIVE_STATS=1`. */
|
|
82
|
+
readonly stats?: boolean;
|
|
83
|
+
readonly now?: () => number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Frames kept for the debug `tsp` op. */
|
|
87
|
+
const RECENT_FRAMES = 64;
|
|
88
|
+
/** An unanswered frame older than this no longer holds rendering back. */
|
|
89
|
+
const STALLED_ACK_MS = 5000;
|
|
90
|
+
|
|
91
|
+
class NativeContext implements DescribeContext {
|
|
92
|
+
cols: number;
|
|
93
|
+
reduceMotion: boolean;
|
|
94
|
+
dark: boolean;
|
|
95
|
+
#kinds: ReadonlySet<string>;
|
|
96
|
+
#features: ReadonlySet<string>;
|
|
97
|
+
|
|
98
|
+
constructor(hello: TspHello, cols: number) {
|
|
99
|
+
this.cols = cols;
|
|
100
|
+
this.reduceMotion = hello.reduceMotion === true;
|
|
101
|
+
this.dark = hello.dark !== false;
|
|
102
|
+
this.#kinds = new Set(hello.kinds);
|
|
103
|
+
this.#features = new Set(hello.features);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
supports(kind: TspKind): boolean {
|
|
107
|
+
return this.#kinds.has(kind);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
feature(name: string): boolean {
|
|
111
|
+
return this.#features.has(name);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Whether `other` advertises exactly the same kinds and features. */
|
|
115
|
+
sameVocabulary(other: NativeContext): boolean {
|
|
116
|
+
return sameSet(this.#kinds, other.#kinds) && sameSet(this.#features, other.#features);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Whether `a` and `b` hold the same strings. */
|
|
121
|
+
function sameSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
122
|
+
if (a.size !== b.size) return false;
|
|
123
|
+
for (const item of a) if (!b.has(item)) return false;
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
129
|
+
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary, the
|
|
130
|
+
* default APC limit and credits, the terminal's width, the appearance omp
|
|
131
|
+
* already detected, and full motion.
|
|
132
|
+
*/
|
|
133
|
+
export function assumedTspHello(terminal: Terminal): TspHello {
|
|
134
|
+
const appearance = terminal.appearance;
|
|
135
|
+
return {
|
|
136
|
+
r: "hello",
|
|
137
|
+
v: TSP_VERSION,
|
|
138
|
+
term: "tern",
|
|
139
|
+
kinds: TSP_KINDS,
|
|
140
|
+
apc: TSP_DEFAULT_APC_LIMIT,
|
|
141
|
+
credits: TSP_DEFAULT_CREDITS,
|
|
142
|
+
cols: terminal.columns,
|
|
143
|
+
dark: appearance === undefined ? !isLightTheme(getCurrentThemeName()) : appearance === "dark",
|
|
144
|
+
reduceMotion: false,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
class Surface {
|
|
149
|
+
readonly id: string;
|
|
150
|
+
readonly mode: "inline" | "screen";
|
|
151
|
+
readonly reconciler: Reconciler;
|
|
152
|
+
readonly doc: TspDocument | null;
|
|
153
|
+
seq = 0;
|
|
154
|
+
acked = 0;
|
|
155
|
+
/** Send times of unacknowledged frames, oldest first. */
|
|
156
|
+
unacked: number[] = [];
|
|
157
|
+
uploaded = new Set<string>();
|
|
158
|
+
focus: string | null = null;
|
|
159
|
+
dirty = false;
|
|
160
|
+
|
|
161
|
+
constructor(id: string, mode: "inline" | "screen", mirror: boolean) {
|
|
162
|
+
this.id = id;
|
|
163
|
+
this.mode = mode;
|
|
164
|
+
this.reconciler = new Reconciler(id);
|
|
165
|
+
this.doc = mirror ? new TspDocument(id) : null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function overlayAnchor(options: OverlayOptions | undefined): "center" | "top" | "bottom" {
|
|
170
|
+
const anchor = options?.anchor;
|
|
171
|
+
if (anchor?.startsWith("top")) return "top";
|
|
172
|
+
if (anchor?.startsWith("bottom")) return "bottom";
|
|
173
|
+
return "center";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function overlaySize(options: OverlayOptions | undefined, cols: number): "sm" | "md" | "lg" | "full" {
|
|
177
|
+
if (options?.fullscreen) return "full";
|
|
178
|
+
const width = options?.width;
|
|
179
|
+
if (width === undefined) return "md";
|
|
180
|
+
const fraction = typeof width === "number" ? width / Math.max(1, cols) : Number.parseFloat(width) / 100;
|
|
181
|
+
if (!Number.isFinite(fraction)) return "md";
|
|
182
|
+
if (fraction >= 0.75) return "lg";
|
|
183
|
+
if (fraction >= 0.45) return "md";
|
|
184
|
+
return "sm";
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Blob ids referenced by `image` nodes an op adds or sets. */
|
|
188
|
+
function collectBlobs(op: TspOp, out: Set<string>): void {
|
|
189
|
+
if (op[0] === "set") {
|
|
190
|
+
const blob = op[2].blob;
|
|
191
|
+
if (typeof blob === "string") out.add(blob);
|
|
192
|
+
} else if (op[0] === "add") {
|
|
193
|
+
const visit = (wire: TspNode): void => {
|
|
194
|
+
if (wire.k === "image" && typeof wire.p?.blob === "string") out.add(wire.p.blob);
|
|
195
|
+
if (wire.c) for (const child of wire.c) visit(child);
|
|
196
|
+
};
|
|
197
|
+
visit(op[4]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export class NativeBackend {
|
|
202
|
+
#host: NativeHost;
|
|
203
|
+
#hello: TspHello;
|
|
204
|
+
#cx: NativeContext;
|
|
205
|
+
#limit: number;
|
|
206
|
+
#credits: number;
|
|
207
|
+
#reader = new TspReader();
|
|
208
|
+
#inline: Surface;
|
|
209
|
+
#screen: Surface | null = null;
|
|
210
|
+
#nextSurface = 1;
|
|
211
|
+
#mirror: boolean;
|
|
212
|
+
#recordPath: string | undefined;
|
|
213
|
+
#stats: boolean;
|
|
214
|
+
#now: () => number;
|
|
215
|
+
#recent: TspFrame[] = [];
|
|
216
|
+
#sawResize = false;
|
|
217
|
+
#live = false;
|
|
218
|
+
#overlayNodes = new Map<Component, { key: string; node: NativeNode }>();
|
|
219
|
+
#unbindTheme: (() => void) | undefined;
|
|
220
|
+
#palette: NativeThemePalette | undefined;
|
|
221
|
+
#paletteKey: string | undefined;
|
|
222
|
+
/** Serialized palette last sent, to skip resends of an unchanged theme. */
|
|
223
|
+
#paletteSent: string | undefined;
|
|
224
|
+
|
|
225
|
+
constructor(host: NativeHost, hello: TspHello, options: NativeBackendOptions = {}) {
|
|
226
|
+
this.#host = host;
|
|
227
|
+
this.#hello = hello;
|
|
228
|
+
this.#cx = new NativeContext(hello, hello.cols ?? host.terminal.columns);
|
|
229
|
+
this.#limit = TSP_DEFAULT_APC_LIMIT;
|
|
230
|
+
this.#credits = TSP_DEFAULT_CREDITS;
|
|
231
|
+
this.#applyHello(hello);
|
|
232
|
+
this.#mirror = options.mirror === true;
|
|
233
|
+
this.#recordPath = options.recordPath ?? (Bun.env.PI_TUI_TSP_RECORD || undefined);
|
|
234
|
+
this.#stats = options.stats ?? Bun.env.PI_TUI_NATIVE_STATS === "1";
|
|
235
|
+
this.#now = options.now ?? Date.now;
|
|
236
|
+
this.#inline = this.#newSurface("inline");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** The terminal's hello reply. */
|
|
240
|
+
get hello(): TspHello {
|
|
241
|
+
return this.#hello;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** Describe context shared by every surface. */
|
|
245
|
+
get context(): DescribeContext {
|
|
246
|
+
return this.#cx;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `rows` fallback nodes in the last frame. */
|
|
250
|
+
get fallbackCount(): number {
|
|
251
|
+
return (this.#screen ?? this.#inline).reconciler.fallbackCount;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Open the inline surface and send the first frame. Native rendering is
|
|
256
|
+
* announced only once the surface is open: Tern drops the shell's title
|
|
257
|
+
* when a command's first surface opens, so the tab title set on the change
|
|
258
|
+
* has to follow the `o`.
|
|
259
|
+
*/
|
|
260
|
+
start(): void {
|
|
261
|
+
if (this.#live) return;
|
|
262
|
+
this.#live = true;
|
|
263
|
+
this.#useNerdSymbols(true);
|
|
264
|
+
this.#watchTheme();
|
|
265
|
+
this.#open(this.#inline);
|
|
266
|
+
setNativeRendering(true);
|
|
267
|
+
this.render();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Any theme swap (switch, selector preview, auto light/dark, file reload)
|
|
272
|
+
* schedules a render; `render()` resends the palette when it changed, so
|
|
273
|
+
* bursts coalesce into one `t` and no frame is needed for it.
|
|
274
|
+
*/
|
|
275
|
+
#watchTheme(): void {
|
|
276
|
+
this.#unbindTheme?.();
|
|
277
|
+
let initial = true;
|
|
278
|
+
this.#unbindTheme = bindTheme(() => {
|
|
279
|
+
if (initial) return;
|
|
280
|
+
this.#host.requestRender();
|
|
281
|
+
});
|
|
282
|
+
initial = false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** The current palette, recomputed only when its inputs changed. */
|
|
286
|
+
#currentPalette(): NativeThemePalette {
|
|
287
|
+
const key = getNativeThemePaletteKey();
|
|
288
|
+
if (this.#palette === undefined || key !== this.#paletteKey) {
|
|
289
|
+
this.#paletteKey = key;
|
|
290
|
+
this.#palette = getNativeThemePalette();
|
|
291
|
+
}
|
|
292
|
+
return this.#palette;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Send the palette to `surface` (right after its `o`, before any `f`). */
|
|
296
|
+
#sendPalette(surface: Surface): void {
|
|
297
|
+
const palette = this.#currentPalette();
|
|
298
|
+
if (!palette.dark && !palette.light) return;
|
|
299
|
+
this.#paletteSent = JSON.stringify(palette);
|
|
300
|
+
this.#write("t", { sf: surface.id, ...palette });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Resend the palette to every open surface when the theme changed since it was sent. */
|
|
304
|
+
#refreshPalette(): void {
|
|
305
|
+
const key = this.#paletteKey;
|
|
306
|
+
const palette = this.#currentPalette();
|
|
307
|
+
if (key === this.#paletteKey || JSON.stringify(palette) === this.#paletteSent) return;
|
|
308
|
+
this.#sendPalette(this.#inline);
|
|
309
|
+
if (this.#screen) this.#sendPalette(this.#screen);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Tern's font carries every Nerd Font glyph, and it never answers the Glyph
|
|
314
|
+
* Protocol, so icons come from the nerd preset (process-local, not the
|
|
315
|
+
* user's saved setting) while a surface is live.
|
|
316
|
+
*/
|
|
317
|
+
#useNerdSymbols(on: boolean): void {
|
|
318
|
+
if (setNativeSymbolPreset(on ? "nerd" : undefined)) this.#host.invalidate();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Close every surface. The inline transcript stays in scrollback unless
|
|
323
|
+
* `keep` is false (a revoked optimistic start: rows repaint from scratch).
|
|
324
|
+
*/
|
|
325
|
+
stop(keep = true): void {
|
|
326
|
+
if (!this.#live) return;
|
|
327
|
+
this.#live = false;
|
|
328
|
+
if (this.#screen) this.#close(this.#screen, false);
|
|
329
|
+
this.#screen = null;
|
|
330
|
+
this.#close(this.#inline, keep);
|
|
331
|
+
this.#inline.doc?.close();
|
|
332
|
+
setNativeRendering(false);
|
|
333
|
+
this.#unbindTheme?.();
|
|
334
|
+
this.#unbindTheme = undefined;
|
|
335
|
+
this.#useNerdSymbols(false);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* After a stop/start cycle (external editor, suspend): adopt the closed
|
|
340
|
+
* inline surface so the transcript continues in place instead of being
|
|
341
|
+
* printed again. A `gone` reply for it opens a fresh surface.
|
|
342
|
+
*/
|
|
343
|
+
resume(hello: TspHello): void {
|
|
344
|
+
if (this.#live) return;
|
|
345
|
+
this.#hello = hello;
|
|
346
|
+
this.#applyHello(hello);
|
|
347
|
+
this.#live = true;
|
|
348
|
+
this.#useNerdSymbols(true);
|
|
349
|
+
this.#watchTheme();
|
|
350
|
+
const surface = this.#inline;
|
|
351
|
+
surface.reconciler.detachLive();
|
|
352
|
+
surface.unacked = [];
|
|
353
|
+
surface.acked = surface.seq;
|
|
354
|
+
surface.focus = null;
|
|
355
|
+
surface.dirty = false;
|
|
356
|
+
this.#write("o", { id: surface.id, mode: "inline", title: "omp", role: "omp.session", adopt: true });
|
|
357
|
+
this.#sendPalette(surface);
|
|
358
|
+
// After the `o`, as in `start()`.
|
|
359
|
+
setNativeRendering(true);
|
|
360
|
+
this.render();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* The terminal's real `hello` reply after an optimistic start: adopt its
|
|
365
|
+
* APC limit, credits, cell size, kinds, appearance and motion preference.
|
|
366
|
+
* A width the terminal already reported in a `resize` event wins over the
|
|
367
|
+
* reply's. A different vocabulary or motion preference re-describes every
|
|
368
|
+
* component, so kinds the terminal lacks fall back.
|
|
369
|
+
*/
|
|
370
|
+
confirm(hello: TspHello): void {
|
|
371
|
+
const before = this.#cx;
|
|
372
|
+
const sawResize = this.#sawResize;
|
|
373
|
+
this.#hello = hello;
|
|
374
|
+
this.#applyHello(hello);
|
|
375
|
+
if (sawResize) {
|
|
376
|
+
this.#cx.cols = before.cols;
|
|
377
|
+
this.#sawResize = true;
|
|
378
|
+
}
|
|
379
|
+
if (this.#cx.dark !== before.dark) this.#host.appearanceChanged(this.#cx.dark);
|
|
380
|
+
if (this.#cx.reduceMotion !== before.reduceMotion || !this.#cx.sameVocabulary(before)) this.#host.invalidate();
|
|
381
|
+
this.#host.requestRender();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/** SIGWINCH: refresh the width used for `rows` fallback until the terminal reports one. */
|
|
385
|
+
noteTerminalColumns(columns: number): void {
|
|
386
|
+
if (this.#sawResize || columns === this.#cx.cols) return;
|
|
387
|
+
this.#cx.cols = columns;
|
|
388
|
+
this.#host.requestRender();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/** Reconcile and send a frame, unless the target surface is out of credits. */
|
|
392
|
+
render(): void {
|
|
393
|
+
if (!this.#live) return;
|
|
394
|
+
this.#refreshPalette();
|
|
395
|
+
const overlays = this.#host.overlays();
|
|
396
|
+
let fullscreen = -1;
|
|
397
|
+
for (let i = overlays.length - 1; i >= 0; i--) {
|
|
398
|
+
// A picker/prefs sheet floats in `layer` over the transcript even
|
|
399
|
+
// when the TUI shows it fullscreen.
|
|
400
|
+
const o = overlays[i]!;
|
|
401
|
+
if (o.options?.fullscreen && !o.component.nativeSheet?.(this.#cx) && !o.component.nativeOverlay) {
|
|
402
|
+
fullscreen = i;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
let surface: Surface;
|
|
407
|
+
let main: readonly NativeChild[];
|
|
408
|
+
let dock: readonly NativeChild[];
|
|
409
|
+
let layer: NativeChild[];
|
|
410
|
+
if (fullscreen >= 0) {
|
|
411
|
+
if (!this.#screen) {
|
|
412
|
+
this.#screen = this.#newSurface("screen");
|
|
413
|
+
this.#open(this.#screen);
|
|
414
|
+
}
|
|
415
|
+
surface = this.#screen;
|
|
416
|
+
main = [overlays[fullscreen]!.component];
|
|
417
|
+
dock = [];
|
|
418
|
+
layer = overlays.slice(fullscreen + 1).map(overlay => this.#overlayNode(overlay));
|
|
419
|
+
} else {
|
|
420
|
+
if (this.#screen) {
|
|
421
|
+
this.#close(this.#screen, false);
|
|
422
|
+
this.#screen = null;
|
|
423
|
+
}
|
|
424
|
+
surface = this.#inline;
|
|
425
|
+
const described = this.#host.describeSurface(this.#cx);
|
|
426
|
+
main = described.main;
|
|
427
|
+
dock = described.dock;
|
|
428
|
+
layer = overlays.map(overlay => this.#overlayNode(overlay));
|
|
429
|
+
}
|
|
430
|
+
this.#pruneOverlayNodes(overlays);
|
|
431
|
+
if (!this.#hasCredit(surface)) {
|
|
432
|
+
surface.dirty = true;
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
surface.dirty = false;
|
|
436
|
+
const ops = surface.reconciler.reconcile({ main, dock, layer }, this.#cx);
|
|
437
|
+
const focused = this.#host.focused();
|
|
438
|
+
const focus = focused ? surface.reconciler.focusTarget(focused) : null;
|
|
439
|
+
if (focus !== surface.focus) {
|
|
440
|
+
ops.push(["focus", focus]);
|
|
441
|
+
surface.focus = focus;
|
|
442
|
+
}
|
|
443
|
+
if (this.#stats) {
|
|
444
|
+
logger.debug("TSP frame", { sf: surface.id, ops: ops.length, rows: surface.reconciler.fallbackCount });
|
|
445
|
+
}
|
|
446
|
+
if (ops.length === 0) return;
|
|
447
|
+
this.#uploadBlobs(surface, ops);
|
|
448
|
+
this.#sendFrame(surface, ops);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Handle one complete `ESC _ tsp;…` input string (events and late replies).
|
|
453
|
+
* Returns false when `sequence` isn't TSP.
|
|
454
|
+
*/
|
|
455
|
+
handleInput(sequence: string): boolean {
|
|
456
|
+
const raw = splitTspMessage(sequence);
|
|
457
|
+
if (!raw) return false;
|
|
458
|
+
this.#record("in", raw.verb, raw.params, raw.body);
|
|
459
|
+
const message = this.#reader.feed(sequence);
|
|
460
|
+
if (message?.verb === "e") this.#handleEvent(message.event);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Reference document of the live surface (mirror mode only). */
|
|
465
|
+
document(): TspNode | undefined {
|
|
466
|
+
return (this.#screen ?? this.#inline).doc?.snapshot();
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Last sent frames, oldest first (mirror mode only). */
|
|
470
|
+
recentFrames(count = RECENT_FRAMES): readonly TspFrame[] {
|
|
471
|
+
return this.#recent.slice(-count);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
#applyHello(hello: TspHello): void {
|
|
475
|
+
this.#limit = hello.apc !== undefined && hello.apc > 0 ? hello.apc : TSP_DEFAULT_APC_LIMIT;
|
|
476
|
+
this.#credits = hello.credits !== undefined && hello.credits > 0 ? hello.credits : TSP_DEFAULT_CREDITS;
|
|
477
|
+
this.#cx = new NativeContext(hello, hello.cols ?? this.#cx.cols);
|
|
478
|
+
this.#sawResize = false;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
#newSurface(mode: "inline" | "screen"): Surface {
|
|
482
|
+
return new Surface(`s:${this.#nextSurface++}`, mode, this.#mirror);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
#open(surface: Surface): void {
|
|
486
|
+
this.#write("o", { id: surface.id, mode: surface.mode, title: "omp", role: "omp.session" });
|
|
487
|
+
this.#sendPalette(surface);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
#close(surface: Surface, keep: boolean): void {
|
|
491
|
+
this.#write("x", { id: surface.id, keep });
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
#hasCredit(surface: Surface): boolean {
|
|
495
|
+
if (surface.unacked.length < this.#credits) return true;
|
|
496
|
+
const oldest = surface.unacked[0]!;
|
|
497
|
+
if (this.#now() - oldest < STALLED_ACK_MS) return false;
|
|
498
|
+
logger.warn("TSP: terminal stopped acknowledging frames; resuming without credits", {
|
|
499
|
+
sf: surface.id,
|
|
500
|
+
s: surface.seq,
|
|
501
|
+
acked: surface.acked,
|
|
502
|
+
});
|
|
503
|
+
surface.unacked = [];
|
|
504
|
+
surface.acked = surface.seq;
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
#sendFrame(surface: Surface, ops: readonly TspOp[]): void {
|
|
509
|
+
surface.seq++;
|
|
510
|
+
surface.unacked.push(this.#now());
|
|
511
|
+
const frame: TspFrame = { sf: surface.id, s: surface.seq, ops };
|
|
512
|
+
if (surface.doc) {
|
|
513
|
+
const errors = surface.doc.applyFrame(frame);
|
|
514
|
+
if (errors.length > 0) logger.warn("TSP: reference document rejected ops", { sf: surface.id, errors });
|
|
515
|
+
this.#recent.push(frame);
|
|
516
|
+
if (this.#recent.length > RECENT_FRAMES) this.#recent.splice(0, this.#recent.length - RECENT_FRAMES);
|
|
517
|
+
}
|
|
518
|
+
this.#write("f", frame);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#uploadBlobs(surface: Surface, ops: readonly TspOp[]): void {
|
|
522
|
+
const ids = new Set<string>();
|
|
523
|
+
for (const op of ops) collectBlobs(op, ids);
|
|
524
|
+
for (const id of ids) {
|
|
525
|
+
if (surface.uploaded.has(id)) continue;
|
|
526
|
+
const blob = getNativeBlob(id);
|
|
527
|
+
if (!blob) {
|
|
528
|
+
logger.warn("TSP: image references an unregistered blob", { id });
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
surface.uploaded.add(id);
|
|
532
|
+
const body = Buffer.from(blob.bytes.buffer, blob.bytes.byteOffset, blob.bytes.byteLength).toString("base64");
|
|
533
|
+
// The full body, so a replay (Tern's `surface-play`) shows the image.
|
|
534
|
+
this.#record("out", "b", { id, mime: blob.mime }, body);
|
|
535
|
+
this.#host.terminal.write(encodeTspMessage("b", body, { id, mime: blob.mime }, this.#limit));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
#write(verb: "o" | "f" | "t" | "x", body: unknown): void {
|
|
540
|
+
this.#record("out", verb, undefined, body);
|
|
541
|
+
this.#host.terminal.write(encodeTspJson(verb, body, undefined, this.#limit));
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
#record(dir: "in" | "out", verb: string, params: Readonly<Record<string, string>> | undefined, body: unknown): void {
|
|
545
|
+
const path = this.#recordPath;
|
|
546
|
+
if (!path) return;
|
|
547
|
+
let payload = body;
|
|
548
|
+
if (dir === "in" && typeof body === "string") {
|
|
549
|
+
try {
|
|
550
|
+
payload = JSON.parse(body);
|
|
551
|
+
} catch {
|
|
552
|
+
payload = body;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
fs.appendFileSync(path, `${JSON.stringify({ t: this.#now(), dir, verb, params, body: payload })}\n`);
|
|
557
|
+
} catch (error) {
|
|
558
|
+
logger.warn("TSP: recording failed; disabling", { path, error: String(error) });
|
|
559
|
+
this.#recordPath = undefined;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#surfaceFor(sf: string | undefined): Surface | null {
|
|
564
|
+
if (sf === undefined || sf === this.#inline.id) return this.#inline;
|
|
565
|
+
return this.#screen?.id === sf ? this.#screen : null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#handleEvent(event: TspEvent): void {
|
|
569
|
+
switch (event.ev) {
|
|
570
|
+
case "ack": {
|
|
571
|
+
const surface = this.#surfaceFor(event.sf);
|
|
572
|
+
if (!surface || event.s <= surface.acked) return;
|
|
573
|
+
const newly = Math.min(event.s, surface.seq) - surface.acked;
|
|
574
|
+
surface.acked = Math.min(event.s, surface.seq);
|
|
575
|
+
surface.unacked.splice(0, newly);
|
|
576
|
+
if (surface.dirty) this.#host.requestRender();
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
case "resize": {
|
|
580
|
+
this.#sawResize = true;
|
|
581
|
+
if (event.cols === this.#cx.cols) return;
|
|
582
|
+
this.#cx.cols = event.cols;
|
|
583
|
+
this.#host.requestRender();
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
case "theme":
|
|
587
|
+
this.#cx.dark = event.dark;
|
|
588
|
+
this.#host.appearanceChanged(event.dark);
|
|
589
|
+
return;
|
|
590
|
+
case "motion":
|
|
591
|
+
this.#cx.reduceMotion = event.reduce;
|
|
592
|
+
this.#host.motionChanged(event.reduce);
|
|
593
|
+
return;
|
|
594
|
+
case "visible":
|
|
595
|
+
return;
|
|
596
|
+
case "error":
|
|
597
|
+
logger.warn("TSP: terminal reported an error", event);
|
|
598
|
+
return;
|
|
599
|
+
case "gone":
|
|
600
|
+
if (event.ids.includes(this.#inline.id)) {
|
|
601
|
+
// Adopt found nothing (evicted, or another pane): start over.
|
|
602
|
+
this.#inline = this.#newSurface("inline");
|
|
603
|
+
if (this.#live) this.#open(this.#inline);
|
|
604
|
+
this.#host.requestRender();
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
this.#surfaceFor(event.sf)?.reconciler.forget(event.ids);
|
|
608
|
+
return;
|
|
609
|
+
case "toggle":
|
|
610
|
+
case "select":
|
|
611
|
+
case "activate":
|
|
612
|
+
case "action":
|
|
613
|
+
case "change":
|
|
614
|
+
this.#routeUiEvent(event);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
#routeUiEvent(event: Extract<TspEvent, { ev: "toggle" | "select" | "activate" | "action" | "change" }>): void {
|
|
620
|
+
const reconciler = this.#surfaceFor(event.sf)?.reconciler;
|
|
621
|
+
const target = reconciler?.target(event.id);
|
|
622
|
+
if (!reconciler || !target?.component.handleNativeEvent) return;
|
|
623
|
+
// A list's items are nodes (`<list>/<key>`, or a component's root id) and
|
|
624
|
+
// map back to their described key. Data-first kinds (picker, prefs) send
|
|
625
|
+
// the program's own item ids (model ids, paths), which pass through.
|
|
626
|
+
const item = (id: string): string =>
|
|
627
|
+
id.startsWith(`${event.id}/`) || id.startsWith(`${event.id}.`) || !id.includes(".")
|
|
628
|
+
? reconciler.itemKey(id)
|
|
629
|
+
: id;
|
|
630
|
+
let ui: NativeUiEvent;
|
|
631
|
+
switch (event.ev) {
|
|
632
|
+
case "toggle":
|
|
633
|
+
ui = { type: "toggle", key: target.keypath, collapsed: event.collapsed };
|
|
634
|
+
break;
|
|
635
|
+
case "select":
|
|
636
|
+
ui = { type: "select", key: target.keypath, item: item(event.item) };
|
|
637
|
+
break;
|
|
638
|
+
case "activate":
|
|
639
|
+
ui = { type: "activate", key: target.keypath, item: item(event.item) };
|
|
640
|
+
break;
|
|
641
|
+
case "action":
|
|
642
|
+
ui = { type: "action", key: target.keypath, act: event.act, value: event.value, mods: event.mods ?? [] };
|
|
643
|
+
break;
|
|
644
|
+
case "change":
|
|
645
|
+
ui = { type: "change", key: target.keypath, item: event.item, value: event.value };
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
target.component.handleNativeEvent(ui);
|
|
649
|
+
this.#host.requestRender();
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* The `overlay` node wrapping a TUI overlay, memoized while its placement
|
|
654
|
+
* is unchanged; a component describing its own sheet goes in unwrapped.
|
|
655
|
+
*/
|
|
656
|
+
#overlayNode(overlay: NativeOverlay): NativeChild {
|
|
657
|
+
if (overlay.component.nativeSheet?.(this.#cx)) return overlay.component;
|
|
658
|
+
const own = overlay.component.nativeOverlay;
|
|
659
|
+
const anchor = own?.anchor ?? overlayAnchor(overlay.options);
|
|
660
|
+
const size = own?.size ?? overlaySize(overlay.options, this.#cx.cols);
|
|
661
|
+
const modal = overlay.focused || overlay.options?.fullscreen === true;
|
|
662
|
+
const role = own?.role;
|
|
663
|
+
const head = own?.head;
|
|
664
|
+
const key = `${anchor}|${size}|${modal}|${role}|${JSON.stringify(head)}`;
|
|
665
|
+
const cached = this.#overlayNodes.get(overlay.component);
|
|
666
|
+
if (cached?.key === key) return cached.node;
|
|
667
|
+
const wrapper = node(
|
|
668
|
+
"overlay",
|
|
669
|
+
{ anchor, size, modal, ...(role ? { role } : {}), ...(head ? { head } : {}) },
|
|
670
|
+
[overlay.component],
|
|
671
|
+
`ov-${nativeComponentId(overlay.component)}`,
|
|
672
|
+
);
|
|
673
|
+
this.#overlayNodes.set(overlay.component, { key, node: wrapper });
|
|
674
|
+
return wrapper;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
#pruneOverlayNodes(overlays: readonly NativeOverlay[]): void {
|
|
678
|
+
if (this.#overlayNodes.size <= overlays.length) return;
|
|
679
|
+
const live = new Set(overlays.map(overlay => overlay.component));
|
|
680
|
+
for (const component of this.#overlayNodes.keys()) if (!live.has(component)) this.#overlayNodes.delete(component);
|
|
681
|
+
}
|
|
682
|
+
}
|