pi-cc-extensions 0.7.11 → 0.8.1
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/README.md +23 -9
- package/assets/readme/welcome.webp +0 -0
- package/extensions/claude-code-style.ts +320 -156
- package/extensions/compact-style.ts +1143 -0
- package/extensions/tool-result-sanitize.ts +17 -0
- package/package.json +4 -3
|
@@ -1,56 +1,75 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import {
|
|
3
|
+
DynamicBorder,
|
|
3
4
|
keyHint,
|
|
4
|
-
getSettingsListTheme,
|
|
5
5
|
ToolExecutionComponent,
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
installCompactStyle,
|
|
9
|
+
type CompactStyleHooks,
|
|
10
|
+
type CompactStyleMode,
|
|
11
|
+
} from "./compact-style.ts";
|
|
12
|
+
import { sanitizeToolResultText } from "./tool-result-sanitize.ts";
|
|
13
|
+
import { Container, SelectList, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
8
14
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
15
|
import { homedir } from "node:os";
|
|
10
16
|
import { join } from "node:path";
|
|
11
17
|
|
|
12
18
|
/**
|
|
13
|
-
* Claude Code Style for pi
|
|
14
|
-
*
|
|
15
|
-
* Tool calls and results use a compact fallback renderer by default. Renderers
|
|
16
|
-
* listed in excludeRenderers remain untouched. A collapsed tool result can be
|
|
17
|
-
* clicked on its Ctrl+O hint to expand only that tool.
|
|
18
|
-
*
|
|
19
|
-
* Dynamic commands:
|
|
20
|
-
* /ccstyle open interactive settings
|
|
21
|
-
* /ccstyle on enable
|
|
22
|
-
* /ccstyle off disable
|
|
23
|
-
* /ccstyle status show current state
|
|
19
|
+
* Claude Code Style for pi.
|
|
24
20
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
* This is the package's only entry point. Compact transcript rendering lives in
|
|
22
|
+
* the internal compact-style module and is routed by the mode below.
|
|
27
23
|
*/
|
|
28
24
|
|
|
29
|
-
type Config = {
|
|
30
|
-
|
|
25
|
+
export type Config = {
|
|
26
|
+
mode: CompactStyleMode;
|
|
31
27
|
excludeRenderers: string[];
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
const CONFIG_PATH = join(homedir(), ".pi", "agent", "claude-code-style.json");
|
|
35
31
|
|
|
32
|
+
export function normalizeConfig(input: unknown): Config {
|
|
33
|
+
const source = (input && typeof input === "object" ? input : {}) as Record<string, unknown>;
|
|
34
|
+
const mode = source.mode;
|
|
35
|
+
const migratedMode: CompactStyleMode =
|
|
36
|
+
mode === "on" || mode === "off" || mode === "compact"
|
|
37
|
+
? mode
|
|
38
|
+
: typeof source.enabled === "boolean"
|
|
39
|
+
? (source.enabled ? "on" : "off")
|
|
40
|
+
: "on";
|
|
41
|
+
const excludeRenderers = Array.isArray(source.excludeRenderers)
|
|
42
|
+
? [...new Set(source.excludeRenderers.filter((name): name is string => typeof name === "string" && name.length > 0))]
|
|
43
|
+
: [];
|
|
44
|
+
return { mode: migratedMode, excludeRenderers };
|
|
45
|
+
}
|
|
46
|
+
|
|
36
47
|
let config: Config = loadConfig();
|
|
37
48
|
|
|
38
49
|
function loadConfig(): Config {
|
|
39
50
|
try {
|
|
40
51
|
if (existsSync(CONFIG_PATH)) {
|
|
41
|
-
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as unknown;
|
|
53
|
+
const normalized = normalizeConfig(parsed);
|
|
54
|
+
const source = parsed as Record<string, unknown>;
|
|
55
|
+
// Persist the one-time enabled:boolean -> mode migration while retaining
|
|
56
|
+
// the existing exclusion list.
|
|
57
|
+
if (
|
|
58
|
+
typeof source.enabled === "boolean"
|
|
59
|
+
&& (source.mode !== "on" && source.mode !== "off" && source.mode !== "compact")
|
|
60
|
+
) {
|
|
61
|
+
try {
|
|
62
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(normalized, null, 2));
|
|
63
|
+
} catch {
|
|
64
|
+
// A read-only config still uses the migrated in-memory value.
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return normalized;
|
|
49
68
|
}
|
|
50
69
|
} catch {
|
|
51
70
|
// Ignore bad config and fall back to defaults.
|
|
52
71
|
}
|
|
53
|
-
return {
|
|
72
|
+
return { mode: "on", excludeRenderers: [] };
|
|
54
73
|
}
|
|
55
74
|
|
|
56
75
|
function saveConfig() {
|
|
@@ -64,24 +83,6 @@ function oneLine(value: unknown, max = 72): string {
|
|
|
64
83
|
return text.length > max ? `${text.slice(0, max - 1)}…` : text;
|
|
65
84
|
}
|
|
66
85
|
|
|
67
|
-
const UNSAFE_TERMINAL_ESCAPE = new RegExp(
|
|
68
|
-
"\\u001B\\][\\s\\S]*?(?:\\u0007|\\u001B\\x5C)"
|
|
69
|
-
+ "|\\u001B[PX^_][\\s\\S]*?\\u001B\\x5C"
|
|
70
|
-
+ "|(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]"
|
|
71
|
-
+ "|\\u001B[@-_]",
|
|
72
|
-
"g",
|
|
73
|
-
);
|
|
74
|
-
|
|
75
|
-
/** Prevent captured terminal control responses from being replayed by tool renderers. */
|
|
76
|
-
function sanitizeToolResultText(value: string): string {
|
|
77
|
-
return value
|
|
78
|
-
.replace(UNSAFE_TERMINAL_ESCAPE, "")
|
|
79
|
-
.replace(/\x1B/g, "")
|
|
80
|
-
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "")
|
|
81
|
-
.replace(/\r\n/g, "\n")
|
|
82
|
-
.replace(/\r/g, "\n");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
86
|
function textFromResult(result: any): string {
|
|
86
87
|
const item = result?.content?.find?.((c: any) => c.type === "text");
|
|
87
88
|
return item?.type === "text" ? sanitizeToolResultText(item.text ?? "") : "";
|
|
@@ -253,7 +254,7 @@ export class ExpandedToolResultText {
|
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
function renderCollapsedToolResult(body: string, collapsedHint = ""): string {
|
|
256
|
-
return
|
|
257
|
+
return `\n ⎿ ${body}${collapsedHint}`;
|
|
257
258
|
}
|
|
258
259
|
|
|
259
260
|
function renderExpandedToolResult(
|
|
@@ -938,49 +939,57 @@ function scheduleSessionRender(): void {
|
|
|
938
939
|
const BRIGHT_GREEN = "\x1b[38;2;80;220;100m";
|
|
939
940
|
const ANSI_RESET = "\x1b[0m";
|
|
940
941
|
|
|
941
|
-
function
|
|
942
|
-
|
|
942
|
+
function refreshCurrentTranscript(compactStyle: CompactStyleHooks, ctx?: any): void {
|
|
943
|
+
compactStyle.refresh();
|
|
944
|
+
toolMouseTui?.requestRender?.(true);
|
|
945
|
+
ctx?.ui?.requestRender?.(true);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
function applyStyleMode(mode: CompactStyleMode, ctx: any, compactStyle: CompactStyleHooks): void {
|
|
949
|
+
config.mode = mode;
|
|
943
950
|
saveConfig();
|
|
944
|
-
|
|
951
|
+
refreshCurrentTranscript(compactStyle, ctx);
|
|
952
|
+
ctx.ui.notify(`Claude Code style: ${mode}`, "info");
|
|
945
953
|
}
|
|
946
954
|
|
|
947
|
-
async function showCcstylePanel(ctx: any): Promise<void> {
|
|
955
|
+
async function showCcstylePanel(ctx: any, compactStyle: CompactStyleHooks): Promise<void> {
|
|
948
956
|
if (ctx?.mode !== "tui" || !ctx?.hasUI || typeof ctx.ui?.custom !== "function") {
|
|
949
957
|
ctx.ui?.notify?.("/ccstyle requires TUI mode", "warning");
|
|
950
958
|
return;
|
|
951
959
|
}
|
|
952
960
|
|
|
953
|
-
|
|
961
|
+
const modes: Array<{ value: CompactStyleMode; label: string; description: string }> = [
|
|
962
|
+
{ value: "on", label: "on", description: "Claude Code style" },
|
|
963
|
+
{ value: "off", label: "off", description: "Pi native output" },
|
|
964
|
+
{ value: "compact", label: "compact", description: "Compact transcript" },
|
|
965
|
+
];
|
|
966
|
+
const selectedMode = await (ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: (value?: CompactStyleMode) => void) => {
|
|
954
967
|
const container = new Container();
|
|
955
|
-
container.addChild(new
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
tui.requestRender();
|
|
971
|
-
},
|
|
972
|
-
() => done(),
|
|
973
|
-
);
|
|
974
|
-
container.addChild(settingsList);
|
|
968
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
969
|
+
container.addChild(new Text(theme.fg("accent", theme.bold("Claude Code Style")), 1, 0));
|
|
970
|
+
const selectList = new SelectList(modes, modes.length, {
|
|
971
|
+
selectedPrefix: (text: string) => theme.fg("accent", text),
|
|
972
|
+
selectedText: (text: string) => theme.fg("accent", text),
|
|
973
|
+
description: (text: string) => theme.fg("muted", text),
|
|
974
|
+
scrollInfo: (text: string) => theme.fg("dim", text),
|
|
975
|
+
noMatch: (text: string) => theme.fg("warning", text),
|
|
976
|
+
});
|
|
977
|
+
selectList.setSelectedIndex(modes.findIndex((item) => item.value === config.mode));
|
|
978
|
+
selectList.onSelect = (item: { value: string }) => done(item.value as CompactStyleMode);
|
|
979
|
+
selectList.onCancel = () => done();
|
|
980
|
+
container.addChild(selectList);
|
|
981
|
+
container.addChild(new Text(theme.fg("dim", "↑↓ navigate · enter select · esc cancel"), 1, 0));
|
|
982
|
+
container.addChild(new DynamicBorder((text: string) => theme.fg("accent", text)));
|
|
975
983
|
return {
|
|
976
984
|
render: (width: number) => container.render(width),
|
|
977
985
|
invalidate: () => container.invalidate(),
|
|
978
986
|
handleInput(data: string) {
|
|
979
|
-
|
|
987
|
+
selectList.handleInput(data);
|
|
980
988
|
tui.requestRender();
|
|
981
989
|
},
|
|
982
990
|
};
|
|
983
|
-
});
|
|
991
|
+
}) as Promise<CompactStyleMode | undefined>);
|
|
992
|
+
if (selectedMode) applyStyleMode(selectedMode, ctx, compactStyle);
|
|
984
993
|
}
|
|
985
994
|
|
|
986
995
|
function renderDefault(tool: any, slot: "renderCall" | "renderResult", args: any[], fallback = "") {
|
|
@@ -1027,7 +1036,7 @@ function createCcstyleTool(originalTool: any): any {
|
|
|
1027
1036
|
...originalTool,
|
|
1028
1037
|
renderShell: "self",
|
|
1029
1038
|
renderCall(args: any, theme: any, context: any) {
|
|
1030
|
-
if (
|
|
1039
|
+
if (config.mode !== "on") {
|
|
1031
1040
|
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
1032
1041
|
}
|
|
1033
1042
|
|
|
@@ -1046,7 +1055,7 @@ function createCcstyleTool(originalTool: any): any {
|
|
|
1046
1055
|
return new Text(title, 0, 0);
|
|
1047
1056
|
},
|
|
1048
1057
|
renderResult(result: any, options: any, theme: any, context: any) {
|
|
1049
|
-
if (
|
|
1058
|
+
if (config.mode !== "on") {
|
|
1050
1059
|
return renderDefault(originalTool, "renderResult", [result, options, theme, context], textFromResult(result));
|
|
1051
1060
|
}
|
|
1052
1061
|
|
|
@@ -1082,19 +1091,31 @@ const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch
|
|
|
1082
1091
|
const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
|
|
1083
1092
|
const COMPONENT_TOOL_SELF_SHELL_MODE = Symbol.for("pi.ccstyle.component-tool-self-shell-mode");
|
|
1084
1093
|
const SUBAGENT_TOOL_NAMES = new Set(["Agent"]);
|
|
1085
|
-
|
|
1094
|
+
|
|
1095
|
+
type ToolRenderMethods = {
|
|
1096
|
+
hasRendererDefinition: (...args: any[]) => boolean;
|
|
1097
|
+
getRenderShell: (...args: any[]) => "default" | "self";
|
|
1098
|
+
getCallRenderer: (...args: any[]) => any;
|
|
1099
|
+
getResultRenderer: (...args: any[]) => any;
|
|
1100
|
+
};
|
|
1086
1101
|
|
|
1087
1102
|
type GlobalToolRenderPatch = {
|
|
1103
|
+
version: 2;
|
|
1088
1104
|
prototype: any;
|
|
1089
1105
|
owner: object;
|
|
1106
|
+
active: boolean;
|
|
1090
1107
|
enabled: () => boolean;
|
|
1108
|
+
mode: () => CompactStyleMode;
|
|
1091
1109
|
wrap: (tool: any) => any;
|
|
1092
1110
|
byDefinition: WeakMap<object, any>;
|
|
1093
1111
|
byName: Map<string, any>;
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1112
|
+
downstream: ToolRenderMethods;
|
|
1113
|
+
installed: ToolRenderMethods;
|
|
1114
|
+
// Keep the legacy aliases so an older extension build can read this Symbol.
|
|
1115
|
+
originalHasRendererDefinition: ToolRenderMethods["hasRendererDefinition"];
|
|
1116
|
+
originalGetRenderShell: ToolRenderMethods["getRenderShell"];
|
|
1117
|
+
originalGetCallRenderer: ToolRenderMethods["getCallRenderer"];
|
|
1118
|
+
originalGetResultRenderer: ToolRenderMethods["getResultRenderer"];
|
|
1098
1119
|
};
|
|
1099
1120
|
|
|
1100
1121
|
function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
@@ -1118,33 +1139,43 @@ export function preservesOriginalRenderer(
|
|
|
1118
1139
|
);
|
|
1119
1140
|
}
|
|
1120
1141
|
|
|
1121
|
-
function
|
|
1122
|
-
const
|
|
1123
|
-
if (
|
|
1142
|
+
function syncToolShell(component: any, shell: "default" | "self"): void {
|
|
1143
|
+
const target = shell === "self" ? component.selfRenderContainer : component.contentBox;
|
|
1144
|
+
if (!target || !Array.isArray(component.children)) return;
|
|
1145
|
+
const candidates = new Set([component.contentText, component.contentBox, component.selfRenderContainer].filter(Boolean));
|
|
1146
|
+
const indexes = component.children
|
|
1147
|
+
.map((child: any, index: number) => candidates.has(child) ? index : -1)
|
|
1148
|
+
.filter((index: number) => index >= 0);
|
|
1149
|
+
const targetIndex = indexes[0];
|
|
1150
|
+
// During construction getRenderShell() runs immediately before Pi mounts the
|
|
1151
|
+
// selected shell. Do not mount it here or the constructor will add it twice.
|
|
1152
|
+
if (targetIndex === undefined) return;
|
|
1153
|
+
component.children[targetIndex] = target;
|
|
1154
|
+
for (const index of indexes.sort((left: number, right: number) => right - left)) {
|
|
1155
|
+
if (index !== targetIndex) component.children.splice(index, 1);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1124
1158
|
|
|
1159
|
+
function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch): boolean {
|
|
1125
1160
|
const extensionDefinition = component.toolDefinition;
|
|
1126
1161
|
const builtInDefinition = component.builtInToolDefinition;
|
|
1127
1162
|
const definition = extensionDefinition ?? builtInDefinition;
|
|
1128
1163
|
const toolName = String(component.toolName || definition?.name || "");
|
|
1129
1164
|
const useCcstyle =
|
|
1130
|
-
patch.
|
|
1165
|
+
patch.mode() === "on" &&
|
|
1131
1166
|
!preservesOriginalRenderer(extensionDefinition, toolName, builtInDefinition);
|
|
1132
|
-
// ToolExecutionComponent chooses its child shell in the constructor. Keep that
|
|
1133
|
-
// choice stable for this row so toggling ccstyle cannot switch containers later.
|
|
1134
1167
|
component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
|
|
1135
1168
|
return useCcstyle;
|
|
1136
1169
|
}
|
|
1137
1170
|
|
|
1138
1171
|
function shouldUseSelfShell(component: any, patch: GlobalToolRenderPatch): boolean {
|
|
1139
|
-
const selectedMode = component[COMPONENT_TOOL_SELF_SHELL_MODE];
|
|
1140
|
-
if (typeof selectedMode === "boolean") return selectedMode;
|
|
1141
|
-
|
|
1142
1172
|
const definition = component.toolDefinition ?? component.builtInToolDefinition;
|
|
1143
1173
|
const toolName = String(component.toolName || definition?.name || "");
|
|
1144
1174
|
const useSelfShell =
|
|
1145
|
-
patch.
|
|
1175
|
+
patch.mode() !== "off" &&
|
|
1146
1176
|
SUBAGENT_TOOL_NAMES.has(toolName) &&
|
|
1147
|
-
definition
|
|
1177
|
+
definition != null &&
|
|
1178
|
+
definition.renderShell === undefined;
|
|
1148
1179
|
component[COMPONENT_TOOL_SELF_SHELL_MODE] = useSelfShell;
|
|
1149
1180
|
return useSelfShell;
|
|
1150
1181
|
}
|
|
@@ -1169,61 +1200,171 @@ function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): an
|
|
|
1169
1200
|
return wrapped;
|
|
1170
1201
|
}
|
|
1171
1202
|
|
|
1172
|
-
function
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
owner: globalToolRenderOwner,
|
|
1181
|
-
enabled: () => config.enabled,
|
|
1182
|
-
wrap: createCcstyleTool,
|
|
1183
|
-
byDefinition: new WeakMap(),
|
|
1184
|
-
byName: new Map(),
|
|
1185
|
-
originalHasRendererDefinition: prototype.hasRendererDefinition,
|
|
1186
|
-
originalGetRenderShell: prototype.getRenderShell,
|
|
1187
|
-
originalGetCallRenderer: prototype.getCallRenderer,
|
|
1188
|
-
originalGetResultRenderer: prototype.getResultRenderer,
|
|
1189
|
-
};
|
|
1203
|
+
function prototypeToolRenderMethods(prototype: any): ToolRenderMethods {
|
|
1204
|
+
return {
|
|
1205
|
+
hasRendererDefinition: prototype.hasRendererDefinition,
|
|
1206
|
+
getRenderShell: prototype.getRenderShell,
|
|
1207
|
+
getCallRenderer: prototype.getCallRenderer,
|
|
1208
|
+
getResultRenderer: prototype.getResultRenderer,
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1190
1211
|
|
|
1191
|
-
|
|
1212
|
+
function isOwnershipAwarePatch(value: any): value is GlobalToolRenderPatch {
|
|
1213
|
+
if (!value || value.version !== 2 || !value.installed || !value.downstream) return false;
|
|
1214
|
+
return ["hasRendererDefinition", "getRenderShell", "getCallRenderer", "getResultRenderer"]
|
|
1215
|
+
.every((name) => typeof value.installed[name] === "function" && typeof value.downstream[name] === "function");
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function isLegacyInstalledWrapper(method: unknown, downstreamField: string): boolean {
|
|
1219
|
+
if (typeof method !== "function") return false;
|
|
1220
|
+
try {
|
|
1221
|
+
const source = Function.prototype.toString.call(method);
|
|
1222
|
+
return source.includes(downstreamField)
|
|
1223
|
+
&& (
|
|
1224
|
+
source.includes("shouldGloballyStyleTool")
|
|
1225
|
+
|| source.includes("shouldUseSelfShell")
|
|
1226
|
+
|| source.includes("getGloballyStyledTool")
|
|
1227
|
+
);
|
|
1228
|
+
} catch {
|
|
1229
|
+
return false;
|
|
1192
1230
|
}
|
|
1231
|
+
}
|
|
1193
1232
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1233
|
+
function downstreamForGlobalToolInstall(prototype: any, previous: any): ToolRenderMethods {
|
|
1234
|
+
const current = prototypeToolRenderMethods(prototype);
|
|
1235
|
+
if (!previous || previous.prototype !== prototype) return current;
|
|
1236
|
+
if (isOwnershipAwarePatch(previous)) {
|
|
1237
|
+
return {
|
|
1238
|
+
hasRendererDefinition: current.hasRendererDefinition === previous.installed.hasRendererDefinition
|
|
1239
|
+
? previous.downstream.hasRendererDefinition
|
|
1240
|
+
: current.hasRendererDefinition,
|
|
1241
|
+
getRenderShell: current.getRenderShell === previous.installed.getRenderShell
|
|
1242
|
+
? previous.downstream.getRenderShell
|
|
1243
|
+
: current.getRenderShell,
|
|
1244
|
+
getCallRenderer: current.getCallRenderer === previous.installed.getCallRenderer
|
|
1245
|
+
? previous.downstream.getCallRenderer
|
|
1246
|
+
: current.getCallRenderer,
|
|
1247
|
+
getResultRenderer: current.getResultRenderer === previous.installed.getResultRenderer
|
|
1248
|
+
? previous.downstream.getResultRenderer
|
|
1249
|
+
: current.getResultRenderer,
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
// Pre-v2 Symbol state did not retain wrapper references. Recognize its known
|
|
1254
|
+
// wrappers when possible; otherwise preserve the current method as external.
|
|
1255
|
+
const legacyDownstream = (method: Function, field: string): Function => {
|
|
1256
|
+
const saved = previous[field];
|
|
1257
|
+
return typeof saved === "function" && isLegacyInstalledWrapper(method, field) ? saved : method;
|
|
1214
1258
|
};
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1259
|
+
return {
|
|
1260
|
+
hasRendererDefinition: legacyDownstream(
|
|
1261
|
+
current.hasRendererDefinition,
|
|
1262
|
+
"originalHasRendererDefinition",
|
|
1263
|
+
) as ToolRenderMethods["hasRendererDefinition"],
|
|
1264
|
+
getRenderShell: legacyDownstream(
|
|
1265
|
+
current.getRenderShell,
|
|
1266
|
+
"originalGetRenderShell",
|
|
1267
|
+
) as ToolRenderMethods["getRenderShell"],
|
|
1268
|
+
getCallRenderer: legacyDownstream(
|
|
1269
|
+
current.getCallRenderer,
|
|
1270
|
+
"originalGetCallRenderer",
|
|
1271
|
+
) as ToolRenderMethods["getCallRenderer"],
|
|
1272
|
+
getResultRenderer: legacyDownstream(
|
|
1273
|
+
current.getResultRenderer,
|
|
1274
|
+
"originalGetResultRenderer",
|
|
1275
|
+
) as ToolRenderMethods["getResultRenderer"],
|
|
1218
1276
|
};
|
|
1219
1277
|
}
|
|
1220
1278
|
|
|
1221
|
-
function
|
|
1222
|
-
|
|
1223
|
-
|
|
1279
|
+
function disconnectGlobalToolRenderPatch(patch: any): void {
|
|
1280
|
+
if (!patch || typeof patch !== "object") return;
|
|
1281
|
+
patch.active = false;
|
|
1224
1282
|
patch.enabled = () => false;
|
|
1283
|
+
patch.mode = () => "off";
|
|
1284
|
+
patch.wrap = (tool: any) => tool;
|
|
1225
1285
|
patch.byDefinition = new WeakMap();
|
|
1226
|
-
patch.byName.clear();
|
|
1286
|
+
if (patch.byName && typeof patch.byName.clear === "function") patch.byName.clear();
|
|
1287
|
+
else patch.byName = new Map();
|
|
1288
|
+
}
|
|
1289
|
+
|
|
1290
|
+
function installGlobalToolRendering(): GlobalToolRenderPatch {
|
|
1291
|
+
const prototype = (ToolExecutionComponent as any).prototype;
|
|
1292
|
+
const host = globalThis as any;
|
|
1293
|
+
const previous = host[GLOBAL_TOOL_RENDER_PATCH];
|
|
1294
|
+
const downstream = downstreamForGlobalToolInstall(prototype, previous);
|
|
1295
|
+
// Any wrapper retained by an external owner must become a callback-free
|
|
1296
|
+
// pass-through before the new installation is linked above it.
|
|
1297
|
+
disconnectGlobalToolRenderPatch(previous);
|
|
1298
|
+
|
|
1299
|
+
const patch: GlobalToolRenderPatch = {
|
|
1300
|
+
version: 2,
|
|
1301
|
+
prototype,
|
|
1302
|
+
owner: {},
|
|
1303
|
+
active: true,
|
|
1304
|
+
enabled: () => config.mode === "on",
|
|
1305
|
+
mode: () => config.mode,
|
|
1306
|
+
wrap: createCcstyleTool,
|
|
1307
|
+
byDefinition: new WeakMap(),
|
|
1308
|
+
byName: new Map(),
|
|
1309
|
+
downstream,
|
|
1310
|
+
installed: undefined as any,
|
|
1311
|
+
originalHasRendererDefinition: downstream.hasRendererDefinition,
|
|
1312
|
+
originalGetRenderShell: downstream.getRenderShell,
|
|
1313
|
+
originalGetCallRenderer: downstream.getCallRenderer,
|
|
1314
|
+
originalGetResultRenderer: downstream.getResultRenderer,
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
patch.installed = {
|
|
1318
|
+
hasRendererDefinition: function (this: any, ...args: any[]) {
|
|
1319
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) return true;
|
|
1320
|
+
return patch.downstream.hasRendererDefinition.apply(this, args);
|
|
1321
|
+
},
|
|
1322
|
+
getRenderShell: function (this: any, ...args: any[]) {
|
|
1323
|
+
if (!patch.active) return patch.downstream.getRenderShell.apply(this, args);
|
|
1324
|
+
const shell = shouldUseSelfShell(this, patch) || shouldGloballyStyleTool(this, patch)
|
|
1325
|
+
? "self"
|
|
1326
|
+
: patch.downstream.getRenderShell.apply(this, args);
|
|
1327
|
+
syncToolShell(this, shell);
|
|
1328
|
+
return shell;
|
|
1329
|
+
},
|
|
1330
|
+
getCallRenderer: function (this: any, ...args: any[]) {
|
|
1331
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
1332
|
+
return getGloballyStyledTool(this, patch).renderCall;
|
|
1333
|
+
}
|
|
1334
|
+
return patch.downstream.getCallRenderer.apply(this, args);
|
|
1335
|
+
},
|
|
1336
|
+
getResultRenderer: function (this: any, ...args: any[]) {
|
|
1337
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
1338
|
+
return getGloballyStyledTool(this, patch).renderResult;
|
|
1339
|
+
}
|
|
1340
|
+
return patch.downstream.getResultRenderer.apply(this, args);
|
|
1341
|
+
},
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
prototype.hasRendererDefinition = patch.installed.hasRendererDefinition;
|
|
1345
|
+
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
1346
|
+
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
1347
|
+
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
1348
|
+
host[GLOBAL_TOOL_RENDER_PATCH] = patch;
|
|
1349
|
+
return patch;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
1353
|
+
if (!patch.active) return;
|
|
1354
|
+
disconnectGlobalToolRenderPatch(patch);
|
|
1355
|
+
const prototype = patch.prototype;
|
|
1356
|
+
if (prototype.hasRendererDefinition === patch.installed.hasRendererDefinition) {
|
|
1357
|
+
prototype.hasRendererDefinition = patch.downstream.hasRendererDefinition;
|
|
1358
|
+
}
|
|
1359
|
+
if (prototype.getRenderShell === patch.installed.getRenderShell) {
|
|
1360
|
+
prototype.getRenderShell = patch.downstream.getRenderShell;
|
|
1361
|
+
}
|
|
1362
|
+
if (prototype.getCallRenderer === patch.installed.getCallRenderer) {
|
|
1363
|
+
prototype.getCallRenderer = patch.downstream.getCallRenderer;
|
|
1364
|
+
}
|
|
1365
|
+
if (prototype.getResultRenderer === patch.installed.getResultRenderer) {
|
|
1366
|
+
prototype.getResultRenderer = patch.downstream.getResultRenderer;
|
|
1367
|
+
}
|
|
1227
1368
|
}
|
|
1228
1369
|
|
|
1229
1370
|
const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
|
|
@@ -1251,15 +1392,20 @@ function notePendingScrollMessage(role: unknown): void {
|
|
|
1251
1392
|
}
|
|
1252
1393
|
|
|
1253
1394
|
export default function (pi: ExtensionAPI) {
|
|
1254
|
-
installGlobalToolRendering();
|
|
1395
|
+
const globalToolRendering = installGlobalToolRendering();
|
|
1255
1396
|
deactivateLegacyCompactionRendering();
|
|
1397
|
+
const compactStyle: CompactStyleHooks = installCompactStyle(pi, {
|
|
1398
|
+
getMode: () => config.mode,
|
|
1399
|
+
getExcludeRenderers: () => config.excludeRenderers,
|
|
1400
|
+
});
|
|
1256
1401
|
|
|
1257
1402
|
pi.registerCommand("ccstyle", {
|
|
1258
1403
|
description: "Configure Claude Code style",
|
|
1259
1404
|
getArgumentCompletions: (prefix) => {
|
|
1260
1405
|
const topLevel = [
|
|
1261
1406
|
{ value: "on", label: "on", description: "Enable Claude Code style" },
|
|
1262
|
-
{ value: "off", label: "off", description: "
|
|
1407
|
+
{ value: "off", label: "off", description: "Use Pi's native renderer" },
|
|
1408
|
+
{ value: "compact", label: "compact", description: "Use compact transcript rendering" },
|
|
1263
1409
|
{ value: "status", label: "status", description: "Show current state" },
|
|
1264
1410
|
];
|
|
1265
1411
|
return topLevel.filter((item) => item.value.startsWith(prefix));
|
|
@@ -1267,31 +1413,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
1267
1413
|
handler: async (args, ctx) => {
|
|
1268
1414
|
const arg = args.trim().toLowerCase();
|
|
1269
1415
|
if (!arg) {
|
|
1270
|
-
await showCcstylePanel(ctx);
|
|
1416
|
+
await showCcstylePanel(ctx, compactStyle);
|
|
1271
1417
|
return;
|
|
1272
1418
|
}
|
|
1273
|
-
if (arg === "on" || arg === "off") {
|
|
1274
|
-
|
|
1419
|
+
if (arg === "on" || arg === "off" || arg === "compact") {
|
|
1420
|
+
applyStyleMode(arg, ctx, compactStyle);
|
|
1275
1421
|
return;
|
|
1276
1422
|
}
|
|
1277
1423
|
if (arg === "status") {
|
|
1278
|
-
ctx.ui.notify(`Claude Code style: ${config.
|
|
1424
|
+
ctx.ui.notify(`Claude Code style: ${config.mode}`, "info");
|
|
1279
1425
|
return;
|
|
1280
1426
|
}
|
|
1281
|
-
ctx.ui.notify("Usage: /ccstyle [on|off|status]", "warning");
|
|
1427
|
+
ctx.ui.notify("Usage: /ccstyle [on|off|compact|status]", "warning");
|
|
1282
1428
|
},
|
|
1283
1429
|
});
|
|
1284
1430
|
|
|
1285
|
-
pi.
|
|
1286
|
-
|
|
1287
|
-
handler: async (ctx) => {
|
|
1288
|
-
config.enabled = !config.enabled;
|
|
1289
|
-
saveConfig();
|
|
1290
|
-
ctx.ui.notify(`Claude Code style: ${config.enabled ? "on" : "off"}`, "info");
|
|
1291
|
-
},
|
|
1292
|
-
});
|
|
1293
|
-
|
|
1294
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
1431
|
+
pi.on("session_start", async (event, ctx) => {
|
|
1432
|
+
compactStyle?.onSessionStart(event, ctx);
|
|
1295
1433
|
pendingScrollMessages = 0;
|
|
1296
1434
|
assistantMessageActive = false;
|
|
1297
1435
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
@@ -1303,7 +1441,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1303
1441
|
notePendingScrollMessage(event?.message?.role);
|
|
1304
1442
|
});
|
|
1305
1443
|
|
|
1306
|
-
pi.on("message_update", async (event) => {
|
|
1444
|
+
pi.on("message_update", async (event, ctx) => {
|
|
1445
|
+
compactStyle?.onMessageUpdate(event, ctx);
|
|
1307
1446
|
if (event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
1308
1447
|
});
|
|
1309
1448
|
|
|
@@ -1311,9 +1450,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
1311
1450
|
if (event?.message?.role === "assistant") assistantMessageActive = false;
|
|
1312
1451
|
});
|
|
1313
1452
|
|
|
1314
|
-
pi.on("
|
|
1453
|
+
pi.on("agent_start", async (event, ctx) => {
|
|
1454
|
+
compactStyle?.onAgentStart(event, ctx);
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1457
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
1458
|
+
compactStyle?.onAgentEnd(event, ctx);
|
|
1459
|
+
});
|
|
1460
|
+
|
|
1461
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
1462
|
+
compactStyle?.onTurnStart(event, ctx);
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
1466
|
+
compactStyle?.onToolExecutionStart(event, ctx);
|
|
1467
|
+
});
|
|
1468
|
+
|
|
1469
|
+
pi.on("tool_execution_update", async (event, ctx) => {
|
|
1470
|
+
compactStyle?.onToolExecutionUpdate(event, ctx);
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
1474
|
+
compactStyle?.onToolExecutionEnd(event, ctx);
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
1478
|
+
compactStyle?.onSessionShutdown(event, ctx);
|
|
1315
1479
|
teardownToolMouseInteraction();
|
|
1316
|
-
deactivateGlobalToolRendering();
|
|
1480
|
+
deactivateGlobalToolRendering(globalToolRendering);
|
|
1317
1481
|
deactivateLegacyCompactionRendering();
|
|
1318
1482
|
clearAllAnimations();
|
|
1319
1483
|
});
|