@yaag/tui 0.4.0 → 0.5.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/package.json +2 -2
- package/src/drill-controller.ts +49 -13
- package/src/index.ts +5 -0
- package/src/overlay-geometry.ts +44 -0
- package/src/system-prompt-overlay.ts +2 -13
- package/src/transcript-overlay.ts +5 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaag/tui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@earendil-works/pi-tui": "^0.84.0",
|
|
22
|
-
"@yaag/runtime": "0.
|
|
22
|
+
"@yaag/runtime": "0.5.0"
|
|
23
23
|
}
|
|
24
24
|
}
|
package/src/drill-controller.ts
CHANGED
|
@@ -21,12 +21,9 @@ import {
|
|
|
21
21
|
import type { NamedKeybindings } from "./key-router.ts";
|
|
22
22
|
import { renderActionsMenu } from "./node-actions.ts";
|
|
23
23
|
import { agentOfPath } from "./node-path-parse.ts";
|
|
24
|
+
import { overlayContentRows, overlayPanelRows } from "./overlay-geometry.ts";
|
|
24
25
|
import { extractSystemPrompt } from "./session-transcript.ts";
|
|
25
|
-
import {
|
|
26
|
-
renderSystemPromptOverlay,
|
|
27
|
-
systemPromptContentRows,
|
|
28
|
-
systemPromptLines,
|
|
29
|
-
} from "./system-prompt-overlay.ts";
|
|
26
|
+
import { renderSystemPromptOverlay, systemPromptLines } from "./system-prompt-overlay.ts";
|
|
30
27
|
import { nodeTranscriptLines } from "./transcript-content.ts";
|
|
31
28
|
import { renderTranscriptOverlay } from "./transcript-overlay.ts";
|
|
32
29
|
import { NO_TRANSCRIPT_STUB } from "./transcript-render.ts";
|
|
@@ -48,6 +45,12 @@ export interface DrillHost {
|
|
|
48
45
|
agentInfo(agent: string): AgentInfo | undefined;
|
|
49
46
|
/** Reads an Agent's session file body, or null when it is missing. */
|
|
50
47
|
readSession(agent: string): Promise<string | null>;
|
|
48
|
+
/**
|
|
49
|
+
* Reads the system-prompt sidecar the Agent's own pi wrote, or null when
|
|
50
|
+
* there is none — an Agent of a replayed Run, or a session that predates the
|
|
51
|
+
* sidecar.
|
|
52
|
+
*/
|
|
53
|
+
readSystemPromptSidecar(agent: string): Promise<string | null>;
|
|
51
54
|
/** An Agent's session file path, or null when the spawn reported none. */
|
|
52
55
|
sessionPath(agent: string): string | null;
|
|
53
56
|
copyPath(text: string): Promise<void>;
|
|
@@ -105,7 +108,7 @@ export class DrillController {
|
|
|
105
108
|
nodePath: state.path,
|
|
106
109
|
content: this.#linesFor(state),
|
|
107
110
|
scroll: state.scroll,
|
|
108
|
-
rows: this.#
|
|
111
|
+
rows: this.#panelRows(),
|
|
109
112
|
width,
|
|
110
113
|
});
|
|
111
114
|
case "systemPrompt":
|
|
@@ -113,12 +116,20 @@ export class DrillController {
|
|
|
113
116
|
nodePath: state.path,
|
|
114
117
|
content: this.#linesFor(state),
|
|
115
118
|
scroll: state.scroll,
|
|
116
|
-
rows: this.#
|
|
119
|
+
rows: this.#panelRows(),
|
|
117
120
|
width,
|
|
118
121
|
});
|
|
119
122
|
}
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
/**
|
|
126
|
+
* The total panel height both overlays draw at, derived from the host's row
|
|
127
|
+
* budget by the shared geometry so pi's own chrome keeps its rows.
|
|
128
|
+
*/
|
|
129
|
+
#panelRows(): number {
|
|
130
|
+
return overlayPanelRows(this.#host.rows());
|
|
131
|
+
}
|
|
132
|
+
|
|
122
133
|
/** Routes one input byte string; returns whether a layer consumed it. */
|
|
123
134
|
handleInput(data: string): boolean {
|
|
124
135
|
const action = this.#route(data);
|
|
@@ -226,12 +237,14 @@ export class DrillController {
|
|
|
226
237
|
}
|
|
227
238
|
|
|
228
239
|
/**
|
|
229
|
-
* The page height the open layer scrolls by.
|
|
230
|
-
*
|
|
240
|
+
* The page height the open layer scrolls by. Both overlays draw the same
|
|
241
|
+
* panel and spend the same frame rows, so both page by its content rows and
|
|
242
|
+
* scrolling matches what is drawn.
|
|
231
243
|
*/
|
|
232
244
|
#pageRows(state: DrillState): number {
|
|
233
|
-
|
|
234
|
-
|
|
245
|
+
return state.kind === "tree" || state.kind === "menu"
|
|
246
|
+
? this.#host.rows()
|
|
247
|
+
: overlayContentRows(this.#panelRows());
|
|
235
248
|
}
|
|
236
249
|
|
|
237
250
|
/** Drops retained content and voids every in-flight read. */
|
|
@@ -266,8 +279,8 @@ export class DrillController {
|
|
|
266
279
|
* closed or replaced modal.
|
|
267
280
|
*/
|
|
268
281
|
async #loadSystemPrompt(nodePath: string, generation: number): Promise<void> {
|
|
269
|
-
const
|
|
270
|
-
const prompt =
|
|
282
|
+
const agent = agentOfPath(nodePath);
|
|
283
|
+
const prompt = await this.#readPrompt(agent);
|
|
271
284
|
if (generation !== this.#generation) return;
|
|
272
285
|
const state = this.#state;
|
|
273
286
|
if (state.kind !== "systemPrompt" || state.path !== nodePath) return;
|
|
@@ -275,6 +288,29 @@ export class DrillController {
|
|
|
275
288
|
this.#host.requestRender();
|
|
276
289
|
}
|
|
277
290
|
|
|
291
|
+
/**
|
|
292
|
+
* The Agent's system prompt, from the sidecar its own pi wrote, or else from
|
|
293
|
+
* the session file, or null when neither records one.
|
|
294
|
+
*
|
|
295
|
+
* The sidecar wins because it holds the prompt pi really assembled; the
|
|
296
|
+
* session file is read only for a session written before the sidecar existed.
|
|
297
|
+
*/
|
|
298
|
+
async #readPrompt(agent: string): Promise<string | null> {
|
|
299
|
+
const sidecar = await this.#readSidecar(agent);
|
|
300
|
+
if (sidecar !== null && sidecar !== "") return sidecar;
|
|
301
|
+
const session = await this.#readSession(agent);
|
|
302
|
+
return session === null ? null : extractSystemPrompt(session);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/** A sidecar that cannot be read degrades to the session file. */
|
|
306
|
+
async #readSidecar(agent: string): Promise<string | null> {
|
|
307
|
+
try {
|
|
308
|
+
return await this.#host.readSystemPromptSidecar(agent);
|
|
309
|
+
} catch {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
278
314
|
/** A session file that cannot be read degrades to the labelled stub. */
|
|
279
315
|
async #readSession(agent: string): Promise<string | null> {
|
|
280
316
|
try {
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,11 @@ export {
|
|
|
40
40
|
export { agentOfPath, type NodePathSegment, parseNodePath } from "./node-path-parse.ts";
|
|
41
41
|
export { renderNodeTable } from "./node-table.ts";
|
|
42
42
|
export { type FramedBoxOptions, renderFramedBox } from "./overlay-frame.ts";
|
|
43
|
+
export {
|
|
44
|
+
OVERLAY_CHROME_ROWS,
|
|
45
|
+
overlayContentRows,
|
|
46
|
+
overlayPanelRows,
|
|
47
|
+
} from "./overlay-geometry.ts";
|
|
43
48
|
export {
|
|
44
49
|
initialScroll,
|
|
45
50
|
maxOffset,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared height geometry for the drill-in overlays (ADR-0034).
|
|
3
|
+
*
|
|
4
|
+
* The Run view is a non-overlay `ctx.ui.custom()` component: pi draws its own
|
|
5
|
+
* chrome — the editor box and the status line — below the component, and it
|
|
6
|
+
* gives the component no height budget. A panel as tall as the terminal
|
|
7
|
+
* therefore pushes that chrome off the screen. These two pure helpers give
|
|
8
|
+
* every overlay one contract: `overlayPanelRows` turns a terminal row budget
|
|
9
|
+
* into the TOTAL panel height, and `overlayContentRows` turns that total into
|
|
10
|
+
* the content rows the panel can draw.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** Frame rows a panel spends on its title, its footer, and its bottom border. */
|
|
14
|
+
export const OVERLAY_CHROME_ROWS = 3;
|
|
15
|
+
|
|
16
|
+
/** Fraction of the row budget a panel takes on a tall terminal. */
|
|
17
|
+
const HEIGHT_FRACTION = 0.9;
|
|
18
|
+
|
|
19
|
+
/** Rows kept free for pi chrome below the component, on a short terminal. */
|
|
20
|
+
const HOST_CHROME_ROWS = 6;
|
|
21
|
+
|
|
22
|
+
/** The shortest panel that still draws one content row. */
|
|
23
|
+
const MIN_PANEL_ROWS = 4;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The total panel height for a terminal row budget of `budget`.
|
|
27
|
+
*
|
|
28
|
+
* The fraction keeps a margin on a tall terminal; the absolute reserve keeps
|
|
29
|
+
* one on a short terminal, where a fraction alone leaves too little. A budget
|
|
30
|
+
* smaller than both floors to the shortest drawable panel. Failure mode: a
|
|
31
|
+
* budget that is not a finite number degrades to that same floor.
|
|
32
|
+
*/
|
|
33
|
+
export function overlayPanelRows(budget: number): number {
|
|
34
|
+
if (!Number.isFinite(budget)) return MIN_PANEL_ROWS;
|
|
35
|
+
const rows = Math.floor(budget);
|
|
36
|
+
const fitted = Math.min(Math.round(rows * HEIGHT_FRACTION), rows - HOST_CHROME_ROWS);
|
|
37
|
+
return Math.max(MIN_PANEL_ROWS, fitted);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The content rows a panel of `panelRows` total height draws. */
|
|
41
|
+
export function overlayContentRows(panelRows: number): number {
|
|
42
|
+
const rows = Number.isFinite(panelRows) ? Math.floor(panelRows) : 0;
|
|
43
|
+
return Math.max(1, rows - OVERLAY_CHROME_ROWS);
|
|
44
|
+
}
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { renderFramedBox } from "./overlay-frame.ts";
|
|
15
|
+
import { overlayContentRows } from "./overlay-geometry.ts";
|
|
15
16
|
import { resolveOffset, type Scroll } from "./overlay-scroll.ts";
|
|
16
17
|
import { wrapLines } from "./wrap-text.ts";
|
|
17
18
|
|
|
@@ -20,9 +21,6 @@ const FOOTER = " ↑↓ scroll c copy path esc back";
|
|
|
20
21
|
/** Fraction of the terminal width the centered panel takes. */
|
|
21
22
|
const WIDTH_FRACTION = 0.9;
|
|
22
23
|
|
|
23
|
-
/** Frame rows the panel spends on its title, footer, and bottom border. */
|
|
24
|
-
const CHROME_ROWS = 3;
|
|
25
|
-
|
|
26
24
|
/** Frame columns one content row loses to the borders and their padding. */
|
|
27
25
|
const CHROME_COLUMNS = 4;
|
|
28
26
|
|
|
@@ -49,11 +47,6 @@ export interface SystemPromptOverlayOptions {
|
|
|
49
47
|
readonly width: number;
|
|
50
48
|
}
|
|
51
49
|
|
|
52
|
-
/** The content rows a panel of `rows` total height draws. */
|
|
53
|
-
export function systemPromptContentRows(rows: number): number {
|
|
54
|
-
return Math.max(1, floor(rows) - CHROME_ROWS);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
50
|
/**
|
|
58
51
|
* Wraps a prompt body to the panel drawn at `width`.
|
|
59
52
|
*
|
|
@@ -70,7 +63,7 @@ export function renderSystemPromptOverlay(options: SystemPromptOverlayOptions):
|
|
|
70
63
|
const width = frameWidth(options.width);
|
|
71
64
|
const panel = panelWidth(width);
|
|
72
65
|
const margin = " ".repeat(Math.max(0, Math.floor((width - panel) / 2)));
|
|
73
|
-
const rows =
|
|
66
|
+
const rows = overlayContentRows(options.rows);
|
|
74
67
|
const offset = resolveOffset(options.scroll, options.content.length, rows);
|
|
75
68
|
const visible = options.content.slice(offset, offset + rows);
|
|
76
69
|
return renderFramedBox({
|
|
@@ -104,7 +97,3 @@ function blankRows(count: number): readonly string[] {
|
|
|
104
97
|
function padding(columns: number): string {
|
|
105
98
|
return " ".repeat(Math.max(0, columns));
|
|
106
99
|
}
|
|
107
|
-
|
|
108
|
-
function floor(value: number): number {
|
|
109
|
-
return Math.floor(Number.isFinite(value) ? value : 0);
|
|
110
|
-
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { renderFramedBox } from "./overlay-frame.ts";
|
|
9
|
+
import { overlayContentRows } from "./overlay-geometry.ts";
|
|
9
10
|
import { resolveOffset, type Scroll } from "./overlay-scroll.ts";
|
|
10
11
|
|
|
11
12
|
const FOOTER = " ↑↓ scroll c copy path esc back";
|
|
@@ -14,7 +15,8 @@ const FOOTER = " ↑↓ scroll c copy path esc back";
|
|
|
14
15
|
* One transcript-overlay render request.
|
|
15
16
|
*
|
|
16
17
|
* Contract: `content` and `scroll` decide the visible slice; `rows` is the
|
|
17
|
-
*
|
|
18
|
+
* TOTAL panel height, frame chrome included, and the overlay derives its
|
|
19
|
+
* content rows from it; `width` is the exact visible width of every returned
|
|
18
20
|
* line. Failure mode: empty `content`, a `rows` of zero or less, and a scroll
|
|
19
21
|
* offset past the end all draw a valid frame instead of throwing.
|
|
20
22
|
*/
|
|
@@ -24,14 +26,14 @@ export interface TranscriptOverlayOptions {
|
|
|
24
26
|
/** Already-rendered content lines, from `nodeTranscriptLines`. */
|
|
25
27
|
readonly content: readonly string[];
|
|
26
28
|
readonly scroll: Scroll;
|
|
27
|
-
/**
|
|
29
|
+
/** Panel height, chrome included; a value under four draws one content row. */
|
|
28
30
|
readonly rows: number;
|
|
29
31
|
readonly width: number;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
/** Draws the framed, scrolled overlay for one node's transcript. */
|
|
33
35
|
export function renderTranscriptOverlay(options: TranscriptOverlayOptions): readonly string[] {
|
|
34
|
-
const rows =
|
|
36
|
+
const rows = overlayContentRows(options.rows);
|
|
35
37
|
const offset = resolveOffset(options.scroll, options.content.length, rows);
|
|
36
38
|
return renderFramedBox({
|
|
37
39
|
title: `${options.nodePath} ─ transcript`,
|