@oh-my-pi/pi-tui 18.6.0 → 18.6.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.
- package/CHANGELOG.md +32 -0
- package/README.md +6 -2
- package/dist/types/app-keybindings.d.ts +6 -1
- package/dist/types/apps/git/git-tui.d.ts +3 -2
- package/dist/types/chat/assistant-message.d.ts +6 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +1 -0
- package/dist/types/chat/user-message.d.ts +2 -2
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/components/editor.d.ts +4 -0
- package/dist/types/components/image.d.ts +14 -0
- package/dist/types/components/input.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/key-hint-format.d.ts +8 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +2 -2
- package/dist/types/native/encode.d.ts +4 -3
- package/dist/types/native/node.d.ts +8 -1
- package/dist/types/native/overlay.d.ts +3 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub.d.ts +1 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -0
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/usage-dashboard.d.ts +4 -4
- package/dist/types/prompt/custom-editor.d.ts +10 -4
- package/dist/types/prompt/interactive-context-helpers.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/terminal-capabilities.d.ts +13 -0
- package/dist/types/tui.d.ts +5 -0
- package/package.json +9 -9
- package/src/app-keybindings.ts +6 -1
- package/src/apps/git/diff-pane.ts +70 -18
- package/src/apps/git/git-tui.ts +21 -14
- package/src/apps/git/sidebar.ts +6 -1
- package/src/chat/assistant-message.ts +26 -4
- package/src/chat/chat-transcript-builder.ts +4 -0
- package/src/chat/tool-execution.ts +6 -1
- package/src/chat/user-message.ts +16 -5
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.ts +6 -3
- package/src/components/editor.ts +14 -0
- package/src/components/image.ts +68 -1
- package/src/components/input.ts +14 -6
- package/src/components/loader.ts +10 -17
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.ts +25 -10
- package/src/native/apply.ts +4 -3
- package/src/native/backend.ts +10 -3
- package/src/native/encode.ts +4 -3
- package/src/native/node.ts +8 -1
- package/src/native/overlay.ts +4 -4
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub.ts +4 -0
- package/src/overlays/agent-transcript-viewer.ts +2 -0
- package/src/overlays/btw-history-panel.ts +124 -38
- package/src/overlays/copy-selector.ts +2 -0
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/rewind-selector.ts +2 -0
- package/src/overlays/usage-dashboard.ts +17 -8
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/custom-editor.ts +188 -67
- package/src/prompt/interactive-context-helpers.ts +2 -0
- package/src/prompt/queued-messages.ts +6 -4
- package/src/space-hold.ts +156 -52
- package/src/terminal-capabilities.ts +42 -23
- package/src/tui.ts +36 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.6.3] - 2026-10-06
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- `WorkingRowSpec` no longer takes `rate`: the native working row reads spinner, elapsed time, divider, then the intent, and the tok/s readout moved to the composer bar
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `ComposerNativeState.rate` shows a tok/s readout in the native composer bar after the effort chip, and `ComposerNativeState.thinkingInModel` draws the thinking level as the model chip's icon instead of a separate chip
|
|
14
|
+
- `formatTooltipKey` formats a key for a native tooltip: unicode keycap glyphs whatever the symbol preset, `esc` for Escape
|
|
15
|
+
- `ImageOptions.requestRender` repaints an image without an image budget once its SIXEL encode lands ([#14529](https://github.com/can1357/oh-my-pi/pull/14529) by [@H4vC](https://github.com/H4vC))
|
|
16
|
+
- `AssistantMessageComponent` can keep finished thinking sections expanded via `setExpandThinkingBlocks()`; sections folded by hand stay folded ([#14519](https://github.com/can1357/oh-my-pi/pull/14519) by [@H4vC](https://github.com/H4vC))
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- `/hotkeys` shows the effective `app.stt.pushToTalk` binding, including `Disabled` when unbound ([#6592](https://github.com/can1357/oh-my-pi/pull/6592) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
|
|
21
|
+
- SIXEL images are encoded off the main thread, so showing an image no longer stalls the terminal ([#14529](https://github.com/can1357/oh-my-pi/pull/14529) by [@H4vC](https://github.com/H4vC))
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Fixed native tooltips (composer, working row, queue, pause screen, git and extension dashboards) naming keys with Nerd Font icons Tern's UI font lacks, or as raw key ids (`ctrl+g`); they use keycap glyphs (`⇧⇥`, `⌃G`)
|
|
26
|
+
- Fixed Tern's per-turn usage row showing a 24-hour time on a 12-hour terminal clock; the row and its tooltip now follow the terminal's clock, keeping the tooltip's ISO date ([#14565](https://github.com/can1357/oh-my-pi/pull/14565) by [@wolfiesch](https://github.com/wolfiesch))
|
|
27
|
+
- Fixed hold-Space push-to-talk stopping the instant recording began (showing "No speech detected.") when opening the microphone briefly froze the UI ([#14463](https://github.com/can1357/oh-my-pi/pull/14463) by [@pgkt04](https://github.com/pgkt04))
|
|
28
|
+
|
|
29
|
+
## [18.6.1] - 2026-10-04
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
|
|
33
|
+
- Fixed the Space key in the Git diff pane so it stages or unstages the focused hunk instead of scrolling or acting on the wrong change.
|
|
34
|
+
- Fixed the BTW history sheet in Tern: the history list no longer collapses beside a long answer, the panes lose their foldable `##` headings, and the arrow/page keys scroll the answer ([#14331](https://github.com/can1357/oh-my-pi/pull/14331) by [@H4vC](https://github.com/H4vC))
|
|
35
|
+
- Fixed a single saved side question in the BTW history panel opening on its one-row list: its answer now has focus, and Enter or `f` to follow up jumps to the bottom of the conversation ([#14331](https://github.com/can1357/oh-my-pi/pull/14331) by [@H4vC](https://github.com/H4vC))
|
|
36
|
+
|
|
5
37
|
## [18.6.0] - 2026-10-03
|
|
6
38
|
|
|
7
39
|
### Fixed
|
package/README.md
CHANGED
|
@@ -455,7 +455,7 @@ const spacer = new Spacer(2); // 2 empty lines (default: 1)
|
|
|
455
455
|
|
|
456
456
|
### Image
|
|
457
457
|
|
|
458
|
-
Renders images inline for terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm, and Warp on macOS/Linux)
|
|
458
|
+
Renders images inline for terminals that support the Kitty graphics protocol (Kitty, Ghostty, WezTerm, and Warp on macOS/Linux), iTerm2 inline images or SIXEL. Falls back to a text placeholder on unsupported terminals.
|
|
459
459
|
|
|
460
460
|
```typescript
|
|
461
461
|
interface ImageTheme {
|
|
@@ -466,17 +466,21 @@ interface ImageOptions {
|
|
|
466
466
|
maxWidthCells?: number;
|
|
467
467
|
maxHeightCells?: number;
|
|
468
468
|
filename?: string;
|
|
469
|
+
budget?: ImageBudget; // usually tui.imageBudget
|
|
470
|
+
requestRender?: () => void; // repaint once a SIXEL encode lands; defaults to the budget's
|
|
469
471
|
}
|
|
470
472
|
|
|
471
473
|
const image = new Image(
|
|
472
474
|
base64Data, // base64-encoded image data
|
|
473
475
|
"image/png", // MIME type
|
|
474
476
|
theme, // ImageTheme
|
|
475
|
-
options, // optional ImageOptions
|
|
477
|
+
{ ...options, budget: tui.imageBudget }, // optional ImageOptions
|
|
476
478
|
);
|
|
477
479
|
tui.addChild(image);
|
|
478
480
|
```
|
|
479
481
|
|
|
482
|
+
SIXEL images encode off the JavaScript thread: the first render reserves the image's rows and the image appears on the repaint `budget` or `requestRender` triggers once the encode lands. Without either, it waits for an unrelated repaint.
|
|
483
|
+
|
|
480
484
|
Supported formats: PNG, JPEG, GIF, WebP. Dimensions are parsed from the image headers automatically.
|
|
481
485
|
|
|
482
486
|
## Autocomplete
|
|
@@ -40,6 +40,7 @@ interface AppKeybindings {
|
|
|
40
40
|
"app.tree.unfoldOrDown": true;
|
|
41
41
|
"app.plan.toggle": true;
|
|
42
42
|
"app.history.search": true;
|
|
43
|
+
"app.stt.pushToTalk": true;
|
|
43
44
|
"app.stt.toggle": true;
|
|
44
45
|
"app.live.toggle": true;
|
|
45
46
|
}
|
|
@@ -329,9 +330,13 @@ export declare const KEYBINDINGS: {
|
|
|
329
330
|
readonly defaultKeys: "ctrl+r";
|
|
330
331
|
readonly description: "Search history";
|
|
331
332
|
};
|
|
333
|
+
readonly "app.stt.pushToTalk": {
|
|
334
|
+
readonly defaultKeys: "space";
|
|
335
|
+
readonly description: "Hold to record speech-to-text";
|
|
336
|
+
};
|
|
332
337
|
readonly "app.stt.toggle": {
|
|
333
338
|
readonly defaultKeys: [];
|
|
334
|
-
readonly description: "Toggle speech-to-text
|
|
339
|
+
readonly description: "Toggle speech-to-text recording";
|
|
335
340
|
};
|
|
336
341
|
readonly "app.live.toggle": {
|
|
337
342
|
readonly defaultKeys: "ctrl+l";
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
* arrows/PgUp/PgDn, vim motions (`j`/`k`/`h`/`l`/`g`/`G`), and mouse
|
|
14
14
|
* clicks/wheel. All toolbar buttons are clickable and mirrored by keys:
|
|
15
15
|
* `v` cycles the view (`1`–`4` pick one), `alt+↓`/`alt+↑` jump hunks and roll
|
|
16
|
-
* into the adjacent file at the edges, `
|
|
17
|
-
*
|
|
16
|
+
* into the adjacent file at the edges, `space` stages/unstages the focused
|
|
17
|
+
* diff row (the focused hunk in hunk view), `s`/`u` stage/unstage
|
|
18
|
+
* (hunk-aware), `x` discards a hunk, `delete` discards the
|
|
18
19
|
* whole file (press twice to confirm), `w` wraps, `b` cycles
|
|
19
20
|
* whitespace handling (exact → ignore whitespace → ignore
|
|
20
21
|
* formatting/import-only changes), `c` jumps to the commit form, `r`
|
|
@@ -38,7 +38,7 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
38
38
|
* for lack of a target is re-rendered with its reaction stripped.
|
|
39
39
|
*/
|
|
40
40
|
pickReactionTarget(transcript: readonly Component[]): void;
|
|
41
|
-
constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string
|
|
41
|
+
constructor(message?: AssistantMessage, hideThinkingBlock?: boolean, onImageUpdate?: () => void, thinkingRenderers?: readonly AssistantThinkingRenderer[], imageBudget?: ImageBudget, proseOnlyThinking?: boolean, linkTargets?: ReadonlyMap<string, string>, expandThinkingBlocks?: boolean);
|
|
42
42
|
/**
|
|
43
43
|
* Show or clear the trailing cache-invalidation divider. Set at `message_end`
|
|
44
44
|
* (live) or during rebuild, once the turn's usage is known and compared
|
|
@@ -65,6 +65,11 @@ export declare class AssistantMessageComponent extends Container {
|
|
|
65
65
|
*/
|
|
66
66
|
setMidStreamPublication(allowed: boolean): void;
|
|
67
67
|
setProseOnlyThinking(proseOnly: boolean): void;
|
|
68
|
+
/**
|
|
69
|
+
* Keep finished thinking sections expanded instead of folding them to "Thought for 12s".
|
|
70
|
+
* Sections the user folded or unfolded by hand keep that choice.
|
|
71
|
+
*/
|
|
72
|
+
setExpandThinkingBlocks(expand: boolean): void;
|
|
68
73
|
dispose(): void;
|
|
69
74
|
/**
|
|
70
75
|
* Toggle suppression of the inline `Error: …` line while the same error is
|
|
@@ -25,6 +25,7 @@ export interface ChatTranscriptBuilderDeps {
|
|
|
25
25
|
cwd: string;
|
|
26
26
|
hideThinkingBlock?: () => boolean;
|
|
27
27
|
proseOnlyThinking?: () => boolean;
|
|
28
|
+
expandThinkingBlocks?: () => boolean;
|
|
28
29
|
/** Session-scoped resolved destinations for model-authored Markdown links. */
|
|
29
30
|
linkTargets?: ReadonlyMap<string, string>;
|
|
30
31
|
requestRender: () => void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Component, Container } from "../tui.js";
|
|
2
2
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { ReactionTarget } from "./reaction.js";
|
|
4
|
-
import { type NativeNode, type NativeUiEvent } from "../native/node.js";
|
|
4
|
+
import { type DescribeContext, type NativeNode, type NativeUiEvent } from "../native/node.js";
|
|
5
5
|
/** How a user bubble styles its prose and chips (see {@link userBubbleColor}). */
|
|
6
6
|
export interface UserBubbleOptions {
|
|
7
7
|
/** Materialized `file://` targets per attached image, indexed by chip number. */
|
|
@@ -42,7 +42,7 @@ export declare class UserMessageComponent extends Container implements ReactionT
|
|
|
42
42
|
* agent's reaction are chips at the bottom-right, so a reaction landing
|
|
43
43
|
* later updates the frame in place, even deep in scrollback.
|
|
44
44
|
*/
|
|
45
|
-
describe(): NativeNode;
|
|
45
|
+
describe(cx?: DescribeContext): NativeNode;
|
|
46
46
|
/** Hover toolbar clicks: omp's own copy and rewind commands. */
|
|
47
47
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
48
48
|
render(width: number): readonly string[];
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import type { NativeNode } from "../native/node.js";
|
|
2
2
|
import { Container } from "../tui.js";
|
|
3
|
+
/** How a {@link StatusNotice} shows. */
|
|
4
|
+
export interface StatusNoticeOptions {
|
|
5
|
+
/** Styles the ANSI line; resolved at render so a theme change re-shapes it. */
|
|
6
|
+
styleFn?: (text: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Whether a native terminal toasts it (default). False keeps the notice to
|
|
9
|
+
* the ANSI transcript: background chatter such as MCP connection progress,
|
|
10
|
+
* which would otherwise re-toast on every server that connects or fails.
|
|
11
|
+
*/
|
|
12
|
+
toast?: boolean;
|
|
13
|
+
}
|
|
3
14
|
/**
|
|
4
15
|
* A transient status notice (`showStatus`: "Thinking blocks: hidden", "Copied
|
|
5
16
|
* to clipboard"). The ANSI render appends a blank row and the dim line to the
|
|
6
17
|
* transcript; a native terminal shows it as a toast instead, so notices never
|
|
7
|
-
* pile up in the history. Updating the text re-shows the toast
|
|
18
|
+
* pile up in the history. Updating the text re-shows the toast; a notice
|
|
19
|
+
* with `toast: false` describes nothing natively.
|
|
8
20
|
*/
|
|
9
21
|
export declare class StatusNotice extends Container {
|
|
10
22
|
#private;
|
|
11
|
-
constructor(message: string,
|
|
12
|
-
setMessage(message: string,
|
|
23
|
+
constructor(message: string, options?: StatusNoticeOptions);
|
|
24
|
+
setMessage(message: string, options?: StatusNoticeOptions): void;
|
|
13
25
|
describe(): NativeNode;
|
|
14
26
|
}
|
|
@@ -330,6 +330,10 @@ export declare class Editor implements Component, Focusable {
|
|
|
330
330
|
* {@link getExpandedText}). Hosts that intercept large pastes through {@link onLargePaste} use
|
|
331
331
|
* this to re-insert a (possibly transformed) paste without re-triggering the interception hook. */
|
|
332
332
|
insertPaste(content: string): void;
|
|
333
|
+
/** Whether a character-jump hotkey is waiting for its target character. */
|
|
334
|
+
get isJumpPending(): boolean;
|
|
335
|
+
/** Type literal text through the character-editing pipeline without keybinding dispatch. */
|
|
336
|
+
typeCharacter(text: string): void;
|
|
333
337
|
/**
|
|
334
338
|
* Whether an autocomplete popup is on screen. An `@` list narrowed to no match
|
|
335
339
|
* while its refresh is pending stays open internally but is hidden, so it does
|
|
@@ -16,7 +16,19 @@ export interface ImageOptions {
|
|
|
16
16
|
* repaint replaces the placement instead of stacking a duplicate.
|
|
17
17
|
*/
|
|
18
18
|
imageKey?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Schedules a repaint once this image's off-thread SIXEL encode lands.
|
|
21
|
+
* Defaults to the budget's repaint; a host without a budget must pass one,
|
|
22
|
+
* or the image's reserved rows stay blank until an unrelated repaint.
|
|
23
|
+
*/
|
|
24
|
+
requestRender?: () => void;
|
|
19
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Run `render` for rows bound for native scrollback. Those rows are never
|
|
28
|
+
* repainted, so an image whose off-thread SIXEL encode has not landed encodes
|
|
29
|
+
* synchronously instead of committing its reserved blank rows.
|
|
30
|
+
*/
|
|
31
|
+
export declare function renderForScrollback<T>(render: () => T): T;
|
|
20
32
|
/** Default count of inline images kept as live graphics before older ones fall back to text. */
|
|
21
33
|
export declare const DEFAULT_MAX_INLINE_IMAGES = 8;
|
|
22
34
|
/**
|
|
@@ -52,6 +64,8 @@ export declare class ImageBudget {
|
|
|
52
64
|
get cap(): number;
|
|
53
65
|
get enabled(): boolean;
|
|
54
66
|
setRequestRender(requestRender: () => void): void;
|
|
67
|
+
/** Ask for a repaint, e.g. once an image's off-thread encode settles. */
|
|
68
|
+
requestRender(): void;
|
|
55
69
|
setCap(cap: number): void;
|
|
56
70
|
/**
|
|
57
71
|
* Stable graphics id for a logical image. A non-empty `key` maps to the same
|
|
@@ -28,6 +28,7 @@ export declare class Input implements Component, Focusable {
|
|
|
28
28
|
setValue(value: string): void;
|
|
29
29
|
setUseTerminalCursor(useTerminalCursor: boolean): void;
|
|
30
30
|
getUseTerminalCursor(): boolean;
|
|
31
|
+
capturesInput(data: string): boolean;
|
|
31
32
|
/**
|
|
32
33
|
* Apply one key: the editor's text bindings (motion, deletion, kill ring,
|
|
33
34
|
* undo), pastes and printable text. Returns whether the key was the
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { KeyId } from "../keys.js";
|
|
1
2
|
import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node.js";
|
|
2
3
|
import { type ShimmerPalette } from "../theme/shimmer.js";
|
|
3
4
|
import type { TUI } from "../tui.js";
|
|
@@ -23,16 +24,14 @@ export interface WorkingRowSpec {
|
|
|
23
24
|
readonly variant?: WorkingRowVariant;
|
|
24
25
|
/** Shimmer palette of the label (a session-accented intent). */
|
|
25
26
|
readonly palette?: ShimmerPalette;
|
|
26
|
-
/** Live tok/s, docked before the stop control. */
|
|
27
|
-
readonly rate?: number;
|
|
28
27
|
/** Key id that interrupts (`escape`); undefined hides the stop control (Esc would not cancel). */
|
|
29
|
-
readonly interruptKey?:
|
|
28
|
+
readonly interruptKey?: KeyId;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
31
|
* The dock's working row: `starburst` spinner (or a retry countdown ring),
|
|
33
|
-
* the
|
|
34
|
-
* control whose click sends `interrupt`. Compaction adds an
|
|
35
|
-
* `progress` before the stop control; a retry says "Cancel".
|
|
32
|
+
* the elapsed time, the `·` divider, the intent shimmering, a grow spacer,
|
|
33
|
+
* then the stop control whose click sends `interrupt`. Compaction adds an
|
|
34
|
+
* indeterminate `progress` before the stop control; a retry says "Cancel".
|
|
36
35
|
*/
|
|
37
36
|
export declare function describeWorkingRow(spec: WorkingRowSpec, cx: DescribeContext, now?: number): NativeNode;
|
|
38
37
|
type ColorFn = (str: string) => string;
|
|
@@ -23,6 +23,14 @@ export type KeyName = KeyId | ModifierName;
|
|
|
23
23
|
* is capitalized. Other keys are capitalized (`f5` → `F5`).
|
|
24
24
|
*/
|
|
25
25
|
export declare function formatKeyHint(key: KeyName): string;
|
|
26
|
+
/**
|
|
27
|
+
* Format one key for a native tooltip (a TSP `title`): unicode keycap glyphs
|
|
28
|
+
* whatever the theme's symbol preset (`shift+tab` → `⇧⇥`), which Tern's
|
|
29
|
+
* tooltip splits into keycaps, and `esc` as Tern's own keycaps read. The nerd
|
|
30
|
+
* preset's icons are missing from Tern's UI font, and its spaced chords would
|
|
31
|
+
* not parse as keys.
|
|
32
|
+
*/
|
|
33
|
+
export declare function formatTooltipKey(key: KeyName): string;
|
|
26
34
|
/** Format alternative keys as a slash-separated hint (`["f5", "alt+r"]` → `F5/⌥R`). */
|
|
27
35
|
export declare function formatKeyHints(keys: KeyName | readonly KeyName[]): string;
|
|
28
36
|
/** Format a double-tap gesture: `←←` with glyphs, `Left Left` with words. */
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reference document applier: applies TSP frames to a node tree the way the
|
|
3
|
-
* spec says the terminal does (
|
|
4
|
-
* one; a rejected op is reported and
|
|
5
|
-
* the frame. `settle` is recorded as a
|
|
3
|
+
* spec says the terminal does (Tern SDK, `protocol/documents.md`, frame ops).
|
|
4
|
+
* Ops are validated and applied one by one; a rejected op is reported and
|
|
5
|
+
* skipped without affecting the rest of the frame. `settle` is recorded as a
|
|
6
|
+
* hint and never restricts later ops.
|
|
6
7
|
*
|
|
7
8
|
* Used by tests, the debug server's `doc` op, and the TSP test harness.
|
|
8
9
|
*/
|
|
@@ -83,8 +83,8 @@ export declare class NativeBackend {
|
|
|
83
83
|
* The terminal's real `hello` reply after an optimistic start: adopt its
|
|
84
84
|
* APC limit, credits, cell size, kinds, appearance and motion preference.
|
|
85
85
|
* A width the terminal already reported in a `resize` event wins over the
|
|
86
|
-
* reply's. A different vocabulary
|
|
87
|
-
* component, so kinds the terminal lacks fall back.
|
|
86
|
+
* reply's. A different vocabulary, motion preference or clock re-describes
|
|
87
|
+
* every component, so kinds the terminal lacks fall back.
|
|
88
88
|
*/
|
|
89
89
|
confirm(hello: TspHello): void;
|
|
90
90
|
/** SIGWINCH: refresh the width used for `rows` fallback until the terminal reports one. */
|
|
@@ -26,9 +26,10 @@ export declare function encodeTspMessage(verb: TspVerb, body: string, params?: T
|
|
|
26
26
|
export declare function encodeTspJson(verb: TspVerb, value: unknown, params?: TspParams, limit?: number): string;
|
|
27
27
|
/**
|
|
28
28
|
* The `hello` query; callers follow it with a DA1 sentinel. `features: ["edit"]`
|
|
29
|
-
* tells the terminal that omp applies its `edit` events (
|
|
30
|
-
*
|
|
31
|
-
* says omp applies `undo` events,
|
|
29
|
+
* tells the terminal that omp applies its `edit` events (Tern SDK,
|
|
30
|
+
* `protocol/input.md`), so it may keep a native selection in omp's editors;
|
|
31
|
+
* without it, every key stays omp's. `"undo"` says omp applies `undo` events,
|
|
32
|
+
* so the terminal may turn ⌃Z in a field into one.
|
|
32
33
|
* `"send"` accepts an explicit prompt for a live composer without simulating keys.
|
|
33
34
|
*/
|
|
34
35
|
export declare function encodeTspHelloQuery(version?: string): string;
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
* `rows` fallback node rendered through `render(cx.cols)`.
|
|
12
12
|
*
|
|
13
13
|
* See `packages/wire/src/tsp.ts` for the wire vocabulary and
|
|
14
|
-
* `
|
|
14
|
+
* the Tern SDK's Surface Protocol reference (`docs/sdk/src/protocol` in the
|
|
15
|
+
* Stencil repository, https://docs.stencil.so/tern/protocol/) for the spec.
|
|
15
16
|
*/
|
|
16
17
|
import type { TspEvent, TspKind, TspProps, TspScrollBy, TspSpan } from "@oh-my-pi/pi-wire";
|
|
17
18
|
import type { Component } from "../tui.js";
|
|
@@ -70,6 +71,12 @@ export interface DescribeContext {
|
|
|
70
71
|
readonly reduceMotion: boolean;
|
|
71
72
|
/** The terminal's appearance is dark. */
|
|
72
73
|
readonly dark: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* The user's system reads a 12-hour clock (`false`: 24-hour); `undefined`
|
|
76
|
+
* when the terminal doesn't say. Pass it as `hour12` when formatting times:
|
|
77
|
+
* the process's own default locale may not know.
|
|
78
|
+
*/
|
|
79
|
+
readonly hour12?: boolean;
|
|
73
80
|
/** Whether the terminal renders `kind` natively (else describe something simpler or let it fall back). */
|
|
74
81
|
supports(kind: TspKind): boolean;
|
|
75
82
|
/**
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* component's root is a `card` whose `head` carries the title.
|
|
8
8
|
*/
|
|
9
9
|
import type { TspProps, TspSpan, TspText } from "@oh-my-pi/pi-wire";
|
|
10
|
-
import type
|
|
10
|
+
import { type KeyName } from "../key-hint-format.js";
|
|
11
11
|
import { type Keybinding } from "../keybindings.js";
|
|
12
12
|
import type { NativeChild, NativeNode } from "./node.js";
|
|
13
13
|
/** One footer hint: the keys that trigger it and what it does. */
|
|
@@ -44,10 +44,10 @@ export interface ActionButtonOptions {
|
|
|
44
44
|
/** Sibling identity key (defaults to the action id); `leafKey(event.key)` reports it back. */
|
|
45
45
|
readonly key?: string;
|
|
46
46
|
/** Key id of the keyboard path the button mirrors, drawn as a keycap inside it (`"r"`, `"ctrl+g"`). */
|
|
47
|
-
readonly keys?:
|
|
47
|
+
readonly keys?: KeyName;
|
|
48
48
|
/** `accent` = the view's hero action, `error` = destructive. */
|
|
49
49
|
readonly tone?: "accent" | "error";
|
|
50
|
-
/** Tooltip; defaults to the label plus the keycap (`"Refresh r"
|
|
50
|
+
/** Tooltip; defaults to the label plus the keycap (`"Refresh r"`, see {@link formatTooltipKey}). */
|
|
51
51
|
readonly title?: string;
|
|
52
52
|
/** Target of an `open` action (URL or `file://` path). */
|
|
53
53
|
readonly href?: string;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Settling: the frame provider marks finalized transcript blocks so the
|
|
3
|
-
* native reconciler stops keeping their description (
|
|
4
|
-
*
|
|
5
|
-
* targeted ops by id, followed by a fresh `settle`
|
|
3
|
+
* native reconciler stops keeping their description (Tern SDK,
|
|
4
|
+
* `protocol/operations.md`, "Settling"). A settled block stays editable: a
|
|
5
|
+
* later change is sent as targeted ops by id, followed by a fresh `settle`
|
|
6
|
+
* hint.
|
|
6
7
|
*/
|
|
7
8
|
import type { Component } from "../tui.js";
|
|
8
9
|
/** Mark `component` (and everything it describes) as finalized. Idempotent; a no-op outside a TSP terminal. */
|
|
@@ -73,6 +73,7 @@ export interface AgentHubDeps<TRecord extends AgentRecordLike = AgentRecordLike>
|
|
|
73
73
|
/** Mirrors the main transcript's thinking-block visibility. */
|
|
74
74
|
hideThinkingBlock?: () => boolean;
|
|
75
75
|
proseOnlyThinking?: () => boolean;
|
|
76
|
+
expandThinkingBlocks?: () => boolean;
|
|
76
77
|
/** Keys toggling tool output expansion (app.tools.expand). */
|
|
77
78
|
expandKeys?: KeyId[];
|
|
78
79
|
/** Focus the main view on this agent's live session (ctx.focusAgentSession). When absent (collab guest, tests), Enter opens the in-hub chat view instead. */
|
|
@@ -54,6 +54,7 @@ export interface AgentTranscriptViewerDeps {
|
|
|
54
54
|
cwd: string;
|
|
55
55
|
hideThinkingBlock?: () => boolean;
|
|
56
56
|
proseOnlyThinking?: () => boolean;
|
|
57
|
+
expandThinkingBlocks?: () => boolean;
|
|
57
58
|
expandKeys: KeyId[];
|
|
58
59
|
/** Keys that toggle the whole hub closed (app.agents.hub + app.session.observe). */
|
|
59
60
|
hubKeys: KeyId[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Component, type Focusable, Input } from "../index.js";
|
|
2
2
|
import { type BtwHistoryRecord } from "./btw-history.js";
|
|
3
3
|
import type { SpaceHoldHandler } from "../space-hold.js";
|
|
4
|
+
import { type KeyId } from "../app-keybindings.js";
|
|
4
5
|
import type { NativeNode, NativeUiEvent } from "../native/node.js";
|
|
5
6
|
interface BtwHistoryPanelOptions {
|
|
6
7
|
records: readonly BtwHistoryRecord[];
|
|
@@ -9,10 +10,17 @@ interface BtwHistoryPanelOptions {
|
|
|
9
10
|
onCancel: (record: BtwHistoryRecord) => void;
|
|
10
11
|
canFollowUp?: (record: BtwHistoryRecord) => boolean;
|
|
11
12
|
onFollowUp?: (record: BtwHistoryRecord, question: string, signal: AbortSignal) => Promise<boolean>;
|
|
12
|
-
/**
|
|
13
|
+
/** Configured push-to-talk keys for a follow-up composer. */
|
|
14
|
+
spaceHoldKeys?: readonly KeyId[];
|
|
13
15
|
spaceHold?: (input: Input) => SpaceHoldHandler;
|
|
14
16
|
requestRender: () => void;
|
|
15
17
|
getHeight: () => number;
|
|
18
|
+
/**
|
|
19
|
+
* Esc closes the panel even while the selected answer runs (it keeps
|
|
20
|
+
* streaming in the background) and `x` cancels it, instead of Esc cancelling
|
|
21
|
+
* first. For a host with no inline panel to fall back to (Tern).
|
|
22
|
+
*/
|
|
23
|
+
escapeHides?: boolean;
|
|
16
24
|
}
|
|
17
25
|
/** Session-local side questions. Selecting or copying never promotes them into chat. */
|
|
18
26
|
export declare class BtwHistoryPanel implements Component, Focusable {
|
|
@@ -25,6 +33,10 @@ export declare class BtwHistoryPanel implements Component, Focusable {
|
|
|
25
33
|
markCopied(recordId: string, text: string): void;
|
|
26
34
|
invalidate(): void;
|
|
27
35
|
openFollowUp(recordId: string): boolean;
|
|
36
|
+
/** Select a record with its answer pane focused, read from the top (a just-asked /btw). */
|
|
37
|
+
showRecord(recordId: string): void;
|
|
38
|
+
/** Only an active follow-up composer can claim its configured push-to-talk keys. */
|
|
39
|
+
capturesInput(data: string): boolean;
|
|
28
40
|
/** Enhanced clipboard pastes belong only to the active composer. */
|
|
29
41
|
pasteText(text: string): void;
|
|
30
42
|
handleInput(data: string): void;
|
|
@@ -31,6 +31,7 @@ export interface CopySelectorDeps {
|
|
|
31
31
|
cwd: string;
|
|
32
32
|
hideThinkingBlock?: () => boolean;
|
|
33
33
|
proseOnlyThinking?: () => boolean;
|
|
34
|
+
expandThinkingBlocks?: () => boolean;
|
|
34
35
|
linkTargets?: ReadonlyMap<string, string>;
|
|
35
36
|
requestRender: () => void;
|
|
36
37
|
/** Replaces the "Copy" header when the picker is reused for another purpose. */
|
|
@@ -48,6 +48,7 @@ export interface RewindSelectorDeps {
|
|
|
48
48
|
cwd: string;
|
|
49
49
|
hideThinkingBlock?: () => boolean;
|
|
50
50
|
proseOnlyThinking?: () => boolean;
|
|
51
|
+
expandThinkingBlocks?: () => boolean;
|
|
51
52
|
linkTargets?: ReadonlyMap<string, string>;
|
|
52
53
|
requestRender: () => void;
|
|
53
54
|
/** Sibling branch paths of `entryId`'s turn (excluding the turn itself). */
|
|
@@ -121,12 +121,12 @@ export declare class UsageDashboardComponent implements Component {
|
|
|
121
121
|
* The sheet body (the terminal's `lg` overlay is the frame): a head row
|
|
122
122
|
* (checked ago, Overview/Details tabs, Refresh), then either the provider
|
|
123
123
|
* grid of frames with window meters and the activity heatmap, or the
|
|
124
|
-
* per-provider detail tables
|
|
125
|
-
* `progress`/`table` on terminals without
|
|
126
|
-
* inputs change.
|
|
124
|
+
* per-provider detail tables, then Close like the other report sheets.
|
|
125
|
+
* `meter`/`chart` fall back to `progress`/`table` on terminals without
|
|
126
|
+
* them. Rebuilt only when its inputs change.
|
|
127
127
|
*/
|
|
128
128
|
describe(cx: DescribeContext): NativeNode;
|
|
129
|
-
/** Tab clicks switch views like Enter/Esc; the Refresh button runs `r
|
|
129
|
+
/** Tab clicks switch views like Enter/Esc; the Refresh button runs `r`; Close closes from either view. */
|
|
130
130
|
handleNativeEvent(event: NativeUiEvent): void;
|
|
131
131
|
handleInput(data: string): void;
|
|
132
132
|
}
|
|
@@ -18,6 +18,10 @@ export interface ComposerNativeState {
|
|
|
18
18
|
};
|
|
19
19
|
/** Thinking effort word (`high`, `off`, `auto`); undefined when the model has no thinking. */
|
|
20
20
|
readonly thinking?: string;
|
|
21
|
+
/** The thinking level draws as the model chip's icon instead of its own chip (where the terminal has `effort`). */
|
|
22
|
+
readonly thinkingInModel?: boolean;
|
|
23
|
+
/** Generation tok/s after the thinking level (live while running, else the last reading); undefined hides it. */
|
|
24
|
+
readonly rate?: number;
|
|
21
25
|
/** A turn is running: the send keycap becomes a Stop button. */
|
|
22
26
|
readonly running: boolean;
|
|
23
27
|
/** The viewed subagent's lineage, outermost first and the viewed agent last; undefined on the main session. */
|
|
@@ -229,7 +233,8 @@ export declare class CustomEditor extends Editor {
|
|
|
229
233
|
* The TSP composer: role `omp.editor[.bash|.python]` (tone `pending` while
|
|
230
234
|
* a turn runs) over the context hairline, the viewing header while a
|
|
231
235
|
* subagent is focused, the attachment chips, a `line` row of the
|
|
232
|
-
* shell-mode chip and the input, and the `bar`: model chip, effort chip
|
|
236
|
+
* shell-mode chip and the input, and the `bar`: model chip, effort chip
|
|
237
|
+
* (or the effort glyph as the model chip's icon), the generation rate,
|
|
233
238
|
* the other status facts, usage, then send (Stop while a turn runs).
|
|
234
239
|
* Clicks come back as `status.model`, `thinking.cycle`, `submit`,
|
|
235
240
|
* `interrupt` and `focus:<id>` actions.
|
|
@@ -271,9 +276,9 @@ export declare class CustomEditor extends Editor {
|
|
|
271
276
|
onLeftAtStart?: () => void;
|
|
272
277
|
/** Called when the viewing header asks to view agent `id` ({@link MAIN_AGENT_ID}: the main session). */
|
|
273
278
|
onFocusAgent?: (id: string) => void;
|
|
274
|
-
/**
|
|
275
|
-
* stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion)
|
|
276
|
-
* open autocomplete menu. */
|
|
279
|
+
/** Configurable push-to-talk; set its `handler` to enable it. It is a text-composition gesture, so
|
|
280
|
+
* it stays out of Vim's Normal/Visual modes (where the space bar is the `l` motion), away from an
|
|
281
|
+
* open autocomplete menu, and out of a pending character jump (whose target may be the key). */
|
|
277
282
|
readonly spaceHold: SpaceHoldGesture;
|
|
278
283
|
setActionKeys(action: ConfigurableEditorAction, keys: KeyId[]): void;
|
|
279
284
|
/**
|
|
@@ -288,6 +293,7 @@ export declare class CustomEditor extends Editor {
|
|
|
288
293
|
* Clear all custom key handlers.
|
|
289
294
|
*/
|
|
290
295
|
clearCustomKeyHandlers(): void;
|
|
296
|
+
capturesInput(data: string): boolean;
|
|
291
297
|
handleInput(data: string): void;
|
|
292
298
|
/**
|
|
293
299
|
* Route a keystroke through the base text-editor pipeline only, skipping the
|
|
@@ -15,6 +15,7 @@ export interface AssistantMessageHost {
|
|
|
15
15
|
readonly viewSession: AssistantMessageSession;
|
|
16
16
|
readonly effectiveHideThinkingBlock: boolean;
|
|
17
17
|
readonly proseOnlyThinking: boolean;
|
|
18
|
+
readonly expandThinkingBlocks: boolean;
|
|
18
19
|
readonly assistantImagesVisible: boolean;
|
|
19
20
|
readonly hideToolActivity: boolean;
|
|
20
21
|
readonly toolOutputExpanded: boolean;
|
|
@@ -1,45 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { type KeyId } from "./keys.js";
|
|
2
|
+
/** Printable text a push-to-talk candidate key types (plain or shifted single characters and Space),
|
|
3
|
+
* or undefined for non-printable keys and chords, which the gesture reserves instead of typing. */
|
|
4
|
+
export declare function getSpaceHoldText(data: string, canonical: string | undefined): string | undefined;
|
|
5
|
+
/** Max gap (ms) between two configured-key presses for a later one to count as OS key auto-repeat
|
|
6
|
+
* rather than a deliberate press. OS auto-repeat is fast; a deliberate tap (even a fast one) is slower. */
|
|
3
7
|
export declare const SPACE_REPEAT_MAX_GAP_MS = 120;
|
|
4
|
-
/** Two consecutive inter-
|
|
8
|
+
/** Two consecutive inter-key gaps are "mechanical" (machine-driven auto-repeat) when both are
|
|
5
9
|
* within {@link SPACE_REPEAT_MAX_GAP_MS} and differ by no more than this — an absolute jitter floor
|
|
6
10
|
* or, for slower repeat rates, {@link SPACE_REPEAT_JITTER_RATIO} of the smaller gap. OS key-repeat
|
|
7
|
-
* is metronomic;
|
|
8
|
-
* steady. */
|
|
11
|
+
* is metronomic; human typing is irregular, so its deltas do not stay this steady. */
|
|
9
12
|
export declare const SPACE_REPEAT_JITTER_MS = 18;
|
|
10
13
|
export declare const SPACE_REPEAT_JITTER_RATIO = 0.35;
|
|
11
|
-
/** Consecutive mechanical (fast + steady) deltas that confirm
|
|
12
|
-
* recording. Needs a sustained metronomic cadence, so jittery
|
|
14
|
+
/** Consecutive mechanical (fast + steady) deltas that confirm a configured key is held and start
|
|
15
|
+
* recording. Needs a sustained metronomic cadence, so jittery typing and deliberate taps never
|
|
13
16
|
* reach it. */
|
|
14
17
|
export declare const SPACE_HOLD_MECHANICAL_RUN = 2;
|
|
15
|
-
/** Idle gap (ms) after the last repeated
|
|
16
|
-
*
|
|
18
|
+
/** Idle gap (ms) after the last repeated configured-key press that counts as release and ends
|
|
19
|
+
* push-to-talk. Must comfortably exceed the OS key-repeat interval. */
|
|
17
20
|
export declare const SPACE_HOLD_RELEASE_MS = 250;
|
|
18
|
-
/**
|
|
21
|
+
/** A release timer firing more than this (ms) past its deadline means the event loop stalled (a
|
|
22
|
+
* synchronous microphone open, GC, a heavy render) while the bar may still be held: the repeats
|
|
23
|
+
* emitted during the stall are queued but unread, so the stall must not read as a release. */
|
|
24
|
+
export declare const SPACE_HOLD_STALL_SLACK_MS = 100;
|
|
25
|
+
/** What a recognized push-to-talk hold drives — the start and stop callbacks. */
|
|
19
26
|
export interface SpaceHoldHandler {
|
|
20
|
-
/** Gate for the gesture. Returns false to
|
|
21
|
-
* disabling push-to-talk restores plain space behavior. */
|
|
27
|
+
/** Gate for the gesture. Returns false to let matching keys behave normally. */
|
|
22
28
|
enabled(): boolean;
|
|
23
|
-
/** A sustained hold was recognized
|
|
29
|
+
/** A sustained hold was recognized; any safely typed candidate text has already been deleted. */
|
|
24
30
|
onStart(): void;
|
|
25
|
-
/** The held
|
|
31
|
+
/** The held key was released (an idle gap with no further repeats, or any other key). */
|
|
26
32
|
onEnd(): void;
|
|
27
33
|
}
|
|
28
34
|
/** What the host must do with a keypress after {@link SpaceHoldGesture.process} has seen it:
|
|
29
|
-
* `"pass"` — handle it normally; `"type"` — insert
|
|
30
|
-
* turns into a hold); `"swallow"` — drop it, the gesture consumed it. */
|
|
35
|
+
* `"pass"` — handle it normally; `"type"` — insert its safely decoded printable text now (it is
|
|
36
|
+
* tracked back out if the run turns into a hold); `"swallow"` — drop it, the gesture consumed it. */
|
|
31
37
|
export type SpaceHoldStep = "pass" | "type" | "swallow";
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* taps are too slow, so neither escalates and both keep typing real spaces; the few spaces typed
|
|
37
|
-
* before a real hold is recognized are tracked back out of the host input. */
|
|
38
|
+
/** Push-to-talk key-hold state machine. A held key emits OS auto-repeat at a steady fast interval.
|
|
39
|
+
* We watch inter-key deltas and only recognize a hold once {@link SPACE_HOLD_MECHANICAL_RUN}
|
|
40
|
+
* consecutive deltas are mechanical (see {@link gapsAreMechanical}). Deliberate taps and jittery
|
|
41
|
+
* runs stay typed; only text typed during the current steady run is removed on recognition. */
|
|
38
42
|
export declare class SpaceHoldGesture {
|
|
39
43
|
#private;
|
|
40
|
-
/**
|
|
44
|
+
/** Configured alternative keys. Alternatives are tracked independently and never combine into one run. */
|
|
45
|
+
get keys(): readonly KeyId[];
|
|
46
|
+
set keys(keys: readonly KeyId[]);
|
|
47
|
+
/** Unset (the default) keeps configured keys behaving normally. */
|
|
41
48
|
handler: SpaceHoldHandler | undefined;
|
|
42
49
|
constructor(deleteTyped: (count: number) => void, allowed?: () => boolean);
|
|
43
|
-
/**
|
|
44
|
-
|
|
50
|
+
/** Pure focused-owner preflight on raw input: whether the key must reach the host before
|
|
51
|
+
* TUI-wide input listeners. Only non-printable bindings are claimed — they would otherwise
|
|
52
|
+
* trigger app shortcuts mid-hold; printable candidates keep the normal listener path so
|
|
53
|
+
* raw-input observers (e.g. extensions) still see typed text, as they did before remapping. */
|
|
54
|
+
shouldRoute(data: string): boolean;
|
|
55
|
+
/** Feed one canonical keypress and, when safely printable, its decoded text length. The host
|
|
56
|
+
* records the actual inserted length with {@link recordTyped} after applying normal typing. */
|
|
57
|
+
process(key: string | undefined, insertedCharacterLength?: number): SpaceHoldStep;
|
|
58
|
+
/** Account for text the host actually inserted for the preceding `"type"` step. */
|
|
59
|
+
recordTyped(insertedCharacterLength: number): void;
|
|
45
60
|
}
|