@oh-my-pi/pi-tui 18.13.1 → 18.13.3

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 (73) hide show
  1. package/CHANGELOG.md +44 -15
  2. package/dist/types/apps/session-picker.d.ts +4 -0
  3. package/dist/types/chat/assistant-message.d.ts +1 -1
  4. package/dist/types/chat/bash-execution.d.ts +2 -0
  5. package/dist/types/chat/display-preferences.d.ts +2 -0
  6. package/dist/types/chat/tool-execution.d.ts +1 -1
  7. package/dist/types/chrome/context-map.d.ts +15 -0
  8. package/dist/types/chrome/context-presentation.d.ts +48 -0
  9. package/dist/types/chrome/context-view-model.d.ts +77 -0
  10. package/dist/types/components/editor.d.ts +2 -0
  11. package/dist/types/overlays/advisor-config.d.ts +6 -0
  12. package/dist/types/overlays/annotation-overlay.d.ts +7 -1
  13. package/dist/types/overlays/annotation-types.d.ts +28 -0
  14. package/dist/types/overlays/ask-dialog.d.ts +9 -2
  15. package/dist/types/overlays/context-viewer-format.d.ts +42 -0
  16. package/dist/types/overlays/context-viewer-overview.d.ts +70 -0
  17. package/dist/types/overlays/context-viewer.d.ts +25 -0
  18. package/dist/types/overlays/hook-input.d.ts +1 -0
  19. package/dist/types/overlays/plan-review-overlay.d.ts +2 -0
  20. package/dist/types/overlays/running-subagent-badge.d.ts +7 -1
  21. package/dist/types/overlays/session-selector.d.ts +13 -2
  22. package/dist/types/status-line/context-usage.d.ts +15 -30
  23. package/dist/types/theme/symbols.d.ts +1 -1
  24. package/dist/types/theme/theme.d.ts +0 -23
  25. package/dist/types/tools/ask.d.ts +2 -0
  26. package/dist/types/tools/bash-interactive.d.ts +6 -0
  27. package/dist/types/tools/fetch.d.ts +1 -0
  28. package/dist/types/tools/github.d.ts +3 -0
  29. package/dist/types/tools/read.d.ts +3 -0
  30. package/dist/types/tools/wait.d.ts +6 -0
  31. package/dist/types/tui.d.ts +6 -1
  32. package/package.json +10 -10
  33. package/src/apps/session-picker.ts +5 -0
  34. package/src/chat/advisor-message.ts +2 -0
  35. package/src/chat/assistant-message.ts +3 -1
  36. package/src/chat/bash-execution.ts +6 -0
  37. package/src/chat/display-preferences.ts +3 -0
  38. package/src/chat/tool-execution.ts +7 -3
  39. package/src/chrome/context-map.ts +62 -0
  40. package/src/chrome/context-presentation.ts +26 -0
  41. package/src/chrome/context-view-model.ts +73 -0
  42. package/src/components/editor.ts +26 -1
  43. package/src/components/markdown.ts +14 -2
  44. package/src/native/backend.ts +2 -64
  45. package/src/overlays/advisor-config.ts +9 -2
  46. package/src/overlays/annotation-overlay.ts +125 -37
  47. package/src/overlays/annotation-types.ts +24 -0
  48. package/src/overlays/ask-dialog.ts +55 -15
  49. package/src/overlays/context-viewer-format.ts +145 -0
  50. package/src/overlays/context-viewer-overview.ts +381 -0
  51. package/src/overlays/context-viewer.ts +728 -0
  52. package/src/overlays/hook-input.ts +2 -0
  53. package/src/overlays/hook-selector.ts +2 -2
  54. package/src/overlays/plan-review-overlay.ts +13 -2
  55. package/src/overlays/running-subagent-badge.ts +12 -1
  56. package/src/overlays/session-selector.ts +195 -26
  57. package/src/overlays/usage-dashboard.ts +5 -12
  58. package/src/prompt/custom-editor.ts +6 -1
  59. package/src/render/output-pane.ts +3 -3
  60. package/src/status-line/context-usage.ts +56 -580
  61. package/src/terminal.ts +24 -2
  62. package/src/theme/symbols.ts +16 -0
  63. package/src/theme/theme.ts +0 -65
  64. package/src/tools/ask.ts +2 -0
  65. package/src/tools/bash-interactive.ts +6 -1
  66. package/src/tools/bash.ts +5 -23
  67. package/src/tools/fetch.ts +1 -0
  68. package/src/tools/github.ts +23 -3
  69. package/src/tools/job-widget.ts +13 -1
  70. package/src/tools/json-tree.ts +1 -1
  71. package/src/tools/read.ts +3 -1
  72. package/src/tools/wait.ts +6 -0
  73. package/src/tui.ts +30 -5
@@ -1,10 +1,6 @@
1
1
  import type { AgentMessage, Tokenizer } from "@oh-my-pi/pi-agent-core";
2
2
  import type { CompactionSettings } from "@oh-my-pi/pi-agent-core/compaction";
3
3
  import type { Tool as AiTool, Model } from "@oh-my-pi/pi-ai";
4
- import type { Theme } from "../theme/index.js";
5
- import { Container } from "../tui.js";
6
- import type { TspText } from "@oh-my-pi/pi-wire";
7
- import type { DescribeContext, NativeNode } from "../native/node.js";
8
4
  interface ContextSkill {
9
5
  readonly name: string;
10
6
  readonly description?: string;
@@ -63,8 +59,6 @@ interface CategoryInfo {
63
59
  id: CategoryId;
64
60
  label: string;
65
61
  tokens: number;
66
- color: "accent" | "warning" | "success" | "userMessageText" | "customMessageLabel";
67
- glyph: string;
68
62
  }
69
63
  export interface ContextBreakdown {
70
64
  model: Model | undefined;
@@ -128,6 +122,21 @@ export declare function getToolSchemaMetadataRevision(tools: ToolSchemaSource):
128
122
  * discovered-agent metadata changes).
129
123
  */
130
124
  export declare function invalidateToolSchemaMetadata(tools: ToolSchemaSource): void;
125
+ /** Provider-visible text of one tool definition, in wire order. */
126
+ export interface ToolSchemaFragments {
127
+ /** Name, description, wire schema JSON, then rendered examples; fields that cannot be serialized are omitted. */
128
+ readonly fragments: string[];
129
+ /** `fragments` with the schema JSON indented for reading; present only when a display indent was requested. */
130
+ readonly display?: string[];
131
+ /** Why serialization stopped; the schema or examples after that point are missing from `fragments`. */
132
+ readonly schemaError?: string;
133
+ }
134
+ /**
135
+ * Provider-visible text of one tool definition. `displayIndent` additionally
136
+ * returns `display`, the same fragments with the schema JSON indented, from
137
+ * the same schema conversion.
138
+ */
139
+ export declare function toolSchemaFragments(tool: ContextTool, displayIndent?: number): ToolSchemaFragments;
131
140
  /**
132
141
  * Estimate provider-visible tool-schema tokens.
133
142
  *
@@ -158,28 +167,4 @@ export declare function computeNonMessageBreakdown(session: NonMessageTokenSourc
158
167
  * session and model.
159
168
  */
160
169
  export declare function computeContextBreakdown(session: ContextUsageSession, options: ContextUsageOptions): ContextBreakdown;
161
- /** The `/context` title: the model once one is selected (the body leads with its window). */
162
- export declare function contextUsageHead(breakdown: ContextBreakdown): TspText;
163
- /**
164
- * Native context-usage panel for terminals without `meter`: the cell grid as
165
- * unwrapped styled rows beside the legend. The terminal places the legend
166
- * beside the grid, or below it when narrow.
167
- */
168
- export declare function describeContextUsage(breakdown: ContextBreakdown): NativeNode;
169
- /**
170
- * The `/context` report body, without a frame or title of its own (the panel
171
- * showing it is titled {@link contextUsageHead}): ANSI renders the cell grid
172
- * beside the legend; natively it is drawn with `meter`s where the terminal has
173
- * them and as the glyph grid otherwise.
174
- */
175
- export declare class ContextUsageView extends Container {
176
- #private;
177
- constructor(breakdown: ContextBreakdown, theme: Theme);
178
- describe(cx: DescribeContext): NativeNode;
179
- }
180
- /**
181
- * Render a colorful context-usage panel as ANSI text. Output is a series of
182
- * lines pairing the grid (left) with the legend (right).
183
- */
184
- export declare function renderContextUsage(breakdown: ContextBreakdown, theme: Theme): string;
185
170
  export {};
@@ -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" | "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" | "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";
6
6
  export type SymbolMap = Record<SymbolKey, string>;
7
7
  /**
8
8
  * Icon vocabulary for slash-command autocomplete type indicators. Each name
@@ -114,29 +114,6 @@ export declare function stopThemeWatcher(): void;
114
114
  * Used by HTML export to generate CSS custom properties.
115
115
  */
116
116
  export declare function getResolvedThemeColors(themeName?: string): Promise<Record<string, string>>;
117
- /** One appearance variant of {@link NativeThemePalette}: token name → `#rrggbb`. */
118
- export type NativeThemeVariant = Record<string, string>;
119
- /** omp's resolved theme for a Tern Surface Protocol terminal (the `t` verb body minus `sf`). */
120
- export interface NativeThemePalette {
121
- dark?: NativeThemeVariant;
122
- light?: NativeThemeVariant;
123
- name: {
124
- dark?: string;
125
- light?: string;
126
- };
127
- }
128
- /**
129
- * Cheap identity of everything {@link getNativeThemePalette} depends on;
130
- * recompute the palette only when it changes.
131
- */
132
- export declare function getNativeThemePaletteKey(): string;
133
- /**
134
- * omp's theme for a native surface: with auto theme, the configured dark and
135
- * light themes; otherwise (or while the theme selector previews one) the
136
- * active theme under its own appearance only. Themes without a loadable
137
- * definition (in-memory instances) contribute nothing.
138
- */
139
- export declare function getNativeThemePalette(): NativeThemePalette;
140
117
  /**
141
118
  * Check if a theme is a "light" theme by analyzing its status-line background
142
119
  * luminance. Loads theme JSON synchronously (built-in or custom file on disk)
@@ -26,6 +26,8 @@ export interface AskToolDetails {
26
26
  customInput?: string;
27
27
  /** Optional note attached to the selected answer in the rich ask dialog. */
28
28
  note?: string;
29
+ /** Label of the option the note is on; unset when it is on the custom answer. */
30
+ noteOption?: string;
29
31
  /** True when the answer was auto-selected because the dialog timed out. */
30
32
  timedOut?: boolean;
31
33
  /** Multi-part question mode */
@@ -6,6 +6,12 @@ import type { Theme } from "../theme/theme.js";
6
6
  export interface BashInteractiveTerminalBackend {
7
7
  resize(columns: number, rows: number): void;
8
8
  }
9
+ /**
10
+ * Translates a TUI key event into the bytes a terminal would send to a PTY
11
+ * child; key releases encode to "". Shared by the agent `pty` console and
12
+ * `!` commands (`BashExecutionComponent.encodePtyInput`).
13
+ */
14
+ export declare function normalizeInputForPty(data: string, applicationCursorKeysMode: boolean): string;
9
15
  /** Interactive terminal overlay driven by an external PTY controller. */
10
16
  export declare class BashInteractiveOverlayComponent implements Component {
11
17
  #private;
@@ -9,6 +9,7 @@ export interface ReadUrlToolDetails {
9
9
  finalUrl: string;
10
10
  contentType: string;
11
11
  method: string;
12
+ raw?: boolean;
12
13
  truncated: boolean;
13
14
  notes: string[];
14
15
  meta?: OutputMeta;
@@ -85,6 +85,7 @@ export interface GhRunWatchFailedLogDetails {
85
85
  conclusion?: string;
86
86
  tail?: string;
87
87
  available: boolean;
88
+ unavailableReason?: string;
88
89
  }
89
90
  /** Live workflow watcher display snapshot. */
90
91
  export interface GhRunWatchViewDetails {
@@ -130,6 +131,8 @@ export interface GhFailedJobLog {
130
131
  full?: string;
131
132
  tail?: string;
132
133
  available: boolean;
134
+ unavailableReason?: string;
135
+ truncated?: boolean;
133
136
  }
134
137
  /** Choose the terminal conclusion or current job state. */
135
138
  export declare function formatJobState(job: GhRunJobSnapshot): string;
@@ -29,7 +29,10 @@ export interface ReadToolDetails {
29
29
  suffixResolution?: {
30
30
  from: string;
31
31
  to: string;
32
+ raw?: boolean;
32
33
  };
34
+ /** Raw source output keeps descriptive diagnostics in details, not in the body. */
35
+ raw?: boolean;
33
36
  url?: string;
34
37
  finalUrl?: string;
35
38
  contentType?: string;
@@ -55,6 +55,12 @@ export interface JobSnapshot {
55
55
  * on collision, else the job id itself. See {@link AsyncJob.agentId}.
56
56
  */
57
57
  agentUrlId?: string;
58
+ /**
59
+ * Whether this job's turn published `<agentUrlId>.md`. False where the job
60
+ * body reported that it wrote none, so `agent://` still holds an earlier
61
+ * turn's output; undefined where the body does not report it.
62
+ */
63
+ agentOutputPublished?: boolean;
58
64
  }
59
65
  /** Outcome of cancelling one background job. */
60
66
  export type CancelStatus = "cancelled" | "not_found" | "already_completed";
@@ -363,6 +363,8 @@ export declare class TUI extends Container {
363
363
  * observers (live stream publisher, session recorder) coexist.
364
364
  */
365
365
  addPaintListener(listener: PaintListener): () => void;
366
+ /** Observe actual ANSI frame writes, including transient resize paints; returns the unsubscribe. */
367
+ addFrameWriteListener(listener: (data: string) => void): () => void;
366
368
  /** Install the product-owned bounded frame provider. */
367
369
  setFrameProvider(provider: TerminalFrameProvider | undefined): void;
368
370
  get fullRedraws(): number;
@@ -505,7 +507,10 @@ export declare class TUI extends Container {
505
507
  * never come.
506
508
  */
507
509
  addStartListener(listener: StartListener): () => void;
508
- addInputListener(listener: InputListener): () => void;
510
+ /** Registers a key listener; `prepend` gives a foreground input owner first refusal over global shortcuts. */
511
+ addInputListener(listener: InputListener, options?: {
512
+ prepend?: boolean;
513
+ }): () => void;
509
514
  removeInputListener(listener: InputListener): void;
510
515
  stop(): void;
511
516
  /**
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.13.1",
4
+ "version": "18.13.3",
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,20 +41,20 @@
41
41
  "gen:glyphs": "bun scripts/gen-glyphs.ts"
42
42
  },
43
43
  "dependencies": {
44
- "@oh-my-pi/omptype": "18.13.1",
45
- "@oh-my-pi/pi-agent-core": "18.13.1",
46
- "@oh-my-pi/pi-ai": "18.13.1",
47
- "@oh-my-pi/pi-catalog": "18.13.1",
48
- "@oh-my-pi/pi-natives": "18.13.1",
49
- "@oh-my-pi/pi-utils": "18.13.1",
50
- "@oh-my-pi/pi-wire": "18.13.1",
51
- "@oh-my-pi/snapcompact": "18.13.1"
44
+ "@oh-my-pi/omptype": "18.13.3",
45
+ "@oh-my-pi/pi-agent-core": "18.13.3",
46
+ "@oh-my-pi/pi-ai": "18.13.3",
47
+ "@oh-my-pi/pi-catalog": "18.13.3",
48
+ "@oh-my-pi/pi-natives": "18.13.3",
49
+ "@oh-my-pi/pi-utils": "18.13.3",
50
+ "@oh-my-pi/pi-wire": "18.13.3",
51
+ "@oh-my-pi/snapcompact": "18.13.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"
55
55
  },
56
56
  "engines": {
57
- "bun": ">=1.3.14"
57
+ "bun": ">=1.4.4"
58
58
  },
59
59
  "files": [
60
60
  "src",
@@ -11,6 +11,8 @@ export interface SessionPickerHost<T extends SessionSelectorEntry = SessionSelec
11
11
  loadPinnedIds?(): Promise<ReadonlySet<string>>;
12
12
  loadHistoryMatcher?(): SessionHistoryMatcher;
13
13
  deleteSession?(session: T): Promise<boolean>;
14
+ /** Rename the session and resolve with the title as stored, or null when it did not happen. */
15
+ renameSession?(session: T, title: string): Promise<string | null>;
14
16
  loadAllSessions?(): Promise<T[]>;
15
17
  }
16
18
 
@@ -21,6 +23,8 @@ export interface SessionPickerOptions<T extends SessionSelectorEntry = SessionSe
21
23
  scopeLabel?: string | false;
22
24
  showCwd?: boolean;
23
25
  allowDelete?: boolean;
26
+ /** Rename the selected session; resolve with the title as stored, or null when it did not happen. */
27
+ onRename?: (session: T, title: string) => Promise<string | null>;
24
28
  allowGlobalScope?: boolean;
25
29
  historySearch?: boolean;
26
30
  pinnedIds?: ReadonlySet<string>;
@@ -63,6 +67,7 @@ export async function selectSession<T extends SessionSelectorEntry>(
63
67
  },
64
68
  {
65
69
  onDelete: options.allowDelete === false ? undefined : host.deleteSession,
70
+ onRename: options.onRename ?? host.renameSession,
66
71
  historyMatcher,
67
72
  loadAllSessions: options.allowGlobalScope === false ? undefined : host.loadAllSessions,
68
73
  allSessions: options.allSessions,
@@ -2,6 +2,7 @@ import { type Component } from "../tui";
2
2
  import { Disclosure } from "../components/disclosure";
3
3
  import { visibleWidth } from "../utils";
4
4
  import type { AdvisorMessageDetails, AdvisorNote, AdvisorSeverity } from "./messages";
5
+ import { chatTranscriptDisplayPreferences } from "./display-preferences";
5
6
  import { formatBadge, replaceTabs, type ToolUIColor, wrapTextWithAnsi } from "../render/render-utils";
6
7
  import { Ellipsis, truncateToWidth } from "../render";
7
8
  import type { Theme } from "../theme";
@@ -274,6 +275,7 @@ export function createAdvisorMessageCard(
274
275
  body: () => new AdvisorNotes(notes, 0, uiTheme),
275
276
  expanded: getExpanded(),
276
277
  paddingX: 1,
278
+ maxCollapsedRows: chatTranscriptDisplayPreferences.advisorCollapsedLines || undefined,
277
279
  });
278
280
  // The tool-output toggle owns expansion state; synchronize the controlled
279
281
  // disclosure from the callback on every render.
@@ -792,8 +792,9 @@ export class AssistantMessageComponent extends Container {
792
792
  return this.#transcriptBlockFinalized;
793
793
  }
794
794
 
795
- /** Whether a figure still waits for the raster of its final source; the transcript holds retirement meanwhile. */
795
+ /** Hold image and figure rendering until the completed block can enter immutable scrollback. */
796
796
  isTranscriptBlockPending(): boolean {
797
+ if (this.#showImages && this.#kittyConversionsAwaited.size > 0) return true;
797
798
  for (const block of this.#figureBlocks.values()) if (block.pending) return true;
798
799
  return false;
799
800
  }
@@ -1615,6 +1616,7 @@ export class AssistantMessageComponent extends Container {
1615
1616
  })
1616
1617
  .catch(() => {
1617
1618
  this.#kittyConversionsAwaited.delete(key);
1619
+ this.#onImageUpdate?.();
1618
1620
  });
1619
1621
  }
1620
1622
  }
@@ -16,6 +16,7 @@ import type { 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";
19
+ import { normalizeInputForPty } from "../tools/bash-interactive";
19
20
  import { getSixelLineMask, isSixelPassthroughEnabled, sanitizeWithOptionalSixelPassthrough } from "../render/sixel";
20
21
  import {
21
22
  buildExecutionFrame,
@@ -286,6 +287,11 @@ export class BashExecutionComponent extends Container {
286
287
  this.#drainPtyQueue();
287
288
  }
288
289
 
290
+ /** Bytes a terminal would send for a key event, honoring the command's cursor-key mode; "" for key releases. */
291
+ encodePtyInput(data: string): string {
292
+ return normalizeInputForPty(data, this.#ptyTerminal?.modes.applicationCursorKeysMode ?? false);
293
+ }
294
+
289
295
  #drainPtyQueue(): void {
290
296
  const terminal = this.#ptyTerminal;
291
297
  if (!terminal || this.#ptyWriting) return;
@@ -6,6 +6,8 @@ export interface ChatTranscriptDisplayPreferences {
6
6
  cacheMissMarker: boolean;
7
7
  showTokenUsage: boolean;
8
8
  showTurnTime: boolean;
9
+ /** Max rows a collapsed advisor card shows below its header; 0 = no cap. */
10
+ advisorCollapsedLines: number;
9
11
  }
10
12
 
11
13
  /** Current transcript display preferences. */
@@ -16,6 +18,7 @@ export const chatTranscriptDisplayPreferences: ChatTranscriptDisplayPreferences
16
18
  cacheMissMarker: false,
17
19
  showTokenUsage: false,
18
20
  showTurnTime: false,
21
+ advisorCollapsedLines: 0,
19
22
  };
20
23
 
21
24
  /** Apply host display preferences without pulling settings into the renderer. */
@@ -711,14 +711,15 @@ export class ToolExecutionComponent extends Container {
711
711
  // Convert async - catch errors from processing
712
712
  convertImageToPngShared(image)
713
713
  .then(converted => {
714
+ this.#kittyConversionsAwaited.delete(key);
714
715
  this.#kittyConverted.set(key, converted);
715
716
  this.#displayInputVersion++;
716
717
  this.#updateDisplay();
717
718
  this.#ui.requestRender();
718
719
  })
719
720
  .catch(() => {
720
- // Ignore conversion failures - display will use original image format
721
721
  this.#kittyConversionsAwaited.delete(key);
722
+ this.#ui.requestRender();
722
723
  });
723
724
  }
724
725
  }
@@ -859,9 +860,12 @@ export class ToolExecutionComponent extends Container {
859
860
  }
860
861
  }
861
862
 
862
- /** Whether the result's figure still waits for its raster; the transcript holds retirement meanwhile. */
863
+ /** Hold visible results until their figures and Kitty images are ready for immutable scrollback. */
863
864
  isTranscriptBlockPending(): boolean {
864
- return this.#toolActivityVisible && this.#figure?.pending === true;
865
+ return (
866
+ this.#toolActivityVisible &&
867
+ ((this.#showImages && this.#kittyConversionsAwaited.size > 0) || this.#figure?.pending === true)
868
+ );
865
869
  }
866
870
 
867
871
  /**
@@ -0,0 +1,62 @@
1
+ /** Pure occupancy calculations for contiguous token segments, independent of themes and renderers. */
2
+ export interface ContextMapSegment<T> {
3
+ readonly id: T;
4
+ readonly tokens: number;
5
+ }
6
+
7
+ export interface ContextMapCell<T> {
8
+ readonly id: T;
9
+ readonly full: boolean;
10
+ }
11
+
12
+ /**
13
+ * Lays `segments` out in order over a `columns` × `rows` grid representing `windowTokens`; each cell takes the
14
+ * segment covering most of it and is `full` at 70% coverage or more. Returns `[]` when the window or grid
15
+ * size is unusable, and omits cells no segment touches.
16
+ */
17
+ export function planContextMap<T>(
18
+ segments: readonly ContextMapSegment<T>[],
19
+ windowTokens: number,
20
+ columns: number,
21
+ rows: number,
22
+ ): ContextMapCell<T>[] {
23
+ if (
24
+ !Number.isFinite(windowTokens) ||
25
+ windowTokens <= 0 ||
26
+ !Number.isInteger(columns) ||
27
+ !Number.isInteger(rows) ||
28
+ columns <= 0 ||
29
+ rows <= 0
30
+ )
31
+ return [];
32
+ const cellCount = columns * rows;
33
+ const tokensPerCell = windowTokens / cellCount;
34
+ const ranges: { id: T; start: number; end: number }[] = [];
35
+ let cursor = 0;
36
+ for (const segment of segments) {
37
+ if (!Number.isFinite(segment.tokens) || segment.tokens <= 0) continue;
38
+ const end = Math.min(windowTokens, cursor + segment.tokens);
39
+ if (end > cursor) ranges.push({ id: segment.id, start: cursor, end });
40
+ cursor = end;
41
+ if (cursor >= windowTokens) break;
42
+ }
43
+ const cells: ContextMapCell<T>[] = [];
44
+ let first = 0;
45
+ for (let index = 0; index < cellCount; index++) {
46
+ const start = index * tokensPerCell;
47
+ const end = (index + 1) * tokensPerCell;
48
+ while (first < ranges.length && ranges[first]!.end <= start) first++;
49
+ let winner: T | undefined;
50
+ let overlap = 0;
51
+ for (let part = first; part < ranges.length && ranges[part]!.start < end; part++) {
52
+ const range = ranges[part]!;
53
+ const covered = Math.min(end, range.end) - Math.max(start, range.start);
54
+ if (covered > overlap) {
55
+ overlap = covered;
56
+ winner = range.id;
57
+ }
58
+ }
59
+ if (winner !== undefined) cells.push({ id: winner, full: overlap / tokensPerCell >= 0.7 });
60
+ }
61
+ return cells;
62
+ }
@@ -0,0 +1,26 @@
1
+ import type { ThemeColor } from "../theme";
2
+ import type { SymbolKey } from "../theme/symbols";
3
+ import type { ContextCategoryId } from "./context-view-model";
4
+
5
+ /** Shared category styles for maps and interactive legends; resolve glyphs at render time. */
6
+ const SYSTEM = { color: "accent", symbol: "context.content" } as const;
7
+ const DETAILS = { color: "customMessageLabel", symbol: "context.content" } as const;
8
+ const TOOLS = { color: "warning", symbol: "context.content" } as const;
9
+ const SKILLS = { color: "success", symbol: "context.content" } as const;
10
+ const MESSAGES = { color: "userMessageText", symbol: "context.messages" } as const;
11
+ const OUTPUT = { color: "muted", symbol: "context.messages" } as const;
12
+ export const CONTEXT_PRESENTATION = {
13
+ "system-prompt": SYSTEM,
14
+ skills: SKILLS,
15
+ "built-in-tools": TOOLS,
16
+ "custom-tools": DETAILS,
17
+ // Shares the System Prompt color; the message glyph tells them apart in the map.
18
+ "user-messages": { color: "accent", symbol: "context.messages" },
19
+ "assistant-messages": MESSAGES,
20
+ "assistant-thinking": DETAILS,
21
+ "tool-calls": TOOLS,
22
+ "tool-output": OUTPUT,
23
+ buffer: { color: "warning", symbol: "context.buffer" },
24
+ free: { color: "dim", symbol: "context.free" },
25
+ } as const satisfies Record<ContextPresentationId, { color: ThemeColor; symbol: SymbolKey }>;
26
+ export type ContextPresentationId = ContextCategoryId | "buffer" | "free";
@@ -0,0 +1,73 @@
1
+ /** Context snapshot shapes shared by the session snapshot builder and the context viewer. */
2
+ import type { ContextBreakdown } from "../status-line/context-usage";
3
+
4
+ interface ContextCategoryDescriptor {
5
+ readonly id: string;
6
+ readonly label: string;
7
+ /** Overview row label when the Overview weight differs from the category's content. */
8
+ readonly overviewLabel?: string;
9
+ /** Listed with its weight but never opened. */
10
+ readonly openable?: false;
11
+ }
12
+
13
+ /** Context categories in display order. */
14
+ export const CONTEXT_CATEGORIES = [
15
+ // The Overview weight excludes the Skills listing; content views keep complete system blocks.
16
+ { id: "system-prompt", label: "System Prompt", overviewLabel: "System Prompt (excluding Skills)" },
17
+ // An estimate of the prompt's skill listing, without entries of its own.
18
+ { id: "skills", label: "Skills", openable: false },
19
+ { id: "built-in-tools", label: "Built-in Tools" },
20
+ { id: "custom-tools", label: "Custom Tools" },
21
+ { id: "user-messages", label: "User / Developer Messages" },
22
+ { id: "assistant-messages", label: "Assistant Messages" },
23
+ { id: "assistant-thinking", label: "Assistant Thinking" },
24
+ { id: "tool-calls", label: "Tool Calls" },
25
+ { id: "tool-output", label: "Tool Output" },
26
+ ] as const satisfies readonly ContextCategoryDescriptor[];
27
+
28
+ export type ContextCategoryId = (typeof CONTEXT_CATEGORIES)[number]["id"];
29
+
30
+ const DESCRIPTORS_BY_ID = new Map<ContextCategoryId, ContextCategoryDescriptor>(
31
+ CONTEXT_CATEGORIES.map(descriptor => [descriptor.id, descriptor]),
32
+ );
33
+
34
+ /** Descriptor of a category id: its label, optional Overview label, and whether it can be opened. */
35
+ export function contextCategoryDescriptor(id: ContextCategoryId): ContextCategoryDescriptor {
36
+ return DESCRIPTORS_BY_ID.get(id)!;
37
+ }
38
+
39
+ export interface ContextThinkingGroup {
40
+ readonly id: string;
41
+ readonly label: string;
42
+ readonly tokens: number;
43
+ readonly interrupted?: boolean;
44
+ }
45
+
46
+ export interface ContextViewEntry {
47
+ readonly id: string;
48
+ readonly label: string;
49
+ readonly text: string;
50
+ /** Aggregation weight for the unscaled replay estimate; shared blocks contribute through their group. */
51
+ readonly tokens: number;
52
+ readonly interrupted?: boolean;
53
+ readonly thinkingGroup?: ContextThinkingGroup;
54
+ readonly path?: string;
55
+ readonly info?: string;
56
+ readonly timestamp?: number;
57
+ readonly callId?: string;
58
+ }
59
+
60
+ export interface ContextViewCategory {
61
+ id: ContextCategoryId;
62
+ key?: string;
63
+ label: string;
64
+ /** Overview attribution weight, not reported usage; may differ from the entry sum (e.g. Skills subtraction). */
65
+ tokens: number;
66
+ entries: ContextViewEntry[];
67
+ children?: ContextViewCategory[];
68
+ }
69
+
70
+ export interface ContextViewData {
71
+ readonly breakdown: ContextBreakdown;
72
+ readonly categories: readonly ContextViewCategory[];
73
+ }