pi-compact-tools 0.16.0 → 0.16.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
CHANGED
|
@@ -93,12 +93,12 @@ Optional, in `~/.pi/agent/compact-tools.json` or `<project>/.pi/compact-tools.js
|
|
|
93
93
|
Completed `codemode` rows in **Compact, Claude, and Codex** list the outcomes of the tools the script actually called, beside the duration and output-line count:
|
|
94
94
|
|
|
95
95
|
```text
|
|
96
|
-
Done in 1.234s (42 lines) ·
|
|
96
|
+
Done in 1.234s (42 lines) · read 3 · bash 1 · edit 3 2
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Each tool appears once with plain colored counts and no count parentheses or pipe: `read 3 · bash 1 · edit 3 2`. Single-outcome calls show one green success count, red failure count, or yellow unfinished-only count. Mixed success/failure calls show the green success count followed by the red failure count, separated only by a space; zero-count placeholders are omitted. Unfinished calls alongside settled calls add a separate yellow note such as `edit 3 2 1 unfinished`. Summary names retain their theme title hue at 75% brightness without bold, while outcome numbers keep their hues at 80% brightness. The main tool title and code preview remain unchanged. `Done in … (N lines)` uses a neutral tone one step brighter than the frame; spaces and `·` separators retain the dim frame chrome. There are no circles, leading bars, underlines, patch totals, or per-call diff lists. Non-edit groups retain first-occurrence order, and edit always appears last. Incomplete records show `(partial)`. Long summaries wrap to the pane width. Previews and author-provided expanded views remain available; expansion does not add patch details. Claude and Codex place the indented completion summary after output and keep disclosure last. Completion rows do not consume Claude’s output-preview limit.
|
|
100
100
|
|
|
101
|
-
The summary uses the host’s nested-call events and saved records
|
|
101
|
+
The summary uses the host’s nested-call events and saved records without changing what the model sees. It retains only tool names and statuses, never arguments, results, or edit diff totals. An in-process `/reload`, resumed sessions, and `/tree` navigation preserve or restore call counts and outcomes. Hosts without nested-call data keep the previous row rendering.
|
|
102
102
|
|
|
103
103
|
## UI-only behavior
|
|
104
104
|
|
|
@@ -141,7 +141,7 @@ export function paletteOf(theme: Theme): object {
|
|
|
141
141
|
|
|
142
142
|
/** Every token the derived colors are made from. */
|
|
143
143
|
const PALETTE_TOKENS: readonly ThemeColor[] = [
|
|
144
|
-
"text", "dim", "muted", "toolDiffAdded", "toolDiffRemoved",
|
|
144
|
+
"text", "dim", "muted", "toolTitle", "success", "error", "warning", "toolDiffAdded", "toolDiffRemoved",
|
|
145
145
|
"thinkingOff", "thinkingMinimal", "thinkingLow", "thinkingMedium", "thinkingHigh", "thinkingXhigh",
|
|
146
146
|
];
|
|
147
147
|
/** One object per palette seen; a session switches among a handful of themes at most. */
|
|
@@ -1,15 +1,14 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { stripTerminalSequences } from "@earendil-works/pi-tui";
|
|
3
|
-
import {
|
|
3
|
+
import { chromePainter, nestedCountPainter, nestedTitlePainter } from "./compact-tools-palette.ts";
|
|
4
4
|
|
|
5
5
|
const SUMMARY_KEY = Symbol.for("pi-compact-tools.nested-summaries.v1");
|
|
6
6
|
const MAX_PARENTS = 2_000;
|
|
7
7
|
const MAX_CALLS = 256;
|
|
8
|
-
const EDIT_PREVIEW_CALLS = 3;
|
|
9
8
|
const MAX_NAME_LENGTH = 256;
|
|
10
9
|
const MAX_ID_LENGTH = 1_024;
|
|
11
10
|
type Status = "ok" | "error" | "unfinished";
|
|
12
|
-
type Call = { name: string; status: Status
|
|
11
|
+
type Call = { name: string; status: Status };
|
|
13
12
|
type Summary = { calls: Map<string, Call>; partial: boolean; finished: boolean; live: boolean; revision: object };
|
|
14
13
|
type Shared = typeof globalThis & { [SUMMARY_KEY]?: Map<string, Summary> };
|
|
15
14
|
type NestedEvent = { toolCallId: string; toolName: string; parentToolCallId?: string; isError?: boolean; result?: unknown };
|
|
@@ -78,12 +77,8 @@ export class NestedCallSummaries {
|
|
|
78
77
|
return summary.finished;
|
|
79
78
|
}
|
|
80
79
|
const status = ended ? event.isError ? "error" : "ok" : "unfinished";
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
? countEditChanges(result as AgentToolResult<unknown>) : undefined;
|
|
84
|
-
if (previous?.name === event.toolName && previous.status === status
|
|
85
|
-
&& previous.changes?.added === changes?.added && previous.changes?.removed === changes?.removed) return false;
|
|
86
|
-
summary.calls.set(event.toolCallId, { name: event.toolName, status, ...(changes ? { changes } : {}) });
|
|
80
|
+
if (previous?.name === event.toolName && previous.status === status) return false;
|
|
81
|
+
summary.calls.set(event.toolCallId, { name: event.toolName, status });
|
|
87
82
|
summary.live = true;
|
|
88
83
|
summary.revision = {};
|
|
89
84
|
return summary.finished;
|
|
@@ -95,7 +90,7 @@ export class NestedCallSummaries {
|
|
|
95
90
|
const value = message as { role?: string; toolName?: string; toolCallId?: string; nestedCalls?: { calls?: unknown; complete?: unknown } };
|
|
96
91
|
if (value.role !== "toolResult" || value.toolName !== "codemode" || typeof value.toolCallId !== "string"
|
|
97
92
|
|| value.toolCallId.length > MAX_ID_LENGTH || !Array.isArray(value.nestedCalls?.calls)) return false;
|
|
98
|
-
// A bounded persisted snapshot must not
|
|
93
|
+
// A bounded persisted snapshot must not drop observed live calls.
|
|
99
94
|
const existing = this.summaries.get(value.toolCallId);
|
|
100
95
|
if (existing?.live && existing.calls.size > 0) return false;
|
|
101
96
|
const summary: Summary = { calls: new Map(), partial: value.nestedCalls.complete !== true, finished: true, live: false, revision: {} };
|
|
@@ -122,48 +117,45 @@ export class NestedCallSummaries {
|
|
|
122
117
|
}
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
/**
|
|
126
|
-
format(id: string, theme: Theme,
|
|
120
|
+
/** Other tool groups keep first-occurrence order; edit counts always come last. */
|
|
121
|
+
format(id: string, theme: Theme, _expanded = false): string | undefined {
|
|
127
122
|
const summary = this.summaries.get(id);
|
|
128
123
|
if (!summary || (summary.calls.size === 0 && !summary.partial)) return undefined;
|
|
129
|
-
|
|
124
|
+
type Group = { name: string; calls: Call[] };
|
|
125
|
+
const groups: Group[] = [];
|
|
126
|
+
const byTool = new Map<string, Group>();
|
|
130
127
|
for (const call of summary.calls.values()) {
|
|
131
|
-
const key =
|
|
132
|
-
let group =
|
|
133
|
-
if (!group)
|
|
128
|
+
const key = call.name;
|
|
129
|
+
let group = byTool.get(key);
|
|
130
|
+
if (!group) {
|
|
131
|
+
group = { name: call.name, calls: [] };
|
|
132
|
+
byTool.set(key, group);
|
|
133
|
+
groups.push(group);
|
|
134
|
+
}
|
|
134
135
|
group.calls.push(call);
|
|
135
136
|
}
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
const editIndex = groups.findIndex((group) => group.name === "edit");
|
|
138
|
+
if (editIndex >= 0) groups.push(...groups.splice(editIndex, 1));
|
|
139
|
+
const title = nestedTitlePainter(theme);
|
|
140
|
+
const chrome = chromePainter(theme);
|
|
141
|
+
const success = nestedCountPainter(theme, "success");
|
|
142
|
+
const error = nestedCountPainter(theme, "error");
|
|
143
|
+
const warning = nestedCountPainter(theme, "warning");
|
|
144
|
+
const parts = groups.map((group) => {
|
|
140
145
|
const name = stripTerminalSequences(group.name).replace(/[\x00-\x20\x7f-\x9f]/gu, " ").replace(/[\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff]/gu, "").trim() || "tool";
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
// The preview must not hide that an omitted edit failed or is unfinished.
|
|
155
|
-
const failed = hidden.filter((call) => call.status === "error").length;
|
|
156
|
-
const unfinished = hidden.filter((call) => call.status === "unfinished").length;
|
|
157
|
-
let more = theme.fg("dim", `… +${hidden.length} more`);
|
|
158
|
-
const statuses = [failed ? theme.fg("error", `${failed} failed`) : "", unfinished ? theme.fg("warning", `${unfinished} unfinished`) : ""].filter(Boolean);
|
|
159
|
-
if (statuses.length > 0) more += " (" + statuses.join(", ") + ")";
|
|
160
|
-
entries.push(more);
|
|
161
|
-
}
|
|
162
|
-
parts.push(`${name}(${group.calls.length}) [` + entries.join(theme.fg("dim", "|")) + "]");
|
|
163
|
-
} else parts.push(theme.fg(color, glyph) + ` ${name}(${group.calls.length})`);
|
|
164
|
-
}
|
|
165
|
-
if (summary.partial || [...summary.calls.values()].some((call) => call.status === "unfinished")) parts.push(theme.fg("warning", "(partial)"));
|
|
166
|
-
return parts.join(" ");
|
|
146
|
+
|
|
147
|
+
const successes = group.calls.filter((call) => call.status === "ok").length;
|
|
148
|
+
const failures = group.calls.filter((call) => call.status === "error").length;
|
|
149
|
+
const unfinished = group.calls.filter((call) => call.status === "unfinished").length;
|
|
150
|
+
const counts = [successes ? success(String(successes)) : "",
|
|
151
|
+
failures ? error(String(failures)) : ""].filter(Boolean);
|
|
152
|
+
const settled = counts.length > 0;
|
|
153
|
+
if (!settled) counts.push(warning(String(unfinished)));
|
|
154
|
+
return title(name) + chrome(" ") + counts.join(chrome(" "))
|
|
155
|
+
+ (settled && unfinished ? chrome(" ") + warning(`${unfinished} unfinished`) : "");
|
|
156
|
+
});
|
|
157
|
+
const partial = summary.partial || [...summary.calls.values()].some((call) => call.status === "unfinished");
|
|
158
|
+
return parts.join(chrome(" · ")) + (partial ? (parts.length ? " " : "") + warning("(partial)") : "");
|
|
167
159
|
}
|
|
168
160
|
|
|
169
161
|
private trim(): void {
|
|
@@ -38,10 +38,11 @@ type ContrastBand = { color: ThemeColor; keepHue: number; minimum: number; maxim
|
|
|
38
38
|
type ContrastRange = { minimum: number; maximum: number };
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
41
|
+
* A quiet frame tone for rails, connectors, and separators. Completion metadata
|
|
42
|
+
* sits one small brightness step above it so duration and line counts remain legible.
|
|
43
43
|
*/
|
|
44
44
|
const CHROME: ContrastBand = { color: "dim", keepHue: 0.35, minimum: 0.22, maximum: 0.28 };
|
|
45
|
+
const COMPLETION: ContrastBand = { ...CHROME, minimum: 0.26, maximum: 0.32 };
|
|
45
46
|
/** The indicator breathes either side of the chrome tone, dipping below it and peaking above. */
|
|
46
47
|
const PULSE_LOW: ContrastBand = { color: "dim", keepHue: 0.4, minimum: 0.18, maximum: 0.3 };
|
|
47
48
|
const PULSE_HIGH: ContrastBand = { color: "muted", keepHue: 0.4, minimum: 0.4, maximum: 0.6 };
|
|
@@ -105,7 +106,34 @@ export function chromePainter(theme: Theme): (text: string) => string {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
/**
|
|
109
|
+
/** Duration and line-count metadata is slightly brighter than rails and separators. */
|
|
110
|
+
export function completionPainter(theme: Theme): (text: string) => string {
|
|
111
|
+
return perPalette(theme, "completion-metadata", () => {
|
|
112
|
+
const color = bandedRgb(theme, COMPLETION);
|
|
113
|
+
return color ? rgbPainter(theme, color) : (text: string) => theme.fg("dim", text);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Nested names keep the title hue, slightly darker than ordinary titles, without bold. */
|
|
118
|
+
export function nestedTitlePainter(theme: Theme): (text: string) => string {
|
|
119
|
+
return perPalette(theme, "nested-title", () => {
|
|
120
|
+
const title = themeColorRgb(theme, "toolTitle");
|
|
121
|
+
const paint = title ? rgbPainter(theme, interpolateRgb(title, { r: 0, g: 0, b: 0 }, 0.25))
|
|
122
|
+
: (text: string) => theme.fg("toolTitle", text);
|
|
123
|
+
return paint;
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Quiet summary numbers retain outcome hues at 80% brightness. */
|
|
128
|
+
export function nestedCountPainter(theme: Theme, color: "success" | "error" | "warning"): (text: string) => string {
|
|
129
|
+
return perPalette(theme, `nested-count:${color}`, () => {
|
|
130
|
+
const rgb = themeColorRgb(theme, color);
|
|
131
|
+
return rgb ? rgbPainter(theme, interpolateRgb(rgb, { r: 0, g: 0, b: 0 }, 0.20))
|
|
132
|
+
: (text: string) => theme.fg(color, text);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Paint frame rails, connectors, separators, or running status in the quiet chrome tone. */
|
|
109
137
|
export function paintChrome(theme: Theme, text: string): string {
|
|
110
138
|
return chromePainter(theme)(text);
|
|
111
139
|
}
|
|
@@ -52,7 +52,7 @@ import { CODEX_BULLET, CODEX_INDENT } from "./compact-tools-codex.ts";
|
|
|
52
52
|
import { NestedCallSummaries } from "./compact-tools-nested.ts";
|
|
53
53
|
import { renderCodexCall, renderCodexResult } from "./compact-tools-codex-rows.ts";
|
|
54
54
|
import { ToolGroupController } from "./compact-tools-grouping.ts";
|
|
55
|
-
import { chromePainter, paintIndicator } from "./compact-tools-palette.ts";
|
|
55
|
+
import { chromePainter, completionPainter, paintIndicator } from "./compact-tools-palette.ts";
|
|
56
56
|
import { ProgressController } from "./compact-tools-progress.ts";
|
|
57
57
|
import { showReleaseNotice } from "./compact-tools-release.ts";
|
|
58
58
|
import { ToolRuntime } from "./compact-tools-runtime.ts";
|
|
@@ -190,9 +190,10 @@ function renderControls(
|
|
|
190
190
|
? (duration ?? "Running")
|
|
191
191
|
: `${isError ? "Failed" : "Done"}${duration ? ` in ${duration}` : ""}`;
|
|
192
192
|
// A failed row already reads as an error through its output color and Pi's row
|
|
193
|
-
// background, so
|
|
193
|
+
// background, so completion metadata stays neutral rather than repeating that signal.
|
|
194
194
|
const chrome = chromePainter(theme);
|
|
195
|
-
|
|
195
|
+
const metadata = running ? chrome : completionPainter(theme);
|
|
196
|
+
let details = metadata(status);
|
|
196
197
|
if (failureReason && isError && !running) details += chrome(" · ") + theme.fg("error", failureReason);
|
|
197
198
|
// An edit reports what it changed the way a diff does: lines added and removed.
|
|
198
199
|
else if (changes && !running && !isError) {
|
|
@@ -200,7 +201,7 @@ function renderControls(
|
|
|
200
201
|
+ theme.fg("toolDiffRemoved", `-${changes.removed}`) + chrome(")");
|
|
201
202
|
}
|
|
202
203
|
// A failed call produced no result worth counting; "(0 lines)" would only mislead.
|
|
203
|
-
else if (state.resultLineSummary && !running && !isError) details +=
|
|
204
|
+
else if (state.resultLineSummary && !running && !isError) details += metadata(` (${state.resultLineSummary})`);
|
|
204
205
|
if (nestedSummary && !running) details += chrome(" · ") + nestedSummary;
|
|
205
206
|
return nestedSummary ? prefixedSentence(details, chrome(prefix), indent, true) : prefixedText(details, chrome(prefix), indent);
|
|
206
207
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-compact-tools",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Tool rows for Pi in three styles: Claude, Codex, and Compact. Switch with /compact-tools; includes silent mode and a GitHub Dark theme.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/release-notes.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
{
|
|
2
|
+
"0.16.1": {
|
|
3
|
+
"New": [
|
|
4
|
+
"Minimal codemode summaries show each tool once: read 3 · bash 1 · edit 3 2; non-edit tools keep first-call order and edit always comes last",
|
|
5
|
+
"Plain counts use green for success, red for failure, and yellow for unfinished calls, without zero placeholders, count parentheses, status icons, pipes, or edit diff lists",
|
|
6
|
+
"Summary names retain their theme title hue at 75% brightness without bold; outcome counts retain their hues at 80% brightness",
|
|
7
|
+
"Completion duration and output-line metadata is one step brighter; rails, connectors, separators, main tool titles, and code previews are unchanged"
|
|
8
|
+
],
|
|
9
|
+
"Fix": [
|
|
10
|
+
"Interleaved nested calls aggregate into one group per tool, with success counts before failure counts",
|
|
11
|
+
"Reloads, resumed sessions, and /tree navigation preserve or restore nested counts and outcomes without retaining tool arguments, results, or edit diff bookkeeping",
|
|
12
|
+
"Unfinished calls and incomplete records remain visible, including when mixed with settled outcomes"
|
|
13
|
+
],
|
|
14
|
+
"Deprecated": []
|
|
15
|
+
},
|
|
2
16
|
"0.16.0": {
|
|
3
17
|
"New": [
|
|
4
18
|
"Completed codemode rows summarize nested tool successes and failures beside their duration and output-line count without changing model input",
|