@quandev104/pi-style 0.1.4 → 0.1.5
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 +24 -0
- package/README.md +8 -4
- package/dist/extensions/pi-style.js +3789 -1278
- 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 +21 -5
- package/extension-src/pi-style/domain/config-presets.ts +1 -1
- package/extension-src/pi-style/domain/config-types.ts +7 -3
- package/extension-src/pi-style/domain/theme.ts +6 -1
- package/extension-src/pi-style/features/editor/index.ts +169 -27
- package/extension-src/pi-style/features/messages/index.ts +66 -0
- package/extension-src/pi-style/features/tools/bash-execution.ts +112 -0
- package/extension-src/pi-style/features/tools/boxed/bash.ts +154 -130
- package/extension-src/pi-style/features/tools/boxed/batch.ts +50 -27
- package/extension-src/pi-style/features/tools/boxed/command-shape.ts +136 -0
- package/extension-src/pi-style/features/tools/boxed/find.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/gh.ts +1012 -0
- package/extension-src/pi-style/features/tools/boxed/git.ts +1960 -0
- package/extension-src/pi-style/features/tools/boxed/grep.ts +2 -2
- package/extension-src/pi-style/features/tools/boxed/output-tree.ts +9 -10
- package/extension-src/pi-style/features/tools/boxed/read.ts +3 -3
- package/extension-src/pi-style/features/tools/boxed/write.ts +2 -1
- package/extension-src/pi-style/pi/compatibility-coordinator.ts +17 -0
- package/extension-src/pi-style/pi/compatibility-probe.ts +104 -10
- package/extension-src/pi-style/pi/compatibility-registry.ts +19 -3
- package/extension-src/pi-style/pi/index.ts +18 -1
- package/extension-src/pi-style/pi/session-coordinator.ts +24 -0
- package/extension-src/pi-style/shared/box.ts +8 -4
- package/extension-src/pi-style/shared/split-diff.ts +9 -9
- package/package.json +1 -1
- package/themes/titanium-light.json +82 -0
- package/themes/titanium.json +79 -0
- package/themes/.gitkeep +0 -0
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import type { Component } from "@earendil-works/pi-tui";
|
|
15
15
|
import { stripAnsi } from "../../../shared/ansi.js";
|
|
16
|
-
import { type BoxTheme, getTextOutput, shortenPath } from "../../../shared/box.js";
|
|
16
|
+
import { type BoxTheme, dimLine, getTextOutput, shortenPath } from "../../../shared/box.js";
|
|
17
17
|
import { safeTruncateToWidth } from "../../../shared/render-budget.js";
|
|
18
18
|
import {
|
|
19
19
|
type GrepMatch,
|
|
@@ -111,7 +111,7 @@ function renderErrorLines(theme: BoxTheme, errorText: string, width: number): st
|
|
|
111
111
|
.map((line) => line.trim())
|
|
112
112
|
.filter((line) => line.length > 0);
|
|
113
113
|
if (raw.length === 0) return [];
|
|
114
|
-
const prefix = `${TREE_INDENT}${
|
|
114
|
+
const prefix = `${TREE_INDENT}${dimLine("└─")} `;
|
|
115
115
|
const out = raw
|
|
116
116
|
.slice(0, GREP_ERROR_LINES)
|
|
117
117
|
.map((line) => safeTruncateToWidth(`${prefix}${theme.fg("error", line)}`, Math.max(1, width), "…"));
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// as one visual family.
|
|
21
21
|
|
|
22
22
|
import type { BoxTheme } from "../../../shared/box.js";
|
|
23
|
+
import { dimLine } from "../../../shared/box.js";
|
|
23
24
|
import { safeTruncateToWidth } from "../../../shared/render-budget.js";
|
|
24
25
|
|
|
25
26
|
/** Indent for top-level tree rows; matches the quiet-tool batch panel. */
|
|
@@ -251,11 +252,11 @@ export function renderOutputTree(
|
|
|
251
252
|
const lastIndex = visible.length - 1;
|
|
252
253
|
for (let i = 0; i < visible.length; i++) {
|
|
253
254
|
const branch = i < lastIndex || more > 0 ? "├─" : "└─";
|
|
254
|
-
const line = `${indent}${
|
|
255
|
+
const line = `${indent}${dimLine(branch)} ${theme.fg(entryColor, label(visible[i] ?? ""))}`;
|
|
255
256
|
out.push(safeTruncateToWidth(line, safeWidth, "…"));
|
|
256
257
|
}
|
|
257
258
|
if (more > 0) {
|
|
258
|
-
const line = `${indent}${
|
|
259
|
+
const line = `${indent}${dimLine("└─")} ${theme.fg("dim", `… ${more} more ${pluralForm(moreUnit, more)}`)}`;
|
|
259
260
|
out.push(safeTruncateToWidth(line, safeWidth, "…"));
|
|
260
261
|
}
|
|
261
262
|
return out;
|
|
@@ -274,7 +275,7 @@ function formatMatchRow(theme: BoxTheme, match: GrepMatch): string {
|
|
|
274
275
|
// Match rows render in the output text color (not primary) so they read like
|
|
275
276
|
// the matched code; only the file nodes carry the primary color.
|
|
276
277
|
const label = theme.fg("toolOutput", `*${match.line}`);
|
|
277
|
-
const sep =
|
|
278
|
+
const sep = dimLine("│");
|
|
278
279
|
return `${label}${sep} ${theme.fg("toolOutput", match.content)}`;
|
|
279
280
|
}
|
|
280
281
|
|
|
@@ -313,7 +314,7 @@ export function renderGrepTree(
|
|
|
313
314
|
if (singleFile) {
|
|
314
315
|
budget.forEach((match, index) => {
|
|
315
316
|
const isLast = index === totalVisible - 1 && !truncated;
|
|
316
|
-
push(`${indent}${
|
|
317
|
+
push(`${indent}${dimLine(isLast ? "└─" : "├─")} ${formatMatchRow(theme, match)}`);
|
|
317
318
|
});
|
|
318
319
|
} else {
|
|
319
320
|
// Walk the budget, tracking position within each file group so the file
|
|
@@ -323,7 +324,7 @@ export function renderGrepTree(
|
|
|
323
324
|
const group = groups[gi];
|
|
324
325
|
if (!group) continue;
|
|
325
326
|
const isLastGroup = gi === groups.length - 1;
|
|
326
|
-
const trunk = isLastGroup ? " " :
|
|
327
|
+
const trunk = isLastGroup ? " " : dimLine("│");
|
|
327
328
|
|
|
328
329
|
const visibleHere: GrepMatch[] = [];
|
|
329
330
|
for (const match of group.matches) {
|
|
@@ -336,22 +337,20 @@ export function renderGrepTree(
|
|
|
336
337
|
const groupIsLastRendered = shown >= totalVisible && !truncated;
|
|
337
338
|
const fileLabel = options.withIcons ? `${fileIcon(group.file)} ${group.file}` : group.file;
|
|
338
339
|
// File nodes use the primary (accent) color, matching read/ls/find paths.
|
|
339
|
-
push(`${indent}${
|
|
340
|
+
push(`${indent}${dimLine(groupIsLastRendered ? "└─" : "├─")} ${theme.fg("accent", fileLabel)}`);
|
|
340
341
|
|
|
341
342
|
visibleHere.forEach((match, index) => {
|
|
342
343
|
const isLastInGroup = index === visibleHere.length - 1;
|
|
343
344
|
const isLastOverall = groupIsLastRendered && isLastInGroup;
|
|
344
345
|
push(
|
|
345
|
-
`${indent}${trunk}${TREE_CHILD_INDENT}${
|
|
346
|
+
`${indent}${trunk}${TREE_CHILD_INDENT}${dimLine(isLastOverall ? "└─" : "├─")} ${formatMatchRow(theme, match)}`,
|
|
346
347
|
);
|
|
347
348
|
});
|
|
348
349
|
}
|
|
349
350
|
}
|
|
350
351
|
|
|
351
352
|
if (truncated) {
|
|
352
|
-
push(
|
|
353
|
-
`${indent}${theme.fg("borderMuted", "└─")} ${theme.fg("dim", `… ${remaining} more ${pluralForm("match", remaining)}`)}`,
|
|
354
|
-
);
|
|
353
|
+
push(`${indent}${dimLine("└─")} ${theme.fg("dim", `… ${remaining} more ${pluralForm("match", remaining)}`)}`);
|
|
355
354
|
}
|
|
356
355
|
return out;
|
|
357
356
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// Boxed read tool renderer
|
|
2
2
|
// (renderCall/renderResult only; no tool re-registration).
|
|
3
3
|
//
|
|
4
|
-
// Read calls render
|
|
5
|
-
// consecutive reads group into one panel (see
|
|
6
|
-
//
|
|
4
|
+
// Read calls render boxless: a lone read is a single inline line
|
|
5
|
+
// (`➔ Read <path>`), consecutive reads group into one tree panel (see
|
|
6
|
+
// batch.ts).
|
|
7
7
|
|
|
8
8
|
import { stripAnsi } from "../../../shared/ansi.js";
|
|
9
9
|
import { getTextOutput } from "../../../shared/box.js";
|
|
@@ -15,6 +15,7 @@ import { stripAnsi } from "../../../shared/ansi.js";
|
|
|
15
15
|
import {
|
|
16
16
|
type BoxTheme,
|
|
17
17
|
boxedToolWidthKey,
|
|
18
|
+
dimLine,
|
|
18
19
|
getTextOutput,
|
|
19
20
|
renderBoxedToolResult,
|
|
20
21
|
renderCompactBoxedToolCall,
|
|
@@ -62,7 +63,7 @@ function numberedPreviewLines(content: string): NumberedLine[] {
|
|
|
62
63
|
|
|
63
64
|
/** One boxed preview row: dim gutter + toolOutput content. */
|
|
64
65
|
function formatNumberedLine(theme: BoxTheme, line: NumberedLine): string {
|
|
65
|
-
return `${
|
|
66
|
+
return `${dimLine(`${line.number} `)}${theme.fg("toolOutput", line.content)}`;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/** Compact write box: path header, numbered content preview, metrics footer. */
|
|
@@ -125,6 +125,21 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
|
|
|
125
125
|
config,
|
|
126
126
|
});
|
|
127
127
|
const messagesEnabled = (assistantEnabled || specialBlocksEnabled) && config.messages.enabled;
|
|
128
|
+
// The hidden-thinking collapse is an assistant-message surface patch: it needs
|
|
129
|
+
// the assistant flag and `messages.hideThinkingLabel`, independent of the
|
|
130
|
+
// assistant prefix feature.
|
|
131
|
+
const thinkingCollapseEnabled = Boolean(
|
|
132
|
+
authorization.assistant &&
|
|
133
|
+
config.messages.enabled &&
|
|
134
|
+
config.messages.hideThinkingLabel &&
|
|
135
|
+
isTierCAuthorized({
|
|
136
|
+
certifiedHost,
|
|
137
|
+
coreFlag: authorization.core,
|
|
138
|
+
surfaceFlag: true,
|
|
139
|
+
surface: "messages",
|
|
140
|
+
config,
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
128
143
|
const toolsEnabled =
|
|
129
144
|
authorization.tools &&
|
|
130
145
|
isTierCAuthorized({ certifiedHost, coreFlag: authorization.core, surfaceFlag: true, surface: "tools", config });
|
|
@@ -137,6 +152,7 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
|
|
|
137
152
|
...config.messages,
|
|
138
153
|
enabled: messagesEnabled,
|
|
139
154
|
assistantPrefix: assistantEnabled,
|
|
155
|
+
hideThinkingLabel: thinkingCollapseEnabled,
|
|
140
156
|
specialBlocks: messagesEnabled && config.messages.specialBlocks && specialBlocksEnabled,
|
|
141
157
|
},
|
|
142
158
|
tools: {
|
|
@@ -147,6 +163,7 @@ export function createCompatibilityCoordinator(dispose = disposePiCompatibilityP
|
|
|
147
163
|
messageSnapshot: {
|
|
148
164
|
assistantPrefix: authorization.ascii ? "[assistant] " : "│ ",
|
|
149
165
|
assistantEnabled,
|
|
166
|
+
collapseHiddenThinking: thinkingCollapseEnabled,
|
|
150
167
|
},
|
|
151
168
|
toolSnapshot: {
|
|
152
169
|
callMarker: authorization.ascii ? "[tool] " : "[tool] ",
|
|
@@ -3,14 +3,20 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import {
|
|
5
5
|
AssistantMessageComponent,
|
|
6
|
+
BashExecutionComponent,
|
|
6
7
|
BranchSummaryMessageComponent,
|
|
7
8
|
CompactionSummaryMessageComponent,
|
|
8
9
|
CustomMessageComponent,
|
|
9
10
|
SkillInvocationMessageComponent,
|
|
10
11
|
ToolExecutionComponent,
|
|
11
12
|
} from "@earendil-works/pi-coding-agent";
|
|
12
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
decorateMessageRender,
|
|
15
|
+
decorateMessageUpdate,
|
|
16
|
+
type MessageDecorationSnapshot,
|
|
17
|
+
} from "../features/messages/index.js";
|
|
13
18
|
import { renderSpecialMessageBlock, type SpecialBlockSubtype } from "../features/messages/special-blocks.js";
|
|
19
|
+
import { renderBashExecutionBox } from "../features/tools/bash-execution.js";
|
|
14
20
|
import { createToolDecorationOwner } from "../features/tools/index.js";
|
|
15
21
|
import {
|
|
16
22
|
type CompatibilityRecord,
|
|
@@ -31,12 +37,14 @@ const reportStates = new WeakMap<
|
|
|
31
37
|
// fail closed on every unrecorded Pi build and never use module-load capture as trust.
|
|
32
38
|
export const TRUSTED_NATIVE_FINGERPRINTS: Readonly<Record<string, string>> = Object.freeze({
|
|
33
39
|
"native-assistant-message:render": "2a39243f",
|
|
40
|
+
"native-assistant-message:updateContent": "4a2f15ff",
|
|
34
41
|
"native-compaction-message:updateDisplay": "f8c44e78",
|
|
35
42
|
"native-branch-message:updateDisplay": "415d57b7",
|
|
36
43
|
"native-skill-message:updateDisplay": "48099ea6",
|
|
37
44
|
"native-custom-message:rebuild": "76ae2e3a",
|
|
38
45
|
"tool-call-renderer:getCallRenderer": "951ea0e0",
|
|
39
46
|
"tool-result-renderer:getResultRenderer": "8a25cd71",
|
|
47
|
+
"native-bash-execution:render": "a5b5abca",
|
|
40
48
|
});
|
|
41
49
|
|
|
42
50
|
export const CERTIFICATION_TABLE = Object.freeze({
|
|
@@ -80,6 +88,19 @@ export const CERTIFICATION_TABLE = Object.freeze({
|
|
|
80
88
|
adapterId: "tool-renderer-component-v1",
|
|
81
89
|
status: "certified" as const,
|
|
82
90
|
}),
|
|
91
|
+
"native-assistant-message:updateContent": Object.freeze({
|
|
92
|
+
feature: "messages",
|
|
93
|
+
subtype: "native-assistant-message",
|
|
94
|
+
target: AssistantMessageComponent.prototype,
|
|
95
|
+
method: "updateContent",
|
|
96
|
+
writable: true,
|
|
97
|
+
configurable: true,
|
|
98
|
+
name: "updateContent",
|
|
99
|
+
arity: 1,
|
|
100
|
+
fingerprint: TRUSTED_NATIVE_FINGERPRINTS["native-assistant-message:updateContent"],
|
|
101
|
+
adapterId: "message-thinking-collapse-v1",
|
|
102
|
+
status: "certified" as const,
|
|
103
|
+
}),
|
|
83
104
|
"native-compaction-message:updateDisplay": Object.freeze({
|
|
84
105
|
feature: "messages",
|
|
85
106
|
subtype: "native-compaction-message",
|
|
@@ -120,6 +141,23 @@ export const CERTIFICATION_TABLE = Object.freeze({
|
|
|
120
141
|
adapterId: "message-block-boxed-v1",
|
|
121
142
|
status: "certified" as const,
|
|
122
143
|
}),
|
|
144
|
+
"native-bash-execution:render": Object.freeze({
|
|
145
|
+
feature: "tools",
|
|
146
|
+
subtype: "native-bash-execution",
|
|
147
|
+
target: BashExecutionComponent.prototype,
|
|
148
|
+
method: "render",
|
|
149
|
+
writable: true,
|
|
150
|
+
configurable: true,
|
|
151
|
+
// The additive render patch is certified by the class constructor identity
|
|
152
|
+
// (name/arity/source fingerprint): the class defines no own `render`, so
|
|
153
|
+
// the installed own method is the only one and the inherited Container
|
|
154
|
+
// render is the native fallback.
|
|
155
|
+
name: "BashExecutionComponent",
|
|
156
|
+
arity: 2,
|
|
157
|
+
fingerprint: TRUSTED_NATIVE_FINGERPRINTS["native-bash-execution:render"],
|
|
158
|
+
adapterId: "bash-execution-box-v1",
|
|
159
|
+
status: "certified" as const,
|
|
160
|
+
}),
|
|
123
161
|
}),
|
|
124
162
|
});
|
|
125
163
|
|
|
@@ -187,6 +225,12 @@ export interface TargetSpec {
|
|
|
187
225
|
adapterId: string | undefined;
|
|
188
226
|
status: "certified" | "native-fallback";
|
|
189
227
|
fallbackReason?: string;
|
|
228
|
+
/** "add-method" installs a new own method; the class constructor fingerprint certifies the target. */
|
|
229
|
+
kind?: "method" | "add-method";
|
|
230
|
+
/** Function name to verify (defaults to `method`; additive patches verify the class constructor name). */
|
|
231
|
+
identityName?: string;
|
|
232
|
+
/** Expected arity (defaults to the standard per-method rule; additive patches verify the constructor arity). */
|
|
233
|
+
arity?: number;
|
|
190
234
|
}
|
|
191
235
|
|
|
192
236
|
export function fingerprint(value: unknown): string | undefined {
|
|
@@ -201,15 +245,33 @@ export function fingerprint(value: unknown): string | undefined {
|
|
|
201
245
|
|
|
202
246
|
function trustedNativeIdentity(spec: TargetSpec, piVersion: string | undefined): unknown {
|
|
203
247
|
if (piVersion !== TRUSTED_PI_VERSION) return undefined;
|
|
248
|
+
if (spec.kind === "add-method") {
|
|
249
|
+
// Additive install: the prototype must not already own the method (it is
|
|
250
|
+
// inherited), the class constructor identity must match the recorded build,
|
|
251
|
+
// and the inherited method becomes the native fallback for the delegate.
|
|
252
|
+
if (Object.getOwnPropertyDescriptor(spec.target, spec.method)) return undefined;
|
|
253
|
+
const ctor = Object.getOwnPropertyDescriptor(spec.target, "constructor")?.value;
|
|
254
|
+
const key = `${spec.subtype}:${spec.method}`;
|
|
255
|
+
if (
|
|
256
|
+
typeof ctor !== "function" ||
|
|
257
|
+
ctor.name !== (spec.identityName ?? spec.method) ||
|
|
258
|
+
ctor.length !== (spec.arity ?? 0) ||
|
|
259
|
+
fingerprint(ctor) !== TRUSTED_NATIVE_FINGERPRINTS[key]
|
|
260
|
+
)
|
|
261
|
+
return undefined;
|
|
262
|
+
const inherited = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(spec.target), spec.method)?.value;
|
|
263
|
+
return typeof inherited === "function" ? inherited : undefined;
|
|
264
|
+
}
|
|
204
265
|
const descriptor = Object.getOwnPropertyDescriptor(spec.target, spec.method);
|
|
205
266
|
const value = descriptor?.value;
|
|
206
267
|
const key = `${spec.subtype}:${spec.method}`;
|
|
268
|
+
const expectedArity = spec.arity ?? (spec.method === "render" || spec.method === "updateContent" ? 1 : 0);
|
|
207
269
|
if (
|
|
208
270
|
descriptor?.writable !== true ||
|
|
209
271
|
descriptor.configurable !== true ||
|
|
210
272
|
typeof value !== "function" ||
|
|
211
|
-
value.name !== spec.method ||
|
|
212
|
-
value.length !==
|
|
273
|
+
value.name !== (spec.identityName ?? spec.method) ||
|
|
274
|
+
value.length !== expectedArity ||
|
|
213
275
|
fingerprint(value) !== TRUSTED_NATIVE_FINGERPRINTS[key]
|
|
214
276
|
)
|
|
215
277
|
return undefined;
|
|
@@ -225,6 +287,14 @@ export const targetSpecs: readonly TargetSpec[] = [
|
|
|
225
287
|
adapterId: "message-prefix-osc133-v1",
|
|
226
288
|
status: "certified",
|
|
227
289
|
},
|
|
290
|
+
{
|
|
291
|
+
feature: "messages",
|
|
292
|
+
subtype: "native-assistant-message",
|
|
293
|
+
target: AssistantMessageComponent.prototype,
|
|
294
|
+
method: "updateContent",
|
|
295
|
+
adapterId: "message-thinking-collapse-v1",
|
|
296
|
+
status: "certified",
|
|
297
|
+
},
|
|
228
298
|
{
|
|
229
299
|
feature: "messages",
|
|
230
300
|
subtype: "native-compaction-message",
|
|
@@ -273,6 +343,17 @@ export const targetSpecs: readonly TargetSpec[] = [
|
|
|
273
343
|
adapterId: "tool-renderer-component-v1",
|
|
274
344
|
status: "certified",
|
|
275
345
|
},
|
|
346
|
+
{
|
|
347
|
+
feature: "tools",
|
|
348
|
+
subtype: "native-bash-execution",
|
|
349
|
+
target: BashExecutionComponent.prototype,
|
|
350
|
+
method: "render",
|
|
351
|
+
kind: "add-method",
|
|
352
|
+
identityName: "BashExecutionComponent",
|
|
353
|
+
arity: 2,
|
|
354
|
+
adapterId: "bash-execution-box-v1",
|
|
355
|
+
status: "certified",
|
|
356
|
+
},
|
|
276
357
|
];
|
|
277
358
|
|
|
278
359
|
export interface PiVersionResolution {
|
|
@@ -373,15 +454,18 @@ function versionInRange(version: string | undefined): boolean {
|
|
|
373
454
|
return version === TRUSTED_PI_VERSION;
|
|
374
455
|
}
|
|
375
456
|
|
|
376
|
-
function shape(
|
|
377
|
-
const descriptor = Object.getOwnPropertyDescriptor(target, method);
|
|
457
|
+
function shape(spec: TargetSpec): boolean {
|
|
458
|
+
const descriptor = Object.getOwnPropertyDescriptor(spec.target, spec.method);
|
|
459
|
+
// Additive installs need an unowned slot (the method is inherited); every
|
|
460
|
+
// other patch requires the native own writable/configurable method.
|
|
461
|
+
if (spec.kind === "add-method") return descriptor === undefined;
|
|
378
462
|
return typeof descriptor?.value === "function" && descriptor.writable === true && descriptor.configurable === true;
|
|
379
463
|
}
|
|
380
464
|
|
|
381
465
|
export interface CompatibilityProbeOptions {
|
|
382
466
|
markers?: Set<string>;
|
|
383
467
|
config?: Readonly<{
|
|
384
|
-
messages: { enabled: boolean; assistantPrefix: boolean; specialBlocks: boolean };
|
|
468
|
+
messages: { enabled: boolean; assistantPrefix: boolean; specialBlocks: boolean; hideThinkingLabel: boolean };
|
|
385
469
|
tools: { enabled: boolean; style: string; maxCollapsedLines: number; maxExpandedLines: number; dimOutput: boolean };
|
|
386
470
|
preset: string;
|
|
387
471
|
}>;
|
|
@@ -418,7 +502,7 @@ function createFallbackRecord(
|
|
|
418
502
|
|
|
419
503
|
function probeDiagnostic(spec: TargetSpec, piVersion: string | undefined, identity: unknown): string {
|
|
420
504
|
if (!versionInRange(piVersion)) return "Pi version is unknown or outside the recorded 0.83.0 support build";
|
|
421
|
-
if (!shape(spec
|
|
505
|
+
if (!shape(spec)) return "target method shape is not an own writable/configurable function";
|
|
422
506
|
if (identity === undefined) return "recorded 0.83.0 native fingerprint, name, or arity did not match";
|
|
423
507
|
return "exact native identity verified; certified guarded decoration enabled";
|
|
424
508
|
}
|
|
@@ -427,7 +511,9 @@ function surfaceDisabled(spec: TargetSpec, config: CompatibilityProbeOptions["co
|
|
|
427
511
|
if (!config) return false;
|
|
428
512
|
if (spec.feature === "tools") return !config.tools.enabled;
|
|
429
513
|
if (!config.messages.enabled) return true;
|
|
430
|
-
if (spec.subtype === "native-assistant-message") return !config.messages.assistantPrefix;
|
|
514
|
+
if (spec.subtype === "native-assistant-message" && spec.method === "render") return !config.messages.assistantPrefix;
|
|
515
|
+
if (spec.subtype === "native-assistant-message" && spec.method === "updateContent")
|
|
516
|
+
return !config.messages.hideThinkingLabel;
|
|
431
517
|
if (isSpecialBlock(spec)) return !config.messages.specialBlocks;
|
|
432
518
|
return true;
|
|
433
519
|
}
|
|
@@ -456,13 +542,19 @@ function probeSpec(options: {
|
|
|
456
542
|
method: spec.method,
|
|
457
543
|
piVersion: piVersion ?? "unknown",
|
|
458
544
|
versionRange: PI_VERSION_RANGE,
|
|
459
|
-
shape: identity !== undefined && versionInRange(piVersion) && shape(spec
|
|
545
|
+
shape: identity !== undefined && versionInRange(piVersion) && shape(spec),
|
|
460
546
|
generation,
|
|
461
547
|
expectedIdentity: identity,
|
|
462
548
|
hasExpectedIdentity: true,
|
|
463
549
|
diagnostic,
|
|
550
|
+
...(spec.kind ? { kind: spec.kind } : {}),
|
|
464
551
|
delegate: (original, target, args) => {
|
|
465
552
|
markers.add(`${spec.subtype}:delegated`);
|
|
553
|
+
if (spec.subtype === "native-bash-execution")
|
|
554
|
+
return (
|
|
555
|
+
renderBashExecutionBox(target, args) ??
|
|
556
|
+
Reflect.apply(original as (...values: unknown[]) => unknown, target, args)
|
|
557
|
+
);
|
|
466
558
|
if (spec.feature === "tools")
|
|
467
559
|
return (
|
|
468
560
|
toolOwner?.decorateToolRendererSelection(
|
|
@@ -472,8 +564,10 @@ function probeSpec(options: {
|
|
|
472
564
|
args,
|
|
473
565
|
) ?? Reflect.apply(original as (...values: unknown[]) => unknown, target, args)
|
|
474
566
|
);
|
|
475
|
-
if (spec.subtype === "native-assistant-message")
|
|
567
|
+
if (spec.subtype === "native-assistant-message") {
|
|
568
|
+
if (spec.method === "updateContent") return decorateMessageUpdate(original, target, args, messageSnapshot);
|
|
476
569
|
return decorateMessageRender(original, target, args, messageSnapshot);
|
|
570
|
+
}
|
|
477
571
|
return renderSpecialMessageBlock(spec.subtype as SpecialBlockSubtype, original, target, args);
|
|
478
572
|
},
|
|
479
573
|
});
|
|
@@ -5,7 +5,8 @@ export type CompatibilitySubtype =
|
|
|
5
5
|
| "native-skill-message"
|
|
6
6
|
| "native-custom-message"
|
|
7
7
|
| "tool-call-renderer"
|
|
8
|
-
| "tool-result-renderer"
|
|
8
|
+
| "tool-result-renderer"
|
|
9
|
+
| "native-bash-execution";
|
|
9
10
|
|
|
10
11
|
type CompatibilityShape = "supported" | "unsupported" | "conflict" | "installed" | "skipped";
|
|
11
12
|
|
|
@@ -171,6 +172,13 @@ function validateInstall(
|
|
|
171
172
|
if (!options.shape) return skippedResult(options, "unsupported", options.diagnostic ?? "unsupported shape", current);
|
|
172
173
|
const conflict = existing && activeConflict(existing, current, options.generation);
|
|
173
174
|
if (conflict) return conflict;
|
|
175
|
+
if (options.kind === "add-method") {
|
|
176
|
+
// Additive install: the slot must be unowned (the method is inherited); the
|
|
177
|
+
// delegate receives the inherited native function as its fallback identity.
|
|
178
|
+
if (current !== undefined || ownDescriptor !== undefined)
|
|
179
|
+
return skippedResult(options, "conflict", "target already owns the additive method", current);
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
174
182
|
if (typeof current !== "function")
|
|
175
183
|
return skippedResult(
|
|
176
184
|
options,
|
|
@@ -225,6 +233,8 @@ export function installDelegatingPatch(options: {
|
|
|
225
233
|
expectedIdentity?: unknown;
|
|
226
234
|
hasExpectedIdentity?: boolean;
|
|
227
235
|
diagnostic?: string | undefined;
|
|
236
|
+
/** "add-method" installs a new own method on the prototype (nothing may already own the slot); the delegate receives the inherited native function as the fallback. */
|
|
237
|
+
kind?: "method" | "add-method";
|
|
228
238
|
delegate: (original: unknown, thisArg: object, args: unknown[]) => unknown;
|
|
229
239
|
}): InstallResult {
|
|
230
240
|
const { target, method } = options;
|
|
@@ -235,7 +245,9 @@ export function installDelegatingPatch(options: {
|
|
|
235
245
|
const validation = validateInstall(options, current, records.get(method), inspection.descriptor);
|
|
236
246
|
if (validation) return validation;
|
|
237
247
|
const originalDescriptor = Object.getOwnPropertyDescriptor(target, method);
|
|
238
|
-
|
|
248
|
+
// Additive installs have no current owner; the captured inherited native
|
|
249
|
+
// function (expectedIdentity) is both the fallback and the delegate's original.
|
|
250
|
+
const originalIdentity = options.kind === "add-method" ? options.expectedIdentity : current;
|
|
239
251
|
let active = true;
|
|
240
252
|
const installed = function (this: object, ...args: unknown[]): unknown {
|
|
241
253
|
if (!active) return Reflect.apply(originalIdentity as (...values: unknown[]) => unknown, this, args);
|
|
@@ -260,7 +272,11 @@ export function installDelegatingPatch(options: {
|
|
|
260
272
|
};
|
|
261
273
|
try {
|
|
262
274
|
Object.defineProperty(installed, "__piStyleCompatibilityRecord", { value: record, configurable: false });
|
|
263
|
-
|
|
275
|
+
// Additive installs have no original descriptor; write a fresh writable,
|
|
276
|
+
// configurable, non-enumerable own method so the slot stays reversible.
|
|
277
|
+
const descriptor = originalDescriptor
|
|
278
|
+
? { ...originalDescriptor, value: installed }
|
|
279
|
+
: { value: installed, writable: true, enumerable: false, configurable: true };
|
|
264
280
|
const wrote = Reflect.defineProperty(target, method, descriptor);
|
|
265
281
|
registryTestHooks.afterWrite?.();
|
|
266
282
|
const currentDescriptor = Object.getOwnPropertyDescriptor(target, method);
|
|
@@ -66,7 +66,24 @@ export default function piStyleExtension(pi: ExtensionAPI): void {
|
|
|
66
66
|
await coordinator.start(event, ctx);
|
|
67
67
|
});
|
|
68
68
|
pi.on("agent_start", () => coordinator.app.runtime.current?.dismissStartup());
|
|
69
|
-
pi.on("input", () =>
|
|
69
|
+
pi.on("input", (event, _ctx) => {
|
|
70
|
+
coordinator.app.runtime.current?.dismissStartup();
|
|
71
|
+
// Bare `!`/`!!` submit guard: Pi treats `!`-prefixed input as a direct bash
|
|
72
|
+
// command but falls through to normal message submission when the bang has
|
|
73
|
+
// no command after it — sending a literal `!` to the agent. Drop those
|
|
74
|
+
// accidental submits instead; Pi's submit path already cleared the editor
|
|
75
|
+
// (onChange("") resets isBashMode), so the input returns to the normal
|
|
76
|
+
// prompt without sending anything. Only the interactive input box is
|
|
77
|
+
// guarded; rpc/extension sources keep sending text verbatim.
|
|
78
|
+
if (event.source === "interactive") {
|
|
79
|
+
const trimmed = event.text.trimStart();
|
|
80
|
+
if (trimmed.startsWith("!")) {
|
|
81
|
+
const bangLength = trimmed.startsWith("!!") ? 2 : 1;
|
|
82
|
+
if (trimmed.slice(bangLength).trim() === "") return { action: "handled" };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
});
|
|
70
87
|
pi.on("tool_execution_start", () => coordinator.app.runtime.current?.dismissStartup());
|
|
71
88
|
pi.on("model_select", (event) =>
|
|
72
89
|
coordinator.app.update(
|
|
@@ -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 { setSpecialBlockTheme } from "../features/messages/special-blocks.js";
|
|
7
|
+
import { setBashExecutionTheme } from "../features/tools/bash-execution.js";
|
|
7
8
|
import { resetBashTreeRegistry } from "../features/tools/boxed/bash.js";
|
|
8
9
|
import { resetBatchRegistry } from "../features/tools/boxed/batch.js";
|
|
9
10
|
import { resetGrepRegistry } from "../features/tools/boxed/grep.js";
|
|
@@ -94,6 +95,26 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
94
95
|
const applyMessagesConfig = (config: import("../domain/config-types.js").NormalizedPiStyleConfig) => {
|
|
95
96
|
sessionUi?.setHiddenThinkingLabel?.(config.messages.hideThinkingLabel ? "" : undefined);
|
|
96
97
|
};
|
|
98
|
+
/**
|
|
99
|
+
* Auto-apply the configured pi-style theme (default "titanium") once per TUI
|
|
100
|
+
* session before any surface captures the active theme, so a fresh install
|
|
101
|
+
* renders with the intended palette. Failure-safe: an unresolvable target is
|
|
102
|
+
* never passed to Pi (its setTheme falls back to the dark theme on load
|
|
103
|
+
* error, which would clobber the user's theme), and "off" disables the
|
|
104
|
+
* surface for users who keep their own theme.
|
|
105
|
+
*/
|
|
106
|
+
const applyAutoTheme = (
|
|
107
|
+
config: import("../domain/config-types.js").NormalizedPiStyleConfig,
|
|
108
|
+
ctx: ExtensionContext,
|
|
109
|
+
) => {
|
|
110
|
+
const target = config.theme.autoApply;
|
|
111
|
+
if (ctx.mode !== "tui" || !target || target === "off") return;
|
|
112
|
+
const ui = ctx.ui;
|
|
113
|
+
if (ui?.theme?.name === target) return;
|
|
114
|
+
// Resolve before switching (see failure-safe note above).
|
|
115
|
+
if (!ui?.getTheme?.(target)) return;
|
|
116
|
+
ui.setTheme?.(target);
|
|
117
|
+
};
|
|
97
118
|
const app: PiStyleApp = createPiStyleApp(
|
|
98
119
|
undefined,
|
|
99
120
|
{
|
|
@@ -162,6 +183,8 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
162
183
|
productGate = app.productPolicy.corePatchGate;
|
|
163
184
|
active = true;
|
|
164
185
|
compatibility.install(app.config, ctx.mode === "tui", productGate);
|
|
186
|
+
// Auto-apply the configured theme before surfaces capture the active one.
|
|
187
|
+
applyAutoTheme(app.config, ctx);
|
|
165
188
|
// Session-scoped render configuration for the boxed tool/message surfaces.
|
|
166
189
|
// Populated once per session (never inside render).
|
|
167
190
|
sessionTheme = ctx.ui?.theme as never;
|
|
@@ -169,6 +192,7 @@ export function createPiStyleSessionCoordinator(pi: ExtensionAPI, hooks: Compati
|
|
|
169
192
|
applyToolsRenderConfig(app.config);
|
|
170
193
|
applyMessagesConfig(app.config);
|
|
171
194
|
if (ctx.ui?.theme) setSpecialBlockTheme(ctx.ui.theme as never);
|
|
195
|
+
if (ctx.ui?.theme) setBashExecutionTheme(ctx.ui.theme as never);
|
|
172
196
|
const toolDetails = collectToolDetails(pi.getActiveTools?.(), pi.getAllTools?.());
|
|
173
197
|
app.sessionStart(
|
|
174
198
|
{
|
|
@@ -319,8 +319,6 @@ export function formatToolParamLines(args: unknown, theme?: BoxTheme): string[]
|
|
|
319
319
|
return lines;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
-
const RESET_INTENSITY = "\x1b[22m";
|
|
323
|
-
|
|
324
322
|
function colorFromExtra(theme: BoxTheme, extraKey: string, fallbackColor: string, text: string): string {
|
|
325
323
|
const color = getThemeExtra(theme, extraKey);
|
|
326
324
|
if (color) {
|
|
@@ -380,11 +378,17 @@ export function formatBoxedRunningStatus(theme: BoxTheme, elapsedMs: number | un
|
|
|
380
378
|
return `${theme.fg("dim", "◌ Running")}${elapsed}`;
|
|
381
379
|
}
|
|
382
380
|
|
|
381
|
+
/** Structural line — box frame, tree branch, divider, gutter — wrapped in
|
|
382
|
+
* dim terminal-default intensity. Visible in every theme; matches omp. */
|
|
383
|
+
export function dimLine(text: string): string {
|
|
384
|
+
return `\x1b[2m${text}\x1b[22m`;
|
|
385
|
+
}
|
|
386
|
+
|
|
383
387
|
function boxText(theme: BoxTheme, text: string): string {
|
|
384
|
-
return
|
|
388
|
+
return dimLine(text);
|
|
385
389
|
}
|
|
386
390
|
function boxFrameText(theme: BoxTheme, text: string): string {
|
|
387
|
-
return
|
|
391
|
+
return dimLine(text);
|
|
388
392
|
}
|
|
389
393
|
|
|
390
394
|
export function boxedToolBgName(isError?: boolean, isPartial?: boolean): string {
|
|
@@ -7,6 +7,7 @@ import { highlightCode } from "@earendil-works/pi-coding-agent";
|
|
|
7
7
|
import type { Component } from "@earendil-works/pi-tui";
|
|
8
8
|
|
|
9
9
|
import { stripAnsi } from "./ansi.js";
|
|
10
|
+
import { dimLine } from "./box.js";
|
|
10
11
|
import { safeTruncateToWidth, safeVisibleWidth } from "./render-budget.js";
|
|
11
12
|
|
|
12
13
|
// ── Types ──────────────────────────────────────────────────────────
|
|
@@ -761,7 +762,7 @@ class SplitDiffRenderer {
|
|
|
761
762
|
lineKind === "add" ? "toolDiffAdded" : lineKind === "remove" ? "toolDiffRemoved" : "borderMuted";
|
|
762
763
|
const marker = this.ctx.fg(markerColor, markerChar);
|
|
763
764
|
const lineNumber = this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth));
|
|
764
|
-
const divider =
|
|
765
|
+
const divider = dimLine(" │ ");
|
|
765
766
|
const prefix = `${marker} ${lineNumber}${divider}`;
|
|
766
767
|
const prefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} │ `;
|
|
767
768
|
const tailWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
|
|
@@ -791,14 +792,14 @@ class SplitDiffRenderer {
|
|
|
791
792
|
this.ctx.fg(markerColor, markerChar) +
|
|
792
793
|
" " +
|
|
793
794
|
this.ctx.fg(this.getNumberColor(lineKind), lineNumber) +
|
|
794
|
-
|
|
795
|
+
dimLine(" │ ");
|
|
795
796
|
const firstPrefixPlain = `${markerChar} ${lineNumber} │ `;
|
|
796
797
|
|
|
797
798
|
const contPrefixAnsi =
|
|
798
799
|
this.ctx.fg(markerColor, markerChar) +
|
|
799
800
|
" " +
|
|
800
801
|
this.ctx.fg("dim", " ".repeat(this.ctx.lineNumberWidth)) +
|
|
801
|
-
|
|
802
|
+
dimLine(" │ ");
|
|
802
803
|
const contPrefixPlain = `${markerChar} ${" ".repeat(this.ctx.lineNumberWidth)} │ `;
|
|
803
804
|
|
|
804
805
|
const codeWidth = Math.max(1, columnWidth - safeVisibleWidth(firstPrefixPlain));
|
|
@@ -865,7 +866,7 @@ class SplitDiffRenderer {
|
|
|
865
866
|
|
|
866
867
|
render(width: number): string[] {
|
|
867
868
|
const safeWidth = Math.max(20, width);
|
|
868
|
-
const columnSeparator =
|
|
869
|
+
const columnSeparator = dimLine(" │ ");
|
|
869
870
|
const separatorWidth = safeVisibleWidth(stripAnsi(columnSeparator));
|
|
870
871
|
const leftWidth = Math.max(20, Math.floor((safeWidth - separatorWidth) / 2));
|
|
871
872
|
const rightWidth = Math.max(20, safeWidth - separatorWidth - leftWidth);
|
|
@@ -877,15 +878,14 @@ class SplitDiffRenderer {
|
|
|
877
878
|
if (dividerIndex >= 0 && dividerIndex < chars.length) {
|
|
878
879
|
chars[dividerIndex] = junction;
|
|
879
880
|
}
|
|
880
|
-
return
|
|
881
|
+
return dimLine(chars.join(""));
|
|
881
882
|
};
|
|
882
883
|
|
|
883
884
|
const formatHeaderCell = (label: string, columnWidth: number): string => {
|
|
884
885
|
// Keep marker+space columns, then place label inside the line-number column.
|
|
885
886
|
const markerPad = " ";
|
|
886
887
|
const lineNumberLabel = fitToWidth(label, this.ctx.lineNumberWidth);
|
|
887
|
-
const prefixAnsi =
|
|
888
|
-
this.ctx.fg("borderMuted", markerPad) + this.ctx.fg("dim", lineNumberLabel) + this.ctx.fg("borderMuted", " │ ");
|
|
888
|
+
const prefixAnsi = dimLine(markerPad) + this.ctx.fg("dim", lineNumberLabel) + dimLine(" │ ");
|
|
889
889
|
const prefixPlain = `${markerPad}${stripAnsi(lineNumberLabel)} │ `;
|
|
890
890
|
const codeWidth = Math.max(0, columnWidth - safeVisibleWidth(prefixPlain));
|
|
891
891
|
return padRenderedLineWidth(prefixAnsi + " ".repeat(codeWidth), columnWidth);
|
|
@@ -894,7 +894,7 @@ class SplitDiffRenderer {
|
|
|
894
894
|
const lines: string[] = [];
|
|
895
895
|
lines.push(
|
|
896
896
|
padRenderedLineWidth(
|
|
897
|
-
formatBorderCell(leftWidth, "┬") +
|
|
897
|
+
formatBorderCell(leftWidth, "┬") + dimLine("─┬─") + formatBorderCell(rightWidth, "┬"),
|
|
898
898
|
safeWidth,
|
|
899
899
|
),
|
|
900
900
|
);
|
|
@@ -931,7 +931,7 @@ class SplitDiffRenderer {
|
|
|
931
931
|
|
|
932
932
|
lines.push(
|
|
933
933
|
padRenderedLineWidth(
|
|
934
|
-
formatBorderCell(leftWidth, "┴") +
|
|
934
|
+
formatBorderCell(leftWidth, "┴") + dimLine("─┴─") + formatBorderCell(rightWidth, "┴"),
|
|
935
935
|
safeWidth,
|
|
936
936
|
),
|
|
937
937
|
);
|