@linxiraos/pi-tui 1.1.26 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +5 -4
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +9 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +37 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/bracketed-paste.ts +10 -3
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +11 -5
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +5 -4
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +16 -1
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +11 -4
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +36 -0
- package/src/tui.ts +666 -217
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { TerminalNotification } from "../terminal-capabilities";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Classification tier. `session` markers outrank the TERM fallback; `outerApp`
|
|
5
|
+
* hosts rank below it so a multiplexer nested inside them still wins.
|
|
6
|
+
*/
|
|
7
|
+
export type TerminalMultiplexerPrecedence = "session" | "outerApp";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Notification delivery tier, tried in this order so the innermost target wins:
|
|
11
|
+
* a `pane` notifier addresses the exact pane through the multiplexer's CLI, a
|
|
12
|
+
* `surface` notifier addresses the containing app surface, and an `inBand`
|
|
13
|
+
* notifier rewrites the terminal's own notification sequence.
|
|
14
|
+
*/
|
|
15
|
+
export type TerminalMultiplexerNotificationTier = "pane" | "surface" | "inBand";
|
|
16
|
+
|
|
17
|
+
/** One notification offered to a multiplexer notifier. */
|
|
18
|
+
export interface TerminalMultiplexerNotificationRequest {
|
|
19
|
+
readonly message: string | TerminalNotification;
|
|
20
|
+
readonly env: NodeJS.ProcessEnv;
|
|
21
|
+
/** The host terminal's formatted OSC notification, or `null` when it notifies with a bare BEL. */
|
|
22
|
+
readonly sequence: string | null;
|
|
23
|
+
/** Write a sequence through the active terminal. */
|
|
24
|
+
write(data: string): void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface TerminalMultiplexerNotifier {
|
|
28
|
+
readonly tier: TerminalMultiplexerNotificationTier;
|
|
29
|
+
/** Deliver the notification. `false` leaves it to the next notifier and then the terminal fallback. */
|
|
30
|
+
send(request: TerminalMultiplexerNotificationRequest): boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* OSC 8 handling inside a multiplexer session:
|
|
35
|
+
* - `render`: the multiplexer draws and opens links itself, so links work
|
|
36
|
+
* whatever the outer terminal supports, unless another session with a
|
|
37
|
+
* hyperlink policy sits in the path.
|
|
38
|
+
* - `drop`: never forwards OSC 8; its session anywhere in the path disables links.
|
|
39
|
+
* - `forward`: forwards OSC 8 once it self-reports (`TERM_PROGRAM`/`TERM_PROGRAM_VERSION`)
|
|
40
|
+
* at least `major.minor`; older or unreported versions stay off.
|
|
41
|
+
*/
|
|
42
|
+
export type TerminalMultiplexerHyperlinks =
|
|
43
|
+
| "render"
|
|
44
|
+
| "drop"
|
|
45
|
+
| {
|
|
46
|
+
readonly forward: {
|
|
47
|
+
/** Lowercase `TERM_PROGRAM` the multiplexer sets inside its panes. */
|
|
48
|
+
readonly termProgram: string;
|
|
49
|
+
readonly major: number;
|
|
50
|
+
readonly minor: number;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/** A multiplexer that answers OSC 11 from a cached copy of the outer terminal's background color. */
|
|
55
|
+
export interface TerminalMultiplexerBackgroundColorCache {
|
|
56
|
+
/** Wrap a query so it reaches the outer terminal and refreshes the cache. */
|
|
57
|
+
passthrough(query: string): string;
|
|
58
|
+
/** How long the multiplexer needs to store the outer reply before the cache is re-read. */
|
|
59
|
+
readonly settleMs: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Everything omp needs to recognize one terminal multiplexer and adapt to it. */
|
|
63
|
+
export interface TerminalMultiplexerModule<Id extends string = string> {
|
|
64
|
+
readonly id: Id;
|
|
65
|
+
readonly precedence: TerminalMultiplexerPrecedence;
|
|
66
|
+
/** Every environment variable `isInside()` reads. */
|
|
67
|
+
readonly sessionEnvKeys: readonly string[];
|
|
68
|
+
/** Lowercase TERM prefix that identifies the multiplexer when its session markers were stripped. */
|
|
69
|
+
readonly termPrefix?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Whether the multiplexer owns the screen grid. When true, rendering takes the
|
|
72
|
+
* multiplexer path (no scrollback rebuild, anchored resize, skipped probes).
|
|
73
|
+
*/
|
|
74
|
+
readonly ownsScreenGrid: boolean;
|
|
75
|
+
isInside(env?: NodeJS.ProcessEnv): boolean;
|
|
76
|
+
/** Routes notifications the multiplexer would otherwise swallow. */
|
|
77
|
+
readonly notifier?: TerminalMultiplexerNotifier;
|
|
78
|
+
/** OSC 8 policy inside a session; unset defers to the outer terminal. */
|
|
79
|
+
readonly hyperlinks?: TerminalMultiplexerHyperlinks;
|
|
80
|
+
/**
|
|
81
|
+
* The pane's VTE honors DEC 2026 even when DECRQM is unanswered or reports
|
|
82
|
+
* the mode unrecognized (status 0): synchronized output starts on inside a
|
|
83
|
+
* session, and an unrecognized report does not turn it off.
|
|
84
|
+
*/
|
|
85
|
+
readonly honorsSynchronizedOutput?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* The session does not reveal whether its client renders graphics while
|
|
88
|
+
* outer-terminal identity variables leak in, so images and Kitty
|
|
89
|
+
* placeholders stay off unless the user forces them.
|
|
90
|
+
*/
|
|
91
|
+
readonly imagesRequireOverride?: boolean;
|
|
92
|
+
/** Platforms where the multiplexer breaks OSC 11 passthrough, so host appearance is used instead. */
|
|
93
|
+
readonly backgroundQueryBrokenOn?: readonly NodeJS.Platform[];
|
|
94
|
+
/** Present when OSC 11 replies come from a cache an explicit refresh must update first. */
|
|
95
|
+
readonly backgroundColorCache?: TerminalMultiplexerBackgroundColorCache;
|
|
96
|
+
/** Expands REP into identical styled cells, so destructive replays REP-encode their padding. */
|
|
97
|
+
readonly expandsRepPadding?: boolean;
|
|
98
|
+
/** Expires a synchronized update after a deadline, so large rebuild replays renew DEC 2026 as they stream. */
|
|
99
|
+
readonly expiresSynchronizedOutput?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Restoring the alternate buffer schedules a redraw that ends a synchronized
|
|
102
|
+
* update early, so a synchronized resize rebuild stays on the main buffer.
|
|
103
|
+
*/
|
|
104
|
+
readonly altRestoreEndsSynchronizedOutput?: boolean;
|
|
105
|
+
/**
|
|
106
|
+
* May grow its grid before SIGWINCH reaches the app, so a rebuild-mode grow
|
|
107
|
+
* whose full viewport no longer reaches the bottom replays history.
|
|
108
|
+
*/
|
|
109
|
+
readonly growsBeforeSigwinch?: boolean;
|
|
110
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wmux is a Windows multiplexer (Electron + xterm.js) modeled on cmux/herdr that
|
|
3
|
+
* repaints its pane in place. WMUX_CLI / WMUX_PIPE are CLI path overrides and can
|
|
4
|
+
* be set outside a wmux terminal.
|
|
5
|
+
*/
|
|
6
|
+
export declare const wmuxMultiplexer: {
|
|
7
|
+
readonly id: "wmux";
|
|
8
|
+
readonly precedence: "session";
|
|
9
|
+
readonly sessionEnvKeys: readonly ["WMUX", "WMUX_SURFACE_ID"];
|
|
10
|
+
readonly ownsScreenGrid: true;
|
|
11
|
+
readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TerminalMultiplexerModule } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* wmux is a Windows multiplexer (Electron + xterm.js) modeled on cmux/herdr that
|
|
5
|
+
* repaints its pane in place. WMUX_CLI / WMUX_PIPE are CLI path overrides and can
|
|
6
|
+
* be set outside a wmux terminal.
|
|
7
|
+
*/
|
|
8
|
+
export const wmuxMultiplexer = {
|
|
9
|
+
id: "wmux",
|
|
10
|
+
precedence: "session",
|
|
11
|
+
sessionEnvKeys: ["WMUX", "WMUX_SURFACE_ID"],
|
|
12
|
+
ownsScreenGrid: true,
|
|
13
|
+
isInside(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
14
|
+
return env.WMUX === "1" || Boolean(env.WMUX_SURFACE_ID);
|
|
15
|
+
},
|
|
16
|
+
} as const satisfies TerminalMultiplexerModule;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const zellijMultiplexer: {
|
|
2
|
+
readonly id: "zellij";
|
|
3
|
+
readonly precedence: "session";
|
|
4
|
+
readonly sessionEnvKeys: readonly ["ZELLIJ"];
|
|
5
|
+
readonly ownsScreenGrid: true;
|
|
6
|
+
readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
|
|
7
|
+
readonly notifier: {
|
|
8
|
+
readonly tier: "inBand";
|
|
9
|
+
readonly send: ({ sequence, write }: import("./types").TerminalMultiplexerNotificationRequest) => boolean;
|
|
10
|
+
};
|
|
11
|
+
readonly backgroundQueryBrokenOn: readonly ["darwin"];
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TerminalMultiplexerModule } from "./types";
|
|
2
|
+
|
|
3
|
+
export const zellijMultiplexer = {
|
|
4
|
+
id: "zellij",
|
|
5
|
+
precedence: "session",
|
|
6
|
+
sessionEnvKeys: ["ZELLIJ"],
|
|
7
|
+
ownsScreenGrid: true,
|
|
8
|
+
isInside(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
9
|
+
return Boolean(env.ZELLIJ);
|
|
10
|
+
},
|
|
11
|
+
notifier: {
|
|
12
|
+
tier: "inBand",
|
|
13
|
+
// Zellij drops OSCs and has no DCS passthrough; a bare BEL raises its bell
|
|
14
|
+
// flag. Bell notifications already provide that signal.
|
|
15
|
+
send({ sequence, write }) {
|
|
16
|
+
if (sequence === null) return false;
|
|
17
|
+
write(`${sequence}\x07`);
|
|
18
|
+
return true;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
// Zellij currently breaks OSC 11 passthrough on macOS, so terminal-derived
|
|
22
|
+
// appearance cannot be trusted there.
|
|
23
|
+
backgroundQueryBrokenOn: ["darwin"],
|
|
24
|
+
} as const satisfies TerminalMultiplexerModule;
|
package/src/native/apply.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reference document applier: applies TSP frames to a node tree the way the
|
|
3
|
-
* spec says the terminal does (
|
|
4
|
-
* one; a rejected op is reported and
|
|
5
|
-
* the frame. `settle` is recorded as a
|
|
3
|
+
* spec says the terminal does (Tern SDK, `protocol/documents.md`, frame ops).
|
|
4
|
+
* Ops are validated and applied one by one; a rejected op is reported and
|
|
5
|
+
* skipped without affecting the rest of the frame. `settle` is recorded as a
|
|
6
|
+
* hint and never restricts later ops.
|
|
6
7
|
*
|
|
7
8
|
* Used by tests, the debug server's `doc` op, and the TSP test harness.
|
|
8
9
|
*/
|
package/src/native/apply.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reference document applier: applies TSP frames to a node tree the way the
|
|
3
|
-
* spec says the terminal does (
|
|
4
|
-
* one; a rejected op is reported and
|
|
5
|
-
* the frame. `settle` is recorded as a
|
|
3
|
+
* spec says the terminal does (Tern SDK, `protocol/documents.md`, frame ops).
|
|
4
|
+
* Ops are validated and applied one by one; a rejected op is reported and
|
|
5
|
+
* skipped without affecting the rest of the frame. `settle` is recorded as a
|
|
6
|
+
* hint and never restricts later ops.
|
|
6
7
|
*
|
|
7
8
|
* Used by tests, the debug server's `doc` op, and the TSP test harness.
|
|
8
9
|
*/
|
package/src/native/backend.d.ts
CHANGED
|
@@ -48,8 +48,9 @@ export interface NativeBackendOptions {
|
|
|
48
48
|
/**
|
|
49
49
|
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
50
50
|
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary, the
|
|
51
|
-
* default APC limit and credits, the terminal's width, the appearance
|
|
52
|
-
* already detected, and
|
|
51
|
+
* default APC limit and credits, the terminal's width, the appearance omp
|
|
52
|
+
* already detected, full motion, and `blobs`, so a resumed session's images
|
|
53
|
+
* are asked about rather than resent before the reply comes.
|
|
53
54
|
*/
|
|
54
55
|
export declare function assumedTspHello(terminal: Terminal): TspHello;
|
|
55
56
|
export declare class NativeBackend {
|
|
@@ -83,8 +84,8 @@ export declare class NativeBackend {
|
|
|
83
84
|
* The terminal's real `hello` reply after an optimistic start: adopt its
|
|
84
85
|
* APC limit, credits, cell size, kinds, appearance and motion preference.
|
|
85
86
|
* A width the terminal already reported in a `resize` event wins over the
|
|
86
|
-
* reply's. A different vocabulary
|
|
87
|
-
* component, so kinds the terminal lacks fall back.
|
|
87
|
+
* reply's. A different vocabulary, motion preference or clock re-describes
|
|
88
|
+
* every component, so kinds the terminal lacks fall back.
|
|
88
89
|
*/
|
|
89
90
|
confirm(hello: TspHello): void;
|
|
90
91
|
/** SIGWINCH: refresh the width used for `rows` fallback until the terminal reports one. */
|
package/src/native/backend.ts
CHANGED
|
@@ -14,8 +14,18 @@
|
|
|
14
14
|
* render while blocked only marks the surface dirty; the next `ack` reconciles
|
|
15
15
|
* once against the last sent state, so every intermediate change coalesces
|
|
16
16
|
* into one frame (`set`s merged, `text append`s joined).
|
|
17
|
+
*
|
|
18
|
+
* Blobs. Each image's bytes reach the terminal once per connection. A
|
|
19
|
+
* terminal with the `blobs` feature is asked first which it already holds:
|
|
20
|
+
* under `TERN_BLOB_DIR` (Tern's on-disk blob cache, on this machine) every new
|
|
21
|
+
* blob is written there and then asked about, so its bytes never cross the
|
|
22
|
+
* pty; otherwise the first pass after (re)connecting asks, so a resumed
|
|
23
|
+
* session doesn't resend what the terminal kept. Whatever the reply lacks
|
|
24
|
+
* (or a reply that never comes) is sent inline with `b`. Frames never wait:
|
|
25
|
+
* the terminal draws an image whose blob arrives after the frame.
|
|
17
26
|
*/
|
|
18
27
|
import * as fs from "node:fs";
|
|
28
|
+
import * as path from "node:path";
|
|
19
29
|
import * as logger from "@linxiraos/pi-utils/logger";
|
|
20
30
|
import {
|
|
21
31
|
TSP_DEFAULT_APC_LIMIT,
|
|
@@ -40,7 +50,7 @@ import {
|
|
|
40
50
|
} from "../theme/theme";
|
|
41
51
|
import type { Component, OverlayOptions, RenderScheduler, RenderTimer } from "../tui";
|
|
42
52
|
import { TspDocument } from "./apply";
|
|
43
|
-
import { getNativeBlob } from "./blobs";
|
|
53
|
+
import { getNativeBlob, type NativeBlob } from "./blobs";
|
|
44
54
|
import { node } from "./describe";
|
|
45
55
|
import { encodeTspJson, encodeTspMessage, type TspHello, TspReader, splitTspMessage } from "./encode";
|
|
46
56
|
import type { DescribeContext, NativeChild, NativeNode, NativeSurface, NativeUiEvent } from "./node";
|
|
@@ -115,11 +125,50 @@ const RECENT_FRAMES = 64;
|
|
|
115
125
|
const STALLED_ACK_MS = 5000;
|
|
116
126
|
/** Role of the session's surfaces; a screen page may name its own. */
|
|
117
127
|
const SESSION_ROLE = "zeta.session";
|
|
128
|
+
/** A `blobs` query unanswered this long counts its ids as missing: they go inline. */
|
|
129
|
+
const BLOB_REPLY_MS = 3000;
|
|
130
|
+
/** Expired queries kept to pair late replies with their queries, at most. */
|
|
131
|
+
const MAX_BLOB_QUERIES = 16;
|
|
132
|
+
|
|
133
|
+
/** A `blobs` query awaiting its reply; `timer` is unset once its ids no longer depend on it. */
|
|
134
|
+
interface BlobQuery {
|
|
135
|
+
readonly ids: readonly string[];
|
|
136
|
+
timer: RenderTimer | undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Save `blob` in the terminal's blob cache as `<dir>/<id>` (written aside,
|
|
141
|
+
* then renamed; skipped when present). A failure only means the terminal
|
|
142
|
+
* won't find it, so the blob goes inline.
|
|
143
|
+
*/
|
|
144
|
+
async function cacheBlob(dir: string, blob: NativeBlob): Promise<void> {
|
|
145
|
+
const target = path.join(dir, blob.id);
|
|
146
|
+
const temp = `${target}.tmp${process.pid}`;
|
|
147
|
+
try {
|
|
148
|
+
if (await Bun.file(target).exists()) return;
|
|
149
|
+
// Not `Bun.write`: it would create a missing folder, which only Tern may.
|
|
150
|
+
await fs.promises.writeFile(temp, blob.bytes);
|
|
151
|
+
await fs.promises.rename(temp, target);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
logger.debug("TSP: could not save a blob to the terminal's cache", { dir, id: blob.id, error: String(error) });
|
|
154
|
+
await fs.promises.rm(temp, { force: true }).catch(() => {});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* A `b` message's parameters. Tern names a blob by the sha256 of the bytes it
|
|
160
|
+
* receives and ignores `id`, but Tern 0.5.3 and earlier reject a blob without
|
|
161
|
+
* it, so it stays until those are gone.
|
|
162
|
+
*/
|
|
163
|
+
function blobParams(blob: NativeBlob): Record<string, string> {
|
|
164
|
+
return { id: blob.id, mime: blob.mime };
|
|
165
|
+
}
|
|
118
166
|
|
|
119
167
|
class NativeContext implements DescribeContext {
|
|
120
168
|
cols: number;
|
|
121
169
|
reduceMotion: boolean;
|
|
122
170
|
dark: boolean;
|
|
171
|
+
hour12: boolean | undefined;
|
|
123
172
|
#kinds: ReadonlySet<string>;
|
|
124
173
|
#features: ReadonlySet<string>;
|
|
125
174
|
|
|
@@ -127,6 +176,7 @@ class NativeContext implements DescribeContext {
|
|
|
127
176
|
this.cols = cols;
|
|
128
177
|
this.reduceMotion = hello.reduceMotion === true;
|
|
129
178
|
this.dark = hello.dark !== false;
|
|
179
|
+
this.hour12 = hello.hour12;
|
|
130
180
|
this.#kinds = new Set(hello.kinds);
|
|
131
181
|
this.#features = new Set(hello.features);
|
|
132
182
|
}
|
|
@@ -155,8 +205,9 @@ function sameSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
|
155
205
|
/**
|
|
156
206
|
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
157
207
|
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary, the
|
|
158
|
-
* default APC limit and credits, the terminal's width, the appearance
|
|
159
|
-
* already detected, and
|
|
208
|
+
* default APC limit and credits, the terminal's width, the appearance omp
|
|
209
|
+
* already detected, full motion, and `blobs`, so a resumed session's images
|
|
210
|
+
* are asked about rather than resent before the reply comes.
|
|
160
211
|
*/
|
|
161
212
|
export function assumedTspHello(terminal: Terminal): TspHello {
|
|
162
213
|
const appearance = terminal.appearance;
|
|
@@ -165,6 +216,7 @@ export function assumedTspHello(terminal: Terminal): TspHello {
|
|
|
165
216
|
v: TSP_VERSION,
|
|
166
217
|
term: "tern",
|
|
167
218
|
kinds: TSP_KINDS,
|
|
219
|
+
features: ["blobs"],
|
|
168
220
|
apc: TSP_DEFAULT_APC_LIMIT,
|
|
169
221
|
credits: TSP_DEFAULT_CREDITS,
|
|
170
222
|
cols: terminal.columns,
|
|
@@ -184,7 +236,6 @@ class Surface {
|
|
|
184
236
|
acked = 0;
|
|
185
237
|
/** Send times of unacknowledged frames, oldest first. */
|
|
186
238
|
unacked: number[] = [];
|
|
187
|
-
uploaded = new Set<string>();
|
|
188
239
|
focus: string | null = null;
|
|
189
240
|
dirty = false;
|
|
190
241
|
|
|
@@ -254,6 +305,14 @@ export class NativeBackend {
|
|
|
254
305
|
#paletteKey: string | undefined;
|
|
255
306
|
/** Serialized palette last sent, to skip resends of an unchanged theme. */
|
|
256
307
|
#paletteSent: string | undefined;
|
|
308
|
+
/** Pending delivery owns bytes until sent or confirmed held; settled descriptions may already be released. */
|
|
309
|
+
#blobs = new Map<string, NativeBlob | "sent">();
|
|
310
|
+
/** `blobs` queries sent, oldest first: the terminal answers each once, in order. */
|
|
311
|
+
#blobQueries: BlobQuery[] = [];
|
|
312
|
+
/** The next pass with new blobs asks the terminal which it holds (a fresh connection). */
|
|
313
|
+
#askHeld = true;
|
|
314
|
+
/** Bumped when the connection resets or stops, so blob cache writes finishing later are dropped. */
|
|
315
|
+
#blobEpoch = 0;
|
|
257
316
|
|
|
258
317
|
constructor(host: NativeHost, hello: TspHello, options: NativeBackendOptions = {}) {
|
|
259
318
|
this.#host = host;
|
|
@@ -367,6 +426,9 @@ export class NativeBackend {
|
|
|
367
426
|
this.#unbindTheme = undefined;
|
|
368
427
|
this.#useNerdSymbols(false);
|
|
369
428
|
this.#clearStallTimer();
|
|
429
|
+
// Replies no longer reach the backend: forget the queries outright.
|
|
430
|
+
this.#resetBlobs();
|
|
431
|
+
this.#blobQueries = [];
|
|
370
432
|
}
|
|
371
433
|
|
|
372
434
|
/**
|
|
@@ -381,13 +443,14 @@ export class NativeBackend {
|
|
|
381
443
|
this.#live = true;
|
|
382
444
|
this.#useNerdSymbols(true);
|
|
383
445
|
this.#watchTheme();
|
|
446
|
+
this.#resetBlobs();
|
|
384
447
|
const surface = this.#inline;
|
|
385
448
|
surface.reconciler.detachLive();
|
|
386
449
|
surface.unacked = [];
|
|
387
450
|
surface.acked = surface.seq;
|
|
388
451
|
surface.focus = null;
|
|
389
452
|
surface.dirty = false;
|
|
390
|
-
this.#write("o", { id: surface.id, mode: "inline", title: "
|
|
453
|
+
this.#write("o", { id: surface.id, mode: "inline", title: "omp", role: SESSION_ROLE, adopt: true });
|
|
391
454
|
this.#sendPalette(surface);
|
|
392
455
|
// After the `o`, as in `start()`.
|
|
393
456
|
setNativeRendering(true);
|
|
@@ -398,8 +461,8 @@ export class NativeBackend {
|
|
|
398
461
|
* The terminal's real `hello` reply after an optimistic start: adopt its
|
|
399
462
|
* APC limit, credits, cell size, kinds, appearance and motion preference.
|
|
400
463
|
* A width the terminal already reported in a `resize` event wins over the
|
|
401
|
-
* reply's. A different vocabulary
|
|
402
|
-
* component, so kinds the terminal lacks fall back.
|
|
464
|
+
* reply's. A different vocabulary, motion preference or clock re-describes
|
|
465
|
+
* every component, so kinds the terminal lacks fall back.
|
|
403
466
|
*/
|
|
404
467
|
confirm(hello: TspHello): void {
|
|
405
468
|
const before = this.#cx;
|
|
@@ -411,7 +474,12 @@ export class NativeBackend {
|
|
|
411
474
|
this.#sawResize = true;
|
|
412
475
|
}
|
|
413
476
|
if (this.#cx.dark !== before.dark) this.#host.appearanceChanged(this.#cx.dark);
|
|
414
|
-
if (
|
|
477
|
+
if (
|
|
478
|
+
this.#cx.reduceMotion !== before.reduceMotion ||
|
|
479
|
+
this.#cx.hour12 !== before.hour12 ||
|
|
480
|
+
!this.#cx.sameVocabulary(before)
|
|
481
|
+
)
|
|
482
|
+
this.#host.invalidate();
|
|
415
483
|
this.#host.requestRender();
|
|
416
484
|
}
|
|
417
485
|
|
|
@@ -484,7 +552,7 @@ export class NativeBackend {
|
|
|
484
552
|
logger.debug("TSP frame", { sf: surface.id, ops: ops.length, rows: surface.reconciler.fallbackCount });
|
|
485
553
|
}
|
|
486
554
|
if (ops.length === 0) return;
|
|
487
|
-
this.#uploadBlobs(
|
|
555
|
+
this.#uploadBlobs(ops);
|
|
488
556
|
this.#sendFrame(surface, ops);
|
|
489
557
|
}
|
|
490
558
|
|
|
@@ -498,6 +566,7 @@ export class NativeBackend {
|
|
|
498
566
|
this.#record("in", raw.verb, raw.params, raw.body);
|
|
499
567
|
const message = this.#reader.feed(sequence);
|
|
500
568
|
if (message?.verb === "e") this.#handleEvent(message.event);
|
|
569
|
+
else if (message?.reply.r === "blobs") this.#onBlobsReply(message.reply.have);
|
|
501
570
|
return true;
|
|
502
571
|
}
|
|
503
572
|
|
|
@@ -523,7 +592,7 @@ export class NativeBackend {
|
|
|
523
592
|
}
|
|
524
593
|
|
|
525
594
|
#open(surface: Surface): void {
|
|
526
|
-
this.#write("o", { id: surface.id, mode: surface.mode, title: "
|
|
595
|
+
this.#write("o", { id: surface.id, mode: surface.mode, title: "omp", role: surface.role });
|
|
527
596
|
this.#sendPalette(surface);
|
|
528
597
|
}
|
|
529
598
|
|
|
@@ -576,25 +645,118 @@ export class NativeBackend {
|
|
|
576
645
|
this.#write("f", frame);
|
|
577
646
|
}
|
|
578
647
|
|
|
579
|
-
|
|
648
|
+
/** Deliver the blobs `ops` reference that this connection hasn't handled yet (see the module doc). */
|
|
649
|
+
#uploadBlobs(ops: readonly TspOp[]): void {
|
|
580
650
|
const ids = new Set<string>();
|
|
581
651
|
for (const op of ops) collectBlobs(op, ids);
|
|
652
|
+
const fresh: NativeBlob[] = [];
|
|
582
653
|
for (const id of ids) {
|
|
583
|
-
if (
|
|
654
|
+
if (this.#blobs.has(id)) continue;
|
|
584
655
|
const blob = getNativeBlob(id);
|
|
585
|
-
if (
|
|
586
|
-
|
|
587
|
-
|
|
656
|
+
if (blob) fresh.push(blob);
|
|
657
|
+
else logger.warn("TSP: image references an unregistered blob", { id });
|
|
658
|
+
}
|
|
659
|
+
if (fresh.length === 0) return;
|
|
660
|
+
if (!this.#cx.feature("blobs")) {
|
|
661
|
+
for (const blob of fresh) this.#sendBlob(blob);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const dir = Bun.env.TERN_BLOB_DIR;
|
|
665
|
+
if (dir) {
|
|
666
|
+
for (const blob of fresh) this.#blobs.set(blob.id, blob);
|
|
667
|
+
void this.#cacheBlobs(dir, fresh);
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
if (this.#askHeld) {
|
|
671
|
+
this.#askHeld = false;
|
|
672
|
+
this.#askBlobs(fresh);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
for (const blob of fresh) this.#sendBlob(blob);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Write `blobs` to the terminal's cache, then ask about the ones still pending. */
|
|
679
|
+
async #cacheBlobs(dir: string, blobs: readonly NativeBlob[]): Promise<void> {
|
|
680
|
+
const epoch = this.#blobEpoch;
|
|
681
|
+
await Promise.all(blobs.map(blob => cacheBlob(dir, blob)));
|
|
682
|
+
if (epoch !== this.#blobEpoch) return;
|
|
683
|
+
const pending = blobs.filter(blob => this.#blobs.get(blob.id) === blob);
|
|
684
|
+
if (pending.length > 0) this.#askBlobs(pending);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/** Ask the terminal which of `blobs` it holds; the reply (or its absence) settles them. */
|
|
688
|
+
#askBlobs(blobs: readonly NativeBlob[]): void {
|
|
689
|
+
for (const blob of blobs) this.#blobs.set(blob.id, blob);
|
|
690
|
+
const ids = blobs.map(blob => blob.id);
|
|
691
|
+
const query: BlobQuery = { ids, timer: undefined };
|
|
692
|
+
query.timer = this.#scheduler.scheduleRender(() => {
|
|
693
|
+
query.timer = undefined;
|
|
694
|
+
logger.warn("TSP: no reply to a blobs query; sending its blobs inline", { ids: ids.length });
|
|
695
|
+
this.#sendMissing(ids);
|
|
696
|
+
}, BLOB_REPLY_MS);
|
|
697
|
+
// Expired queries stay only to pair late replies; a terminal that never answers mustn't grow the list.
|
|
698
|
+
while (this.#blobQueries.length >= MAX_BLOB_QUERIES && !this.#blobQueries[0]!.timer) this.#blobQueries.shift();
|
|
699
|
+
this.#blobQueries.push(query);
|
|
700
|
+
this.#write("q", { q: "blobs", ids });
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* The reply to the oldest outstanding query. Any `have` id still asked
|
|
705
|
+
* about is held by the terminal (whichever query asked); the query's other
|
|
706
|
+
* ids go inline.
|
|
707
|
+
*/
|
|
708
|
+
#onBlobsReply(have: readonly string[]): void {
|
|
709
|
+
const query = this.#blobQueries.shift();
|
|
710
|
+
for (const id of have) {
|
|
711
|
+
const blob = this.#blobs.get(id);
|
|
712
|
+
if (blob === undefined || blob === "sent") continue;
|
|
713
|
+
// The bytes went another way; a recording still carries them so a replay without them shows the image.
|
|
714
|
+
if (this.#recordPath) {
|
|
715
|
+
const body = Buffer.from(blob.bytes.buffer, blob.bytes.byteOffset, blob.bytes.byteLength).toString(
|
|
716
|
+
"base64",
|
|
717
|
+
);
|
|
718
|
+
this.#record("out", "b", blobParams(blob), body);
|
|
588
719
|
}
|
|
589
|
-
|
|
590
|
-
const body = Buffer.from(blob.bytes.buffer, blob.bytes.byteOffset, blob.bytes.byteLength).toString("base64");
|
|
591
|
-
// The full body, so a replay (Tern's `surface-play`) shows the image.
|
|
592
|
-
this.#record("out", "b", { id, mime: blob.mime }, body);
|
|
593
|
-
this.#host.terminal.write(encodeTspMessage("b", body, { id, mime: blob.mime }, this.#limit));
|
|
720
|
+
this.#blobs.set(id, "sent");
|
|
594
721
|
}
|
|
722
|
+
if (!query?.timer) return;
|
|
723
|
+
query.timer.cancel();
|
|
724
|
+
query.timer = undefined;
|
|
725
|
+
this.#sendMissing(query.ids);
|
|
595
726
|
}
|
|
596
727
|
|
|
597
|
-
|
|
728
|
+
/** Send inline every id of `ids` still waiting on a query. */
|
|
729
|
+
#sendMissing(ids: readonly string[]): void {
|
|
730
|
+
for (const id of ids) {
|
|
731
|
+
const blob = this.#blobs.get(id);
|
|
732
|
+
if (blob !== undefined && blob !== "sent") this.#sendBlob(blob);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
#sendBlob(blob: NativeBlob): void {
|
|
737
|
+
this.#blobs.set(blob.id, "sent");
|
|
738
|
+
const body = Buffer.from(blob.bytes.buffer, blob.bytes.byteOffset, blob.bytes.byteLength).toString("base64");
|
|
739
|
+
// The full body, so a replay (Tern's `surface-play`) shows the image.
|
|
740
|
+
this.#record("out", "b", blobParams(blob), body);
|
|
741
|
+
this.#host.terminal.write(encodeTspMessage("b", body, blobParams(blob), this.#limit));
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* A new connection (resume, or a fresh inline surface after `gone`): the
|
|
746
|
+
* terminal is asked again. Outstanding queries stay, without their
|
|
747
|
+
* deadlines, only to pair their replies.
|
|
748
|
+
*/
|
|
749
|
+
#resetBlobs(): void {
|
|
750
|
+
this.#blobEpoch++;
|
|
751
|
+
this.#blobs.clear();
|
|
752
|
+
this.#askHeld = true;
|
|
753
|
+
for (const query of this.#blobQueries) {
|
|
754
|
+
query.timer?.cancel();
|
|
755
|
+
query.timer = undefined;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
#write(verb: "o" | "f" | "t" | "x" | "q", body: unknown): void {
|
|
598
760
|
this.#record("out", verb, undefined, body);
|
|
599
761
|
this.#host.terminal.write(encodeTspJson(verb, body, undefined, this.#limit));
|
|
600
762
|
}
|
|
@@ -658,6 +820,7 @@ export class NativeBackend {
|
|
|
658
820
|
case "gone":
|
|
659
821
|
if (event.ids.includes(this.#inline.id)) {
|
|
660
822
|
// Adopt found nothing (evicted, or another pane): start over.
|
|
823
|
+
this.#resetBlobs();
|
|
661
824
|
this.#inline = this.#newSurface("inline");
|
|
662
825
|
if (this.#live) this.#open(this.#inline);
|
|
663
826
|
this.#host.requestRender();
|
|
@@ -671,6 +834,8 @@ export class NativeBackend {
|
|
|
671
834
|
case "action":
|
|
672
835
|
case "change":
|
|
673
836
|
case "edit":
|
|
837
|
+
case "undo":
|
|
838
|
+
case "send":
|
|
674
839
|
this.#routeUiEvent(event);
|
|
675
840
|
return;
|
|
676
841
|
case "focus": {
|
|
@@ -687,11 +852,17 @@ export class NativeBackend {
|
|
|
687
852
|
}
|
|
688
853
|
|
|
689
854
|
#routeUiEvent(
|
|
690
|
-
event: Extract<
|
|
855
|
+
event: Extract<
|
|
856
|
+
TspEvent,
|
|
857
|
+
{ ev: "toggle" | "select" | "activate" | "action" | "change" | "edit" | "undo" | "send" }
|
|
858
|
+
>,
|
|
691
859
|
): void {
|
|
692
860
|
const reconciler = this.#surfaceFor(event.sf)?.reconciler;
|
|
693
861
|
const target = reconciler?.target(event.id);
|
|
694
862
|
if (!reconciler || !target?.component.handleNativeEvent) return;
|
|
863
|
+
// Explicit sends must address the live editor, not a stale or invented
|
|
864
|
+
// descendant id that merely shares the component's namespace.
|
|
865
|
+
if (event.ev === "send" && (!this.#live || reconciler.focusTarget(target.component) !== event.id)) return;
|
|
695
866
|
// A list's items are nodes (`<list>/<key>`, or a component's root id) and
|
|
696
867
|
// map back to their described key. Data-first kinds (picker, prefs) send
|
|
697
868
|
// the program's own item ids (model ids, paths), which pass through.
|
|
@@ -721,6 +892,12 @@ export class NativeBackend {
|
|
|
721
892
|
ui = { type: "edit", key: target.keypath, from, to, text, cursor, len };
|
|
722
893
|
break;
|
|
723
894
|
}
|
|
895
|
+
case "undo":
|
|
896
|
+
ui = { type: "undo", key: target.keypath };
|
|
897
|
+
break;
|
|
898
|
+
case "send":
|
|
899
|
+
ui = { type: "send", key: target.keypath, text: event.text };
|
|
900
|
+
break;
|
|
724
901
|
}
|
|
725
902
|
target.component.handleNativeEvent(ui);
|
|
726
903
|
this.#host.requestRender();
|
package/src/native/blobs.d.ts
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
* Content-addressed binary blobs (images) for `image` nodes.
|
|
3
3
|
*
|
|
4
4
|
* `describe()` registers the bytes and puts the returned id in `image.p.blob`;
|
|
5
|
-
* the native backend
|
|
6
|
-
*
|
|
5
|
+
* the native backend delivers each referenced blob once per terminal
|
|
6
|
+
* connection (asking the terminal first which it holds, or through its blob
|
|
7
|
+
* cache), else uploads it with verb `b` before the frame that first
|
|
8
|
+
* references it.
|
|
7
9
|
*/
|
|
8
10
|
import type { TspProps } from "@linxiraos/pi-wire";
|
|
9
11
|
import type { NativeNode } from "./node";
|
|
@@ -12,10 +14,12 @@ export interface NativeBlob {
|
|
|
12
14
|
readonly mime: string;
|
|
13
15
|
readonly bytes: Uint8Array;
|
|
14
16
|
}
|
|
15
|
-
/** Register
|
|
17
|
+
/** Register bytes for process-lifetime, id-only use. Prefer {@link nativeImageNode} for owned images. */
|
|
16
18
|
export declare function registerNativeBlob(bytes: Uint8Array, mime: string): string;
|
|
17
19
|
/** A registered blob by id. */
|
|
18
20
|
export declare function getNativeBlob(id: string): NativeBlob | undefined;
|
|
21
|
+
/** An image whose props own its bytes, including while hidden, unmounted, or awaiting upload. */
|
|
22
|
+
export declare function nativeImageNode(bytes: Uint8Array, mime: string, p?: Omit<TspProps<"image">, "blob" | "builtin">, key?: string): NativeNode;
|
|
19
23
|
/**
|
|
20
24
|
* An `image` node for a base64 payload: the bytes are registered as a blob and
|
|
21
25
|
* the pixel size probed from the header. Decoding and hashing cost real time,
|