@oh-my-pi/pi-tui 18.2.9 → 18.2.10
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 +7 -0
- package/dist/types/status-line/component.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +15 -9
- package/dist/types/tui.d.ts +8 -3
- package/package.json +9 -9
- package/src/status-line/component.ts +9 -0
- package/src/status-line/segments.ts +5 -3
- package/src/status-line/types.ts +2 -0
- package/src/tools/eval-format/index.ts +1 -0
- package/src/tools/eval.ts +22 -14
- package/src/tui.ts +21 -10
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [18.2.10] - 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Added support for multiple concurrent TUI paint listeners to enable simultaneous session recording and streaming
|
|
10
|
+
- Coalesced status event updates for progress-based operations to reduce TUI render overhead
|
|
11
|
+
|
|
5
12
|
## [18.2.9] - 2026-09-22
|
|
6
13
|
|
|
7
14
|
### Added
|
|
@@ -86,6 +86,8 @@ export declare class StatusLineComponent<TSession extends StatusLineSession = St
|
|
|
86
86
|
setStreamStatus(status: {
|
|
87
87
|
viewers: number;
|
|
88
88
|
} | null): void;
|
|
89
|
+
/** Toggle the `● REC` badge shown while `/record` captures the screen. */
|
|
90
|
+
setRecording(recording: boolean): void;
|
|
89
91
|
/** Set the callback that presents detected Codex reset celebrations, or clear it with `undefined`. */
|
|
90
92
|
setCodexResetFireworksHandler(handler: ((event: CodexResetFireworksEvent) => void) | undefined): void;
|
|
91
93
|
setHookStatus(key: string, text: string | undefined): void;
|
|
@@ -54,10 +54,7 @@ interface EvalRenderCellArg {
|
|
|
54
54
|
code?: string;
|
|
55
55
|
title?: string;
|
|
56
56
|
}
|
|
57
|
-
interface EvalRenderArgs {
|
|
58
|
-
language?: string;
|
|
59
|
-
code?: string;
|
|
60
|
-
title?: string;
|
|
57
|
+
interface EvalRenderArgs extends EvalRenderCellArg {
|
|
61
58
|
cells?: EvalRenderCellArg[];
|
|
62
59
|
__partialJson?: string;
|
|
63
60
|
}
|
|
@@ -68,11 +65,20 @@ interface EvalRenderContext {
|
|
|
68
65
|
timeout?: number;
|
|
69
66
|
}
|
|
70
67
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
|
|
68
|
+
* Coalescing key of a progress-snapshot event: `agent` and `judge_batch`
|
|
69
|
+
* events keyed by `id`, where only the newest snapshot matters. Discrete
|
|
70
|
+
* actions (everything else) have no key. Shared by {@link upsertStatusEvent}
|
|
71
|
+
* and the executors' display collectors so both coalesce identically.
|
|
72
|
+
*/
|
|
73
|
+
export declare function statusEventKey(event: {
|
|
74
|
+
op: string;
|
|
75
|
+
[key: string]: unknown;
|
|
76
|
+
}): string | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Append or replace a status event. Progress snapshots (see
|
|
79
|
+
* {@link statusEventKey}) coalesce in place, preserving first-seen order; every
|
|
80
|
+
* other op is a discrete action and simply appends. Keeps the persisted event
|
|
81
|
+
* list bounded even when a subagent or batch emits hundreds of progress ticks.
|
|
76
82
|
*/
|
|
77
83
|
export declare function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void;
|
|
78
84
|
/** Render eval code cells, structured display output, and progress events. */
|
package/dist/types/tui.d.ts
CHANGED
|
@@ -30,9 +30,11 @@ export interface TuiPaint {
|
|
|
30
30
|
readonly columns: number;
|
|
31
31
|
readonly rows: number;
|
|
32
32
|
}
|
|
33
|
+
/** Observer of completed terminal paints; see {@link TUI.addPaintListener}. */
|
|
34
|
+
export type PaintListener = (paint: TuiPaint) => void;
|
|
33
35
|
export interface TUIOptions {
|
|
34
36
|
renderScheduler?: RenderScheduler;
|
|
35
|
-
onPaint?:
|
|
37
|
+
onPaint?: PaintListener;
|
|
36
38
|
}
|
|
37
39
|
/** Physical terminal dimensions supplied to a frame provider. */
|
|
38
40
|
export interface ViewportSize {
|
|
@@ -290,8 +292,11 @@ export declare class TUI extends Container {
|
|
|
290
292
|
hidden: boolean;
|
|
291
293
|
}[];
|
|
292
294
|
constructor(terminal: Terminal, showHardwareCursor?: boolean, options?: TUIOptions);
|
|
293
|
-
/**
|
|
294
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Observe completed terminal paints; returns the unsubscribe. Independent
|
|
297
|
+
* observers (live stream publisher, session recorder) coexist.
|
|
298
|
+
*/
|
|
299
|
+
addPaintListener(listener: PaintListener): () => void;
|
|
295
300
|
/** Install the product-owned bounded frame provider. */
|
|
296
301
|
setFrameProvider(provider: TerminalFrameProvider | undefined): void;
|
|
297
302
|
get fullRedraws(): number;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "18.2.
|
|
4
|
+
"version": "18.2.10",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Stencil Labs, Inc.",
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/omptype": "18.2.
|
|
41
|
-
"@oh-my-pi/pi-agent-core": "18.2.
|
|
42
|
-
"@oh-my-pi/pi-ai": "18.2.
|
|
43
|
-
"@oh-my-pi/pi-catalog": "18.2.
|
|
44
|
-
"@oh-my-pi/pi-natives": "18.2.
|
|
45
|
-
"@oh-my-pi/pi-utils": "18.2.
|
|
46
|
-
"@oh-my-pi/pi-wire": "18.2.
|
|
47
|
-
"@oh-my-pi/snapcompact": "18.2.
|
|
40
|
+
"@oh-my-pi/omptype": "18.2.10",
|
|
41
|
+
"@oh-my-pi/pi-agent-core": "18.2.10",
|
|
42
|
+
"@oh-my-pi/pi-ai": "18.2.10",
|
|
43
|
+
"@oh-my-pi/pi-catalog": "18.2.10",
|
|
44
|
+
"@oh-my-pi/pi-natives": "18.2.10",
|
|
45
|
+
"@oh-my-pi/pi-utils": "18.2.10",
|
|
46
|
+
"@oh-my-pi/pi-wire": "18.2.10",
|
|
47
|
+
"@oh-my-pi/snapcompact": "18.2.10"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"kitty-vt-wasm": "^0.2.0"
|
|
@@ -529,6 +529,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
529
529
|
#vibeWorkerTokenRate: (() => number | null) | null = null;
|
|
530
530
|
#collabStatus: CollabStatus | null = null;
|
|
531
531
|
#streamStatus: { viewers: number } | null = null;
|
|
532
|
+
#recording = false;
|
|
532
533
|
#focusedAgentId: string | undefined;
|
|
533
534
|
#activeRepoCache: ActiveRepoCache | undefined;
|
|
534
535
|
|
|
@@ -930,6 +931,13 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
930
931
|
this.#invalidateStatusLineRenderCache();
|
|
931
932
|
}
|
|
932
933
|
|
|
934
|
+
/** Toggle the `● REC` badge shown while `/record` captures the screen. */
|
|
935
|
+
setRecording(recording: boolean): void {
|
|
936
|
+
if (this.#recording === recording) return;
|
|
937
|
+
this.#recording = recording;
|
|
938
|
+
this.#invalidateStatusLineRenderCache();
|
|
939
|
+
}
|
|
940
|
+
|
|
933
941
|
/** Set the callback that presents detected Codex reset celebrations, or clear it with `undefined`. */
|
|
934
942
|
setCodexResetFireworksHandler(handler: ((event: CodexResetFireworksEvent) => void) | undefined): void {
|
|
935
943
|
this.#onCodexResetFireworks = handler;
|
|
@@ -2174,6 +2182,7 @@ export class StatusLineComponent<TSession extends StatusLineSession = StatusLine
|
|
|
2174
2182
|
vim: this.#vimStatus,
|
|
2175
2183
|
collab: this.#collabStatus,
|
|
2176
2184
|
stream: this.#streamStatus,
|
|
2185
|
+
recording: this.#recording,
|
|
2177
2186
|
usageStats,
|
|
2178
2187
|
contextPercent,
|
|
2179
2188
|
contextTokens,
|
|
@@ -758,9 +758,11 @@ const collabSegment: StatusLineSegment = {
|
|
|
758
758
|
const streamSegment: StatusLineSegment = {
|
|
759
759
|
id: "stream",
|
|
760
760
|
render(ctx) {
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
761
|
+
const badges: string[] = [];
|
|
762
|
+
if (ctx.stream) badges.push(`● LIVE ${statusValue(ctx, `${ctx.stream.viewers}`)}`);
|
|
763
|
+
if (ctx.recording) badges.push("● REC");
|
|
764
|
+
if (badges.length === 0) return { content: "", visible: false };
|
|
765
|
+
return { content: theme.fg("thinkingHigh", badges.join(" ")), visible: true };
|
|
764
766
|
},
|
|
765
767
|
};
|
|
766
768
|
|
package/src/status-line/types.ts
CHANGED
|
@@ -125,6 +125,8 @@ export interface SegmentContext {
|
|
|
125
125
|
} | null;
|
|
126
126
|
collab: CollabStatus | null;
|
|
127
127
|
stream: { viewers: number } | null;
|
|
128
|
+
/** A `/record` capture of this screen is running. */
|
|
129
|
+
recording: boolean;
|
|
128
130
|
// Cached values for performance (computed once per render)
|
|
129
131
|
usageStats: {
|
|
130
132
|
input: number;
|
|
@@ -7,6 +7,7 @@ export * from "./python";
|
|
|
7
7
|
|
|
8
8
|
/** Formats an arbitrary eval-code prefix for display without changing the executed source. */
|
|
9
9
|
export function formatEvalCodeForDisplay(source: string, language: EvalLanguage): string {
|
|
10
|
+
if (/^\s*%/.test(source)) return source;
|
|
10
11
|
switch (language) {
|
|
11
12
|
case "js":
|
|
12
13
|
return formatJavaScriptForDisplay(source);
|
package/src/tools/eval.ts
CHANGED
|
@@ -93,10 +93,7 @@ interface EvalRenderCellArg {
|
|
|
93
93
|
title?: string;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
interface EvalRenderArgs {
|
|
97
|
-
language?: string;
|
|
98
|
-
code?: string;
|
|
99
|
-
title?: string;
|
|
96
|
+
interface EvalRenderArgs extends EvalRenderCellArg {
|
|
100
97
|
cells?: EvalRenderCellArg[];
|
|
101
98
|
__partialJson?: string;
|
|
102
99
|
}
|
|
@@ -126,10 +123,9 @@ function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
|
126
123
|
for (const cell of raw) {
|
|
127
124
|
if (!cell || typeof cell !== "object") continue;
|
|
128
125
|
const language = normalizeRenderLanguage(typeof cell.language === "string" ? cell.language : undefined);
|
|
129
|
-
const code = typeof cell.code === "string" ? cell.code : "";
|
|
130
126
|
out.push({
|
|
131
127
|
language,
|
|
132
|
-
code: formatEvalCodeForDisplay(code, language),
|
|
128
|
+
code: formatEvalCodeForDisplay(cell.code ?? "", language),
|
|
133
129
|
title: typeof cell.title === "string" ? cell.title : undefined,
|
|
134
130
|
});
|
|
135
131
|
}
|
|
@@ -139,16 +135,28 @@ function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
|
139
135
|
type AgentEventStatus = "pending" | "running" | "completed" | "failed" | "aborted";
|
|
140
136
|
|
|
141
137
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* hundreds of throttled progress ticks.
|
|
138
|
+
* Coalescing key of a progress-snapshot event: `agent` and `judge_batch`
|
|
139
|
+
* events keyed by `id`, where only the newest snapshot matters. Discrete
|
|
140
|
+
* actions (everything else) have no key. Shared by {@link upsertStatusEvent}
|
|
141
|
+
* and the executors' display collectors so both coalesce identically.
|
|
147
142
|
*/
|
|
148
|
-
export function
|
|
143
|
+
export function statusEventKey(event: { op: string; [key: string]: unknown }): string | undefined {
|
|
149
144
|
if ((event.op === "agent" || event.op === "judge_batch") && typeof event.id === "string") {
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
return `${event.op}\0${event.id}`;
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Append or replace a status event. Progress snapshots (see
|
|
152
|
+
* {@link statusEventKey}) coalesce in place, preserving first-seen order; every
|
|
153
|
+
* other op is a discrete action and simply appends. Keeps the persisted event
|
|
154
|
+
* list bounded even when a subagent or batch emits hundreds of progress ticks.
|
|
155
|
+
*/
|
|
156
|
+
export function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void {
|
|
157
|
+
const key = statusEventKey(event);
|
|
158
|
+
if (key !== undefined) {
|
|
159
|
+
const idx = events.findIndex(e => statusEventKey(e) === key);
|
|
152
160
|
if (idx >= 0) {
|
|
153
161
|
events[idx] = event;
|
|
154
162
|
return;
|
package/src/tui.ts
CHANGED
|
@@ -137,9 +137,12 @@ export interface TuiPaint {
|
|
|
137
137
|
readonly rows: number;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
/** Observer of completed terminal paints; see {@link TUI.addPaintListener}. */
|
|
141
|
+
export type PaintListener = (paint: TuiPaint) => void;
|
|
142
|
+
|
|
140
143
|
export interface TUIOptions {
|
|
141
144
|
renderScheduler?: RenderScheduler;
|
|
142
|
-
onPaint?:
|
|
145
|
+
onPaint?: PaintListener;
|
|
143
146
|
}
|
|
144
147
|
/** Physical terminal dimensions supplied to a frame provider. */
|
|
145
148
|
export interface ViewportSize {
|
|
@@ -789,7 +792,7 @@ export class TUI extends Container {
|
|
|
789
792
|
#debugNextWindowTop = 0;
|
|
790
793
|
#inputListeners = new Set<InputListener>();
|
|
791
794
|
#startListeners = new Set<StartListener>();
|
|
792
|
-
#
|
|
795
|
+
#paintListeners = new Set<PaintListener>();
|
|
793
796
|
|
|
794
797
|
/** Global callback for debug key (Shift+Ctrl+D). Called before input is forwarded to focused component. */
|
|
795
798
|
onDebug?: () => void;
|
|
@@ -922,7 +925,7 @@ export class TUI extends Container {
|
|
|
922
925
|
super();
|
|
923
926
|
this.terminal = terminal;
|
|
924
927
|
this.#renderScheduler = options?.renderScheduler ?? DEFAULT_RENDER_SCHEDULER;
|
|
925
|
-
|
|
928
|
+
if (options?.onPaint) this.#paintListeners.add(options.onPaint);
|
|
926
929
|
this.#showHardwareCursor = showHardwareCursor === undefined ? this.#showHardwareCursor : showHardwareCursor;
|
|
927
930
|
this.#watchdog = new LoopWatchdog();
|
|
928
931
|
}
|
|
@@ -931,9 +934,15 @@ export class TUI extends Container {
|
|
|
931
934
|
return mode === "append" || mode === "rebuild" || mode === "preserve" ? mode : "preserve";
|
|
932
935
|
}
|
|
933
936
|
|
|
934
|
-
/**
|
|
935
|
-
|
|
936
|
-
|
|
937
|
+
/**
|
|
938
|
+
* Observe completed terminal paints; returns the unsubscribe. Independent
|
|
939
|
+
* observers (live stream publisher, session recorder) coexist.
|
|
940
|
+
*/
|
|
941
|
+
addPaintListener(listener: PaintListener): () => void {
|
|
942
|
+
this.#paintListeners.add(listener);
|
|
943
|
+
return () => {
|
|
944
|
+
this.#paintListeners.delete(listener);
|
|
945
|
+
};
|
|
937
946
|
}
|
|
938
947
|
|
|
939
948
|
/** Install the product-owned bounded frame provider. */
|
|
@@ -2636,10 +2645,12 @@ export class TUI extends Container {
|
|
|
2636
2645
|
}
|
|
2637
2646
|
|
|
2638
2647
|
#notifyPaint(paint: TuiPaint): void {
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2648
|
+
for (const listener of this.#paintListeners) {
|
|
2649
|
+
try {
|
|
2650
|
+
listener(paint);
|
|
2651
|
+
} catch (err) {
|
|
2652
|
+
logger.error("TUI paint listener failed", { err });
|
|
2653
|
+
}
|
|
2643
2654
|
}
|
|
2644
2655
|
}
|
|
2645
2656
|
|