@oh-my-pi/pi-tui 18.14.2 → 18.16.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 (93) hide show
  1. package/CHANGELOG.md +64 -50
  2. package/README.md +1 -1
  3. package/dist/types/app-keybindings.d.ts +5 -0
  4. package/dist/types/apps/session-picker.d.ts +5 -3
  5. package/dist/types/chat/bash-execution.d.ts +4 -1
  6. package/dist/types/chat/eval-execution.d.ts +2 -0
  7. package/dist/types/chat/execution-shared.d.ts +14 -8
  8. package/dist/types/chat/transcript-render-helpers.d.ts +8 -0
  9. package/dist/types/chrome/dynamic-border.d.ts +6 -0
  10. package/dist/types/chrome/transcript-status.d.ts +2 -2
  11. package/dist/types/components/editor.d.ts +15 -6
  12. package/dist/types/components/loader.d.ts +2 -0
  13. package/dist/types/keybinding-matchers.d.ts +7 -0
  14. package/dist/types/overlays/btw-history-panel.d.ts +13 -0
  15. package/dist/types/overlays/btw-history.d.ts +24 -0
  16. package/dist/types/overlays/btw-inject.d.ts +22 -0
  17. package/dist/types/overlays/btw-panel.d.ts +7 -0
  18. package/dist/types/overlays/hook-editor.d.ts +2 -0
  19. package/dist/types/overlays/jobs-panel.d.ts +4 -0
  20. package/dist/types/overlays/session-selector.d.ts +24 -3
  21. package/dist/types/render/render-utils.d.ts +21 -1
  22. package/dist/types/status-line/startup.d.ts +0 -15
  23. package/dist/types/terminal-capabilities.d.ts +1 -1
  24. package/dist/types/theme/symbols.d.ts +3 -1
  25. package/dist/types/theme/theme-class.d.ts +1 -0
  26. package/dist/types/tools/grouped-file-output.d.ts +76 -8
  27. package/dist/types/tools/hashline-format.d.ts +5 -0
  28. package/dist/types/tools/native-view.d.ts +2 -0
  29. package/dist/types/tools/output-meta.d.ts +27 -0
  30. package/dist/types/tools/read.d.ts +10 -0
  31. package/dist/types/tools/wait.d.ts +2 -0
  32. package/dist/types/utils.d.ts +4 -0
  33. package/package.json +9 -9
  34. package/src/app-keybindings.ts +5 -0
  35. package/src/apps/session-picker.ts +7 -3
  36. package/src/chat/bash-execution.ts +20 -9
  37. package/src/chat/chat-transcript-builder.ts +5 -2
  38. package/src/chat/eval-execution.ts +14 -7
  39. package/src/chat/execution-shared.ts +39 -18
  40. package/src/chat/read-tool-group.ts +56 -36
  41. package/src/chat/transcript-render-helpers.ts +52 -6
  42. package/src/chrome/dynamic-border.ts +28 -2
  43. package/src/chrome/transcript-status.ts +5 -3
  44. package/src/components/editor.ts +123 -24
  45. package/src/components/key-value-list.ts +9 -9
  46. package/src/components/loader.ts +7 -0
  47. package/src/components/section.ts +1 -9
  48. package/src/components/table.ts +1 -9
  49. package/src/keybinding-matchers.ts +13 -0
  50. package/src/overlays/btw-history-panel.ts +98 -14
  51. package/src/overlays/btw-history.ts +65 -0
  52. package/src/overlays/btw-inject.ts +91 -0
  53. package/src/overlays/btw-panel.ts +53 -12
  54. package/src/overlays/hook-editor.ts +6 -0
  55. package/src/overlays/jobs-panel.ts +45 -19
  56. package/src/overlays/model-browser.ts +65 -8
  57. package/src/overlays/session-selector.ts +131 -26
  58. package/src/overlays/usage-dashboard.ts +2 -2
  59. package/src/render/code-cell.ts +1 -1
  60. package/src/render/file-list.ts +4 -7
  61. package/src/render/render-utils.ts +36 -6
  62. package/src/setup/scenes/glyph.ts +1 -1
  63. package/src/status-line/startup.ts +36 -70
  64. package/src/terminal-capabilities.ts +5 -0
  65. package/src/theme/symbols.ts +16 -9
  66. package/src/theme/theme-class.ts +3 -0
  67. package/src/tmux.ts +1 -0
  68. package/src/tools/agent-tree.ts +13 -11
  69. package/src/tools/ask.ts +13 -6
  70. package/src/tools/ast-edit.ts +48 -93
  71. package/src/tools/ast-grep.ts +22 -66
  72. package/src/tools/bash.ts +7 -0
  73. package/src/tools/default-renderer.ts +1 -1
  74. package/src/tools/edit.ts +50 -43
  75. package/src/tools/fetch.ts +2 -1
  76. package/src/tools/find.ts +6 -3
  77. package/src/tools/github.ts +14 -46
  78. package/src/tools/glob.ts +2 -1
  79. package/src/tools/grep.ts +43 -287
  80. package/src/tools/grouped-file-output.ts +250 -64
  81. package/src/tools/hashline-format.ts +5 -0
  82. package/src/tools/json-tree.ts +1 -5
  83. package/src/tools/lsp.ts +53 -25
  84. package/src/tools/native-view.ts +11 -5
  85. package/src/tools/output-meta.ts +68 -4
  86. package/src/tools/proc-render.ts +3 -3
  87. package/src/tools/read.ts +14 -0
  88. package/src/tools/task.ts +34 -77
  89. package/src/tools/vibe.ts +4 -4
  90. package/src/tools/wait.ts +8 -5
  91. package/src/tools/write.ts +17 -27
  92. package/src/tools/yield.ts +1 -1
  93. package/src/utils.ts +10 -0
@@ -1,18 +1,3 @@
1
- /**
2
- * Status line for the speculative first frame, before any session exists.
3
- *
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
- *
10
- * @example
11
- * ```ts ignore
12
- * const statusLine = createStartupStatusLine(cached.statusLine);
13
- * statusLine.attachToEditor(editor, getComposerStyle(shape));
14
- * ```
15
- */
16
1
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
17
2
  import type { Model } from "@oh-my-pi/pi-catalog/types";
18
3
  import { StatusLineComponent } from "./component.js";
@@ -18,7 +18,7 @@ export declare enum NotifyProtocol {
18
18
  Osc99 = "\u001B]99;;",
19
19
  Osc9 = "\u001B]9;"
20
20
  }
21
- export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "monstar" | "base" | "trueColor";
21
+ export type TerminalId = "kitty" | "ghostty" | "wezterm" | "iterm2" | "vscode" | "alacritty" | "warp" | "orca" | "otty" | "rio" | "tern" | "monstar" | "rex" | "base" | "trueColor";
22
22
  /** Terminal capability details used for rendering and protocol selection. */
23
23
  export declare class TerminalInfo {
24
24
  readonly id: TerminalId;
@@ -2,7 +2,9 @@ export type SymbolPreset = "unicode" | "nerd" | "ascii";
2
2
  /**
3
3
  * All available symbol keys organized by category.
4
4
  */
5
- export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted" | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "context.content" | "context.messages" | "context.buffer" | "context.free" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "icon.vimReplace" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
5
+ export type SymbolKey = "status.success" | "status.error" | "status.warning" | "status.info" | "status.pending" | "status.disabled" | "status.enabled" | "status.running" | "status.shadowed" | "status.aborted"
6
+ /** @deprecated Alias of `status.success`; kept so extensions and theme overrides that name it keep working. */
7
+ | "status.done" | "nav.cursor" | "nav.selected" | "nav.expand" | "nav.collapse" | "nav.back" | "tree.branch" | "tree.last" | "tree.vertical" | "tree.horizontal" | "tree.hook" | "progress.filled" | "progress.empty" | "context.speculation" | "context.compaction" | "context.content" | "context.messages" | "context.buffer" | "context.free" | "boxRound.topLeft" | "boxRound.topRight" | "boxRound.bottomLeft" | "boxRound.bottomRight" | "boxRound.horizontal" | "boxRound.vertical" | "boxDotted.horizontal" | "boxDotted.vertical" | "boxSharp.topLeft" | "boxSharp.topRight" | "boxSharp.bottomLeft" | "boxSharp.bottomRight" | "boxSharp.horizontal" | "boxSharp.vertical" | "boxSharp.cross" | "boxSharp.teeDown" | "boxSharp.teeUp" | "boxSharp.teeRight" | "boxSharp.teeLeft" | "sep.powerline" | "sep.powerlineThin" | "sep.powerlineLeft" | "sep.powerlineRight" | "sep.powerlineThinLeft" | "sep.powerlineThinRight" | "sep.powerlineCapLeft" | "sep.block" | "sep.space" | "sep.asciiLeft" | "sep.asciiRight" | "sep.dot" | "sep.slash" | "sep.pipe" | "icon.model" | "icon.plan" | "icon.prewalk" | "icon.goal" | "icon.pause" | "icon.loop" | "icon.folder" | "icon.worktree" | "icon.search" | "icon.scratchFolder" | "icon.file" | "icon.git" | "icon.branch" | "icon.pr" | "icon.pin" | "icon.tokens" | "icon.context" | "icon.cost" | "icon.subscription" | "icon.advisor" | "icon.advisorClosed" | "icon.time" | "icon.omp" | "icon.ghost" | "icon.agents" | "icon.job" | "icon.cache" | "icon.cacheMiss" | "icon.input" | "icon.output" | "icon.throughput" | "icon.intelligence" | "icon.host" | "icon.session" | "icon.package" | "icon.warning" | "icon.rewind" | "icon.auto" | "icon.fast" | "icon.extensionSkill" | "icon.extensionTool" | "icon.extensionSlashCommand" | "icon.extensionMcp" | "icon.extensionRule" | "icon.extensionHook" | "icon.extensionPrompt" | "icon.extensionContextFile" | "icon.extensionInstruction" | "icon.vimNormal" | "icon.vimInsert" | "icon.vimVisual" | "icon.vimVisualLine" | "icon.vimReplace" | "cmd.action" | "cmd.prompt" | "cmd.extension" | "cmd.settings" | "cmd.gear" | "cmd.shield" | "cmd.wave" | "cmd.compass" | "cmd.inbox" | "cmd.swap" | "cmd.expand" | "cmd.computer" | "cmd.eye" | "cmd.todo" | "cmd.stats" | "cmd.news" | "cmd.keyboard" | "cmd.export" | "cmd.clipboard" | "cmd.share" | "cmd.broadcast" | "cmd.globe" | "cmd.copy" | "cmd.plus" | "cmd.restart" | "cmd.eraser" | "cmd.trash" | "cmd.compress" | "cmd.vibrate" | "cmd.handoff" | "cmd.history" | "cmd.question" | "cmd.rocket" | "cmd.stethoscope" | "cmd.redo" | "cmd.bug" | "cmd.memory" | "cmd.pencil" | "cmd.folderMove" | "cmd.folderPlus" | "cmd.folderMinus" | "cmd.hammer" | "cmd.power" | "cmd.cart" | "icon.mic" | "icon.camera" | "thinking.minimal" | "thinking.low" | "thinking.medium" | "thinking.high" | "thinking.xhigh" | "thinking.max" | "thinking.autoPending" | "checkbox.checked" | "checkbox.unchecked" | "radio.selected" | "radio.unselected" | "format.bullet" | "format.dash" | "format.bracketLeft" | "format.bracketRight" | "md.quoteBorder" | "md.hrChar" | "md.bullet" | "md.colorSwatch" | "advisor.rail" | "skill.rail" | "btw.rail" | "lang.default" | "lang.typescript" | "lang.javascript" | "lang.python" | "lang.rust" | "lang.go" | "lang.java" | "lang.c" | "lang.cpp" | "lang.csharp" | "lang.ruby" | "lang.julia" | "lang.php" | "lang.swift" | "lang.kotlin" | "lang.shell" | "lang.html" | "lang.css" | "lang.json" | "lang.yaml" | "lang.markdown" | "lang.sql" | "lang.docker" | "lang.lua" | "lang.text" | "lang.env" | "lang.toml" | "lang.xml" | "lang.ini" | "lang.conf" | "lang.log" | "lang.csv" | "lang.tsv" | "lang.image" | "lang.pdf" | "lang.archive" | "lang.binary" | "chip.image" | "chip.video" | "chip.paste" | "tab.appearance" | "tab.model" | "tab.interaction" | "tab.context" | "tab.files" | "tab.shell" | "tab.tools" | "tab.memory" | "tab.tasks" | "tab.providers" | "tool.write" | "tool.edit" | "tool.bash" | "tool.ssh" | "tool.lsp" | "tool.gh" | "tool.webSearch" | "tool.exa" | "tool.browser" | "tool.eval" | "tool.debug" | "tool.mcp" | "tool.job" | "tool.launch" | "tool.task" | "tool.todo" | "tool.memory" | "tool.ask" | "tool.resolve" | "tool.review" | "tool.goal" | "tool.irc" | "tool.delete" | "tool.move" | "key.ctrl" | "key.ctrlMac" | "key.shift" | "key.alt" | "key.altMac" | "key.super" | "key.superMac" | "key.joiner" | "key.enter" | "key.esc" | "key.tab" | "key.backspace" | "key.delete" | "key.space" | "key.up" | "key.down" | "key.left" | "key.right";
6
8
  export type SymbolMap = Record<SymbolKey, string>;
7
9
  /**
8
10
  * Icon vocabulary for slash-command autocomplete type indicators. Each name
@@ -129,6 +129,7 @@ export declare class Theme {
129
129
  running: string;
130
130
  shadowed: string;
131
131
  aborted: string;
132
+ /** @deprecated Alias of `success`. */
132
133
  done: string;
133
134
  };
134
135
  get nav(): {
@@ -52,22 +52,35 @@ export interface FileMatchSection {
52
52
  * Files with no model lines are omitted.
53
53
  */
54
54
  export declare function formatFileMatches(files: string[], grouped: boolean, renderFile: (filePath: string) => FileMatchSection): GroupedFilesOutput;
55
- /** Per-line classification of grouped output, used by renderers for hyperlinks. */
55
+ /** Whether a row of grouped output is a code-frame row the search matched (`*12│…`). */
56
+ export declare function isMatchRow(line: string): boolean;
57
+ /** Per-line classification of grouped output, used by renderers for file rows and hyperlinks. */
56
58
  export interface GroupedLineContext {
57
59
  /** Directory header, file header, or any non-header body/content line. */
58
60
  kind: "dir" | "file" | "content";
59
61
  /** Number of leading `#` for headers; 0 for content lines. */
60
62
  depth: number;
61
- /** Resolved absolute path of the dir/file a header points at (when resolvable). */
63
+ /** Resolved absolute path of the file a header points at (when resolvable). */
62
64
  headerPath?: string;
65
+ /**
66
+ * For file headers, the full display path: the folded directory chain joined
67
+ * with the name, without `#` markers or the hashline tag.
68
+ */
69
+ displayPath?: string;
70
+ /** For file headers, a trailing parenthesized note such as `(2 replacements)`. */
71
+ suffix?: string;
63
72
  /** For content lines, the absolute path of the owning file (line hyperlinks). */
64
73
  filePath?: string;
65
- /** Header is an internal/url-like target the caller resolves itself. */
66
- isUrl?: boolean;
74
+ /**
75
+ * The internal or url-like target, as written, that a header names or a
76
+ * content line belongs to; the caller resolves it (no filesystem path).
77
+ */
78
+ url?: string;
67
79
  }
68
80
  /**
69
81
  * Walk grouped output lines, tracking a directory stack keyed by header depth, so
70
- * each header and body line can be linked back to its absolute filesystem path.
82
+ * each header and body line can be linked back to its absolute filesystem path
83
+ * and each file header to its full display path.
71
84
  * Reconstruction is stack-based (not per-blank-group) so nested directory headers
72
85
  * resolve correctly across the whole output.
73
86
  *
@@ -76,9 +89,64 @@ export interface GroupedLineContext {
76
89
  * formatted relative to cwd regardless of the (sub)directory the search was
77
90
  * scoped to. `fileScope` is the initial owning file for body lines that appear
78
91
  * before any header (single-file scopes have no `#` headers); it defaults to
79
- * `headerBase` and should be passed the scoped file's absolute path.
92
+ * `headerBase` and should be passed the scoped file's absolute path. A URL base
93
+ * or scope (`local://…`) is no filesystem path: headerless lines belong to the
94
+ * URL, and headers under a URL base get no path.
80
95
  */
81
96
  export declare function classifyGroupedLines(lines: readonly string[], headerBase: string | undefined, fileScope?: string | undefined): GroupedLineContext[];
97
+ /**
98
+ * Line groups with one file per group, for terminal tree lists: `# dir/`
99
+ * header rows are dropped (each file row carries its full path), and every
100
+ * file row starts a group of its own, so each file gets its own branch instead
101
+ * of hanging under the previous file of its directory. Rows before a group's
102
+ * first file row (notes, a single-file scope's matches) keep their group.
103
+ */
104
+ export declare function groupsByFile(groups: readonly (readonly number[])[], contexts: readonly GroupedLineContext[]): number[][];
105
+ /** Details fields a grouped-output tool records for links: the display base paths and resolved URL targets. */
106
+ export interface GroupedLinkDetails {
107
+ cwd?: string;
108
+ searchPath?: string;
109
+ displayTargets?: Record<string, string>;
110
+ }
111
+ /**
112
+ * Link targets for the rows of sanitized grouped output: a file row's file, a
113
+ * body row's owning file, and for an internal URL the file `displayTargets`
114
+ * resolves it to. Destinations keep the original path bytes: while the raw
115
+ * `displayContent` still aligns line-for-line with the sanitized `displayText`,
116
+ * a row takes its raw classification's target. Raw text never links a row the
117
+ * sanitized output leaves unlinked.
118
+ */
119
+ export declare function groupedLinkTargets(contexts: readonly GroupedLineContext[], displayText: string, details: GroupedLinkDetails | undefined, rawDetails: unknown): (index: number) => string | undefined;
120
+ /**
121
+ * A memoized styler for the rows of grouped output, so a collapsed preview
122
+ * styles only the rows it shows. A file row is its full path (dim directory,
123
+ * name in {@link FILE_NAME_COLOR}; a URL whole in that color) and any dim
124
+ * suffix, linked to `linkTarget`'s file or, for an unresolved URL, the URL.
125
+ * Body rows come from `styleBody`, tabs expanded; with `linkLines` a numbered
126
+ * code-frame row links to its line in the owning file.
127
+ */
128
+ export declare function createGroupedRowStyler(lines: readonly string[], contexts: readonly GroupedLineContext[], theme: Theme, options: {
129
+ linkTarget: (index: number) => string | undefined;
130
+ styleBody: (index: number) => string;
131
+ linkLines?: boolean;
132
+ }): (index: number) => string;
133
+ /**
134
+ * Fit line groups (one file each) into `budget` rows, the `… N more` summary row
135
+ * included: whole groups first, then the next group cut short when it can still
136
+ * show a row under its file row (the first group always shows its head). The
137
+ * summary counts the `unit`s left out, the rows `isUnit` accepts, out of `total`
138
+ * when the caller knows it; with none left out it counts hidden lines. `gap`
139
+ * rows separate shown groups (framed lists put a blank line between files).
140
+ */
141
+ export declare function fitGroupsToBudget(groups: readonly (readonly number[])[], budget: number, options: {
142
+ isUnit: (index: number) => boolean;
143
+ unit: string;
144
+ total?: number;
145
+ gap?: number;
146
+ }): {
147
+ shown: (readonly number[])[];
148
+ summary: string;
149
+ };
82
150
  /**
83
151
  * Split line indices into blank-line-separated groups, mirroring
84
152
  * `splitGroupsByBlankLine`: when any blank line is present, break on runs of
@@ -108,8 +176,8 @@ export type GroupedOutputEvent = {
108
176
  };
109
177
  /**
110
178
  * Walk grouped output (`# dir/`, `## file.ts (suffix)`, body lines), resolving
111
- * each file header to its full display path (the folded directory chain
112
- * joined with the name) and dropping blank separator lines.
179
+ * each file header to its full display path (see {@link classifyGroupedLines})
180
+ * and dropping blank separator lines.
113
181
  */
114
182
  export declare function walkGroupedOutput(lines: readonly string[]): Generator<GroupedOutputEvent>;
115
183
  /** One source line for {@link numberedCode}: its number (`null` for an elision marker, which adds no line), text and match mark. */
@@ -6,6 +6,11 @@ export declare const HL_FILE_SUFFIX = "]";
6
6
  export declare const HL_FILE_HASH_SEP = "#";
7
7
  /** Number of characters in a displayed file hash. */
8
8
  export declare const HL_FILE_HASH_LENGTH = 4;
9
+ /**
10
+ * A trailing `#TAG` file hash exactly as the hashline store writes it (uppercase
11
+ * hex), so a name's own `#dead` or `#v2` ending survives stripping.
12
+ */
13
+ export declare const HASHLINE_TAG_SUFFIX_RE: RegExp;
9
14
  /** Keyword identifying a file move operation. */
10
15
  export declare const HL_MOVE_KEYWORD = "MV";
11
16
  /** Keyword identifying a file removal operation. */
@@ -8,6 +8,8 @@ import type { NativeToolHead, NativeToolView, ToolRenderResult } from "./rendere
8
8
  export declare function displayPath(filePath: string): string;
9
9
  /** Configured file link for a head's path target, when the path is absolute. */
10
10
  export declare function fileHref(filePath: string | undefined): string | undefined;
11
+ /** Link for a head target that is a web URL (`www.` gains `https://`), else undefined. */
12
+ export declare function urlHref(target: string | undefined): string | undefined;
11
13
  /** Diff stats as one head `meta` string, `+8 −1` (U+2212 minus), or undefined when nothing changed. */
12
14
  export declare function diffStatsMeta(added: number, removed: number): string | undefined;
13
15
  /**
@@ -100,8 +100,21 @@ export interface LimitsMeta {
100
100
  last: number;
101
101
  };
102
102
  selectorBase?: string;
103
+ /** The first cut line exceeds what one `:raw` read returns; that read stops at this many bytes. */
104
+ rawByteCap?: number;
103
105
  };
104
106
  }
107
+ /**
108
+ * The bash minimizer rewrote this output: `filter` is the native filter label
109
+ * (`chain` for a minimized `&&`/`;` chain), the byte counts are the raw capture
110
+ * and the minimized text, and `artifactId` holds the raw capture.
111
+ */
112
+ export interface MinimizedMeta {
113
+ filter: string;
114
+ inputBytes: number;
115
+ outputBytes: number;
116
+ artifactId: string;
117
+ }
105
118
  /**
106
119
  * Structured metadata for tool outputs.
107
120
  */
@@ -114,6 +127,7 @@ export interface OutputMeta {
114
127
  pagedSource?: true;
115
128
  diagnostics?: DiagnosticMeta;
116
129
  limits?: LimitsMeta;
130
+ minimized?: MinimizedMeta;
117
131
  }
118
132
  /**
119
133
  * Reformat pre-formatted diagnostic messages into a multi-level, prefix-folded
@@ -136,6 +150,13 @@ export declare function stripRawOutputArtifactNotice(text: string): {
136
150
  text: string;
137
151
  artifactId?: string;
138
152
  };
153
+ /** Whether `line` is the column-cap notice {@link formatOutputNotice} emits (`[Some lines truncated to 768 chars…]`). */
154
+ export declare function isColumnTruncationNotice(line: string): boolean;
155
+ /**
156
+ * Whether the bracketed notice `line` carries the column-cap clause: alone, or after the parts
157
+ * {@link formatOutputNotice} joins before it (`[Showing lines 1-4 of 5. … Some lines truncated to 768 chars…]`).
158
+ */
159
+ export declare function hasColumnTruncationClause(line: string): boolean;
139
160
  /** Remove a trailing generated output notice when metadata is unavailable. */
140
161
  export declare function stripGeneratedOutputNotice(text: string): string;
141
162
  /** Format truncation ranges and recovery hints. */
@@ -147,6 +168,12 @@ export declare function formatTruncationMetaNotice(truncation: TruncationMeta, s
147
168
  export declare function formatStyledArtifactReference(artifactId: string, theme: Theme): string;
148
169
  /** Describe an incomplete artifact capture. */
149
170
  export declare function formatArtifactErrorNotice(error: OutputArtifactError): string;
171
+ /**
172
+ * `git 120.4KB → 3.2KB`: the minimizer filter that ran and the bytes it took the output from and to.
173
+ * Native labels are short words; a rehydrated session or extension may carry any text, so the label
174
+ * is sanitized to one plain line, its home paths shortened to `~`, and width-bounded.
175
+ */
176
+ export declare function formatMinimizedMeta(minimized: MinimizedMeta): string;
150
177
  /**
151
178
  * Format notices from OutputMeta for LLM consumption.
152
179
  * Returns empty string if no notices needed.
@@ -111,6 +111,16 @@ export declare function splitInternalUrlSel(rawPath: string, specOf?: (scheme: s
111
111
  path: string;
112
112
  sel?: string;
113
113
  };
114
+ /**
115
+ * Split a filesystem read target from its query (`data.json?q=.items` →
116
+ * `data.json`, `?q=.items`), the display twin of the read tool's JSON, SQLite
117
+ * and image-question parsers. URLs own their query string and stay whole, as
118
+ * does a `?` inside a file name.
119
+ */
120
+ export declare function splitReadQuery(readPath: string): {
121
+ path: string;
122
+ query?: string;
123
+ };
114
124
  /** Recognize HTTP URLs and www-prefixed external read targets. */
115
125
  export declare function isReadableUrlPath(value: string): boolean;
116
126
  /** Return the first selected line when every range has valid one-based bounds. */
@@ -17,6 +17,8 @@ export interface JobSnapshot {
17
17
  type: "bash" | "task" | "eval";
18
18
  status: "running" | "completed" | "failed" | "cancelled";
19
19
  label: string;
20
+ /** What the job is for, in the model's words (a process job's `label` is its command line). */
21
+ intent?: string;
20
22
  durationMs: number;
21
23
  /** Process exit status when the job reports one. */
22
24
  exitCode?: number;
@@ -36,6 +36,10 @@ export declare function truncateToWidth(text: string, maxWidth: number, ellipsis
36
36
  export declare function wrapTextWithAnsi(text: string, width: number): string[];
37
37
  export declare function extractSegments(line: string, beforeEnd: number, afterStart: number, afterLen: number, strictAfter: boolean): ExtractSegmentsResult;
38
38
  export declare function replaceTabs(text: string): string;
39
+ /** Tabs expanded and line breaks collapsed to spaces, for text confined to one rendered row. */
40
+ export declare function singleLine(text: string): string;
41
+ /** A non-negative integer cell width; non-finite input becomes `fallback`. */
42
+ export declare function finiteWidth(width: number, fallback?: number): number;
39
43
  /**
40
44
  * Returns a string of n spaces. Uses a pre-allocated buffer for efficiency.
41
45
  */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-tui",
4
- "version": "18.14.2",
4
+ "version": "18.16.0",
5
5
  "description": "Terminal User Interface library with differential rendering for efficient text-based applications",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -41,14 +41,14 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.14.2",
45
- "@oh-my-pi/pi-agent-core": "18.14.2",
46
- "@oh-my-pi/pi-ai": "18.14.2",
47
- "@oh-my-pi/pi-catalog": "18.14.2",
48
- "@oh-my-pi/pi-natives": "18.14.2",
49
- "@oh-my-pi/pi-utils": "18.14.2",
50
- "@oh-my-pi/pi-wire": "18.14.2",
51
- "@oh-my-pi/snapcompact": "18.14.2"
44
+ "@oh-my-pi/omptype": "18.16.0",
45
+ "@oh-my-pi/pi-agent-core": "18.16.0",
46
+ "@oh-my-pi/pi-ai": "18.16.0",
47
+ "@oh-my-pi/pi-catalog": "18.16.0",
48
+ "@oh-my-pi/pi-natives": "18.16.0",
49
+ "@oh-my-pi/pi-utils": "18.16.0",
50
+ "@oh-my-pi/pi-wire": "18.16.0",
51
+ "@oh-my-pi/snapcompact": "18.16.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -44,6 +44,7 @@ interface AppKeybindings {
44
44
  "app.message.followUp": true;
45
45
  "app.retry": true;
46
46
  "app.message.dequeue": true;
47
+ "app.btw.inject": true;
47
48
  "app.clipboard.pasteImage": true;
48
49
  "app.clipboard.pasteTextRaw": true;
49
50
  "app.clipboard.copyLine": true;
@@ -182,6 +183,10 @@ export const KEYBINDINGS = {
182
183
  defaultKeys: ["alt+up", "shift+up"],
183
184
  description: "Dequeue message",
184
185
  },
186
+ "app.btw.inject": {
187
+ defaultKeys: "alt+i",
188
+ description: "Send a /btw answer's steering prompt to the main session",
189
+ },
185
190
  "app.clipboard.pasteImage": {
186
191
  defaultKeys: getDefaultPasteImageKeys(),
187
192
  description: "Paste image or text from clipboard",
@@ -1,8 +1,9 @@
1
1
  import { logger } from "@oh-my-pi/pi-utils";
2
2
  import {
3
3
  SessionSelectorComponent,
4
- type SessionSelectorEntry,
5
4
  type SessionHistoryMatcher,
5
+ type SessionListing,
6
+ type SessionSelectorEntry,
6
7
  } from "../overlays/session-selector";
7
8
  import { runStandaloneTui } from "./standalone-picker";
8
9
 
@@ -14,12 +15,14 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
14
15
  /** Rename the session and resolve with the title as stored, or null when it did not happen. */
15
16
  renameSession?(session: T, title: string): Promise<string | null>;
16
17
  setSessionPinned?(session: T, pinned: boolean): Promise<void>;
17
- loadAllSessions?(): Promise<T[]>;
18
+ loadAllSessions?(): Promise<SessionListing<T>>;
18
19
  }
19
20
 
20
21
  /** Presentation and capability controls for the standalone session picker. */
21
22
  export interface SessionPickerOptions<T extends SessionSelectorEntry = SessionSelectorEntry> {
22
- allSessions?: T[];
23
+ /** Complete listing replacing the preliminary `sessions` once it settles. */
24
+ completeSessions?: Promise<T[]>;
25
+ allSessions?: SessionListing<T>;
23
26
  title?: string;
24
27
  scopeLabel?: string | false;
25
28
  showCwd?: boolean;
@@ -71,6 +74,7 @@ export async function selectSession<T extends SessionSelectorEntry>(
71
74
  onRename: options.onRename ?? host.renameSession,
72
75
  onSetPinned: host.setSessionPinned,
73
76
  historyMatcher,
77
+ completeSessions: options.completeSessions,
74
78
  loadAllSessions: options.allowGlobalScope === false ? undefined : host.loadAllSessions,
75
79
  allSessions: options.allSessions,
76
80
  getTerminalRows: () => ui.terminal.rows,
@@ -12,7 +12,7 @@ import { sanitizeText } from "@oh-my-pi/pi-utils";
12
12
  import type { Terminal as XtermTerminalType } from "@oh-my-pi/pi-utils/vterm";
13
13
  import { theme } from "../theme/theme";
14
14
  import type { OutputArtifactError } from "../tools/streaming-output";
15
- import type { TruncationMeta } from "../tools/output-meta";
15
+ import type { MinimizedMeta, TruncationMeta } from "../tools/output-meta";
16
16
  import { resolveImageOptions } from "../render/render-utils";
17
17
  import { OutputPane } from "../render/output-pane";
18
18
  import { loadXtermTerminal, readTerminalRows, styleTerminalRow } from "../tools/terminal-output";
@@ -26,6 +26,7 @@ import {
26
26
  describeExecutionTool,
27
27
  type ExecutionColorKey,
28
28
  type ExecutionStatus,
29
+ executionStatusLabel,
29
30
  PREVIEW_LINES,
30
31
  resolveExecutionStatus,
31
32
  } from "./execution-shared";
@@ -34,6 +35,7 @@ import { type DescribeContext, type NativeNode, type NativeUiEvent, rootToggleEx
34
35
  import { NativeImageCache } from "../native/blobs";
35
36
  import { imageLinksEnabled, linkedImageSpan, linkedImageText } from "../prompt/image-references";
36
37
  import { Memo } from "../native/memo";
38
+ import type { DynamicBorder } from "../chrome/dynamic-border";
37
39
 
38
40
  const STREAMING_LINE_CAP = PREVIEW_LINES * 5;
39
41
  // Minimum interval between processing incoming chunks for display (ms).
@@ -59,8 +61,10 @@ export class BashExecutionComponent extends Container {
59
61
  #status: ExecutionStatus = "running";
60
62
  #exitCode: number | undefined = undefined;
61
63
  #loader: Loader;
64
+ readonly #bottomBorder: DynamicBorder;
62
65
  #truncation?: TruncationMeta;
63
66
  #artifactError?: OutputArtifactError;
67
+ #minimized?: MinimizedMeta;
64
68
  #expanded = false;
65
69
  // Post-finalize mutation counter (FinalizableBlock.getTranscriptBlockVersion):
66
70
  // a completed command's block still mutates on expansion toggles, and the
@@ -103,9 +107,10 @@ export class BashExecutionComponent extends Container {
103
107
  // Use dim border for excluded-from-context commands (!! prefix)
104
108
  const colorKey = excludeFromContext ? "dim" : "bashMode";
105
109
  this.#colorKey = colorKey;
106
- const { contentContainer, loader } = buildExecutionFrame(this, ui, colorKey);
110
+ const { contentContainer, loader, bottomBorder } = buildExecutionFrame(this, ui, colorKey);
107
111
  this.#contentContainer = contentContainer;
108
112
  this.#loader = loader;
113
+ this.#bottomBorder = bottomBorder;
109
114
  this.#outputPane = new OutputPane(theme, {
110
115
  expanded: false,
111
116
  collapsedMaxLines: PREVIEW_LINES,
@@ -121,7 +126,6 @@ export class BashExecutionComponent extends Container {
121
126
  // Command header
122
127
  this.#headerText = new Text(theme.fg(colorKey, theme.bold(`$ ${command}`)), 1, 0);
123
128
  this.#contentContainer.addChild(this.#headerText);
124
- this.#contentContainer.addChild(this.#loader);
125
129
  }
126
130
 
127
131
  /**
@@ -200,6 +204,7 @@ export class BashExecutionComponent extends Container {
200
204
  exitCode: this.#exitCode,
201
205
  truncation: this.#truncation,
202
206
  artifactError: this.#artifactError,
207
+ minimized: this.#minimized,
203
208
  };
204
209
  return dataFirst
205
210
  ? describeExecutionTool({
@@ -358,6 +363,7 @@ export class BashExecutionComponent extends Container {
358
363
  output?: string;
359
364
  truncation?: TruncationMeta;
360
365
  artifactError?: OutputArtifactError;
366
+ minimized?: MinimizedMeta;
361
367
  images?: readonly ImageContent[];
362
368
  showImages?: boolean;
363
369
  },
@@ -367,6 +373,7 @@ export class BashExecutionComponent extends Container {
367
373
  this.#endedAt ??= performance.now();
368
374
  this.#truncation = options?.truncation;
369
375
  this.#artifactError = options?.artifactError;
376
+ this.#minimized = options?.minimized;
370
377
  this.#images = options?.images ?? [];
371
378
  this.#showImages = options?.showImages ?? true;
372
379
  if (options?.output !== undefined && !this.#ptyMode) {
@@ -383,6 +390,12 @@ export class BashExecutionComponent extends Container {
383
390
  this.#updateDisplay();
384
391
  }
385
392
 
393
+ /** The loader animates the bottom border from outside the child tree, so teardown stops it here. */
394
+ override dispose(): void {
395
+ this.#loader.dispose();
396
+ super.dispose();
397
+ }
398
+
386
399
  override render(width: number): readonly string[] {
387
400
  if (this.#displayDirty) {
388
401
  this.#displayDirty = false;
@@ -440,15 +453,13 @@ export class BashExecutionComponent extends Container {
440
453
  }
441
454
  }
442
455
 
443
- // Loader or status
444
- if (this.#status === "running") {
445
- this.#contentContainer.addChild(this.#loader);
446
- } else {
456
+ // The bottom border keeps the live loader until the run settles, then its exit/cancel marker.
457
+ if (this.#status !== "running") {
458
+ this.#bottomBorder.setLabel(executionStatusLabel(this.#status, this.#exitCode));
447
459
  const footer = buildStatusFooter({
448
- status: this.#status,
449
- exitCode: this.#exitCode,
450
460
  truncation: this.#truncation,
451
461
  artifactError: this.#artifactError,
462
+ minimized: this.#minimized,
452
463
  hiddenLineCount,
453
464
  suppressHiddenCount: hasSixelOutput,
454
465
  });
@@ -37,7 +37,7 @@ import { theme } from "../theme";
37
37
  import {
38
38
  assistantHasVisibleContent,
39
39
  assistantUsageIsBilled,
40
- buildAsyncResultBlock,
40
+ buildAsyncResultComponent,
41
41
  buildFileMentionBlock,
42
42
  buildIrcMessageCard,
43
43
  buildLaunchCompletionBlock,
@@ -356,6 +356,7 @@ export class ChatTranscriptBuilder {
356
356
  component.setComplete(message.exitCode, message.cancelled, {
357
357
  truncation: message.meta?.truncation,
358
358
  artifactError: message.meta?.artifactError,
359
+ minimized: message.meta?.minimized,
359
360
  images: message.images,
360
361
  showImages: displayPreferences.showImages,
361
362
  });
@@ -599,7 +600,9 @@ export class ChatTranscriptBuilder {
599
600
  #appendCustomMessage(message: Extract<AgentMessage, { role: "custom" | "hookMessage" }>): void {
600
601
  if (!message.display) return;
601
602
  if (message.customType === "async-result") {
602
- const component = buildAsyncResultBlock(message);
603
+ const renderer = this.#deps.getMessageRenderer?.(message.customType);
604
+ const component = buildAsyncResultComponent(message, renderer);
605
+ this.#trackExpandable(component);
603
606
  this.container.addChild(component);
604
607
  return;
605
608
  }
@@ -19,12 +19,14 @@ import {
19
19
  describeExecutionTool,
20
20
  type ExecutionColorKey,
21
21
  type ExecutionStatus,
22
+ executionStatusLabel,
22
23
  PREVIEW_LINES,
23
24
  resolveExecutionStatus,
24
25
  } from "./execution-shared";
25
26
  import { code, span } from "../native/describe";
26
27
  import { type DescribeContext, type NativeNode, type NativeUiEvent, rootToggleExpanded } from "../native/node";
27
28
  import { Memo } from "../native/memo";
29
+ import type { DynamicBorder } from "../chrome/dynamic-border";
28
30
 
29
31
  export type EvalExecutionLanguage = "python" | "js";
30
32
 
@@ -32,6 +34,7 @@ export class EvalExecutionComponent extends Container {
32
34
  #status: ExecutionStatus = "running";
33
35
  #exitCode: number | undefined = undefined;
34
36
  #loader: Loader;
37
+ readonly #bottomBorder: DynamicBorder;
35
38
  #truncation?: TruncationMeta;
36
39
  #artifactError?: OutputArtifactError;
37
40
  #expanded = false;
@@ -72,9 +75,10 @@ export class EvalExecutionComponent extends Container {
72
75
  this.#language = language;
73
76
 
74
77
  const colorKey: ExecutionColorKey = this.#excludeFromContext ? "dim" : "pythonMode";
75
- const { contentContainer, loader } = buildExecutionFrame(this, ui, colorKey);
78
+ const { contentContainer, loader, bottomBorder } = buildExecutionFrame(this, ui, colorKey);
76
79
  this.#contentContainer = contentContainer;
77
80
  this.#loader = loader;
81
+ this.#bottomBorder = bottomBorder;
78
82
  this.#outputPane = new OutputPane(theme, {
79
83
  expanded: false,
80
84
  collapsedMaxLines: PREVIEW_LINES,
@@ -89,7 +93,6 @@ export class EvalExecutionComponent extends Container {
89
93
  });
90
94
 
91
95
  this.#contentContainer.addChild(this.#formatHeader(colorKey));
92
- this.#contentContainer.addChild(this.#loader);
93
96
  }
94
97
 
95
98
  /**
@@ -188,6 +191,12 @@ export class EvalExecutionComponent extends Container {
188
191
  this.#updateDisplay();
189
192
  }
190
193
 
194
+ /** The loader animates the bottom border from outside the child tree, so teardown stops it here. */
195
+ override dispose(): void {
196
+ this.#loader.dispose();
197
+ super.dispose();
198
+ }
199
+
191
200
  #updateDisplay(): void {
192
201
  // Only the collapsed preview hides lines; when expanded the footer must
193
202
  // not keep advertising hidden lines / ctrl+o.
@@ -200,12 +209,10 @@ export class EvalExecutionComponent extends Container {
200
209
 
201
210
  if (this.#outputPane.lineCount > 0) this.#contentContainer.addChild(this.#outputPane);
202
211
 
203
- if (this.#status === "running") {
204
- this.#contentContainer.addChild(this.#loader);
205
- } else {
212
+ // The bottom border keeps the live loader until the run settles, then its exit/cancel marker.
213
+ if (this.#status !== "running") {
214
+ this.#bottomBorder.setLabel(executionStatusLabel(this.#status, this.#exitCode));
206
215
  const footer = buildStatusFooter({
207
- status: this.#status,
208
- exitCode: this.#exitCode,
209
216
  truncation: this.#truncation,
210
217
  artifactError: this.#artifactError,
211
218
  hiddenLineCount,