@heyhuynhgiabuu/pi-pretty 0.6.25 → 0.6.26
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 +16 -0
- package/README.md +19 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +137 -34
- package/dist/index.js.map +1 -1
- package/dist/tools/bash.js +1 -1
- package/dist/tools/bash.js.map +1 -1
- package/dist/tools/find.js +4 -4
- package/dist/tools/find.js.map +1 -1
- package/dist/tools/grep.js +1 -1
- package/dist/tools/grep.js.map +1 -1
- package/dist/tools/ls.js +2 -2
- package/dist/tools/ls.js.map +1 -1
- package/dist/tools/read.js +2 -2
- package/dist/tools/read.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/working-indicator.d.ts +63 -6
- package/dist/working-indicator.d.ts.map +1 -1
- package/dist/working-indicator.js +169 -14
- package/dist/working-indicator.js.map +1 -1
- package/package.json +7 -6
- package/src/index.ts +145 -38
- package/src/tools/bash.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/grep.ts +1 -1
- package/src/tools/ls.ts +2 -2
- package/src/tools/read.ts +2 -2
- package/src/types.ts +4 -0
- package/src/working-indicator.ts +214 -21
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type {
|
|
|
15
15
|
ExtensionAPI,
|
|
16
16
|
ExtensionCommandContext,
|
|
17
17
|
ExtensionContext,
|
|
18
|
+
MessageEndEvent,
|
|
18
19
|
MessageUpdateEvent,
|
|
19
20
|
SessionInfoChangedEvent,
|
|
20
21
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -30,13 +31,17 @@ import { registerReadTool } from "./tools/read.js";
|
|
|
30
31
|
import type { PiPrettyDeps, SdkTools } from "./types.js";
|
|
31
32
|
import {
|
|
32
33
|
createThinkingLabelAnimator,
|
|
34
|
+
createThinkingTimer,
|
|
35
|
+
installPerRowThinkingLabels,
|
|
33
36
|
installWorkingIndicator,
|
|
37
|
+
type PerRowThinkingLabels,
|
|
34
38
|
resolveThinkingIndicatorSettings,
|
|
35
39
|
resolveWorkingIndicatorSettings,
|
|
36
|
-
type
|
|
40
|
+
type ThinkingTimer,
|
|
37
41
|
thinkingBlockActive,
|
|
38
42
|
WORKING_INTERVAL_MS,
|
|
39
43
|
type WorkingIndicatorController,
|
|
44
|
+
workingTokens,
|
|
40
45
|
} from "./working-indicator.js";
|
|
41
46
|
|
|
42
47
|
// ---------------------------------------------------------------------------
|
|
@@ -160,13 +165,24 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
160
165
|
const workingSettings = resolveWorkingIndicatorSettings(config.workingIndicator);
|
|
161
166
|
const thinkingSettings = resolveThinkingIndicatorSettings(config.thinkingIndicator);
|
|
162
167
|
let workingController: WorkingIndicatorController | undefined;
|
|
163
|
-
let
|
|
168
|
+
let thinkingTimer: ThinkingTimer | undefined;
|
|
164
169
|
let thinkingInterval: ReturnType<typeof setInterval> | undefined;
|
|
165
170
|
let thinkingLastMessage: unknown;
|
|
166
|
-
let thinkingHiddenCache = true;
|
|
167
171
|
let thinkingHiddenCheckedAt = 0;
|
|
168
172
|
let workingSessionName: string | undefined;
|
|
169
173
|
let workingStreaming = false;
|
|
174
|
+
let workingStatsUpdatedAt = 0;
|
|
175
|
+
let perRowLabels: PerRowThinkingLabels | undefined;
|
|
176
|
+
/** Accumulated thinking time per message timestamp — later runs in the same
|
|
177
|
+
* message resume from this total instead of rewinding to zero. */
|
|
178
|
+
const thinkingElapsedMs = new Map<number, number>();
|
|
179
|
+
const TOKEN_COUNT_FORMAT = new Intl.NumberFormat("en-US");
|
|
180
|
+
|
|
181
|
+
/** Message timestamps are the per-row identity the label patch keys on. */
|
|
182
|
+
const messageTimestamp = (message: unknown): number | undefined => {
|
|
183
|
+
const ts = (message as { timestamp?: unknown } | undefined)?.timestamp;
|
|
184
|
+
return typeof ts === "number" ? ts : undefined;
|
|
185
|
+
};
|
|
170
186
|
|
|
171
187
|
/** pi persists the thinking-visibility toggle as a top-level settings key. */
|
|
172
188
|
const thinkingHidden = (): boolean => {
|
|
@@ -182,6 +198,17 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
182
198
|
}
|
|
183
199
|
};
|
|
184
200
|
|
|
201
|
+
/**
|
|
202
|
+
* True when the 500ms-cached hidden check is stale and settings now show
|
|
203
|
+
* thinking revealed — the single re-check shared by the ticker and the
|
|
204
|
+
* completed-phase message_update path (timestamp guard runs before the read).
|
|
205
|
+
*/
|
|
206
|
+
const thinkingRevealed = (): boolean => {
|
|
207
|
+
if (Date.now() - thinkingHiddenCheckedAt <= 500) return false;
|
|
208
|
+
thinkingHiddenCheckedAt = Date.now();
|
|
209
|
+
return !thinkingHidden();
|
|
210
|
+
};
|
|
211
|
+
|
|
185
212
|
const safeSessionName = (ctx: ExtensionContext): string | undefined => {
|
|
186
213
|
try {
|
|
187
214
|
return ctx.sessionManager?.getSessionName?.() ?? undefined;
|
|
@@ -218,6 +245,15 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
218
245
|
pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => {
|
|
219
246
|
if (ctx.mode === "tui") {
|
|
220
247
|
ctx.ui.setToolsExpanded(false);
|
|
248
|
+
// Per-row hidden-thinking labels: intercept the host's label fan-out so
|
|
249
|
+
// each row keeps its own duration. Falls back to the global-label
|
|
250
|
+
// behavior whenever the host class is missing or reshaped.
|
|
251
|
+
perRowLabels = thinkingSettings.enabled
|
|
252
|
+
? installPerRowThinkingLabels(
|
|
253
|
+
deps?.assistantMessageComponent ??
|
|
254
|
+
(hostSdk as { AssistantMessageComponent?: unknown }).AssistantMessageComponent,
|
|
255
|
+
)
|
|
256
|
+
: undefined;
|
|
221
257
|
workingSessionName = safeSessionName(ctx);
|
|
222
258
|
await installIndicator(ctx);
|
|
223
259
|
}
|
|
@@ -262,6 +298,8 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
262
298
|
pi.on("agent_start", async (_event: unknown, ctx: ExtensionContext) => {
|
|
263
299
|
if (ctx.mode !== "tui") return;
|
|
264
300
|
workingStreaming = true;
|
|
301
|
+
workingStatsUpdatedAt = 0;
|
|
302
|
+
workingController?.setStats(undefined);
|
|
265
303
|
workingController?.start();
|
|
266
304
|
});
|
|
267
305
|
const stopStreaming = async (_event: unknown, ctx: ExtensionContext): Promise<void> => {
|
|
@@ -273,69 +311,135 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
273
311
|
pi.on("agent_end", stopStreaming);
|
|
274
312
|
pi.on("agent_settled", stopStreaming);
|
|
275
313
|
|
|
314
|
+
const clearThinkingInterval = (): void => {
|
|
315
|
+
if (!thinkingInterval) return;
|
|
316
|
+
clearInterval(thinkingInterval);
|
|
317
|
+
thinkingInterval = undefined;
|
|
318
|
+
};
|
|
319
|
+
|
|
276
320
|
const stopThinkingShimmer = (): void => {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
321
|
+
clearThinkingInterval();
|
|
322
|
+
perRowLabels?.clearActive();
|
|
323
|
+
try {
|
|
324
|
+
thinkingTimer?.restore();
|
|
325
|
+
} catch {
|
|
326
|
+
// Teardown must never throw — a host that explodes on restore is left
|
|
327
|
+
// with whatever label it has; the interval is already gone.
|
|
280
328
|
}
|
|
281
|
-
|
|
282
|
-
thinkingAnimator = undefined;
|
|
329
|
+
thinkingTimer = undefined;
|
|
283
330
|
thinkingLastMessage = undefined;
|
|
284
331
|
};
|
|
285
332
|
|
|
333
|
+
const completeThinkingShimmer = (): void => {
|
|
334
|
+
clearThinkingInterval();
|
|
335
|
+
const ts = messageTimestamp(thinkingLastMessage);
|
|
336
|
+
const frozenMs = thinkingTimer?.complete();
|
|
337
|
+
if (ts !== undefined && frozenMs !== undefined) {
|
|
338
|
+
thinkingElapsedMs.set(ts, frozenMs);
|
|
339
|
+
perRowLabels?.complete(ts, frozenMs);
|
|
340
|
+
}
|
|
341
|
+
// The completed row is no longer the animating one — drop the active mark
|
|
342
|
+
// so subsequent global writes freeze it at its own duration.
|
|
343
|
+
perRowLabels?.clearActive();
|
|
344
|
+
thinkingLastMessage = undefined;
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
/** Surface one warning without letting a throwing host escape the crash guard. */
|
|
348
|
+
const notifyThinkingFailure = (ctx: ExtensionContext, error: unknown): void => {
|
|
349
|
+
try {
|
|
350
|
+
ctx.ui?.notify?.(
|
|
351
|
+
`pi-pretty thinking indicator failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
352
|
+
"warning",
|
|
353
|
+
);
|
|
354
|
+
} catch {
|
|
355
|
+
// A host that throws on notify too must not crash the timer path.
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
286
359
|
// Same 30fps cadence as the working row — but the interval lives ONLY while
|
|
287
360
|
// the streaming message is actively emitting a thinking block (each label
|
|
288
361
|
// change rebuilds chat children, so the moment text or tool calls stream the
|
|
289
|
-
//
|
|
362
|
+
// label freezes as `Thought for {duration}` until the run ends).
|
|
290
363
|
const startThinkingShimmer = (ctx: ExtensionContext): void => {
|
|
291
364
|
if (thinkingInterval || !thinkingSettings.enabled) return;
|
|
292
365
|
try {
|
|
293
|
-
thinkingAnimator = createThinkingLabelAnimator(ctx.ui, workingSettings, workingSessionName, thinkingSettings);
|
|
294
|
-
thinkingHiddenCache = thinkingHidden();
|
|
295
366
|
thinkingHiddenCheckedAt = Date.now();
|
|
296
|
-
if (!
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
367
|
+
if (!thinkingHidden()) return; // thinking visible: no label writes at all
|
|
368
|
+
const animator = createThinkingLabelAnimator(ctx.ui, workingSettings, workingSessionName, thinkingSettings);
|
|
369
|
+
const activeTs = messageTimestamp(thinkingLastMessage);
|
|
370
|
+
// Same message thinking again (interleaved runs): resume its total.
|
|
371
|
+
const resumeMs = activeTs !== undefined ? (thinkingElapsedMs.get(activeTs) ?? 0) : 0;
|
|
372
|
+
thinkingTimer = createThinkingTimer(animator, Date.now, resumeMs);
|
|
373
|
+
if (activeTs !== undefined) perRowLabels?.setActive(activeTs);
|
|
374
|
+
thinkingTimer.tick();
|
|
301
375
|
thinkingInterval = setInterval(() => {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
const now = Date.now();
|
|
307
|
-
if (now - thinkingHiddenCheckedAt > 500) {
|
|
308
|
-
thinkingHiddenCache = thinkingHidden();
|
|
309
|
-
thinkingHiddenCheckedAt = now;
|
|
310
|
-
if (!thinkingHiddenCache) {
|
|
376
|
+
try {
|
|
377
|
+
if (!workingStreaming) {
|
|
311
378
|
stopThinkingShimmer();
|
|
312
379
|
return;
|
|
313
380
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
381
|
+
if (thinkingRevealed()) {
|
|
382
|
+
stopThinkingShimmer();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
if (!thinkingBlockActive(thinkingLastMessage)) {
|
|
386
|
+
// Thinking phase over: freeze its duration until the run ends.
|
|
387
|
+
completeThinkingShimmer();
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
thinkingTimer?.tick();
|
|
391
|
+
// The label has no widget of its own — reuse the working widget's
|
|
392
|
+
// TUI handle so the frame lands even when deltas pause mid-thinking.
|
|
393
|
+
workingController?.requestRender();
|
|
394
|
+
} catch (error: unknown) {
|
|
395
|
+
// A throwing host call inside our timer would crash pi (uncaught in
|
|
396
|
+
// setInterval) — tear the label down and surface one warning instead.
|
|
317
397
|
stopThinkingShimmer();
|
|
318
|
-
|
|
398
|
+
notifyThinkingFailure(ctx, error);
|
|
319
399
|
}
|
|
320
|
-
thinkingAnimator?.tick();
|
|
321
400
|
}, WORKING_INTERVAL_MS);
|
|
322
401
|
} catch (error: unknown) {
|
|
323
402
|
stopThinkingShimmer();
|
|
324
|
-
ctx
|
|
325
|
-
`pi-pretty thinking indicator failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
326
|
-
"warning",
|
|
327
|
-
);
|
|
403
|
+
notifyThinkingFailure(ctx, error);
|
|
328
404
|
}
|
|
329
405
|
};
|
|
330
406
|
|
|
331
407
|
// Remember the streaming message; lazily start the shimmer on the first
|
|
332
|
-
// thinking delta (thinkingHidden() is re-checked at most every 500ms).
|
|
408
|
+
// thinking delta (thinkingHidden() is re-checked at most every 500ms). After
|
|
409
|
+
// the phase completes, deltas keep refreshing the visibility cache so a
|
|
410
|
+
// mid-run `hideThinkingBlock` toggle-off restores the default label.
|
|
333
411
|
pi.on("message_update", async (event: MessageUpdateEvent, ctx: ExtensionContext) => {
|
|
334
412
|
if (ctx.mode !== "tui" || !workingStreaming) return;
|
|
335
413
|
thinkingLastMessage = event.message;
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
414
|
+
// Live token suffix on the working row (throttled to one update/second).
|
|
415
|
+
const now = Date.now();
|
|
416
|
+
if (now - workingStatsUpdatedAt >= 1000) {
|
|
417
|
+
workingStatsUpdatedAt = now;
|
|
418
|
+
const tokens = workingTokens(event.message);
|
|
419
|
+
workingController?.setStats(tokens > 0 ? ` (↓ ${TOKEN_COUNT_FORMAT.format(tokens)} tokens)` : undefined);
|
|
420
|
+
}
|
|
421
|
+
if (thinkingInterval) {
|
|
422
|
+
// Complete immediately on the first text/tool delta instead of waiting
|
|
423
|
+
// for the next animation frame.
|
|
424
|
+
if (!thinkingBlockActive(event.message)) completeThinkingShimmer();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (thinkingBlockActive(event.message)) {
|
|
428
|
+
// First thinking delta — or a later phase after tool calls — starts a
|
|
429
|
+
// fresh timer (the completed one is replaced wholesale).
|
|
430
|
+
startThinkingShimmer(ctx);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (thinkingTimer && thinkingRevealed()) stopThinkingShimmer();
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// The completed wording belongs to the message that produced it. The label
|
|
437
|
+
// is global, so once that assistant message ends, restore pi's default —
|
|
438
|
+
// otherwise every later phase stamps its frozen duration onto every hidden
|
|
439
|
+
// thinking row in the transcript (the image-read "Thought for 5s" stack).
|
|
440
|
+
pi.on("message_end", async (event: MessageEndEvent, ctx: ExtensionContext) => {
|
|
441
|
+
if (ctx.mode !== "tui" || event.message.role !== "assistant") return;
|
|
442
|
+
if (thinkingInterval || thinkingTimer) stopThinkingShimmer();
|
|
339
443
|
});
|
|
340
444
|
|
|
341
445
|
pi.on("session_shutdown", async () => {
|
|
@@ -344,6 +448,9 @@ export default async function piPrettyExtension(pi: ExtensionAPI, deps?: PiPrett
|
|
|
344
448
|
workingController?.dispose();
|
|
345
449
|
workingController = undefined;
|
|
346
450
|
stopThinkingShimmer();
|
|
451
|
+
perRowLabels?.uninstall();
|
|
452
|
+
perRowLabels = undefined;
|
|
453
|
+
thinkingElapsedMs.clear();
|
|
347
454
|
workingStreaming = false;
|
|
348
455
|
// Intentionally keep the native FFF finder on session shutdown.
|
|
349
456
|
// Pi can emit shutdown/start during resume or session switching while the
|
package/src/tools/bash.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function registerBashTool(
|
|
|
66
66
|
: rawCmd;
|
|
67
67
|
const commandLabel = theme.fg(ctx.isError ? "error" : "toolTitle", theme.bold(`$ ${cmd}`));
|
|
68
68
|
text.setText(
|
|
69
|
-
fillToolBackground(`\n${TOOL_RESULT_INDENT}${commandLabel}${t}
|
|
69
|
+
fillToolBackground(`\n${TOOL_RESULT_INDENT}${commandLabel}${t}`, undefined, ctx.expanded ? undefined : tw),
|
|
70
70
|
);
|
|
71
71
|
return text;
|
|
72
72
|
},
|
package/src/tools/find.ts
CHANGED
|
@@ -150,7 +150,7 @@ export function registerFindTool(
|
|
|
150
150
|
const pathPart = theme.fg("toolOutput", pathArg);
|
|
151
151
|
const limitPart = limit !== undefined && limit !== null ? theme.fg("dim", ` limit ${limit}`) : "";
|
|
152
152
|
const out = `${findLabel} ${patternPart}${inPart}${pathPart}${limitPart}`;
|
|
153
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}
|
|
153
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
154
154
|
return text;
|
|
155
155
|
},
|
|
156
156
|
|
|
@@ -168,7 +168,7 @@ export function registerFindTool(
|
|
|
168
168
|
const noticeStr = d.notices?.length
|
|
169
169
|
? `\n${TOOL_RESULT_INDENT}${theme.fg("warning", `[${d.notices.join(". ")}]`)}`
|
|
170
170
|
: "";
|
|
171
|
-
text.setText(fillToolBackground(
|
|
171
|
+
text.setText(fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", "0 files")}${noticeStr}\n`));
|
|
172
172
|
return text;
|
|
173
173
|
}
|
|
174
174
|
if (!ctx.expanded) {
|
|
@@ -190,14 +190,14 @@ export function registerFindTool(
|
|
|
190
190
|
const duration = renderToolDuration(r);
|
|
191
191
|
text.setText(
|
|
192
192
|
fillToolBackground(
|
|
193
|
-
|
|
193
|
+
`${TOOL_RESULT_INDENT}${theme.fg("dim", `${d.matchCount} files`)}${duration ? `${FG_DIM}· ${duration}${RST}` : ""}\n${rendered}${noticeStr}\n`,
|
|
194
194
|
),
|
|
195
195
|
);
|
|
196
196
|
return text;
|
|
197
197
|
}
|
|
198
198
|
const fc = r.content?.[0] as TextContent | undefined;
|
|
199
199
|
text.setText(
|
|
200
|
-
fillToolBackground(
|
|
200
|
+
fillToolBackground(`${TOOL_RESULT_INDENT}${theme.fg("dim", fc?.text?.slice(0, 120) ?? "0 files")}\n`),
|
|
201
201
|
);
|
|
202
202
|
return text;
|
|
203
203
|
},
|
package/src/tools/grep.ts
CHANGED
|
@@ -111,7 +111,7 @@ export function registerGrepTool(
|
|
|
111
111
|
if (limit !== undefined && limit !== null) out += theme.fg("dim", ` limit ${limit}`);
|
|
112
112
|
if (literal) out += theme.fg("dim", ` (literal)`);
|
|
113
113
|
if (caseInsensitive) out += theme.fg("dim", ` (case-insensitive)`);
|
|
114
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}
|
|
114
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
115
115
|
return text;
|
|
116
116
|
},
|
|
117
117
|
|
package/src/tools/ls.ts
CHANGED
|
@@ -51,7 +51,7 @@ export function registerLsTool(
|
|
|
51
51
|
let out = theme.fg("toolTitle", theme.bold("ls"));
|
|
52
52
|
if (path) out += ` ${theme.fg("accent", path)}`;
|
|
53
53
|
if (limit !== undefined && limit !== null) out += theme.fg("toolOutput", ` (limit ${limit})`);
|
|
54
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}
|
|
54
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${out}`, ctx.isError ? BG_ERROR : undefined));
|
|
55
55
|
return text;
|
|
56
56
|
},
|
|
57
57
|
|
|
@@ -79,7 +79,7 @@ export function registerLsTool(
|
|
|
79
79
|
.join("\n");
|
|
80
80
|
text.setText(
|
|
81
81
|
fillToolBackground(
|
|
82
|
-
|
|
82
|
+
`${TOOL_RESULT_INDENT}${FG_DIM}${d.entryCount} entries${RST}${renderToolMetrics(result)}\n${rendered}\n`,
|
|
83
83
|
),
|
|
84
84
|
);
|
|
85
85
|
return text;
|
package/src/tools/read.ts
CHANGED
|
@@ -100,7 +100,7 @@ export function registerReadTool(
|
|
|
100
100
|
|
|
101
101
|
const path = String(args.path ?? "");
|
|
102
102
|
const label = theme.fg("error", theme.bold("→ read"));
|
|
103
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${label} ${theme.fg("toolTitle", path)}
|
|
103
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${label} ${theme.fg("toolTitle", path)}`, BG_ERROR));
|
|
104
104
|
return text;
|
|
105
105
|
},
|
|
106
106
|
|
|
@@ -137,7 +137,7 @@ export function registerReadTool(
|
|
|
137
137
|
if (!ctx.expanded) {
|
|
138
138
|
if (skillName) {
|
|
139
139
|
const header = renderSkillHeader(skillName, false, theme);
|
|
140
|
-
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${header}
|
|
140
|
+
text.setText(fillToolBackground(`\n${TOOL_RESULT_INDENT}${header}`, BG_BASE));
|
|
141
141
|
return text;
|
|
142
142
|
}
|
|
143
143
|
text.setText(
|
package/src/types.ts
CHANGED
|
@@ -187,4 +187,8 @@ export interface PiPrettyDeps {
|
|
|
187
187
|
sdk?: SdkTools;
|
|
188
188
|
TextComponent?: new (text?: string, x?: number, y?: number) => ComponentLike;
|
|
189
189
|
fffModule?: typeof import("@ff-labs/fff-node");
|
|
190
|
+
/** Host AssistantMessageComponent class for the per-row label patch; defaults
|
|
191
|
+
* to the package export. Injectable so tests can verify against the exact
|
|
192
|
+
* class instance they construct rows from. */
|
|
193
|
+
assistantMessageComponent?: unknown;
|
|
190
194
|
}
|