@pi-kaush/pi-tool-call-markers 0.2.7 → 0.2.8
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 +1 -1
- package/src/index.ts +97 -11
- package/src/thinking-block-merger.ts +223 -1
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -61,6 +61,7 @@ type PresentationPatchState = {
|
|
|
61
61
|
theme?: ThemeLike;
|
|
62
62
|
collapseParallel: boolean;
|
|
63
63
|
groupCache: WeakMap<ToolExecutionRow, GroupRenderCache>;
|
|
64
|
+
collapsedCache: WeakMap<ToolExecutionRow, CollapsedRenderCache>;
|
|
64
65
|
rowVersions: WeakMap<ToolExecutionRow, number>;
|
|
65
66
|
rowGroups: WeakMap<ToolExecutionRow, ToolExecutionRow[]>;
|
|
66
67
|
rowSignatures: WeakMap<ToolExecutionRow, string>;
|
|
@@ -85,6 +86,14 @@ type GroupRenderCache = {
|
|
|
85
86
|
width: number;
|
|
86
87
|
};
|
|
87
88
|
|
|
89
|
+
type CollapsedRenderCache = {
|
|
90
|
+
lines: string[];
|
|
91
|
+
signature: string;
|
|
92
|
+
version: number;
|
|
93
|
+
themeSample: string;
|
|
94
|
+
width: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
88
97
|
function envEnabled(name: string, defaultValue: boolean): boolean {
|
|
89
98
|
const value = process.env[name]?.trim().toLowerCase();
|
|
90
99
|
if (!value) return defaultValue;
|
|
@@ -116,6 +125,33 @@ function hasVisibleContent(line: string): boolean {
|
|
|
116
125
|
return stripAnsi(line).trim().length > 0;
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
// State-transition fingerprint used by both the updateDisplay version bump
|
|
129
|
+
// and the settled-row render cache. It deliberately covers only shape
|
|
130
|
+
// transitions (partial/expanded/result/error): while a row is partial its
|
|
131
|
+
// content streams and it is never cached, and once settled the rendered
|
|
132
|
+
// content is fully determined by this fingerprint + width + theme.
|
|
133
|
+
function rowSignatureOf(row: ToolExecutionRow): string {
|
|
134
|
+
return `${row.isPartial}|${row.expanded}|${row.result ? 1 : 0}|${row.result?.isError ? 1 : 0}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Theme samples are five theme.fg calls; computing them per row per frame
|
|
138
|
+
// would reintroduce the per-keystroke cost the render caches remove, so
|
|
139
|
+
// cache by theme object identity (a theme switch swaps the object).
|
|
140
|
+
const themeSamples = new WeakMap<ThemeLike, string>();
|
|
141
|
+
function themeSampleFor(theme: ThemeLike): string {
|
|
142
|
+
let sample = themeSamples.get(theme);
|
|
143
|
+
if (!sample) {
|
|
144
|
+
sample =
|
|
145
|
+
theme.fg("toolTitle", "x") +
|
|
146
|
+
theme.fg("muted", "x") +
|
|
147
|
+
theme.fg("dim", "x") +
|
|
148
|
+
theme.fg("warning", "x") +
|
|
149
|
+
theme.fg("error", "x");
|
|
150
|
+
themeSamples.set(theme, sample);
|
|
151
|
+
}
|
|
152
|
+
return sample;
|
|
153
|
+
}
|
|
154
|
+
|
|
119
155
|
type InsetLayout = {
|
|
120
156
|
contentWidth: number;
|
|
121
157
|
left: number;
|
|
@@ -1115,12 +1151,7 @@ function renderGroupedToolRows(
|
|
|
1115
1151
|
): string[] {
|
|
1116
1152
|
const theme = state.theme;
|
|
1117
1153
|
if (!theme) return state.originalRender.call(row, width);
|
|
1118
|
-
const themeSample =
|
|
1119
|
-
theme.fg("toolTitle", "x") +
|
|
1120
|
-
theme.fg("muted", "x") +
|
|
1121
|
-
theme.fg("dim", "x") +
|
|
1122
|
-
theme.fg("warning", "x") +
|
|
1123
|
-
theme.fg("error", "x");
|
|
1154
|
+
const themeSample = themeSampleFor(theme);
|
|
1124
1155
|
const cached = state.groupCache.get(row);
|
|
1125
1156
|
const hasLiveMembers = rows.some(isLiveRow);
|
|
1126
1157
|
if (
|
|
@@ -1330,6 +1361,7 @@ function installPresentationPatch(): PresentationPatchState | undefined {
|
|
|
1330
1361
|
const state: PresentationPatchState = {
|
|
1331
1362
|
collapseParallel: envEnabled(COLLAPSE_PARALLEL_ENV, true),
|
|
1332
1363
|
groupCache: new WeakMap(),
|
|
1364
|
+
collapsedCache: new WeakMap(),
|
|
1333
1365
|
rowVersions: new WeakMap(),
|
|
1334
1366
|
rowGroups: new WeakMap(),
|
|
1335
1367
|
rowSignatures: new WeakMap(),
|
|
@@ -1342,7 +1374,7 @@ function installPresentationPatch(): PresentationPatchState | undefined {
|
|
|
1342
1374
|
// Group members always bump so their leader's cache refreshes; other
|
|
1343
1375
|
// rows only bump on state transitions, so bash's per-second invalidate
|
|
1344
1376
|
// ticks and resize invalidations stop busting caches.
|
|
1345
|
-
const signature =
|
|
1377
|
+
const signature = rowSignatureOf(this);
|
|
1346
1378
|
if (
|
|
1347
1379
|
state.rowGroups.has(this) ||
|
|
1348
1380
|
state.rowSignatures.get(this) !== signature
|
|
@@ -1357,18 +1389,72 @@ function installPresentationPatch(): PresentationPatchState | undefined {
|
|
|
1357
1389
|
this: ToolExecutionRow,
|
|
1358
1390
|
width: number,
|
|
1359
1391
|
): string[] {
|
|
1360
|
-
const
|
|
1392
|
+
const theme = state.theme;
|
|
1361
1393
|
if (
|
|
1362
1394
|
typeof this.expanded !== "boolean" ||
|
|
1363
1395
|
typeof this.isPartial !== "boolean" ||
|
|
1364
1396
|
this.expanded ||
|
|
1365
|
-
!
|
|
1366
|
-
(lines.length === 0 && (this.imageComponents?.length ?? 0) === 0)
|
|
1397
|
+
!theme
|
|
1367
1398
|
) {
|
|
1399
|
+
return state.originalRender.call(this, width);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// ================================================================
|
|
1403
|
+
// Settled-row render cache — PLEASE DO NOT REMOVE THIS FAST PATH.
|
|
1404
|
+
// ================================================================
|
|
1405
|
+
// Pi's TUI re-renders the whole transcript on every keystroke, and
|
|
1406
|
+
// the collapsed-row decoration below rebuilds strings for every row
|
|
1407
|
+
// it touches. On a long session (~1,000 messages, hundreds of tool
|
|
1408
|
+
// rows) that measured at ~80 ms per keystroke — visible input lag.
|
|
1409
|
+
//
|
|
1410
|
+
// Settled rows (not partial, not expanded) have fully static render
|
|
1411
|
+
// output, so their collapsed lines are cached keyed by width +
|
|
1412
|
+
// rowSignatureOf + rowVersions + theme sample. updateDisplay bumps
|
|
1413
|
+
// rowVersions on every real state transition, and partial rows
|
|
1414
|
+
// bypass the cache entirely because their content streams. This
|
|
1415
|
+
// mirrors the existing groupCache discipline; keep them in sync.
|
|
1416
|
+
// ================================================================
|
|
1417
|
+
if (!this.isPartial) {
|
|
1418
|
+
const signature = rowSignatureOf(this);
|
|
1419
|
+
const version = state.rowVersions.get(this) ?? 0;
|
|
1420
|
+
const themeSample = themeSampleFor(theme);
|
|
1421
|
+
const cached = state.collapsedCache.get(this);
|
|
1422
|
+
if (
|
|
1423
|
+
cached &&
|
|
1424
|
+
cached.width === width &&
|
|
1425
|
+
cached.signature === signature &&
|
|
1426
|
+
cached.version === version &&
|
|
1427
|
+
cached.themeSample === themeSample
|
|
1428
|
+
) {
|
|
1429
|
+
return cached.lines;
|
|
1430
|
+
}
|
|
1431
|
+
const lines = state.originalRender.call(this, width);
|
|
1432
|
+
if (lines.length === 0 && (this.imageComponents?.length ?? 0) === 0) {
|
|
1433
|
+
return lines;
|
|
1434
|
+
}
|
|
1435
|
+
try {
|
|
1436
|
+
const collapsed = renderCollapsedToolRow(this, width, theme);
|
|
1437
|
+
state.collapsedCache.set(this, {
|
|
1438
|
+
lines: collapsed,
|
|
1439
|
+
signature,
|
|
1440
|
+
version,
|
|
1441
|
+
themeSample,
|
|
1442
|
+
width,
|
|
1443
|
+
});
|
|
1444
|
+
return collapsed;
|
|
1445
|
+
} catch {
|
|
1446
|
+
return lines;
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// Partial (streaming) rows keep the collapsed presentation but are
|
|
1451
|
+
// never cached: their content changes with every streamed chunk.
|
|
1452
|
+
const lines = state.originalRender.call(this, width);
|
|
1453
|
+
if (lines.length === 0 && (this.imageComponents?.length ?? 0) === 0) {
|
|
1368
1454
|
return lines;
|
|
1369
1455
|
}
|
|
1370
1456
|
try {
|
|
1371
|
-
return renderCollapsedToolRow(this, width,
|
|
1457
|
+
return renderCollapsedToolRow(this, width, theme);
|
|
1372
1458
|
} catch {
|
|
1373
1459
|
return lines;
|
|
1374
1460
|
}
|
|
@@ -5,6 +5,176 @@ const THINKING_GROUPING_PATCHED = Symbol.for("kg.pi.thinkingGrouping.v1");
|
|
|
5
5
|
const PI_SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
6
6
|
const SPINNER_INTERVAL_MS = 80;
|
|
7
7
|
|
|
8
|
+
// Visual experiment for the settled "+ Thought" label. "inherit" keeps Pi's
|
|
9
|
+
// native styling (italic + thinkingText); "gray" sits halfway between the
|
|
10
|
+
// muted and text theme colors. The env var overrides the default so the
|
|
11
|
+
// variants can be compared without editing code or republishing.
|
|
12
|
+
const THOUGHT_LABEL_COLOR_ENV = "PI_TOOL_CALL_MARKERS_THOUGHT_COLOR";
|
|
13
|
+
type ThoughtLabelColor = "inherit" | "orange" | "gray";
|
|
14
|
+
const DEFAULT_THOUGHT_LABEL_COLOR: ThoughtLabelColor = "orange";
|
|
15
|
+
// cobalt2's orange token (#ffb86c).
|
|
16
|
+
const THOUGHT_LABEL_ORANGE = { r: 255, g: 184, b: 108 };
|
|
17
|
+
|
|
18
|
+
function thoughtLabelColorChoice(): ThoughtLabelColor {
|
|
19
|
+
const raw = process.env[THOUGHT_LABEL_COLOR_ENV];
|
|
20
|
+
return raw === "inherit" || raw === "orange" || raw === "gray"
|
|
21
|
+
? raw
|
|
22
|
+
: DEFAULT_THOUGHT_LABEL_COLOR;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const CUBE_VALUES = [0, 95, 135, 175, 215, 255];
|
|
26
|
+
const BASIC_ANSI_RGB: Array<[number, string] | undefined> = [
|
|
27
|
+
[0, "#000000"],
|
|
28
|
+
[1, "#800000"],
|
|
29
|
+
[2, "#008000"],
|
|
30
|
+
[3, "#808000"],
|
|
31
|
+
[4, "#000080"],
|
|
32
|
+
[5, "#800080"],
|
|
33
|
+
[6, "#008080"],
|
|
34
|
+
[7, "#c0c0c0"],
|
|
35
|
+
[8, "#808080"],
|
|
36
|
+
[9, "#ff0000"],
|
|
37
|
+
[10, "#00ff00"],
|
|
38
|
+
[11, "#ffff00"],
|
|
39
|
+
[12, "#0000ff"],
|
|
40
|
+
[13, "#ff00ff"],
|
|
41
|
+
[14, "#00ffff"],
|
|
42
|
+
[15, "#ffffff"],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
type Rgb = { r: number; g: number; b: number };
|
|
46
|
+
|
|
47
|
+
type ThemeDetail = {
|
|
48
|
+
fg(color: string, text: string): string;
|
|
49
|
+
getFgAnsi?(color: string): string;
|
|
50
|
+
getColorMode?(): "truecolor" | "256color" | string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
let activeTheme: ThemeDetail | undefined;
|
|
54
|
+
|
|
55
|
+
function hexToRgb(hex: string): Rgb | undefined {
|
|
56
|
+
const match = /^#([0-9a-f]{6})$/i.exec(hex.trim());
|
|
57
|
+
if (!match) return undefined;
|
|
58
|
+
const value = parseInt(match[1]!, 16);
|
|
59
|
+
return { r: value >> 16, g: (value >> 8) & 255, b: value & 255 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Mirrors Pi's own mapping so a named color can round-trip through a
|
|
63
|
+
// 256-color terminal (e.g. when COLORFGBG forces 256color mode).
|
|
64
|
+
function ansi256ToRgb(index: number): Rgb | undefined {
|
|
65
|
+
if (index < 16) {
|
|
66
|
+
const hex = BASIC_ANSI_RGB[index]?.[1];
|
|
67
|
+
return hex ? hexToRgb(hex) : undefined;
|
|
68
|
+
}
|
|
69
|
+
if (index < 232) {
|
|
70
|
+
const cube = index - 16;
|
|
71
|
+
const channel = (n: number) => (n === 0 ? 0 : 55 + n * 40);
|
|
72
|
+
return {
|
|
73
|
+
r: channel(Math.floor(cube / 36)),
|
|
74
|
+
g: channel(Math.floor((cube % 36) / 6)),
|
|
75
|
+
b: channel(cube % 6),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const gray = 8 + (index - 232) * 10;
|
|
79
|
+
return { r: gray, g: gray, b: gray };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function rgbToAnsi256({ r, g, b }: Rgb): number {
|
|
83
|
+
const nearest = (value: number) =>
|
|
84
|
+
CUBE_VALUES.reduce(
|
|
85
|
+
(best, candidate) =>
|
|
86
|
+
Math.abs(candidate - value) < Math.abs(best - value) ? candidate : best,
|
|
87
|
+
0,
|
|
88
|
+
);
|
|
89
|
+
const cubeR = nearest(r);
|
|
90
|
+
const cubeG = nearest(g);
|
|
91
|
+
const cubeB = nearest(b);
|
|
92
|
+
const cubeDistance =
|
|
93
|
+
(r - cubeR) ** 2 * 0.299 +
|
|
94
|
+
(g - cubeG) ** 2 * 0.587 +
|
|
95
|
+
(b - cubeB) ** 2 * 0.114;
|
|
96
|
+
const luminance = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
|
|
97
|
+
const grayStep = Math.max(0, Math.min(23, Math.round((luminance - 8) / 10)));
|
|
98
|
+
const grayValue = 8 + grayStep * 10;
|
|
99
|
+
const grayDistance = (luminance - grayValue) ** 2;
|
|
100
|
+
const spread = Math.max(r, g, b) - Math.min(r, g, b);
|
|
101
|
+
if (spread < 10 && grayDistance < cubeDistance) return 232 + grayStep;
|
|
102
|
+
return (
|
|
103
|
+
16 +
|
|
104
|
+
36 * CUBE_VALUES.indexOf(cubeR) +
|
|
105
|
+
6 * CUBE_VALUES.indexOf(cubeG) +
|
|
106
|
+
CUBE_VALUES.indexOf(cubeB)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseAnsiFgRgb(ansi: string): Rgb | undefined {
|
|
111
|
+
if (/^\x1b\[39m$/.test(ansi)) return undefined;
|
|
112
|
+
const truecolor = /^\x1b\[38;2;(\d+);(\d+);(\d+)m$/.exec(ansi);
|
|
113
|
+
if (truecolor) {
|
|
114
|
+
return {
|
|
115
|
+
r: Number(truecolor[1]),
|
|
116
|
+
g: Number(truecolor[2]),
|
|
117
|
+
b: Number(truecolor[3]),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const indexed = /^\x1b\[38;5;(\d+)m$/.exec(ansi);
|
|
121
|
+
if (indexed) return ansi256ToRgb(Number(indexed[1]));
|
|
122
|
+
const basic = /^\x1b\[(9[0-7]|3[0-7])m$/.exec(ansi);
|
|
123
|
+
if (basic) {
|
|
124
|
+
const code = Number(basic[1]);
|
|
125
|
+
return ansi256ToRgb(code >= 90 ? code - 90 + 8 : code - 30);
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function fgRgbAnsi({ r, g, b }: Rgb): string {
|
|
131
|
+
if (activeTheme?.getColorMode?.() === "256color") {
|
|
132
|
+
return `\x1b[38;5;${rgbToAnsi256({ r, g, b })}m`;
|
|
133
|
+
}
|
|
134
|
+
return `\x1b[38;2;${r};${g};${b}m`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function midpointRgb(a: Rgb, b: Rgb): Rgb {
|
|
138
|
+
return {
|
|
139
|
+
r: Math.round((a.r + b.r) / 2),
|
|
140
|
+
g: Math.round((a.g + b.g) / 2),
|
|
141
|
+
b: Math.round((a.b + b.b) / 2),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let thoughtLabelPrefix = "";
|
|
146
|
+
let thoughtLabelSuffix = "";
|
|
147
|
+
|
|
148
|
+
function updateThoughtLabelStyle(): void {
|
|
149
|
+
thoughtLabelPrefix = "";
|
|
150
|
+
thoughtLabelSuffix = "";
|
|
151
|
+
const choice = thoughtLabelColorChoice();
|
|
152
|
+
if (choice === "inherit" || !activeTheme) return;
|
|
153
|
+
|
|
154
|
+
let rgb: Rgb | undefined;
|
|
155
|
+
if (choice === "orange") {
|
|
156
|
+
rgb = THOUGHT_LABEL_ORANGE;
|
|
157
|
+
} else if (activeTheme.getFgAnsi) {
|
|
158
|
+
const muted = parseAnsiFgRgb(activeTheme.getFgAnsi("muted"));
|
|
159
|
+
const text = parseAnsiFgRgb(activeTheme.getFgAnsi("text"));
|
|
160
|
+
if (muted && text) rgb = midpointRgb(muted, text);
|
|
161
|
+
}
|
|
162
|
+
if (!rgb) return;
|
|
163
|
+
|
|
164
|
+
// The styled label replaces Pi's italicized Text node wholesale (see
|
|
165
|
+
// restyleHiddenThinkingLabel). The leading italic-off still matters: the
|
|
166
|
+
// TUI's diff renderer can skip bytes shared with the previously drawn
|
|
167
|
+
// italic line, leaving the terminal in italic state otherwise.
|
|
168
|
+
thoughtLabelPrefix = `\x1b[23m${fgRgbAnsi(rgb)}`;
|
|
169
|
+
thoughtLabelSuffix = "\x1b[39m";
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function visibleThoughtLabel(label: string): string {
|
|
173
|
+
if (!thoughtLabelPrefix) return label;
|
|
174
|
+
const raw = label.replace(/\x1b\[[0-9;]*m/g, "");
|
|
175
|
+
return `${thoughtLabelPrefix}${raw}${thoughtLabelSuffix}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
8
178
|
type AssistantMessageLike = {
|
|
9
179
|
content?: unknown[];
|
|
10
180
|
};
|
|
@@ -12,9 +182,46 @@ type AssistantMessageLike = {
|
|
|
12
182
|
type AssistantMessageRow = {
|
|
13
183
|
hiddenThinkingLabel?: unknown;
|
|
14
184
|
hideThinkingBlock?: unknown;
|
|
185
|
+
contentContainer?: { children?: unknown[] };
|
|
15
186
|
updateContent(message: AssistantMessageLike, ...args: unknown[]): void;
|
|
16
187
|
};
|
|
17
188
|
|
|
189
|
+
type TextLikeChild = {
|
|
190
|
+
text?: unknown;
|
|
191
|
+
setText?(text: string): void;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Pi renders the hidden-thinking label as an italic Text node built from the
|
|
195
|
+
// plain label field. Embedding style codes in the field itself is not
|
|
196
|
+
// enough: the TUI diff renderer reuses the byte prefix shared with the
|
|
197
|
+
// previous (italic) frame, so an in-line italic reset may never reach the
|
|
198
|
+
// terminal. Swap the node's text for a self-contained styled version after
|
|
199
|
+
// each native render pass instead.
|
|
200
|
+
function restyleHiddenThinkingLabel(row: AssistantMessageRow): void {
|
|
201
|
+
if (!thoughtLabelPrefix) return;
|
|
202
|
+
if (
|
|
203
|
+
row.hideThinkingBlock !== true ||
|
|
204
|
+
typeof row.hiddenThinkingLabel !== "string"
|
|
205
|
+
) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
const label = row.hiddenThinkingLabel;
|
|
209
|
+
const children = row.contentContainer?.children;
|
|
210
|
+
if (!Array.isArray(children)) return;
|
|
211
|
+
const styled = visibleThoughtLabel(label);
|
|
212
|
+
for (const child of children) {
|
|
213
|
+
const textChild = child as TextLikeChild | undefined;
|
|
214
|
+
if (
|
|
215
|
+
typeof textChild?.text !== "string" ||
|
|
216
|
+
typeof textChild.setText !== "function" ||
|
|
217
|
+
!textChild.text.includes(label)
|
|
218
|
+
) {
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
textChild.setText(styled);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
18
225
|
type ThinkingTiming = {
|
|
19
226
|
finishedAt?: number;
|
|
20
227
|
startedAt: number;
|
|
@@ -186,6 +393,11 @@ function installThinkingGroupingPatch():
|
|
|
186
393
|
// Preserve Pi's native label if its private row shape changes.
|
|
187
394
|
}
|
|
188
395
|
Reflect.apply(state.originalUpdateContent, this, [combined, ...args]);
|
|
396
|
+
try {
|
|
397
|
+
restyleHiddenThinkingLabel(this);
|
|
398
|
+
} catch {
|
|
399
|
+
// Keep Pi's native label styling if the row shape changes.
|
|
400
|
+
}
|
|
189
401
|
};
|
|
190
402
|
|
|
191
403
|
state.patchedUpdateContent = patchedUpdateContent;
|
|
@@ -224,5 +436,15 @@ function uninstallThinkingGroupingPatch(
|
|
|
224
436
|
|
|
225
437
|
export default function (pi: ExtensionAPI) {
|
|
226
438
|
const patch = installThinkingGroupingPatch();
|
|
227
|
-
pi.on("
|
|
439
|
+
pi.on("session_start", (_event, ctx) => {
|
|
440
|
+
if (ctx.mode !== "tui") return;
|
|
441
|
+
activeTheme = ctx.ui.theme as unknown as ThemeDetail;
|
|
442
|
+
updateThoughtLabelStyle();
|
|
443
|
+
});
|
|
444
|
+
pi.on("session_shutdown", () => {
|
|
445
|
+
activeTheme = undefined;
|
|
446
|
+
thoughtLabelPrefix = "";
|
|
447
|
+
thoughtLabelSuffix = "";
|
|
448
|
+
uninstallThinkingGroupingPatch(patch);
|
|
449
|
+
});
|
|
228
450
|
}
|