pi-cc-extensions 0.7.10 → 0.8.0
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 +36 -9
- package/extensions/claude-code-style.ts +298 -135
- package/extensions/compact-style.ts +1136 -0
- package/extensions/tool-result-sanitize.ts +17 -0
- package/package.json +1 -1
|
@@ -4,53 +4,72 @@ import {
|
|
|
4
4
|
getSettingsListTheme,
|
|
5
5
|
ToolExecutionComponent,
|
|
6
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import {
|
|
8
|
+
installCompactStyle,
|
|
9
|
+
type CompactStyleHooks,
|
|
10
|
+
type CompactStyleMode,
|
|
11
|
+
} from "./compact-style.ts";
|
|
12
|
+
import { sanitizeToolResultText } from "./tool-result-sanitize.ts";
|
|
7
13
|
import { Container, SettingsList, 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 ?? "") : "";
|
|
@@ -938,13 +939,20 @@ 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;
|
|
@@ -955,17 +963,17 @@ async function showCcstylePanel(ctx: any): Promise<void> {
|
|
|
955
963
|
container.addChild(new Text(theme.fg("accent", theme.bold("Claude Code Style")), 1, 1));
|
|
956
964
|
const settingsList = new SettingsList(
|
|
957
965
|
[{
|
|
958
|
-
id: "
|
|
966
|
+
id: "mode",
|
|
959
967
|
label: "Output style",
|
|
960
|
-
description: "
|
|
961
|
-
currentValue: config.
|
|
962
|
-
values: ["on", "off"],
|
|
968
|
+
description: "Claude Code, Pi native, or compact transcript rendering",
|
|
969
|
+
currentValue: config.mode,
|
|
970
|
+
values: ["on", "off", "compact"],
|
|
963
971
|
}],
|
|
964
972
|
3,
|
|
965
973
|
getSettingsListTheme(),
|
|
966
974
|
(id: string, newValue: string) => {
|
|
967
|
-
if (id !== "
|
|
968
|
-
|
|
975
|
+
if (id !== "mode" || (newValue !== "on" && newValue !== "off" && newValue !== "compact")) return;
|
|
976
|
+
applyStyleMode(newValue, ctx, compactStyle);
|
|
969
977
|
settingsList.updateValue(id, newValue);
|
|
970
978
|
tui.requestRender();
|
|
971
979
|
},
|
|
@@ -1027,7 +1035,7 @@ function createCcstyleTool(originalTool: any): any {
|
|
|
1027
1035
|
...originalTool,
|
|
1028
1036
|
renderShell: "self",
|
|
1029
1037
|
renderCall(args: any, theme: any, context: any) {
|
|
1030
|
-
if (
|
|
1038
|
+
if (config.mode !== "on") {
|
|
1031
1039
|
return renderDefault(originalTool, "renderCall", [args, theme, context], String(toolName));
|
|
1032
1040
|
}
|
|
1033
1041
|
|
|
@@ -1046,7 +1054,7 @@ function createCcstyleTool(originalTool: any): any {
|
|
|
1046
1054
|
return new Text(title, 0, 0);
|
|
1047
1055
|
},
|
|
1048
1056
|
renderResult(result: any, options: any, theme: any, context: any) {
|
|
1049
|
-
if (
|
|
1057
|
+
if (config.mode !== "on") {
|
|
1050
1058
|
return renderDefault(originalTool, "renderResult", [result, options, theme, context], textFromResult(result));
|
|
1051
1059
|
}
|
|
1052
1060
|
|
|
@@ -1082,19 +1090,31 @@ const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch
|
|
|
1082
1090
|
const COMPONENT_TOOL_RENDER_MODE = Symbol.for("pi.ccstyle.component-tool-render-mode");
|
|
1083
1091
|
const COMPONENT_TOOL_SELF_SHELL_MODE = Symbol.for("pi.ccstyle.component-tool-self-shell-mode");
|
|
1084
1092
|
const SUBAGENT_TOOL_NAMES = new Set(["Agent"]);
|
|
1085
|
-
|
|
1093
|
+
|
|
1094
|
+
type ToolRenderMethods = {
|
|
1095
|
+
hasRendererDefinition: (...args: any[]) => boolean;
|
|
1096
|
+
getRenderShell: (...args: any[]) => "default" | "self";
|
|
1097
|
+
getCallRenderer: (...args: any[]) => any;
|
|
1098
|
+
getResultRenderer: (...args: any[]) => any;
|
|
1099
|
+
};
|
|
1086
1100
|
|
|
1087
1101
|
type GlobalToolRenderPatch = {
|
|
1102
|
+
version: 2;
|
|
1088
1103
|
prototype: any;
|
|
1089
1104
|
owner: object;
|
|
1105
|
+
active: boolean;
|
|
1090
1106
|
enabled: () => boolean;
|
|
1107
|
+
mode: () => CompactStyleMode;
|
|
1091
1108
|
wrap: (tool: any) => any;
|
|
1092
1109
|
byDefinition: WeakMap<object, any>;
|
|
1093
1110
|
byName: Map<string, any>;
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1111
|
+
downstream: ToolRenderMethods;
|
|
1112
|
+
installed: ToolRenderMethods;
|
|
1113
|
+
// Keep the legacy aliases so an older extension build can read this Symbol.
|
|
1114
|
+
originalHasRendererDefinition: ToolRenderMethods["hasRendererDefinition"];
|
|
1115
|
+
originalGetRenderShell: ToolRenderMethods["getRenderShell"];
|
|
1116
|
+
originalGetCallRenderer: ToolRenderMethods["getCallRenderer"];
|
|
1117
|
+
originalGetResultRenderer: ToolRenderMethods["getResultRenderer"];
|
|
1098
1118
|
};
|
|
1099
1119
|
|
|
1100
1120
|
function isMcpToolDefinition(definition: any, toolName: string): boolean {
|
|
@@ -1118,33 +1138,43 @@ export function preservesOriginalRenderer(
|
|
|
1118
1138
|
);
|
|
1119
1139
|
}
|
|
1120
1140
|
|
|
1121
|
-
function
|
|
1122
|
-
const
|
|
1123
|
-
if (
|
|
1141
|
+
function syncToolShell(component: any, shell: "default" | "self"): void {
|
|
1142
|
+
const target = shell === "self" ? component.selfRenderContainer : component.contentBox;
|
|
1143
|
+
if (!target || !Array.isArray(component.children)) return;
|
|
1144
|
+
const candidates = new Set([component.contentText, component.contentBox, component.selfRenderContainer].filter(Boolean));
|
|
1145
|
+
const indexes = component.children
|
|
1146
|
+
.map((child: any, index: number) => candidates.has(child) ? index : -1)
|
|
1147
|
+
.filter((index: number) => index >= 0);
|
|
1148
|
+
const targetIndex = indexes[0];
|
|
1149
|
+
// During construction getRenderShell() runs immediately before Pi mounts the
|
|
1150
|
+
// selected shell. Do not mount it here or the constructor will add it twice.
|
|
1151
|
+
if (targetIndex === undefined) return;
|
|
1152
|
+
component.children[targetIndex] = target;
|
|
1153
|
+
for (const index of indexes.sort((left: number, right: number) => right - left)) {
|
|
1154
|
+
if (index !== targetIndex) component.children.splice(index, 1);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1124
1157
|
|
|
1158
|
+
function shouldGloballyStyleTool(component: any, patch: GlobalToolRenderPatch): boolean {
|
|
1125
1159
|
const extensionDefinition = component.toolDefinition;
|
|
1126
1160
|
const builtInDefinition = component.builtInToolDefinition;
|
|
1127
1161
|
const definition = extensionDefinition ?? builtInDefinition;
|
|
1128
1162
|
const toolName = String(component.toolName || definition?.name || "");
|
|
1129
1163
|
const useCcstyle =
|
|
1130
|
-
patch.
|
|
1164
|
+
patch.mode() === "on" &&
|
|
1131
1165
|
!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
1166
|
component[COMPONENT_TOOL_RENDER_MODE] = useCcstyle;
|
|
1135
1167
|
return useCcstyle;
|
|
1136
1168
|
}
|
|
1137
1169
|
|
|
1138
1170
|
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
1171
|
const definition = component.toolDefinition ?? component.builtInToolDefinition;
|
|
1143
1172
|
const toolName = String(component.toolName || definition?.name || "");
|
|
1144
1173
|
const useSelfShell =
|
|
1145
1174
|
patch.enabled() &&
|
|
1146
1175
|
SUBAGENT_TOOL_NAMES.has(toolName) &&
|
|
1147
|
-
definition
|
|
1176
|
+
definition != null &&
|
|
1177
|
+
definition.renderShell === undefined;
|
|
1148
1178
|
component[COMPONENT_TOOL_SELF_SHELL_MODE] = useSelfShell;
|
|
1149
1179
|
return useSelfShell;
|
|
1150
1180
|
}
|
|
@@ -1169,61 +1199,171 @@ function getGloballyStyledTool(component: any, patch: GlobalToolRenderPatch): an
|
|
|
1169
1199
|
return wrapped;
|
|
1170
1200
|
}
|
|
1171
1201
|
|
|
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
|
-
};
|
|
1202
|
+
function prototypeToolRenderMethods(prototype: any): ToolRenderMethods {
|
|
1203
|
+
return {
|
|
1204
|
+
hasRendererDefinition: prototype.hasRendererDefinition,
|
|
1205
|
+
getRenderShell: prototype.getRenderShell,
|
|
1206
|
+
getCallRenderer: prototype.getCallRenderer,
|
|
1207
|
+
getResultRenderer: prototype.getResultRenderer,
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1190
1210
|
|
|
1191
|
-
|
|
1211
|
+
function isOwnershipAwarePatch(value: any): value is GlobalToolRenderPatch {
|
|
1212
|
+
if (!value || value.version !== 2 || !value.installed || !value.downstream) return false;
|
|
1213
|
+
return ["hasRendererDefinition", "getRenderShell", "getCallRenderer", "getResultRenderer"]
|
|
1214
|
+
.every((name) => typeof value.installed[name] === "function" && typeof value.downstream[name] === "function");
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function isLegacyInstalledWrapper(method: unknown, downstreamField: string): boolean {
|
|
1218
|
+
if (typeof method !== "function") return false;
|
|
1219
|
+
try {
|
|
1220
|
+
const source = Function.prototype.toString.call(method);
|
|
1221
|
+
return source.includes(downstreamField)
|
|
1222
|
+
&& (
|
|
1223
|
+
source.includes("shouldGloballyStyleTool")
|
|
1224
|
+
|| source.includes("shouldUseSelfShell")
|
|
1225
|
+
|| source.includes("getGloballyStyledTool")
|
|
1226
|
+
);
|
|
1227
|
+
} catch {
|
|
1228
|
+
return false;
|
|
1192
1229
|
}
|
|
1230
|
+
}
|
|
1193
1231
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1232
|
+
function downstreamForGlobalToolInstall(prototype: any, previous: any): ToolRenderMethods {
|
|
1233
|
+
const current = prototypeToolRenderMethods(prototype);
|
|
1234
|
+
if (!previous || previous.prototype !== prototype) return current;
|
|
1235
|
+
if (isOwnershipAwarePatch(previous)) {
|
|
1236
|
+
return {
|
|
1237
|
+
hasRendererDefinition: current.hasRendererDefinition === previous.installed.hasRendererDefinition
|
|
1238
|
+
? previous.downstream.hasRendererDefinition
|
|
1239
|
+
: current.hasRendererDefinition,
|
|
1240
|
+
getRenderShell: current.getRenderShell === previous.installed.getRenderShell
|
|
1241
|
+
? previous.downstream.getRenderShell
|
|
1242
|
+
: current.getRenderShell,
|
|
1243
|
+
getCallRenderer: current.getCallRenderer === previous.installed.getCallRenderer
|
|
1244
|
+
? previous.downstream.getCallRenderer
|
|
1245
|
+
: current.getCallRenderer,
|
|
1246
|
+
getResultRenderer: current.getResultRenderer === previous.installed.getResultRenderer
|
|
1247
|
+
? previous.downstream.getResultRenderer
|
|
1248
|
+
: current.getResultRenderer,
|
|
1249
|
+
};
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Pre-v2 Symbol state did not retain wrapper references. Recognize its known
|
|
1253
|
+
// wrappers when possible; otherwise preserve the current method as external.
|
|
1254
|
+
const legacyDownstream = (method: Function, field: string): Function => {
|
|
1255
|
+
const saved = previous[field];
|
|
1256
|
+
return typeof saved === "function" && isLegacyInstalledWrapper(method, field) ? saved : method;
|
|
1214
1257
|
};
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1258
|
+
return {
|
|
1259
|
+
hasRendererDefinition: legacyDownstream(
|
|
1260
|
+
current.hasRendererDefinition,
|
|
1261
|
+
"originalHasRendererDefinition",
|
|
1262
|
+
) as ToolRenderMethods["hasRendererDefinition"],
|
|
1263
|
+
getRenderShell: legacyDownstream(
|
|
1264
|
+
current.getRenderShell,
|
|
1265
|
+
"originalGetRenderShell",
|
|
1266
|
+
) as ToolRenderMethods["getRenderShell"],
|
|
1267
|
+
getCallRenderer: legacyDownstream(
|
|
1268
|
+
current.getCallRenderer,
|
|
1269
|
+
"originalGetCallRenderer",
|
|
1270
|
+
) as ToolRenderMethods["getCallRenderer"],
|
|
1271
|
+
getResultRenderer: legacyDownstream(
|
|
1272
|
+
current.getResultRenderer,
|
|
1273
|
+
"originalGetResultRenderer",
|
|
1274
|
+
) as ToolRenderMethods["getResultRenderer"],
|
|
1218
1275
|
};
|
|
1219
1276
|
}
|
|
1220
1277
|
|
|
1221
|
-
function
|
|
1222
|
-
|
|
1223
|
-
|
|
1278
|
+
function disconnectGlobalToolRenderPatch(patch: any): void {
|
|
1279
|
+
if (!patch || typeof patch !== "object") return;
|
|
1280
|
+
patch.active = false;
|
|
1224
1281
|
patch.enabled = () => false;
|
|
1282
|
+
patch.mode = () => "off";
|
|
1283
|
+
patch.wrap = (tool: any) => tool;
|
|
1225
1284
|
patch.byDefinition = new WeakMap();
|
|
1226
|
-
patch.byName.clear();
|
|
1285
|
+
if (patch.byName && typeof patch.byName.clear === "function") patch.byName.clear();
|
|
1286
|
+
else patch.byName = new Map();
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
function installGlobalToolRendering(): GlobalToolRenderPatch {
|
|
1290
|
+
const prototype = (ToolExecutionComponent as any).prototype;
|
|
1291
|
+
const host = globalThis as any;
|
|
1292
|
+
const previous = host[GLOBAL_TOOL_RENDER_PATCH];
|
|
1293
|
+
const downstream = downstreamForGlobalToolInstall(prototype, previous);
|
|
1294
|
+
// Any wrapper retained by an external owner must become a callback-free
|
|
1295
|
+
// pass-through before the new installation is linked above it.
|
|
1296
|
+
disconnectGlobalToolRenderPatch(previous);
|
|
1297
|
+
|
|
1298
|
+
const patch: GlobalToolRenderPatch = {
|
|
1299
|
+
version: 2,
|
|
1300
|
+
prototype,
|
|
1301
|
+
owner: {},
|
|
1302
|
+
active: true,
|
|
1303
|
+
enabled: () => config.mode === "on",
|
|
1304
|
+
mode: () => config.mode,
|
|
1305
|
+
wrap: createCcstyleTool,
|
|
1306
|
+
byDefinition: new WeakMap(),
|
|
1307
|
+
byName: new Map(),
|
|
1308
|
+
downstream,
|
|
1309
|
+
installed: undefined as any,
|
|
1310
|
+
originalHasRendererDefinition: downstream.hasRendererDefinition,
|
|
1311
|
+
originalGetRenderShell: downstream.getRenderShell,
|
|
1312
|
+
originalGetCallRenderer: downstream.getCallRenderer,
|
|
1313
|
+
originalGetResultRenderer: downstream.getResultRenderer,
|
|
1314
|
+
};
|
|
1315
|
+
|
|
1316
|
+
patch.installed = {
|
|
1317
|
+
hasRendererDefinition: function (this: any, ...args: any[]) {
|
|
1318
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) return true;
|
|
1319
|
+
return patch.downstream.hasRendererDefinition.apply(this, args);
|
|
1320
|
+
},
|
|
1321
|
+
getRenderShell: function (this: any, ...args: any[]) {
|
|
1322
|
+
if (!patch.active) return patch.downstream.getRenderShell.apply(this, args);
|
|
1323
|
+
const shell = shouldUseSelfShell(this, patch) || shouldGloballyStyleTool(this, patch)
|
|
1324
|
+
? "self"
|
|
1325
|
+
: patch.downstream.getRenderShell.apply(this, args);
|
|
1326
|
+
syncToolShell(this, shell);
|
|
1327
|
+
return shell;
|
|
1328
|
+
},
|
|
1329
|
+
getCallRenderer: function (this: any, ...args: any[]) {
|
|
1330
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
1331
|
+
return getGloballyStyledTool(this, patch).renderCall;
|
|
1332
|
+
}
|
|
1333
|
+
return patch.downstream.getCallRenderer.apply(this, args);
|
|
1334
|
+
},
|
|
1335
|
+
getResultRenderer: function (this: any, ...args: any[]) {
|
|
1336
|
+
if (patch.active && shouldGloballyStyleTool(this, patch)) {
|
|
1337
|
+
return getGloballyStyledTool(this, patch).renderResult;
|
|
1338
|
+
}
|
|
1339
|
+
return patch.downstream.getResultRenderer.apply(this, args);
|
|
1340
|
+
},
|
|
1341
|
+
};
|
|
1342
|
+
|
|
1343
|
+
prototype.hasRendererDefinition = patch.installed.hasRendererDefinition;
|
|
1344
|
+
prototype.getRenderShell = patch.installed.getRenderShell;
|
|
1345
|
+
prototype.getCallRenderer = patch.installed.getCallRenderer;
|
|
1346
|
+
prototype.getResultRenderer = patch.installed.getResultRenderer;
|
|
1347
|
+
host[GLOBAL_TOOL_RENDER_PATCH] = patch;
|
|
1348
|
+
return patch;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
function deactivateGlobalToolRendering(patch: GlobalToolRenderPatch): void {
|
|
1352
|
+
if (!patch.active) return;
|
|
1353
|
+
disconnectGlobalToolRenderPatch(patch);
|
|
1354
|
+
const prototype = patch.prototype;
|
|
1355
|
+
if (prototype.hasRendererDefinition === patch.installed.hasRendererDefinition) {
|
|
1356
|
+
prototype.hasRendererDefinition = patch.downstream.hasRendererDefinition;
|
|
1357
|
+
}
|
|
1358
|
+
if (prototype.getRenderShell === patch.installed.getRenderShell) {
|
|
1359
|
+
prototype.getRenderShell = patch.downstream.getRenderShell;
|
|
1360
|
+
}
|
|
1361
|
+
if (prototype.getCallRenderer === patch.installed.getCallRenderer) {
|
|
1362
|
+
prototype.getCallRenderer = patch.downstream.getCallRenderer;
|
|
1363
|
+
}
|
|
1364
|
+
if (prototype.getResultRenderer === patch.installed.getResultRenderer) {
|
|
1365
|
+
prototype.getResultRenderer = patch.downstream.getResultRenderer;
|
|
1366
|
+
}
|
|
1227
1367
|
}
|
|
1228
1368
|
|
|
1229
1369
|
const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
|
|
@@ -1251,15 +1391,20 @@ function notePendingScrollMessage(role: unknown): void {
|
|
|
1251
1391
|
}
|
|
1252
1392
|
|
|
1253
1393
|
export default function (pi: ExtensionAPI) {
|
|
1254
|
-
installGlobalToolRendering();
|
|
1394
|
+
const globalToolRendering = installGlobalToolRendering();
|
|
1255
1395
|
deactivateLegacyCompactionRendering();
|
|
1396
|
+
const compactStyle: CompactStyleHooks = installCompactStyle(pi, {
|
|
1397
|
+
getMode: () => config.mode,
|
|
1398
|
+
getExcludeRenderers: () => config.excludeRenderers,
|
|
1399
|
+
});
|
|
1256
1400
|
|
|
1257
1401
|
pi.registerCommand("ccstyle", {
|
|
1258
1402
|
description: "Configure Claude Code style",
|
|
1259
1403
|
getArgumentCompletions: (prefix) => {
|
|
1260
1404
|
const topLevel = [
|
|
1261
1405
|
{ value: "on", label: "on", description: "Enable Claude Code style" },
|
|
1262
|
-
{ value: "off", label: "off", description: "
|
|
1406
|
+
{ value: "off", label: "off", description: "Use Pi's native renderer" },
|
|
1407
|
+
{ value: "compact", label: "compact", description: "Use compact transcript rendering" },
|
|
1263
1408
|
{ value: "status", label: "status", description: "Show current state" },
|
|
1264
1409
|
];
|
|
1265
1410
|
return topLevel.filter((item) => item.value.startsWith(prefix));
|
|
@@ -1267,31 +1412,23 @@ export default function (pi: ExtensionAPI) {
|
|
|
1267
1412
|
handler: async (args, ctx) => {
|
|
1268
1413
|
const arg = args.trim().toLowerCase();
|
|
1269
1414
|
if (!arg) {
|
|
1270
|
-
await showCcstylePanel(ctx);
|
|
1415
|
+
await showCcstylePanel(ctx, compactStyle);
|
|
1271
1416
|
return;
|
|
1272
1417
|
}
|
|
1273
|
-
if (arg === "on" || arg === "off") {
|
|
1274
|
-
|
|
1418
|
+
if (arg === "on" || arg === "off" || arg === "compact") {
|
|
1419
|
+
applyStyleMode(arg, ctx, compactStyle);
|
|
1275
1420
|
return;
|
|
1276
1421
|
}
|
|
1277
1422
|
if (arg === "status") {
|
|
1278
|
-
ctx.ui.notify(`Claude Code style: ${config.
|
|
1423
|
+
ctx.ui.notify(`Claude Code style: ${config.mode}`, "info");
|
|
1279
1424
|
return;
|
|
1280
1425
|
}
|
|
1281
|
-
ctx.ui.notify("Usage: /ccstyle [on|off|status]", "warning");
|
|
1282
|
-
},
|
|
1283
|
-
});
|
|
1284
|
-
|
|
1285
|
-
pi.registerShortcut("ctrl+shift+o", {
|
|
1286
|
-
description: "Toggle Claude Code-like output style",
|
|
1287
|
-
handler: async (ctx) => {
|
|
1288
|
-
config.enabled = !config.enabled;
|
|
1289
|
-
saveConfig();
|
|
1290
|
-
ctx.ui.notify(`Claude Code style: ${config.enabled ? "on" : "off"}`, "info");
|
|
1426
|
+
ctx.ui.notify("Usage: /ccstyle [on|off|compact|status]", "warning");
|
|
1291
1427
|
},
|
|
1292
1428
|
});
|
|
1293
1429
|
|
|
1294
|
-
pi.on("session_start", async (
|
|
1430
|
+
pi.on("session_start", async (event, ctx) => {
|
|
1431
|
+
compactStyle?.onSessionStart(event, ctx);
|
|
1295
1432
|
pendingScrollMessages = 0;
|
|
1296
1433
|
assistantMessageActive = false;
|
|
1297
1434
|
ctx.ui.setStatus("ccstyle", undefined);
|
|
@@ -1303,7 +1440,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
1303
1440
|
notePendingScrollMessage(event?.message?.role);
|
|
1304
1441
|
});
|
|
1305
1442
|
|
|
1306
|
-
pi.on("message_update", async (event) => {
|
|
1443
|
+
pi.on("message_update", async (event, ctx) => {
|
|
1444
|
+
compactStyle?.onMessageUpdate(event, ctx);
|
|
1307
1445
|
if (event?.message?.role === "assistant") notePendingScrollMessage("assistant");
|
|
1308
1446
|
});
|
|
1309
1447
|
|
|
@@ -1311,9 +1449,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
1311
1449
|
if (event?.message?.role === "assistant") assistantMessageActive = false;
|
|
1312
1450
|
});
|
|
1313
1451
|
|
|
1314
|
-
pi.on("
|
|
1452
|
+
pi.on("agent_start", async (event, ctx) => {
|
|
1453
|
+
compactStyle?.onAgentStart(event, ctx);
|
|
1454
|
+
});
|
|
1455
|
+
|
|
1456
|
+
pi.on("agent_end", async (event, ctx) => {
|
|
1457
|
+
compactStyle?.onAgentEnd(event, ctx);
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
pi.on("turn_start", async (event, ctx) => {
|
|
1461
|
+
compactStyle?.onTurnStart(event, ctx);
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
pi.on("tool_execution_start", async (event, ctx) => {
|
|
1465
|
+
compactStyle?.onToolExecutionStart(event, ctx);
|
|
1466
|
+
});
|
|
1467
|
+
|
|
1468
|
+
pi.on("tool_execution_update", async (event, ctx) => {
|
|
1469
|
+
compactStyle?.onToolExecutionUpdate(event, ctx);
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
1473
|
+
compactStyle?.onToolExecutionEnd(event, ctx);
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
pi.on("session_shutdown", async (event, ctx) => {
|
|
1477
|
+
compactStyle?.onSessionShutdown(event, ctx);
|
|
1315
1478
|
teardownToolMouseInteraction();
|
|
1316
|
-
deactivateGlobalToolRendering();
|
|
1479
|
+
deactivateGlobalToolRendering(globalToolRendering);
|
|
1317
1480
|
deactivateLegacyCompactionRendering();
|
|
1318
1481
|
clearAllAnimations();
|
|
1319
1482
|
});
|