@linxiraos/pi-tui 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/theme/theme.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Terminal, TerminalAppearance } from "../terminal";
|
|
|
5
5
|
import { colorLuma } from "@linxiraos/pi-utils/color";
|
|
6
6
|
import { getCustomThemesDir } from "@linxiraos/pi-utils/dirs";
|
|
7
7
|
import * as logger from "@linxiraos/pi-utils/logger";
|
|
8
|
-
import {
|
|
8
|
+
import { isTerminalBackgroundQueryTrusted } from "../terminal-multiplexer";
|
|
9
9
|
import { setActiveSymbolTheme } from "./active-symbols";
|
|
10
10
|
import { ansi256ToHex, resolveThemeColors, resolveVarRefs } from "./color";
|
|
11
11
|
import {
|
|
@@ -50,18 +50,28 @@ var terminalReportedAppearance: "dark" | "light" | undefined;
|
|
|
50
50
|
/** Appearance reported by the macOS fallback observer, or undefined if not yet available. */
|
|
51
51
|
var macOSReportedAppearance: "dark" | "light" | undefined;
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
const backgroundFallbackListeners = new Set<() => void>();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Subscribe to native fallback appearance changes independently of theme changes.
|
|
57
|
+
* Consumers must re-check the effective background: COLORFGBG can override it.
|
|
58
|
+
*/
|
|
59
|
+
export function onTerminalBackgroundFallbackChange(callback: () => void): () => void {
|
|
60
|
+
backgroundFallbackListeners.add(callback);
|
|
61
|
+
return () => {
|
|
62
|
+
backgroundFallbackListeners.delete(callback);
|
|
63
|
+
};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Detect appearance from trusted OSC 11 reports, then COLORFGBG, then native
|
|
68
|
+
* macOS appearance only on known-broken multiplexer background-query paths.
|
|
69
|
+
* Defaults to dark when none of those signals is available.
|
|
70
|
+
*/
|
|
71
|
+
export function detectTerminalBackground(): "dark" | "light" {
|
|
72
|
+
const backgroundQueryTrusted = isTerminalBackgroundQueryTrusted();
|
|
63
73
|
// Tier 1: terminal-reported appearance from OSC 11 luminance.
|
|
64
|
-
if (
|
|
74
|
+
if (backgroundQueryTrusted && terminalReportedAppearance) {
|
|
65
75
|
return terminalReportedAppearance;
|
|
66
76
|
}
|
|
67
77
|
|
|
@@ -76,7 +86,7 @@ function detectTerminalBackground(): "dark" | "light" {
|
|
|
76
86
|
}
|
|
77
87
|
|
|
78
88
|
// Tier 3: host macOS appearance for known-broken terminal paths only.
|
|
79
|
-
if (
|
|
89
|
+
if (!backgroundQueryTrusted) {
|
|
80
90
|
const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
|
|
81
91
|
if (macAppearance) return macAppearance;
|
|
82
92
|
}
|
|
@@ -214,14 +224,14 @@ export async function initTheme(
|
|
|
214
224
|
assignTheme(await loadTheme(name, getCurrentThemeOptions()));
|
|
215
225
|
if (enableWatcher) {
|
|
216
226
|
await startThemeWatcher();
|
|
217
|
-
startSigwinchListener();
|
|
218
227
|
}
|
|
219
228
|
} catch (err) {
|
|
220
229
|
logger.debug("Theme loading failed, falling back to dark theme", { error: String(err) });
|
|
221
230
|
currentThemeName = "dark";
|
|
222
231
|
assignTheme(await loadTheme("dark", getCurrentThemeOptions()));
|
|
223
|
-
//
|
|
232
|
+
// Skip file watching for the fallback theme, not appearance tracking.
|
|
224
233
|
}
|
|
234
|
+
if (enableWatcher) startSigwinchListener();
|
|
225
235
|
}
|
|
226
236
|
|
|
227
237
|
export async function setTheme(
|
|
@@ -239,6 +249,14 @@ export async function setTheme(
|
|
|
239
249
|
assignTheme(loadedTheme);
|
|
240
250
|
if (enableWatcher) {
|
|
241
251
|
await startThemeWatcher();
|
|
252
|
+
// startThemeWatcher() calls stopThemeWatcher(), which tears down
|
|
253
|
+
// the SIGWINCH handler and the macOS appearance observer that
|
|
254
|
+
// feeds the SIXEL background fallback. Restart it like initTheme()
|
|
255
|
+
// does so a runtime theme change does not silence fallback
|
|
256
|
+
// background repaints. Without this, `setTheme(name, true)` after
|
|
257
|
+
// startup would leave the SIXEL repaint path dead until the next
|
|
258
|
+
// process restart.
|
|
259
|
+
startSigwinchListener();
|
|
242
260
|
}
|
|
243
261
|
notifyThemeChange();
|
|
244
262
|
return { success: true };
|
|
@@ -667,12 +685,26 @@ var macObserver: { stop(): void } | undefined;
|
|
|
667
685
|
|
|
668
686
|
function startMacAppearanceObserver(): void {
|
|
669
687
|
stopMacAppearanceObserver();
|
|
670
|
-
if (
|
|
688
|
+
if (isTerminalBackgroundQueryTrusted()) return;
|
|
671
689
|
try {
|
|
672
690
|
macOSReportedAppearance = detectMacOSAppearance() ?? undefined;
|
|
673
691
|
macObserver = MacAppearanceObserver.start((err, appearance) => {
|
|
674
692
|
if (!err && (appearance === "dark" || appearance === "light")) {
|
|
693
|
+
const changed = appearance !== macOSReportedAppearance;
|
|
675
694
|
macOSReportedAppearance = appearance;
|
|
695
|
+
if (changed) {
|
|
696
|
+
// oxlint-disable-next-line unicorn/no-useless-spread -- listeners may unsubscribe while reporting
|
|
697
|
+
for (const listener of [...backgroundFallbackListeners]) {
|
|
698
|
+
try {
|
|
699
|
+
listener();
|
|
700
|
+
} catch (err) {
|
|
701
|
+
// Debug-level: a listener failure must not break sibling
|
|
702
|
+
// listeners or the appearance observer. Visible only in
|
|
703
|
+
// local debug logs, never in normal runs.
|
|
704
|
+
logger.debug("macOS appearance fallback listener failed", { err });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
676
708
|
reevaluateAutoThemeForAppearance("macOS fallback");
|
|
677
709
|
}
|
|
678
710
|
});
|
package/src/thinking.d.ts
CHANGED
|
@@ -37,6 +37,11 @@ export declare function shouldDisableReasoning(level: ThinkingLevel | undefined)
|
|
|
37
37
|
* Resolves a selector against the current model while preserving explicit "off".
|
|
38
38
|
*/
|
|
39
39
|
export declare function resolveThinkingLevelForModel(model: Model | undefined, level: ThinkingLevel | undefined): ResolvedThinkingLevel | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Level applied to `model` when the user has not picked one for it (carried,
|
|
42
|
+
* global default, auto). Unverified thinking stays unset until the user picks.
|
|
43
|
+
*/
|
|
44
|
+
export declare function automaticThinkingLevel<T>(model: Model | undefined, fallback: T): T | undefined;
|
|
40
45
|
/**
|
|
41
46
|
* Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
|
|
42
47
|
* inside the coding-agent layer: it is never an {@link Effort} or
|
package/src/thinking.ts
CHANGED
|
@@ -132,6 +132,14 @@ export function resolveThinkingLevelForModel(
|
|
|
132
132
|
return clampThinkingLevelForModel(model, level);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Level applied to `model` when the user has not picked one for it (carried,
|
|
137
|
+
* global default, auto). Unverified thinking stays unset until the user picks.
|
|
138
|
+
*/
|
|
139
|
+
export function automaticThinkingLevel<T>(model: Model | undefined, fallback: T): T | undefined {
|
|
140
|
+
return model?.thinking?.unverified ? undefined : fallback;
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
/**
|
|
136
144
|
* Sentinel selector for the coding-agent "auto" thinking mode. Kept entirely
|
|
137
145
|
* inside the coding-agent layer: it is never an {@link Effort} or
|
|
@@ -374,7 +382,7 @@ export function modelSupportsEffortCeiling(model: Model, ceiling: Effort): boole
|
|
|
374
382
|
* models.
|
|
375
383
|
*/
|
|
376
384
|
export function resolveProvisionalAutoLevel(model: Model | undefined): Effort | undefined {
|
|
377
|
-
if (!model?.reasoning) return undefined;
|
|
385
|
+
if (!model?.reasoning || model.thinking?.unverified) return undefined;
|
|
378
386
|
const preferred = model.thinking?.defaultLevel ?? Effort.High;
|
|
379
387
|
return clampAutoThinkingEffort(model, preferred === Effort.Max ? Effort.XHigh : preferred, Effort.XHigh);
|
|
380
388
|
}
|
package/src/tmux.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export { wrapTmuxPassthrough } from "./multiplexers/tmux";
|
|
2
2
|
/** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
|
|
3
3
|
export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
|
|
4
|
+
/**
|
|
5
|
+
* Emulator name candidates from a `#{client_termtype}<TAB>#{client_termname}`
|
|
6
|
+
* line, best first: the terminal-type reply (tmux 3.4+, XTVERSION), then the
|
|
7
|
+
* emulator the client's `TERM` names when it ships its own terminfo entry. Both
|
|
8
|
+
* are kept because the reply can name an unrecognized program (a nested tmux
|
|
9
|
+
* answers `tmux 3.4`) while `TERM` still identifies the emulator.
|
|
10
|
+
*/
|
|
11
|
+
export declare function tmuxClientProgramsFromReport(report: string): string[];
|
|
4
12
|
/**
|
|
5
13
|
* Whether the terminal hosting this tmux session draws TSP programs natively:
|
|
6
14
|
* it sets the session user option `@tern-tsp` while it watches the session.
|
|
@@ -12,10 +20,37 @@ export declare function tmuxSessionSpeaksTsp(env?: NodeJS.ProcessEnv): boolean;
|
|
|
12
20
|
*/
|
|
13
21
|
export declare function setTmuxPaneApcTitles(on: boolean, env?: NodeJS.ProcessEnv): void;
|
|
14
22
|
/**
|
|
15
|
-
* Resolve the terminal emulator
|
|
23
|
+
* Resolve the terminal emulator names recorded for this tmux client, best first.
|
|
16
24
|
*
|
|
17
25
|
* tmux overwrites pane identity variables with its own values, but retains the
|
|
18
|
-
* attached client's terminal-type reply in `#{client_termtype}
|
|
19
|
-
*
|
|
26
|
+
* attached client's terminal-type reply in `#{client_termtype}` and its `TERM`
|
|
27
|
+
* in `#{client_termname}` (see {@link tmuxClientProgramsFromReport}). The local
|
|
28
|
+
* IPC query runs once per process and degrades to `[]` when unavailable.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveTmuxClientTerminalNames(env?: NodeJS.ProcessEnv): readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* Whether tmux draws SIXEL to the client attached to this session: its
|
|
33
|
+
* effective terminal description for that client carries `Sxl`.
|
|
34
|
+
*
|
|
35
|
+
* A tmux built with SIXEL answers every pane's DA1 with attribute 4 and draws a
|
|
36
|
+
* `SIXEL IMAGE (WxH)` text placeholder for clients without SIXEL, so the pane's
|
|
37
|
+
* DA1 says nothing about the outer terminal. tmux itself renders SIXEL only
|
|
38
|
+
* when the client's terminal has `Sxl`, whether from terminfo,
|
|
39
|
+
* `terminal-overrides`, or the `sixel` feature (DA1 or `terminal-features`),
|
|
40
|
+
* and `show-messages -T` reports that merged description. The TUI consults
|
|
41
|
+
* this before trusting the pane's DA1 attribute. Queried once per tmux
|
|
42
|
+
* session; `false` outside tmux, with no attached client, or when the query
|
|
43
|
+
* fails. Nested tmux usually reports no `Sxl`: the inner client's terminal is
|
|
44
|
+
* the outer tmux, whose DA1 starts with level 1, which tmux does not parse
|
|
45
|
+
* for features.
|
|
46
|
+
*/
|
|
47
|
+
export declare function tmuxClientRendersSixel(env?: NodeJS.ProcessEnv): boolean;
|
|
48
|
+
/**
|
|
49
|
+
* Whether tmux forwards this pane's `\x1bPtmux;…\x1b\\` passthrough envelopes
|
|
50
|
+
* to the attached client. tmux 3.3+ defaults `allow-passthrough` to `off` and
|
|
51
|
+
* then silently drops every wrapped sequence, so Kitty graphics sent through
|
|
52
|
+
* the envelope never reach a Kitty-capable outer terminal. Resolves the
|
|
53
|
+
* effective pane value (pane → window → global). Queried once per pane;
|
|
54
|
+
* `false` outside tmux or when the query fails.
|
|
20
55
|
*/
|
|
21
|
-
export declare function
|
|
56
|
+
export declare function tmuxPaneAllowsPassthrough(env?: NodeJS.ProcessEnv): boolean;
|
package/src/tmux.ts
CHANGED
|
@@ -10,24 +10,55 @@ export function wrapTmuxPassthroughIfNeeded(payload: string, env: NodeJS.Process
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
const CLIENT_TERMTYPE_NAME = /^([A-Za-z][A-Za-z0-9._+-]*)(?=\s|\(|$)/u;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// One `tmux display-message` round trip over the server socket, which answers in
|
|
14
|
+
// single-digit milliseconds when healthy. 500ms kept losing the race on a loaded
|
|
15
|
+
// machine (fork/exec of the tmux client alone can exceed it on a saturated box),
|
|
16
|
+
// and a lost race is indistinguishable from "not tmux": the process then falls
|
|
17
|
+
// back to the true-color profile for the whole session. 2s bounds the startup
|
|
18
|
+
// stall only when tmux is genuinely wedged.
|
|
19
|
+
const CLIENT_TERMINAL_TIMEOUT_MS = 2_000;
|
|
20
|
+
/** Client `TERM` values that name their emulator; tmux records this on every version. */
|
|
21
|
+
const CLIENT_TERMNAME_PROGRAMS: ReadonlyArray<readonly [RegExp, string]> = [
|
|
22
|
+
[/^xterm-kitty$/u, "kitty"],
|
|
23
|
+
[/^xterm-ghostty$/u, "ghostty"],
|
|
24
|
+
[/^wezterm$/u, "wezterm"],
|
|
25
|
+
];
|
|
26
|
+
let cachedClientTerminalNames: readonly string[] | undefined;
|
|
15
27
|
|
|
16
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Emulator name candidates from a `#{client_termtype}<TAB>#{client_termname}`
|
|
30
|
+
* line, best first: the terminal-type reply (tmux 3.4+, XTVERSION), then the
|
|
31
|
+
* emulator the client's `TERM` names when it ships its own terminfo entry. Both
|
|
32
|
+
* are kept because the reply can name an unrecognized program (a nested tmux
|
|
33
|
+
* answers `tmux 3.4`) while `TERM` still identifies the emulator.
|
|
34
|
+
*/
|
|
35
|
+
export function tmuxClientProgramsFromReport(report: string): string[] {
|
|
36
|
+
// Split before trimming: an empty terminal type leaves the line starting with the tab.
|
|
37
|
+
const [termtype = "", termname = ""] = report.split("\t");
|
|
38
|
+
const programs: string[] = [];
|
|
39
|
+
const program = CLIENT_TERMTYPE_NAME.exec(termtype.trim())?.[1];
|
|
40
|
+
if (program) programs.push(program);
|
|
41
|
+
const term = termname.trim().toLowerCase();
|
|
42
|
+
const termProgram = CLIENT_TERMNAME_PROGRAMS.find(([pattern]) => pattern.test(term))?.[1];
|
|
43
|
+
if (termProgram) programs.push(termProgram);
|
|
44
|
+
return programs;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function queryTmuxClientTerminalNames(env: NodeJS.ProcessEnv): readonly string[] {
|
|
17
48
|
const tmux = $which("tmux", { PATH: env.PATH });
|
|
18
|
-
if (!tmux) return
|
|
49
|
+
if (!tmux) return [];
|
|
19
50
|
try {
|
|
20
|
-
const result = Bun.spawnSync([tmux, "display-message", "-p", "#{client_termtype}"], {
|
|
51
|
+
const result = Bun.spawnSync([tmux, "display-message", "-p", "#{client_termtype}\t#{client_termname}"], {
|
|
21
52
|
env,
|
|
22
53
|
stdout: "pipe",
|
|
23
54
|
stderr: "ignore",
|
|
24
|
-
timeout:
|
|
55
|
+
timeout: CLIENT_TERMINAL_TIMEOUT_MS,
|
|
25
56
|
killSignal: "SIGKILL",
|
|
26
57
|
});
|
|
27
|
-
if (result.exitCode !== 0) return
|
|
28
|
-
return
|
|
58
|
+
if (result.exitCode !== 0) return [];
|
|
59
|
+
return tmuxClientProgramsFromReport(result.stdout.toString());
|
|
29
60
|
} catch {
|
|
30
|
-
return
|
|
61
|
+
return [];
|
|
31
62
|
}
|
|
32
63
|
}
|
|
33
64
|
|
|
@@ -75,14 +106,77 @@ export function setTmuxPaneApcTitles(on: boolean, env: NodeJS.ProcessEnv = Bun.e
|
|
|
75
106
|
}
|
|
76
107
|
|
|
77
108
|
/**
|
|
78
|
-
* Resolve the terminal emulator
|
|
109
|
+
* Resolve the terminal emulator names recorded for this tmux client, best first.
|
|
79
110
|
*
|
|
80
111
|
* tmux overwrites pane identity variables with its own values, but retains the
|
|
81
|
-
* attached client's terminal-type reply in `#{client_termtype}
|
|
82
|
-
*
|
|
112
|
+
* attached client's terminal-type reply in `#{client_termtype}` and its `TERM`
|
|
113
|
+
* in `#{client_termname}` (see {@link tmuxClientProgramsFromReport}). The local
|
|
114
|
+
* IPC query runs once per process and degrades to `[]` when unavailable.
|
|
83
115
|
*/
|
|
84
|
-
export function
|
|
85
|
-
if (!tmuxMultiplexer.isInside(env) || isBunTestRuntime()) return
|
|
86
|
-
|
|
87
|
-
return
|
|
116
|
+
export function resolveTmuxClientTerminalNames(env: NodeJS.ProcessEnv = Bun.env): readonly string[] {
|
|
117
|
+
if (!tmuxMultiplexer.isInside(env) || isBunTestRuntime()) return [];
|
|
118
|
+
cachedClientTerminalNames ??= queryTmuxClientTerminalNames(env);
|
|
119
|
+
return cachedClientTerminalNames;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const SIXEL_CAPABILITY = /^\s*\d+: Sxl: \(flag\) true$/mu;
|
|
123
|
+
let cachedClientSixel: { tmux: string; renders: boolean } | undefined;
|
|
124
|
+
|
|
125
|
+
function queryTmuxClientRendersSixel(env: NodeJS.ProcessEnv): boolean {
|
|
126
|
+
const client = runTmux(["display-message", "-p", "#{client_name}"], env)?.trim();
|
|
127
|
+
if (!client) return false;
|
|
128
|
+
return SIXEL_CAPABILITY.test(runTmux(["show-messages", "-T", "-t", client], env) ?? "");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Whether tmux draws SIXEL to the client attached to this session: its
|
|
133
|
+
* effective terminal description for that client carries `Sxl`.
|
|
134
|
+
*
|
|
135
|
+
* A tmux built with SIXEL answers every pane's DA1 with attribute 4 and draws a
|
|
136
|
+
* `SIXEL IMAGE (WxH)` text placeholder for clients without SIXEL, so the pane's
|
|
137
|
+
* DA1 says nothing about the outer terminal. tmux itself renders SIXEL only
|
|
138
|
+
* when the client's terminal has `Sxl`, whether from terminfo,
|
|
139
|
+
* `terminal-overrides`, or the `sixel` feature (DA1 or `terminal-features`),
|
|
140
|
+
* and `show-messages -T` reports that merged description. The TUI consults
|
|
141
|
+
* this before trusting the pane's DA1 attribute. Queried once per tmux
|
|
142
|
+
* session; `false` outside tmux, with no attached client, or when the query
|
|
143
|
+
* fails. Nested tmux usually reports no `Sxl`: the inner client's terminal is
|
|
144
|
+
* the outer tmux, whose DA1 starts with level 1, which tmux does not parse
|
|
145
|
+
* for features.
|
|
146
|
+
*/
|
|
147
|
+
export function tmuxClientRendersSixel(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
148
|
+
const tmux = env.TMUX;
|
|
149
|
+
if (!tmux) return false;
|
|
150
|
+
if (cachedClientSixel?.tmux !== tmux) cachedClientSixel = { tmux, renders: queryTmuxClientRendersSixel(env) };
|
|
151
|
+
return cachedClientSixel.renders;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let cachedPanePassthrough: { key: string; allowed: boolean } | undefined;
|
|
155
|
+
|
|
156
|
+
function queryTmuxPaneAllowsPassthrough(env: NodeJS.ProcessEnv): boolean {
|
|
157
|
+
const pane = env.TMUX_PANE;
|
|
158
|
+
const target = pane ? ["-t", pane] : [];
|
|
159
|
+
const value = runTmux(["show-options", "-Apqv", ...target, "allow-passthrough"], env)?.trim();
|
|
160
|
+
if (value === undefined) return false;
|
|
161
|
+
// tmux before 3.3 has no `allow-passthrough` option (`-q` prints nothing)
|
|
162
|
+
// and always forwards DCS passthrough.
|
|
163
|
+
return value === "" || value === "on" || value === "all";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Whether tmux forwards this pane's `\x1bPtmux;…\x1b\\` passthrough envelopes
|
|
168
|
+
* to the attached client. tmux 3.3+ defaults `allow-passthrough` to `off` and
|
|
169
|
+
* then silently drops every wrapped sequence, so Kitty graphics sent through
|
|
170
|
+
* the envelope never reach a Kitty-capable outer terminal. Resolves the
|
|
171
|
+
* effective pane value (pane → window → global). Queried once per pane;
|
|
172
|
+
* `false` outside tmux or when the query fails.
|
|
173
|
+
*/
|
|
174
|
+
export function tmuxPaneAllowsPassthrough(env: NodeJS.ProcessEnv = Bun.env): boolean {
|
|
175
|
+
const tmux = env.TMUX;
|
|
176
|
+
if (!tmux) return false;
|
|
177
|
+
const key = `${tmux}\0${env.TMUX_PANE ?? ""}`;
|
|
178
|
+
if (cachedPanePassthrough?.key !== key) {
|
|
179
|
+
cachedPanePassthrough = { key, allowed: queryTmuxPaneAllowsPassthrough(env) };
|
|
180
|
+
}
|
|
181
|
+
return cachedPanePassthrough.allowed;
|
|
88
182
|
}
|
package/src/tools/ask.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { getMarkdownTheme, type Theme } from "../theme/theme";
|
|
|
23
23
|
|
|
24
24
|
import { outputBlockContentWidth, renderStatusLine } from "../render";
|
|
25
25
|
import { framedToolCard } from "../render/tool-card";
|
|
26
|
+
import { wrapPrefixedLine } from "../render/hanging-wrap";
|
|
26
27
|
|
|
27
28
|
import { formatErrorMessage, formatMeta, formatTitle, sanitizeCarriageReturns } from "../render/render-utils";
|
|
28
29
|
import { tuiText, tuiTextFmt } from "../i18n";
|
|
@@ -203,20 +204,27 @@ export function optionMarker(uiTheme: Theme, multi: boolean | undefined, selecte
|
|
|
203
204
|
return selected ? uiTheme.radio.selected : uiTheme.radio.unselected;
|
|
204
205
|
}
|
|
205
206
|
|
|
207
|
+
function wrapOptionLabel(marker: string, label: string, width: number): string[] {
|
|
208
|
+
const prefix = ` ${marker} `;
|
|
209
|
+
return wrapPrefixedLine(prefix, " ".repeat(visibleWidth(prefix)), label, width);
|
|
210
|
+
}
|
|
211
|
+
|
|
206
212
|
/** Render the offered options for a question form as flat marker bullets (no tree guides). */
|
|
207
213
|
function renderQuestionOptionLines(
|
|
208
214
|
uiTheme: Theme,
|
|
209
215
|
mdTheme: MarkdownTheme,
|
|
210
216
|
options: AskRenderOption[],
|
|
211
217
|
multi: boolean | undefined,
|
|
218
|
+
width: number,
|
|
212
219
|
): string[] {
|
|
213
220
|
const out: string[] = [];
|
|
214
221
|
for (const opt of options) {
|
|
215
222
|
const optLabel = renderInlineMarkdown(opt.label, mdTheme, t => uiTheme.fg("muted", t));
|
|
216
|
-
out.push(
|
|
223
|
+
out.push(...wrapOptionLabel(uiTheme.fg("dim", optionMarker(uiTheme, multi, false)), optLabel, width));
|
|
217
224
|
if (opt.description?.trim()) {
|
|
218
225
|
const description = renderInlineMarkdown(opt.description.trim(), mdTheme, t => uiTheme.fg("dim", t));
|
|
219
|
-
|
|
226
|
+
const prefix = ` ${uiTheme.fg("dim", "↳")} `;
|
|
227
|
+
out.push(...wrapPrefixedLine(prefix, " ".repeat(visibleWidth(prefix)), description, width));
|
|
220
228
|
}
|
|
221
229
|
}
|
|
222
230
|
return out;
|
|
@@ -261,6 +269,7 @@ function renderAnswerOptionLines(
|
|
|
261
269
|
customInput: string | undefined,
|
|
262
270
|
note: string | undefined,
|
|
263
271
|
width: number,
|
|
272
|
+
contentWidth: number,
|
|
264
273
|
selectedIndices?: ReadonlySet<number>,
|
|
265
274
|
): string[] {
|
|
266
275
|
const selected = new Set(selectedOptions ?? []);
|
|
@@ -283,7 +292,7 @@ function renderAnswerOptionLines(
|
|
|
283
292
|
const labelStyled = renderInlineMarkdown(label, mdTheme, t =>
|
|
284
293
|
isSelected ? uiTheme.fg("toolOutput", t) : uiTheme.fg("muted", t),
|
|
285
294
|
);
|
|
286
|
-
out.push(
|
|
295
|
+
out.push(...wrapOptionLabel(markerStyled, labelStyled, contentWidth));
|
|
287
296
|
}
|
|
288
297
|
if (customInput !== undefined) out.push(...renderCustomInputLines(uiTheme, customInput));
|
|
289
298
|
if (note !== undefined) out.push(...renderNoteLines(uiTheme, note, width));
|
|
@@ -461,7 +470,7 @@ export const askToolRenderer = {
|
|
|
461
470
|
? tuiText("askQuestionOne", "1 question")
|
|
462
471
|
: tuiTextFmt("askQuestionManyFmt", "%d questions", questions.length),
|
|
463
472
|
)}`;
|
|
464
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
473
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
465
474
|
const sections = questions.map(q => {
|
|
466
475
|
const meta: string[] = [];
|
|
467
476
|
if (q.multi) meta.push(tuiText("askMultiMeta", "multi"));
|
|
@@ -470,7 +479,7 @@ export const askToolRenderer = {
|
|
|
470
479
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
471
480
|
const mdLines = md(q.question, width);
|
|
472
481
|
const lines = q.options?.length
|
|
473
|
-
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, q.options, q.multi)]
|
|
482
|
+
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, q.options, q.multi, contentWidth)]
|
|
474
483
|
: mdLines;
|
|
475
484
|
return { label: `${uiTheme.fg("dim", `[${q.id}]`)}${metaStr}`, content: lines };
|
|
476
485
|
});
|
|
@@ -496,11 +505,11 @@ export const askToolRenderer = {
|
|
|
496
505
|
if (questionOptions?.length) meta.push(`options:${questionOptions.length}`);
|
|
497
506
|
const header = `${label}${formatMeta(meta, uiTheme)}`;
|
|
498
507
|
const multi = args.multi;
|
|
499
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
508
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
500
509
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
501
510
|
const mdLines = md(question, width);
|
|
502
511
|
const bodyLines = questionOptions?.length
|
|
503
|
-
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, questionOptions, multi)]
|
|
512
|
+
? [...mdLines, ...renderQuestionOptionLines(uiTheme, mdTheme, questionOptions, multi, contentWidth)]
|
|
504
513
|
: mdLines;
|
|
505
514
|
return {
|
|
506
515
|
header,
|
|
@@ -570,7 +579,7 @@ export const askToolRenderer = {
|
|
|
570
579
|
},
|
|
571
580
|
uiTheme,
|
|
572
581
|
);
|
|
573
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
582
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
574
583
|
const rawResults = rawDetails.results ?? [];
|
|
575
584
|
const sections = results.map((r, index) => {
|
|
576
585
|
// Sanitizing preserves order and length, so raw indices align with `r`.
|
|
@@ -587,6 +596,7 @@ export const askToolRenderer = {
|
|
|
587
596
|
r.customInput,
|
|
588
597
|
r.note,
|
|
589
598
|
width,
|
|
599
|
+
contentWidth,
|
|
590
600
|
selectedIndicesFor(raw?.options, raw?.selectedOptions),
|
|
591
601
|
),
|
|
592
602
|
];
|
|
@@ -625,7 +635,7 @@ export const askToolRenderer = {
|
|
|
625
635
|
const dCustom = details.customInput;
|
|
626
636
|
const dNote = details.note;
|
|
627
637
|
const dTimedOut = details.timedOut;
|
|
628
|
-
return framedToolCard(uiTheme, ({ width }) => {
|
|
638
|
+
return framedToolCard(uiTheme, ({ width, contentWidth }) => {
|
|
629
639
|
// md() returns a shared cached array (module-level Markdown LRU) — copy before appending.
|
|
630
640
|
const bodyLines = [
|
|
631
641
|
...md(question, width),
|
|
@@ -638,6 +648,7 @@ export const askToolRenderer = {
|
|
|
638
648
|
dCustom,
|
|
639
649
|
dNote,
|
|
640
650
|
width,
|
|
651
|
+
contentWidth,
|
|
641
652
|
selectedIndicesFor(rawDetails.options, rawDetails.selectedOptions),
|
|
642
653
|
),
|
|
643
654
|
];
|
package/src/tools/ast-edit.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
2
|
import type { Theme } from "../theme/theme";
|
|
3
3
|
import type { OutputMeta } from "./output-meta";
|
|
4
|
-
import type { NativeToolView, RenderResultOptions } from "./renderer";
|
|
4
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
5
5
|
/** Display metadata returned by ast-edit. */
|
|
6
6
|
export interface AstEditToolDetails {
|
|
7
7
|
totalReplacements: number;
|
|
@@ -47,7 +47,7 @@ export declare const astEditToolRenderer: {
|
|
|
47
47
|
}>;
|
|
48
48
|
details?: AstEditToolDetails;
|
|
49
49
|
isError?: boolean;
|
|
50
|
-
}, options:
|
|
50
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: AstEditRenderArgs): Component;
|
|
51
51
|
describeCall(args: AstEditRenderArgs): NativeToolView;
|
|
52
52
|
describeResult(result: {
|
|
53
53
|
content: Array<{
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord, sanitizeText } from "@linxiraos/pi-utils";
|
|
1
2
|
import type { Component } from "../tui";
|
|
2
3
|
import { Text } from "../components/text";
|
|
3
4
|
import { replaceTabs } from "../utils";
|
|
@@ -14,11 +15,16 @@ import {
|
|
|
14
15
|
} from "../render/render-utils";
|
|
15
16
|
import { classifyGroupedLines, groupLineIndicesByBlank } from "./grouped-file-output";
|
|
16
17
|
import type { OutputMeta } from "./output-meta";
|
|
17
|
-
import type {
|
|
18
|
+
import type {
|
|
19
|
+
NativeToolHead,
|
|
20
|
+
NativeToolView,
|
|
21
|
+
RenderResultContextOptions,
|
|
22
|
+
RenderResultOptions,
|
|
23
|
+
ToolRenderer,
|
|
24
|
+
} from "./renderer";
|
|
18
25
|
import { code, compact, diff, node, span, text } from "../native/describe";
|
|
19
26
|
import type { NativeChild, NativeNode } from "../native/node";
|
|
20
27
|
import { diffStatsMeta, displayPath, errorText, fileDiffSection, noteText, resultText, statsText } from "./native-view";
|
|
21
|
-
import { getLanguageFromPath } from "../lang-from-path";
|
|
22
28
|
|
|
23
29
|
/** Display metadata returned by ast-edit. */
|
|
24
30
|
export interface AstEditToolDetails {
|
|
@@ -148,9 +154,9 @@ function parseAstEditChanges(lines: readonly string[]): AstEditFileDiff[] {
|
|
|
148
154
|
return files;
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
/** A file's proposed rewrite as a `diff`:
|
|
157
|
+
/** A file's proposed rewrite as a `diff`: Tern highlights it by the file's grammar and draws no path header. */
|
|
152
158
|
function astEditDiff(file: AstEditFileDiff): NativeNode {
|
|
153
|
-
return diff(file.lines.join("\n"), {
|
|
159
|
+
return diff(file.lines.join("\n"), { path: file.path });
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
/** The single rewrite pattern (`target:"pattern"`), or the rewrite count, for the head. */
|
|
@@ -190,7 +196,7 @@ export const astEditToolRenderer = {
|
|
|
190
196
|
|
|
191
197
|
renderResult(
|
|
192
198
|
result: { content: Array<{ type: string; text?: string }>; details?: AstEditToolDetails; isError?: boolean },
|
|
193
|
-
options:
|
|
199
|
+
options: RenderResultContextOptions,
|
|
194
200
|
uiTheme: Theme,
|
|
195
201
|
args?: AstEditRenderArgs,
|
|
196
202
|
): Component {
|
|
@@ -245,6 +251,19 @@ export const astEditToolRenderer = {
|
|
|
245
251
|
// Resolve hyperlinks over the whole output so nested directory headers
|
|
246
252
|
// reconstruct across the blank-line groups the tree list collapses by.
|
|
247
253
|
const contexts = classifyGroupedLines(allLines, details?.cwd ?? details?.searchPath, details?.searchPath);
|
|
254
|
+
// The display lines are sanitized; reconstruct link targets from the original
|
|
255
|
+
// grouped headers only when they still align with those display lines.
|
|
256
|
+
const rawDetails = options.renderContext?.rawDetails;
|
|
257
|
+
const rawContent =
|
|
258
|
+
isRecord(rawDetails) && typeof rawDetails.displayContent === "string" ? rawDetails.displayContent : undefined;
|
|
259
|
+
const rawLines = rawContent?.split("\n");
|
|
260
|
+
const rawCwd = isRecord(rawDetails) && typeof rawDetails.cwd === "string" ? rawDetails.cwd : undefined;
|
|
261
|
+
const rawSearchPath =
|
|
262
|
+
isRecord(rawDetails) && typeof rawDetails.searchPath === "string" ? rawDetails.searchPath : undefined;
|
|
263
|
+
const rawContexts =
|
|
264
|
+
rawContent !== undefined && rawLines?.length === allLines.length && sanitizeText(rawContent) === textContent
|
|
265
|
+
? classifyGroupedLines(rawLines, rawCwd ?? rawSearchPath, rawSearchPath)
|
|
266
|
+
: undefined;
|
|
248
267
|
// Style lazily: collapsed bodies show only the first groups.
|
|
249
268
|
const styledLines: (string | undefined)[] = Array.from({ length: allLines.length }, () => undefined);
|
|
250
269
|
const styleLine = (index: number): string => {
|
|
@@ -255,12 +274,16 @@ export const astEditToolRenderer = {
|
|
|
255
274
|
// second vertical bar inside the frame border.
|
|
256
275
|
const display = replaceTabs(allLines[index]!.replace("│", " "));
|
|
257
276
|
let styled: string;
|
|
277
|
+
const linkPath =
|
|
278
|
+
ctx.headerPath && rawContexts?.[index]?.kind === ctx.kind && rawContexts[index]?.depth === ctx.depth
|
|
279
|
+
? (rawContexts[index]?.headerPath ?? ctx.headerPath)
|
|
280
|
+
: ctx.headerPath;
|
|
258
281
|
if (ctx.kind === "dir") {
|
|
259
282
|
const accent = uiTheme.fg("accent", display);
|
|
260
|
-
styled =
|
|
283
|
+
styled = linkPath ? fileHyperlink(linkPath, accent) : accent;
|
|
261
284
|
} else if (ctx.kind === "file") {
|
|
262
285
|
const tinted = uiTheme.fg(ctx.depth === 1 ? "accent" : "dim", display);
|
|
263
|
-
styled =
|
|
286
|
+
styled = linkPath ? fileHyperlink(linkPath, tinted) : tinted;
|
|
264
287
|
} else if (display.startsWith("+")) {
|
|
265
288
|
styled = uiTheme.fg("toolDiffAdded", display);
|
|
266
289
|
} else if (display.startsWith("-")) {
|
package/src/tools/ast-grep.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
2
|
import type { Theme } from "../theme/theme";
|
|
3
3
|
import type { OutputMeta } from "./output-meta";
|
|
4
|
-
import type {
|
|
4
|
+
import type { NativeToolView, RenderResultContextOptions, RenderResultOptions } from "./renderer";
|
|
5
5
|
/** Display metadata returned by ast-grep. */
|
|
6
6
|
export interface AstGrepToolDetails {
|
|
7
7
|
matchCount: number;
|
|
@@ -46,7 +46,7 @@ export declare const astGrepToolRenderer: {
|
|
|
46
46
|
}>;
|
|
47
47
|
details?: AstGrepToolDetails;
|
|
48
48
|
isError?: boolean;
|
|
49
|
-
}, options:
|
|
49
|
+
}, options: RenderResultContextOptions, uiTheme: Theme, args?: AstGrepRenderArgs): Component;
|
|
50
50
|
describeCall(args: AstGrepRenderArgs): NativeToolView;
|
|
51
51
|
describeResult(result: {
|
|
52
52
|
content: Array<{
|