@quandev104/pi-style 0.2.7 → 0.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 +23 -0
- package/README.md +2 -2
- package/dist/extensions/pi-style.js +8900 -8464
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +2 -0
- package/extension-src/pi-style/domain/config-normalization.ts +3 -0
- package/extension-src/pi-style/domain/config-types.ts +8 -0
- package/extension-src/pi-style/features/messages/index.ts +451 -37
- package/extension-src/pi-style/features/messages/special-blocks.ts +2 -22
- package/extension-src/pi-style/features/startup/index.ts +24 -4
- package/extension-src/pi-style/features/startup/logo.ts +22 -18
- package/extension-src/pi-style/features/tools/bash-execution.ts +24 -8
- package/extension-src/pi-style/features/tools/boxed/edit.ts +25 -30
- package/extension-src/pi-style/features/tools/boxed/git.ts +21 -13
- package/extension-src/pi-style/features/tools/boxed/quick-edit.ts +35 -38
- package/extension-src/pi-style/features/tools/boxed/shared.ts +34 -0
- package/extension-src/pi-style/features/tools/boxed/turn-summary.ts +114 -38
- package/extension-src/pi-style/features/tools/boxed/write.ts +1 -0
- package/extension-src/pi-style/features/tools/index.ts +10 -6
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +2 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +88 -19
- package/extension-src/pi-style/pi/session-coordinator.ts +20 -1
- package/extension-src/pi-style/shared/ansi.ts +45 -0
- package/extension-src/pi-style/shared/box.ts +126 -47
- package/package.json +10 -9
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
// global tool-output toggle (Ctrl+O) expands everything again
|
|
9
9
|
// (`options.expanded` is read, never written).
|
|
10
10
|
//
|
|
11
|
+
// The summary also reports the turn's aggregate diff stats (`· Edit +6 -2`,
|
|
12
|
+
// diff colors) computed purely from tool-result data — `details.diff` for
|
|
13
|
+
// edit, the parsed `── diff ──` output section for the quick-edit family
|
|
14
|
+
// (the same sources the box renderers read) — so live, scroll-back, and
|
|
15
|
+
// resume render identically. `write` carries no diff and is skipped; error
|
|
16
|
+
// members keep their visible blocks and never contribute stats.
|
|
17
|
+
//
|
|
11
18
|
// Mutating tools (edit/write/quick_edit/substitute_edit/target_edit) are
|
|
12
19
|
// exempt from the summary by default (`tools.collapseMutatingTools: off`):
|
|
13
20
|
// their blocks are the record of what was done to the user's files, so they
|
|
@@ -34,8 +41,11 @@
|
|
|
34
41
|
import type { Component } from "@earendil-works/pi-tui";
|
|
35
42
|
import type { BoxTheme } from "../../../shared/box.js";
|
|
36
43
|
import { safeTruncateToWidth } from "../../../shared/render-budget.js";
|
|
44
|
+
import { countDiffStats, firstText } from "../../../shared/split-diff.js";
|
|
37
45
|
import { pluralForm } from "./output-tree.js";
|
|
46
|
+
import { extractQuickEditDiff, getQuickEditToolConfig } from "./quick-edit.js";
|
|
38
47
|
import { getToolsRenderConfig } from "./session-config.js";
|
|
48
|
+
import { formatDiffStatsPair } from "./shared.js";
|
|
39
49
|
|
|
40
50
|
export interface TurnMemberInfo {
|
|
41
51
|
readonly toolCallId: string;
|
|
@@ -45,6 +55,8 @@ export interface TurnMemberInfo {
|
|
|
45
55
|
isError: boolean;
|
|
46
56
|
/** Frozen wall-clock elapsed (ms), recorded from the renderer context state. */
|
|
47
57
|
elapsedMs?: number;
|
|
58
|
+
/** Frozen diff line stats recorded from the tool result (edit family). */
|
|
59
|
+
diffStats?: { additions: number; removals: number } | undefined;
|
|
48
60
|
}
|
|
49
61
|
|
|
50
62
|
export interface TurnState {
|
|
@@ -114,22 +126,72 @@ function toolCallsOf(message: unknown): ToolCallLike[] {
|
|
|
114
126
|
return calls;
|
|
115
127
|
}
|
|
116
128
|
|
|
117
|
-
|
|
129
|
+
/** Tool-result fields the registry consumes (ToolResultMessage subset). */
|
|
130
|
+
export interface TurnResultLike {
|
|
131
|
+
readonly toolCallId: string;
|
|
132
|
+
readonly isError?: boolean;
|
|
133
|
+
readonly content?: readonly unknown[] | undefined;
|
|
134
|
+
readonly details?: unknown;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Result facts per tool call id: presence, error flag, and raw payload. */
|
|
138
|
+
interface RawMemberResult {
|
|
139
|
+
readonly isError: boolean;
|
|
140
|
+
readonly content?: readonly unknown[] | undefined;
|
|
141
|
+
readonly details?: unknown;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Extract a mutating member's diff line stats from its tool result — the
|
|
146
|
+
* same sources the box renderers read: `details.diff` for `edit`, the
|
|
147
|
+
* parsed `── diff ──` output section for the quick-edit family. `write`
|
|
148
|
+
* carries no diff and yields undefined. Pure: no render-time work.
|
|
149
|
+
*/
|
|
150
|
+
function diffStatsFromResult(
|
|
151
|
+
toolName: string,
|
|
152
|
+
result: RawMemberResult | undefined,
|
|
153
|
+
): { additions: number; removals: number } | undefined {
|
|
154
|
+
if (!result) return undefined;
|
|
155
|
+
if (isMutatingTool(toolName) && toolName !== "write") {
|
|
156
|
+
const diff = (result.details as { diff?: unknown } | undefined)?.diff;
|
|
157
|
+
if (typeof diff === "string" && diff.length > 0) return countDiffStats(diff);
|
|
158
|
+
}
|
|
159
|
+
if (getQuickEditToolConfig(toolName)) {
|
|
160
|
+
const text = Array.isArray(result.content)
|
|
161
|
+
? firstText(result.content as Array<{ type: string; text?: string }>)
|
|
162
|
+
: "";
|
|
163
|
+
const diff = text ? extractQuickEditDiff(text) : undefined;
|
|
164
|
+
if (diff) return countDiffStats(diff);
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildMembers(
|
|
118
170
|
calls: readonly ToolCallLike[],
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
)
|
|
122
|
-
if (calls.length === 0) return undefined;
|
|
123
|
-
const complete = calls.every((call) => typeof call.id === "string" && isErrorById.has(call.id));
|
|
124
|
-
const members: TurnMemberInfo[] = calls.map((call) => {
|
|
171
|
+
resultsById: ReadonlyMap<string, RawMemberResult>,
|
|
172
|
+
): TurnMemberInfo[] {
|
|
173
|
+
return calls.map((call) => {
|
|
125
174
|
const toolCallId = String(call.id ?? "");
|
|
175
|
+
const toolName = typeof call.name === "string" ? call.name : "tool";
|
|
176
|
+
const result = resultsById.get(toolCallId);
|
|
126
177
|
return {
|
|
127
178
|
toolCallId,
|
|
128
|
-
toolName
|
|
129
|
-
hasResult:
|
|
130
|
-
isError:
|
|
179
|
+
toolName,
|
|
180
|
+
hasResult: result !== undefined,
|
|
181
|
+
isError: result?.isError === true,
|
|
182
|
+
diffStats: diffStatsFromResult(toolName, result),
|
|
131
183
|
};
|
|
132
184
|
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function registerTurn(
|
|
188
|
+
calls: readonly ToolCallLike[],
|
|
189
|
+
resultsById: ReadonlyMap<string, RawMemberResult>,
|
|
190
|
+
ended: boolean,
|
|
191
|
+
): TurnState | undefined {
|
|
192
|
+
if (calls.length === 0) return undefined;
|
|
193
|
+
const complete = calls.every((call) => typeof call.id === "string" && resultsById.has(String(call.id ?? "")));
|
|
194
|
+
const members: TurnMemberInfo[] = buildMembers(calls, resultsById);
|
|
133
195
|
const leader = members.find((member) => !member.isError && (!isMutatingTool(member.toolName) || mutatingCollapses()));
|
|
134
196
|
const turn: TurnState = {
|
|
135
197
|
leaderId: leader?.toolCallId ?? "",
|
|
@@ -140,11 +202,6 @@ function registerTurn(
|
|
|
140
202
|
return turn;
|
|
141
203
|
}
|
|
142
204
|
|
|
143
|
-
export interface TurnResultLike {
|
|
144
|
-
readonly toolCallId: string;
|
|
145
|
-
readonly isError?: boolean;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
205
|
/**
|
|
149
206
|
* One summary group = one agent run (user request → `agent_end`). Pi emits
|
|
150
207
|
* `turn_end` per assistant message, so tool batches of the same request are
|
|
@@ -165,20 +222,16 @@ export function beginAgentRun(): void {
|
|
|
165
222
|
export function registerTurnFromMessage(message: unknown, toolResults: readonly TurnResultLike[]): void {
|
|
166
223
|
const calls = toolCallsOf(message);
|
|
167
224
|
if (calls.length === 0) return;
|
|
168
|
-
const
|
|
225
|
+
const resultsById = new Map<string, RawMemberResult>();
|
|
169
226
|
for (const result of toolResults) {
|
|
170
227
|
if (typeof result?.toolCallId !== "string") continue;
|
|
171
|
-
|
|
228
|
+
resultsById.set(result.toolCallId, {
|
|
229
|
+
isError: result.isError === true,
|
|
230
|
+
content: result.content,
|
|
231
|
+
details: result.details,
|
|
232
|
+
});
|
|
172
233
|
}
|
|
173
|
-
const newMembers: TurnMemberInfo[] = calls
|
|
174
|
-
const toolCallId = String(call.id ?? "");
|
|
175
|
-
return {
|
|
176
|
-
toolCallId,
|
|
177
|
-
toolName: typeof call.name === "string" ? call.name : "tool",
|
|
178
|
-
hasResult: isErrorById.has(toolCallId),
|
|
179
|
-
isError: isErrorById.get(toolCallId) === true,
|
|
180
|
-
};
|
|
181
|
-
});
|
|
234
|
+
const newMembers: TurnMemberInfo[] = buildMembers(calls, resultsById);
|
|
182
235
|
const leader = newMembers.find(
|
|
183
236
|
(member) => !member.isError && (!isMutatingTool(member.toolName) || mutatingCollapses()),
|
|
184
237
|
);
|
|
@@ -215,6 +268,7 @@ interface TurnEntryLike {
|
|
|
215
268
|
readonly message?: {
|
|
216
269
|
readonly role?: unknown;
|
|
217
270
|
readonly content?: unknown;
|
|
271
|
+
readonly details?: unknown;
|
|
218
272
|
readonly stopReason?: unknown;
|
|
219
273
|
readonly toolCallId?: unknown;
|
|
220
274
|
readonly isError?: unknown;
|
|
@@ -231,8 +285,7 @@ interface TurnEntryLike {
|
|
|
231
285
|
export function rebuildTurnRegistryFromEntries(entries: readonly TurnEntryLike[] | undefined): void {
|
|
232
286
|
memberByCallId.clear();
|
|
233
287
|
if (!Array.isArray(entries)) return;
|
|
234
|
-
const
|
|
235
|
-
const resultById = new Set<string>();
|
|
288
|
+
const resultsById = new Map<string, RawMemberResult>();
|
|
236
289
|
const runs: Array<{
|
|
237
290
|
calls: ToolCallLike[];
|
|
238
291
|
lastStopReason: string | undefined;
|
|
@@ -247,8 +300,11 @@ export function rebuildTurnRegistryFromEntries(entries: readonly TurnEntryLike[]
|
|
|
247
300
|
if (entry?.type !== "message") return;
|
|
248
301
|
const message = entry.message;
|
|
249
302
|
if (message?.role === "toolResult" && typeof message.toolCallId === "string") {
|
|
250
|
-
|
|
251
|
-
|
|
303
|
+
resultsById.set(message.toolCallId, {
|
|
304
|
+
isError: message.isError === true,
|
|
305
|
+
content: Array.isArray(message.content) ? (message.content as readonly unknown[]) : undefined,
|
|
306
|
+
details: message.details,
|
|
307
|
+
});
|
|
252
308
|
} else if (message?.role === "assistant") {
|
|
253
309
|
if (!current) current = { calls: [], lastStopReason: undefined, followedByUser: false };
|
|
254
310
|
const calls = toolCallsOf(message);
|
|
@@ -267,9 +323,9 @@ export function rebuildTurnRegistryFromEntries(entries: readonly TurnEntryLike[]
|
|
|
267
323
|
});
|
|
268
324
|
closeRun();
|
|
269
325
|
for (const run of runs) {
|
|
270
|
-
const complete = run.calls.every((call) => typeof call.id === "string" &&
|
|
326
|
+
const complete = run.calls.every((call) => typeof call.id === "string" && resultsById.has(String(call.id ?? "")));
|
|
271
327
|
const ended = complete && (run.followedByUser || run.lastStopReason !== undefined);
|
|
272
|
-
registerTurn(run.calls,
|
|
328
|
+
registerTurn(run.calls, resultsById, ended);
|
|
273
329
|
}
|
|
274
330
|
}
|
|
275
331
|
|
|
@@ -349,25 +405,37 @@ export interface TurnSummaryParts {
|
|
|
349
405
|
readonly failedCount: number;
|
|
350
406
|
/** Sum of members' frozen elapsed; undefined when nothing was recorded. */
|
|
351
407
|
readonly elapsedMs: number | undefined;
|
|
408
|
+
/** Aggregate diff line stats over non-error edit-family members; undefined
|
|
409
|
+
* when none carried a diff. Collected regardless of the mutating exemption:
|
|
410
|
+
* visible edit blocks are exactly what these stats describe. */
|
|
411
|
+
readonly diffStats: { additions: number; removals: number } | undefined;
|
|
352
412
|
}
|
|
353
413
|
|
|
354
414
|
/**
|
|
355
415
|
* Aggregate a turn's collapsed members into summary parts (pure). Mutating
|
|
356
|
-
* members are excluded unless `tools.collapseMutatingTools`
|
|
357
|
-
* their visible blocks are the record; the summary
|
|
358
|
-
* hides.
|
|
416
|
+
* members are excluded from counts/elapsed unless `tools.collapseMutatingTools`
|
|
417
|
+
* is on — by default their visible blocks are the record; the summary counts
|
|
418
|
+
* only what it hides. Their diff stats aggregate either way.
|
|
359
419
|
*/
|
|
360
420
|
export function turnSummaryParts(turn: TurnState): TurnSummaryParts {
|
|
361
421
|
const counts = new Map<string, number>();
|
|
362
422
|
const order: string[] = [];
|
|
363
423
|
let failedCount = 0;
|
|
364
424
|
let elapsedMs: number | undefined;
|
|
425
|
+
let diffAdditions = 0;
|
|
426
|
+
let diffRemovals = 0;
|
|
427
|
+
let diffMembers = 0;
|
|
365
428
|
const collapseMutating = mutatingCollapses();
|
|
366
429
|
for (const member of turn.members) {
|
|
367
430
|
if (member.isError) {
|
|
368
431
|
failedCount++;
|
|
369
432
|
continue;
|
|
370
433
|
}
|
|
434
|
+
if (member.diffStats !== undefined) {
|
|
435
|
+
diffAdditions += member.diffStats.additions;
|
|
436
|
+
diffRemovals += member.diffStats.removals;
|
|
437
|
+
diffMembers++;
|
|
438
|
+
}
|
|
371
439
|
if (!collapseMutating && isMutatingTool(member.toolName)) continue;
|
|
372
440
|
if (member.elapsedMs !== undefined) elapsedMs = (elapsedMs ?? 0) + member.elapsedMs;
|
|
373
441
|
const existing = counts.get(member.toolName);
|
|
@@ -383,16 +451,24 @@ export function turnSummaryParts(turn: TurnState): TurnSummaryParts {
|
|
|
383
451
|
// neutral phrasing with the invariant tool name: `used 5 TaskCreate`.
|
|
384
452
|
return style ? `${style.verb} ${count} ${pluralForm(style.unit, count)}` : `used ${count} ${toolName}`;
|
|
385
453
|
});
|
|
386
|
-
return {
|
|
454
|
+
return {
|
|
455
|
+
parts,
|
|
456
|
+
failedCount,
|
|
457
|
+
elapsedMs,
|
|
458
|
+
diffStats: diffMembers > 0 ? { additions: diffAdditions, removals: diffRemovals } : undefined,
|
|
459
|
+
};
|
|
387
460
|
}
|
|
388
461
|
|
|
389
462
|
function formatTurnSummaryLine(theme: BoxTheme, turn: TurnState): string {
|
|
390
463
|
const summary = turnSummaryParts(turn);
|
|
391
464
|
// The summary is deliberately quiet: the whole line renders dim so completed
|
|
392
|
-
// tool work recedes behind the assistant's answer. Only the
|
|
393
|
-
//
|
|
465
|
+
// tool work recedes behind the assistant's answer. Only the diff stats
|
|
466
|
+
// (`+N` added / `-M` removed) and the failed marker stay color-coded —
|
|
467
|
+
// changes and errors must remain visible at a glance.
|
|
394
468
|
const parts = summary.parts.join(", ");
|
|
395
469
|
let line = `${theme.fg("dim", `➔ ${parts}`)}`;
|
|
470
|
+
if (summary.diffStats !== undefined)
|
|
471
|
+
line += `${theme.fg("dim", " · Edit ")}${formatDiffStatsPair(theme, summary.diffStats.additions, summary.diffStats.removals)}`;
|
|
396
472
|
if (summary.failedCount > 0)
|
|
397
473
|
line += theme.fg("error", ` · ${summary.failedCount} ${pluralForm("failure", summary.failedCount)}`);
|
|
398
474
|
if (summary.elapsedMs !== undefined) line += theme.fg("dim", ` · ${(summary.elapsedMs / 1000).toFixed(2)}s`);
|
|
@@ -89,6 +89,7 @@ function renderWritePreviewBox(
|
|
|
89
89
|
isError: options.isError,
|
|
90
90
|
isPending: options.isPending,
|
|
91
91
|
running: Boolean(options.running),
|
|
92
|
+
tint: true, // the write preview is a framed box — it owns its status tint
|
|
92
93
|
bodyLines: () => {
|
|
93
94
|
if (preview.length === 0) return [];
|
|
94
95
|
const shown = preview.slice(0, budget).map((line) => formatNumberedLine(theme, line));
|
|
@@ -15,13 +15,16 @@ function hideBatchMember(instance: object): void {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Neutralize the native ToolExecutionComponent
|
|
18
|
+
* Neutralize the native ToolExecutionComponent container fill for boxed
|
|
19
19
|
* rendering: Pi's updateDisplay sets contentBox/selfRenderContainer bgFn to
|
|
20
20
|
* toolPendingBg/toolErrorBg/toolSuccessBg before invoking the renderers. The
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
21
|
+
* rendered boxes own their tint (box ⇒ background): boxed components wrap
|
|
22
|
+
* their own lines in the status fill while boxless surfaces (quiet-tool rows,
|
|
23
|
+
* tree panels, git/gh semantic cards, turn summaries) stay transparent — so
|
|
24
|
+
* the container fill is always removed. The native Box padding (1,1) is
|
|
25
|
+
* zeroed as well, or the frame would gain stray blank rows and an indent.
|
|
26
|
+
* Runs on every boxed dispatch; updateDisplay re-applies both on the next
|
|
27
|
+
* pass and this wrapper re-neutralizes them.
|
|
25
28
|
*/
|
|
26
29
|
function neutralizeToolContainerBackground(instance: object): void {
|
|
27
30
|
const host = instance as {
|
|
@@ -421,7 +424,6 @@ export function createToolDecorationOwner(snapshot: Partial<ToolDecorationSnapsh
|
|
|
421
424
|
// fallback renderer, mirroring the generic boxed fallback used for
|
|
422
425
|
// unknown tool names.
|
|
423
426
|
if (typeof renderer !== "function") {
|
|
424
|
-
neutralizeToolContainerBackground(instance);
|
|
425
427
|
if (subtype === "tool-call-renderer")
|
|
426
428
|
return (callArgs: unknown, theme: unknown, context: unknown) => {
|
|
427
429
|
const component = renderBoxedToolCall(
|
|
@@ -430,6 +432,7 @@ export function createToolDecorationOwner(snapshot: Partial<ToolDecorationSnapsh
|
|
|
430
432
|
theme as never,
|
|
431
433
|
context as never,
|
|
432
434
|
);
|
|
435
|
+
neutralizeToolContainerBackground(instance);
|
|
433
436
|
// Same batch-member contract as the native-renderer path: a
|
|
434
437
|
// collapsed turn member (or quiet batch member) returns the
|
|
435
438
|
// singleton and must be hidden, or Pi leaves a stray native
|
|
@@ -445,6 +448,7 @@ export function createToolDecorationOwner(snapshot: Partial<ToolDecorationSnapsh
|
|
|
445
448
|
theme as never,
|
|
446
449
|
context as never,
|
|
447
450
|
);
|
|
451
|
+
neutralizeToolContainerBackground(instance);
|
|
448
452
|
if (component === EMPTY_BATCH_COMPONENT) hideBatchMember(instance);
|
|
449
453
|
return component;
|
|
450
454
|
};
|
|
@@ -168,6 +168,8 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
|
|
|
168
168
|
assistantPrefix: authorization.ascii ? "[assistant] " : "│ ",
|
|
169
169
|
assistantEnabled,
|
|
170
170
|
collapseHiddenThinking: thinkingCollapseEnabled,
|
|
171
|
+
thoughtSummary: thinkingCollapseEnabled && config.messages.thoughtSummary,
|
|
172
|
+
thoughtGlyph: authorization.ascii ? ">" : "◈",
|
|
171
173
|
},
|
|
172
174
|
toolSnapshot: {
|
|
173
175
|
callMarker: authorization.ascii ? "[tool] " : "[tool] ",
|
|
@@ -35,13 +35,16 @@ import {
|
|
|
35
35
|
* changes the identity degrades that single surface to its native fallback while
|
|
36
36
|
* every other surface continues.
|
|
37
37
|
*/
|
|
38
|
-
export const SUPPORTED_VERSION_RANGE = ">=0.83.0 <0.
|
|
38
|
+
export const SUPPORTED_VERSION_RANGE = ">=0.83.0 <0.86.0";
|
|
39
39
|
export const SUPPORTED_PI_VERSIONS: readonly string[] = Object.freeze([
|
|
40
40
|
"0.83.0",
|
|
41
41
|
"0.84.0",
|
|
42
42
|
"0.84.1",
|
|
43
43
|
"0.84.2",
|
|
44
44
|
"0.84.3",
|
|
45
|
+
"0.84.4",
|
|
46
|
+
"0.85.0",
|
|
47
|
+
"0.85.1",
|
|
45
48
|
]);
|
|
46
49
|
|
|
47
50
|
/** A recorded native identity for one certified surface. */
|
|
@@ -71,6 +74,17 @@ export interface KnownNativeIdentity {
|
|
|
71
74
|
* identity (0.83.0–0.84.2) and the bundled identity (0.84.3). The modular
|
|
72
75
|
* `dist/index.js` itself is unchanged in 0.84.3 — the switch is which class
|
|
73
76
|
* objects the running CLI actually serves to extensions.
|
|
77
|
+
*
|
|
78
|
+
* 0.84.4 rebuilds both artifact families byte-identically for every surface
|
|
79
|
+
* below, so it shares the 0.84.3 identities. 0.85.0 drifts exactly three
|
|
80
|
+
* surfaces (in both families) while preserving name/arity and the adapter
|
|
81
|
+
* contracts, so each carries a re-recorded 0.85.0 identity:
|
|
82
|
+
* - `AssistantMessageComponent.updateContent` adds per-run thinking visibility
|
|
83
|
+
* overrides (`thinkingVisibilityOverrides` + a `MouseRegion` click toggle per
|
|
84
|
+
* thinking run; the `isStreaming` default parameter and arity 1 are unchanged).
|
|
85
|
+
* - `ToolExecutionComponent.getCallRenderer`/`getResultRenderer` drop the
|
|
86
|
+
* `builtInToolDefinition` fallback branches and simply return
|
|
87
|
+
* `this.toolDefinition?.renderCall`/`renderResult` (still renderer-or-undefined).
|
|
74
88
|
*/
|
|
75
89
|
export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNativeIdentity[]>> = Object.freeze({
|
|
76
90
|
"native-assistant-message:render": Object.freeze([
|
|
@@ -78,13 +92,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
78
92
|
name: "render",
|
|
79
93
|
arity: 1,
|
|
80
94
|
fingerprint: "2a39243f",
|
|
81
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
95
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
82
96
|
}),
|
|
83
97
|
Object.freeze({
|
|
84
98
|
name: "render",
|
|
85
99
|
arity: 1,
|
|
86
100
|
fingerprint: "a9be09a3",
|
|
87
|
-
versions: Object.freeze(["0.84.3"]),
|
|
101
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
88
102
|
}),
|
|
89
103
|
]),
|
|
90
104
|
"native-assistant-message:updateContent": Object.freeze([
|
|
@@ -101,7 +115,7 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
101
115
|
name: "updateContent",
|
|
102
116
|
arity: 1,
|
|
103
117
|
fingerprint: "d2114491",
|
|
104
|
-
versions: Object.freeze(["0.84.0", "0.84.1", "0.84.2"]),
|
|
118
|
+
versions: Object.freeze(["0.84.0", "0.84.1", "0.84.2", "0.84.4"]),
|
|
105
119
|
}),
|
|
106
120
|
// 0.84.3: the CLI loads a minified bundled runtime and extensions receive
|
|
107
121
|
// the in-bundle class objects, so this method's toString() is the minified
|
|
@@ -111,7 +125,32 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
111
125
|
name: "updateContent",
|
|
112
126
|
arity: 1,
|
|
113
127
|
fingerprint: "356b7e83",
|
|
114
|
-
versions: Object.freeze(["0.84.3"]),
|
|
128
|
+
versions: Object.freeze(["0.84.3", "0.84.4"]),
|
|
129
|
+
}),
|
|
130
|
+
// 0.85.0 modular: adds per-run thinking visibility overrides and a
|
|
131
|
+
// MouseRegion click toggle per thinking run (arity stays 1).
|
|
132
|
+
Object.freeze({
|
|
133
|
+
name: "updateContent",
|
|
134
|
+
arity: 1,
|
|
135
|
+
fingerprint: "80e338d2",
|
|
136
|
+
versions: Object.freeze(["0.85.0", "0.85.1"]),
|
|
137
|
+
}),
|
|
138
|
+
// 0.85.0 bundled: same drift, minified.
|
|
139
|
+
Object.freeze({
|
|
140
|
+
name: "updateContent",
|
|
141
|
+
arity: 1,
|
|
142
|
+
fingerprint: "c3d72f2b",
|
|
143
|
+
versions: Object.freeze(["0.85.0"]),
|
|
144
|
+
}),
|
|
145
|
+
// 0.85.1 bundled: the rebundled runtime renames the minified `message2`
|
|
146
|
+
// parameter to `message` — the modular dist is unchanged from 0.85.0 (only
|
|
147
|
+
// the GPT-6 Astra model catalog and fullscreen-scroll fixes landed), so the
|
|
148
|
+
// minified method text drifts while behavior stays identical.
|
|
149
|
+
Object.freeze({
|
|
150
|
+
name: "updateContent",
|
|
151
|
+
arity: 1,
|
|
152
|
+
fingerprint: "31632e19",
|
|
153
|
+
versions: Object.freeze(["0.85.1"]),
|
|
115
154
|
}),
|
|
116
155
|
]),
|
|
117
156
|
"native-compaction-message:updateDisplay": Object.freeze([
|
|
@@ -119,13 +158,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
119
158
|
name: "updateDisplay",
|
|
120
159
|
arity: 0,
|
|
121
160
|
fingerprint: "f8c44e78",
|
|
122
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
161
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
123
162
|
}),
|
|
124
163
|
Object.freeze({
|
|
125
164
|
name: "updateDisplay",
|
|
126
165
|
arity: 0,
|
|
127
166
|
fingerprint: "5118a51d",
|
|
128
|
-
versions: Object.freeze(["0.84.3"]),
|
|
167
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
129
168
|
}),
|
|
130
169
|
]),
|
|
131
170
|
"native-branch-message:updateDisplay": Object.freeze([
|
|
@@ -133,13 +172,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
133
172
|
name: "updateDisplay",
|
|
134
173
|
arity: 0,
|
|
135
174
|
fingerprint: "415d57b7",
|
|
136
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
175
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
137
176
|
}),
|
|
138
177
|
Object.freeze({
|
|
139
178
|
name: "updateDisplay",
|
|
140
179
|
arity: 0,
|
|
141
180
|
fingerprint: "2185274e",
|
|
142
|
-
versions: Object.freeze(["0.84.3"]),
|
|
181
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
143
182
|
}),
|
|
144
183
|
]),
|
|
145
184
|
"native-skill-message:updateDisplay": Object.freeze([
|
|
@@ -147,13 +186,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
147
186
|
name: "updateDisplay",
|
|
148
187
|
arity: 0,
|
|
149
188
|
fingerprint: "48099ea6",
|
|
150
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
189
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
151
190
|
}),
|
|
152
191
|
Object.freeze({
|
|
153
192
|
name: "updateDisplay",
|
|
154
193
|
arity: 0,
|
|
155
194
|
fingerprint: "4051fd65",
|
|
156
|
-
versions: Object.freeze(["0.84.3"]),
|
|
195
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
157
196
|
}),
|
|
158
197
|
]),
|
|
159
198
|
"native-custom-message:rebuild": Object.freeze([
|
|
@@ -161,13 +200,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
161
200
|
name: "rebuild",
|
|
162
201
|
arity: 0,
|
|
163
202
|
fingerprint: "76ae2e3a",
|
|
164
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
203
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
165
204
|
}),
|
|
166
205
|
Object.freeze({
|
|
167
206
|
name: "rebuild",
|
|
168
207
|
arity: 0,
|
|
169
208
|
fingerprint: "b89987cc",
|
|
170
|
-
versions: Object.freeze(["0.84.3"]),
|
|
209
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
171
210
|
}),
|
|
172
211
|
]),
|
|
173
212
|
"tool-call-renderer:getCallRenderer": Object.freeze([
|
|
@@ -175,13 +214,28 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
175
214
|
name: "getCallRenderer",
|
|
176
215
|
arity: 0,
|
|
177
216
|
fingerprint: "951ea0e0",
|
|
178
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
217
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4"]),
|
|
179
218
|
}),
|
|
180
219
|
Object.freeze({
|
|
181
220
|
name: "getCallRenderer",
|
|
182
221
|
arity: 0,
|
|
183
222
|
fingerprint: "e50613b7",
|
|
184
|
-
versions: Object.freeze(["0.84.3"]),
|
|
223
|
+
versions: Object.freeze(["0.84.3", "0.84.4"]),
|
|
224
|
+
}),
|
|
225
|
+
// 0.85.0 modular: drops the `builtInToolDefinition` fallback branches
|
|
226
|
+
// and returns `this.toolDefinition?.renderCall` directly.
|
|
227
|
+
Object.freeze({
|
|
228
|
+
name: "getCallRenderer",
|
|
229
|
+
arity: 0,
|
|
230
|
+
fingerprint: "e0a9ed86",
|
|
231
|
+
versions: Object.freeze(["0.85.0"]),
|
|
232
|
+
}),
|
|
233
|
+
// 0.85.0 bundled: same drift, minified.
|
|
234
|
+
Object.freeze({
|
|
235
|
+
name: "getCallRenderer",
|
|
236
|
+
arity: 0,
|
|
237
|
+
fingerprint: "73116365",
|
|
238
|
+
versions: Object.freeze(["0.85.0", "0.85.1"]),
|
|
185
239
|
}),
|
|
186
240
|
]),
|
|
187
241
|
"tool-result-renderer:getResultRenderer": Object.freeze([
|
|
@@ -189,13 +243,28 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
189
243
|
name: "getResultRenderer",
|
|
190
244
|
arity: 0,
|
|
191
245
|
fingerprint: "8a25cd71",
|
|
192
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
246
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4"]),
|
|
193
247
|
}),
|
|
194
248
|
Object.freeze({
|
|
195
249
|
name: "getResultRenderer",
|
|
196
250
|
arity: 0,
|
|
197
251
|
fingerprint: "28c4dc22",
|
|
198
|
-
versions: Object.freeze(["0.84.3"]),
|
|
252
|
+
versions: Object.freeze(["0.84.3", "0.84.4"]),
|
|
253
|
+
}),
|
|
254
|
+
// 0.85.0 modular: drops the `builtInToolDefinition` fallback branches
|
|
255
|
+
// and returns `this.toolDefinition?.renderResult` directly.
|
|
256
|
+
Object.freeze({
|
|
257
|
+
name: "getResultRenderer",
|
|
258
|
+
arity: 0,
|
|
259
|
+
fingerprint: "1567dcf4",
|
|
260
|
+
versions: Object.freeze(["0.85.0"]),
|
|
261
|
+
}),
|
|
262
|
+
// 0.85.0 bundled: same drift, minified.
|
|
263
|
+
Object.freeze({
|
|
264
|
+
name: "getResultRenderer",
|
|
265
|
+
arity: 0,
|
|
266
|
+
fingerprint: "d613a2a3",
|
|
267
|
+
versions: Object.freeze(["0.85.0", "0.85.1"]),
|
|
199
268
|
}),
|
|
200
269
|
]),
|
|
201
270
|
"native-bash-execution:render": Object.freeze([
|
|
@@ -207,13 +276,13 @@ export const KNOWN_NATIVE_IDENTITIES: Readonly<Record<string, readonly KnownNati
|
|
|
207
276
|
name: "BashExecutionComponent",
|
|
208
277
|
arity: 2,
|
|
209
278
|
fingerprint: "a5b5abca",
|
|
210
|
-
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2"]),
|
|
279
|
+
versions: Object.freeze(["0.83.0", "0.84.0", "0.84.1", "0.84.2", "0.84.4", "0.85.0"]),
|
|
211
280
|
}),
|
|
212
281
|
Object.freeze({
|
|
213
282
|
name: "BashExecutionComponent",
|
|
214
283
|
arity: 2,
|
|
215
284
|
fingerprint: "98d22d96",
|
|
216
|
-
versions: Object.freeze(["0.84.3"]),
|
|
285
|
+
versions: Object.freeze(["0.84.3", "0.84.4", "0.85.0", "0.85.1"]),
|
|
217
286
|
}),
|
|
218
287
|
]),
|
|
219
288
|
});
|
|
@@ -4,6 +4,7 @@ import type { ConfigFilePort } from "../app/config-storage.js";
|
|
|
4
4
|
import { createPiStyleApp, type PiStyleApp } from "../app/index.js";
|
|
5
5
|
import { resolveTheme } from "../domain/theme.js";
|
|
6
6
|
import { resetPendingImageRegistry } from "../features/messages/image-input.js";
|
|
7
|
+
import { setThoughtLabelTheme } from "../features/messages/index.js";
|
|
7
8
|
import { setMessagesRenderConfig } from "../features/messages/render-config.js";
|
|
8
9
|
import { setSpecialBlockTheme } from "../features/messages/special-blocks.js";
|
|
9
10
|
import { setBashExecutionTheme } from "../features/tools/bash-execution.js";
|
|
@@ -94,9 +95,26 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
94
95
|
* Hide Pi's "Thinking..." placeholder label: an empty label renders zero
|
|
95
96
|
* lines, so the thinking block leaves no trace while content stays hidden.
|
|
96
97
|
* Passing undefined restores the default label.
|
|
98
|
+
*
|
|
99
|
+
* Gated on the certified `updateContent` surface actually being installed:
|
|
100
|
+
* blanking without the patch leaves Pi's native invisible-row gap (worse
|
|
101
|
+
* than the label it replaces), so an unsupported runtime identity keeps the
|
|
102
|
+
* native `Thinking...` label instead.
|
|
97
103
|
*/
|
|
104
|
+
const thinkingCollapseInstalled = (): boolean => {
|
|
105
|
+
const records = compatibility.report?.recordSnapshots ?? [];
|
|
106
|
+
return records.some(
|
|
107
|
+
(record) =>
|
|
108
|
+
record.subtype === "native-assistant-message" &&
|
|
109
|
+
record.method === "updateContent" &&
|
|
110
|
+
record.shape === "installed" &&
|
|
111
|
+
!record.disposed,
|
|
112
|
+
);
|
|
113
|
+
};
|
|
98
114
|
const applyMessagesConfig = (config: import("../domain/config-types.js").NormalizedPiStyleConfig) => {
|
|
99
|
-
sessionUi?.setHiddenThinkingLabel?.(
|
|
115
|
+
sessionUi?.setHiddenThinkingLabel?.(
|
|
116
|
+
config.messages.hideThinkingLabel && thinkingCollapseInstalled() ? "" : undefined,
|
|
117
|
+
);
|
|
100
118
|
// User-prompt image previews (ADR 0008) + clipboard image input (ADR
|
|
101
119
|
// 0009): the leaves gate their respective sides (preview: stage+render;
|
|
102
120
|
// clipboard: input transform) and size the preview images.
|
|
@@ -211,6 +229,7 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
211
229
|
applyMessagesConfig(app.config);
|
|
212
230
|
if (ctx.ui?.theme) setSpecialBlockTheme(ctx.ui.theme as never);
|
|
213
231
|
if (ctx.ui?.theme) setBashExecutionTheme(ctx.ui.theme as never);
|
|
232
|
+
if (ctx.ui?.theme) setThoughtLabelTheme(ctx.ui.theme as never);
|
|
214
233
|
const toolDetails = collectToolDetails(pi.getActiveTools?.(), pi.getAllTools?.());
|
|
215
234
|
app.sessionStart(
|
|
216
235
|
{
|
|
@@ -108,6 +108,51 @@ export function resetAnsi(value: string): string {
|
|
|
108
108
|
export function fitAnsiWidth(value: string, width: number, ellipsis = "…"): string {
|
|
109
109
|
return visibleWidth(value) <= width ? value : truncateAnsi(value, width, ellipsis);
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Fit a string by dropping the HEAD (keeping the tail): for path-like values
|
|
113
|
+
* whose meaningful part is the end (the repo name). ANSI-aware — an escape run
|
|
114
|
+
* belongs to the visible character it precedes, so dropped characters drop
|
|
115
|
+
* their styling with them and the first kept character keeps its own escape.
|
|
116
|
+
*/
|
|
117
|
+
export function fitAnsiWidthTail(value: string, width: number, ellipsis = "…"): string {
|
|
118
|
+
if (width <= 0) return "";
|
|
119
|
+
if (visibleWidth(value) <= width) return resetAnsi(value);
|
|
120
|
+
const ellipsisWidth = visibleWidth(ellipsis);
|
|
121
|
+
if (width <= ellipsisWidth) return resetAnsi(ellipsis);
|
|
122
|
+
type Unit = { ansi: string; char: string; charWidth: number };
|
|
123
|
+
const units: Unit[] = [];
|
|
124
|
+
let pending = "";
|
|
125
|
+
for (let i = 0; i < value.length; i++) {
|
|
126
|
+
const code = value.charCodeAt(i);
|
|
127
|
+
if (code === 27) {
|
|
128
|
+
const start = i;
|
|
129
|
+
i++;
|
|
130
|
+
while (i + 1 < value.length && !isFinal(value[i + 1] ?? "")) i++;
|
|
131
|
+
i++;
|
|
132
|
+
pending += value.slice(start, i + 1);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
let char = value[i] ?? "";
|
|
136
|
+
// Keep surrogate pairs (emoji etc.) as one unit.
|
|
137
|
+
if (code >= 0xd800 && code <= 0xdbff && i + 1 < value.length) char += value[++i];
|
|
138
|
+
units.push({ ansi: pending, char, charWidth: visibleWidth(char) || 1 });
|
|
139
|
+
pending = "";
|
|
140
|
+
}
|
|
141
|
+
let kept = 0;
|
|
142
|
+
let used = ellipsisWidth;
|
|
143
|
+
while (kept < units.length) {
|
|
144
|
+
const unit = units[units.length - 1 - kept];
|
|
145
|
+
if (!unit || used + unit.charWidth > width) break;
|
|
146
|
+
used += unit.charWidth;
|
|
147
|
+
kept++;
|
|
148
|
+
}
|
|
149
|
+
let output = ellipsis;
|
|
150
|
+
for (let index = units.length - kept; index < units.length; index++) {
|
|
151
|
+
const unit = units[index];
|
|
152
|
+
if (unit) output += unit.ansi + unit.char;
|
|
153
|
+
}
|
|
154
|
+
return resetAnsi(output);
|
|
155
|
+
}
|
|
111
156
|
export function truncateAnsi(value: string, width: number, ellipsis = "…"): string {
|
|
112
157
|
if (width <= 0) return "";
|
|
113
158
|
if (visibleWidth(value) <= width) return resetAnsi(value);
|