@oh-my-pi/pi-tui 18.13.0 → 18.13.2
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 +36 -32
- package/dist/types/chrome/context-map.d.ts +15 -0
- package/dist/types/chrome/context-presentation.d.ts +48 -0
- package/dist/types/chrome/context-view-model.d.ts +77 -0
- package/dist/types/overlays/advisor-config.d.ts +6 -0
- package/dist/types/overlays/ask-dialog.d.ts +12 -3
- package/dist/types/overlays/context-viewer-format.d.ts +42 -0
- package/dist/types/overlays/context-viewer-overview.d.ts +70 -0
- package/dist/types/overlays/context-viewer.d.ts +25 -0
- package/dist/types/overlays/hook-input.d.ts +1 -0
- package/dist/types/overlays/pause-screen.d.ts +13 -3
- package/dist/types/overlays/running-subagent-badge.d.ts +7 -1
- package/dist/types/status-line/component.d.ts +2 -2
- package/dist/types/status-line/context-usage.d.ts +15 -30
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/fetch.d.ts +1 -0
- package/dist/types/tools/github.d.ts +3 -0
- package/dist/types/tools/read.d.ts +3 -0
- package/dist/types/tools/wait.d.ts +6 -0
- package/dist/types/tui.d.ts +2 -0
- package/package.json +9 -9
- package/src/chrome/context-map.ts +62 -0
- package/src/chrome/context-presentation.ts +26 -0
- package/src/chrome/context-view-model.ts +73 -0
- package/src/components/editor.ts +23 -0
- package/src/components/markdown.ts +14 -2
- package/src/overlays/advisor-config.ts +9 -2
- package/src/overlays/ask-dialog.ts +122 -35
- package/src/overlays/context-viewer-format.ts +145 -0
- package/src/overlays/context-viewer-overview.ts +381 -0
- package/src/overlays/context-viewer.ts +728 -0
- package/src/overlays/hook-input.ts +2 -0
- package/src/overlays/pause-screen.ts +166 -37
- package/src/overlays/running-subagent-badge.ts +12 -1
- package/src/render/output-pane.ts +3 -3
- package/src/status-line/component.ts +6 -4
- package/src/status-line/context-usage.ts +56 -580
- package/src/theme/symbols.ts +16 -0
- package/src/tools/ask.ts +4 -0
- package/src/tools/bash.ts +5 -23
- package/src/tools/fetch.ts +1 -0
- package/src/tools/github.ts +23 -3
- package/src/tools/read.ts +3 -1
- package/src/tools/wait.ts +6 -0
- package/src/tui.ts +23 -3
package/src/theme/symbols.ts
CHANGED
|
@@ -38,6 +38,10 @@ export type SymbolKey =
|
|
|
38
38
|
// Context gauge boundaries
|
|
39
39
|
| "context.speculation"
|
|
40
40
|
| "context.compaction"
|
|
41
|
+
| "context.content"
|
|
42
|
+
| "context.messages"
|
|
43
|
+
| "context.buffer"
|
|
44
|
+
| "context.free"
|
|
41
45
|
// Box Drawing - Rounded
|
|
42
46
|
| "boxRound.topLeft"
|
|
43
47
|
| "boxRound.topRight"
|
|
@@ -419,6 +423,10 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
419
423
|
// Context gauge boundaries
|
|
420
424
|
"context.speculation": "╎",
|
|
421
425
|
"context.compaction": "┃",
|
|
426
|
+
"context.content": "⛁",
|
|
427
|
+
"context.messages": "⛃",
|
|
428
|
+
"context.buffer": "⛝",
|
|
429
|
+
"context.free": "⛶",
|
|
422
430
|
// Box (rounded)
|
|
423
431
|
"boxRound.topLeft": "╭",
|
|
424
432
|
"boxRound.topRight": "╮",
|
|
@@ -745,6 +753,10 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
745
753
|
// Context gauge boundaries — vector intersection starts async speculation; auto-fix applies compaction.
|
|
746
754
|
"context.speculation": "\u{f055d}",
|
|
747
755
|
"context.compaction": "\u{f0068}",
|
|
756
|
+
"context.content": "⛁",
|
|
757
|
+
"context.messages": "⛃",
|
|
758
|
+
"context.buffer": "⛝",
|
|
759
|
+
"context.free": "⛶",
|
|
748
760
|
// Box Drawing - Rounded (same as unicode)
|
|
749
761
|
// pick: ╭ | alt: ┌ ┏ ╔
|
|
750
762
|
"boxRound.topLeft": "╭",
|
|
@@ -1203,6 +1215,10 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
1203
1215
|
// Context gauge boundaries
|
|
1204
1216
|
"context.speculation": ":",
|
|
1205
1217
|
"context.compaction": "|",
|
|
1218
|
+
"context.content": "#",
|
|
1219
|
+
"context.messages": "@",
|
|
1220
|
+
"context.buffer": "x",
|
|
1221
|
+
"context.free": ".",
|
|
1206
1222
|
// Box Drawing - Rounded (ASCII fallback)
|
|
1207
1223
|
"boxRound.topLeft": "+",
|
|
1208
1224
|
"boxRound.topRight": "+",
|
package/src/tools/ask.ts
CHANGED
|
@@ -37,6 +37,8 @@ export interface QuestionResult {
|
|
|
37
37
|
customInput?: string;
|
|
38
38
|
/** Optional note attached to the selected answer in the rich ask dialog. */
|
|
39
39
|
note?: string;
|
|
40
|
+
/** Label of the option the note is on; unset when it is on the custom answer. */
|
|
41
|
+
noteOption?: string;
|
|
40
42
|
/** True when the answer was auto-selected because the dialog timed out. */
|
|
41
43
|
timedOut?: boolean;
|
|
42
44
|
}
|
|
@@ -50,6 +52,8 @@ export interface AskToolDetails {
|
|
|
50
52
|
customInput?: string;
|
|
51
53
|
/** Optional note attached to the selected answer in the rich ask dialog. */
|
|
52
54
|
note?: string;
|
|
55
|
+
/** Label of the option the note is on; unset when it is on the custom answer. */
|
|
56
|
+
noteOption?: string;
|
|
53
57
|
/** True when the answer was auto-selected because the dialog timed out. */
|
|
54
58
|
timedOut?: boolean;
|
|
55
59
|
/** Multi-part question mode */
|
package/src/tools/bash.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Component } from "../tui";
|
|
2
|
-
import { getProjectDir } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { getProjectDir, jsonContainerEnd } from "@oh-my-pi/pi-utils";
|
|
3
3
|
import { highlightCode, type Theme } from "../theme/theme";
|
|
4
4
|
import { renderStatusLine } from "../render/status-line";
|
|
5
5
|
import { framedToolCard, type ToolCardSnapshot } from "../render/tool-card";
|
|
@@ -174,28 +174,10 @@ function extractPartialBashEnv(partialJson: string | undefined): Readonly<Record
|
|
|
174
174
|
const objectStart = envStart === -1 ? -1 : partialJson.indexOf("{", envStart);
|
|
175
175
|
if (objectStart !== -1) {
|
|
176
176
|
// Scan only the env object: stop at its closing brace (outside strings)
|
|
177
|
-
// so the keys that follow it are not read as env assignments.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const ch = partialJson.charCodeAt(i);
|
|
182
|
-
if (ch === 0x22) {
|
|
183
|
-
// Skip the string body, honoring escapes; an unterminated string runs to the end.
|
|
184
|
-
for (i++; i < partialJson.length; i++) {
|
|
185
|
-
const inner = partialJson.charCodeAt(i);
|
|
186
|
-
if (inner === 0x5c) i++;
|
|
187
|
-
else if (inner === 0x22) break;
|
|
188
|
-
}
|
|
189
|
-
} else if (ch === 0x7b || ch === 0x5b) {
|
|
190
|
-
depth++;
|
|
191
|
-
} else if (ch === 0x7d || ch === 0x5d) {
|
|
192
|
-
if (depth === 0) {
|
|
193
|
-
objectEnd = i;
|
|
194
|
-
break;
|
|
195
|
-
}
|
|
196
|
-
depth--;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
177
|
+
// so the keys that follow it are not read as env assignments. An
|
|
178
|
+
// unclosed object (still streaming) runs to the end.
|
|
179
|
+
const end = jsonContainerEnd(partialJson, objectStart);
|
|
180
|
+
const objectEnd = end === -1 ? partialJson.length : end - 1;
|
|
199
181
|
const envBody = partialJson.slice(objectStart + 1, objectEnd);
|
|
200
182
|
const matcher = /"([A-Za-z_][A-Za-z0-9_]*)"\s*:\s*"((?:\\.|[^"\\])*)(?:"|$)/gu;
|
|
201
183
|
for (const match of envBody.matchAll(matcher)) {
|
package/src/tools/fetch.ts
CHANGED
package/src/tools/github.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
type ToolUIColor,
|
|
22
22
|
type ToolUIStatus,
|
|
23
23
|
TRUNCATE_LENGTHS,
|
|
24
|
+
sanitizeDisplayWarning,
|
|
24
25
|
truncateToWidth as truncateVisualWidth,
|
|
25
26
|
} from "../render/render-utils";
|
|
26
27
|
|
|
@@ -236,6 +237,11 @@ function renderRunBlock(run: GhRunWatchRunDetails, width: number, theme: Theme):
|
|
|
236
237
|
return lines;
|
|
237
238
|
}
|
|
238
239
|
|
|
240
|
+
function formatUnavailableLog(entry: GhRunWatchFailedLogDetails): string {
|
|
241
|
+
const reason = entry.unavailableReason ? sanitizeDisplayWarning(entry.unavailableReason) : "";
|
|
242
|
+
return `log tail unavailable${reason ? `: ${reason}` : ""}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
239
245
|
function renderFailedLogs(
|
|
240
246
|
failedLogs: GhRunWatchFailedLogDetails[],
|
|
241
247
|
width: number,
|
|
@@ -254,7 +260,7 @@ function renderFailedLogs(
|
|
|
254
260
|
);
|
|
255
261
|
|
|
256
262
|
if (!entry.available || !entry.tail) {
|
|
257
|
-
lines.push(theme.fg("dim",
|
|
263
|
+
lines.push(theme.fg("dim", truncateVisualWidth(` ${formatUnavailableLog(entry)}`, Math.max(8, width))));
|
|
258
264
|
continue;
|
|
259
265
|
}
|
|
260
266
|
|
|
@@ -463,7 +469,9 @@ function describeWatch(watch: GhRunWatchViewDetails, isError: boolean): NativeTo
|
|
|
463
469
|
const failed = (watch.failedLogs ?? []).flatMap((entry): NativeChild[] => {
|
|
464
470
|
const context = entry.workflowName ? `${entry.workflowName} #${entry.runId}` : `run #${entry.runId}`;
|
|
465
471
|
const title = text([span(plainText(entry.jobName), "error"), span(` ${plainText(context)}`, "muted")]);
|
|
466
|
-
if (!entry.available || !entry.tail)
|
|
472
|
+
if (!entry.available || !entry.tail) {
|
|
473
|
+
return [title, noteText(formatUnavailableLog(entry))];
|
|
474
|
+
}
|
|
467
475
|
return [title, ansi(entry.tail, { follow: true, preview: { lines: PREVIEW_LIMITS.OUTPUT_COLLAPSED } })];
|
|
468
476
|
});
|
|
469
477
|
return {
|
|
@@ -659,6 +667,7 @@ export interface GhRunWatchFailedLogDetails {
|
|
|
659
667
|
conclusion?: string;
|
|
660
668
|
tail?: string;
|
|
661
669
|
available: boolean;
|
|
670
|
+
unavailableReason?: string;
|
|
662
671
|
}
|
|
663
672
|
|
|
664
673
|
/** Live workflow watcher display snapshot. */
|
|
@@ -708,6 +717,8 @@ export interface GhFailedJobLog {
|
|
|
708
717
|
full?: string;
|
|
709
718
|
tail?: string;
|
|
710
719
|
available: boolean;
|
|
720
|
+
unavailableReason?: string;
|
|
721
|
+
truncated?: boolean;
|
|
711
722
|
}
|
|
712
723
|
|
|
713
724
|
/** Choose the terminal conclusion or current job state. */
|
|
@@ -764,12 +775,21 @@ export function renderFailedJobLogs(
|
|
|
764
775
|
lines.push("");
|
|
765
776
|
const logText = options.mode === "full" ? entry.full : entry.tail;
|
|
766
777
|
if (entry.available && logText) {
|
|
767
|
-
lines.push(
|
|
778
|
+
lines.push(
|
|
779
|
+
options.mode === "full"
|
|
780
|
+
? entry.truncated
|
|
781
|
+
? "Partial log (capture truncated):"
|
|
782
|
+
: "Full log:"
|
|
783
|
+
: entry.truncated
|
|
784
|
+
? `Last ${options.tail} captured log lines (log capture truncated):`
|
|
785
|
+
: `Last ${options.tail} log lines:`,
|
|
786
|
+
);
|
|
768
787
|
lines.push("```text");
|
|
769
788
|
lines.push(logText);
|
|
770
789
|
lines.push("```");
|
|
771
790
|
} else {
|
|
772
791
|
lines.push(options.mode === "full" ? "Full log unavailable." : "Log tail unavailable.");
|
|
792
|
+
if (entry.unavailableReason) pushLine(lines, "Reason", entry.unavailableReason);
|
|
773
793
|
}
|
|
774
794
|
lines.push("");
|
|
775
795
|
}
|
package/src/tools/read.ts
CHANGED
|
@@ -62,7 +62,9 @@ export interface ReadToolDetails {
|
|
|
62
62
|
* screenshots, URL reads and other images not backed by a file. Display-only.
|
|
63
63
|
*/
|
|
64
64
|
mediaPath?: string;
|
|
65
|
-
suffixResolution?: { from: string; to: string };
|
|
65
|
+
suffixResolution?: { from: string; to: string; raw?: boolean };
|
|
66
|
+
/** Raw source output keeps descriptive diagnostics in details, not in the body. */
|
|
67
|
+
raw?: boolean;
|
|
66
68
|
url?: string;
|
|
67
69
|
finalUrl?: string;
|
|
68
70
|
contentType?: string;
|
package/src/tools/wait.ts
CHANGED
|
@@ -89,6 +89,12 @@ export interface JobSnapshot {
|
|
|
89
89
|
* on collision, else the job id itself. See {@link AsyncJob.agentId}.
|
|
90
90
|
*/
|
|
91
91
|
agentUrlId?: string;
|
|
92
|
+
/**
|
|
93
|
+
* Whether this job's turn published `<agentUrlId>.md`. False where the job
|
|
94
|
+
* body reported that it wrote none, so `agent://` still holds an earlier
|
|
95
|
+
* turn's output; undefined where the body does not report it.
|
|
96
|
+
*/
|
|
97
|
+
agentOutputPublished?: boolean;
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
/** Outcome of cancelling one background job. */
|
package/src/tui.ts
CHANGED
|
@@ -926,6 +926,7 @@ export class TUI extends Container {
|
|
|
926
926
|
/** True from the end of start() until stop(): the terminal modes start listeners enable are live. */
|
|
927
927
|
#started = false;
|
|
928
928
|
#paintListeners = new Set<PaintListener>();
|
|
929
|
+
#frameWriteListeners = new Set<(data: string) => void>();
|
|
929
930
|
|
|
930
931
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
931
932
|
onDebug?: () => void;
|
|
@@ -1142,6 +1143,14 @@ export class TUI extends Container {
|
|
|
1142
1143
|
};
|
|
1143
1144
|
}
|
|
1144
1145
|
|
|
1146
|
+
/** Observe actual ANSI frame writes, including transient resize paints; returns the unsubscribe. */
|
|
1147
|
+
addFrameWriteListener(listener: (data: string) => void): () => void {
|
|
1148
|
+
this.#frameWriteListeners.add(listener);
|
|
1149
|
+
return () => {
|
|
1150
|
+
this.#frameWriteListeners.delete(listener);
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1145
1154
|
/** Install the product-owned bounded frame provider. */
|
|
1146
1155
|
setFrameProvider(provider: TerminalFrameProvider | undefined): void {
|
|
1147
1156
|
this.#frameProvider = provider;
|
|
@@ -3414,6 +3423,16 @@ export class TUI extends Container {
|
|
|
3414
3423
|
}
|
|
3415
3424
|
}
|
|
3416
3425
|
|
|
3426
|
+
#notifyFrameWrite(data: string): void {
|
|
3427
|
+
for (const listener of this.#frameWriteListeners) {
|
|
3428
|
+
try {
|
|
3429
|
+
listener(data);
|
|
3430
|
+
} catch (err) {
|
|
3431
|
+
logger.error("TUI frame write listener failed", { err });
|
|
3432
|
+
}
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3417
3436
|
#renderProviderFrame(width: number, height: number): void {
|
|
3418
3437
|
const provider = this.#frameProvider;
|
|
3419
3438
|
if (!provider || width <= 0 || height <= 0) return;
|
|
@@ -3777,6 +3796,7 @@ export class TUI extends Container {
|
|
|
3777
3796
|
this.terminal.write(this.#rebuildNoticeSequence(width, height));
|
|
3778
3797
|
}
|
|
3779
3798
|
this.terminal.write(buffer);
|
|
3799
|
+
this.#notifyFrameWrite(buffer);
|
|
3780
3800
|
this.#debugPaint = {
|
|
3781
3801
|
lines: prepared.lines,
|
|
3782
3802
|
windowTop: this.#debugNextWindowTop,
|
|
@@ -4580,9 +4600,9 @@ export class TUI extends Container {
|
|
|
4580
4600
|
} else if (hideCursor) {
|
|
4581
4601
|
cursorBuffer = "\x1b[?25l";
|
|
4582
4602
|
}
|
|
4583
|
-
this
|
|
4584
|
-
|
|
4585
|
-
);
|
|
4603
|
+
const frame = `${this.#paintBeginSequence}${prelude}${full ? "\x1b[H" : ""}${rowsBuffer}${cursorBuffer}${this.#paintEndSequence}`;
|
|
4604
|
+
this.terminal.write(frame);
|
|
4605
|
+
this.#notifyFrameWrite(frame);
|
|
4586
4606
|
this.#debugPaint = {
|
|
4587
4607
|
lines: prepared.lines,
|
|
4588
4608
|
windowTop: 0,
|