@pi-kaush/pi-tool-call-markers 0.3.8 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - Give the codemode tool its own `¢¢` anchor and collapsed label. Codemode
6
+ rows used to collapse to a script preview; they now show the nested
7
+ calls the script ran (repeats deduped to `name ×N`) followed by the
8
+ script's `// @options:` params — `¢¢ read ×2, fffind · "max_output_tokens":
9
+ 2500, "timeout_ms": 120000` — so the row says what the script did and
10
+ how it ran. While the script streams, the call list grows live; scripts
11
+ with neither calls nor a parsable options line keep the generic args
12
+ summary, and click-to-expand still restores the full script and result.
13
+
5
14
  - Render an asked question as a user-input moment instead of a raw tool row.
6
15
  `ask_user_question` used to collapse to `│ * {"questions":…`, which is a tool
7
16
  call marker doing the work of a prompt. An asked question now takes the
package/README.md CHANGED
@@ -21,7 +21,7 @@ Collapsed tool rows use semantic theme colors with no gear, background fill, box
21
21
  ```
22
22
 
23
23
  - **Two-column outer inset.** Tool markers and image output align with an inset conversation surface. Very narrow terminals reduce the decoration before useful content.
24
- - **`│` tool-call rail.** Every collapsed call anchors on a bold glyph instead of its tool name — `●` read, `+` write, `±` edit, `○` local search, `≡` ls, `↗` web search and fetch, and `$` bash; unmapped tools fall back to `*` — with a single space between the anchor and the call text, no joining colon. Grouped rows flow directly across tool types.
24
+ - **`│` tool-call rail.** Every collapsed call anchors on a bold glyph instead of its tool name — `●` read, `+` write, `±` edit, `○` local search, `≡` ls, `↗` web search and fetch, `¢¢` codemode, and `$` bash; unmapped tools fall back to `*` — with a single space between the anchor and the call text, no joining colon. Grouped rows flow directly across tool types.
25
25
  - **Semantic, low-contrast status.** The anchor and the call content share the collapsed mute, pending state is warning-colored, and failures remain error-colored. Ordinary tool states have no background.
26
26
  - **Width-safe outcome tails.** Long summaries truncate before useful tails such as `→ done`, `→ 42 lines`, `→ +2/-1`, or a `bash` duration.
27
27
  - **Stable running groups.** Adjacent calls group as they appear, including settled failures, which stay error-colored inside the group. Pending state and elapsed `bash` time settle into the final outcome without changing the row count.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-kaush/pi-tool-call-markers",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "Compact and group Pi tool calls, with a bundled extension for adjacent thinking blocks.",
5
5
  "license": "MIT",
6
6
  "author": "Kaushik Gopal",
package/src/index.ts CHANGED
@@ -42,6 +42,7 @@ const TOOL_CALL_GLYPHS: ReadonlyMap<string, string> = new Map([
42
42
  ["web_search", "↗"],
43
43
  ["fetch_content", "↗"],
44
44
  ["get_search_content", "↗"],
45
+ ["codemode", "¢¢"],
45
46
  ]);
46
47
  const PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v3");
47
48
  const LEGACY_PRESENTATION_PATCHED = Symbol.for("kg.pi.toolPresentation.v2");
@@ -835,6 +836,68 @@ function renderedCallSummary(
835
836
  : fgCollapsed(theme, "muted", "(no arguments)");
836
837
  }
837
838
 
839
+ // Codemode carries its tunables in the script's first line —
840
+ // `// @options: {"max_output_tokens": 2500, "timeout_ms": 120000}` — so a
841
+ // collapsed row shows those params instead of a script preview. The
842
+ // prefix and first-line handling mirror pi-codemode's parseCodemodeSource;
843
+ // anything absent or unparsable returns undefined so the row keeps the
844
+ // ordinary summary paths.
845
+ const CODEMODE_OPTIONS_PREFIX = "// @options:";
846
+
847
+ function codemodeOptionsLabel(row: ToolExecutionRow): string | undefined {
848
+ if (row.toolName !== "codemode") return undefined;
849
+ const code = (row.args as { code?: unknown } | undefined)?.code;
850
+ if (typeof code !== "string") return undefined;
851
+ const newline = code.indexOf("\n");
852
+ const head = newline === -1 ? code : code.slice(0, newline);
853
+ const firstLine = head.replace(/\r$/, "").trimStart();
854
+ if (!firstLine.startsWith(CODEMODE_OPTIONS_PREFIX)) return undefined;
855
+ const directive = firstLine.slice(CODEMODE_OPTIONS_PREFIX.length).trim();
856
+ let parsed: unknown;
857
+ try {
858
+ parsed = JSON.parse(directive);
859
+ } catch {
860
+ return undefined;
861
+ }
862
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
863
+ return undefined;
864
+ const params = Object.entries(parsed as Record<string, unknown>).map(
865
+ ([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)}`,
866
+ );
867
+ return params.length > 0 ? params.join(", ") : undefined;
868
+ }
869
+
870
+ // The nested calls a script makes stream through result.details.calls with
871
+ // a status per call — the clearest one-line account of what the script did
872
+ // (or is doing, while partial). Repeats collapse to `name ×N` in first-seen
873
+ // order; anything unshaped returns undefined.
874
+ function codemodeCallsSummary(row: ToolExecutionRow): string | undefined {
875
+ const calls = row.result?.details?.calls;
876
+ if (!Array.isArray(calls) || calls.length === 0) return undefined;
877
+ const counts = new Map<string, number>();
878
+ for (const call of calls) {
879
+ if (!isRecord(call)) continue;
880
+ const name = call.name;
881
+ if (typeof name !== "string" || name.length === 0) continue;
882
+ counts.set(name, (counts.get(name) ?? 0) + 1);
883
+ }
884
+ if (counts.size === 0) return undefined;
885
+ return [...counts]
886
+ .map(([name, count]) => (count > 1 ? `${name} ×${count}` : name))
887
+ .join(", ");
888
+ }
889
+
890
+ // Codemode's collapsed label pairs what the script did with how it ran:
891
+ // `read ×2, fffind · "max_output_tokens": 2500`. Without either part the row
892
+ // falls through to the ordinary summary paths.
893
+ function codemodeCallLabel(row: ToolExecutionRow): string | undefined {
894
+ if (row.toolName !== "codemode") return undefined;
895
+ const parts = [codemodeCallsSummary(row), codemodeOptionsLabel(row)].filter(
896
+ (part) => part !== undefined,
897
+ );
898
+ return parts.length > 0 ? parts.join(" · ") : undefined;
899
+ }
900
+
838
901
  // The collapsed-row seam lives here and nowhere else: which anchor a row
839
902
  // takes, how that anchor is styled, and the call text that follows it.
840
903
  // Anchors always render bold and take no joining colon — `│ ● src/a.ts`,
@@ -864,9 +927,13 @@ function collapsedSeam(
864
927
  // is gone once settled — while ordinary rows scrape the rendered call
865
928
  // line, which already drops its heading token.
866
929
  const selfRendered = row.getRenderShell?.() === "self";
867
- const source = selfRendered
868
- ? selfRenderedCallLabel(row)
869
- : renderedCallSummary(row, budget, theme);
930
+ // Codemode rows pair the script's nested calls with its `@options` params;
931
+ // with neither, they fall through to the ordinary summary paths.
932
+ const source =
933
+ codemodeCallLabel(row) ??
934
+ (selfRendered
935
+ ? selfRenderedCallLabel(row)
936
+ : renderedCallSummary(row, budget, theme));
870
937
  const plain = sanitizeInline(stripAnsi(source)).trim();
871
938
  // The anchor stands in for the label's leading token. Only self-rendered
872
939
  // labels still carry it; a subagent fallback keeps `subagent` as its own
@@ -148,10 +148,23 @@ type MarkdownLikeChild = {
148
148
  // the italic default style is what identifies the trace: Pi passes it for
149
149
  // thinking content and for nothing else. Callers gate on the row actually
150
150
  // carrying thinking, so no other italic block is touched.
151
- function unitalicizeSettledThinking(row: AssistantMessageRow): void {
151
+ // Pi 1.0 wraps each thinking child in a MouseRegion (click toggles
152
+ // visibility); older versions hang the Markdown/Text directly. Walk both
153
+ // shapes so the restyle reaches the node either way.
154
+ function contentNodes(row: AssistantMessageRow): unknown[] {
152
155
  const children = row.contentContainer?.children;
153
- if (!Array.isArray(children)) return;
156
+ if (!Array.isArray(children)) return [];
157
+ const nodes: unknown[] = [];
154
158
  for (const child of children) {
159
+ nodes.push(child);
160
+ const inner = (child as { child?: unknown } | undefined)?.child;
161
+ if (inner !== undefined && inner !== null) nodes.push(inner);
162
+ }
163
+ return nodes;
164
+ }
165
+
166
+ function unitalicizeSettledThinking(row: AssistantMessageRow): void {
167
+ for (const child of contentNodes(row)) {
155
168
  const markdown = child as MarkdownLikeChild | undefined;
156
169
  const style = markdown?.defaultTextStyle;
157
170
  if (style?.italic !== true) continue;
@@ -174,11 +187,9 @@ function restyleHiddenThinkingLabel(row: AssistantMessageRow): void {
174
187
  return;
175
188
  }
176
189
  const label = row.hiddenThinkingLabel;
177
- const children = row.contentContainer?.children;
178
- if (!Array.isArray(children)) return;
179
190
  if (!stylesThinkingLabels()) return;
180
191
  const styled = visibleThoughtLabel(label);
181
- for (const child of children) {
192
+ for (const child of contentNodes(row)) {
182
193
  const textChild = child as TextLikeChild | undefined;
183
194
  if (
184
195
  typeof textChild?.text !== "string" ||