@aiwayds/dsh-tui-pi 1.0.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/README.zh-CN.md +1 -0
- package/lib/attachments.d.ts +69 -0
- package/lib/attachments.js +110 -0
- package/lib/attachments.js.map +1 -0
- package/lib/btw-overlay.d.ts +55 -0
- package/lib/btw-overlay.js +153 -0
- package/lib/btw-overlay.js.map +1 -0
- package/lib/btw.d.ts +224 -0
- package/lib/btw.js +419 -0
- package/lib/btw.js.map +1 -0
- package/lib/clipboard.d.ts +10 -0
- package/lib/clipboard.js +13 -0
- package/lib/clipboard.js.map +1 -1
- package/lib/footer.d.ts +2 -1
- package/lib/footer.js +3 -1
- package/lib/footer.js.map +1 -1
- package/lib/index.js +94 -0
- package/lib/index.js.map +1 -1
- package/lib/messages.d.ts +2 -0
- package/lib/messages.js +24 -6
- package/lib/messages.js.map +1 -1
- package/lib/theme-settings.js +1 -0
- package/lib/theme-settings.js.map +1 -1
- package/lib/tui.d.ts +10 -0
- package/lib/tui.js +23 -1
- package/lib/tui.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,6 +24,7 @@ https://github.com/user-attachments/assets/6a7e00bb-1fd0-4bc5-9070-457f1e9fa54d
|
|
|
24
24
|
- [**Model profiles & favorites**](#model-profiles-and-favorites) — switch a whole model setup per project and keep the picker small.
|
|
25
25
|
- [**Sessions & resume**](#sessions-and-resume) — sessions stay tidy automatically and resume in a few keystrokes.
|
|
26
26
|
- [**Themes**](#themes) — GitHub light/dark palettes, hot-switchable; `auto` follows your terminal.
|
|
27
|
+
- [**Search, selection & images**](#search-selection-and-images) — `Ctrl+Shift+F` over the whole transcript, drag-select copies to the OS clipboard, attachments from web/Feishu render inline, LaTeX replies draw as Unicode math.
|
|
27
28
|
|
|
28
29
|
---
|
|
29
30
|
|
|
@@ -107,6 +108,15 @@ GitHub light/dark palettes, hot-switched with `/theme`; `auto` detects your term
|
|
|
107
108
|
|
|
108
109
|
---
|
|
109
110
|
|
|
111
|
+
## Search, selection and images
|
|
112
|
+
|
|
113
|
+
- **Transcript search** — `Ctrl+Shift+F` opens an input overlay over the transcript; `Enter`/`Ctrl+G` and `Shift+Enter`/`Ctrl+Shift+G` walk the matches, `Esc` closes. While it is open the search owns the keys, so your Esc/Ctrl+C gestures stay untouched.
|
|
114
|
+
- **Selection copy** — drag to select anywhere in the transcript; releasing copies through the OS clipboard (`pbcopy` / `wl-copy` / `xclip` / `xsel` / `clip`, with OSC 52 riding along for terminals that honor it). `DSH_TUI_COPY_ON_SELECT=0` keeps selection visual-only. Mouse tracking is still tuned by `DSH_TUI_MOUSE` (default `buttons`: clicks, wheel, drag-selection and scrollbar dragging, no idle-motion noise).
|
|
115
|
+
- **Images from other surfaces** — messages sent with attachments from the web UI or Feishu render inline in the transcript: a real bitmap on Kitty / Ghostty / WezTerm (kitty graphics) and iTerm2, a clickable filename fallback elsewhere, and a muted "unavailable" note if the stored attachment fails verification. Session resume re-renders past images too.
|
|
116
|
+
- **LaTeX in replies** — `$e^{i\pi}+1=0$` and `$$\int_0^1 x\,dx$$` render as terminal-friendly Unicode math (`e^(iπ)+1 = 0`, `∫₀¹ x dx`). Quirk inherited from upstream: a literal `$$` pair in prose is treated as display math.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
110
120
|
## Slash commands
|
|
111
121
|
|
|
112
122
|
| Command | What it does |
|
|
@@ -116,6 +126,7 @@ GitHub light/dark palettes, hot-switched with `/theme`; `auto` detects your term
|
|
|
116
126
|
| `/session` | Read-only info: id, cwd, model, token usage, event count. |
|
|
117
127
|
| `/resume` | Pick a persisted session (newest first), validate its log, restore it. |
|
|
118
128
|
| `/new` | Detach the current session; the next prompt opens a fresh one. |
|
|
129
|
+
| `/btw` | By-the-way side question while the main task runs — one tool-less model call over a recent-conversation snapshot, streamed into a temporary overlay. Never kept in the session; idle main line refuses it; `--model provider/model` overrides the route; bare `/btw` reopens the last answer (`DSH_TUI_BTW_CONTEXT_MESSAGES` sizes the snapshot). |
|
|
119
130
|
| `/settings` | Text-based settings browser (namespaces, schema walk, secrets masked). |
|
|
120
131
|
| `/export` | Write the current session log as JSONL. |
|
|
121
132
|
| `/permission` | Permission-preset picker (read-only / workspace-write / danger-full-access). |
|
|
@@ -151,6 +162,7 @@ Anything else falls through to the model as an ordinary prompt; dsh-native comma
|
|
|
151
162
|
| `Ctrl+L` | Open the model/think picker |
|
|
152
163
|
| `Ctrl+G` | Open the subagent picker (viewer `Enter` opens steer) |
|
|
153
164
|
| `Ctrl+O` | Pending-message queue (s steer now · d remove) |
|
|
165
|
+
| `Ctrl+Shift+F` | Transcript search (`Enter`/`Ctrl+G` next · `Shift+Enter`/`Ctrl+Shift+G` previous · `Esc` close) |
|
|
154
166
|
| `Tab` | Cycle agent presets |
|
|
155
167
|
| `↑` / `↓` | Browse submitted-message history |
|
|
156
168
|
|
|
@@ -173,7 +185,7 @@ dsh-tui:
|
|
|
173
185
|
minBytes: 20480
|
|
174
186
|
```
|
|
175
187
|
|
|
176
|
-
Other knobs: `dsh-tui.panelHeight` (think/tool panel height), `dsh-tui.iconSet` (`auto`/`nerdfont`/`plain` — powerline glyphs adapt to your font; install a Nerd Font with `node scripts/install-font.mjs`), `~/.dsh/keybindings.json` (key remaps).
|
|
188
|
+
Other knobs: `dsh-tui.panelHeight` (think/tool panel height), `dsh-tui.iconSet` (`auto`/`nerdfont`/`plain` — powerline glyphs adapt to your font; install a Nerd Font with `node scripts/install-font.mjs`), `dsh-tui.footerHints` (toggle each footer hint segment, incl. `search`), `DSH_TUI_COPY_ON_SELECT=0` (keep drag-selection visual-only), `~/.dsh/keybindings.json` (key remaps).
|
|
177
189
|
|
|
178
190
|
---
|
|
179
191
|
|
package/README.zh-CN.md
CHANGED
|
@@ -116,6 +116,7 @@ GitHub 明/暗配色,`/theme` 热切换;`auto` 检测你的终端并跟随
|
|
|
116
116
|
| `/session` | 只读信息:id、cwd、model、token 用量、事件数。 |
|
|
117
117
|
| `/resume` | 选择持久化的 session(新的在前),校验日志后恢复。 |
|
|
118
118
|
| `/new` | 分离当前 session;下一条 prompt 开启新会话。 |
|
|
119
|
+
| `/btw` | 主线任务运行中的「顺带一问」——一次无工具的单次模型调用(带最近对话快照),答案流式呈现在临时浮层里。不进会话记录;主线空闲时拒绝使用;`--model provider/model` 临时换路由;空参 `/btw` 回看上一条问答(`DSH_TUI_BTW_CONTEXT_MESSAGES` 控制快照条数)。 |
|
|
119
120
|
| `/settings` | 文本式设置浏览器(命名空间、schema 遍历、密钥脱敏)。 |
|
|
120
121
|
| `/export` | 把当前会话日志写成 JSONL。 |
|
|
121
122
|
| `/permission` | 权限预设选择器(read-only / workspace-write / danger-full-access)。 |
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript rendering for user-message image attachments (pi-tui Image).
|
|
3
|
+
*
|
|
4
|
+
* dsh records attached images in user content as `ImageBlock`s whose
|
|
5
|
+
* attachment is a REFERENCE (attachmentId/mediaType/bytes/width/height),
|
|
6
|
+
* never inline bytes. The reference is content-addressed: the attachmentId
|
|
7
|
+
* embeds the sha256 of the stored bytes, and the durable store lives at
|
|
8
|
+
* `$DSH_HOME/attachments/v1/objects/<aa>/<sha256>` (dsh-attachment-local).
|
|
9
|
+
* The TUI renders a muted placeholder the moment the event arrives, then
|
|
10
|
+
* loads the bytes out of band and swaps the placeholder for a pi-tui
|
|
11
|
+
* `Image` component — kitty/iterm2 terminals paint the real bitmap,
|
|
12
|
+
* everything else falls back to the component's text placeholder
|
|
13
|
+
* (shortened path-style name + OSC 8 file:// link). A failed load (missing
|
|
14
|
+
* object, corrupt digest, metadata mismatch) degrades to a muted
|
|
15
|
+
* "unavailable" note; the transcript never blocks or throws on images.
|
|
16
|
+
*
|
|
17
|
+
* Session replay covers this automatically: resume feeds historical
|
|
18
|
+
* `user/message` events through the same TranscriptRenderer.applyEvent
|
|
19
|
+
* path, so past images render (and re-load) like live ones.
|
|
20
|
+
*
|
|
21
|
+
* Budget: alt-screen image surfaces cost real rows and (under kitty)
|
|
22
|
+
* placements, so a message renders at most {@link MAX_IMAGES_PER_MESSAGE}
|
|
23
|
+
* bitmaps; the rest collapse into a single "+N more" line.
|
|
24
|
+
*/
|
|
25
|
+
import { Container } from '@earendil-works/pi-tui';
|
|
26
|
+
import { type TuiTheme } from './theme/index.ts';
|
|
27
|
+
/** Bitmaps rendered per message; extra image blocks collapse to a "+N more" line. */
|
|
28
|
+
export declare const MAX_IMAGES_PER_MESSAGE = 8;
|
|
29
|
+
/** The versioned attachment storage root (`$DSH_HOME/attachments/v1`). */
|
|
30
|
+
export declare function attachmentsRoot(home?: string): string;
|
|
31
|
+
/** The image block shape dsh carries in user-message content (structural). */
|
|
32
|
+
export interface ImageBlockLike {
|
|
33
|
+
type: 'image';
|
|
34
|
+
attachment: {
|
|
35
|
+
attachmentId: string;
|
|
36
|
+
mediaType: string;
|
|
37
|
+
bytes: number;
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
name?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export declare function isImageBlock(block: unknown): block is ImageBlockLike;
|
|
44
|
+
/** Verified stored bytes — the slice of dsh-attachment-local's result we use. */
|
|
45
|
+
interface StoredImage {
|
|
46
|
+
data: Uint8Array;
|
|
47
|
+
}
|
|
48
|
+
export declare function __setImageReaderForTest(reader: AttachmentReader | undefined): void;
|
|
49
|
+
/**
|
|
50
|
+
* The byte-loading seam. Structural twin of dsh-attachment-local's
|
|
51
|
+
* `readImageFile(root, ref, signal)` (its branded `ImageAttachmentRef` is
|
|
52
|
+
* the same shape; the cast lives at the default-deps site below).
|
|
53
|
+
*/
|
|
54
|
+
export type AttachmentReader = (root: string, ref: ImageBlockLike['attachment'], signal?: AbortSignal) => Promise<StoredImage>;
|
|
55
|
+
export interface ImageRenderDeps {
|
|
56
|
+
/** Attachment storage root; defaults to {@link attachmentsRoot}. */
|
|
57
|
+
root?: string;
|
|
58
|
+
/** Byte loader; defaults to dsh-attachment-local's verified reader. */
|
|
59
|
+
read?: AttachmentReader;
|
|
60
|
+
/** Repaint hook fired when an async load settles; defaults to a no-op. */
|
|
61
|
+
requestRender?: () => void;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Render a message's image blocks into the transcript doc: one slot per
|
|
65
|
+
* image (placeholder → loaded bitmap / unavailable note), then a trailing
|
|
66
|
+
* Spacer. Never throws — a bad attachment degrades to a note.
|
|
67
|
+
*/
|
|
68
|
+
export declare function renderImageAttachments(doc: Container, blocks: readonly ImageBlockLike[], theme: TuiTheme, deps?: ImageRenderDeps): void;
|
|
69
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript rendering for user-message image attachments (pi-tui Image).
|
|
3
|
+
*
|
|
4
|
+
* dsh records attached images in user content as `ImageBlock`s whose
|
|
5
|
+
* attachment is a REFERENCE (attachmentId/mediaType/bytes/width/height),
|
|
6
|
+
* never inline bytes. The reference is content-addressed: the attachmentId
|
|
7
|
+
* embeds the sha256 of the stored bytes, and the durable store lives at
|
|
8
|
+
* `$DSH_HOME/attachments/v1/objects/<aa>/<sha256>` (dsh-attachment-local).
|
|
9
|
+
* The TUI renders a muted placeholder the moment the event arrives, then
|
|
10
|
+
* loads the bytes out of band and swaps the placeholder for a pi-tui
|
|
11
|
+
* `Image` component — kitty/iterm2 terminals paint the real bitmap,
|
|
12
|
+
* everything else falls back to the component's text placeholder
|
|
13
|
+
* (shortened path-style name + OSC 8 file:// link). A failed load (missing
|
|
14
|
+
* object, corrupt digest, metadata mismatch) degrades to a muted
|
|
15
|
+
* "unavailable" note; the transcript never blocks or throws on images.
|
|
16
|
+
*
|
|
17
|
+
* Session replay covers this automatically: resume feeds historical
|
|
18
|
+
* `user/message` events through the same TranscriptRenderer.applyEvent
|
|
19
|
+
* path, so past images render (and re-load) like live ones.
|
|
20
|
+
*
|
|
21
|
+
* Budget: alt-screen image surfaces cost real rows and (under kitty)
|
|
22
|
+
* placements, so a message renders at most {@link MAX_IMAGES_PER_MESSAGE}
|
|
23
|
+
* bitmaps; the rest collapse into a single "+N more" line.
|
|
24
|
+
*/
|
|
25
|
+
import { Container, Image, Spacer, Text } from '@earendil-works/pi-tui';
|
|
26
|
+
import { readImageFile } from '@deepseek-ai/dsh-attachment-local';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
import { dshHome } from "./append-system.js";
|
|
29
|
+
import { ansiFg, RESET } from "./theme/index.js";
|
|
30
|
+
/** Bitmaps rendered per message; extra image blocks collapse to a "+N more" line. */
|
|
31
|
+
export const MAX_IMAGES_PER_MESSAGE = 8;
|
|
32
|
+
/** The versioned attachment storage root (`$DSH_HOME/attachments/v1`). */
|
|
33
|
+
export function attachmentsRoot(home = dshHome()) {
|
|
34
|
+
return join(home, 'attachments', 'v1');
|
|
35
|
+
}
|
|
36
|
+
export function isImageBlock(block) {
|
|
37
|
+
return (typeof block === 'object' && block !== null
|
|
38
|
+
&& block.type === 'image'
|
|
39
|
+
&& typeof block.attachment === 'object');
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Test seam for the byte loader: TranscriptRenderer calls the render path
|
|
43
|
+
* without deps, so tests swap the loader here instead of faking a dsh
|
|
44
|
+
* attachment store. `undefined` restores the real verified reader.
|
|
45
|
+
*/
|
|
46
|
+
let testReader;
|
|
47
|
+
export function __setImageReaderForTest(reader) {
|
|
48
|
+
testReader = reader;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render a message's image blocks into the transcript doc: one slot per
|
|
52
|
+
* image (placeholder → loaded bitmap / unavailable note), then a trailing
|
|
53
|
+
* Spacer. Never throws — a bad attachment degrades to a note.
|
|
54
|
+
*/
|
|
55
|
+
export function renderImageAttachments(doc, blocks, theme, deps = {}) {
|
|
56
|
+
if (blocks.length === 0)
|
|
57
|
+
return;
|
|
58
|
+
const read = deps.read ?? testReader ?? readImageFile;
|
|
59
|
+
const root = deps.root ?? attachmentsRoot();
|
|
60
|
+
const requestRender = deps.requestRender ?? (() => { });
|
|
61
|
+
const muted = (text) => new Text(ansiFg(theme.palette.fgMuted) + text + RESET, 1, 0);
|
|
62
|
+
// The component's own text fallback (unsupported terminal) carries the
|
|
63
|
+
// same muted paint as our loading/unavailable notes.
|
|
64
|
+
const imageTheme = { fallbackColor: (str) => ansiFg(theme.palette.fgMuted) + str + RESET };
|
|
65
|
+
const shown = blocks.slice(0, MAX_IMAGES_PER_MESSAGE);
|
|
66
|
+
for (const block of shown) {
|
|
67
|
+
const slot = new Container();
|
|
68
|
+
const name = block.attachment.name ?? block.attachment.attachmentId;
|
|
69
|
+
const placeholder = muted(`🖼 ${name} — loading (${block.attachment.width}×${block.attachment.height})`);
|
|
70
|
+
slot.addChild(placeholder);
|
|
71
|
+
doc.addChild(slot);
|
|
72
|
+
void loadImageSlot(slot, placeholder, block, { read, root, requestRender, muted, imageTheme });
|
|
73
|
+
}
|
|
74
|
+
if (blocks.length > shown.length) {
|
|
75
|
+
doc.addChild(muted(`🖼 +${blocks.length - shown.length} more images not shown`));
|
|
76
|
+
}
|
|
77
|
+
doc.addChild(new Spacer(1));
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Settle one slot: swap the placeholder for the loaded Image (the
|
|
81
|
+
* component itself decides bitmap vs text fallback by terminal capability)
|
|
82
|
+
* or, on failure, for a muted "unavailable" note carrying the attachment
|
|
83
|
+
* error code. Fire-and-forget by contract — every rejection is caught.
|
|
84
|
+
*/
|
|
85
|
+
async function loadImageSlot(slot, placeholder, block, deps) {
|
|
86
|
+
const name = block.attachment.name ?? block.attachment.attachmentId;
|
|
87
|
+
let loaded;
|
|
88
|
+
try {
|
|
89
|
+
loaded = await deps.read(deps.root, block.attachment);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
const code = error?.code
|
|
93
|
+
?? error?.detailCode;
|
|
94
|
+
slot.removeChild(placeholder);
|
|
95
|
+
slot.addChild(deps.muted(`🖼 ${name} — unavailable${code ? ` (${code})` : ''}`));
|
|
96
|
+
try {
|
|
97
|
+
deps.requestRender();
|
|
98
|
+
}
|
|
99
|
+
catch { /* TUI already disposed */ }
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const base64 = Buffer.from(loaded.data).toString('base64');
|
|
103
|
+
slot.removeChild(placeholder);
|
|
104
|
+
slot.addChild(new Image(base64, block.attachment.mediaType, deps.imageTheme, { filename: block.attachment.name }));
|
|
105
|
+
try {
|
|
106
|
+
deps.requestRender();
|
|
107
|
+
}
|
|
108
|
+
catch { /* TUI already disposed */ }
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../src/attachments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The btw overlay — framed floating panel presenting one side-call exchange
|
|
3
|
+
* (CONTEXT.md: Btw overlay). Bound to a single BtwRunState object: a live
|
|
4
|
+
* run is the controller's own mutable state (setText streaming, Markdown
|
|
5
|
+
* once on settle), a review is a plain snapshot object from the Last-btw
|
|
6
|
+
* slot. Esc closes through the shared overlay focus contract — closing
|
|
7
|
+
* never stops the run; it delivers into the slot regardless.
|
|
8
|
+
*
|
|
9
|
+
* Pure rendering + key routing only; all decisions live in btw.ts. This file
|
|
10
|
+
* is the PanelHost glue and is exercised on a terminal, not in unit tests
|
|
11
|
+
* (same split as route-dialog).
|
|
12
|
+
*/
|
|
13
|
+
import { type Component, type TUI } from '@earendil-works/pi-tui';
|
|
14
|
+
import { type TuiTheme } from './theme/index.ts';
|
|
15
|
+
import type { BtwController, BtwRunState } from './btw.ts';
|
|
16
|
+
export declare class BtwOverlayPanel implements Component {
|
|
17
|
+
private readonly run;
|
|
18
|
+
private readonly controller;
|
|
19
|
+
private readonly theme;
|
|
20
|
+
private readonly onClose;
|
|
21
|
+
private readonly question;
|
|
22
|
+
/** Streaming view — one Text, setText per change (AGENTS.md #2). */
|
|
23
|
+
private readonly answerText;
|
|
24
|
+
private streamedText;
|
|
25
|
+
/** Built once when the run settles; never re-parsed per frame. */
|
|
26
|
+
private finalMarkdown;
|
|
27
|
+
constructor(run: BtwRunState, controller: BtwController, theme: () => TuiTheme, onClose: () => void);
|
|
28
|
+
invalidate(): void;
|
|
29
|
+
render(width: number): string[];
|
|
30
|
+
handleInput(data: string): void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Overlay lifecycle for btw runs: `open(run)` shows/swaps the framed panel
|
|
34
|
+
* bound to that run state; Esc and cancelAll close it through restoreFocus.
|
|
35
|
+
* Two-step wiring — construct first, `attach(controller)` once it exists
|
|
36
|
+
* (the panel needs the controller for the queued count; the controller needs
|
|
37
|
+
* these callbacks at construction).
|
|
38
|
+
*/
|
|
39
|
+
export declare class BtwOverlayWire {
|
|
40
|
+
private readonly tui;
|
|
41
|
+
private readonly theme;
|
|
42
|
+
private readonly restoreFocus;
|
|
43
|
+
private host;
|
|
44
|
+
private controller;
|
|
45
|
+
constructor(deps: {
|
|
46
|
+
tui: TUI;
|
|
47
|
+
theme: () => TuiTheme;
|
|
48
|
+
restoreFocus: () => void;
|
|
49
|
+
});
|
|
50
|
+
attach(controller: BtwController): void;
|
|
51
|
+
/** Called by the controller for every launched or reviewed run. */
|
|
52
|
+
open(run: BtwRunState): void;
|
|
53
|
+
/** Called by cancelAll — the overlay's run is gone either way. */
|
|
54
|
+
requestClose(): void;
|
|
55
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The btw overlay — framed floating panel presenting one side-call exchange
|
|
3
|
+
* (CONTEXT.md: Btw overlay). Bound to a single BtwRunState object: a live
|
|
4
|
+
* run is the controller's own mutable state (setText streaming, Markdown
|
|
5
|
+
* once on settle), a review is a plain snapshot object from the Last-btw
|
|
6
|
+
* slot. Esc closes through the shared overlay focus contract — closing
|
|
7
|
+
* never stops the run; it delivers into the slot regardless.
|
|
8
|
+
*
|
|
9
|
+
* Pure rendering + key routing only; all decisions live in btw.ts. This file
|
|
10
|
+
* is the PanelHost glue and is exercised on a terminal, not in unit tests
|
|
11
|
+
* (same split as route-dialog).
|
|
12
|
+
*/
|
|
13
|
+
import { getKeybindings, Markdown, Text } from '@earendil-works/pi-tui';
|
|
14
|
+
import { PanelHost, panelThemeFns } from "./panels.js";
|
|
15
|
+
import { BOLD, RESET, ansiFg } from "./theme/index.js";
|
|
16
|
+
import { clipToWidth } from "./text.js";
|
|
17
|
+
/**
|
|
18
|
+
* Answer row budget, sized so the panel NEVER overflows the 24-row e2e
|
|
19
|
+
* terminal (AGENTS.md matrix floor): maxHeight '80%' ≈ 19 rows there, the
|
|
20
|
+
* FramedOverlay chrome eats 4, fixed rows are 4 (title + 2 blanks + status),
|
|
21
|
+
* and the question rows come out of the remainder per frame — an overlay
|
|
22
|
+
* taller than that just shows a roomier answer. Overflow of the answer
|
|
23
|
+
* itself is a named tail window (the newest rows stay visible).
|
|
24
|
+
*/
|
|
25
|
+
const BTW_ANSWER_BASE_BUDGET = 11;
|
|
26
|
+
const BTW_ANSWER_MIN_BUDGET = 3;
|
|
27
|
+
export class BtwOverlayPanel {
|
|
28
|
+
run;
|
|
29
|
+
controller;
|
|
30
|
+
theme;
|
|
31
|
+
onClose;
|
|
32
|
+
question;
|
|
33
|
+
/** Streaming view — one Text, setText per change (AGENTS.md #2). */
|
|
34
|
+
answerText;
|
|
35
|
+
streamedText = '';
|
|
36
|
+
/** Built once when the run settles; never re-parsed per frame. */
|
|
37
|
+
finalMarkdown;
|
|
38
|
+
constructor(run, controller, theme, onClose) {
|
|
39
|
+
this.run = run;
|
|
40
|
+
this.controller = controller;
|
|
41
|
+
this.theme = theme;
|
|
42
|
+
this.onClose = onClose;
|
|
43
|
+
this.question = new Text(run.question, 1, 0);
|
|
44
|
+
this.answerText = new Text('', 1, 0);
|
|
45
|
+
}
|
|
46
|
+
invalidate() { }
|
|
47
|
+
render(width) {
|
|
48
|
+
const fns = panelThemeFns(this.theme());
|
|
49
|
+
const wrap = Math.max(2, width - 2);
|
|
50
|
+
const questionLines = this.question.render(wrap);
|
|
51
|
+
const budget = Math.max(BTW_ANSWER_MIN_BUDGET, BTW_ANSWER_BASE_BUDGET - questionLines.length);
|
|
52
|
+
const lines = [
|
|
53
|
+
fns.accent(BOLD + clipToWidth(`⌘ btw — ${this.run.modelLabel}`, wrap) + RESET),
|
|
54
|
+
...questionLines,
|
|
55
|
+
'',
|
|
56
|
+
];
|
|
57
|
+
const queued = this.controller.queuedCount;
|
|
58
|
+
if (this.run.status === 'streaming') {
|
|
59
|
+
if (this.run.answerText !== this.streamedText) {
|
|
60
|
+
this.answerText.setText(this.run.answerText);
|
|
61
|
+
this.streamedText = this.run.answerText;
|
|
62
|
+
}
|
|
63
|
+
if (this.run.answerText !== '') {
|
|
64
|
+
lines.push(...tailWindow(this.answerText.render(wrap), budget, wrap));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
lines.push(fns.muted(clipToWidth('Thinking…', wrap)));
|
|
68
|
+
}
|
|
69
|
+
lines.push('');
|
|
70
|
+
lines.push(fns.subtle(clipToWidth(`Running alongside the main task · Esc close${queued > 0 ? ` · ${queued} queued` : ''}`, wrap)));
|
|
71
|
+
return lines;
|
|
72
|
+
}
|
|
73
|
+
if (this.run.status === 'error') {
|
|
74
|
+
lines.push(fns.muted(clipToWidth(`✘ ${this.run.error ?? 'btw failed.'}`, wrap)));
|
|
75
|
+
}
|
|
76
|
+
else if (this.run.answerText === '') {
|
|
77
|
+
lines.push(fns.muted(clipToWidth('(no answer text)', wrap)));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (this.finalMarkdown === undefined) {
|
|
81
|
+
// Parses ONCE on the settled answer — never per frame, per token.
|
|
82
|
+
this.finalMarkdown = new Markdown(this.run.answerText, 1, 0, this.theme().markdown, {
|
|
83
|
+
color: text => ansiFg(this.theme().palette.fgDefault) + text + RESET,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
lines.push(...tailWindow(this.finalMarkdown.render(wrap), budget, wrap));
|
|
87
|
+
}
|
|
88
|
+
lines.push('');
|
|
89
|
+
lines.push(fns.subtle(clipToWidth(`Not kept in the session · /btw reopens this answer${queued > 0 ? ` · ${queued} queued` : ''}`, wrap)));
|
|
90
|
+
return lines;
|
|
91
|
+
}
|
|
92
|
+
handleInput(data) {
|
|
93
|
+
if (getKeybindings().matches(data, 'tui.select.cancel'))
|
|
94
|
+
this.onClose();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** Last rows that fit the budget; the hidden count is named above the tail. */
|
|
98
|
+
function tailWindow(rendered, budget, wrap) {
|
|
99
|
+
if (rendered.length <= budget)
|
|
100
|
+
return rendered;
|
|
101
|
+
const hidden = rendered.length - budget;
|
|
102
|
+
return [clipToWidth(`… ${hidden} lines above`, wrap), ...rendered.slice(-budget)];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Overlay lifecycle for btw runs: `open(run)` shows/swaps the framed panel
|
|
106
|
+
* bound to that run state; Esc and cancelAll close it through restoreFocus.
|
|
107
|
+
* Two-step wiring — construct first, `attach(controller)` once it exists
|
|
108
|
+
* (the panel needs the controller for the queued count; the controller needs
|
|
109
|
+
* these callbacks at construction).
|
|
110
|
+
*/
|
|
111
|
+
export class BtwOverlayWire {
|
|
112
|
+
tui;
|
|
113
|
+
theme;
|
|
114
|
+
restoreFocus;
|
|
115
|
+
host;
|
|
116
|
+
controller;
|
|
117
|
+
constructor(deps) {
|
|
118
|
+
this.tui = deps.tui;
|
|
119
|
+
this.theme = deps.theme;
|
|
120
|
+
this.restoreFocus = deps.restoreFocus;
|
|
121
|
+
}
|
|
122
|
+
attach(controller) {
|
|
123
|
+
this.controller = controller;
|
|
124
|
+
}
|
|
125
|
+
/** Called by the controller for every launched or reviewed run. */
|
|
126
|
+
open(run) {
|
|
127
|
+
const controller = this.controller;
|
|
128
|
+
if (controller === undefined)
|
|
129
|
+
return;
|
|
130
|
+
// A failed PanelHost.open releases the keyboard exactly like a normal
|
|
131
|
+
// close (PanelHost calls onError first); the run itself is unaffected.
|
|
132
|
+
const close = () => {
|
|
133
|
+
this.host?.close();
|
|
134
|
+
this.host = undefined;
|
|
135
|
+
controller.setOverlayOpen(false);
|
|
136
|
+
this.restoreFocus();
|
|
137
|
+
};
|
|
138
|
+
this.host?.close();
|
|
139
|
+
this.host = new PanelHost(this.tui, this.theme(), close);
|
|
140
|
+
controller.setOverlayOpen(true);
|
|
141
|
+
this.host.open(new BtwOverlayPanel(run, controller, this.theme, close), '70%', '80%');
|
|
142
|
+
}
|
|
143
|
+
/** Called by cancelAll — the overlay's run is gone either way. */
|
|
144
|
+
requestClose() {
|
|
145
|
+
if (this.host !== undefined) {
|
|
146
|
+
this.host.close();
|
|
147
|
+
this.host = undefined;
|
|
148
|
+
this.controller?.setOverlayOpen(false);
|
|
149
|
+
this.restoreFocus();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=btw-overlay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"btw-overlay.js","sourceRoot":"","sources":["../src/btw-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAA4B,MAAM,wBAAwB,CAAA;AACjG,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AACtD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAiB,MAAM,kBAAkB,CAAA;AAErE,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAEvC;;;;;;;GAOG;AACH,MAAM,sBAAsB,GAAG,EAAE,CAAA;AACjC,MAAM,qBAAqB,GAAG,CAAC,CAAA;AAE/B,MAAM,OAAO,eAAe;IACT,GAAG,CAAa;IAChB,UAAU,CAAe;IACzB,KAAK,CAAgB;IACrB,OAAO,CAAY;IACnB,QAAQ,CAAM;IAC/B,oEAAoE;IACnD,UAAU,CAAM;IACzB,YAAY,GAAG,EAAE,CAAA;IACzB,kEAAkE;IAC1D,aAAa,CAAsB;IAE3C,YACE,GAAgB,EAChB,UAAyB,EACzB,KAAqB,EACrB,OAAmB;QAEnB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAA;QACd,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;QAC5B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAA;QAClB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAA;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;QAC5C,IAAI,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,UAAU,KAAU,CAAC;IAErB,MAAM,CAAC,KAAa;QAClB,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,CAAA;QACnC,MAAM,aAAa,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAChD,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,qBAAqB,EAAE,sBAAsB,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;QAC7F,MAAM,KAAK,GAAa;YACtB,GAAG,CAAC,MAAM,CAAC,IAAI,GAAG,WAAW,CAAC,WAAW,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,GAAG,KAAK,CAAC;YAC9E,GAAG,aAAa;YAChB,EAAE;SACH,CAAA;QAED,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,CAAA;QAC1C,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACpC,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,CAAC,YAAY,EAAE,CAAC;gBAC9C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;gBAC5C,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,CAAA;YACzC,CAAC;YACD,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,EAAE,CAAC;gBAC/B,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;YACvE,CAAC;iBAAM,CAAC;gBACN,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;YACvD,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACd,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAC/B,8CAA8C,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EACvF,IAAI,CACL,CAAC,CAAC,CAAA;YACH,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,GAAG,CAAC,KAAK,IAAI,aAAa,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QAClF,CAAC;aAAM,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE,EAAE,CAAC;YACtC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,WAAW,CAAC,kBAAkB,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;QAC9D,CAAC;aAAM,CAAC;YACN,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;gBACrC,kEAAkE;gBAClE,IAAI,CAAC,aAAa,GAAG,IAAI,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE;oBAClF,KAAK,EAAE,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,KAAK;iBACrE,CAAC,CAAA;YACJ,CAAC;YACD,KAAK,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;QAC1E,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACd,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAC/B,qDAAqD,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,EAC9F,IAAI,CACL,CAAC,CAAC,CAAA;QACH,OAAO,KAAK,CAAA;IACd,CAAC;IAED,WAAW,CAAC,IAAY;QACtB,IAAI,cAAc,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,mBAAmB,CAAC;YAAE,IAAI,CAAC,OAAO,EAAE,CAAA;IACzE,CAAC;CACF;AAED,+EAA+E;AAC/E,SAAS,UAAU,CAAC,QAAkB,EAAE,MAAc,EAAE,IAAY;IAClE,IAAI,QAAQ,CAAC,MAAM,IAAI,MAAM;QAAE,OAAO,QAAQ,CAAA;IAC9C,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,MAAM,CAAA;IACvC,OAAO,CAAC,WAAW,CAAC,KAAK,MAAM,cAAc,EAAE,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;AACnF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,OAAO,cAAc;IACR,GAAG,CAAK;IACR,KAAK,CAAgB;IACrB,YAAY,CAAY;IACjC,IAAI,CAAuB;IAC3B,UAAU,CAA2B;IAE7C,YAAY,IAAmE;QAC7E,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,CAAA;QACnB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;QACvB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,UAAyB;QAC9B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAA;IAC9B,CAAC;IAED,mEAAmE;IACnE,IAAI,CAAC,GAAgB;QACnB,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAClC,IAAI,UAAU,KAAK,SAAS;YAAE,OAAM;QACpC,sEAAsE;QACtE,uEAAuE;QACvE,MAAM,KAAK,GAAG,GAAS,EAAE;YACvB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;YAClB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAA;YACrB,UAAU,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;YAChC,IAAI,CAAC,YAAY,EAAE,CAAA;QACrB,CAAC,CAAA;QACD,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAA;QAClB,IAAI,CAAC,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,KAAK,CAAC,CAAA;QACxD,UAAU,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;QAC/B,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IACvF,CAAC;IAED,kEAAkE;IAClE,YAAY;QACV,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC5B,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;YACjB,IAAI,CAAC,IAAI,GAAG,SAAS,CAAA;YACrB,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;YACtC,IAAI,CAAC,YAAY,EAAE,CAAA;QACrB,CAAC;IACH,CAAC;CACF"}
|