@oh-my-pi/pi-tui 18.8.6 → 18.8.8

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 (103) hide show
  1. package/CHANGELOG.md +45 -79
  2. package/README.md +7 -5
  3. package/dist/types/chat/assistant-message.d.ts +1 -0
  4. package/dist/types/chat/compaction-summary-message.d.ts +1 -0
  5. package/dist/types/chat/execution-shared.d.ts +6 -5
  6. package/dist/types/chat/user-message.d.ts +2 -0
  7. package/dist/types/components/box.d.ts +1 -0
  8. package/dist/types/components/disclosure.d.ts +1 -0
  9. package/dist/types/components/image.d.ts +1 -0
  10. package/dist/types/components/key-value-list.d.ts +1 -0
  11. package/dist/types/components/layout/row.d.ts +1 -0
  12. package/dist/types/components/layout/stack.d.ts +1 -0
  13. package/dist/types/components/markdown.d.ts +1 -0
  14. package/dist/types/components/scroll-view.d.ts +1 -0
  15. package/dist/types/components/section.d.ts +1 -0
  16. package/dist/types/components/table.d.ts +1 -0
  17. package/dist/types/components/text.d.ts +1 -0
  18. package/dist/types/components/truncated-text.d.ts +1 -0
  19. package/dist/types/keybinding-matchers.d.ts +3 -0
  20. package/dist/types/native/blobs.d.ts +3 -1
  21. package/dist/types/overlays/annotation-overlay.d.ts +12 -0
  22. package/dist/types/overlays/annotation-types.d.ts +2 -0
  23. package/dist/types/overlays/hook-selector.d.ts +9 -4
  24. package/dist/types/overlays/model-browser.d.ts +2 -2
  25. package/dist/types/overlays/model-hub.d.ts +21 -4
  26. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  27. package/dist/types/overlays/session-selector.d.ts +1 -1
  28. package/dist/types/overlays/usage-dashboard.d.ts +3 -1
  29. package/dist/types/render/output-pane.d.ts +1 -0
  30. package/dist/types/render/render-utils.d.ts +1 -0
  31. package/dist/types/render/tool-card.d.ts +6 -0
  32. package/dist/types/render/width-aware-text.d.ts +2 -0
  33. package/dist/types/status-line/types.d.ts +1 -1
  34. package/dist/types/terminal.d.ts +10 -2
  35. package/dist/types/theme/symbols.d.ts +1 -1
  36. package/dist/types/tools/eval.d.ts +1 -1
  37. package/dist/types/tools/native-view.d.ts +47 -0
  38. package/dist/types/tools/renderer.d.ts +15 -2
  39. package/dist/types/tools/write.d.ts +1 -1
  40. package/dist/types/tools/yield.d.ts +22 -0
  41. package/dist/types/tui.d.ts +31 -0
  42. package/dist/types/vim.d.ts +19 -5
  43. package/package.json +9 -9
  44. package/src/chat/advisor-message.ts +11 -0
  45. package/src/chat/assistant-message.ts +6 -0
  46. package/src/chat/compaction-summary-message.ts +8 -0
  47. package/src/chat/execution-shared.ts +42 -25
  48. package/src/chat/read-tool-group.ts +6 -4
  49. package/src/chat/skill-message.ts +6 -0
  50. package/src/chat/table-chart.ts +17 -20
  51. package/src/chat/tool-execution.ts +27 -2
  52. package/src/chat/user-message.ts +14 -0
  53. package/src/chrome/transcript-container.ts +32 -1
  54. package/src/components/box.ts +5 -0
  55. package/src/components/disclosure.ts +8 -0
  56. package/src/components/editor.ts +182 -5
  57. package/src/components/image.ts +7 -5
  58. package/src/components/key-value-list.ts +4 -0
  59. package/src/components/layout/row.ts +7 -0
  60. package/src/components/layout/stack.ts +7 -0
  61. package/src/components/markdown.ts +8 -0
  62. package/src/components/scroll-view.ts +5 -0
  63. package/src/components/section.ts +5 -0
  64. package/src/components/table.ts +4 -0
  65. package/src/components/text.ts +7 -0
  66. package/src/components/truncated-text.ts +5 -0
  67. package/src/keybinding-matchers.ts +6 -2
  68. package/src/native/backend.ts +16 -15
  69. package/src/native/blobs.ts +43 -10
  70. package/src/native/reconcile.ts +4 -0
  71. package/src/overlays/agent-hub.ts +7 -3
  72. package/src/overlays/annotation-overlay.ts +87 -6
  73. package/src/overlays/annotation-types.ts +2 -0
  74. package/src/overlays/hook-selector.ts +12 -5
  75. package/src/overlays/model-browser.ts +3 -3
  76. package/src/overlays/model-hub.ts +107 -27
  77. package/src/overlays/session-observer-registry.ts +10 -2
  78. package/src/overlays/session-selector.ts +29 -19
  79. package/src/overlays/snapcompact-shape-preview.ts +2 -3
  80. package/src/overlays/usage-dashboard.ts +70 -23
  81. package/src/prompt/attachment-chips.ts +13 -10
  82. package/src/prompt/composer.ts +1 -0
  83. package/src/render/output-pane.ts +6 -0
  84. package/src/render/render-utils.ts +11 -0
  85. package/src/render/tool-card.ts +15 -0
  86. package/src/render/width-aware-text.ts +5 -0
  87. package/src/status-line/segments.ts +3 -0
  88. package/src/status-line/types.ts +1 -1
  89. package/src/terminal.ts +24 -3
  90. package/src/theme/symbols.ts +5 -0
  91. package/src/tools/bash.ts +92 -42
  92. package/src/tools/edit.ts +142 -120
  93. package/src/tools/eval.ts +149 -123
  94. package/src/tools/fetch.ts +5 -6
  95. package/src/tools/index.ts +2 -0
  96. package/src/tools/mcp.ts +15 -2
  97. package/src/tools/native-view.ts +102 -1
  98. package/src/tools/read.ts +7 -5
  99. package/src/tools/renderer.ts +15 -2
  100. package/src/tools/write.ts +70 -56
  101. package/src/tools/yield.ts +225 -0
  102. package/src/tui.ts +93 -0
  103. package/src/vim.ts +333 -18
@@ -2,7 +2,7 @@ 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" | "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" | "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" | "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" | "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";
6
6
  export type SymbolMap = Record<SymbolKey, string>;
7
7
  /**
8
8
  * Icon vocabulary for slash-command autocomplete type indicators. Each name
@@ -95,7 +95,7 @@ export declare const evalToolRenderer: {
95
95
  }, options: RenderResultOptions & {
96
96
  renderContext?: EvalRenderContext;
97
97
  }, uiTheme: Theme, _args?: EvalRenderArgs): Component;
98
- describeCall(args: EvalRenderArgs, _options: RenderResultOptions): NativeToolView;
98
+ describeCall(args: EvalRenderArgs, options: RenderResultOptions): NativeToolView;
99
99
  describeResult(result: {
100
100
  content: Array<{
101
101
  type: string;
@@ -72,6 +72,53 @@ export declare function truncationNotice(meta: OutputMeta | undefined): NativeNo
72
72
  * truncation / artifact-capture notice from `meta`; undefined when empty.
73
73
  */
74
74
  export declare function footnoteText(parts: readonly string[], meta?: OutputMeta): NativeNode | undefined;
75
+ /** Drawn lines of a run's input (command, cell code) before its "N more lines" button. */
76
+ export declare const RUN_INPUT_PREVIEW_LINES = 6;
77
+ /**
78
+ * What a run's foot names: `running` (live clock), `background` (an async job
79
+ * outlives the call), and the settled outcomes. A failure with an exit code
80
+ * reads `exit N`; without one, `Failed`.
81
+ */
82
+ export type RunState = "running" | "background" | "done" | "failed" | "cancelled" | "timed-out";
83
+ /** Inputs of {@link runFoot}. */
84
+ export interface RunFootInput {
85
+ readonly state: RunState;
86
+ /** Exit code of a `failed` run; a non-zero code names the state `exit N`. */
87
+ readonly exitCode?: number;
88
+ /** Milliseconds run so far (`running`) or in total; the time is omitted when undefined. */
89
+ readonly elapsedMs?: number;
90
+ /** Quiet facts (job id, service state, artifact), muted and ` · `-joined. */
91
+ readonly facts?: readonly string[];
92
+ /** Truncation / artifact-capture notices, appended to the facts in `warning`. */
93
+ readonly meta?: OutputMeta;
94
+ }
95
+ /**
96
+ * The foot line of a run box (`omp.run.foot`): the state word in its tone,
97
+ * the run time (a live clock while running, frozen once settled) and the
98
+ * quiet facts. Glyphs are the terminal's styling, never text.
99
+ */
100
+ export declare function runFoot(input: RunFootInput): NativeNode;
101
+ /** A run's input section: the command or cell source, clamped to {@link RUN_INPUT_PREVIEW_LINES}. */
102
+ export declare function runInput(source: string, p: {
103
+ role: string;
104
+ lang: string;
105
+ wrap: boolean;
106
+ }): NativeNode;
107
+ /** A run's terminal output: follows its tail, clamped to the last `previewLines` lines while folded. */
108
+ export declare function runOutput(output: string, p: {
109
+ role: string;
110
+ previewLines: number;
111
+ key?: string;
112
+ }): NativeNode;
113
+ /**
114
+ * One run as one box (`omp.run`): input, output, status lines and foot in
115
+ * order. `tone` tints the box: `error` for a failed run, `warning` for a
116
+ * timed-out one.
117
+ */
118
+ export declare function runBox(children: readonly (NativeChild | undefined | false)[], p: {
119
+ key: string;
120
+ tone?: TspTone;
121
+ }): NativeNode;
75
122
  /** A diagnostic's severity glyph: `x-circle` error, `warn` warning, `info`, `lightbulb` hint, in the severity tone. */
76
123
  export declare function diagnosticGlyph(severity: ParsedDiagnostic["severity"]): NativeNode;
77
124
  /**
@@ -25,6 +25,14 @@ export interface RenderResultOptions {
25
25
  * Streamed `xd://` previews stay queued until this is set.
26
26
  */
27
27
  executionStarted?: boolean;
28
+ /**
29
+ * Milliseconds since this call began executing (`tool_execution_start`),
30
+ * or its total once settled; undefined before it starts. Native describe
31
+ * hooks only.
32
+ */
33
+ elapsedMs?: number;
34
+ /** The call was abandoned without a final result (turn aborted, sealed). Native describe hooks only. */
35
+ cancelled?: boolean;
28
36
  }
29
37
  /** Render options for a result, plus the tool-specific context the transcript threads through. */
30
38
  export type RenderResultContextOptions = RenderResultOptions & {
@@ -85,6 +93,8 @@ export interface NativeToolHead {
85
93
  readonly targetKind?: TspToolProps["targetKind"];
86
94
  /** Language for `command` targets. */
87
95
  readonly lang?: string;
96
+ /** The raw command line the head's Copy command button copies (set implicitly by a `command` target). */
97
+ readonly command?: string;
88
98
  /** `file://` link for path targets. */
89
99
  readonly href?: string;
90
100
  /** Short facts after the target (`+8 −1`, `5 matches · 2 files`). */
@@ -111,10 +121,13 @@ export interface NativeToolView {
111
121
  readonly body?: readonly NativeChild[];
112
122
  /** Tone override; the frame otherwise derives it from the call status. */
113
123
  readonly tone?: TspTone;
114
- /** Body clamp while collapsed; `{tail}` keeps the end (terminal output). Defaults to the transcript's preview size. */
124
+ /**
125
+ * Body clamp while collapsed; `{tail}` keeps the end (terminal output). Defaults to the transcript's preview size.
126
+ * `children`: the body stays whole while collapsed and its own `ansi`/`code` previews clamp instead.
127
+ */
115
128
  readonly preview?: TspPreview | {
116
129
  readonly tail: number;
117
- } | "none";
130
+ } | "none" | "children";
118
131
  /** Starts expanded whatever the transcript's expand state (the todo checklist); the user can still fold it. */
119
132
  readonly open?: boolean;
120
133
  /** Render frameless (`frame:"inline"`): a head line plus a disclosed body. */
@@ -81,7 +81,7 @@ export declare const writeToolRenderer: {
81
81
  renderCall(args: WriteRenderArgs, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
82
82
  renderContext?: WriteRenderContext;
83
83
  }, uiTheme: Theme): Component | undefined;
84
- renderResult(result: WriteResult, options: RenderResultOptions & {
84
+ renderResult(result: WriteResult, options: RenderResultOptions & WriteStreamingPreviewStateCarrier & {
85
85
  renderContext?: WriteRenderContext;
86
86
  }, uiTheme: Theme, args?: WriteRenderArgs): Component;
87
87
  describeCall(args: WriteRenderArgs, options: RenderResultOptions & {
@@ -0,0 +1,22 @@
1
+ import type { Theme } from "../theme/theme.js";
2
+ import type { Component } from "../tui.js";
3
+ import type { NativeToolView, RenderResultContextOptions, RenderResultOptions, ToolRenderResult } from "./renderer.js";
4
+ import type { YieldItem } from "./task.js";
5
+ /** `yield` arguments as sent; strict-mode providers send omitted optionals as `null`. */
6
+ interface YieldRenderArgs {
7
+ type?: string | string[] | null;
8
+ data?: unknown;
9
+ error?: string | null;
10
+ /** 1-based workpool item number. */
11
+ key?: number;
12
+ }
13
+ /** Shows a `yield` call's submitted data instead of its model-facing acknowledgement. */
14
+ export declare const yieldToolRenderer: {
15
+ renderCall(args: YieldRenderArgs, options: RenderResultOptions, theme: Theme): Component;
16
+ renderResult(result: ToolRenderResult<YieldItem>, options: RenderResultContextOptions, theme: Theme, args?: YieldRenderArgs): Component;
17
+ describeCall(args: YieldRenderArgs, options: RenderResultOptions): NativeToolView;
18
+ describeResult(result: ToolRenderResult<YieldItem>, _options: RenderResultContextOptions, args?: YieldRenderArgs): NativeToolView;
19
+ mergeCallAndResult: true;
20
+ animatedPendingPreview: true;
21
+ };
22
+ export {};
@@ -78,6 +78,10 @@ export interface TUIStartOptions {
78
78
  * Paint without owning stdin: the terminal stays in cooked mode (kernel
79
79
  * echo + line editing at the hardware cursor) until {@link TUI.enableInput}
80
80
  * switches to raw input and replays the kernel-buffered keystrokes.
81
+ *
82
+ * A terminal expected to speak TSP needs raw input from the start, so it
83
+ * gets it; its keystrokes are held instead (TSP events and the cell-size
84
+ * reply still apply) until {@link TUI.releaseHeldInput} replays them.
81
85
  */
82
86
  deferInput?: boolean;
83
87
  }
@@ -169,6 +173,16 @@ export interface Component {
169
173
  * Called when theme changes or when component needs to re-render from scratch.
170
174
  */
171
175
  invalidate?(): void;
176
+ /**
177
+ * Optional hook to drop memoized render output (rows, parse and wrap state)
178
+ * that the next `render()` can rebuild from state the component keeps.
179
+ * Unlike {@link invalidate}, it must not rebuild anything eagerly: no
180
+ * renderer or extension callbacks, no image conversions, and no child
181
+ * replacement or disposal. The next render must return the same rows it
182
+ * would have returned without the release. Components without it are
183
+ * simply not released.
184
+ */
185
+ releaseRenderCaches?(): void;
172
186
  /**
173
187
  * Optional hook to set whether this component ignores tight layout mode.
174
188
  */
@@ -308,6 +322,7 @@ export declare class Container implements Component {
308
322
  /** Dispose every child, then detach it from this container. */
309
323
  disposeChildren(): void;
310
324
  invalidate(): void;
325
+ releaseRenderCaches(): void;
311
326
  /**
312
327
  * Propagate teardown to children. Call when the container's children are
313
328
  * being permanently discarded (not when they are detached for reuse — use
@@ -458,6 +473,22 @@ export declare class TUI extends Container {
458
473
  * path. Idempotent; no-op when input was never deferred.
459
474
  */
460
475
  enableInput(): void;
476
+ /**
477
+ * Replay the keystrokes held since a TSP `deferInput` start through the
478
+ * normal input path, then deliver input live. Call once the app's key
479
+ * handlers are installed so a hotkey pressed during startup still fires.
480
+ * Only keys typed while the start-time focus owner had focus are held. The
481
+ * hold survives a stop/start until released. Idempotent; no-op when nothing
482
+ * is held.
483
+ */
484
+ releaseHeldInput(): void;
485
+ /**
486
+ * Hand held-key ownership from `previous` to `next` when the app replaces
487
+ * the component focused at start (a swapped-in custom editor), so keys
488
+ * typed into the replacement stay queued behind the held ones instead of
489
+ * overtaking them. No-op unless `previous` owns the held keys.
490
+ */
491
+ replaceHeldFocus(previous: Component, next: Component): void;
461
492
  addStartListener(listener: StartListener): () => void;
462
493
  addInputListener(listener: InputListener): () => void;
463
494
  removeInputListener(listener: InputListener): void;
@@ -8,8 +8,8 @@
8
8
  * This is a usable subset of Vim, not a reimplementation of it (see issue #3299): Normal and Visual
9
9
  * modes, the common motions, and operators built from those motions.
10
10
  */
11
- export type VimMode = "insert" | "normal" | "visual" | "visual-line";
12
- export type VimOperator = "d" | "y" | "c";
11
+ export type VimMode = "insert" | "normal" | "visual" | "visual-line" | "replace";
12
+ export type VimOperator = "d" | "y" | "c" | ">" | "<";
13
13
  export interface VimPosition {
14
14
  line: number;
15
15
  col: number;
@@ -50,6 +50,20 @@ export type VimCommand = {
50
50
  count: number;
51
51
  } | {
52
52
  kind: "undo";
53
+ } | {
54
+ kind: "replace";
55
+ from: VimPosition;
56
+ to: VimPosition;
57
+ text: string;
58
+ } | {
59
+ kind: "join";
60
+ fromLine: number;
61
+ lines: number;
62
+ } | {
63
+ kind: "indent";
64
+ fromLine: number;
65
+ toLine: number;
66
+ out: boolean;
53
67
  };
54
68
  /** Start offset of the grapheme after `col`, clamped to `text.length`. */
55
69
  export declare function nextGraphemeStart(text: string, col: number): number;
@@ -62,11 +76,11 @@ export declare class VimState {
62
76
  mode: VimMode;
63
77
  /** Fixed end of a Visual selection; the cursor is the moving end. */
64
78
  anchor: VimPosition | null;
65
- /** True while a count, operator, `g`, or text-object prefix is half-typed — Escape cancels it. */
79
+ /** True while a count, operator, `g`, text-object, find, or `r` prefix is half-typed — Escape cancels it. */
66
80
  get pending(): boolean;
67
81
  /**
68
- * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`) — empty when
69
- * nothing is pending. Hosts render this next to the mode so a partially entered operator is
82
+ * The half-typed command as Vim would echo it (`"2"`, `"d"`, `"2d"`, `"di"`, `"df"`, `"r"`) — empty
83
+ * when nothing is pending. Hosts render this next to the mode so a partially entered operator is
70
84
  * visible instead of silently swallowing the next keystroke.
71
85
  */
72
86
  get pendingText(): string;
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.8.6",
4
+ "version": "18.8.8",
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.8.6",
45
- "@oh-my-pi/pi-agent-core": "18.8.6",
46
- "@oh-my-pi/pi-ai": "18.8.6",
47
- "@oh-my-pi/pi-catalog": "18.8.6",
48
- "@oh-my-pi/pi-natives": "18.8.6",
49
- "@oh-my-pi/pi-utils": "18.8.6",
50
- "@oh-my-pi/pi-wire": "18.8.6",
51
- "@oh-my-pi/snapcompact": "18.8.6"
44
+ "@oh-my-pi/omptype": "18.8.8",
45
+ "@oh-my-pi/pi-agent-core": "18.8.8",
46
+ "@oh-my-pi/pi-ai": "18.8.8",
47
+ "@oh-my-pi/pi-catalog": "18.8.8",
48
+ "@oh-my-pi/pi-natives": "18.8.8",
49
+ "@oh-my-pi/pi-utils": "18.8.8",
50
+ "@oh-my-pi/pi-wire": "18.8.8",
51
+ "@oh-my-pi/snapcompact": "18.8.8"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
@@ -52,6 +52,10 @@ class AdvisorHeader implements Component {
52
52
  this.#uiTheme = uiTheme;
53
53
  }
54
54
 
55
+ releaseRenderCaches(): void {
56
+ this.#cache = undefined;
57
+ }
58
+
55
59
  invalidate(): void {
56
60
  this.#cache = undefined;
57
61
  }
@@ -82,6 +86,10 @@ class AdvisorNotes implements Component {
82
86
  this.#uiTheme = uiTheme;
83
87
  }
84
88
 
89
+ releaseRenderCaches(): void {
90
+ this.#cache = undefined;
91
+ }
92
+
85
93
  invalidate(): void {
86
94
  this.#cache = undefined;
87
95
  }
@@ -236,6 +244,9 @@ export function createAdvisorMessageCard(
236
244
  invalidate(): void {
237
245
  disclosure.invalidate();
238
246
  },
247
+ releaseRenderCaches(): void {
248
+ disclosure.releaseRenderCaches();
249
+ },
239
250
  dispose(): void {
240
251
  disclosure.dispose();
241
252
  },
@@ -575,6 +575,12 @@ export class AssistantMessageComponent extends Container {
575
575
  }
576
576
  }
577
577
 
578
+ override releaseRenderCaches(): void {
579
+ super.releaseRenderCaches();
580
+ this.#dropStableRenders();
581
+ this.#emergencyText?.releaseRenderCaches();
582
+ }
583
+
578
584
  setHideThinkingBlock(hide: boolean): void {
579
585
  this.#hideThinkingBlock = hide;
580
586
  this.#thinkingCollapsed.clear();
@@ -53,6 +53,10 @@ class DividerSummary implements Component {
53
53
  this.#cache = undefined;
54
54
  }
55
55
 
56
+ releaseRenderCaches(): void {
57
+ this.#cache = undefined;
58
+ }
59
+
56
60
  render(width: number): readonly string[] {
57
61
  width = Math.max(1, width);
58
62
  if (this.#cache?.width === width) return this.#cache.lines;
@@ -150,6 +154,10 @@ class SummaryMessageComponent implements Component {
150
154
  if (this.#ignoreTight !== undefined) this.#disclosure.setIgnoreTight(this.#ignoreTight);
151
155
  }
152
156
 
157
+ releaseRenderCaches(): void {
158
+ this.#disclosure.releaseRenderCaches();
159
+ }
160
+
153
161
  dispose(): void {
154
162
  if (this.#disposed) return;
155
163
  this.#disposed = true;
@@ -20,6 +20,7 @@ import { DEFAULT_TERMINAL_PREVIEW_LINES, expandKeyHint } from "../render/render-
20
20
  import type { TspCardStatus, TspSpan, TspText, TspTone } from "@oh-my-pi/pi-wire";
21
21
  import { card, node, span, text } from "../native/describe";
22
22
  import type { NativeChild, NativeNode } from "../native/node";
23
+ import { runBox, runFoot, runInput, runOutput, type RunState } from "../tools/native-view";
23
24
 
24
25
  /** Output rows shown while an execution is collapsed. */
25
26
  export const PREVIEW_LINES = 20;
@@ -190,7 +191,7 @@ export interface ExecutionToolInput {
190
191
  /** Tool name for the head icon (`bash`, `eval`). */
191
192
  readonly name: string;
192
193
  readonly title: string;
193
- /** The command / cell source, shown once in the head. */
194
+ /** The command / cell source: the run box's input and the head's Copy command. */
194
195
  readonly command: string;
195
196
  readonly lang: string;
196
197
  /** Excluded from context (`!!`/`$$`). */
@@ -208,39 +209,55 @@ export interface ExecutionToolInput {
208
209
  readonly artifactError?: OutputArtifactError;
209
210
  }
210
211
 
212
+ const EXECUTION_RUN_STATE: Record<ExecutionStatus, RunState> = {
213
+ running: "running",
214
+ complete: "done",
215
+ cancelled: "cancelled",
216
+ error: "failed",
217
+ };
218
+
211
219
  /**
212
- * A user `!`/`$` run as the agent's `tool` frame (§6.6): the command in the
213
- * head with a `you` badge (plus `not sent` when excluded from context), the
214
- * timer, exit chip and cancelled note; the body is the output as an `ansi`
215
- * mini terminal following its tail, then one quiet truncation line.
220
+ * A user `!`/`$` run as the agent's `tool` frame (§6.6): a head with a `you`
221
+ * badge (plus `not sent` when excluded from context) and the command for Copy
222
+ * command; the body is one run box — the source, the output as an `ansi` mini
223
+ * terminal following its tail, and the foot (state, time, truncation) — with
224
+ * any images below it. Timer, exit chip and cancelled note stay head data.
216
225
  */
217
226
  export function describeExecutionTool(input: ExecutionToolInput): NativeNode {
218
227
  const running = input.status === "running";
219
- const body: NativeChild[] = [];
220
- if (input.output) body.push(node("ansi", { text: input.output, follow: running }, undefined, "output"));
221
- if (input.images) body.push(...input.images);
222
- const notes: string[] = [];
223
- if (input.truncation) notes.push(formatTruncationMetaNotice(input.truncation));
224
- if (input.artifactError) notes.push(formatArtifactErrorNotice(input.artifactError));
225
- if (notes.length > 0) {
226
- body.push({
227
- ...text([span(notes.join(" · "), "muted")], { wrap: "word", role: "omp.tool.notice" }),
228
- key: "foot",
229
- });
230
- }
228
+ const shell = input.name === "bash";
231
229
  const ms = Math.max(0, Math.round((input.endedAt ?? performance.now()) - input.startedAt));
230
+ const state = EXECUTION_RUN_STATE[input.status];
231
+ const box = runBox(
232
+ [
233
+ runInput(input.command, {
234
+ role: shell ? "omp.tool.bash.command" : "omp.tool.eval.input",
235
+ lang: input.lang,
236
+ wrap: shell,
237
+ }),
238
+ input.output.length > 0 &&
239
+ runOutput(input.output, {
240
+ role: shell ? "omp.tool.bash.output" : "omp.tool.eval.output",
241
+ previewLines: DEFAULT_TERMINAL_PREVIEW_LINES,
242
+ }),
243
+ runFoot({
244
+ state,
245
+ exitCode: input.exitCode,
246
+ elapsedMs: ms,
247
+ meta: { truncation: input.truncation, artifactError: input.artifactError },
248
+ }),
249
+ ],
250
+ { key: "run", tone: state === "failed" ? "error" : undefined },
251
+ );
232
252
  const badges: { text: string; tone?: TspTone; title?: string }[] = [{ text: "you", tone: "user" }];
233
253
  if (input.excluded) badges.push({ text: "not sent", title: "Not sent to the model", tone: "muted" });
234
- const hasBody = body.length > 0;
235
254
  return node(
236
255
  "tool",
237
256
  {
238
257
  role: input.role,
239
258
  name: input.name,
240
259
  title: input.title,
241
- target: input.command,
242
- targetKind: "command",
243
- lang: input.lang,
260
+ command: input.command,
244
261
  badges,
245
262
  status: EXECUTION_CARD_STATUS[input.status],
246
263
  age: running ? ms : undefined,
@@ -248,11 +265,11 @@ export function describeExecutionTool(input: ExecutionToolInput): NativeNode {
248
265
  exit: input.status === "error" ? input.exitCode : undefined,
249
266
  note: input.status === "cancelled" ? "cancelled" : undefined,
250
267
  frame: "card",
251
- collapsible: hasBody,
252
- collapsed: hasBody ? !input.expanded : undefined,
253
- preview: hasBody ? { tail: DEFAULT_TERMINAL_PREVIEW_LINES } : undefined,
268
+ collapsible: true,
269
+ collapsed: !input.expanded,
270
+ preview: "children",
254
271
  },
255
- body,
272
+ [box, ...(input.images ?? [])],
256
273
  );
257
274
  }
258
275
 
@@ -1143,6 +1143,10 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
1143
1143
  const title = pathDisplay ? `Read ${pathDisplay}` : "Read";
1144
1144
  let cachedWidth: number | undefined;
1145
1145
  let cachedLines: string[] | undefined;
1146
+ const dropCache = () => {
1147
+ cachedWidth = undefined;
1148
+ cachedLines = undefined;
1149
+ };
1146
1150
  const expanded = this.#expanded;
1147
1151
  const component: Component = {
1148
1152
  render: (width: number) => {
@@ -1164,10 +1168,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
1164
1168
  cachedWidth = width;
1165
1169
  return cachedLines;
1166
1170
  },
1167
- invalidate: () => {
1168
- cachedWidth = undefined;
1169
- cachedLines = undefined;
1170
- },
1171
+ invalidate: dropCache,
1172
+ releaseRenderCaches: dropCache,
1171
1173
  };
1172
1174
  this.addChild(component);
1173
1175
  }
@@ -271,6 +271,12 @@ class SkillCallout implements Component {
271
271
  this.#lines = undefined;
272
272
  }
273
273
 
274
+ releaseRenderCaches(): void {
275
+ this.#source = undefined;
276
+ this.#lines = undefined;
277
+ this.#box.releaseRenderCaches();
278
+ }
279
+
274
280
  render(width: number): readonly string[] {
275
281
  const inner = this.#box.render(Math.max(1, width - 1));
276
282
  if (this.#source === inner && this.#lines !== undefined) return this.#lines;
@@ -16,8 +16,8 @@ import { chartAlt, renderChartSvg } from "../charts/chart-svg";
16
16
  import { analyzeTable, type TableAnalysis } from "../charts/table-data";
17
17
  import type { ImageBudget } from "../components/image";
18
18
  import { lexDocument } from "../components/markdown";
19
- import { node } from "../native/describe";
20
- import { registerNativeBlob } from "../native/blobs";
19
+ import { keyed } from "../native/describe";
20
+ import { nativeImageNode } from "../native/blobs";
21
21
  import type { NativeNode } from "../native/node";
22
22
  import { getThemeEpoch } from "../theme";
23
23
  import type { Component } from "../tui";
@@ -85,11 +85,11 @@ interface ChartEntry {
85
85
  let chartMode: TableChartMode = "off";
86
86
  let chartPlanner: TableChartPlanner | undefined;
87
87
  const charts = new Map<string, ChartEntry>();
88
- /** The theme-resolved native blob last registered for a chart. */
89
- const kNativeBlob = Symbol("tableChart.nativeBlob");
88
+ /** The theme-resolved image node owning a chart's native bytes. */
89
+ const kNativeImage = Symbol("tableChart.nativeImage");
90
90
 
91
91
  interface NativeTagged extends TableChart {
92
- [kNativeBlob]?: { epoch: number; blob: string };
92
+ [kNativeImage]?: { epoch: number; image: NativeNode };
93
93
  }
94
94
 
95
95
  /** Set how tables become charts (the host's `tui.autoGraph`) and its `smart` planner; the host rebuilds the transcript after. */
@@ -205,24 +205,21 @@ function drawChart(analysis: TableAnalysis, plan: ChartPlan, guessed = false): T
205
205
  export function describeTableChart(chart: TableChart, key: string): NativeNode {
206
206
  const epoch = getThemeEpoch();
207
207
  const tagged: NativeTagged = chart;
208
- let entry = tagged[kNativeBlob];
208
+ let entry = tagged[kNativeImage];
209
209
  if (entry?.epoch !== epoch) {
210
210
  const svg = prepareSvg(chart.svg, svgFigurePalette());
211
- entry = { epoch, blob: registerNativeBlob(new TextEncoder().encode(svg), "image/svg+xml") };
212
- tagged[kNativeBlob] = entry;
211
+ entry = {
212
+ epoch,
213
+ image: nativeImageNode(new TextEncoder().encode(svg), "image/svg+xml", {
214
+ alt: chart.alt,
215
+ w: chart.width,
216
+ h: chart.height,
217
+ max: { w: `${Math.round(chart.width / UNITS_PER_COLUMN)}ch` },
218
+ }),
219
+ };
220
+ tagged[kNativeImage] = entry;
213
221
  }
214
- return node(
215
- "image",
216
- {
217
- blob: entry.blob,
218
- alt: chart.alt,
219
- w: chart.width,
220
- h: chart.height,
221
- max: { w: `${Math.round(chart.width / UNITS_PER_COLUMN)}ch` },
222
- },
223
- undefined,
224
- key,
225
- );
222
+ return keyed(entry.image, key);
226
223
  }
227
224
 
228
225
  export interface TableChartFigureOptions {
@@ -111,10 +111,13 @@ function resolveEditModeForTool(toolName: string, tool: AgentTool | undefined):
111
111
 
112
112
  type ToolRendererStage = "call" | "result";
113
113
 
114
- /** A renderer's preview as the fallback card's clamp (`tail` becomes a head clamp of the same size). */
114
+ /**
115
+ * A renderer's preview as the fallback card's clamp (`tail` becomes a head
116
+ * clamp of the same size; `children` leaves the clamping to the body's own previews).
117
+ */
115
118
  function cardPreview(preview: NativeToolView["preview"]): TspPreview | undefined {
116
119
  if (preview === undefined) return { lines: DEFAULT_TERMINAL_PREVIEW_LINES };
117
- if (preview === "none") return undefined;
120
+ if (preview === "none" || preview === "children") return undefined;
118
121
  if (preview !== "auto" && "tail" in preview) return { lines: preview.tail };
119
122
  return preview;
120
123
  }
@@ -170,6 +173,24 @@ class SafeToolRendererComponent implements Component {
170
173
  invalidate.call(this.#component);
171
174
  }
172
175
 
176
+ releaseRenderCaches(): void {
177
+ const release = this.#component.releaseRenderCaches;
178
+ if (release === undefined) return;
179
+ try {
180
+ release.call(this.#component);
181
+ } catch (err) {
182
+ if (!this.#warned) {
183
+ this.#warned = true;
184
+ logger.warn("Tool renderer failed", {
185
+ tool: this.#toolName,
186
+ stage: this.#stage,
187
+ phase: "releaseRenderCaches",
188
+ error: String(err),
189
+ });
190
+ }
191
+ }
192
+ }
193
+
173
194
  setIgnoreTight(ignore: boolean): void {
174
195
  const setIgnoreTight = this.#component.setIgnoreTight;
175
196
  if (setIgnoreTight === undefined) return;
@@ -1007,6 +1028,7 @@ export class ToolExecutionComponent extends Container {
1007
1028
  target: head?.target,
1008
1029
  targetKind: head?.targetKind,
1009
1030
  lang: head?.lang,
1031
+ command: head?.command,
1010
1032
  href: head?.href,
1011
1033
  meta: head?.meta,
1012
1034
  badges: badges.length > 0 ? badges : undefined,
@@ -1106,11 +1128,14 @@ export class ToolExecutionComponent extends Container {
1106
1128
  * A renderer that throws falls back to the generic card.
1107
1129
  */
1108
1130
  #nativeView(): NativeToolView {
1131
+ const status = this.#nativeStatus();
1109
1132
  const options: RenderResultContextOptions = {
1110
1133
  expanded: this.#expanded,
1111
1134
  isPartial: this.#isPartial,
1112
1135
  argsComplete: this.#argsComplete,
1113
1136
  executionStarted: this.#executionStarted,
1137
+ elapsedMs: this.#elapsedMs(status),
1138
+ cancelled: status === "cancelled",
1114
1139
  renderContext: this.#buildRenderContext(),
1115
1140
  };
1116
1141
  // Custom tools (MCP, extensions) carry their own describe hooks, mirroring