@linxiraos/pi-tui 1.2.1 → 1.3.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.
Files changed (274) hide show
  1. package/CHANGELOG.md +188 -125
  2. package/README.md +36 -0
  3. package/THIRD-PARTY-NOTICES.txt +55 -5
  4. package/dist/types/app-keybindings.d.ts +18 -8
  5. package/dist/types/apps/git/diff-pane.d.ts +5 -0
  6. package/dist/types/apps/git/git-tui.d.ts +16 -2
  7. package/dist/types/apps/git/state.d.ts +11 -0
  8. package/dist/types/autocomplete.d.ts +2 -0
  9. package/dist/types/charts/chart-svg.d.ts +7 -13
  10. package/dist/types/chat/advisor-message.d.ts +4 -1
  11. package/dist/types/chat/assistant-message.d.ts +9 -4
  12. package/dist/types/chat/figure-markdown.d.ts +7 -2
  13. package/dist/types/chat/messages.d.ts +3 -1
  14. package/dist/types/chat/read-tool-group.d.ts +2 -2
  15. package/dist/types/chat/table-chart.d.ts +11 -15
  16. package/dist/types/chat/tool-execution.d.ts +32 -20
  17. package/dist/types/chat/user-message.d.ts +3 -2
  18. package/dist/types/chrome/countdown-timer.d.ts +3 -2
  19. package/dist/types/chrome/diff.d.ts +2 -3
  20. package/dist/types/chrome/transcript-container.d.ts +2 -0
  21. package/dist/types/components/image.d.ts +2 -0
  22. package/dist/types/components/markdown.d.ts +49 -3
  23. package/dist/types/components/text.d.ts +2 -0
  24. package/dist/types/debug-server.d.ts +2 -0
  25. package/dist/types/keybinding-matchers.d.ts +6 -0
  26. package/dist/types/multiplexers/tmux.d.ts +1 -0
  27. package/dist/types/multiplexers/types.d.ts +6 -0
  28. package/dist/types/native/backend.d.ts +5 -4
  29. package/dist/types/native/blobs.d.ts +24 -12
  30. package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
  31. package/dist/types/overlays/annotation-overlay.d.ts +19 -0
  32. package/dist/types/overlays/approval-dialog.d.ts +79 -0
  33. package/dist/types/overlays/ask-dialog.d.ts +11 -6
  34. package/dist/types/overlays/btw-history-panel.d.ts +2 -6
  35. package/dist/types/overlays/hook-editor.d.ts +7 -2
  36. package/dist/types/overlays/hook-input.d.ts +12 -4
  37. package/dist/types/overlays/job-tree.d.ts +50 -0
  38. package/dist/types/overlays/jobs-panel.d.ts +25 -11
  39. package/dist/types/overlays/login-dialog.d.ts +8 -3
  40. package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
  41. package/dist/types/overlays/model-browser.d.ts +6 -1
  42. package/dist/types/overlays/model-picker.d.ts +2 -0
  43. package/dist/types/overlays/pause-screen.d.ts +5 -0
  44. package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
  45. package/dist/types/overlays/report-panel.d.ts +5 -1
  46. package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
  47. package/dist/types/overlays/session-account-selector.d.ts +4 -0
  48. package/dist/types/overlays/session-info-overlay.d.ts +7 -3
  49. package/dist/types/overlays/session-selector.d.ts +3 -1
  50. package/dist/types/overlays/usage-dashboard.d.ts +6 -6
  51. package/dist/types/prompt/composer.d.ts +21 -20
  52. package/dist/types/prompt/custom-editor.d.ts +20 -10
  53. package/dist/types/prompt/image-references.d.ts +22 -0
  54. package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
  55. package/dist/types/prompt/macos-spelling.d.ts +2 -1
  56. package/dist/types/prompt/prose-gate.d.ts +3 -0
  57. package/dist/types/prompt/queued-messages.d.ts +1 -1
  58. package/dist/types/prompt/typing-word.d.ts +25 -0
  59. package/dist/types/prompt/welcome.d.ts +26 -6
  60. package/dist/types/render/hanging-wrap.d.ts +7 -0
  61. package/dist/types/render/hyperlink.d.ts +2 -0
  62. package/dist/types/render/render-utils.d.ts +9 -1
  63. package/dist/types/render/utils.d.ts +1 -1
  64. package/dist/types/setup/scenes/splash.d.ts +6 -5
  65. package/dist/types/status-line/context-usage.d.ts +4 -1
  66. package/dist/types/status-line/host.d.ts +6 -4
  67. package/dist/types/status-line/startup.d.ts +6 -6
  68. package/dist/types/status-line/types.d.ts +3 -0
  69. package/dist/types/stdin-buffer.d.ts +7 -0
  70. package/dist/types/terminal-capabilities.d.ts +16 -1
  71. package/dist/types/terminal-multiplexer.d.ts +7 -0
  72. package/dist/types/terminal.d.ts +8 -4
  73. package/dist/types/theme/symbols.d.ts +1 -1
  74. package/dist/types/theme/theme.d.ts +11 -0
  75. package/dist/types/thinking.d.ts +5 -0
  76. package/dist/types/tmux.d.ts +39 -4
  77. package/dist/types/tools/ast-edit.d.ts +2 -2
  78. package/dist/types/tools/ast-grep.d.ts +2 -2
  79. package/dist/types/tools/bash.d.ts +6 -1
  80. package/dist/types/tools/edit.d.ts +2 -0
  81. package/dist/types/tools/eval.d.ts +53 -4
  82. package/dist/types/tools/find.d.ts +14 -4
  83. package/dist/types/tools/glob.d.ts +3 -3
  84. package/dist/types/tools/grep.d.ts +2 -2
  85. package/dist/types/tools/grouped-file-output.d.ts +9 -4
  86. package/dist/types/tools/native-view.d.ts +1 -1
  87. package/dist/types/tools/read.d.ts +32 -10
  88. package/dist/types/tools/renderer.d.ts +2 -0
  89. package/dist/types/tools/task.d.ts +31 -0
  90. package/dist/types/tools/write.d.ts +2 -0
  91. package/dist/types/tui.d.ts +20 -3
  92. package/package.json +10 -10
  93. package/src/app-keybindings.d.ts +18 -8
  94. package/src/app-keybindings.ts +20 -8
  95. package/src/apps/autoresearch-dashboard.ts +444 -27
  96. package/src/apps/debug/raw-sse.ts +2 -2
  97. package/src/apps/git/diff-pane.ts +39 -41
  98. package/src/apps/git/git-tui.ts +80 -14
  99. package/src/apps/git/sidebar.ts +10 -1
  100. package/src/apps/git/state.ts +12 -0
  101. package/src/autocomplete.d.ts +2 -0
  102. package/src/autocomplete.ts +21 -19
  103. package/src/bracketed-paste.ts +9 -2
  104. package/src/charts/chart-svg.d.ts +7 -13
  105. package/src/charts/chart-svg.ts +19 -22
  106. package/src/chat/advisor-message.d.ts +4 -1
  107. package/src/chat/advisor-message.ts +77 -24
  108. package/src/chat/assistant-message.d.ts +9 -4
  109. package/src/chat/assistant-message.ts +127 -31
  110. package/src/chat/bash-execution.ts +7 -2
  111. package/src/chat/figure-markdown.d.ts +7 -2
  112. package/src/chat/figure-markdown.ts +10 -3
  113. package/src/chat/image-loading.ts +9 -6
  114. package/src/chat/messages.d.ts +3 -1
  115. package/src/chat/messages.ts +4 -1
  116. package/src/chat/read-tool-group.d.ts +2 -2
  117. package/src/chat/read-tool-group.ts +97 -42
  118. package/src/chat/svg-figure.ts +3 -1
  119. package/src/chat/svg-source.ts +6 -3
  120. package/src/chat/table-chart.d.ts +11 -15
  121. package/src/chat/table-chart.ts +24 -57
  122. package/src/chat/tool-execution.d.ts +32 -20
  123. package/src/chat/tool-execution.ts +103 -36
  124. package/src/chat/user-message.d.ts +3 -2
  125. package/src/chat/user-message.ts +54 -34
  126. package/src/chrome/countdown-timer.ts +3 -3
  127. package/src/chrome/diff.d.ts +2 -3
  128. package/src/chrome/diff.ts +4 -9
  129. package/src/chrome/transcript-container.d.ts +2 -0
  130. package/src/chrome/transcript-container.ts +212 -28
  131. package/src/components/editor.ts +12 -12
  132. package/src/components/image.d.ts +2 -0
  133. package/src/components/image.ts +54 -20
  134. package/src/components/markdown.d.ts +49 -3
  135. package/src/components/markdown.ts +461 -11
  136. package/src/components/settings-list.ts +2 -4
  137. package/src/components/text.d.ts +2 -0
  138. package/src/components/text.ts +11 -1
  139. package/src/debug-server.d.ts +2 -0
  140. package/src/debug-server.ts +51 -69
  141. package/src/hotkeys-markdown.ts +2 -0
  142. package/src/keybinding-matchers.d.ts +6 -0
  143. package/src/keybinding-matchers.ts +19 -0
  144. package/src/multiplexers/tmux.d.ts +1 -0
  145. package/src/multiplexers/tmux.ts +3 -0
  146. package/src/multiplexers/types.d.ts +6 -0
  147. package/src/multiplexers/types.ts +6 -0
  148. package/src/native/backend.d.ts +5 -4
  149. package/src/native/backend.ts +13 -5
  150. package/src/native/blobs.d.ts +24 -12
  151. package/src/native/blobs.ts +56 -7
  152. package/src/native/reconcile.ts +43 -21
  153. package/src/overlays/advisor-config.ts +259 -8
  154. package/src/overlays/agent-hub-projection.d.ts +10 -5
  155. package/src/overlays/agent-hub-projection.ts +63 -12
  156. package/src/overlays/annotation-overlay.ts +422 -168
  157. package/src/overlays/approval-dialog.ts +389 -0
  158. package/src/overlays/ask-dialog.ts +244 -105
  159. package/src/overlays/btw-history-panel.ts +208 -147
  160. package/src/overlays/extensions/extension-list.ts +4 -6
  161. package/src/overlays/hook-editor.ts +90 -32
  162. package/src/overlays/hook-input.ts +105 -19
  163. package/src/overlays/job-tree.ts +177 -0
  164. package/src/overlays/jobs-panel.ts +222 -142
  165. package/src/overlays/login-dialog.ts +201 -91
  166. package/src/overlays/mcp-add-wizard.ts +562 -279
  167. package/src/overlays/model-browser.ts +32 -7
  168. package/src/overlays/model-hub.ts +4 -10
  169. package/src/overlays/model-picker.ts +58 -21
  170. package/src/overlays/pause-screen.ts +81 -23
  171. package/src/overlays/plan-review-overlay.ts +16 -0
  172. package/src/overlays/plugin-selector.ts +17 -1
  173. package/src/overlays/queue-mode-selector.ts +10 -0
  174. package/src/overlays/report-panel.ts +6 -2
  175. package/src/overlays/reset-usage-selector.ts +130 -21
  176. package/src/overlays/session-account-selector.ts +36 -0
  177. package/src/overlays/session-info-overlay.ts +199 -76
  178. package/src/overlays/session-selector.ts +15 -10
  179. package/src/overlays/settings-selector.ts +3 -0
  180. package/src/overlays/show-images-selector.ts +1 -0
  181. package/src/overlays/snapcompact-shape-preview.ts +13 -6
  182. package/src/overlays/theme-selector.ts +6 -1
  183. package/src/overlays/thinking-selector.ts +8 -4
  184. package/src/overlays/tree-selector.ts +117 -2
  185. package/src/overlays/usage-dashboard.ts +498 -144
  186. package/src/prompt/composer-cache.ts +1 -1
  187. package/src/prompt/composer.ts +194 -74
  188. package/src/prompt/custom-editor.ts +168 -65
  189. package/src/prompt/image-references.d.ts +22 -0
  190. package/src/prompt/image-references.ts +79 -2
  191. package/src/prompt/internal-url-autocomplete.ts +1 -0
  192. package/src/prompt/macos-spelling.ts +28 -5
  193. package/src/prompt/prose-gate.ts +3 -0
  194. package/src/prompt/queued-messages.ts +9 -2
  195. package/src/prompt/tips.txt +1 -2
  196. package/src/prompt/typing-word.ts +59 -0
  197. package/src/prompt/welcome.ts +97 -45
  198. package/src/render/code-cell.ts +18 -5
  199. package/src/render/hanging-wrap.d.ts +7 -0
  200. package/src/render/hanging-wrap.ts +58 -0
  201. package/src/render/hyperlink.d.ts +2 -0
  202. package/src/render/hyperlink.ts +5 -0
  203. package/src/render/render-utils.d.ts +9 -1
  204. package/src/render/render-utils.ts +16 -2
  205. package/src/render/utils.d.ts +1 -1
  206. package/src/render/utils.ts +1 -1
  207. package/src/render/width-aware-text.ts +6 -5
  208. package/src/setup/scenes/splash.ts +22 -8
  209. package/src/status-line/component.ts +23 -10
  210. package/src/status-line/context-usage.ts +176 -73
  211. package/src/status-line/footer.ts +18 -35
  212. package/src/status-line/host.ts +6 -1
  213. package/src/status-line/segments.ts +16 -5
  214. package/src/status-line/startup.ts +6 -6
  215. package/src/status-line/types.ts +1 -0
  216. package/src/stdin-buffer.d.ts +7 -0
  217. package/src/stdin-buffer.ts +11 -1
  218. package/src/terminal-capabilities.d.ts +16 -1
  219. package/src/terminal-capabilities.ts +66 -28
  220. package/src/terminal-multiplexer.d.ts +7 -0
  221. package/src/terminal-multiplexer.ts +14 -0
  222. package/src/terminal.d.ts +8 -4
  223. package/src/terminal.ts +160 -46
  224. package/src/theme/symbols.d.ts +1 -1
  225. package/src/theme/symbols.ts +1 -0
  226. package/src/theme/theme-class.ts +1 -0
  227. package/src/theme/theme.d.ts +11 -0
  228. package/src/theme/theme.ts +46 -14
  229. package/src/thinking.d.ts +5 -0
  230. package/src/thinking.ts +9 -1
  231. package/src/tmux.d.ts +39 -4
  232. package/src/tmux.ts +110 -16
  233. package/src/tools/ask.ts +20 -9
  234. package/src/tools/ast-edit.d.ts +2 -2
  235. package/src/tools/ast-edit.ts +30 -7
  236. package/src/tools/ast-grep.d.ts +2 -2
  237. package/src/tools/ast-grep.ts +39 -35
  238. package/src/tools/bash.d.ts +6 -1
  239. package/src/tools/bash.ts +15 -9
  240. package/src/tools/debug.ts +2 -1
  241. package/src/tools/default-renderer.ts +3 -2
  242. package/src/tools/edit.d.ts +2 -0
  243. package/src/tools/edit.ts +100 -21
  244. package/src/tools/eval.d.ts +53 -4
  245. package/src/tools/eval.ts +173 -137
  246. package/src/tools/find.d.ts +14 -4
  247. package/src/tools/find.ts +71 -18
  248. package/src/tools/glob.d.ts +3 -3
  249. package/src/tools/glob.ts +51 -17
  250. package/src/tools/grep.d.ts +2 -2
  251. package/src/tools/grep.ts +99 -22
  252. package/src/tools/grouped-file-output.d.ts +9 -4
  253. package/src/tools/grouped-file-output.ts +21 -6
  254. package/src/tools/lsp.ts +30 -18
  255. package/src/tools/mcp.ts +2 -2
  256. package/src/tools/native-view.d.ts +1 -1
  257. package/src/tools/native-view.ts +2 -2
  258. package/src/tools/read.d.ts +32 -10
  259. package/src/tools/read.ts +89 -43
  260. package/src/tools/renderer.d.ts +2 -0
  261. package/src/tools/renderer.ts +2 -0
  262. package/src/tools/task.d.ts +31 -0
  263. package/src/tools/task.ts +40 -7
  264. package/src/tools/todo.ts +3 -2
  265. package/src/tools/wait.ts +16 -30
  266. package/src/tools/web-search.ts +2 -1
  267. package/src/tools/write.d.ts +2 -0
  268. package/src/tools/write.ts +36 -12
  269. package/src/tools/yield.ts +2 -2
  270. package/src/tui.d.ts +20 -3
  271. package/src/tui.ts +140 -36
  272. package/src/utils.ts +5 -3
  273. package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
  274. package/src/tools/eval-format/code-mode-declarations.ts +0 -61
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * Status line for the speculative first frame, before any session exists.
3
3
  *
4
- * InteractiveMode persists {@link StatusLineStartupData} (settings plus the last
5
- * session's model and thinking state); the next launch renders it through the
6
- * real {@link StatusLineComponent} as a fresh, unnamed session at the live
7
- * terminal width. Path and git branch are read live; context usage is reported
8
- * unknown so the gauge shows the window without a percent.
4
+ * InteractiveMode persists {@link StatusLineStartupData} (settings plus the
5
+ * resolved default role model and status inputs); the next launch renders it
6
+ * through the real {@link StatusLineComponent} as a fresh, unnamed session at
7
+ * the live terminal width. Path and git branch are read live; context usage is
8
+ * reported unknown so the gauge shows the window without a percent.
9
9
  *
10
10
  * @example
11
11
  * ```ts ignore
@@ -32,7 +32,7 @@ import type { StatusLineSettings } from "./types";
32
32
  export interface StatusLineStartupData {
33
33
  readonly settings: StatusLineSettings;
34
34
  readonly gitEnabled: boolean;
35
- /** Model of the last session; the next fresh session usually starts on it too. */
35
+ /** Model resolved for the next fresh session's default role. */
36
36
  readonly model?: Model;
37
37
  readonly thinkingLevel?: ThinkingLevel;
38
38
  readonly autoThinking: boolean;
@@ -36,6 +36,7 @@ export interface CollabStatus {
36
36
  }
37
37
 
38
38
  export interface StatusLineSegmentOptions {
39
+ pi?: { showProfile?: boolean };
39
40
  model?: { showThinkingLevel?: boolean };
40
41
  path?: { abbreviate?: boolean; maxLength?: number; stripWorkPrefix?: boolean };
41
42
  git?: { showBranch?: boolean; showStaged?: boolean; showUnstaged?: boolean; showUntracked?: boolean };
@@ -61,6 +61,13 @@ export type StdinBufferEventMap = {
61
61
  export declare class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
62
62
  #private;
63
63
  constructor(options?: StdinBufferOptions);
64
+ /**
65
+ * Whether a bracketed paste has started and its end marker has not arrived.
66
+ * Terminal skips its per-read DEC 2004 reassertion meanwhile: the host is
67
+ * evidently in bracketed-paste mode, and a paste delivered one key record
68
+ * per read would otherwise cost one terminal write per pasted key (#15757).
69
+ */
70
+ get pasting(): boolean;
64
71
  process(data: string | Buffer): void;
65
72
  /**
66
73
  * Gate unbracketed raw-paste classification on event-loop responsiveness.
@@ -442,6 +442,16 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
442
442
  this.#pasteByteLimit = options.pasteByteLimit ?? PASTE_MAX_BYTES;
443
443
  }
444
444
 
445
+ /**
446
+ * Whether a bracketed paste has started and its end marker has not arrived.
447
+ * Terminal skips its per-read DEC 2004 reassertion meanwhile: the host is
448
+ * evidently in bracketed-paste mode, and a paste delivered one key record
449
+ * per read would otherwise cost one terminal write per pasted key (#15757).
450
+ */
451
+ get pasting(): boolean {
452
+ return this.#pasteMode;
453
+ }
454
+
445
455
  process(data: string | Buffer): void {
446
456
  // Handle high-byte conversion (for compatibility with parseKeypress)
447
457
  // If buffer has single byte > 127, convert to ESC + (byte - 128)
@@ -578,7 +588,7 @@ export class StdinBuffer extends EventEmitter<StdinBufferEventMap> {
578
588
  const probe = this.#pasteOverlap + chunk;
579
589
  if (probe.indexOf(BRACKETED_PASTE_END) === -1) {
580
590
  this.#pasteChunks.push(chunk);
581
- this.#pasteBytes += chunk.length;
591
+ this.#pasteBytes += Buffer.byteLength(chunk, "utf8");
582
592
  const keep = BRACKETED_PASTE_END.length - 1;
583
593
  this.#pasteOverlap = probe.length > keep ? probe.slice(probe.length - keep) : probe;
584
594
  if (this.#pasteBytes > this.#pasteByteLimit) {
@@ -1,6 +1,13 @@
1
1
  import type { HangulCompatibilityJamoWidth } from "./utils";
2
2
  export * from "./terminal-multiplexer";
3
3
  export { wrapTmuxPassthrough } from "./tmux";
4
+ /**
5
+ * Trusted terminal background when reported; otherwise black for detected dark
6
+ * appearance or white for detected light appearance, using theme auto-detection.
7
+ */
8
+ export declare function getTerminalBackgroundColor(): number;
9
+ /** Store an OSC 11 background, or reset to appearance detection (also for tests). */
10
+ export declare function setTerminalBackgroundColor(color: number | undefined): void;
4
11
  export declare enum ImageProtocol {
5
12
  Kitty = "\u001B_G",
6
13
  Iterm2 = "\u001B]1337;File=",
@@ -339,6 +346,14 @@ export interface ParsedKittyPlacementLine {
339
346
  * callers fall back to writing the line verbatim.
340
347
  */
341
348
  export declare function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacementLine | null;
349
+ /**
350
+ * Rows above its own line that an {@link Image} direct-placement line paints
351
+ * over: the `CUU(rows-1)` of its lead-in, or 0 for any other line. The renderer
352
+ * erases those rows before a SIXEL/iTerm2 block is written again, because
353
+ * those protocols have no delete command and the block's reserved rows are
354
+ * byte-identical between frames, so the diff never rewrites them.
355
+ */
356
+ export declare function directPlacementLift(line: string): number;
342
357
  /**
343
358
  * Rebuild an {@link Image} direct-placement line for the viewport row it is
344
359
  * written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
@@ -397,7 +412,7 @@ export declare function getImageDimensions(base64Data: string, mimeType: string)
397
412
  * thread; `null` when the encode fails. For output that cannot wait for an
398
413
  * off-thread encode.
399
414
  */
400
- export declare function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number): string | null;
415
+ export declare function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number, backgroundRgb?: number): string | null;
401
416
  export declare function renderImage(base64Data: string, imageDimensions: ImageDimensions, options?: ImageRenderOptions): {
402
417
  sequence?: string;
403
418
  lines?: string[];
@@ -12,16 +12,35 @@ import {
12
12
  } from "./kitty-graphics";
13
13
  import {
14
14
  isInsideTerminalMultiplexer,
15
+ isTerminalBackgroundQueryTrusted,
15
16
  routeTerminalMultiplexerNotification,
16
17
  terminalMultiplexerForTerm,
17
18
  terminalMultiplexerSessions,
18
19
  } from "./terminal-multiplexer";
19
- import { resolveTmuxClientTerminalName, wrapTmuxPassthroughIfNeeded } from "./tmux";
20
+ import { resolveTmuxClientTerminalNames, tmuxPaneAllowsPassthrough, wrapTmuxPassthroughIfNeeded } from "./tmux";
21
+ import { detectTerminalBackground } from "./theme/theme";
20
22
  import type { HangulCompatibilityJamoWidth } from "./utils";
21
23
 
22
24
  export * from "./terminal-multiplexer";
23
25
  export { wrapTmuxPassthrough } from "./tmux";
24
26
 
27
+ /** Last valid OSC 11 background, packed as 0xRRGGBB. */
28
+ let terminalBackgroundColor: number | undefined;
29
+
30
+ /**
31
+ * Trusted terminal background when reported; otherwise black for detected dark
32
+ * appearance or white for detected light appearance, using theme auto-detection.
33
+ */
34
+ export function getTerminalBackgroundColor(): number {
35
+ if (isTerminalBackgroundQueryTrusted() && terminalBackgroundColor !== undefined) return terminalBackgroundColor;
36
+ return detectTerminalBackground() === "light" ? 0xffffff : 0x000000;
37
+ }
38
+
39
+ /** Store an OSC 11 background, or reset to appearance detection (also for tests). */
40
+ export function setTerminalBackgroundColor(color: number | undefined): void {
41
+ terminalBackgroundColor = color;
42
+ }
43
+
25
44
  export enum ImageProtocol {
26
45
  Kitty = "\x1b_G",
27
46
  Iterm2 = "\x1b]1337;File=",
@@ -486,9 +505,10 @@ function getFallbackImageProtocol(
486
505
  if (!isTTY) return null;
487
506
  if (terminalId === "vscode" || terminalId === "alacritty") return null;
488
507
  const term = env.TERM?.toLowerCase() ?? "";
489
- if (term.includes("screen") || term.includes("tmux") || term.includes("ghostty")) {
490
- return ImageProtocol.Kitty;
491
- }
508
+ // tmux TERM names the pane, not the attached client's graphics support.
509
+ // Leave unknown clients unselected so the runtime SIXEL probe can run.
510
+ if (env.TMUX || term.includes("tmux")) return null;
511
+ if (term.includes("screen") || term.includes("ghostty")) return ImageProtocol.Kitty;
492
512
  return null;
493
513
  }
494
514
  /**
@@ -557,6 +577,12 @@ export function resolveImageProtocol(
557
577
  ) {
558
578
  return null;
559
579
  }
580
+ // Kitty graphics reach a tmux client only through the DCS passthrough
581
+ // envelope, which tmux drops unless `allow-passthrough` is enabled. A Kitty
582
+ // client identity alone would otherwise send images into the void.
583
+ if (imageProtocol === ImageProtocol.Kitty && env.TMUX && !tmuxPaneAllowsPassthrough(env)) {
584
+ return null;
585
+ }
560
586
  return imageProtocol;
561
587
  }
562
588
 
@@ -678,9 +704,11 @@ export function detectTerminalId(env: NodeJS.ProcessEnv = Bun.env): TerminalId {
678
704
  if (programId) return programId;
679
705
 
680
706
  // tmux >= 3.2 replaces the pane's identity with `TERM_PROGRAM=tmux`.
681
- // Its server still holds the attached client's terminal-type reply.
682
- const clientProgramId = fromProgram(resolveTmuxClientTerminalName(env) ?? undefined);
683
- if (clientProgramId) return clientProgramId;
707
+ // Its server still holds the attached client's terminal-type reply and TERM.
708
+ for (const clientProgram of resolveTmuxClientTerminalNames(env)) {
709
+ const clientProgramId = fromProgram(clientProgram);
710
+ if (clientProgramId) return clientProgramId;
711
+ }
684
712
 
685
713
  if (TERM?.toLowerCase().includes("ghostty")) return "ghostty";
686
714
  if (TERM && caseEq(TERM, "monstar")) return "monstar";
@@ -971,6 +999,21 @@ export function parseKittyDirectPlacementLine(line: string): ParsedKittyPlacemen
971
999
  };
972
1000
  }
973
1001
 
1002
+ /** `ESC 7` + `CUU(n)` lead-in of an {@link Image} direct-placement line. */
1003
+ const DIRECT_PLACEMENT_LIFT = /^\x1b7\x1b\[(\d+)A/;
1004
+
1005
+ /**
1006
+ * Rows above its own line that an {@link Image} direct-placement line paints
1007
+ * over: the `CUU(rows-1)` of its lead-in, or 0 for any other line. The renderer
1008
+ * erases those rows before a SIXEL/iTerm2 block is written again, because
1009
+ * those protocols have no delete command and the block's reserved rows are
1010
+ * byte-identical between frames, so the diff never rewrites them.
1011
+ */
1012
+ export function directPlacementLift(line: string): number {
1013
+ const m = DIRECT_PLACEMENT_LIFT.exec(line);
1014
+ return m ? Number(m[1]) : 0;
1015
+ }
1016
+
974
1017
  /**
975
1018
  * Rebuild an {@link Image} direct-placement line for the viewport row it is
976
1019
  * written at. The component-rendered line encodes `CUU(rows-1)`, which clamps
@@ -1248,9 +1291,14 @@ export function getImageDimensions(base64Data: string, mimeType: string): ImageD
1248
1291
  * thread; `null` when the encode fails. For output that cannot wait for an
1249
1292
  * off-thread encode.
1250
1293
  */
1251
- export function encodeSixelNow(base64Data: string, widthPx: number, heightPx: number): string | null {
1294
+ export function encodeSixelNow(
1295
+ base64Data: string,
1296
+ widthPx: number,
1297
+ heightPx: number,
1298
+ backgroundRgb = getTerminalBackgroundColor(),
1299
+ ): string | null {
1252
1300
  try {
1253
- return encodeSixel(new Uint8Array(Buffer.from(base64Data, "base64")), widthPx, heightPx);
1301
+ return encodeSixel(new Uint8Array(Buffer.from(base64Data, "base64")), widthPx, heightPx, backgroundRgb);
1254
1302
  } catch {
1255
1303
  return null;
1256
1304
  }
@@ -1309,25 +1357,15 @@ export function renderImage(
1309
1357
  }
1310
1358
 
1311
1359
  if (TERMINAL.imageProtocol === ImageProtocol.Sixel) {
1312
- // SIXEL encodes in 6-pixel vertical bands. A height that is not a
1313
- // multiple of 6 is padded with transparent rows, but the terminal
1314
- // still allocates cell rows for the padded height. When the padded
1315
- // height crosses a cell boundary the terminal uses one more row
1316
- // than fit.rows, so the next line of content overwrites the bottom
1317
- // of the image — a visible slice stripped from the image. Round the
1318
- // encode height DOWN to the largest multiple of 6 that fits within
1319
- // the requested row budget, so the band boundary aligns without
1320
- // padding and the reserved row count never exceeds fit.rows. Scale
1321
- // the width by the same ratio so resize_exact preserves the aspect
1322
- // ratio instead of squashing the image vertically.
1323
- const rawHeightPx = Math.max(1, fit.rows * cellDims.heightPx);
1324
- const targetHeightPx = Math.max(6, Math.floor(rawHeightPx / 6) * 6);
1325
- const heightScale = targetHeightPx / rawHeightPx;
1326
- const targetWidthPx = Math.max(1, Math.round(fit.columns * cellDims.widthPx * heightScale));
1327
- const rows = Math.max(1, Math.ceil(targetHeightPx / cellDims.heightPx));
1328
- const sequence = options.sixel
1329
- ? options.sixel(targetWidthPx, targetHeightPx)
1330
- : encodeSixelNow(base64Data, targetWidthPx, targetHeightPx);
1360
+ // Encode the cell box itself, so a raster drawn at the cell size
1361
+ // (SvgFigure) is placed 1:1 instead of resampled. The encoder drops a
1362
+ // partial last 6px band rather than letting the terminal pad it into
1363
+ // the next row; only a box under one band (sub-6px cells) still takes
1364
+ // the rows of a whole one.
1365
+ const heightPx = fit.rows * cellDims.heightPx;
1366
+ const widthPx = fit.columns * cellDims.widthPx;
1367
+ const rows = heightPx < 6 ? Math.ceil(6 / cellDims.heightPx) : fit.rows;
1368
+ const sequence = options.sixel ? options.sixel(widthPx, heightPx) : encodeSixelNow(base64Data, widthPx, heightPx);
1331
1369
  if (sequence === null) return null;
1332
1370
  // Undefined while the provider's encode is pending: the rows stay reserved.
1333
1371
  return { sequence, rows };
@@ -38,6 +38,7 @@ declare const registry: readonly [{
38
38
  readonly passthrough: typeof import("./tmux").wrapTmuxPassthrough;
39
39
  readonly settleMs: 100;
40
40
  };
41
+ readonly gatesBracketedPaste: true;
41
42
  readonly expandsRepPadding: true;
42
43
  readonly expiresSynchronizedOutput: true;
43
44
  readonly altRestoreEndsSynchronizedOutput: true;
@@ -100,6 +101,12 @@ export declare function hasTerminalMultiplexerSession(multiplexer: TerminalMulti
100
101
  * order. Nested sessions all appear; TERM is not consulted.
101
102
  */
102
103
  export declare function terminalMultiplexerSessions(env?: NodeJS.ProcessEnv): TerminalMultiplexerModule<TerminalMultiplexer>[];
104
+ /**
105
+ * Whether OSC 11 background replies are trusted on this platform. Any active
106
+ * session with `backgroundQueryBrokenOn` matching the platform makes the entire
107
+ * path untrusted; TERM-only classification does not identify an active session.
108
+ */
109
+ export declare function isTerminalBackgroundQueryTrusted(env?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
103
110
  /**
104
111
  * The multiplexer TERM names, whether or not its session markers survived.
105
112
  * This is classification's TERM fallback; session markers are not consulted.
@@ -67,6 +67,20 @@ export function terminalMultiplexerSessions(
67
67
  return TERMINAL_MULTIPLEXERS.filter(multiplexer => multiplexer.isInside(env));
68
68
  }
69
69
 
70
+ /**
71
+ * Whether OSC 11 background replies are trusted on this platform. Any active
72
+ * session with `backgroundQueryBrokenOn` matching the platform makes the entire
73
+ * path untrusted; TERM-only classification does not identify an active session.
74
+ */
75
+ export function isTerminalBackgroundQueryTrusted(
76
+ env: NodeJS.ProcessEnv = Bun.env,
77
+ platform: NodeJS.Platform = process.platform,
78
+ ): boolean {
79
+ return !TERMINAL_MULTIPLEXERS.some(
80
+ multiplexer => multiplexer.isInside(env) && multiplexer.backgroundQueryBrokenOn?.includes(platform),
81
+ );
82
+ }
83
+
70
84
  /**
71
85
  * The multiplexer TERM names, whether or not its session markers survived.
72
86
  * This is classification's TERM fallback; session markers are not consulted.
package/src/terminal.d.ts CHANGED
@@ -117,7 +117,7 @@ export type GlyphProtocolReportHandler = (supported: boolean) => void;
117
117
  /**
118
118
  * Outcome of the Tern Surface Protocol `hello` probe: the terminal's reply,
119
119
  * or null when it answered the DA1 sentinel first (no TSP) or the probe was
120
- * skipped (`PI_TUI_NATIVE=0`, multiplexers).
120
+ * skipped (`PI_TUI_NATIVE=0`, multiplexers, devenv's reloading shell).
121
121
  */
122
122
  export type TspHelloHandler = (hello: TspHello | null) => void;
123
123
  /**
@@ -181,6 +181,7 @@ export interface Terminal {
181
181
  get kittyEnableSequence(): string | null;
182
182
  readonly keyboardEnhancementEnterSequence?: string | null;
183
183
  readonly keyboardEnhancementExitSequence?: string | null;
184
+ readonly keyboardEnhancementRestoreSequence?: string | null;
184
185
  moveBy(lines: number): void;
185
186
  hideCursor(force?: boolean): void;
186
187
  showCursor(force?: boolean): void;
@@ -283,9 +284,11 @@ export interface ProcessTerminalOptions {
283
284
  conpty?: boolean;
284
285
  /**
285
286
  * Whether stdin is a native Windows console handle (win32, not WSL), whose
286
- * console host answers `CSI ? 9001 h` with win32-input-mode key records.
287
- * Defaults to `process.platform === "win32"`; only consulted when `conpty`
288
- * is also true.
287
+ * console host is certain to answer `CSI ? 9001 h` with win32-input-mode key
288
+ * records. When false (WSL), the keyboard fallback requests modifyOtherKeys
289
+ * alongside win32-input-mode, since a WSL pty may not be relayed by a console
290
+ * host. Defaults to `process.platform === "win32"`; only consulted when
291
+ * `conpty` is also true.
289
292
  */
290
293
  nativeWindowsConsole?: boolean;
291
294
  }
@@ -298,6 +301,7 @@ export declare class ProcessTerminal implements Terminal {
298
301
  get kittyProtocolActive(): boolean;
299
302
  get kittyEnableSequence(): string | null;
300
303
  get keyboardEnhancementEnterSequence(): string | null;
304
+ get keyboardEnhancementRestoreSequence(): string | null;
301
305
  get keyboardEnhancementExitSequence(): string | null;
302
306
  get appearance(): TerminalAppearance | undefined;
303
307
  onAppearanceChange(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): void;