@oh-my-pi/pi-tui 18.8.7 → 18.8.9

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 (78) hide show
  1. package/CHANGELOG.md +52 -86
  2. package/dist/types/charts/chart-plan.d.ts +11 -5
  3. package/dist/types/charts/chart-svg.d.ts +11 -7
  4. package/dist/types/charts/table-data.d.ts +6 -1
  5. package/dist/types/chat/execution-shared.d.ts +6 -5
  6. package/dist/types/keybinding-matchers.d.ts +3 -0
  7. package/dist/types/kitty-graphics.d.ts +2 -2
  8. package/dist/types/multiplexers/cmux.d.ts +16 -0
  9. package/dist/types/multiplexers/herdr.d.ts +26 -0
  10. package/dist/types/multiplexers/notify.d.ts +13 -0
  11. package/dist/types/multiplexers/orca.d.ts +12 -0
  12. package/dist/types/multiplexers/screen.d.ts +9 -0
  13. package/dist/types/multiplexers/tmux.d.ts +29 -0
  14. package/dist/types/multiplexers/types.d.ts +100 -0
  15. package/dist/types/multiplexers/wmux.d.ts +12 -0
  16. package/dist/types/multiplexers/zellij.d.ts +12 -0
  17. package/dist/types/overlays/annotation-overlay.d.ts +12 -0
  18. package/dist/types/overlays/annotation-types.d.ts +2 -0
  19. package/dist/types/overlays/hook-selector.d.ts +9 -4
  20. package/dist/types/overlays/model-browser.d.ts +2 -2
  21. package/dist/types/overlays/model-hub.d.ts +21 -4
  22. package/dist/types/overlays/session-observer-registry.d.ts +2 -0
  23. package/dist/types/overlays/usage-dashboard.d.ts +3 -1
  24. package/dist/types/status-line/types.d.ts +3 -3
  25. package/dist/types/terminal-capabilities.d.ts +24 -32
  26. package/dist/types/terminal-multiplexer.d.ts +127 -14
  27. package/dist/types/terminal.d.ts +5 -4
  28. package/dist/types/theme/symbols.d.ts +1 -1
  29. package/dist/types/tmux.d.ts +1 -4
  30. package/dist/types/tools/eval.d.ts +1 -1
  31. package/dist/types/tools/native-view.d.ts +47 -0
  32. package/dist/types/tools/renderer.d.ts +15 -2
  33. package/dist/types/tui.d.ts +20 -0
  34. package/dist/types/vim.d.ts +19 -5
  35. package/package.json +9 -9
  36. package/src/charts/chart-plan.ts +152 -12
  37. package/src/charts/chart-svg.ts +479 -126
  38. package/src/charts/table-data.ts +43 -8
  39. package/src/chat/execution-shared.ts +42 -25
  40. package/src/chat/svg-figure.ts +45 -8
  41. package/src/chat/tool-execution.ts +9 -2
  42. package/src/components/editor.ts +182 -5
  43. package/src/keybinding-matchers.ts +6 -2
  44. package/src/kitty-graphics.ts +4 -4
  45. package/src/multiplexers/cmux.ts +29 -0
  46. package/src/multiplexers/herdr.ts +64 -0
  47. package/src/multiplexers/notify.ts +24 -0
  48. package/src/multiplexers/orca.ts +16 -0
  49. package/src/multiplexers/screen.ts +15 -0
  50. package/src/multiplexers/tmux.ts +40 -0
  51. package/src/multiplexers/types.ts +110 -0
  52. package/src/multiplexers/wmux.ts +16 -0
  53. package/src/multiplexers/zellij.ts +24 -0
  54. package/src/overlays/agent-hub.ts +7 -3
  55. package/src/overlays/annotation-overlay.ts +87 -6
  56. package/src/overlays/annotation-types.ts +2 -0
  57. package/src/overlays/hook-selector.ts +12 -5
  58. package/src/overlays/model-browser.ts +3 -3
  59. package/src/overlays/model-hub.ts +107 -27
  60. package/src/overlays/session-observer-registry.ts +10 -2
  61. package/src/overlays/usage-dashboard.ts +70 -23
  62. package/src/prompt/composer.ts +1 -0
  63. package/src/status-line/component.ts +12 -8
  64. package/src/status-line/segments.ts +10 -11
  65. package/src/status-line/types.ts +3 -3
  66. package/src/terminal-capabilities.ts +65 -181
  67. package/src/terminal-multiplexer.ts +112 -33
  68. package/src/terminal.ts +34 -28
  69. package/src/theme/session-color.ts +1 -7
  70. package/src/theme/symbols.ts +5 -0
  71. package/src/theme/theme.ts +7 -4
  72. package/src/tmux.ts +4 -11
  73. package/src/tools/bash.ts +82 -31
  74. package/src/tools/eval.ts +149 -123
  75. package/src/tools/native-view.ts +102 -1
  76. package/src/tools/renderer.ts +15 -2
  77. package/src/tui.ts +86 -12
  78. package/src/vim.ts +333 -18
@@ -65,13 +65,30 @@ export interface ModelHubCallbacks {
65
65
  /** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
66
66
  onCycleOrderChange?: (order: string[]) => void;
67
67
  /**
68
- * Persist typed text as `model`'s own compaction point: a token count (`90000`,
69
- * `90k`, `1M`), a percentage (`80%`), or empty to reset. Returns an error
70
- * message for input it rejects, which keeps the field open.
68
+ * Persist typed text as `model`'s own compaction limit: a token base the
69
+ * policy scales (`400k`), a fixed trigger (`f400k`), a percentage (`80%`), or
70
+ * empty to reset. Rejected input returns `{ kind: "error" }`; input that
71
+ * needs acknowledgement (one that opens the extended window) returns
72
+ * `{ kind: "confirm" }` until called again with the same text and
73
+ * `confirmed`. Either keeps the field open.
71
74
  */
72
- onCompactionPointChange?: (model: Model, input: string) => string | undefined;
75
+ onCompactionPointChange?: (model: Model, input: string, confirmed: boolean) => CompactionPointChangeResult | undefined;
76
+ /**
77
+ * One short line on where `model` would compact with the typed limit
78
+ * (`compacts at 340K · 85% of 400K base`), shown beside the field while
79
+ * typing; undefined for input that does not parse or fit.
80
+ */
81
+ previewCompactionPoint?: (model: Model, input: string) => string | undefined;
73
82
  onCancel: () => void;
74
83
  }
84
+ /** Why {@link ModelHubCallbacks.onCompactionPointChange} kept the field open. */
85
+ export type CompactionPointChangeResult = {
86
+ kind: "error";
87
+ message: string;
88
+ } | {
89
+ kind: "confirm";
90
+ message: string;
91
+ };
75
92
  export interface ModelHubOptions {
76
93
  /** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
77
94
  initialProviderId?: string;
@@ -58,6 +58,8 @@ export interface ObservableSession {
58
58
  lastUpdate: number;
59
59
  /** Latest progress snapshot from the subagent executor */
60
60
  progress?: AgentProgress;
61
+ /** `Date.now()` when {@link progress} arrived; its `durationMs` was current then. */
62
+ progressAt?: number;
61
63
  }
62
64
  /** Coarse source of an observer change; callers use it to separate lifecycle work from high-frequency progress. */
63
65
  export type SessionObserverChangeKind = "main" | "reset" | "lifecycle" | "progress";
@@ -35,8 +35,10 @@ export interface ProviderCard {
35
35
  unavailableAccounts: string[];
36
36
  /** Window rows in provider-declared order (e.g. 5h → weekly → monthly); the fullest CARD_MAX_WINDOWS lead. */
37
37
  windows: CardWindowRow[];
38
- /** True when every account reports no limits (e.g. enterprise plans). */
38
+ /** True when every account reports no limits (e.g. enterprise plans, providers without a quota API). */
39
39
  unlimited: boolean;
40
+ /** Distinct report notes across accounts (e.g. why a provider exposes no quota windows). */
41
+ notes: string[];
40
42
  /** True when nothing is used anywhere (or there are no limits): collapses to a tick. */
41
43
  idle: boolean;
42
44
  resetCredits?: {
@@ -112,7 +112,7 @@ export interface SegmentContext {
112
112
  } | null;
113
113
  /** Modal editing state, or null when `tui.vimMode` is off. */
114
114
  vim: {
115
- mode: "insert" | "normal" | "visual" | "visual-line";
115
+ mode: "insert" | "normal" | "visual" | "visual-line" | "replace";
116
116
  /** Half-typed operator/count (`"2d"`), empty when nothing is pending. */
117
117
  pending: string;
118
118
  /** Lines spanned by the active Visual selection; 0 outside Visual modes. */
@@ -245,8 +245,8 @@ export interface SegmentView {
245
245
  }
246
246
  /**
247
247
  * The status line's facts for the native composer. A TSP terminal shows no
248
- * status strip: the tab title carries the session, the pane header the path
249
- * and branch, and the composer the rest.
248
+ * status strip: the tab title carries the session, the pane header the path,
249
+ * and the composer the rest (the git branch among its facts).
250
250
  */
251
251
  export interface ComposerFacts {
252
252
  /**
@@ -1,6 +1,6 @@
1
1
  import type { HangulCompatibilityJamoWidth } from "./utils.js";
2
2
  export * from "./terminal-multiplexer.js";
3
- export { isInsideTmux, wrapTmuxPassthrough } from "./tmux.js";
3
+ export { wrapTmuxPassthrough } from "./tmux.js";
4
4
  export declare enum ImageProtocol {
5
5
  Kitty = "\u001B_G",
6
6
  Iterm2 = "\u001B]1337;File=",
@@ -55,12 +55,6 @@ export declare class TerminalInfo {
55
55
  formatNotification(message: string | TerminalNotification): string;
56
56
  sendNotification(message: string | TerminalNotification): void;
57
57
  }
58
- /**
59
- * Whether the agent process is running inside a Zellij session. Read fresh on
60
- * each call (like {@link isInsideTmux}) so a session attached/detached mid-run
61
- * is observed and tests can toggle `Bun.env.ZELLIJ` per case.
62
- */
63
- export declare function isInsideZellij(env?: NodeJS.ProcessEnv): boolean;
64
58
  /**
65
59
  * Whether the agent process runs in an SSH session, so the terminal emulator
66
60
  * is remote and host-local input/keyboard modes cannot be assumed.
@@ -96,12 +90,12 @@ export declare function synchronizedOutputUserOverride(env?: NodeJS.ProcessEnv):
96
90
  * 2. Positive `TERM_FEATURES` advertisement (`Sy`) — survives SSH/mux wrapping.
97
91
  * 3. Windows Terminal (1.24+) via `WT_SESSION`, on native win32 and the
98
92
  * WSL/SSH-fronted host alike.
99
- * 4. Herdr panes. Herdr is otherwise treated as a multiplexer so leaked
100
- * kitty/ghostty identities cannot enable placeholder graphics, but its
101
- * pane VTE is libghostty and already suppresses compositing while DEC 2026
102
- * is set. Leaving sync off lets CUP-diff paints and split write(2) chunks
103
- * composite as dirty-row patches — the live viewport tears, with the top
104
- * frozen while only the bottom refreshes.
93
+ * 4. A multiplexer session whose pane VTE honors DEC 2026 regardless of its
94
+ * DECRQM reply (Herdr's libghostty pane). Such a multiplexer still owns
95
+ * the grid, so leaked kitty/ghostty identities cannot enable placeholder
96
+ * graphics, but leaving sync off lets CUP-diff paints and split write(2)
97
+ * chunks composite as dirty-row patches — the live viewport tears, with
98
+ * the top frozen while only the bottom refreshes.
105
99
  * 5. Known direct terminals with confirmed support. SSH does *not* disable —
106
100
  * DEC 2026 passes through SSH when the outer terminal honors it.
107
101
  * 6. Everything else starts off, including risky multiplexers; the runtime
@@ -159,27 +153,25 @@ export declare function hyperlinksUserOverride(env?: NodeJS.ProcessEnv): boolean
159
153
  * Policy (highest precedence first):
160
154
  * 1. Explicit user override (`PI_NO_HYPERLINKS=1` off, `PI_FORCE_HYPERLINKS=1`
161
155
  * on). Opt-out wins ties.
162
- * 2. Herdr pane with no nested screen/tmux: on. Herdr hides the outer
163
- * terminal (`TERM=xterm-256color`, no `TERM_PROGRAM`), but it renders
164
- * OSC 8 in its own grid and opens links itself on Ctrl+click, so the
165
- * outer terminal's support does not matter.
156
+ * 2. Every multiplexer session with a hyperlink policy renders OSC 8 itself
157
+ * (`render`, e.g. Herdr): on. Herdr hides the outer terminal
158
+ * (`TERM=xterm-256color`, no `TERM_PROGRAM`), so the outer terminal's
159
+ * support does not matter.
166
160
  * 3. Static terminal capability — terminals whose {@link TerminalInfo} marks
167
161
  * `hyperlinks: false` (e.g. `base`) stay off unless the user forced on.
168
- * 4. GNU screen's explicit session marker (`STY`) always off, even if tmux is
169
- * also present: a screen layer anywhere in the path cannot forward OSC 8.
170
- * 5. tmux session (`TMUX` set): enabled when tmux self-reports >= 3.4 via
171
- * `TERM_PROGRAM_VERSION` (tmux 3.4 stores OSC 8 as a cell attribute and
172
- * forwards it to outer terminals whose `terminal-features` include
173
- * `hyperlinks`). Older or unknown versions stay off; on outer terminals
174
- * without the feature configured, tmux silently drops the sequence —
175
- * identical to today. Checked before the screen-family TERM heuristic
176
- * because tmux's historical `default-terminal` is `screen-256color`, so
177
- * `TERM=screen*` inside a tmux session must NOT short-circuit to off.
178
- * 6. screen-family TERM without `TMUX` always off: screen never gained OSC 8
179
- * support.
180
- * 7. tmux-family TERM without `TMUX` env — unusual (e.g. inspection scripts);
181
- * no version available, so off.
182
- * 8. Otherwise honor the static terminal capability.
162
+ * 4. A session that never forwards OSC 8 (`drop`, e.g. GNU screen's `STY`)
163
+ * always off, even when a forwarding multiplexer is also present.
164
+ * 5. Forwarding sessions (`forward`, e.g. `TMUX`): enabled when every one
165
+ * self-reports at least its forwarding version via `TERM_PROGRAM` /
166
+ * `TERM_PROGRAM_VERSION` (tmux 3.4). Older or unknown versions stay off;
167
+ * on outer terminals without the feature configured, tmux silently drops
168
+ * the sequence. Checked before the TERM heuristic because tmux's
169
+ * historical `default-terminal` is `screen-256color`, so `TERM=screen*`
170
+ * inside a tmux session must NOT short-circuit to off.
171
+ * 6. TERM naming a multiplexer whose session markers were stripped: off,
172
+ * since no session proves the multiplexer forwards OSC 8 (screen never
173
+ * does; tmux has no version to check).
174
+ * 7. Otherwise honor the static terminal capability.
183
175
  */
184
176
  export declare function shouldEnableHyperlinksByDefault(env?: NodeJS.ProcessEnv, terminalId?: TerminalId): boolean;
185
177
  /**
@@ -1,20 +1,133 @@
1
- /** True when this process is running inside a Herdr pane. */
2
- export declare function isInsideHerdr(env?: NodeJS.ProcessEnv): boolean;
3
- /** Terminal multiplexers omp recognizes as owning the screen grid. */
4
- export type TerminalMultiplexer = "herdr" | "tmux" | "screen" | "zellij" | "cmux" | "wmux";
1
+ import type { TerminalMultiplexerModule, TerminalMultiplexerNotificationRequest } from "./multiplexers/types.js";
5
2
  /**
6
- * Classify which terminal multiplexer owns the current screen grid, or `null`
7
- * for a direct terminal. Single source of truth for both the render-path gate
3
+ * Every multiplexer omp recognizes, in classification order within each
4
+ * precedence tier. Adding a multiplexer means adding its module here.
5
+ */
6
+ declare const registry: readonly [{
7
+ readonly id: "herdr";
8
+ readonly precedence: "session";
9
+ readonly sessionEnvKeys: readonly ["HERDR_ENV", "HERDR_PANE_ID", "HERDR_TAB_ID", "HERDR_WORKSPACE_ID"];
10
+ readonly ownsScreenGrid: true;
11
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
12
+ readonly notifier: {
13
+ readonly tier: "pane";
14
+ readonly send: ({ message, env }: TerminalMultiplexerNotificationRequest) => boolean;
15
+ };
16
+ readonly hyperlinks: "render";
17
+ readonly honorsSynchronizedOutput: true;
18
+ readonly imagesRequireOverride: true;
19
+ }, {
20
+ readonly id: "tmux";
21
+ readonly precedence: "session";
22
+ readonly sessionEnvKeys: readonly ["TMUX"];
23
+ readonly termPrefix: "tmux";
24
+ readonly ownsScreenGrid: true;
25
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
26
+ readonly notifier: {
27
+ readonly tier: "inBand";
28
+ readonly send: ({ sequence, write }: TerminalMultiplexerNotificationRequest) => boolean;
29
+ };
30
+ readonly hyperlinks: {
31
+ readonly forward: {
32
+ readonly termProgram: "tmux";
33
+ readonly major: 3;
34
+ readonly minor: 4;
35
+ };
36
+ };
37
+ readonly backgroundColorCache: {
38
+ readonly passthrough: typeof import("./tmux.js").wrapTmuxPassthrough;
39
+ readonly settleMs: 100;
40
+ };
41
+ readonly expandsRepPadding: true;
42
+ readonly expiresSynchronizedOutput: true;
43
+ readonly altRestoreEndsSynchronizedOutput: true;
44
+ readonly growsBeforeSigwinch: true;
45
+ }, {
46
+ readonly id: "screen";
47
+ readonly precedence: "session";
48
+ readonly sessionEnvKeys: readonly ["STY"];
49
+ readonly termPrefix: "screen";
50
+ readonly ownsScreenGrid: true;
51
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
52
+ readonly hyperlinks: "drop";
53
+ }, {
54
+ readonly id: "zellij";
55
+ readonly precedence: "session";
56
+ readonly sessionEnvKeys: readonly ["ZELLIJ"];
57
+ readonly ownsScreenGrid: true;
58
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
59
+ readonly notifier: {
60
+ readonly tier: "inBand";
61
+ readonly send: ({ sequence, write }: TerminalMultiplexerNotificationRequest) => boolean;
62
+ };
63
+ readonly backgroundQueryBrokenOn: readonly ["darwin"];
64
+ }, {
65
+ readonly id: "cmux";
66
+ readonly precedence: "session";
67
+ readonly sessionEnvKeys: readonly ["CMUX_WORKSPACE_ID", "CMUX_SURFACE_ID", "CMUX_REMOTE_TRANSPORT"];
68
+ readonly ownsScreenGrid: true;
69
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
70
+ readonly notifier: {
71
+ readonly tier: "surface";
72
+ readonly send: ({ message, env }: TerminalMultiplexerNotificationRequest) => boolean;
73
+ };
74
+ }, {
75
+ readonly id: "wmux";
76
+ readonly precedence: "session";
77
+ readonly sessionEnvKeys: readonly ["WMUX", "WMUX_SURFACE_ID"];
78
+ readonly ownsScreenGrid: true;
79
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
80
+ }, {
81
+ readonly id: "orca";
82
+ readonly precedence: "outerApp";
83
+ readonly sessionEnvKeys: readonly ["ORCA_PANE_KEY", "ORCA_WORKTREE_ID"];
84
+ readonly ownsScreenGrid: false;
85
+ readonly isInside: (env?: NodeJS.ProcessEnv) => boolean;
86
+ }];
87
+ /** Terminal multiplexers omp recognizes. */
88
+ export type TerminalMultiplexer = (typeof registry)[number]["id"];
89
+ /** Every environment variable multiplexer classification reads, including the TERM fallback. */
90
+ export declare const TERMINAL_MULTIPLEXER_ENV_KEYS: readonly string[];
91
+ /**
92
+ * Whether an explicit session marker identifies the current provider.
93
+ *
94
+ * TERM is intentionally excluded: it is a classification fallback, not proof
95
+ * that a particular multiplexer session owns the current grid.
96
+ */
97
+ export declare function hasTerminalMultiplexerSession(multiplexer: TerminalMultiplexer, env?: NodeJS.ProcessEnv): boolean;
98
+ /**
99
+ * Every multiplexer whose explicit session markers are present, in registry
100
+ * order. Nested sessions all appear; TERM is not consulted.
101
+ */
102
+ export declare function terminalMultiplexerSessions(env?: NodeJS.ProcessEnv): TerminalMultiplexerModule<TerminalMultiplexer>[];
103
+ /**
104
+ * The multiplexer TERM names, whether or not its session markers survived.
105
+ * This is classification's TERM fallback; session markers are not consulted.
106
+ */
107
+ export declare function terminalMultiplexerForTerm(env?: NodeJS.ProcessEnv): TerminalMultiplexerModule<TerminalMultiplexer> | undefined;
108
+ /**
109
+ * Registry entry for the multiplexer hosting the current process, or
110
+ * `undefined` for a direct terminal. Same precedence as
111
+ * {@link classifyTerminalMultiplexer}.
112
+ */
113
+ export declare function classifyTerminalMultiplexerModule(env?: NodeJS.ProcessEnv): TerminalMultiplexerModule<TerminalMultiplexer> | undefined;
114
+ /**
115
+ * Classify which terminal multiplexer hosts the current process, or `null` for
116
+ * a direct terminal. Single source of truth for both the render-path gate
8
117
  * (`isInsideTerminalMultiplexer`) and the debug snapshot label.
9
118
  *
10
- * TMUX/STY/ZELLIJ, Herdr, and the CMUX/WMUX workspace/surface/remote-transport
11
- * markers are authoritative session signals. TERM can also survive when those
12
- * are stripped (`sudo` without -E, `su`, env-sanitizing launchers/ssh). Do not
13
- * use CMUX_SOCKET_PATH / WMUX_CLI / WMUX_PIPE here: they are CLI socket/path
14
- * overrides and can be set outside a CMUX/WMUX terminal. wmux is a Windows
15
- * multiplexer (Electron + xterm.js) modeled on cmux/herdr that repaints its
16
- * pane in place and exports WMUX=1 plus a native WMUX_SURFACE_ID.
119
+ * Session markers are authoritative. TERM can also survive when those markers
120
+ * are stripped (`sudo` without -E, `su`, env-sanitizing launchers/ssh), so a
121
+ * `tmux`/`screen` TERM prefix comes next. Outer applications such as Orca rank
122
+ * last so a multiplexer running inside them wins.
17
123
  */
18
124
  export declare function classifyTerminalMultiplexer(env?: NodeJS.ProcessEnv): TerminalMultiplexer | null;
19
- /** True when a terminal multiplexer owns the current screen grid. */
125
+ /** True when the classified multiplexer owns the current screen grid. */
20
126
  export declare function isInsideTerminalMultiplexer(env?: NodeJS.ProcessEnv): boolean;
127
+ /**
128
+ * Offer a notification to every active session's notifier, innermost tier
129
+ * first. Returns whether one delivered it; otherwise the terminal fallback
130
+ * applies unchanged.
131
+ */
132
+ export declare function routeTerminalMultiplexerNotification(request: TerminalMultiplexerNotificationRequest): boolean;
133
+ export {};
@@ -298,10 +298,11 @@ export declare class ProcessTerminal implements Terminal {
298
298
  onAppearanceReport(callback: (appearance: TerminalAppearance, requestToken?: TerminalAppearanceRequestToken) => void): () => void;
299
299
  /**
300
300
  * Re-query the terminal background through the startup DA1-sentinel FIFO,
301
- * pending/queued gating, parsing, dedup, and appearance callbacks. Inside
302
- * tmux, only this explicit path first passes an OSC 11 query to the outer
303
- * terminal, waits briefly for tmux to consume the response into its cache,
304
- * then reads that cache with a direct query. The outer query deliberately has
301
+ * pending/queued gating, parsing, dedup, and appearance callbacks. Inside a
302
+ * multiplexer that answers OSC 11 from a cache (tmux), only this explicit
303
+ * path first passes an OSC 11 query to the outer terminal, waits briefly for
304
+ * the multiplexer to consume the response into its cache, then reads that
305
+ * cache with a direct query. The outer query deliberately has
305
306
  * no DA1 sentinel: multiplexers can decode a fragmented DA1 response as a key
306
307
  * sequence and leak the remaining bytes into the editor. Startup and Mode 2031
307
308
  * probes remain direct. Suppressed while inactive, headless, or after teardown.
@@ -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
@@ -1,7 +1,4 @@
1
- /** Whether the process is running inside a tmux session. */
2
- export declare function isInsideTmux(env?: NodeJS.ProcessEnv): boolean;
3
- /** Wrap a control sequence in tmux's DCS passthrough envelope. */
4
- export declare function wrapTmuxPassthrough(payload: string): string;
1
+ export { wrapTmuxPassthrough } from "./multiplexers/tmux.js";
5
2
  /** Pass a control sequence through tmux, leaving direct-terminal output unchanged. */
6
3
  export declare function wrapTmuxPassthroughIfNeeded(payload: string, env?: NodeJS.ProcessEnv): string;
7
4
  /**
@@ -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. */
@@ -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
  }
@@ -469,6 +473,22 @@ export declare class TUI extends Container {
469
473
  * path. Idempotent; no-op when input was never deferred.
470
474
  */
471
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;
472
492
  addStartListener(listener: StartListener): () => void;
473
493
  addInputListener(listener: InputListener): () => void;
474
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.7",
4
+ "version": "18.8.9",
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.7",
45
- "@oh-my-pi/pi-agent-core": "18.8.7",
46
- "@oh-my-pi/pi-ai": "18.8.7",
47
- "@oh-my-pi/pi-catalog": "18.8.7",
48
- "@oh-my-pi/pi-natives": "18.8.7",
49
- "@oh-my-pi/pi-utils": "18.8.7",
50
- "@oh-my-pi/pi-wire": "18.8.7",
51
- "@oh-my-pi/snapcompact": "18.8.7"
44
+ "@oh-my-pi/omptype": "18.8.9",
45
+ "@oh-my-pi/pi-agent-core": "18.8.9",
46
+ "@oh-my-pi/pi-ai": "18.8.9",
47
+ "@oh-my-pi/pi-catalog": "18.8.9",
48
+ "@oh-my-pi/pi-natives": "18.8.9",
49
+ "@oh-my-pi/pi-utils": "18.8.9",
50
+ "@oh-my-pi/pi-wire": "18.8.9",
51
+ "@oh-my-pi/snapcompact": "18.8.9"
52
52
  },
53
53
  "devDependencies": {
54
54
  "kitty-vt-wasm": "^0.2.0"