@clapecho233/pi-smart-fold
v0.2.3
Published
pi coding-agent extension: collapse tool output at startup and fold thinking blocks to a live last line
Maintainers
Readme
pi-smart-fold
A pi coding-agent extension that keeps the session transcript compact — without losing key information.
| Without smart-fold | With smart-fold |
| --- | --- |
| Long thinking walls, verbose tool output, full file previews | One-line Thought for 12.4s summaries, folded tool output, write src/index.ts +12 -3 headers |
Features
1. Thinking fold + live timer (smart mode, default)
While the model thinks, the block shows a bold Thinking… (8s) header line, followed by the scrolling tail of the thinking text (like tail -f). One click toggles directly to the full text so far (the ticking Thinking… (8s) line stays pinned at the bottom and keeps updating); one more click toggles back to the scrolling tail. There is no intermediate label state — a single click always switches between scrolling tail ↔ full view:
Scrolling tail (default) Full view (after one click)
Thinking… (8s) …earlier thinking text…
…latest line of thinking …thinking still streaming…
Thinking… (9s) ← pinned at the bottom, live-updatingAfter the thinking run finishes, the block collapses to a single bold Thought for 12.4s line (measured duration). If the block was in full view at that moment, it transitions smoothly to the pinned duration footer first.
Single-click expand/collapse per block: finished thinking starts hidden (seeded by a prototype patch). One click → that block expands to its full text (with the bold duration line kept at the bottom); one more click → collapses back. No intermediate states, and only the clicked block is affected — other thinking runs in the same message are untouched. The same one-click toggle works for a run that already finished while its message is still streaming (e.g. thinking done, answer text or a tool call still coming): the folded Thought for … line expands directly — never through pi's bare Thought… label middle state.
- Fully takes over native folding: pi's built-in hidden-thinking label (
Thinking...) is replaced with per-messageThought for …labels generated by this extension. What you see after clicks orctrl+tis always our text, never the native one. (Insmartmodectrl+thas no extra effect — use clicks or/fold expandinstead.) - Exact click detection: an idempotent prototype patch on pi's publicly exported
AssistantMessageComponentseeds the hidden state of finished thinking runs and observes writes to the component's internal visibility map (distinguishingctrl+t's clear operation). Clicks on runs of a still-streaming message — the live run as well as already-finished ones — are redirected so the native two-state toggle's hidden middle state never appears. Global re-renders — theme changes, window resizes, layout redraws — are never mistaken for clicks. If a future pi version can't be patched, the extension degrades gracefully to pi's native display (with/fold expandas a fallback). - Persistent durations: each thinking run's duration is recorded in the session file keyed by content hash (a
smart-fold-thinkingcustom entry, never sent to the LLM context), so durations still show after/resume. Multiple thinking runs within one message (between tool calls) are timed individually. - Code-block safe: while a code fence streams through the thinking text, structural fence lines are skipped in the collapsed tail — the block never flashes fully open for a frame. In the expanded view an unclosed code fence is closed automatically so the
Thinking…/Thought for …footer renders below it as bold text, never swallowed into the code block as literal**asterisks**. - Truncation is measured in terminal display columns (CJK characters / emoji count as 2), keeping the line ending with an
…prefix when over-wide. - Folding is display-only: the session file and the context sent to the model are never modified.
Other modes: tail (always one line — the text tail with a bold duration prefix), full (show full text after the run ends, with the duration footer), off (disable and restore pi's default thinking display).
Fallback: /fold expand on|off (or "Expand all thinking" in the settings panel) temporarily expands/collapses all finished thinking blocks. Daily usage only needs single clicks on individual blocks; new sessions always start fully folded.
2. Tool output folding
On every session_start (startup, /reload, /new, /resume, /fork) the extension calls ctx.ui.setToolsExpanded(false) so tool output stays collapsed; press ctrl+o to expand manually.
3. Tool call line truncation + write/edit diff stats
- Long call lines are truncated:
bash/read/grep/find/lscall lines collapse to a single line, cut to the terminal width with a trailing…(pi's ANSI-awaretruncateToWidth). An extra…marker is appended when the command has more lines. Click orctrl+oto expand and see everything. - write / edit line-diff stats:
write: reads the original file content before the write executes and computes a line-level diff (common prefix/suffix trimming + LCS). The header line gains a green+added/ red-removedsuffix, e.g.write src/index.ts +12 -3; new files show+N -0. Files larger than 8 MB are skipped. Stats are persisted with the tool result in the session file, so they still show in restored sessions (writes made before this version have no record and show nothing).edit: diffs eachedits[]entry'soldText → newTextdirectly and sums the result, e.g.edit src/app.ts +2 -1(updates live while arguments stream in).
- write/edit collapse to a header-only line by default (
writeCollapsed: header): collapsed rows show just the one-line header — no code content or diff preview (not natively possible in pi; implemented by wrapping bothrenderCallandrenderResult— the result area renders an empty component while collapsed, and error messages remain visible). Click /ctrl+oexpands to the full diff / syntax-highlighted content. The+N -Mstat stays in the header line in both states (edit's native full-width background bar is preserved; the stat is injected into the trailing padding without changing the line width). Switch topreviewin settings to restore pi's native preview. - Implemented as rendering wrappers around pi's built-in tools (execution logic is fully reused from each
create*ToolDefinition).
Settings
/fold Open a /config-style interactive settings list (Enter/Space to change, Esc to close)Or change values directly (with autocompletion):
/fold thinking smart|tail|full|off # on=smart, off=off accepted for compatibility
/fold tools on|off # fold tool output at session start
/fold writestat on|off # write/edit diff stats toggle
/fold writecollapsed header|preview # write rows: header-only / keep preview when collapsedConfig file smart-fold.config.json (next to the extension entry; the legacy boolean thinkingFold is migrated automatically):
{
"toolsFold": true,
"thinking": "smart",
"writeStat": true,
"writeCollapsed": "header"
}Installation
Any one of these:
# Option A: clone into pi's global extension auto-discovery directory
git clone <this-repo> ~/.pi/agent/extensions/smart-fold
# Option B: install via the pi package manager
pi install git:<repo-url>
# Option B2: install from npm
pi install @clapecho233/pi-smart-fold
# Option C: add to settings.json
# ~/.pi/agent/settings.json → { "extensions": ["/path/to/pi-smart-fold"] }
# Try it temporarily
pi -e /path/to/pi-smart-fold/index.tsThe extension has no npm dependencies — pi loads the TypeScript directly via jiti.
Development
npm install # dev deps only — the package ships no install scripts (npm ≥11 warns / npm 12 blocks them)
npm run link:pi # link the installed pi runtime into node_modules (after clone; re-run after a pi upgrade)
npm run typecheck # strict tsc against the installed pi's .d.ts (no emit)
npm run check # link + typecheck + jiti load-check + unit tests + click simulation
npm test # unit tests for the pure functions (native TS type stripping, Node ≥ 22.18, no build step)
npm run test:sim # click-cycle simulation against the real pi AssistantMessageComponentThe extension itself has no npm dependencies — pi loads the TypeScript directly via jiti.
typescript / @types/node are dev-only, and node_modules/@earendil-works/* are symlinks into
the pi installation that loads the extension (created by scripts/link-pi.mjs, dev-only — at runtime
pi's jiti loader resolves @earendil-works/* imports itself, which is why the package ships no
install script), so typecheck always runs against the exact pi version installed on the machine.
Project layout:
index.ts Extension entry (transformer / events / write render wrappers / /fold settings UI)
lib/fold.ts Pure functions: display width, tail truncation, duration formatting, line diff
lib/thinking.ts Thinking timer state machine (per-run durations keyed by content hash)
lib/config.ts Config load/save (with legacy migration; falls back to defaults when missing/corrupt)
scripts/link-pi.mjs Symlink the installed pi runtime into node_modules (version-following)
scripts/load-check.mjs Smoke test: load the extension via pi's jiti and exercise registration
test/fold.test.mjs Unit tests
test/click-sim.mjs Click-cycle simulation (real pi component + simulated clicks, verifies tail ↔ full toggling)Known limitations
- Single-click toggling of thinking runs in a live message depends on the prototype patch's redirect; without the patch it degrades to pi's native two-state toggle (two clicks to expand).
- Expand state is runtime-only: new sessions/reloads start folded again. Window resizes never collapse blocks you've expanded.
- write diff stats only apply to writes executed in the current session (restored older sessions have no pre-execution snapshot to diff against).
- When stacked with pi's native "Hide thinking blocks" (
ctrl+ttoggle): if thinking is hidden that way, the live scrolling line won't show — keep the native display at its default and let this extension handle folding.
Compatibility
Built and verified against pi 1.0.0 public extension APIs: registerMarkdownTransformer,
ctx.ui.setToolsExpanded / setHiddenThinkingLabel, create*ToolDefinition (and friends),
registerTool (renderCall / renderResult + ToolRenderContext), registerCommand, appendEntry,
SettingsList, and the AssistantMessageComponent click-internals patch (updateContent,
thinkingVisibilityOverrides, hiddenThinkingLabel).
After upgrading pi, run npm run link:pi && npm run check — the links follow the newly installed
version automatically, and the check verifies types, jiti loading, and behavior against it.
