little-coder 1.9.3 → 1.9.4

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.
@@ -8,6 +8,7 @@ import {
8
8
  type SubCoderResult,
9
9
  } from "./spawn.ts";
10
10
  import { SubCoderTracker } from "./tracker.ts";
11
+ import { truncateLineToWidth } from "../_shared/width.ts";
11
12
 
12
13
  // The `dispatch` tool: the main little-coder spawns isolated child little-coder
13
14
  // sessions ("sub-coders") to research a focused question — they read the repo
@@ -190,11 +191,22 @@ export default function (pi: ExtensionAPI) {
190
191
  });
191
192
  }
192
193
 
193
- /** A minimal pi-tui Component backed by precomputed lines. */
194
- function makeComponent(lines: string[]) {
194
+ /** A minimal pi-tui Component backed by precomputed lines.
195
+ *
196
+ * pi paints custom tool-result panels with a 1-char left margin + background-
197
+ * frame fill, so any line we hand back that's wider than `width - 1` overflows
198
+ * the terminal and crashes pi-tui (issue #51 / reopen of #48 — the dispatch
199
+ * renderer fed an unbounded sub-coder report sentence into the panel, and on
200
+ * `--resume` the same renderer paints session history, so the crash recurred
201
+ * even after v1.9.2 capped the *live* tracker). We respect the pi-supplied
202
+ * `width` here and truncate every line to `width - 2`, leaving a 2-char
203
+ * safety margin so wide unicode chars in the report can't sneak past our
204
+ * char-count-based visibleWidth approximation. */
205
+ export function makeComponent(lines: string[]) {
195
206
  return {
196
- render(_width: number): string[] {
197
- return lines;
207
+ render(width: number): string[] {
208
+ const cap = Math.max(1, width - 2);
209
+ return lines.map((l) => truncateLineToWidth(l, cap));
198
210
  },
199
211
  invalidate() {},
200
212
  };
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { makeComponent } from "./index.ts";
3
+
4
+ // Live regression for issue #51 (and the #48 reopen — same root cause from a
5
+ // different code path). pi paints the dispatch tool-result panel with a 1-char
6
+ // background-color left margin + fill; any line we return wider than
7
+ // `width - 1` overflows pi-tui and crashes the session, including on
8
+ // `--resume` because pi re-renders saved tool results from session history.
9
+ //
10
+ // The user's crash log showed a 134-char sub-coder report sentence rendered
11
+ // at terminal width 133 → 135 > 133. This test drives makeComponent at the
12
+ // same width with the same shape and asserts no emitted line exceeds.
13
+
14
+ const stripAnsi = (s: string) => s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
15
+ const visibleWidth = (s: string) => stripAnsi(s).length;
16
+
17
+ describe("issue #51 — dispatch renderResult doesn't overflow", () => {
18
+ it("caps a wide sub-coder report line to fit the pi-supplied width", () => {
19
+ const wideSentence =
20
+ "There is **no `rate_limits` table**. The entire file defines a single class, `ConversationStore`, which manages only one SQLite table:";
21
+ // Sanity: this is the exact 134-char shape from the user's crash log.
22
+ expect(wideSentence.length).toBeGreaterThan(133);
23
+ const comp = makeComponent([
24
+ "✓ Storage schema",
25
+ "**Report: `bot/storage.py` Schema Analysis**",
26
+ "",
27
+ wideSentence,
28
+ "",
29
+ " …",
30
+ "(Ctrl+O to expand)",
31
+ ]);
32
+ const out = comp.render(133);
33
+ const max = Math.max(...out.map((l) => visibleWidth(l)));
34
+ expect(max).toBeLessThanOrEqual(133);
35
+ // The truncated wide sentence keeps its prefix verbatim — it's not blanked
36
+ // out, just clipped with an ellipsis so the user can still read most of it.
37
+ const truncated = out[3];
38
+ expect(stripAnsi(truncated).startsWith("There is **no")).toBe(true);
39
+ });
40
+
41
+ it("survives a narrow terminal (40 cols) without throwing", () => {
42
+ const comp = makeComponent([
43
+ "very long content " + "x".repeat(500),
44
+ "another long line " + "y".repeat(200),
45
+ ]);
46
+ const out = comp.render(40);
47
+ expect(Math.max(...out.map((l) => visibleWidth(l)))).toBeLessThanOrEqual(40);
48
+ });
49
+
50
+ it("preserves short lines unchanged", () => {
51
+ const comp = makeComponent(["short", "tiny"]);
52
+ expect(comp.render(133)).toEqual(["short", "tiny"]);
53
+ });
54
+ });
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to little-coder are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and little-coder's public interface (CLI, providers, tools, skills) follows semver starting at `v0.0.1` post-rename.
4
4
 
5
+ ## [v1.9.4] — 2026-06-18
6
+
7
+ ### Fixed
8
+ - **Dispatch tool-result panel overflows the terminal on wide report lines** ([#51](https://github.com/itayinbarr/little-coder/issues/51), reopen of [#48](https://github.com/itayinbarr/little-coder/issues/48)). v1.9.2 capped every line the *live* sub-coder tracker emitted, but the **dispatch tool's result renderer** (`subagent/index.ts`'s `makeComponent`) was still ignoring the `width` arg pi passes to `render(width)` — it returned the precomputed lines verbatim. pi paints the tool-result panel with a 1-char background-color left margin, so any sub-coder report sentence wider than `terminal_width - 1` overflowed pi-tui. Crash log line 453 was a 134-char markdown sentence rendered at terminal width 133 → 135 > 133. The same path runs on **`--resume`** (pi re-paints saved tool results from session history), so v1.9.2 users still hit it after upgrading whenever they resumed a session with a wide dispatch report saved — that's why @steverhoades caught the regression. `makeComponent` now truncates every emitted line to `width - 2` using the existing `_shared/width.ts` utility (2-char safety margin for wide unicode under our char-count-based `visibleWidth` approximation), so the dispatch panel can no longer crash a session — live, on resume, or anywhere else. New `subagent/issue-51-repro.test.ts` drives `makeComponent` with the user's exact 134-char content shape at width 133 and asserts no emitted line exceeds, plus a narrow-terminal (40-col) survival check.
9
+
10
+ ### Notes for upgraders
11
+ - No CLI-flag or public-API changes. If you saw `Rendered line N exceeds terminal width` on v1.9.2 / 1.9.3 — especially while *resuming* a session — 1.9.4 fixes it. If you still see it after upgrading, the offending line in `~/.pi/agent/pi-crash.log` should let us spot the source; reopen #51 or #48 with the log attached.
12
+
13
+ ---
14
+
5
15
  ## [v1.9.3] — 2026-06-18
6
16
 
7
17
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "little-coder",
3
- "version": "1.9.3",
3
+ "version": "1.9.4",
4
4
  "description": "A pi-based coding agent optimized for small local language models. Reproduces the whitepaper's scaffold-model-fit adaptations as pi extensions.",
5
5
  "homepage": "https://github.com/itayinbarr/little-coder",
6
6
  "repository": {