@selesai/code 0.10.1 → 0.11.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.
Files changed (128) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/config.d.ts +5 -3
  3. package/dist/config.js +6 -4
  4. package/dist/core/skills.d.ts +2 -0
  5. package/dist/core/skills.js +1 -0
  6. package/dist/defaults/settings.json +0 -3
  7. package/dist/extensions/auto-session-name.test.ts +157 -0
  8. package/dist/extensions/auto-session-name.ts +96 -0
  9. package/dist/extensions/cost-reconcile.test.ts +76 -0
  10. package/dist/extensions/cost-reconcile.ts +278 -0
  11. package/dist/extensions/enable-readonly-tools.test.ts +11 -0
  12. package/dist/extensions/enable-readonly-tools.ts +7 -3
  13. package/dist/extensions/package.json +3 -0
  14. package/dist/extensions/pi-tool-display/CHANGELOG.md +329 -0
  15. package/dist/extensions/pi-tool-display/LICENSE +21 -0
  16. package/dist/extensions/pi-tool-display/README.md +417 -0
  17. package/dist/extensions/pi-tool-display/config/config.example.json +40 -0
  18. package/dist/extensions/pi-tool-display/index.ts +3 -0
  19. package/dist/extensions/pi-tool-display/package-lock.json +2555 -0
  20. package/dist/extensions/pi-tool-display/package.json +86 -0
  21. package/dist/extensions/pi-tool-display/src/agent-dir.ts +35 -0
  22. package/dist/extensions/pi-tool-display/src/ansi-utils.ts +116 -0
  23. package/dist/extensions/pi-tool-display/src/bash-display.ts +213 -0
  24. package/dist/extensions/pi-tool-display/src/capabilities.ts +88 -0
  25. package/dist/extensions/pi-tool-display/src/config-modal.ts +541 -0
  26. package/dist/extensions/pi-tool-display/src/config-store.ts +304 -0
  27. package/dist/extensions/pi-tool-display/src/debug-logger.ts +150 -0
  28. package/dist/extensions/pi-tool-display/src/diff-presentation.ts +72 -0
  29. package/dist/extensions/pi-tool-display/src/diff-renderer.ts +2517 -0
  30. package/dist/extensions/pi-tool-display/src/disposable.ts +34 -0
  31. package/dist/extensions/pi-tool-display/src/extension-lifecycle.ts +14 -0
  32. package/dist/extensions/pi-tool-display/src/index.ts +109 -0
  33. package/dist/extensions/pi-tool-display/src/line-width-safety.ts +93 -0
  34. package/dist/extensions/pi-tool-display/src/modal-icons.ts +57 -0
  35. package/dist/extensions/pi-tool-display/src/pending-diff-preview.ts +370 -0
  36. package/dist/extensions/pi-tool-display/src/presets.ts +117 -0
  37. package/dist/extensions/pi-tool-display/src/render-utils.ts +186 -0
  38. package/dist/extensions/pi-tool-display/src/settings-inspector-modal.ts +475 -0
  39. package/dist/extensions/pi-tool-display/src/thinking-label.ts +343 -0
  40. package/dist/extensions/pi-tool-display/src/tool-call-box.ts +181 -0
  41. package/dist/extensions/pi-tool-display/src/tool-metadata.ts +139 -0
  42. package/dist/extensions/pi-tool-display/src/tool-overrides.ts +2168 -0
  43. package/dist/extensions/pi-tool-display/src/types.ts +109 -0
  44. package/dist/extensions/pi-tool-display/src/user-message-box-markdown.ts +64 -0
  45. package/dist/extensions/pi-tool-display/src/user-message-box-native.ts +66 -0
  46. package/dist/extensions/pi-tool-display/src/user-message-box-patch.ts +72 -0
  47. package/dist/extensions/pi-tool-display/src/user-message-box-renderer.ts +419 -0
  48. package/dist/extensions/pi-tool-display/src/user-message-box-utils.ts +106 -0
  49. package/dist/extensions/pi-tool-display/src/write-display-utils.ts +31 -0
  50. package/dist/extensions/pi-tool-display/src/zellij-modal.ts +1027 -0
  51. package/dist/extensions/pi-tool-display/tests/active-backlog-red.test.ts +65 -0
  52. package/dist/extensions/pi-tool-display/tests/ansi-utils.test.ts +345 -0
  53. package/dist/extensions/pi-tool-display/tests/bash-display.test.ts +442 -0
  54. package/dist/extensions/pi-tool-display/tests/capabilities-edge.test.ts +360 -0
  55. package/dist/extensions/pi-tool-display/tests/capabilities.test.ts +54 -0
  56. package/dist/extensions/pi-tool-display/tests/config-modal.test.ts +360 -0
  57. package/dist/extensions/pi-tool-display/tests/config-store.test.ts +87 -0
  58. package/dist/extensions/pi-tool-display/tests/custom-tool-overrides.test.ts +415 -0
  59. package/dist/extensions/pi-tool-display/tests/debug-logger-edge.test.ts +630 -0
  60. package/dist/extensions/pi-tool-display/tests/debug-logger.test.ts +62 -0
  61. package/dist/extensions/pi-tool-display/tests/diff-renderer-ansi.test.ts +55 -0
  62. package/dist/extensions/pi-tool-display/tests/diff-renderer-edge.test.ts +886 -0
  63. package/dist/extensions/pi-tool-display/tests/diff-renderer-numbering.test.ts +135 -0
  64. package/dist/extensions/pi-tool-display/tests/diff-renderer-width.test.ts +454 -0
  65. package/dist/extensions/pi-tool-display/tests/diff-renderer-wrap.test.ts +169 -0
  66. package/dist/extensions/pi-tool-display/tests/index-integration.test.ts +354 -0
  67. package/dist/extensions/pi-tool-display/tests/pending-diff-preview.test.ts +79 -0
  68. package/dist/extensions/pi-tool-display/tests/presets-edge.test.ts +229 -0
  69. package/dist/extensions/pi-tool-display/tests/presets.test.ts +26 -0
  70. package/dist/extensions/pi-tool-display/tests/reload-behavior.test.ts +1241 -0
  71. package/dist/extensions/pi-tool-display/tests/render-utils.test.ts +710 -0
  72. package/dist/extensions/pi-tool-display/tests/settings-inspector.test.ts +450 -0
  73. package/dist/extensions/pi-tool-display/tests/thinking-label-edge.test.ts +904 -0
  74. package/dist/extensions/pi-tool-display/tests/thinking-label.test.ts +72 -0
  75. package/dist/extensions/pi-tool-display/tests/tool-call-box.test.ts +105 -0
  76. package/dist/extensions/pi-tool-display/tests/tool-overrides-config.test.ts +613 -0
  77. package/dist/extensions/pi-tool-display/tests/tool-overrides-mcp.test.ts +499 -0
  78. package/dist/extensions/pi-tool-display/tests/tool-overrides-registration.test.ts +315 -0
  79. package/dist/extensions/pi-tool-display/tests/tool-overrides-write-meta.test.ts +68 -0
  80. package/dist/extensions/pi-tool-display/tests/tool-ui-utils.test.ts +413 -0
  81. package/dist/extensions/pi-tool-display/tests/user-message-box-performance-red.test.ts +176 -0
  82. package/dist/extensions/pi-tool-display/tests/user-message-box.test.ts +908 -0
  83. package/dist/extensions/pi-tool-display/tests/zellij-modal.test.ts +848 -0
  84. package/dist/extensions/pi-tool-display/tool-display-api-consumer.d.ts +30 -0
  85. package/dist/extensions/pi-tool-display/tool-display-api-consumer.js +40 -0
  86. package/dist/extensions/pi-zentui/extensions/zentui/format.ts +46 -1
  87. package/dist/extensions/pi-zentui/extensions/zentui/index.ts +10 -0
  88. package/dist/extensions/pi-zentui/extensions/zentui/user-message-styles.ts +1 -1
  89. package/dist/extensions/pi-zentui/test/extension-compliance.test.ts +3 -3
  90. package/dist/extensions/pi-zentui/test/format.test.ts +50 -2
  91. package/dist/modes/interactive/components/startup-box.d.ts +38 -0
  92. package/dist/modes/interactive/components/startup-box.js +90 -0
  93. package/dist/modes/interactive/interactive-mode.d.ts +3 -0
  94. package/dist/modes/interactive/interactive-mode.js +85 -122
  95. package/dist/skills/agent-browser/SKILL.md +1 -0
  96. package/dist/skills/batch-grill-me/SKILL.md +1 -0
  97. package/dist/skills/brandkit/SKILL.md +1 -0
  98. package/dist/skills/design-references/SKILL.md +1 -0
  99. package/dist/skills/design-taste-frontend/SKILL.md +1 -0
  100. package/dist/skills/design-taste-frontend-v1/SKILL.md +1 -0
  101. package/dist/skills/full-output-enforcement/SKILL.md +1 -0
  102. package/dist/skills/gpt-taste/SKILL.md +1 -0
  103. package/dist/skills/grill-me/SKILL.md +1 -0
  104. package/dist/skills/handoff/SKILL.md +1 -0
  105. package/dist/skills/handoff-text/SKILL.md +1 -0
  106. package/dist/skills/high-end-visual-design/SKILL.md +1 -0
  107. package/dist/skills/image-to-code/SKILL.md +1 -0
  108. package/dist/skills/imagegen-frontend-mobile/SKILL.md +1 -0
  109. package/dist/skills/imagegen-frontend-web/SKILL.md +1 -0
  110. package/dist/skills/implanger/SKILL.md +1 -0
  111. package/dist/skills/improve-codebase/SKILL.md +1 -0
  112. package/dist/skills/industrial-brutalist-ui/SKILL.md +1 -0
  113. package/dist/skills/minimalist-ui/SKILL.md +1 -0
  114. package/dist/skills/pi-subagents/SKILL.md +1 -0
  115. package/dist/skills/planger/SKILL.md +1 -0
  116. package/dist/skills/ponytail/SKILL.md +1 -0
  117. package/dist/skills/ponytail-audit/SKILL.md +1 -0
  118. package/dist/skills/ponytail-debt/SKILL.md +1 -0
  119. package/dist/skills/ponytail-gain/SKILL.md +1 -0
  120. package/dist/skills/ponytail-help/SKILL.md +1 -0
  121. package/dist/skills/ponytail-review/SKILL.md +1 -0
  122. package/dist/skills/redesign-existing-projects/SKILL.md +1 -0
  123. package/dist/skills/selesai-handoff/SKILL.md +1 -0
  124. package/dist/skills/stitch-design-taste/SKILL.md +1 -0
  125. package/dist/skills/unlazy/SKILL.md +1 -0
  126. package/dist/skills/web-design-guidelines/SKILL.md +1 -0
  127. package/dist/skills/workflow/SKILL.md +1 -0
  128. package/package.json +3 -3
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Cost Reconcile — records the real billed cost the provider reports in its
3
+ * own response payload, for any provider that includes one.
4
+ *
5
+ * pi-ai reads token counts from provider payloads but discards any
6
+ * provider-reported `cost` field, recomputing cost from the local rate card.
7
+ * For gateways (OpenRouter etc.) and custom catalog entries that bill with
8
+ * their own tokenizers or dated pricing, the estimate diverges from the bill.
9
+ *
10
+ * Extensions cannot hook the response body through the event API
11
+ * (`after_provider_response` exposes only status/headers), but provider SDKs
12
+ * fall back to `globalThis.fetch`. This extension installs a tee-wrapping
13
+ * fetch once per process: LLM API responses are scanned for a
14
+ * provider-reported cost (LiteLLM `x-litellm-response-cost` header,
15
+ * OpenRouter `usage.cost`, `total_cost`, plus object-shaped `cost.total`)
16
+ * and its response id, then recorded as a custom session entry on
17
+ * `message_end`.
18
+ *
19
+ * Providers whose payloads carry no cost (first-party OpenAI/Anthropic due to
20
+ * their usage APIs) simply record nothing — their rate-card estimate matches
21
+ * their bill anyway when catalog prices are configured.
22
+ *
23
+ * The zentui footer prefers reconciled entries over the rate-card total when
24
+ * both exist for the same response id.
25
+ */
26
+
27
+ import type { ExtensionAPI } from "@selesai/code";
28
+
29
+ const ENTRY_TYPE = "cost-reconcile";
30
+ const ENTRY_VERSION = 1 as const;
31
+
32
+ // Guard against pathological bodies; real usage chunks arrive well under 1MB.
33
+ const MAX_CAPTURE_BYTES = 4 * 1024 * 1024;
34
+ // Only LLM API paths are tee'd and scanned; unrelated fetches (web fetch tool,
35
+ // model catalog refreshes) pass through untouched.
36
+ const LLM_PATH_RE = /(\/chat\/completions|\/responses|\/messages\b|:streamGenerateContent)/;
37
+ const MAX_CAPTURED_IDS = 50;
38
+ const MAX_PENDING_LOOKUPS = 200;
39
+ const MAX_SESSION_ENTRIES = 2_000;
40
+
41
+ type ReconcileEntry = {
42
+ version: typeof ENTRY_VERSION;
43
+ provider: string;
44
+ model: string;
45
+ responseId: string;
46
+ /** Provider-reported billed cost in USD. */
47
+ cost: number;
48
+ reconciledAt: number;
49
+ source: "payload";
50
+ };
51
+
52
+ /**
53
+ * Scan a captured SSE/JSON body for provider-reported cost and response ids.
54
+ * Exported for tests.
55
+ *
56
+ * In streams, usage lands in the final chunk, so the LAST cost match wins.
57
+ */
58
+ type MessageInfo = {
59
+ provider: string;
60
+ model: string;
61
+ responseId: string;
62
+ };
63
+
64
+ // Process-wide capture state. The fetch patch is installed once per process
65
+ // and must not capture session-bound `pi`; per-session message_end handlers
66
+ // read these maps and write entries through their own session's appendEntry.
67
+ const processCaptured = new Map<string, number>();
68
+ const processWaiting = new Map<
69
+ string,
70
+ { info: MessageInfo; write: (info: MessageInfo, cost: number) => void }
71
+ >();
72
+
73
+ /**
74
+ * Scan a captured SSE/JSON body for provider-reported cost and response ids.
75
+ * Pure function; exported for tests.
76
+ *
77
+ * In streams, usage lands in the final chunk, so the LAST cost match wins.
78
+ */
79
+ /**
80
+ * Parse a LiteLLM `x-litellm-response-cost` header value (a USD float,
81
+ * possibly in scientific notation). Exported for tests.
82
+ */
83
+ export function parseCostHeader(value: string | null): number | undefined {
84
+ if (value === null) return undefined;
85
+ const cost = Number(value);
86
+ return Number.isFinite(cost) && cost >= 0 ? cost : undefined;
87
+ }
88
+
89
+ export function extractCosts(
90
+ text: string,
91
+ ): { ids: string[]; cost?: number } {
92
+ const ids = new Set<string>();
93
+ for (const match of text.matchAll(/"id"\s*:\s*"([^"]{1,200})"/g)) {
94
+ ids.add(match[1]);
95
+ if (ids.size >= MAX_CAPTURED_IDS) break;
96
+ }
97
+
98
+ // OpenRouter native API uses `total_cost`; chat-completions streams use
99
+ // `cost` (number or `{total}`); gateways behind the responses API have
100
+ // been observed with object-shaped cost too.
101
+ let cost: number | undefined;
102
+ for (const match of text.matchAll(
103
+ /"total_cost"\s*:\s*(-?[\d.eE+]+)|"cost"\s*:\s*(-?[\d.eE+]+)|"cost"\s*:\s*\{[^}]*?"total"\s*:\s*(-?[\d.eE+]+)/g,
104
+ )) {
105
+ const value = Number(match[1] ?? match[2] ?? match[3]);
106
+ if (Number.isFinite(value) && value >= 0) cost = value;
107
+ }
108
+
109
+ return { ids: [...ids], cost };
110
+ }
111
+
112
+ function isLlmUrl(input: unknown): boolean {
113
+ try {
114
+ const url =
115
+ typeof input === "string"
116
+ ? input
117
+ : input instanceof URL
118
+ ? input.href
119
+ : input instanceof Request
120
+ ? input.url
121
+ : undefined;
122
+ if (!url) return false;
123
+ if (!/^https?:/i.test(url)) return false;
124
+ return LLM_PATH_RE.test(url);
125
+ } catch {
126
+ return false;
127
+ }
128
+ }
129
+
130
+ /** Install the capture fetch once per process, across extension reloads. */
131
+ const FETCH_PATCHED = Symbol.for("selesai.cost-reconcile.fetch-patched");
132
+
133
+ export default function costReconcileExtension(pi: ExtensionAPI): void {
134
+ /** Response ids already written to this session's file. */
135
+ const seen = new Set<string>();
136
+ let entriesWritten = 0;
137
+
138
+ const writeEntry = (info: MessageInfo, cost: number): void => {
139
+ if (seen.has(info.responseId) || entriesWritten >= MAX_SESSION_ENTRIES) return;
140
+ seen.add(info.responseId);
141
+ entriesWritten += 1;
142
+ const entry: ReconcileEntry = {
143
+ version: ENTRY_VERSION,
144
+ provider: info.provider,
145
+ model: info.model,
146
+ responseId: info.responseId,
147
+ cost,
148
+ reconciledAt: Date.now(),
149
+ source: "payload",
150
+ };
151
+ try {
152
+ pi.appendEntry(ENTRY_TYPE, entry);
153
+ } catch {
154
+ // transcript persistence failure must not break the session
155
+ }
156
+ };
157
+
158
+ const finishCapture = (rawBody: string, headerCost?: number): void => {
159
+ // ponytail: one naive regex pass over the whole body; a streaming
160
+ // parser only matters if bodies grow past the 4MB cap.
161
+ const { ids, cost } = extractCosts(rawBody);
162
+ // LiteLLM's header is the amount the gateway billed; prefer it over
163
+ // any cost the upstream payload reports.
164
+ const effectiveCost = headerCost ?? cost;
165
+ if (effectiveCost === undefined) return;
166
+ for (const responseId of ids) {
167
+ processCaptured.set(responseId, effectiveCost);
168
+ const waiting = processWaiting.get(responseId);
169
+ if (waiting) {
170
+ processWaiting.delete(responseId);
171
+ waiting.write(waiting.info, effectiveCost);
172
+ }
173
+ }
174
+ if (processCaptured.size > 500) {
175
+ for (const key of [...processCaptured.keys()].slice(0, processCaptured.size - 500))
176
+ processCaptured.delete(key);
177
+ }
178
+ };
179
+
180
+ pi.on("session_start", (_event, ctx) => {
181
+ try {
182
+ const entries =
183
+ (
184
+ ctx.sessionManager as {
185
+ getEntries?: () => readonly { type?: string; customType?: string; data?: ReconcileEntry }[];
186
+ }
187
+ ).getEntries?.() ?? [];
188
+ for (const entry of entries) {
189
+ if (entry?.type !== "custom" || entry?.customType !== ENTRY_TYPE) continue;
190
+ const responseId = entry?.data?.responseId;
191
+ if (typeof responseId === "string") seen.add(responseId);
192
+ }
193
+ } catch {
194
+ // best effort only
195
+ }
196
+
197
+ const globalFetch = globalThis as typeof globalThis & { [FETCH_PATCHED]?: boolean };
198
+ if (globalFetch[FETCH_PATCHED]) return;
199
+ globalFetch[FETCH_PATCHED] = true;
200
+ const realFetch = globalThis.fetch.bind(globalThis);
201
+ globalThis.fetch = async (input, init) => {
202
+ const response = await realFetch(input as Parameters<typeof realFetch>[0], init);
203
+ try {
204
+ if (!isLlmUrl(input)) return response;
205
+ const contentType = response.headers.get("content-type") ?? "";
206
+ if (!contentType.includes("json") && !contentType.includes("event-stream")) return response;
207
+ if (!response.body) return response;
208
+ const headerCost = parseCostHeader(response.headers.get("x-litellm-response-cost"));
209
+ const [main, tee] = response.body.tee();
210
+ void (async () => {
211
+ let raw = "";
212
+ try {
213
+ const reader = tee.getReader();
214
+ const decoder = new TextDecoder();
215
+ for (;;) {
216
+ const { done, value } = await reader.read();
217
+ if (done) break;
218
+ raw += decoder.decode(value, { stream: true });
219
+ if (raw.length > MAX_CAPTURE_BYTES) break;
220
+ }
221
+ } catch {
222
+ // capture is best-effort; the main branch is unaffected
223
+ }
224
+ finishCapture(raw, headerCost);
225
+ })();
226
+ // Preserve response identity as seen by the SDK: body swapped, rest identical.
227
+ return new Response(main, {
228
+ status: response.status,
229
+ statusText: response.statusText,
230
+ headers: response.headers,
231
+ });
232
+ } catch {
233
+ return response;
234
+ }
235
+ };
236
+ });
237
+
238
+ pi.on("message_end", (event) => {
239
+ const message = event.message as
240
+ | {
241
+ role?: string;
242
+ provider?: string;
243
+ model?: string;
244
+ responseId?: string;
245
+ stopReason?: string;
246
+ }
247
+ | undefined;
248
+ if (!message || message.role !== "assistant") return;
249
+ if (message.stopReason === "error" || message.stopReason === "aborted") return;
250
+ const responseId = message.responseId;
251
+ if (typeof responseId !== "string" || responseId.length === 0) return;
252
+
253
+ const info: MessageInfo = {
254
+ provider: message.provider ?? "",
255
+ model: message.responseModel ?? message.model ?? "",
256
+ responseId,
257
+ };
258
+ const cost = processCaptured.get(responseId);
259
+ if (cost !== undefined) {
260
+ writeEntry(info, cost);
261
+ return;
262
+ }
263
+ // The tee branch may finish after message_end; remember the message so
264
+ // the capture can match it on completion and write via this session.
265
+ if (!seen.has(responseId) && !processWaiting.has(responseId)) {
266
+ waitingLimitGuard();
267
+ processWaiting.set(responseId, { info, write: writeEntry });
268
+ }
269
+ });
270
+ }
271
+
272
+ // Trim oldest waiting entries so unclaimed captures cannot grow unbounded.
273
+ function waitingLimitGuard(): void {
274
+ if (processWaiting.size >= MAX_PENDING_LOOKUPS) {
275
+ for (const key of [...processWaiting.keys()].slice(0, processWaiting.size - MAX_PENDING_LOOKUPS + 1))
276
+ processWaiting.delete(key);
277
+ }
278
+ }
@@ -22,6 +22,7 @@ function setup() {
22
22
  handler = registeredHandler;
23
23
  }),
24
24
  registerTool: vi.fn((tool: { name: string }) => active.add(tool.name)),
25
+ getAllTools: vi.fn(() => [...active].map((name) => ({ name }))),
25
26
  };
26
27
  enableReadonlyTools(pi as any);
27
28
  return { active, handler, pi };
@@ -49,4 +50,14 @@ describe("enable-readonly-tools", () => {
49
50
  expect(pi.registerTool).toHaveBeenCalledTimes(3);
50
51
  expect(createGrepToolDefinition).toHaveBeenCalledWith("/project");
51
52
  });
53
+
54
+ it("does not replace a display extension's existing readonly renderers", () => {
55
+ const { active, handler, pi } = setup();
56
+ active.add("grep");
57
+ active.add("find");
58
+ active.add("ls");
59
+
60
+ handler({ toolName: "read", isError: false }, { cwd: "/project" });
61
+ expect(pi.registerTool).not.toHaveBeenCalled();
62
+ });
52
63
  });
@@ -13,8 +13,12 @@ export default function (pi: ExtensionAPI) {
13
13
  pi.on("tool_execution_end", (event, ctx) => {
14
14
  if (enabled || event.isError || !bootstrapTools.has(event.toolName)) return;
15
15
  enabled = true;
16
- pi.registerTool(createGrepToolDefinition(ctx.cwd));
17
- pi.registerTool(createFindToolDefinition(ctx.cwd));
18
- pi.registerTool(createLsToolDefinition(ctx.cwd));
16
+
17
+ // pi-tool-display may already own these renderer definitions. Re-registering
18
+ // Pi's plain built-ins here silently discarded its boxed grep/find/ls views.
19
+ const active = new Set(pi.getAllTools().map((tool) => tool.name));
20
+ if (!active.has("grep")) pi.registerTool(createGrepToolDefinition(ctx.cwd));
21
+ if (!active.has("find")) pi.registerTool(createFindToolDefinition(ctx.cwd));
22
+ if (!active.has("ls")) pi.registerTool(createLsToolDefinition(ctx.cwd));
19
23
  });
20
24
  }
@@ -6,8 +6,10 @@
6
6
  "pi": {
7
7
  "extensions": [
8
8
  "./agent-browser.ts",
9
+ "./auto-session-name.ts",
9
10
  "./copy-turn.ts",
10
11
  "./context-compaction-reminder.ts",
12
+ "./cost-reconcile.ts",
11
13
  "./enable-readonly-tools.ts",
12
14
  "./pi-intercom/index.ts",
13
15
  "./ponytail/index.js",
@@ -20,6 +22,7 @@
20
22
  "./tokenin-onboarding.ts",
21
23
  "./undo.ts",
22
24
  "./pi-subagents",
25
+ "./pi-tool-display",
23
26
  "./pi-web-agent",
24
27
  "./web-agent-onboarding.ts",
25
28
  "./tps.ts",
@@ -0,0 +1,329 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+ - Added a `boxed` preset and `boxedToolCalls` config toggle that renders each built-in tool call and its result inside an ASCII-bordered box, matching the startup resource-list style.
12
+
13
+ ## [0.5.0] - 2026-07-03
14
+
15
+ ### Added
16
+ - Added an `enabled` config toggle that gates tool override registration, with reload cleanup that disposes overrides and patches on `session_shutdown`. ([c78163d](https://github.com/MasuRii/pi-tool-display/commit/c78163dddc0f94b7a542d2d1e01109c903bc70cc))
17
+ - Added regression coverage for the active backlog: expanded large-diff rendering in constrained tmux-style panes (#23) and the esbuild lockfile bump from PR #24. ([7e46231](https://github.com/MasuRii/pi-tool-display/commit/7e4623191583f31a056602d8a08f1a4a7accd8b6))
18
+
19
+ ### Changed
20
+ - Widened Pi coding-agent and Pi TUI peer dependency ranges through `^0.80.0` and added a `postinstall` patch with npm `overrides` to resolve known vulnerabilities in transitive dependencies. ([5f753a9](https://github.com/MasuRii/pi-tool-display/commit/5f753a9407fca6c2a7463d90eede01ce056c7bbc))
21
+ - Extracted render helpers and consolidated tool override logic to reduce inline duplication. ([3239d7a](https://github.com/MasuRii/pi-tool-display/commit/3239d7a825fe14e5cdcac86c75dd4b21aec0018c))
22
+ - Updated the lockfile-resolved `esbuild` dependency from `0.28.0` to `0.28.1` via Dependabot PR #24. ([caf65f2](https://github.com/MasuRii/pi-tool-display/commit/caf65f209c49ddcfc8362ff95c58a6a91cd1ba03))
23
+ - Updated README badge styling and added a ko-fi support button. ([2094fb6](https://github.com/MasuRii/pi-tool-display/commit/2094fb64ca4e79ff7d947d0f8be2f2d9ea967fe2))
24
+
25
+ ### Fixed
26
+ - Capped expanded edit/write diff bodies with the existing `expandedPreviewMaxLines` setting and a visible omission hint so large diffs stay bounded in small tmux panes (#23). Thanks to @jmikedupont2 for reporting. ([7e46231](https://github.com/MasuRii/pi-tool-display/commit/7e4623191583f31a056602d8a08f1a4a7accd8b6))
27
+
28
+ ## [0.4.3] - 2026-06-16
29
+
30
+ ### Added
31
+ - Added `customToolOverrides` for explicit opt-in rendering of non-built-in extension tools, with `generic` as the default kind and optional `mcp` rendering for MCP proxy-style arguments.
32
+ - Added custom tool override coverage for malformed config, output modes, late tool registration, argument shapes, and runtime contract preservation.
33
+
34
+ ### Changed
35
+ - Preserved configured MCP output mode even when MCP tools are not detected at startup, so dynamically registered MCP tools can still be decorated later.
36
+
37
+ ### Fixed
38
+ - Bash tool display overrides now preserve Pi `settings.json` shell settings (`shellPath` and `shellCommandPrefix`) when rebuilding the bash tool.
39
+
40
+ ## [0.4.2] - 2026-06-01
41
+
42
+ ### Changed
43
+ - Deferred config modal, settings inspector, and built-in tool metadata loading until needed to reduce startup work.
44
+ - Replaced shared agent-directory lookup with a local `PI_CODING_AGENT_DIR`-aware resolver for config and capability checks.
45
+ - Widened peer dependency ranges to `^0.74.0 || ^0.75.0 || ^0.77.0 || ^0.78.0`.
46
+
47
+ ### Fixed
48
+ - Corrected classic-mode diff line-number gutter spacing.
49
+
50
+ ## [0.4.1] - 2026-05-26
51
+
52
+ ### Added
53
+ - Reload-safe extension lifecycle: `src/disposable.ts` cleanup registry that disposes all tool overrides, prototype patches, timers, and event handlers on `session_shutdown(reason: "reload")`, preventing orphaned pi-mono default rendering after `/reload`.
54
+ - Comprehensive test suite with 15 new test files covering reload behavior, bash display, MCP overrides, ANSI utilities, diff renderer edge cases, user message boxes, thinking labels, render utilities, and integration tests (696 total tests, up from 68).
55
+
56
+ ### Fixed
57
+ - Bash display now respects `shellPath` and `commandPrefix` from `settings.json` when present (#21).
58
+ - Bash spinner timer reworked to use toolCallId-keyed Map instead of `__piToolDisplayBashSpinner`, with interval reduced from 80ms to 200ms, defensive `invalidate()` check, and all timers registered in the cleanup registry (#19).
59
+ - Bash override registration is now deferred (like read/grep/edit) and uses `before_agent_start` to discover ownership via `pi.getAllTools()` before overriding, preventing conflicts with other extensions (#17). Thanks to @iwinux for reporting.
60
+ - `pi.registerTool` is now intercepted to decorate MCP tools as they register, eliminating a race condition where `session_start`/`before_agent_start` fire before `pi-mcp-adapter` finishes registering tools (#15, #18). Thanks to @dashanlkk for reporting and opening PR #18.
61
+ - `isMcpToolCandidate()` heuristics expanded to match `mcp`, `mcp_*`, `*_mcp`, names containing `server:` or starting with `ctx_`, and parameter schemas containing `mcpServer`, `serverUrl`, or `server_name`, catching many MCP servers that were previously false negatives.
62
+ - `stripBackgroundSgrParams()` now correctly preserves foreground RGB sequences like `38;2;12;49;200m` instead of misinterpreting color component `49` as a background reset (#8, #3). Thanks to @michaelrommel for the patch and @w-winter for reporting.
63
+ - `patchUserMessageRenderPrototype` now restores stale patches from prior extension instances before re-patching, and `registerNativeUserMessageBox` has a duplicate-prevention guard with `session_shutdown` restoration (#10). OSC 133 stripping is now scoped to prompt-control sequences only; OSC 8 hyperlinks are preserved. Thanks to @w-winter for reporting.
64
+ - Thinking label duplicate-prevention guard prevents re-registering event handlers across reloads; `session_shutdown(reason: "reload")` resets the guard so re-registration works after reload; recursive nested-array handling added for malformed thinking content (#2). Thanks to @agustif for PR #2.
65
+ - `registerDeferredBuiltInToolOverrides()` is now also called on `session_start` (not just `before_agent_start`), fixing a reload bug where read/grep/edit/bash tools fell back to default pi-mono rendering.
66
+
67
+ ## [0.4.0] - 2026-05-22
68
+
69
+ ### Added
70
+ - Added the `./tool-display-api-consumer` subpath export so other extensions can decorate tool definitions through the runtime tool-display API or queue decorations until `pi-tool-display` is loaded.
71
+ - Added hashline-anchor-aware diff rendering so read/edit anchor lines can display their `LINE#HASH` labels in the diff gutter.
72
+
73
+ ### Changed
74
+ - Deferred built-in tool override registration until the built-in owner is available and refreshed cached built-in tools on session lifecycle changes.
75
+ - Redacted secret-like debug payload values and switched debug writes to asynchronous buffered file logging.
76
+
77
+ ## [0.3.6] - 2026-05-04
78
+
79
+ ### Added
80
+ - Documented the `debug` config flag for opt-in file diagnostics under the runtime-created `debug/` directory with terminal debug output kept disabled.
81
+ - Added regression coverage for config store isolation, pending diff preview safety, tool override registration, presets, and thinking label rendering.
82
+
83
+ ### Changed
84
+ - Scoped projected pending edit and write previews to the active workspace before reading files, with clear fallback notices when previews cannot be resolved safely.
85
+ - Shared ANSI sanitization and width-safety helpers across diff and user message rendering for more consistent narrow-pane output.
86
+
87
+ ### Fixed
88
+ - Hardened pending write metadata tracking so preview and execution state do not leak across tool call lifecycles.
89
+ - Improved tool override preview reads and write state handling for safer partial-render updates.
90
+
91
+ ## [0.3.5] - 2026-04-27
92
+
93
+ ### Changed
94
+ - Removed the deleted bundled screenshot asset from published package contents and removed the corresponding README project-structure reference to `assets/pi-tool-display.png`.
95
+
96
+ ## [0.3.4] - 2026-04-24
97
+
98
+ ### Added
99
+ - Added projected pending diff previews for partial `edit` and `write` tool calls so the TUI can show `pending edit`, `pending overwrite`, and `pending create` diffs before execution finishes
100
+ - Added preview fallback notices when projected edit previews cannot be resolved deterministically from the current file contents
101
+
102
+ ### Changed
103
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to `^0.70.2`
104
+ - Diff renderer write headers now support contextual action labels so pending previews can display `pending edit`, `pending overwrite`, and `pending create`
105
+
106
+ ### Fixed
107
+ - Restored native user message box spacing on recent Pi releases by extracting markdown through the newer nested `Box` wrapper and stripping OSC 133 prompt markers from fallback content normalization
108
+ - Limited fallback OSC stripping to OSC 133 prompt markers so OSC 8 hyperlinks and other non-prompt OSC sequences remain intact in user message rendering
109
+
110
+ ## [0.3.2] - 2026-04-15
111
+
112
+ ### Added
113
+ - `diffIndicatorMode` config option with three styles: `bars` (persistent vertical indicators), `classic` (+/- markers on first row only), and `none` (no indicator column)
114
+ - Config modal dropdown for diff indicator style selection under "Diff indicators" setting
115
+
116
+ ### Changed
117
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to `^0.67.2`
118
+ - Config path resolution now uses `getAgentDir()` API to correctly respect `PI_CODING_AGENT_DIR` environment variable (thanks to @tynanbe for PR #6)
119
+ - Diff renderer now supports mode-aware indicator glyph resolution (bars, classic, none)
120
+ - Line prefix width calculations adjusted per indicator mode for accurate diff column alignment
121
+ - Removed unused `session_switch` listener from native user message box registration
122
+ - Added top margin line to native user message box rendering (thanks to @w-winter for the suggestion)
123
+ - Rebalanced diff row and inline emphasis background mixing for more consistent added/removed line readability
124
+
125
+ ### Fixed
126
+ - Diff indicator markers now render correctly across all indicator modes with proper continuation handling
127
+ - Classic mode now shows +/- only on first visual row, with spacing on wrapped continuation lines
128
+ - Corrected ANSI background reset detection so RGB color sequences containing component value `49` no longer break inline diff emphasis background rendering (thanks to @michaelrommel for reporting issue #8)
129
+
130
+ ## [0.3.1] - 2026-04-01
131
+
132
+ ### Changed
133
+ - Updated npm keywords and package metadata for improved discoverability
134
+ - Added Related Pi Extensions cross-linking section to README
135
+
136
+ ## [0.3.0] - 2026-04-01
137
+
138
+ ### Added
139
+ - `prepareArguments` delegate support for built-in tool overrides (read, grep, find, ls, edit, write, bash)
140
+ - `buildPromptSnippetFromDescription()` helper to derive prompt snippets from MCP tool descriptions
141
+ - MCP proxy prompt metadata (`MCP_PROXY_PROMPT_SNIPPET`, `MCP_PROXY_PROMPT_GUIDELINES`) for tool registration
142
+ - Write execution metadata tracking via tool call ID for accurate diff rendering across execution lifecycle
143
+ - `applyLineBackgroundToWidth()` helper for consistent line background handling in diff renderer
144
+
145
+ ### Changed
146
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to ^0.64.0
147
+ - Refactored tool-overrides to use context-based argument extraction instead of closure state
148
+ - Improved diff renderer width handling with cleaner background reset logic
149
+ - Simplified continuation prefix rendering by removing unnecessary row background parameters
150
+ - Enhanced MCP proxy tool registration with proper prompt metadata propagation
151
+
152
+ ### Fixed
153
+ - Write diff rendering now correctly tracks previous content and file existence state across render phases
154
+ - Tool call rendering now uses context-based state instead of global mutable state
155
+
156
+ ### Tests
157
+ - Added tests for diff renderer width handling with line backgrounds
158
+ - Added tests for tool-overrides configuration and prepareArguments delegation
159
+
160
+ ## [0.2.0] - 2026-03-24
161
+
162
+ ### Added
163
+ - `bashOutputMode` config option with three modes: `opencode` (classic collapse), `summary` (line count only), `preview` (show lines)
164
+ - Live bash preview with spinner animation and elapsed time during command execution
165
+ - `bash-display.ts` module for bash call rendering with spinner state management
166
+ - `modal-icons.ts` module for Nerd Font detection and modal icon sets (`PI_NERD_FONTS` and `POWERLINE_NERD_FONTS` env vars)
167
+ - `settings-inspector-modal.ts` module with split-pane inspector UI (category list + setting details)
168
+ - Search icon in settings inspector modal filter hint (Nerd Font `\uF002` or emoji `🔍`)
169
+
170
+ ### Changed
171
+ - Settings modal now uses split-pane inspector with setting descriptions, summaries, and advanced notes
172
+ - Modal width increased to accommodate split-pane layout (wider terminals get more space)
173
+ - `showTruncationHints` config now defaults to `false`
174
+ - `showRtkCompactionHints` config now defaults to `false`
175
+ - Bash output now supports different rendering modes controlled by `bashOutputMode`
176
+ - Refactored config modal to use new inspector modal component instead of legacy settings modal
177
+
178
+ ### Config
179
+ - Added `bashOutputMode: "opencode" | "summary" | "preview"` to config schema
180
+ - Updated example config to demonstrate new `bashOutputMode` option
181
+ - Presets now include appropriate `bashOutputMode` values ("summary" for compact, "preview" for verbose)
182
+
183
+ ### Tests
184
+ - Added comprehensive tests for bash output modes (opencode, summary, preview)
185
+ - Added test coverage for spinner state management and elapsed time formatting
186
+ - Added tests for modal icon detection with various terminal environments
187
+
188
+ ## [0.1.12] - 2026-03-23
189
+
190
+ ### Added
191
+ - `tool-metadata.ts` module with shared utilities: `toRecord`, `getTextField`, `isMcpToolCandidate`, `extractPromptMetadata`
192
+ - `cloneToolParameters` function to deep-copy built-in tool parameter schemas for extension renderers
193
+ - Comprehensive tests for MCP detection, config guards, output modes, and metadata cloning
194
+ - Plural label support in search summaries
195
+ - Conditional truncation hints via `showTruncationHints` config (defaults to `false`)
196
+ - Keywords for better npm discoverability: `hide`, `collapse`, `truncate`, `compact`, `diff`, `output-mode`
197
+
198
+ ### Changed
199
+ - Updated `@mariozechner/pi-coding-agent` and `@mariozechner/pi-tui` peer dependencies to ^0.62.0
200
+ - Extracted shared utilities to dedicated `tool-metadata.ts` module for reuse across capabilities and tool-overrides
201
+ - Refactored tool-overrides to preserve `promptSnippet` and `promptGuidelines` on overridden read, edit, and write tools
202
+ - Improved diff renderer with accurate line number tracking and line number delta calculation for proper hunk tracking
203
+ - Removed Windows path normalization from system prompt sanitizer working-directory handling
204
+ - Simplified system prompt sanitizer to only handle documentation removal
205
+ - Enhanced package description to highlight hide/collapse/truncate capabilities for better npm discovery
206
+
207
+ ### Tests
208
+ - Added test suites for diff renderer numbering and wrap handling
209
+ - Added tests for tool-overrides config and registration behavior
210
+ - Added tests for capabilities module with MCP detection scenarios
211
+
212
+ ## [0.1.11] - 2026-03-13
213
+
214
+ ### Changed
215
+ - Refactored `sequenceAffectsBackground` to `sequenceResetsBackground` with simpler logic that only detects background reset sequences (codes 0 and 49)
216
+
217
+ ### Tests
218
+ - Added test coverage for diff-renderer width handling
219
+
220
+ ## [0.1.10] - 2026-03-13
221
+
222
+ ### Fixed
223
+ - Add npm override for file-type >=21.3.1 to resolve CVE (infinite loop in ASF parser)
224
+
225
+ ## [0.1.9] - 2026-03-13
226
+
227
+ ### Added
228
+ - Write overwrite diff guard to skip expensive diff computation for large files (4000+ lines or 1M+ cells)
229
+ - Cached user message markdown renderer to avoid rebuilding markdown for large content on every render
230
+ - Configurable limits for user message markdown (100K characters, 2000 lines)
231
+
232
+ ### Changed
233
+ - Improved performance for large write operations by using approximate stats instead of computing full diff
234
+ - User message markdown now bypasses rebuild for extremely large content (>100K chars or >2000 lines)
235
+ - Optimized write diff rendering to defer detailed data computation until actually needed
236
+
237
+ ### Fixed
238
+ - Prevented UI slowdown on very large file writes by showing guard message instead of computing expensive diffs
239
+ - Avoided redundant markdown parser instantiation for repeated renders of the same user message
240
+
241
+ ## [0.1.8] - 2026-03-12
242
+
243
+ ### Changed
244
+ - Extracted diff presentation logic into dedicated `diff-presentation.ts` module with `DiffPresentationMode`, `buildDiffSummaryText`, `normalizeDiffRenderWidth`, and `resolveDiffPresentationMode` utilities
245
+ - Improved compact line rendering with dedicated marker and prefix functions
246
+ - Added width-safe diff rendering utilities for consistent terminal width handling
247
+
248
+ ## [0.1.7] - 2026-03-07
249
+
250
+ ### Added
251
+ - Added line-width safety utilities for diff rendering so collapsed and expanded diff output can be clamped to the current pane width.
252
+ - Added utility test coverage for write display helpers, native user message box helpers, and narrow-width diff hint behavior.
253
+
254
+ ### Changed
255
+ - Updated README documentation to reflect the current command surface, config model, width-safe diff behavior, native user message box pipeline, and project structure.
256
+ - Refactored native user message box rendering into focused markdown, patching, renderer, and ANSI/background utility modules.
257
+
258
+ ### Fixed
259
+ - Prevented diff rendering and collapsed diff hints from overflowing narrow terminal widths by progressively shortening hint text and clamping rendered lines.
260
+ - Preserved inline `write` call summaries with line-count and byte-size metadata when content is available.
261
+ - Prevented thinking label presentation changes from leaking into future assistant context by sanitizing stored thinking blocks during the `context` extension event.
262
+ - Hardened thinking label normalization to strip ANSI residue fragments such as `38;5;208m` before display formatting.
263
+ - Restored final-message thinking label persistence on `message_end` so themed labels remain consistent after streaming and across session reloads.
264
+ - Improved native user message box rendering so markdown content, ANSI-only blank lines, and background fill behave more consistently.
265
+
266
+ ## [0.1.6] - 2026-03-04
267
+
268
+ ### Fixed
269
+ - Use absolute GitHub raw URL for README image to fix npm display
270
+
271
+ ## [0.1.5] - 2026-03-04
272
+
273
+ ### Added
274
+ - Thinking labels feature that prefixes AI reasoning blocks with themed "Thinking:" labels for better readability
275
+
276
+ ### Changed
277
+ - Rewrote README.md with professional documentation standards
278
+ - Added comprehensive feature documentation, configuration reference, and usage examples
279
+ - Simplified settings modal by removing less-used advanced options (expandedPreviewMaxLines, diffSplitMinWidth, diffCollapsedLines, tool ownership toggles)
280
+
281
+ ## [0.1.4] - 2026-03-02
282
+
283
+ ### Added
284
+ - Auto-detection of MCP and RTK capabilities to conditionally expose related UI/config controls.
285
+
286
+ ### Changed
287
+ - `/tool-display` modal now hides MCP settings when MCP tooling is unavailable.
288
+ - `/tool-display` modal now hides RTK compaction hint settings when RTK optimizer is unavailable.
289
+ - Runtime rendering now force-disables MCP output mode and RTK hint rendering when those capabilities are unavailable.
290
+ - Native user message box is now user-configurable via `enableNativeUserMessageBox` in config and `/tool-display` settings.
291
+
292
+ ## [0.1.3] - 2026-03-02
293
+
294
+ ### Added
295
+ - Added per-tool ownership config via `registerToolOverrides` for `read`, `grep`, `find`, `ls`, `bash`, `edit`, and `write` so users can avoid tool ownership conflicts with other extensions.
296
+ - Added settings modal toggles for built-in tool ownership and `/reload` guidance when ownership changes.
297
+ - Added backward-compatible config migration from legacy `registerReadToolOverride` to `registerToolOverrides.read`.
298
+
299
+ ### Changed
300
+ - Built-in tool override registration is now conditional per tool based on ownership settings.
301
+ - Updated README configuration/troubleshooting docs for multi-tool extension compatibility.
302
+
303
+ ## [0.1.2] - 2026-03-01
304
+
305
+ ### Fixed
306
+ - Corrected `write` call rendering state handling so path changes without new content no longer reuse stale line/size metadata from previous writes.
307
+ - Restored write call suffix rendering (`(X lines, Y)`) when content is available, improving call summary consistency.
308
+
309
+ ## [0.1.1] - 2026-03-01
310
+
311
+ ### Changed
312
+ - Reorganized repository layout to a cleaner package structure:
313
+ - moved implementation modules to `src/`
314
+ - moved screenshot assets to `assets/`
315
+ - moved example config to `config/`
316
+ - kept root `index.ts` as stable Pi auto-discovery entrypoint.
317
+ - Simplified TypeScript build command to use `tsconfig.json` project mode.
318
+ - Updated README installation heading now that npm package is published.
319
+
320
+ ## [0.1.0] - 2026-03-01
321
+
322
+ ### Added
323
+ - Public repository scaffolding (`README.md`, `LICENSE`, `CHANGELOG.md`, `.gitignore`, `.npmignore`).
324
+ - Package metadata for public distribution (`keywords`, `files`, `license`, `publishConfig`, engine constraints).
325
+ - Vendored `zellij-modal.ts` to keep this extension self-contained as a standalone repository.
326
+
327
+ ### Changed
328
+ - Updated `config-modal.ts` to use local `zellij-modal.ts` import.
329
+ - Updated build script to include `zellij-modal.ts`.