@quandev104/pi-style 0.1.6 → 0.1.7
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 +6 -0
- package/README.md +3 -1
- package/dist/extensions/pi-style.js +8319 -8027
- package/dist/extensions/pi-style.js.map +1 -1
- package/extension-src/pi-style/app/command-service.ts +4 -0
- package/extension-src/pi-style/domain/config-normalization.ts +12 -1
- package/extension-src/pi-style/domain/config-presets.ts +2 -2
- package/extension-src/pi-style/domain/config-types.ts +18 -2
- package/extension-src/pi-style/features/messages/index.ts +58 -10
- package/extension-src/pi-style/features/tools/boxed/index.ts +41 -1
- package/extension-src/pi-style/features/tools/boxed/session-config.ts +3 -0
- package/extension-src/pi-style/features/tools/boxed/turn-summary.ts +368 -0
- package/extension-src/pi-style/features/tools/index.ts +15 -0
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +1 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +8 -2
- package/extension-src/pi-style/pi/index.ts +37 -3
- package/extension-src/pi-style/pi/session-coordinator.ts +7 -0
- package/package.json +1 -1
|
@@ -48,12 +48,14 @@ const allowedPaths = new Set([
|
|
|
48
48
|
"messages.assistantPrefix",
|
|
49
49
|
"messages.specialBlocks",
|
|
50
50
|
"messages.hideThinkingLabel",
|
|
51
|
+
"messages.hideInterimText",
|
|
51
52
|
"tools.enabled",
|
|
52
53
|
"tools.style",
|
|
53
54
|
"tools.maxCollapsedLines",
|
|
54
55
|
"tools.maxExpandedLines",
|
|
55
56
|
"tools.dimOutput",
|
|
56
57
|
"tools.showElapsed",
|
|
58
|
+
"tools.collapseAfterTurn",
|
|
57
59
|
"theme.nerdFonts",
|
|
58
60
|
"theme.terminalBackgroundSync",
|
|
59
61
|
"theme.autoApply",
|
|
@@ -78,9 +80,11 @@ function validatePathValue(path: string, value: unknown): boolean {
|
|
|
78
80
|
"messages.assistantPrefix",
|
|
79
81
|
"messages.specialBlocks",
|
|
80
82
|
"messages.hideThinkingLabel",
|
|
83
|
+
"messages.hideInterimText",
|
|
81
84
|
"tools.enabled",
|
|
82
85
|
"tools.showElapsed",
|
|
83
86
|
"tools.dimOutput",
|
|
87
|
+
"tools.collapseAfterTurn",
|
|
84
88
|
"compatibility.allowSafePatches",
|
|
85
89
|
"compatibility.allowCorePatches",
|
|
86
90
|
"compatibility.preferExistingEditor",
|
|
@@ -28,7 +28,13 @@ export const DEFAULT_CONFIG: NormalizedPiStyleConfig = Object.freeze({
|
|
|
28
28
|
contextBarWidth: 10,
|
|
29
29
|
}),
|
|
30
30
|
editor: Object.freeze({ enabled: true, style: "dock", frame: "rounded", showMetadata: false, hint: "" }),
|
|
31
|
-
messages: Object.freeze({
|
|
31
|
+
messages: Object.freeze({
|
|
32
|
+
enabled: true,
|
|
33
|
+
assistantPrefix: true,
|
|
34
|
+
specialBlocks: true,
|
|
35
|
+
hideThinkingLabel: true,
|
|
36
|
+
hideInterimText: true,
|
|
37
|
+
}),
|
|
32
38
|
tools: Object.freeze({
|
|
33
39
|
enabled: true,
|
|
34
40
|
style: "compact-box",
|
|
@@ -36,6 +42,7 @@ export const DEFAULT_CONFIG: NormalizedPiStyleConfig = Object.freeze({
|
|
|
36
42
|
maxExpandedLines: 50,
|
|
37
43
|
dimOutput: false,
|
|
38
44
|
showElapsed: true,
|
|
45
|
+
collapseAfterTurn: true,
|
|
39
46
|
}),
|
|
40
47
|
theme: Object.freeze({
|
|
41
48
|
nerdFonts: "auto",
|
|
@@ -169,6 +176,7 @@ export function normalizeConfig(
|
|
|
169
176
|
assistantPrefix: bool(messages.assistantPrefix, defaults.messages.assistantPrefix),
|
|
170
177
|
specialBlocks: bool(messages.specialBlocks, defaults.messages.specialBlocks),
|
|
171
178
|
hideThinkingLabel: bool(messages.hideThinkingLabel, defaults.messages.hideThinkingLabel),
|
|
179
|
+
hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText),
|
|
172
180
|
}),
|
|
173
181
|
tools: Object.freeze({
|
|
174
182
|
enabled: bool(tools.enabled, defaults.tools.enabled),
|
|
@@ -177,6 +185,7 @@ export function normalizeConfig(
|
|
|
177
185
|
maxExpandedLines: maxExpanded,
|
|
178
186
|
dimOutput: bool(tools.dimOutput, defaults.tools.dimOutput),
|
|
179
187
|
showElapsed: bool(tools.showElapsed, defaults.tools.showElapsed),
|
|
188
|
+
collapseAfterTurn: bool(tools.collapseAfterTurn, defaults.tools.collapseAfterTurn),
|
|
180
189
|
}),
|
|
181
190
|
theme: Object.freeze({
|
|
182
191
|
nerdFonts: stringEnum(theme.nerdFonts, ["auto", "on", "off"], defaults.theme.nerdFonts),
|
|
@@ -227,9 +236,11 @@ const BOOL_PATHS = new Set([
|
|
|
227
236
|
"messages.assistantPrefix",
|
|
228
237
|
"messages.specialBlocks",
|
|
229
238
|
"messages.hideThinkingLabel",
|
|
239
|
+
"messages.hideInterimText",
|
|
230
240
|
"tools.enabled",
|
|
231
241
|
"tools.showElapsed",
|
|
232
242
|
"tools.dimOutput",
|
|
243
|
+
"tools.collapseAfterTurn",
|
|
233
244
|
"compatibility.allowSafePatches",
|
|
234
245
|
"compatibility.allowCorePatches",
|
|
235
246
|
"compatibility.preferExistingEditor",
|
|
@@ -10,7 +10,7 @@ export const CONFIG_PRESETS: Readonly<Record<PresetName, Readonly<PiStyleConfig>
|
|
|
10
10
|
editor: { style: "native", frame: "native", showMetadata: false },
|
|
11
11
|
startup: { mode: "off" },
|
|
12
12
|
messages: { enabled: false },
|
|
13
|
-
tools: { enabled: false },
|
|
13
|
+
tools: { enabled: false, collapseAfterTurn: false },
|
|
14
14
|
theme: { terminalBackgroundSync: "off" },
|
|
15
15
|
}),
|
|
16
16
|
compact: Object.freeze({
|
|
@@ -41,7 +41,7 @@ export const CONFIG_PRESETS: Readonly<Record<PresetName, Readonly<PiStyleConfig>
|
|
|
41
41
|
editor: { style: "native", frame: "native", showMetadata: false },
|
|
42
42
|
startup: { mode: "off" },
|
|
43
43
|
messages: { enabled: false },
|
|
44
|
-
tools: { enabled: false },
|
|
44
|
+
tools: { enabled: false, collapseAfterTurn: false },
|
|
45
45
|
theme: { terminalBackgroundSync: "off", autoApply: "off" },
|
|
46
46
|
}),
|
|
47
47
|
});
|
|
@@ -32,7 +32,13 @@ export interface PiStyleConfig {
|
|
|
32
32
|
contextBarWidth?: number;
|
|
33
33
|
};
|
|
34
34
|
editor?: { enabled?: boolean; style?: string; frame?: string; showMetadata?: boolean; hint?: string };
|
|
35
|
-
messages?: {
|
|
35
|
+
messages?: {
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
assistantPrefix?: boolean;
|
|
38
|
+
specialBlocks?: boolean;
|
|
39
|
+
hideThinkingLabel?: boolean;
|
|
40
|
+
hideInterimText?: boolean;
|
|
41
|
+
};
|
|
36
42
|
tools?: {
|
|
37
43
|
enabled?: boolean;
|
|
38
44
|
style?: string;
|
|
@@ -40,6 +46,8 @@ export interface PiStyleConfig {
|
|
|
40
46
|
maxExpandedLines?: number;
|
|
41
47
|
dimOutput?: boolean;
|
|
42
48
|
showElapsed?: boolean;
|
|
49
|
+
/** Collapse a completed turn's tool blocks into one summary line (ADR 0007). */
|
|
50
|
+
collapseAfterTurn?: boolean;
|
|
43
51
|
};
|
|
44
52
|
theme?: {
|
|
45
53
|
nerdFonts?: string;
|
|
@@ -81,7 +89,13 @@ export interface NormalizedPiStyleConfig {
|
|
|
81
89
|
contextBarWidth: number;
|
|
82
90
|
};
|
|
83
91
|
readonly editor: { enabled: boolean; style: EditorStyle; frame: EditorFrame; showMetadata: boolean; hint: string };
|
|
84
|
-
readonly messages: {
|
|
92
|
+
readonly messages: {
|
|
93
|
+
enabled: boolean;
|
|
94
|
+
assistantPrefix: boolean;
|
|
95
|
+
specialBlocks: boolean;
|
|
96
|
+
hideThinkingLabel: boolean;
|
|
97
|
+
hideInterimText: boolean;
|
|
98
|
+
};
|
|
85
99
|
readonly tools: {
|
|
86
100
|
enabled: boolean;
|
|
87
101
|
style: string;
|
|
@@ -89,6 +103,8 @@ export interface NormalizedPiStyleConfig {
|
|
|
89
103
|
maxExpandedLines: number;
|
|
90
104
|
dimOutput: boolean;
|
|
91
105
|
showElapsed: boolean;
|
|
106
|
+
/** Collapse a completed turn's tool blocks into one summary line (ADR 0007). */
|
|
107
|
+
collapseAfterTurn: boolean;
|
|
92
108
|
};
|
|
93
109
|
readonly theme: {
|
|
94
110
|
nerdFonts: NerdFontsMode;
|
|
@@ -178,6 +178,8 @@ export type MessageDecorationSnapshot = Readonly<{
|
|
|
178
178
|
assistantEnabled: boolean;
|
|
179
179
|
/** Drop the hidden-thinking label row and its trailing spacer (zero-trace collapse). */
|
|
180
180
|
collapseHiddenThinking: boolean;
|
|
181
|
+
/** Hide the text of assistant messages that also carry tool calls (interim narration). */
|
|
182
|
+
hideInterimText: boolean;
|
|
181
183
|
}>;
|
|
182
184
|
|
|
183
185
|
export function decorateMessageRender(
|
|
@@ -188,6 +190,7 @@ export function decorateMessageRender(
|
|
|
188
190
|
assistantPrefix: "│ ",
|
|
189
191
|
assistantEnabled: true,
|
|
190
192
|
collapseHiddenThinking: false,
|
|
193
|
+
hideInterimText: false,
|
|
191
194
|
},
|
|
192
195
|
): unknown {
|
|
193
196
|
if (typeof original !== "function") return undefined;
|
|
@@ -207,6 +210,34 @@ function isSpacerChild(child: unknown): boolean {
|
|
|
207
210
|
return typeof (child as { setLines?: unknown } | undefined)?.setLines === "function";
|
|
208
211
|
}
|
|
209
212
|
|
|
213
|
+
/**
|
|
214
|
+
* The assistant TEXT Markdown block: `Markdown(text, pad, 0, theme, undefined,
|
|
215
|
+
* { transform })` — duck-typed by setText + options object + no defaultTextStyle.
|
|
216
|
+
* Thinking Markdown passes a `{ color, italic }` defaultTextStyle and is
|
|
217
|
+
* therefore excluded; plain Text components carry no `options`.
|
|
218
|
+
*/
|
|
219
|
+
function isInterimTextChild(child: unknown): boolean {
|
|
220
|
+
const candidate = child as { setText?: unknown; options?: unknown; defaultTextStyle?: unknown } | undefined;
|
|
221
|
+
return (
|
|
222
|
+
typeof candidate?.setText === "function" &&
|
|
223
|
+
candidate.options !== undefined &&
|
|
224
|
+
typeof candidate.options === "object" &&
|
|
225
|
+
candidate.defaultTextStyle === undefined
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Whether the message content includes a toolCall item (interim narration marker). */
|
|
230
|
+
function hasToolCallItems(message: unknown): boolean {
|
|
231
|
+
if (!message || typeof message !== "object") return false;
|
|
232
|
+
const content = (message as { content?: unknown }).content;
|
|
233
|
+
return (
|
|
234
|
+
Array.isArray(content) &&
|
|
235
|
+
content.some(
|
|
236
|
+
(item) => item !== null && typeof item === "object" && (item as { type?: unknown }).type === "toolCall",
|
|
237
|
+
)
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
210
241
|
/**
|
|
211
242
|
* The hidden-thinking placeholder: a Text (setCustomBgFn) whose ANSI-stripped
|
|
212
243
|
* rendered content is empty. Duck-typed on the public shape because
|
|
@@ -240,11 +271,11 @@ export function decorateMessageUpdate(
|
|
|
240
271
|
assistantPrefix: "│ ",
|
|
241
272
|
assistantEnabled: true,
|
|
242
273
|
collapseHiddenThinking: false,
|
|
274
|
+
hideInterimText: false,
|
|
243
275
|
},
|
|
244
276
|
): unknown {
|
|
245
277
|
if (typeof original !== "function") return undefined;
|
|
246
278
|
const result = Reflect.apply(original, instance, args);
|
|
247
|
-
if (!snapshot.collapseHiddenThinking) return result;
|
|
248
279
|
const target = instance as {
|
|
249
280
|
hideThinkingBlock?: boolean;
|
|
250
281
|
hiddenThinkingLabel?: string;
|
|
@@ -252,15 +283,32 @@ export function decorateMessageUpdate(
|
|
|
252
283
|
};
|
|
253
284
|
// Only meaningful when Pi renders the hidden-block label (hideThinkingBlock)
|
|
254
285
|
// and the extension has blanked that label out ("" — the zero-trace mode).
|
|
255
|
-
if (target.hideThinkingBlock
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
286
|
+
if (snapshot.collapseHiddenThinking && target.hideThinkingBlock === true && target.hiddenThinkingLabel === "") {
|
|
287
|
+
const children = target.contentContainer?.children;
|
|
288
|
+
if (children) {
|
|
289
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
290
|
+
if (!isBlankTextChild(children[index])) continue;
|
|
291
|
+
children.splice(index, 1);
|
|
292
|
+
// Drop the Spacer(1) the native layout appends after the thinking run when
|
|
293
|
+
// another visible block follows; the message keeps only its shared top padding.
|
|
294
|
+
if (isSpacerChild(children[index])) children.splice(index, 1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
// Interim narration: assistant messages that carry tool calls use their text
|
|
299
|
+
// only to narrate while working; the tool blocks tell the story. Hide the text
|
|
300
|
+
// so the feed shows the run's summary and the final answer. Deterministic per
|
|
301
|
+
// content — streaming, scroll-back, and resume behave identically. Errors and
|
|
302
|
+
// truncation notices are Text children and stay; if only spacers remain the
|
|
303
|
+
// message becomes zero-trace.
|
|
304
|
+
if (snapshot.hideInterimText && hasToolCallItems(args[0])) {
|
|
305
|
+
const children = target.contentContainer?.children;
|
|
306
|
+
if (children) {
|
|
307
|
+
for (let index = children.length - 1; index >= 0; index--) {
|
|
308
|
+
if (isInterimTextChild(children[index])) children.splice(index, 1);
|
|
309
|
+
}
|
|
310
|
+
if (children.every((child) => isSpacerChild(child))) children.length = 0;
|
|
311
|
+
}
|
|
264
312
|
}
|
|
265
313
|
return result;
|
|
266
314
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { Component } from "@earendil-works/pi-tui";
|
|
8
8
|
import type { BoxTheme } from "../../../shared/box.js";
|
|
9
9
|
import { bashTool } from "./bash.js";
|
|
10
|
-
import { closeActiveBatch, isBatchableTool } from "./batch.js";
|
|
10
|
+
import { closeActiveBatch, EMPTY_BATCH_COMPONENT, isBatchableTool } from "./batch.js";
|
|
11
11
|
import { editTool } from "./edit.js";
|
|
12
12
|
import { renderFallbackCall, renderFallbackResult } from "./fallback.js";
|
|
13
13
|
import { findTool } from "./find.js";
|
|
@@ -15,7 +15,16 @@ import { grepTool } from "./grep.js";
|
|
|
15
15
|
import { lsTool } from "./ls.js";
|
|
16
16
|
import { getQuickEditToolConfig, quickEditTool } from "./quick-edit.js";
|
|
17
17
|
import { readTool } from "./read.js";
|
|
18
|
+
import { getStateElapsedMs, getToolsRenderConfig } from "./session-config.js";
|
|
18
19
|
import type { BoxedToolContext, BoxedToolDefinition } from "./shared.js";
|
|
20
|
+
import {
|
|
21
|
+
emptyTurnResult,
|
|
22
|
+
getTurnEntry,
|
|
23
|
+
noteTurnMemberElapsed,
|
|
24
|
+
noteTurnMemberRender,
|
|
25
|
+
renderTurnSummaryCall,
|
|
26
|
+
type TurnState,
|
|
27
|
+
} from "./turn-summary.js";
|
|
19
28
|
import { writeTool } from "./write.js";
|
|
20
29
|
|
|
21
30
|
function quickEditToolFor(toolName: string): BoxedToolDefinition {
|
|
@@ -41,6 +50,18 @@ export function hasBoxedRenderer(toolName: unknown): boolean {
|
|
|
41
50
|
return typeof toolName === "string" && Object.hasOwn(REGISTRY, toolName);
|
|
42
51
|
}
|
|
43
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Turn-summary gate (ADR 0007): the member belongs to an ended turn, Pi's
|
|
55
|
+
* global tool-output state is collapsed, the surface is enabled, and the block
|
|
56
|
+
* itself is not an error (errors always stay visible).
|
|
57
|
+
*/
|
|
58
|
+
function collapsedTurnFor(toolCallId: string, expanded: boolean): TurnState | undefined {
|
|
59
|
+
if (expanded || !getToolsRenderConfig().collapseAfterTurn) return undefined;
|
|
60
|
+
const entry = getTurnEntry(toolCallId);
|
|
61
|
+
if (!entry?.turn.ended || entry.member.isError) return undefined;
|
|
62
|
+
return entry.turn;
|
|
63
|
+
}
|
|
64
|
+
|
|
44
65
|
export function renderBoxedToolCall(
|
|
45
66
|
toolName: unknown,
|
|
46
67
|
args: Record<string, unknown>,
|
|
@@ -50,6 +71,16 @@ export function renderBoxedToolCall(
|
|
|
50
71
|
// Any non-batchable tool call is a batch boundary: the next quiet call starts
|
|
51
72
|
// a fresh batch instead of joining the previous one.
|
|
52
73
|
if (!isBatchableTool(toolName)) closeActiveBatch();
|
|
74
|
+
const turn = collapsedTurnFor(context.toolCallId, context.expanded);
|
|
75
|
+
if (turn) {
|
|
76
|
+
if (turn.leaderId === context.toolCallId) return renderTurnSummaryCall(theme, turn);
|
|
77
|
+
// Same singleton the batch machinery uses: the decoration's hideBatchMember
|
|
78
|
+
// (identity-compared) removes the instance so members consume zero lines.
|
|
79
|
+
return EMPTY_BATCH_COMPONENT;
|
|
80
|
+
}
|
|
81
|
+
// Capture the component invalidate so the turn_end path can force this block
|
|
82
|
+
// to re-run the renderer selectors (pi only re-invokes them from updateDisplay).
|
|
83
|
+
noteTurnMemberRender(context.toolCallId, context.invalidate);
|
|
53
84
|
const tool = typeof toolName === "string" ? REGISTRY[toolName] : undefined;
|
|
54
85
|
if (tool) return tool.call(args, theme, context);
|
|
55
86
|
return renderFallbackCall(toolName, args, theme, context);
|
|
@@ -62,6 +93,15 @@ export function renderBoxedToolResult(
|
|
|
62
93
|
theme: BoxTheme,
|
|
63
94
|
context: BoxedToolContext,
|
|
64
95
|
): Component {
|
|
96
|
+
const turn = collapsedTurnFor(context.toolCallId, options.expanded);
|
|
97
|
+
if (turn) {
|
|
98
|
+
// Freeze the member's wall-clock elapsed into the registry once the turn
|
|
99
|
+
// collapsed (the value is already frozen by the renderer context state).
|
|
100
|
+
if (!options.isPartial) noteTurnMemberElapsed(context.toolCallId, getStateElapsedMs(context.state));
|
|
101
|
+
if (turn.leaderId === context.toolCallId) return emptyTurnResult();
|
|
102
|
+
return EMPTY_BATCH_COMPONENT;
|
|
103
|
+
}
|
|
104
|
+
noteTurnMemberRender(context.toolCallId, context.invalidate);
|
|
65
105
|
const tool = typeof toolName === "string" ? REGISTRY[toolName] : undefined;
|
|
66
106
|
if (tool) return tool.result(result, options, theme, context);
|
|
67
107
|
return renderFallbackResult(toolName, result, options, theme, context);
|
|
@@ -12,6 +12,8 @@ export interface ToolsRenderConfig {
|
|
|
12
12
|
batchOpenGlyph: string;
|
|
13
13
|
/** Nerd Font mode is active: file-type icons render in output trees. */
|
|
14
14
|
nerdFonts: boolean;
|
|
15
|
+
/** Collapse a completed turn's tool blocks into one summary line (ADR 0007). */
|
|
16
|
+
collapseAfterTurn: boolean;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
let sessionToolsConfig: ToolsRenderConfig = {
|
|
@@ -21,6 +23,7 @@ let sessionToolsConfig: ToolsRenderConfig = {
|
|
|
21
23
|
showElapsed: true,
|
|
22
24
|
batchOpenGlyph: "●",
|
|
23
25
|
nerdFonts: false,
|
|
26
|
+
collapseAfterTurn: true,
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
export function setToolsRenderConfig(config: Partial<ToolsRenderConfig>): void {
|