@oh-my-pi/pi-coding-agent 17.1.8 → 17.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/CHANGELOG.md +92 -0
- package/dist/{CHANGELOG-vt8ene9g.md → CHANGELOG-be1f2t8h.md} +92 -0
- package/dist/cli.js +9986 -9776
- package/dist/{template-dys3vk5b.js → template-8vdv6xb4.js} +1 -1
- package/dist/types/advisor/runtime.d.ts +8 -2
- package/dist/types/advisor/transcript-recorder.d.ts +15 -0
- package/dist/types/auto-thinking/classifier.d.ts +9 -2
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/session-picker.d.ts +14 -7
- package/dist/types/collab/guest.d.ts +21 -2
- package/dist/types/commands/launch.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +63 -8
- package/dist/types/cursor-bridge-tools.d.ts +54 -0
- package/dist/types/cursor.d.ts +193 -8
- package/dist/types/edit/edit-clipboard.d.ts +19 -0
- package/dist/types/edit/hashline/diff.d.ts +9 -1
- package/dist/types/edit/index.d.ts +8 -1
- package/dist/types/edit/streaming.d.ts +8 -1
- package/dist/types/extensibility/extensions/runner.d.ts +19 -2
- package/dist/types/extensibility/extensions/types.d.ts +25 -1
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +2 -0
- package/dist/types/internal-urls/local-protocol.d.ts +3 -2
- package/dist/types/launch/terminal-output-worker-client.d.ts +5 -0
- package/dist/types/launch/terminal-output-worker-protocol.d.ts +14 -0
- package/dist/types/launch/terminal-output-worker.d.ts +1 -0
- package/dist/types/lsp/client.d.ts +2 -0
- package/dist/types/main.d.ts +2 -0
- package/dist/types/mcp/manager.d.ts +33 -4
- package/dist/types/modes/components/codex-reset-fireworks.d.ts +51 -0
- package/dist/types/modes/components/read-tool-group.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +6 -0
- package/dist/types/modes/components/status-line/component.d.ts +23 -0
- package/dist/types/modes/components/tool-execution.d.ts +16 -7
- package/dist/types/modes/components/usage-row.d.ts +2 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +26 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +4 -2
- package/dist/types/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +16 -0
- package/dist/types/modes/theme/theme.d.ts +12 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session-events.d.ts +2 -0
- package/dist/types/session/agent-session-types.d.ts +36 -1
- package/dist/types/session/agent-session.d.ts +4 -2
- package/dist/types/session/claude-session-store.d.ts +13 -0
- package/dist/types/session/codex-session-store.d.ts +14 -0
- package/dist/types/session/credential-pin.d.ts +58 -0
- package/dist/types/session/foreign-session-import.d.ts +15 -0
- package/dist/types/session/foreign-session-jsonl.d.ts +9 -0
- package/dist/types/session/foreign-session-store.d.ts +23 -0
- package/dist/types/session/session-advisors.d.ts +47 -2
- package/dist/types/session/session-entries.d.ts +19 -1
- package/dist/types/session/session-handoff.d.ts +4 -1
- package/dist/types/session/session-loader.d.ts +2 -0
- package/dist/types/session/session-maintenance.d.ts +1 -1
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-tools.d.ts +25 -1
- package/dist/types/session/turn-recovery.d.ts +10 -4
- package/dist/types/system-prompt.d.ts +2 -1
- package/dist/types/task/executor.d.ts +11 -2
- package/dist/types/task/render.d.ts +7 -0
- package/dist/types/thinking.d.ts +18 -6
- package/dist/types/tools/acp-bridge.d.ts +28 -5
- package/dist/types/tools/grep.d.ts +17 -1
- package/dist/types/tools/hub/launch.d.ts +5 -1
- package/dist/types/tools/index.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +19 -0
- package/dist/types/tools/read.d.ts +2 -0
- package/dist/types/tools/xdev.d.ts +2 -1
- package/dist/types/utils/changelog.d.ts +19 -0
- package/dist/types/utils/git.d.ts +10 -0
- package/dist/types/utils/jj.d.ts +11 -4
- package/dist/types/web/search/types.d.ts +2 -2
- package/package.json +12 -12
- package/src/advisor/runtime.ts +138 -12
- package/src/advisor/transcript-recorder.ts +56 -0
- package/src/auto-thinking/classifier.ts +48 -10
- package/src/cli/args.ts +6 -0
- package/src/cli/flag-tables.ts +2 -0
- package/src/cli/gallery-fixtures/fs.ts +25 -0
- package/src/cli/session-picker.ts +34 -17
- package/src/cli/ttsr-cli.ts +19 -1
- package/src/cli.ts +9 -0
- package/src/collab/guest.ts +37 -4
- package/src/collab/host.ts +1 -0
- package/src/commands/launch.ts +6 -0
- package/src/config/model-registry.ts +103 -25
- package/src/config/settings-schema.ts +65 -6
- package/src/config/settings.ts +25 -0
- package/src/cursor-bridge-tools.ts +81 -0
- package/src/cursor.ts +463 -12
- package/src/edit/edit-clipboard.ts +23 -0
- package/src/edit/hashline/diff.ts +49 -10
- package/src/edit/hashline/execute.ts +38 -6
- package/src/edit/hashline/filesystem.ts +27 -3
- package/src/edit/index.ts +12 -2
- package/src/edit/renderer.ts +4 -4
- package/src/edit/streaming.ts +17 -2
- package/src/export/html/template.js +1 -1
- package/src/extensibility/extensions/runner.ts +70 -0
- package/src/extensibility/extensions/types.ts +30 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +12 -13
- package/src/internal-urls/local-protocol.ts +3 -2
- package/src/internal-urls/mcp-protocol.ts +4 -1
- package/src/launch/terminal-output-worker-client.ts +53 -0
- package/src/launch/terminal-output-worker-protocol.ts +11 -0
- package/src/launch/terminal-output-worker.ts +23 -0
- package/src/lsp/client.ts +18 -3
- package/src/lsp/index.ts +4 -0
- package/src/lsp/render.ts +1 -1
- package/src/main.ts +127 -56
- package/src/mcp/manager.ts +120 -18
- package/src/modes/acp/acp-agent.ts +15 -9
- package/src/modes/components/chat-transcript-builder.ts +30 -11
- package/src/modes/components/codex-reset-fireworks.ts +369 -0
- package/src/modes/components/read-tool-group.ts +159 -17
- package/src/modes/components/session-selector.ts +19 -3
- package/src/modes/components/status-line/component.ts +381 -54
- package/src/modes/components/tool-execution.ts +78 -31
- package/src/modes/components/usage-row.ts +10 -5
- package/src/modes/controllers/event-controller.ts +26 -4
- package/src/modes/controllers/mcp-command-controller.ts +68 -3
- package/src/modes/controllers/selector-controller.ts +94 -36
- package/src/modes/controllers/tan-command-controller.ts +14 -0
- package/src/modes/interactive-mode.ts +31 -17
- package/src/modes/rpc/rpc-client.ts +18 -0
- package/src/modes/rpc/rpc-mode.ts +16 -1
- package/src/modes/rpc/rpc-types.ts +11 -0
- package/src/modes/theme/theme.ts +151 -6
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +38 -21
- package/src/prompts/system/auto-thinking-difficulty.md +4 -2
- package/src/prompts/system/plan-mode-active.md +2 -2
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/system/xdev-mount-notice.md +1 -1
- package/src/prompts/tools/bash.md +15 -17
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/glob.md +7 -6
- package/src/prompts/tools/grep.md +7 -6
- package/src/sdk.ts +178 -15
- package/src/session/agent-session-events.ts +1 -0
- package/src/session/agent-session-types.ts +40 -1
- package/src/session/agent-session.ts +302 -175
- package/src/session/claude-session-store.ts +426 -0
- package/src/session/codex-session-store.ts +673 -0
- package/src/session/credential-pin.ts +93 -0
- package/src/session/foreign-session-import.ts +52 -0
- package/src/session/foreign-session-jsonl.ts +29 -0
- package/src/session/foreign-session-store.ts +26 -0
- package/src/session/model-controls.ts +11 -3
- package/src/session/session-advisors.ts +140 -16
- package/src/session/session-entries.ts +21 -1
- package/src/session/session-handoff.ts +20 -3
- package/src/session/session-loader.ts +25 -9
- package/src/session/session-maintenance.ts +59 -11
- package/src/session/session-manager.ts +61 -0
- package/src/session/session-tools.ts +107 -5
- package/src/session/settings-stream-fn.ts +1 -0
- package/src/session/turn-recovery.ts +97 -51
- package/src/slash-commands/builtin-registry.ts +131 -3
- package/src/system-prompt.ts +3 -2
- package/src/task/executor.ts +17 -4
- package/src/task/render.ts +20 -4
- package/src/thinking.ts +29 -10
- package/src/tools/acp-bridge.ts +52 -8
- package/src/tools/browser.ts +5 -0
- package/src/tools/checkpoint.ts +0 -13
- package/src/tools/grep.ts +64 -8
- package/src/tools/hub/launch.ts +10 -15
- package/src/tools/index.ts +28 -4
- package/src/tools/path-utils.ts +88 -0
- package/src/tools/read.ts +3 -0
- package/src/tools/write.ts +9 -3
- package/src/tools/xdev.ts +39 -14
- package/src/utils/changelog.ts +144 -4
- package/src/utils/git.ts +61 -14
- package/src/utils/jj.ts +19 -13
- package/src/web/search/providers/codex.ts +34 -0
- package/src/web/search/types.ts +1 -1
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Component,
|
|
3
|
+
getSegmenter,
|
|
4
|
+
matchesKey,
|
|
5
|
+
type OverlayHandle,
|
|
6
|
+
type OverlayOptions,
|
|
7
|
+
truncateToWidth,
|
|
8
|
+
visibleWidth,
|
|
9
|
+
} from "@oh-my-pi/pi-tui";
|
|
10
|
+
import { type ThemeColor, theme } from "../theme/theme";
|
|
11
|
+
|
|
12
|
+
const FRAME_INTERVAL_MS = 85;
|
|
13
|
+
const FRAME_COUNT = 34;
|
|
14
|
+
|
|
15
|
+
const FIREWORK_THEME_COLORS = {
|
|
16
|
+
cyan: "mdLink",
|
|
17
|
+
dim: "dim",
|
|
18
|
+
gold: "warning",
|
|
19
|
+
green: "success",
|
|
20
|
+
pink: "accent",
|
|
21
|
+
violet: "thinkingXhigh",
|
|
22
|
+
white: "text",
|
|
23
|
+
} as const satisfies Record<string, ThemeColor>;
|
|
24
|
+
|
|
25
|
+
type FireworkColor = keyof typeof FIREWORK_THEME_COLORS;
|
|
26
|
+
|
|
27
|
+
/** The active Codex account fields retained between status refreshes. */
|
|
28
|
+
export interface CodexResetUsageSnapshot {
|
|
29
|
+
/** When this usage report was observed, if supplied by the provider. */
|
|
30
|
+
observedAt?: number;
|
|
31
|
+
/** Weekly usage, its quota identity, and its previously scheduled reset deadline. */
|
|
32
|
+
sevenDay?: { percent: number; resetsAt?: number; tier?: string; plan?: string };
|
|
33
|
+
savedResets?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A detected Codex quota event that can trigger the fireworks presentation. */
|
|
37
|
+
export type CodexResetFireworksEvent =
|
|
38
|
+
| { kind: "unscheduled-weekly-reset" }
|
|
39
|
+
| { kind: "saved-reset-banked"; added: number; available: number };
|
|
40
|
+
|
|
41
|
+
interface CanvasCell {
|
|
42
|
+
glyph: string;
|
|
43
|
+
color: FireworkColor;
|
|
44
|
+
priority: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface FireworkBurst {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
start: number;
|
|
51
|
+
color: FireworkColor;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ActiveFireworks {
|
|
55
|
+
component: CodexResetFireworksComponent;
|
|
56
|
+
overlay: OverlayHandle;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface CodexResetFireworksHost {
|
|
60
|
+
ui: {
|
|
61
|
+
showOverlay(component: Component, options?: OverlayOptions): OverlayHandle;
|
|
62
|
+
setFocus(component: Component): void;
|
|
63
|
+
requestRender(): void;
|
|
64
|
+
readonly terminal: { readonly rows: number };
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const BURSTS: readonly FireworkBurst[] = [
|
|
69
|
+
{ x: 0.17, y: 0.46, start: 5, color: "pink" },
|
|
70
|
+
{ x: 0.48, y: 0.2, start: 9, color: "cyan" },
|
|
71
|
+
{ x: 0.78, y: 0.42, start: 13, color: "gold" },
|
|
72
|
+
{ x: 0.31, y: 0.24, start: 17, color: "violet" },
|
|
73
|
+
{ x: 0.65, y: 0.28, start: 21, color: "green" },
|
|
74
|
+
{ x: 0.88, y: 0.18, start: 25, color: "pink" },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Compare consecutive reports for one Codex account. A saved-reset grant takes
|
|
79
|
+
* precedence when both changes arrive in the same report. A verified decrease,
|
|
80
|
+
* or a prior positive balance becoming unavailable, suppresses the weekly event
|
|
81
|
+
* because the user may have redeemed a credit. Other weekly usage drops are
|
|
82
|
+
* celebrated only before the previously scheduled reset deadline.
|
|
83
|
+
*/
|
|
84
|
+
export function detectCodexResetFireworks(
|
|
85
|
+
previous: CodexResetUsageSnapshot,
|
|
86
|
+
current: CodexResetUsageSnapshot,
|
|
87
|
+
): CodexResetFireworksEvent | undefined {
|
|
88
|
+
const previousSavedResets = previous.savedResets;
|
|
89
|
+
const currentSavedResets = current.savedResets;
|
|
90
|
+
if (previousSavedResets !== undefined) {
|
|
91
|
+
if (currentSavedResets === undefined) {
|
|
92
|
+
if (previousSavedResets > 0) return undefined;
|
|
93
|
+
} else {
|
|
94
|
+
if (currentSavedResets > previousSavedResets) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "saved-reset-banked",
|
|
97
|
+
added: currentSavedResets - previousSavedResets,
|
|
98
|
+
available: currentSavedResets,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (currentSavedResets < previousSavedResets) return undefined;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!previous.sevenDay || !current.sevenDay) return undefined;
|
|
106
|
+
if (previous.sevenDay.tier !== current.sevenDay.tier || previous.sevenDay.plan !== current.sevenDay.plan) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
const previousWeeklyPercent = Math.round(Math.max(0, Math.min(100, previous.sevenDay.percent)));
|
|
110
|
+
const currentWeeklyPercent = Math.round(Math.max(0, Math.min(100, current.sevenDay.percent)));
|
|
111
|
+
if (previousWeeklyPercent === 0 || currentWeeklyPercent >= previousWeeklyPercent) return undefined;
|
|
112
|
+
|
|
113
|
+
const scheduledResetAt = previous.sevenDay.resetsAt;
|
|
114
|
+
if (
|
|
115
|
+
scheduledResetAt === undefined ||
|
|
116
|
+
!Number.isFinite(scheduledResetAt) ||
|
|
117
|
+
typeof current.observedAt !== "number" ||
|
|
118
|
+
!Number.isFinite(current.observedAt) ||
|
|
119
|
+
current.observedAt >= scheduledResetAt
|
|
120
|
+
) {
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
return { kind: "unscheduled-weekly-reset" };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function setCell(
|
|
127
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
128
|
+
x: number,
|
|
129
|
+
y: number,
|
|
130
|
+
glyph: string,
|
|
131
|
+
color: FireworkColor,
|
|
132
|
+
priority: number,
|
|
133
|
+
): void {
|
|
134
|
+
const row = canvas[y];
|
|
135
|
+
if (!row || x < 0 || x >= row.length) return;
|
|
136
|
+
const current = row[x];
|
|
137
|
+
if (!current || priority >= current.priority) row[x] = { glyph, color, priority };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function drawText(
|
|
141
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
142
|
+
x: number,
|
|
143
|
+
y: number,
|
|
144
|
+
text: string,
|
|
145
|
+
color: FireworkColor,
|
|
146
|
+
priority: number,
|
|
147
|
+
): void {
|
|
148
|
+
let column = x;
|
|
149
|
+
for (const { segment } of getSegmenter().segment(text)) {
|
|
150
|
+
const width = visibleWidth(segment);
|
|
151
|
+
if (width <= 0) continue;
|
|
152
|
+
setCell(canvas, column, y, segment, color, priority);
|
|
153
|
+
for (let continuation = 1; continuation < width; continuation++) {
|
|
154
|
+
setCell(canvas, column + continuation, y, "", color, priority);
|
|
155
|
+
}
|
|
156
|
+
column += width;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function drawBanner(
|
|
161
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
162
|
+
left: number,
|
|
163
|
+
artWidth: number,
|
|
164
|
+
height: number,
|
|
165
|
+
event: CodexResetFireworksEvent,
|
|
166
|
+
): void {
|
|
167
|
+
if (height < 3 || artWidth < 8) return;
|
|
168
|
+
const panelWidth = Math.min(62, artWidth);
|
|
169
|
+
const panelLeft = left + Math.floor((artWidth - panelWidth) / 2);
|
|
170
|
+
const top = height - 3;
|
|
171
|
+
const innerWidth = panelWidth - 2;
|
|
172
|
+
const titleText =
|
|
173
|
+
event.kind === "unscheduled-weekly-reset" ? " O P E N A I R E S E T " : " S A V E D R E S E T ";
|
|
174
|
+
const subtitleText =
|
|
175
|
+
event.kind === "unscheduled-weekly-reset"
|
|
176
|
+
? "Weekly usage cleared early · ESC to return"
|
|
177
|
+
: event.added === 1
|
|
178
|
+
? `New reset banked · ${event.available} available · ESC to return`
|
|
179
|
+
: `${event.added} resets banked · ${event.available} available · ESC to return`;
|
|
180
|
+
const title = truncateToWidth(titleText, innerWidth, "");
|
|
181
|
+
const subtitle = truncateToWidth(subtitleText, innerWidth, "");
|
|
182
|
+
const titleOffset = Math.floor((innerWidth - visibleWidth(title)) / 2);
|
|
183
|
+
const subtitleOffset = Math.floor((innerWidth - visibleWidth(subtitle)) / 2);
|
|
184
|
+
|
|
185
|
+
drawText(canvas, panelLeft, top, `╭${"─".repeat(innerWidth)}╮`, "violet", 20);
|
|
186
|
+
drawText(canvas, panelLeft + 1 + titleOffset, top, title, "gold", 21);
|
|
187
|
+
drawText(canvas, panelLeft, top + 1, `│${" ".repeat(innerWidth)}│`, "violet", 20);
|
|
188
|
+
drawText(canvas, panelLeft + 1 + subtitleOffset, top + 1, subtitle, "cyan", 21);
|
|
189
|
+
drawText(canvas, panelLeft, top + 2, `╰${"─".repeat(innerWidth)}╯`, "violet", 20);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function drawStars(
|
|
193
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
194
|
+
left: number,
|
|
195
|
+
artWidth: number,
|
|
196
|
+
skyHeight: number,
|
|
197
|
+
frame: number,
|
|
198
|
+
): void {
|
|
199
|
+
if (skyHeight <= 0) return;
|
|
200
|
+
const count = Math.min(26, Math.max(5, Math.floor(artWidth / 3)));
|
|
201
|
+
for (let index = 0; index < count; index++) {
|
|
202
|
+
const x = left + ((index * 37 + 11) % artWidth);
|
|
203
|
+
const y = (index * 7 + 2) % skyHeight;
|
|
204
|
+
const bright = (index + Math.floor(frame / 3)) % 5 === 0;
|
|
205
|
+
setCell(canvas, x, y, bright ? "+" : ".", bright ? "white" : "dim", bright ? 2 : 1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function drawBurst(
|
|
210
|
+
canvas: Array<Array<CanvasCell | undefined>>,
|
|
211
|
+
burst: FireworkBurst,
|
|
212
|
+
left: number,
|
|
213
|
+
artWidth: number,
|
|
214
|
+
skyHeight: number,
|
|
215
|
+
frame: number,
|
|
216
|
+
): void {
|
|
217
|
+
if (skyHeight <= 1) return;
|
|
218
|
+
const centerX = left + Math.round((artWidth - 1) * burst.x);
|
|
219
|
+
const centerY = Math.max(0, Math.min(skyHeight - 2, Math.round((skyHeight - 1) * burst.y)));
|
|
220
|
+
const age = frame - burst.start;
|
|
221
|
+
|
|
222
|
+
if (age >= -6 && age < 0) {
|
|
223
|
+
const progress = (age + 6) / 6;
|
|
224
|
+
const y = skyHeight - 1 - Math.round(progress * (skyHeight - 1 - centerY));
|
|
225
|
+
setCell(canvas, centerX, y, "^", "white", 8);
|
|
226
|
+
setCell(canvas, centerX, y + 1, "|", burst.color, 7);
|
|
227
|
+
setCell(canvas, centerX, y + 2, ".", "gold", 6);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (age < 0 || age > 8) return;
|
|
231
|
+
|
|
232
|
+
const radius = age === 0 ? 0 : 0.8 + age * 0.92;
|
|
233
|
+
const gravity = Math.floor((age * age) / 22);
|
|
234
|
+
const glyphs = ["@", "*", "*", "+", "o", "o", ".", ".", "."] as const;
|
|
235
|
+
const particleColor: FireworkColor = age <= 5 ? burst.color : age <= 7 ? "gold" : "dim";
|
|
236
|
+
|
|
237
|
+
for (let particle = 0; particle < 20; particle++) {
|
|
238
|
+
const angle = (particle / 20) * Math.PI * 2 + burst.start * 0.17;
|
|
239
|
+
const x = centerX + Math.round(Math.cos(angle) * radius * 1.75);
|
|
240
|
+
const y = centerY + Math.round(Math.sin(angle) * radius * 0.58 + gravity);
|
|
241
|
+
setCell(canvas, x, y, glyphs[age], particleColor, 10);
|
|
242
|
+
if (age >= 2 && age <= 6) {
|
|
243
|
+
const trailRadius = Math.max(0, radius - 1.4);
|
|
244
|
+
const trailX = centerX + Math.round(Math.cos(angle) * trailRadius * 1.75);
|
|
245
|
+
const trailY = centerY + Math.round(Math.sin(angle) * trailRadius * 0.58 + gravity);
|
|
246
|
+
setCell(canvas, trailX, trailY, ".", "dim", 5);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (age <= 2) setCell(canvas, centerX, centerY, age === 0 ? "@" : "+", "white", 12);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function renderCanvas(canvas: Array<Array<CanvasCell | undefined>>): string[] {
|
|
253
|
+
return canvas.map(row => {
|
|
254
|
+
let output = "";
|
|
255
|
+
let run = "";
|
|
256
|
+
let runColor: FireworkColor | undefined;
|
|
257
|
+
for (const cell of row) {
|
|
258
|
+
if (cell && cell.color !== runColor) {
|
|
259
|
+
if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run;
|
|
260
|
+
run = "";
|
|
261
|
+
runColor = cell.color;
|
|
262
|
+
}
|
|
263
|
+
run += cell?.glyph ?? " ";
|
|
264
|
+
}
|
|
265
|
+
if (run) output += runColor ? theme.fg(FIREWORK_THEME_COLORS[runColor], run) : run;
|
|
266
|
+
return output;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** Render one deterministic animation frame for the top-third overlay. */
|
|
271
|
+
function renderCodexResetFireworks(
|
|
272
|
+
width: number,
|
|
273
|
+
height: number,
|
|
274
|
+
frame: number,
|
|
275
|
+
event: CodexResetFireworksEvent,
|
|
276
|
+
): string[] {
|
|
277
|
+
const safeWidth = Math.max(1, Math.floor(width));
|
|
278
|
+
const safeHeight = Math.max(1, Math.floor(height));
|
|
279
|
+
const artWidth = Math.min(96, safeWidth);
|
|
280
|
+
const left = Math.floor((safeWidth - artWidth) / 2);
|
|
281
|
+
const skyHeight = Math.max(0, safeHeight - 3);
|
|
282
|
+
const canvas = Array.from({ length: safeHeight }, () => new Array<CanvasCell | undefined>(safeWidth));
|
|
283
|
+
|
|
284
|
+
drawStars(canvas, left, artWidth, skyHeight, frame);
|
|
285
|
+
for (const burst of BURSTS) drawBurst(canvas, burst, left, artWidth, skyHeight, frame);
|
|
286
|
+
drawBanner(canvas, left, artWidth, safeHeight, event);
|
|
287
|
+
return renderCanvas(canvas);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
class CodexResetFireworksComponent implements Component {
|
|
291
|
+
#timer: NodeJS.Timeout | undefined;
|
|
292
|
+
#done = Promise.withResolvers<void>();
|
|
293
|
+
#frame = 0;
|
|
294
|
+
#disposed = false;
|
|
295
|
+
|
|
296
|
+
constructor(
|
|
297
|
+
readonly host: CodexResetFireworksHost,
|
|
298
|
+
readonly event: CodexResetFireworksEvent,
|
|
299
|
+
) {}
|
|
300
|
+
|
|
301
|
+
run(): Promise<void> {
|
|
302
|
+
this.#timer ??= setInterval(() => {
|
|
303
|
+
if (this.#disposed) return;
|
|
304
|
+
this.#frame = (this.#frame + 1) % FRAME_COUNT;
|
|
305
|
+
this.host.ui.requestRender();
|
|
306
|
+
}, FRAME_INTERVAL_MS);
|
|
307
|
+
this.host.ui.requestRender();
|
|
308
|
+
return this.#done.promise;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
dispose(): void {
|
|
312
|
+
if (this.#disposed) return;
|
|
313
|
+
this.#disposed = true;
|
|
314
|
+
if (this.#timer) {
|
|
315
|
+
clearInterval(this.#timer);
|
|
316
|
+
this.#timer = undefined;
|
|
317
|
+
}
|
|
318
|
+
this.#done.resolve();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
handleInput(data: string): void {
|
|
322
|
+
if (matchesKey(data, "escape") || matchesKey(data, "esc")) this.#done.resolve();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
render(width: number): readonly string[] {
|
|
326
|
+
const height = Math.max(1, Math.floor(this.host.ui.terminal.rows * 0.33));
|
|
327
|
+
return renderCodexResetFireworks(width, height, this.#frame, this.event);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Owns the at-most-one modal celebration lifecycle for an interactive session. */
|
|
332
|
+
export class CodexResetFireworksController {
|
|
333
|
+
#active: ActiveFireworks | undefined;
|
|
334
|
+
|
|
335
|
+
constructor(private readonly host: CodexResetFireworksHost) {}
|
|
336
|
+
|
|
337
|
+
/** Present a celebration unless another one already owns the modal overlay. */
|
|
338
|
+
show(event: CodexResetFireworksEvent): boolean {
|
|
339
|
+
if (this.#active) return false;
|
|
340
|
+
const component = new CodexResetFireworksComponent(this.host, event);
|
|
341
|
+
const overlay = this.host.ui.showOverlay(component, {
|
|
342
|
+
anchor: "top-center",
|
|
343
|
+
width: "100%",
|
|
344
|
+
maxHeight: "33%",
|
|
345
|
+
margin: 0,
|
|
346
|
+
});
|
|
347
|
+
this.#active = { component, overlay };
|
|
348
|
+
this.host.ui.setFocus(component);
|
|
349
|
+
void component.run().then(() => this.#finish(component));
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Stop the active celebration and release its overlay, if present. */
|
|
354
|
+
dispose(): void {
|
|
355
|
+
const active = this.#active;
|
|
356
|
+
if (!active) return;
|
|
357
|
+
this.#active = undefined;
|
|
358
|
+
active.component.dispose();
|
|
359
|
+
active.overlay.hide();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
#finish(component: CodexResetFireworksComponent): void {
|
|
363
|
+
const active = this.#active;
|
|
364
|
+
if (!active || active.component !== component) return;
|
|
365
|
+
this.#active = undefined;
|
|
366
|
+
component.dispose();
|
|
367
|
+
active.overlay.hide();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import type { AssistantMessage, Usage } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
4
|
import { Container, Text } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { InternalUrlRouter, XD_URL_PREFIX } from "../../internal-urls";
|
|
@@ -6,7 +7,9 @@ import { getLanguageFromPath, theme } from "../../modes/theme/theme";
|
|
|
6
7
|
import { parseLineRanges, selectorLineRanges, splitPathAndSel } from "../../tools/path-utils";
|
|
7
8
|
import { PREVIEW_LIMITS, shortenPath } from "../../tools/render-utils";
|
|
8
9
|
import { fileHyperlink, renderCodeCell, tryResolveInternalUrlSync } from "../../tui";
|
|
10
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
9
11
|
import type { ToolExecutionHandle } from "./tool-execution";
|
|
12
|
+
import { formatUsageRow } from "./usage-row";
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Extract the read call's target path. `path` is the canonical arg; `file_path`
|
|
@@ -39,6 +42,33 @@ export function readArgsCollapseIntoGroup(args: unknown): boolean {
|
|
|
39
42
|
return target.startsWith(XD_URL_PREFIX) || !InternalUrlRouter.instance().canHandle(target);
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Return the collapsed read calls that can own a turn's usage row. Mixed-tool
|
|
47
|
+
* turns and visible content after a read keep the standalone row so request
|
|
48
|
+
* metrics retain their transcript ordering.
|
|
49
|
+
*/
|
|
50
|
+
export function groupedReadUsageCallIds(message: AssistantMessage): string[] | undefined {
|
|
51
|
+
const toolCallIds: string[] = [];
|
|
52
|
+
let sawToolCall = false;
|
|
53
|
+
for (const content of message.content) {
|
|
54
|
+
if (content.type === "toolCall") {
|
|
55
|
+
if (content.name !== "read" || !readArgsCollapseIntoGroup(content.arguments)) return undefined;
|
|
56
|
+
sawToolCall = true;
|
|
57
|
+
toolCallIds.push(content.id);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (
|
|
61
|
+
sawToolCall &&
|
|
62
|
+
(content.type === "image" ||
|
|
63
|
+
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
64
|
+
(content.type === "thinking" && canonicalizeMessage(content.thinking)))
|
|
65
|
+
) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return toolCallIds.length > 0 ? toolCallIds : undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
42
72
|
type ReadRenderArgs = {
|
|
43
73
|
path?: string;
|
|
44
74
|
file_path?: string;
|
|
@@ -94,6 +124,14 @@ type ReadEntry = {
|
|
|
94
124
|
codeLineNumbers?: Array<number | null>;
|
|
95
125
|
};
|
|
96
126
|
|
|
127
|
+
type ReadUsageRow = {
|
|
128
|
+
toolCallIds: readonly string[];
|
|
129
|
+
usage: Usage;
|
|
130
|
+
durationMs?: number;
|
|
131
|
+
ttftMs?: number;
|
|
132
|
+
timestamp?: number;
|
|
133
|
+
};
|
|
134
|
+
|
|
97
135
|
/** Number of code lines to show in collapsed preview mode */
|
|
98
136
|
const COLLAPSED_PREVIEW_LINES = PREVIEW_LIMITS.OUTPUT_COLLAPSED;
|
|
99
137
|
|
|
@@ -290,6 +328,8 @@ function formatMergedSelectorParts(selectors: string[]): string {
|
|
|
290
328
|
|
|
291
329
|
export class ReadToolGroupComponent extends Container implements ToolExecutionHandle {
|
|
292
330
|
#entries = new Map<string, ReadEntry>();
|
|
331
|
+
#usageRows = new Map<string, ReadUsageRow>();
|
|
332
|
+
#usageBatchByToolCallId = new Map<string, string>();
|
|
293
333
|
#text: Text;
|
|
294
334
|
#expanded = false;
|
|
295
335
|
#showContentPreview: boolean;
|
|
@@ -394,6 +434,33 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
394
434
|
this.#updateDisplay();
|
|
395
435
|
}
|
|
396
436
|
|
|
437
|
+
/**
|
|
438
|
+
* Nest one request's usage beneath the last visible read call from that
|
|
439
|
+
* request. Parallel reads share one row rather than duplicating request totals.
|
|
440
|
+
*/
|
|
441
|
+
attachUsage(
|
|
442
|
+
toolCallIds: readonly string[],
|
|
443
|
+
usage: Usage,
|
|
444
|
+
durationMs?: number,
|
|
445
|
+
ttftMs?: number,
|
|
446
|
+
timestamp?: number,
|
|
447
|
+
): boolean {
|
|
448
|
+
const attachedToolCallIds: string[] = [];
|
|
449
|
+
let anchorId: string | undefined;
|
|
450
|
+
for (const toolCallId of toolCallIds) {
|
|
451
|
+
if (!this.#entries.has(toolCallId)) continue;
|
|
452
|
+
attachedToolCallIds.push(toolCallId);
|
|
453
|
+
anchorId = toolCallId;
|
|
454
|
+
}
|
|
455
|
+
if (!anchorId) return false;
|
|
456
|
+
for (const toolCallId of attachedToolCallIds) {
|
|
457
|
+
this.#usageBatchByToolCallId.set(toolCallId, anchorId);
|
|
458
|
+
}
|
|
459
|
+
this.#usageRows.set(anchorId, { toolCallIds: attachedToolCallIds, usage, durationMs, ttftMs, timestamp });
|
|
460
|
+
this.#updateDisplay();
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
397
464
|
setArgsComplete(_toolCallId?: string): void {
|
|
398
465
|
this.#updateDisplay();
|
|
399
466
|
}
|
|
@@ -427,13 +494,15 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
427
494
|
if (!this.#shouldRenderPreviewRow(row)) {
|
|
428
495
|
const statusSymbol = this.#formatStatus(this.#statusForTargets(row.targets));
|
|
429
496
|
const pathDisplay = this.#formatRowPath(row);
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
);
|
|
497
|
+
const lines = [` ${statusSymbol} ${theme.fg("toolTitle", theme.bold("Read"))} ${pathDisplay}`.trimEnd()];
|
|
498
|
+
const usageRows = this.#usageRowsBySummaryRow(displayRows).get(0) ?? [];
|
|
499
|
+
this.#appendUsageRows(lines, usageRows, " ");
|
|
500
|
+
this.#text.setText(lines.join("\n"));
|
|
433
501
|
this.addChild(this.#text);
|
|
434
502
|
}
|
|
435
503
|
for (const entry of this.#previewEntriesForRow(row)) {
|
|
436
504
|
this.#addContentPreview(entry);
|
|
505
|
+
this.#addPreviewUsage(entry);
|
|
437
506
|
}
|
|
438
507
|
return;
|
|
439
508
|
}
|
|
@@ -443,8 +512,9 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
443
512
|
const entriesWithoutPreview = entries.filter(entry => !this.#shouldRenderPreview(entry));
|
|
444
513
|
const summaryTargets = this.#displayTargetsForEntries(entriesWithoutPreview);
|
|
445
514
|
const rows = this.#buildSummaryRows(summaryTargets);
|
|
515
|
+
const usageRowsBySummaryRow = this.#usageRowsBySummaryRow(rows);
|
|
446
516
|
for (const [index, row] of rows.entries()) {
|
|
447
|
-
this.#appendSummaryRow(lines, row, index, rows.length);
|
|
517
|
+
this.#appendSummaryRow(lines, row, index, rows.length, usageRowsBySummaryRow.get(index) ?? []);
|
|
448
518
|
}
|
|
449
519
|
|
|
450
520
|
this.#text.setText(lines.join("\n"));
|
|
@@ -453,6 +523,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
453
523
|
for (const entry of entries) {
|
|
454
524
|
if (this.#shouldRenderPreview(entry)) {
|
|
455
525
|
this.#addContentPreview(entry);
|
|
526
|
+
this.#addPreviewUsage(entry);
|
|
456
527
|
}
|
|
457
528
|
}
|
|
458
529
|
}
|
|
@@ -480,27 +551,37 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
480
551
|
}
|
|
481
552
|
|
|
482
553
|
#buildSummaryRows(targets: ReadDisplayTarget[]): ReadSummaryRow[] {
|
|
483
|
-
const
|
|
554
|
+
const selectorTargetsByBasePathAndBatch = new Map<string, Map<string | undefined, ReadDisplayTarget[]>>();
|
|
484
555
|
for (const target of targets) {
|
|
485
556
|
if (!target.selector) continue;
|
|
486
|
-
|
|
557
|
+
let targetsByBatch = selectorTargetsByBasePathAndBatch.get(target.basePath);
|
|
558
|
+
if (!targetsByBatch) {
|
|
559
|
+
targetsByBatch = new Map<string | undefined, ReadDisplayTarget[]>();
|
|
560
|
+
selectorTargetsByBasePathAndBatch.set(target.basePath, targetsByBatch);
|
|
561
|
+
}
|
|
562
|
+
const batchId = this.#usageBatchByToolCallId.get(target.entry.toolCallId);
|
|
563
|
+
const existing = targetsByBatch.get(batchId);
|
|
487
564
|
if (existing) existing.push(target);
|
|
488
|
-
else
|
|
565
|
+
else targetsByBatch.set(batchId, [target]);
|
|
489
566
|
}
|
|
490
567
|
|
|
491
|
-
const
|
|
492
|
-
for (const [basePath,
|
|
493
|
-
if (basePath
|
|
494
|
-
|
|
568
|
+
const mergedTargetsByTarget = new Map<ReadDisplayTarget, ReadDisplayTarget[]>();
|
|
569
|
+
for (const [basePath, targetsByBatch] of selectorTargetsByBasePathAndBatch) {
|
|
570
|
+
if (!basePath) continue;
|
|
571
|
+
for (const groupedTargets of targetsByBatch.values()) {
|
|
572
|
+
if (groupedTargets.length <= 1) continue;
|
|
573
|
+
for (const target of groupedTargets) {
|
|
574
|
+
mergedTargetsByTarget.set(target, groupedTargets);
|
|
575
|
+
}
|
|
495
576
|
}
|
|
496
577
|
}
|
|
497
578
|
|
|
498
|
-
const
|
|
579
|
+
const emittedMergedTargets = new Set<ReadDisplayTarget[]>();
|
|
499
580
|
const rows: ReadSummaryRow[] = [];
|
|
500
581
|
for (const target of targets) {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
582
|
+
const mergedTargets = mergedTargetsByTarget.get(target);
|
|
583
|
+
if (mergedTargets) {
|
|
584
|
+
if (!emittedMergedTargets.has(mergedTargets)) {
|
|
504
585
|
rows.push({
|
|
505
586
|
targetPath: `${target.basePath}:${formatMergedSelectorParts(
|
|
506
587
|
mergedTargets
|
|
@@ -510,7 +591,7 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
510
591
|
basePath: target.basePath,
|
|
511
592
|
targets: mergedTargets,
|
|
512
593
|
});
|
|
513
|
-
|
|
594
|
+
emittedMergedTargets.add(mergedTargets);
|
|
514
595
|
}
|
|
515
596
|
continue;
|
|
516
597
|
}
|
|
@@ -519,9 +600,58 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
519
600
|
return rows;
|
|
520
601
|
}
|
|
521
602
|
|
|
522
|
-
#appendSummaryRow(
|
|
603
|
+
#appendSummaryRow(
|
|
604
|
+
lines: string[],
|
|
605
|
+
row: ReadSummaryRow,
|
|
606
|
+
index: number,
|
|
607
|
+
total: number,
|
|
608
|
+
usageRows: ReadUsageRow[],
|
|
609
|
+
): void {
|
|
523
610
|
const connector = index === total - 1 ? theme.tree.last : theme.tree.branch;
|
|
524
611
|
lines.push(` ${theme.fg("dim", connector)} ${this.#formatRow(row)}`.trimEnd());
|
|
612
|
+
|
|
613
|
+
const connectorWidth = Bun.stringWidth(connector);
|
|
614
|
+
const continuation =
|
|
615
|
+
index === total - 1
|
|
616
|
+
? " ".repeat(connectorWidth)
|
|
617
|
+
: `${theme.tree.vertical}${" ".repeat(Math.max(0, connectorWidth - Bun.stringWidth(theme.tree.vertical)))}`;
|
|
618
|
+
this.#appendUsageRows(lines, usageRows, ` ${continuation} `);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#usageRowsBySummaryRow(rows: ReadSummaryRow[]): Map<number, ReadUsageRow[]> {
|
|
622
|
+
const lastRowIndexByToolCallId = new Map<string, number>();
|
|
623
|
+
for (const [index, row] of rows.entries()) {
|
|
624
|
+
for (const target of row.targets) {
|
|
625
|
+
lastRowIndexByToolCallId.set(target.entry.toolCallId, index);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const usageRowsByIndex = new Map<number, ReadUsageRow[]>();
|
|
630
|
+
for (const usageRow of this.#usageRows.values()) {
|
|
631
|
+
let lastRowIndex: number | undefined;
|
|
632
|
+
for (const toolCallId of usageRow.toolCallIds) {
|
|
633
|
+
const index = lastRowIndexByToolCallId.get(toolCallId);
|
|
634
|
+
if (index !== undefined && (lastRowIndex === undefined || index > lastRowIndex)) {
|
|
635
|
+
lastRowIndex = index;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
if (lastRowIndex === undefined) continue;
|
|
639
|
+
const usageRows = usageRowsByIndex.get(lastRowIndex);
|
|
640
|
+
if (usageRows) usageRows.push(usageRow);
|
|
641
|
+
else usageRowsByIndex.set(lastRowIndex, [usageRow]);
|
|
642
|
+
}
|
|
643
|
+
return usageRowsByIndex;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
#appendUsageRows(lines: string[], usageRows: ReadUsageRow[], prefix: string): void {
|
|
647
|
+
for (const usageRow of usageRows) {
|
|
648
|
+
lines.push(
|
|
649
|
+
theme.fg(
|
|
650
|
+
"dim",
|
|
651
|
+
`${prefix}${formatUsageRow(usageRow.usage, usageRow.durationMs, usageRow.ttftMs, usageRow.timestamp)}`,
|
|
652
|
+
),
|
|
653
|
+
);
|
|
654
|
+
}
|
|
525
655
|
}
|
|
526
656
|
|
|
527
657
|
#formatRow(row: ReadSummaryRow): string {
|
|
@@ -659,6 +789,18 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
659
789
|
this.addChild(component);
|
|
660
790
|
}
|
|
661
791
|
|
|
792
|
+
#addPreviewUsage(entry: ReadEntry): void {
|
|
793
|
+
const usageRow = this.#usageRows.get(entry.toolCallId);
|
|
794
|
+
if (!usageRow) return;
|
|
795
|
+
this.addChild(
|
|
796
|
+
new Text(
|
|
797
|
+
theme.fg("dim", formatUsageRow(usageRow.usage, usageRow.durationMs, usageRow.ttftMs, usageRow.timestamp)),
|
|
798
|
+
3,
|
|
799
|
+
0,
|
|
800
|
+
),
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
662
804
|
#shouldRenderPreview(entry: ReadEntry): boolean {
|
|
663
805
|
return this.#showContentPreview && entry.contentText !== undefined;
|
|
664
806
|
}
|