pi-jar 0.2.8 → 0.3.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.
package/README.md CHANGED
@@ -2,13 +2,13 @@
2
2
 
3
3
  A warm, role-aware TUI and workflow extension for [Pi](https://github.com/earendil-works/pi). pi-jar turns the terminal into a more expressive agent workspace with a living pixel-fire welcome, an adaptive composer, switchable **profiles**, first-class **plan**, **goal** and **role** workflows, tracked tasks, reviewable changes, background shells and parallel subagents.
4
4
 
5
- > Works with the current `@earendil-works/*` Pi API. Pi's core packages are peer dependencies (`"*"`), so pi-jar always runs against the Pi you have installed; it is tested against the latest release (0.87.x).
5
+ > Works with the current `@earendil-works/*` Pi API. Pi's core packages are peer dependencies (`"*"`), so pi-jar always runs against the Pi you have installed; it is tested against Pi 1.2.0.
6
6
 
7
7
  ## See it in action
8
8
 
9
- ![pi-jar welcome: pixel flame over the π, the hero message and workspace card, and the composer with Ember](https://raw.githubusercontent.com/ygrip/pi-jar/main/docs/assets/pi-jar-demo.gif)
9
+ ![pi-jar 0.3.0: logo, profile switching, welcome dismissal, jar_ask choices, bouncing Ember and split diff](https://raw.githubusercontent.com/ygrip/pi-jar/main/docs/assets/pi-jar-demo.gif)
10
10
 
11
- The showcase is captured from a real pi-jar terminal session: the torch-style `π`, animated flame and embers, live workspace state, tasks, roles and composer are the actual TUI rather than a mockup. Capture and media guidelines live in [docs/SHOWCASE.md](docs/SHOWCASE.md).
11
+ [Watch the cinematic 77-second MP4](https://github.com/ygrip/pi-jar/blob/main/docs/assets/pi-jar-demo.mp4) for sharper text and the original soundtrack. The GIF is a shorter, silent preview. See actual profile switching, the welcome disappear after sending a prompt, and `jar_ask` choices. Terminal views are the real pi-jar TUI; model replies are scripted locally, and task, plan, code-change, mascot-reaction, idle-age and agent data are offline fixtures—not autonomous model execution. Recorded with isolated settings and no personal credentials, then framed with [fframes](https://fframes.studio). Details: [docs/SHOWCASE.md](docs/SHOWCASE.md).
12
12
 
13
13
  ## Highlights
14
14
 
@@ -25,7 +25,7 @@ The showcase is captured from a real pi-jar terminal session: the torch-style `
25
25
  | **Tasks & questions** | A Claude/omp-style `jar_todo` checklist with subtasks and per-task progress that the agent maintains itself, and `jar_ask` structured questions with options, multi-select and free-form answers. |
26
26
  | **Change review** | Every file the agent or its editing subagents change is remembered as it was; `/diff` (<kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>D</kbd>) shows changed files (each counted once) next to a colored diff, with accept or revert per file or all at once. |
27
27
  | **Background shells** | `jar_shell` runs dev servers, watchers and long tests in the background; watch/exit events wake an idle agent or reach it at the next safe turn boundary. Completion status appears immediately in the UI even during model reasoning. Running shells get a footer row; click it (or <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>A</kbd>) to tail or kill them. |
28
- | **Subagents** | The main agent becomes a moderator over a configurable retained pool (2, 4, 6, 8 or 16; default 2, or 4 in an opt-in Swarm profile). It delegates only when there are at least two independent chunks that each need about ten or more tool calls. `jar_delegate` starts `scout`, `fork` or sandboxed `worktree` agents and returns immediately, so the main agent can keep responding to user steering; Pi RPC event-bus messages deliver initial and resumed turn completions without polling. `jar_subagent` peeks, steers, asks BTW questions, pauses, resumes and stops them. Workers can exchange bounded structured Q/A through `jar_discuss`; worktree changes reach `/diff` only when the moderator stops a worker. |
28
+ | **Subagents** | The main agent becomes a moderator over a configurable retained pool (2, 4, 6, 8 or 16; default 2, or 4 in an opt-in Swarm profile). It identifies substantial independent tasks before implementation and delegates when parallel ownership saves work, without requiring guessed tool-call counts. Use `scout` for external research, `fork` for contextual read-only review, and `worktree` for isolated implementation. `jar_delegate` starts `scout`, `fork` or sandboxed `worktree` agents and returns immediately, so the main agent can keep responding to user steering; Pi RPC event-bus messages deliver initial and resumed turn completions without polling. `jar_subagent` peeks, steers, asks BTW questions, pauses, resumes and stops them. Workers can exchange bounded structured Q/A through `jar_discuss`; worktree changes reach `/diff` only when the moderator stops a worker. |
29
29
  | **Sessions & history** | Recent sessions (with their goal and plan) on the welcome card, one click from resuming; <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>H</kbd> searches earlier prompts; pasted images show as chips under the composer. |
30
30
  | **Footer & themes** | Responsive footer (model, effort, session, cwd, context, RAM, cost, quota, goal, roles, branch, live subagents and shells) with unicode, [Nerd Font](https://www.nerdfonts.com) or ascii icons, and seven dark themes. |
31
31
 
@@ -122,20 +122,19 @@ The welcome stays until your first prompt, then dissolves (or hides immediately
122
122
  - **Image chips**: images you paste (<kbd>Ctrl</kbd>+<kbd>V</kbd>) or reference by path appear under the frame as `▣ pasted png · 1280×720 · 240 KB`.
123
123
  - **Prompt history search**: <kbd>Ctrl</kbd>+<kbd>Alt</kbd>+<kbd>H</kbd> fuzzy-searches every prompt in this session and the opening prompts of recent ones; <kbd>Enter</kbd> puts the pick in the composer to edit. <kbd>↑</kbd>/<kbd>↓</kbd> still walks Pi's history.
124
124
  - **Next-prompt suggestions**: when the agent finishes a request it proposes one likely next prompt (`jar_suggest`). It appears as dim ghost text in the empty input; <kbd>Tab</kbd> (or <kbd>→</kbd>, or a click on it) fills it in so you can edit and press <kbd>Enter</kbd>. Typing, sending, or a new run clears it. Toggle in settings.
125
- - **Ember the mascot** perches on the top-left of the input — the face sits in the border, the flickering tips just above:
125
+ - **Ember the mascot** is an original shaded flame pet: **11 × 10 pixels** packed into **11 columns × 5 terminal rows**: a round, cute ember with a curled flame tuft, small 1 × 2 bean eyes, pink blush and a tiny mouth. It uses [pixel-pet's real raster/half-block approach](https://github.com/Namenomeaning/pixel-pet), not a decorative text face. No upstream code/assets or image protocols are required.
126
126
 
127
- | Mood | Face | When |
128
- | --- | --- | --- |
129
- | idle / blink | `(•ᴗ•)` `(-ᴗ-)` | waiting for you; blinks every few seconds |
130
- | happy / thinking | `(^ᴗ^)` `(°ᴗ°)` | the agent is generating |
131
- | focused | `(>ᴗ<)` | a tool is running (sparks fly) |
132
- | curious | `(•o•)` | a question or dialog is open |
133
- | sleepy | `(-ω-)` | idle for two minutes (`z` rises) |
134
- | oops | `(×_×)` | a run failed |
135
- | proud | `(★ᴗ★)` | a goal was completed |
136
- | poked | `(^o^)` | you clicked it |
127
+ | Mood | When |
128
+ | --- | --- |
129
+ | idle / blink | waiting for you; blinks every few seconds |
130
+ | happy / thinking | the agent is generating |
131
+ | focused / curious | a tool is running / a dialog is open |
132
+ | sleepy / error | idle for two minutes / a run failed |
133
+ | proud / poked | a goal completed / you clicked the pet |
134
+
135
+ Roomy terminals show the small pet above the composer, with a reserved mood-spark row: `zzz` when sleepy, `✦` on completion, and `?` while waiting. Happy, completed and poked moods squash and hop into the existing spark row; errors pout with puffed cheeks and a small huff. The six-row perch never moves the editor. There is no extra `Ember` name label. Below 60 columns or 24 rows it uses a compact face/crown. Motion-off freezes the entire sprite. Truecolor and 256-color rendering keep the same shaded silhouette. See [mascot rendering and visual research](docs/FLAME.md#ember-the-composer-mascot-srcmascotts).
137
136
 
138
- The title also shows the active profile in parentheses, then Pi's session name (or a stable readable alias like `silver-lantern`): `mascot · (Default) silver-lantern`. Toggle the mascot in settings; `/jar composer off` restores Pi's editor with your draft intact.
137
+ The border shows the active profile in parentheses, then Pi's session name (or a stable readable alias like `silver-lantern`): `(Default) silver-lantern`. Toggle the mascot in settings; `/jar composer off` restores Pi's editor with your draft intact.
139
138
 
140
139
  ## Plan mode
141
140
 
@@ -183,6 +182,8 @@ Plan state is stored in the session branch, so reloading or navigating the tree
183
182
  - **Guard rails.** The loop pauses when you interrupt (<kbd>Esc</kbd>), a run fails, plan mode starts, the agent calls `jar_goal block` (it needs you), or the round budget runs out (default 8 automatic rounds per user message; settings → Pi → Goal auto rounds). A new message from you resets the budget. `/goal resume` continues.
184
183
  - The footer and welcome show progress: `◎ goal · Ship · 3/5 tasks · round 2/8 · auditing`.
185
184
 
185
+ **Compaction and unfinished work:** Native automatic compaction preserves task/goal state and continues Pi's active request lifecycle. `jar_todo` tells the agent to resume unfinished authorized work after compaction, but a checklist alone does not schedule another model turn. Use `/goal <outcome>` when you want bounded, enforced continuation through implementation and audit. Manual `/compact` aborts the current run and pauses an active goal; use `/goal resume` when ready. Explicit stops, errors and round limits are not overridden.
186
+
186
187
  ## Model roles
187
188
 
188
189
  Roles map a purpose to a model. They live in `~/.pi/agent/pi-jar-roles.json` (Pi's agent directory), with optional per-project overrides in `<project>/.pi/pi-jar-roles.json`:
@@ -302,9 +303,8 @@ Providers cache a prompt as an ordered prefix: tools, then the system prompt, th
302
303
  ```
303
304
  Use `parent` with `append` to add a genuinely new subtask to active work; `start`, `done`, `open`, `edit`, `list`, `add` and `delete` remain available.
304
305
  - **`jar_ask`** — structured questions: numbered options with descriptions, single or multi-select, *Type your own answer* (multi-line, paste-friendly) and *Chat about this* to discuss before choosing.
305
- - **`/diff`** — review what the agent (and its editing subagents) changed since the first edit to each file: files with `+/−` counts on the left, a full-path header and numbered, colored diff on the right. Changed lines get a colored rail and tinted background, replaced lines highlight the changed words, code is syntax-highlighted for known languages, and collapsed regions are labeled `┄ N unchanged lines ┄`. `/` filters files; `t` switches unified and aligned side-by-side views (unified below 70 columns), `←`/`→` (or `[`/`]`) change the context from 0 lines to the whole file, `w` wraps long lines, and `n`/`p`, `Home`/`End` navigate. Large changes offer summary or explicit windowed review (`v`), with clearly labeled coarse fallback and existing safety limits—not arbitrary-file streaming. See [diff review](docs/DIFF-REVIEW.md). `a` accept (keep, stop tracking), `r` then `y` revert (restore the original, or remove a file it created), `A`/`R` for all. The footer shows `± N files · /diff` while anything is unreviewed; each file counts once no matter who edited it. Only `edit`/`write` changes inside the project are tracked; shell-made changes are not.
306
- - **Context diet (opt-in)** — `/jar settings` → **Pi** → **Context diet (prior-turn reasoning)**. Saved independently per profile; OFF by default. Removes eligible thinking blocks from completed earlier user turns only in outgoing context, never from stored history. In the same completed turn, a `read` result that a later read of the same range or a successful edit/write of the same file superseded becomes a one-line stub; each turn is rewritten only once, when it completes, so later prefix-cache hits are kept. Current reasoning signatures, user requests, tool call/result pairing and workflow state remain intact; ambiguous tool chains are left unchanged. Opaque signature character counts are diagnostics, not token savings.
307
- - **Context budget** — `/jar settings` → **Pi** → **Context budget** (`off`, `suggest` default, `compact`) and **Context budget limit** (80k–200k, default 120k). When the last call's prompt (input plus cache) passes the limit, the footer's context metric turns amber and `suggest` shows one notice per crossing (`/compact`, or `/new` for the next task). `compact` compacts once at the next safe point: right after a `jar_todo` item completes while work remains (the run stops after that turn, compacts, then continues with a hidden note), or when the run settles; it never cuts a tool chain. The footer shows the average cost per call over the last ten calls next to the total. A prompt after a finished task list in a large context suggests `/new`; a prompt after more than five idle minutes with over 100k context warns that the provider cache has probably expired. No keep-warm requests are sent.
306
+ - **`/diff`** — review what the agent (and its editing subagents) changed since the first edit to each file: files with `+/−` counts on the left, a full-path header and OMP-inspired split-first **Before/After** diff on the right (unified below 70 pane columns). Changed lines get a colored rail and soft tinted background, replaced lines highlight changed words with a stronger tint, code is syntax-highlighted for known languages, and collapsed regions are labeled `┄ N unchanged lines ┄`. `/` filters files; `t` switches unified and aligned side-by-side views (unified below 70 columns), `←`/`→` (or `[`/`]`) change the context from 0 lines to the whole file, `w` wraps long lines, and `n`/`p`, `Home`/`End` navigate. Large changes offer summary or explicit windowed review (`v`), with clearly labeled coarse fallback and existing safety limits—not arbitrary-file streaming. See [diff review](docs/DIFF-REVIEW.md). `a` accept (keep, stop tracking), `r` then `y` revert (restore the original, or remove a file it created), `A`/`R` for all. The footer shows `± N files · /diff` while anything is unreviewed; each file counts once no matter who edited it. Only `edit`/`write` changes inside the project are tracked; shell-made changes are not.
307
+ - **Context ownership** — pi-jar does not prune prior reasoning/tool results, impose a soft context limit, or abort/compact/resume work at task boundaries. The context meter and cache diagnostics remain passive. Pi's native compaction and explicit `/compact` (including the user-selected plan approval action) remain available. Retired `contextDiet` and `contextBudget` settings are ignored and dropped on the next settings/profile save; loading does not rewrite user files.
308
308
  - **Read hygiene** — a whole-file `read` (no offset/limit) of a file over 50 KB returns its first 400 lines with a note on how to continue. Opt-in **Repeated-read stub** (`/jar settings` → **Pi**): re-reading the same range of an unchanged file returns `[unchanged since read #N (312 lines); pass force: true to re-read]`. Any edit/write of the file, a changed file revision, compaction, branch navigation or a new session clears it.
309
309
  - **`jar_shell`** — `start` (optional `name`, `watch` regex, `notify`, task/service `purpose`, `waitMs` up to 30 s), `list`, `peek`, bounded `wait`, `output`, `kill`. Use plain `bash` for finite commands expected to finish in about two minutes; `jar_shell` is for services, watchers and long jobs. `start` with `waitMs` returns the exit code and output tail in one call when the job finishes in time (acknowledged, so no later notification), otherwise the output so far; Esc cancels only the wait. `output` returns only lines new since your last read (with counts of trimmed or skipped lines); `all: true` repeats the trailing window. ANSI-stripped logs have per-job caps and a 4 MiB aggregate budget; surfaced finished jobs compact to a 32 KiB tail. Live jobs retain useful tails and are never killed to reclaim logs. Watch/exit notifications are compact and coalesced; reads acknowledge observed events. Active model responses cannot be interrupted by a completion event: the UI previews status without consuming it, and model delivery waits for a safe boundary. When no useful work remains, use bounded `wait` for the relevant finite jobs rather than waiting for an automatic wake-up. Each running shell gets a footer row and an activity view (`/jar shells`) with `x` kill and `f` follow. All shells stop when the session ends.
310
310
  - **`jar_delegate`** — spawns retained, session-scoped subagents up to **Max subagents** in `/jar settings` (2, 4, 6, 8 or 16; default 2). Idle, paused and hibernated agents count toward this pool; over-cap batches are rejected. `/profiles` → **Create Swarm profile** copies the current settings with a pool of 4. `scout` is fresh/read-only, `fork` inherits conversation read-only, and `worktree` forks context into a path-guarded Git worktree. Settled retained agents close their Pi process after 30 seconds of quiet while keeping their private session and workspace; `resume`/`ask` relaunch that same session. Launch returns before startup or the first turn completes, including legacy one-shot `write: true` mode. Turn and stop events carry a summary of at most 400 characters plus changed files; all events pending at one boundary arrive as one `pi-jar.subagent` message, together with the moderator's unread discussion mail. `/roles` warns when the `scout` or `reviewer` role would run on the main model while a cheaper model is available.
@@ -323,7 +323,7 @@ Providers cache a prompt as an ordered prefix: tools, then the system prompt, th
323
323
 
324
324
  - **Appearance** — accent, motion, rounded composer, Ember mascot, next-prompt suggestions, pi-jar UI, icons (`unicode` default, `nerd` for a [Nerd Font](https://www.nerdfonts.com) terminal like omp's nerd preset, `ascii` for plain labels).
325
325
  - **Footer** — field visibility. In fullscreen mode the session name (or `sessions` for an unnamed session) opens the session picker and each subagent/shell row opens the activity view. SoL-Pi's savings status is not repeated in the footer (it already notifies).
326
- - **Pi** — mouse clicks (Pi fullscreen mode), copy on select, goal auto rounds, **Max subagents** (explicit choice then Save; default 2, existing saved values are kept), advisor, advisor gates (default off), context diet, cache diagnostics (default on), context budget and its limit, repeated-read stub.
326
+ - **Pi** — mouse clicks (Pi fullscreen mode), copy on select, goal auto rounds, **Max subagents** (explicit choice then Save; default 2, existing saved values are kept), advisor, advisor gates (default off), cache diagnostics (default on), repeated-read stub.
327
327
 
328
328
  pi-jar preferences are saved in `pi-jar-settings.json` in Pi's agent directory; the Pi tab writes Pi's own settings.
329
329
 
@@ -337,7 +337,6 @@ Other extensions can publish teammate roles and quota windows through Pi's publi
337
337
  pi-jar/
338
338
  ├── extensions/index.ts extension entry: wiring, welcome, footer, commands
339
339
  ├── src/
340
- │ ├── context-budget.ts context budget, per-call cost average, topic and cold-cache hints
341
340
  │ ├── cache-breaks.ts prompt-cache break diagnostics: payload fingerprints, diff, usage correlation, JSONL log; cache-breaks-view.ts is its tab
342
341
  │ ├── flame.ts pixel fire simulation
343
342
  │ ├── mascot.ts Ember's moods and sprites
@@ -1,14 +1,15 @@
1
1
  # Diff review
2
2
 
3
- `/diff` is pi-jar's post-edit review UI: it inspects captured before/after content, then lets you accept (stop tracking) or explicitly confirm a revert. It is inspired by the split-pane, hunk-navigation and bounded-rendering ideas in [pi-show-diffs](https://github.com/xRyul/pi-show-diffs), but does not copy its code or change the tool pre-approval flow.
3
+ `/diff` is pi-jar's post-edit review UI: it inspects captured before/after content, then lets you accept (stop tracking) or explicitly confirm a revert. Its split-first presentation and soft/strong change backgrounds follow [Oh My Pi's Git diff pane](https://github.com/can1357/oh-my-pi/blob/b91db66e9b0b65a2c977e6a5bc74b0f4b1d58d1c/packages/tui/src/apps/git/diff-pane.ts). Existing hunk-navigation and bounded-rendering ideas also draw on [pi-show-diffs](https://github.com/xRyul/pi-show-diffs). No upstream code is copied, and the tool pre-approval flow is unchanged.
4
4
 
5
5
  - Type `/` in the file list to filter by absolute/relative path or change status (content search is intentionally omitted to keep keystrokes bounded over large retained snapshots). Press Enter to keep the filter, Escape to clear it, and `n`/`N` to move through matching files.
6
6
  - The right pane starts with the selected file's absolute path and change summary.
7
7
  - Normal previews render a viewport of Git-style hunks. Each hunk header is preceded by a `┄ N unchanged lines ┄` label for the region it skips. `PgUp`/`PgDn`, `g`/`G`, `Home`/`End`, or the mouse wheel move through the preview; `n`/`p` jump hunks. Rendering windows the requested rows instead of constructing a rendered row for every file line.
8
- - `t` toggles unified rows and aligned side-by-side Original/Updated columns: within a replacement, the k-th removed line sits beside the k-th added line, and a longer side continues alone. Panes narrower than 70 columns keep the unified layout. Toggling keeps the hunk at the top of the viewport.
8
+ - Wide panes open in aligned side-by-side **Before/After** columns with wrapping off, matching OMP's split-first defaults. `t` toggles unified rows and split columns: within a replacement, the k-th removed line sits beside the k-th added line, and a longer side continues alone. Panes narrower than 70 columns keep the unified layout. Toggling keeps the hunk at the top of the viewport. Split columns show one line number per side; unified rows retain old/new line-number gutters.
9
9
  - `←`/`→` (or `[`/`]`) step the context through 0, 1, 3 (default), 5, 10, 25 lines and the whole file. `w` wraps long lines onto continuation rows instead of truncating them; with wrapping, `End` shows the last full page.
10
- - Changed lines carry a colored `▌` rail and the theme's tool success/error background. For a replaced line pair, the changed words are shown in inverse video, computed by a bounded token LCS (lines that share no word, very long lines and coarse previews are not emphasized). Files whose language Pi recognizes are syntax-highlighted per line with the active theme; lines over 1,000 characters keep plain diff colours.
10
+ - Changed lines retain a colored `▌` rail. Native truecolor themes blend the neutral tool background with the added/removed foreground colors: soft backgrounds cover the padded text body, and stronger backgrounds mark changed words. The blend strengths follow OMP (18% soft/42% strong on dark palettes, 24%/48% on light palettes); pi-jar uses the theme's neutral tool background because terminal canvas RGB is not exposed here. Themes without accessible RGB colors retain tool success/error backgrounds and inverse-video word emphasis.
11
+ - Word changes use a bounded token LCS (lines that share no word, very long lines and coarse previews are not emphasized). Files whose language Pi recognizes are syntax-highlighted per line with the active theme; lines over 1,000 characters keep plain diff colours. Syntax resets restore active background/word emphasis instead of erasing it.
11
12
  - Changes over 256 KiB or 1,500 combined lines initially show summary counts. Press `v` to explicitly review a bounded preview or return to summary. Previews are capped at 100,000 combined lines per file. When exact LCS diff exceeds its existing CPU/memory budget, the full-review choice uses a clearly marked coarse replacement hunk that preserves common prefix/suffix and line order.
12
13
  - Safe revert tracking remains capped at 1 MiB per file and 8 MiB of retained baselines. Binary, oversized, or otherwise untracked files cannot be reverted from this UI. The summary does not imply content beyond those safety caps is reviewable.
13
14
 
14
- The implementation uses the pi-tui custom component and windowed split-frame renderer already used by pi-jar. Upstream visual reference: [xRyul/pi-show-diffs README](https://github.com/xRyul/pi-show-diffs#diff-review-ux), MIT-licensed: aligned split rows, intraline highlights, rail markers, labeled collapsed regions, adjustable context and wrapping. No upstream source was copied, and pi-show-diffs' pre-apply approval and inline editing are not part of `/diff`, which reviews changes after they are applied.
15
+ The implementation uses the pi-tui custom component and windowed split-frame renderer already used by pi-jar. OMP reference pinned to commit `b91db66e9b0b65a2c977e6a5bc74b0f4b1d58d1c`: [`packages/tui/src/apps/git/diff-pane.ts`](https://github.com/can1357/oh-my-pi/blob/b91db66e9b0b65a2c977e6a5bc74b0f4b1d58d1c/packages/tui/src/apps/git/diff-pane.ts), specifically `palette()`, `#renderSide`, and `#renderLine`. Its split default, wrap-off default, padded soft backgrounds and stronger word backgrounds are the visual reference—not its Git patching or minimap features. Additional MIT-licensed reference: [xRyul/pi-show-diffs README](https://github.com/xRyul/pi-show-diffs#diff-review-ux) for aligned split rows, rail markers, collapsed regions, context and wrapping. No upstream source was copied. Pre-apply approval and inline editing are not part of `/diff`, which reviews changes after they are applied.
@@ -40,18 +40,17 @@ Baseline: a 309-call, $6.34 session (`node scripts/session-cost.mjs 01a101ba-…
40
40
 
41
41
  | Change | Where | Effect |
42
42
  | --- | --- | --- |
43
- | Context budget (`suggest` by default at 120k; opt-in `compact` at a safe point) and average cost per call in the footer | `src/context-budget.ts` | Long sessions are compacted or split before every call re-sends 200k+ |
44
- | Topic-switch and cold-cache hints | `src/context-budget.ts` | `/new` for unrelated work; `/compact` before re-sending a large expired context |
43
+ | Context controls retired in 0.3.0 | Pi owns compaction; pi-jar meters/diagnostics only observe | No prior-turn pruning, soft-limit notices or budget-triggered abort/compact/resume |
45
44
  | `bash` for finite commands; `jar_shell start` `waitMs`; incremental `output` | `src/shells.ts` | Short jobs take one call instead of start → output → wait, and repeated reads don't resend old output |
46
- | 400-line cap on whole reads of files over 50 KB; opt-in repeated-read stub; diet stubs superseded reads | `src/compact-tools.ts`, `src/context-diet.ts` | Smaller tool results and fewer duplicate reads in the context |
47
- | Pool default 2 (opt-in Swarm profile: 4); delegate only for ≥2 chunks of ≥10 tool calls | `src/settings.ts`, `src/delegate.ts`, `src/profile-ui.ts` | Fewer moderation round-trips |
45
+ | 400-line cap on whole reads of files over 50 KB; opt-in repeated-read stub | `src/compact-tools.ts` | Smaller new tool results without rewriting previous turns |
46
+ | Pool default 2 (opt-in Swarm profile: 4); route substantial independent tasks before implementation | `src/settings.ts`, `src/delegate.ts`, `src/profile-ui.ts` | Scouts research external references; worktrees implement without duplicate main-agent work |
48
47
  | Events: ≤400-char summary + changed files, one message per boundary, discussion mail inlined | `src/delegate.ts`, `src/discussion.ts` | Events shrink from ~1.9 KB to ~0.5 KB; no `list` call to read mail; no echoes of own messages |
49
48
  | `/roles` warns when `scout`/`reviewer` run on the main model while a cheaper one is available | `src/model-roles.ts` | Subagent calls move to a cheaper model |
50
49
  | Advisor gates off by default; loop threshold 4; re-reads, shell polls and test reruns are never loops | `src/advisor.ts` | No side calls from normal fix loops |
51
50
  | Cache-break diagnostics: one notice per costly prompt-cache break, `/cache-breaks`, a per-session JSONL log | `src/cache-breaks.ts`, `src/cache-breaks-view.ts` | The tool list, system section or message that forced a rewrite is named instead of guessed |
52
51
  | Rules moved into tool descriptions; `jar_plan_submit`/`jar_goal` active only in their mode | all tool modules, `src/tool-activation.ts` | Smaller fixed prompt (below) |
53
52
 
54
- Fixed prompt that pi-jar contributes, measured from Pi's built system message in RPC mode (same project, same Pi 1.0.4): the `rules` section went from 7,584 to 1,675 characters and `tools` from 1,773 to 1,476; two schemas (~900 characters) are no longer sent outside their mode. Tool descriptions and schemas grew by ~1.1k characters because the guidance moved there. Net: ~5k characters (~1.3k tokens) less on every call. That is modest next to context growth, so the context budget and shell/read changes carry most of the saving.
53
+ Fixed prompt that pi-jar contributes, measured from Pi's built system message in RPC mode (same project, same Pi 1.0.4): the `rules` section went from 7,584 to 1,675 characters and `tools` from 1,773 to 1,476; two schemas (~900 characters) are no longer sent outside their mode. Tool descriptions and schemas grew by ~1.1k characters because the guidance moved there. Net: ~5k characters (~1.3k tokens) less on every call. These historical measurements do not establish current savings; context-budget controls were subsequently removed at the user's request.
55
54
 
56
55
  Cache breaks: an audit of a $30 session found ~68% of the cost was cache rewrites caused by prompt-prefix changes: tools activated mid-session (the cache read fell to 0 and ~230k tokens were rewritten per call), an extension replacing old tool results with placeholders (the read stuck at a mid-history offset), and one unexplained break at a user-turn boundary. Pi reports *that* a call missed the cache, not why. `src/cache-breaks.ts` fingerprints each provider payload (model, tools, system-prompt sections, messages; hashes and sizes only), finds the first segment where the previous request stopped being a prefix of this one, and joins that with the call's usage; a call whose cache read falls more than 4,096 tokens short of the previous prompt is a costly break. It only observes, and is on by default (`cacheDiagnostics`).
57
56
 
@@ -69,11 +68,11 @@ Not done: Pi's compaction thresholds and the user's own settings are left alone;
69
68
 
70
69
  | Item | Why |
71
70
  | --- | --- |
72
- | 1. Single Context Projector | The four `context` hooks (child automation filter, diet, plan, goal) are each one `customType` scan that allocates only when something is stale — negligible next to request serialization. Merging them couples independent modules for no measurable gain. Revisit only if `/jar perf` shows context hooks in a profile. |
73
- | 2. Context Diet v2 (`stripper` model, `DietSnapshot`) | A model-written summary replaces exact history with a lossy one, adds a side call per compression and duplicates Pi's own compaction. Needs its own design (fidelity, resumability, cache-hit impact) before code. |
74
- | 3. Diet-aware delegation | Depends on item 2. Today `scout` already starts fresh and `fork` is an explicit mode; changing the defaults alters subagent results. |
71
+ | 1. Single Context Projector | The workflow `context` hooks (child automation filter, plan, goal) are each one `customType` scan that allocates only when something is stale — negligible next to request serialization. Merging them couples independent modules for no measurable gain. Revisit only if `/jar perf` shows context hooks in a profile. |
72
+ | 2. Context Diet v2 (`stripper` model, `DietSnapshot`) | Retired, not planned: the user requested removal of pi-jar context rewriting as inefficient. Native Pi owns compaction. |
73
+ | 3. Diet-aware delegation | Retired with context diet. `scout` already starts fresh and `fork` explicitly inherits context. |
75
74
  | 4. Strict utility roles | Side calls without a role use the current model by design (`askRole`); making them fail or go deterministic breaks advisor and commit messages for users without roles. Addressed instead by the one-time notice above. |
76
- | 10. Compact workflow history | Rewriting persisted session history is ruled out; provider context is handled by the cache-stable projection above. |
75
+ | 10. Compact workflow history | Rewriting persisted session history is ruled out; pi-jar no longer projects prior-turn provider history. |
77
76
 
78
77
  ## Success criteria
79
78
 
package/docs/FLAME.md CHANGED
@@ -32,23 +32,26 @@ The welcome ticks every 90 ms only while it is visible and motion is on. On term
32
32
 
33
33
  ## Ember, the composer mascot (`src/mascot.ts`)
34
34
 
35
- Ember is two layers:
35
+ Ember is an original, shaded **11 × 10 pixel ember pet**, packed into **11 columns × 5 terminal rows** using `▀`/`▄` cells with independently colored upper/lower halves. Its round bulb body has a curled flame tuft, dark outline, orange shadow, golden face and pale highlight. The face sits low and wide for a cute read: small 1 × 2 bean eyes, pink blush and a one-pixel mouth (red and open when happy). Expressions fit within 3 × 2 eye patches. It sits above the composer without an `Ember` name label; the border retains only profile/session context.
36
36
 
37
- - a **face** in the composer's top border — always `( + three glyphs + )`, five columns wide in every mood, so the title never shifts;
38
- - **flame tips** on the row above the frame, five columns wide, aligned over the face.
37
+ A full pet appears at **60+ columns and 24+ terminal rows**. A fixed extra row above it holds mood sparks: `zzz` for sleep, `✦` for completion and `?` for waiting, blank otherwise. Happy, completed and poked moods squash, hop one terminal row into the spark row, and settle. The spark returns when grounded. Errors pout with puffed cheeks and a short grounded huff; thinking glances sideways. Every pose preserves the same six-row perch, so the editor never jumps. Smaller terminals use a compact five-column face/crown (also with sleepy `zzz`); below 40 columns the crown is omitted. Draft expansion reserves the six-row perch, and mouse coordinates subtract its actual height. Clicking anywhere in the pet pokes it. Turning the mascot off removes every layer.
38
+
39
+ `mascotPixels()` authors the raster and squash/pout poses; `mascotSprite()` packs it into terminal cells; `mascotSpark()` supplies the fixed-width mood marker; `mascotPerch()` owns the fixed-height hop layout. Truecolor terminals get the eight-color Ember palette; other terminals get corresponding ANSI 256-color cells. No image protocol, image assets, browser overlay, renderer dependency or extra animation loop is required.
40
+
41
+ ### Pixel-pet research
42
+
43
+ [Pixel-pet](https://github.com/Namenomeaning/pixel-pet) (MIT) uses a real pixel raster—not decorative text faces. Its [`assets/slime.json`](https://github.com/Namenomeaning/pixel-pet/blob/main/plugins/pixel-pet/assets/slime.json) defines a 17 × 11 body, a layered color palette and eye/cheek anchors. [`hooks/pixels.ts`](https://github.com/Namenomeaning/pixel-pet/blob/main/plugins/pixel-pet/hooks/pixels.ts) stamps 3 × 3 expressions, props and companions onto a canvas; `encodeCells()` packs two pixels into each terminal half-block cell. Its SVG renderer is for Desktop.
44
+
45
+ The initial five-column glyph-only adaptation was rejected during visual review. The revised Ember uses the raster, shading, eye-overlay and half-cell approach with original warm flame artwork; no upstream sprites or code are copied. Reference GIFs and a side-by-side raster comparison were inspected, in addition to layout/state tests. Tool-specific props and companion trails are not included in this version.
39
46
 
40
47
  Moods come from Pi's observed activity plus local timers:
41
48
 
42
- | Mood | Face | Tips | Trigger |
43
- | --- | --- | --- | --- |
44
- | idle | `(•ᴗ•)` | swaying `▴▲▴` | nothing happening |
45
- | blink | `(-ᴗ-)` | still | 180 ms, every 3–6 s at random |
46
- | happy / thinking | `(^ᴗ^)` / `(°ᴗ°)` | bouncing / orbiting `∙` | generating (alternates every few seconds) |
47
- | tool | `(>ᴗ<)` | sparks `*` `·` | a tool is running |
48
- | waiting | `(•o•)` | `?` | a dialog or question is open |
49
- | sleepy | `(-ω-)` | dimmed, rising `z` | idle for two minutes |
50
- | error | `(×_×)` | drooping `ˇ` | a run failed (3 s) |
51
- | complete | `(★ᴗ★)` | star and sparks | a goal was completed (4 s) |
52
- | poke | `(^o^)` | bounce | you clicked the face (1.5 s) |
53
-
54
- Faces and tips are painted from the flame palette (pale-gold eyes, orange shell, redder when upset) or theme colors without 24-bit color. One unref'd 240 ms timer drives expressions, and it requests a render **only when the sprite actually changes**. Motion-off stops the timer and shows a calm face; turning the mascot off removes both layers. Below 40 columns the tips row is dropped.
49
+ | Mood | Full-sprite expression | Trigger |
50
+ | --- | --- | --- |
51
+ | idle / blink | single-pixel pupils / short horizontal eyelids | idle; 180 ms blink every 3–6 s |
52
+ | happy / thinking | smiling eyes and mouth / side-looking pupils | generating; alternates every few seconds |
53
+ | tool / waiting | inward-focused eyes / wide eyes and round mouth | running tool / dialog or question |
54
+ | sleepy / error | lowered eyelids / pursed mouth and puffed cheeks | idle for two minutes / failed run (3 s) |
55
+ | complete / poke | sparkling eyes and smile / surprised eyes and mouth | goal complete (4 s) / click (1.5 s) |
56
+
57
+ Active poses use 600 ms frames driven by Pi's existing render cadence, never a separate active timer. Transient completion, poke and error reactions schedule only their remaining pose frames, ending at the flash deadline. Idle is deliberately calm between exact blink and sleep deadlines. Sleepy idle stops scheduling. Motion-off stops timers and freezes the **entire raster** to idle, even while a tool or error flash is active. Tests cover every pixel frame's dimensions/colors, truecolor and 256-color cells, responsive fallback, draft-height reservation, mouse mapping and motion-off stability. These checks prove mechanics; visual comparison is a separate review.
@@ -80,7 +80,7 @@ pi-jar registers `/usage`, `/context`, `/cache-breaks` and `/advisor`. Pi keeps
80
80
 
81
81
  Every model call re-sends the whole context, so cost grows with session length far more than with the work done (see [EFFICIENCY_PLAN.md](./EFFICIENCY_PLAN.md#session-cost-reduction)). pi-jar never edits these files; apply them yourself if they fit:
82
82
 
83
- - **Compact earlier.** In `~/.pi/agent/settings.json` set `"compaction": { "reserveTokens": 140000, "keepRecentTokens": 20000 }`. With a 272k-window model this compacts around 130k instead of near the limit. pi-jar's context budget (`/jar settings` → Context budget) suggests `/compact` or `/new` at the same point, or compacts at the next finished task when set to `compact`.
83
+ - **Own context explicitly.** Use Pi's `/compact` to carry a summary forward or `/new` for unrelated work. Native auto-compaction is controlled by Pi's settings, not pi-jar. pi-jar no longer strips prior-turn context or enforces a soft limit; it never changes your Pi compaction preferences.
84
84
  - **Enable heavy tool packages per project.** A package such as `pi-mono-figma` (~20 tools) sends every schema on every call; enable it only in projects that use it.
85
85
  - **Load each MCP server once.** Use codebase-memory either through the lazy `mcp` proxy **or** as direct tools, not both.
86
86
  - **Activate tools at startup, not mid-session.** Changing the tool list, the system prompt or an earlier message makes the provider re-process the whole prefix after it: an MCP server or tool package switched on halfway through a long session rewrote ~230k tokens on every call until the cache caught up. pi-jar announces costly cache breaks and `/cache-breaks` lists what changed (see [Cache break diagnostics](../README.md#cache-break-diagnostics)).
package/docs/SHOWCASE.md CHANGED
@@ -17,6 +17,48 @@ The primary loop should make the product understandable without narration:
17
17
 
18
18
  ## Capture guidelines
19
19
 
20
- Keep the source capture in a dark terminal using a pi-jar theme. Crop dead time before the UI appears, avoid showing secrets or unrelated desktop chrome, and prefer a short loop over a full recording. The README version should be optimized for quick loading; the original recording can remain outside the repository.
20
+ Keep the source capture in a dark terminal (a bundled pi-jar theme is preferred). Crop dead time before the UI appears, avoid showing secrets or unrelated desktop chrome, and prefer a short loop over a full recording. The README version should be optimized for quick loading; the original recording can remain outside the repository.
21
21
 
22
22
  The visual is supporting evidence, not the documentation itself. Every demonstrated feature should still be explained in the README or the focused docs.
23
+
24
+ ## Version 0.3.0 walkthrough
25
+
26
+ - **MP4:** [`pi-jar-demo.mp4`](https://github.com/ygrip/pi-jar/blob/main/docs/assets/pi-jar-demo.mp4), 76.5 seconds, 1920 × 1080, 24 fps, H.264 + AAC, web-optimized (`faststart`), approximately 9.3 MB.
27
+ - **README GIF:** [`pi-jar-demo.gif`](https://raw.githubusercontent.com/ygrip/pi-jar/main/docs/assets/pi-jar-demo.gif), a condensed 48-second loop at 1280 × 720 and 12 fps, 256 colors, approximately 11.4 MB. Silent, as GIFs are.
28
+ - **Film:** the actual flame-over-π pi-jar logo vectorized for the opening, warm/violet glow, eased reveals, terminal closeups, chapter captions and an original synthesized score. Both exports show profile switching, sending a prompt that dismisses the welcome, real `jar_ask` choices, the cute bean-eyed Ember, celebration hops, pouting, plan review, tasks, split diff, delegation activity and sleepy `zzz`. The MP4 also includes settings and unified diff.
29
+
30
+ Local exports are `docs/assets/pi-jar-demo.mp4` and `docs/assets/pi-jar-demo.gif`. README media uses hosted repository URLs so npm installations do not carry roughly 21 MB of video assets. Hosted links reflect the committed/uploaded repository version; local exports are the review source before publication.
31
+
32
+ ### What is real, and what is a fixture
33
+
34
+ The recording runs the current extension in Pi and captures its actual terminal rendering, including the raster mascot, animated welcome, native settings/profile dialogs, `jar_ask`, plan view, task editor, split diff and activity view. The profile picker really selects Focus; `/profiles Swarm` switches again, and `/profiles Default` returns before the first prompt locks the session profile. Sending “Give me three tiny weekend project ideas and let me pick one.” dismisses the welcome through the normal lifecycle. fframes supplies the surrounding logo/title card, frame and progress bar.
35
+
36
+ A scripted localhost-only provider responds to the real prompt with a `jar_ask` tool call; the real tool presents options and consumes the recorded selection. Its replies are deterministic fixture data, not inference by an external model. Task and plan content, the small `ember.ts` change, completion/error reactions, idle age for the sleepy shot, and three completed agent records are also offline fixtures. The activity scene explicitly says no model or child agent was started. Footer roles come from the existing labeled `/jar demo` mode. This is a UI walkthrough, not a claim that an agent autonomously implemented or verified the illustrated change. Goal execution, real child-process execution, network quota lookups and every possible panel are not demonstrated.
37
+
38
+ ### Isolation and rendering
39
+
40
+ Install the video skill in a fresh temporary directory, not the repository or personal agent directory:
41
+
42
+ ```bash
43
+ mkdir -p /tmp/pi-jar-feature-demo
44
+ cd /tmp/pi-jar-feature-demo
45
+ npx --yes skills add https://fframes.studio --yes --agent pi
46
+ ```
47
+
48
+ The capture used a separate project, `HOME`, `PI_CODING_AGENT_DIR` and session directory, with normal extension/skill/prompt/theme discovery disabled and only the recording driver loaded explicitly. The driver loads a snapshot of pi-jar and sends the recorded prompt only to a scripted provider bound to `127.0.0.1` on an ephemeral port. No external model/API requests occur. Pi and the local provider are terminated and reaped after capture. No personal auth, sessions or other extensions enter the recording. Only the exported MP4 and GIF belong in the repository; source capture, video project, Cargo build products and installed skills stay outside it.
49
+
50
+ The actual toolchain was asciicast → `agg` → FFmpeg terminal MP4 → fframes 1.2.0 Rust/SVG + Skia/Metal render → FFmpeg web MP4 and condensed GIF. The revised capture uses a 128 × 40 terminal, the smaller mascot and no name label. Original music is synthesized from oscillators, pulses and transition swells; no third-party audio is used. Encoded audio was measured at about −14.2 LUFS and −3.7 dBTP, without clipping. The terminal uses Pi's dark theme. fframes reads the terminal MP4 through a runtime `MediaDirectory`; video cannot be placed in its compile-time `include_media_dir!` folder, which holds the font.
51
+
52
+ In the prepared fframes project, inspect representative frames before rendering:
53
+
54
+ ```bash
55
+ cargo run --release -- frame 1.5s,17s,23s,25s,28s,35s,39s,70s
56
+ cargo run --release -- render
57
+ ffmpeg -i out.mp4 -c copy -movflags +faststart pi-jar-demo.mp4
58
+ ```
59
+
60
+ For a full-length GIF, use `fps=12,scale=1280:-1:flags=lanczos`, a 256-color `palettegen`, and `paletteuse=dither=none:diff_mode=rectangle` with `-an -loop 0`. The README's 48-second preview instead concatenates MP4 ranges **0.5–5.5, 13–20.5, 21.5–33, 34.5–42, 43–46, 49.5–52.5, 54–57, 63–65, 69.5–72.5 and 74–76.5 seconds** before applying that palette pipeline. Reset each trimmed range's timestamps before concatenating.
61
+
62
+ Pad the source terminal clip through the entire film's final frame; a shorter clip makes fframes return an empty SVG at the end. Avoid CSS color names such as `violet` as SVG IDs in fframes 1.2.0's compile-time macro; use a non-color identifier such as `violetGlow`.
63
+
64
+ The media was fully decoded. The logo, profile picker and switched profile labels, welcome before/after submission, `jar_ask`, mascot pose strip, and representative plan/diff/activity/sleep and final GIF frames were visually inspected. fframes inspected 307 frames without diagnostics. `ffprobe` verifies duration, dimensions and frame rates. No remote upload or publication is part of this workflow.
@@ -16,6 +16,7 @@ Subagents use Pi's standard `--tools` allowlist. Each `jar_delegate` task can cu
16
16
  - `skills` (per task) uses the same syntax for Pi skills, by name or path: `["mom", "punakawan"]` (or `[]`) is exact, `["-lavish"]` removes from the defaults; omit it to keep the defaults. Children then run `--no-skills --skill <path>...`.
17
17
  - `inheritTools: true` snapshots the parent's currently active tool names, then filters them for mode safety plus the trusted extras from `pi-jar-subagents.json`. It does not grant tools registered later. Pi hosts must expose `getActiveTools()` (or the integration must supply an active-name getter); otherwise inheritance/configurable lists fail with an actionable error.
18
18
  - Inherited `scout`/`fork` tools are known read-only/discovery tools, named web tools and trusted extras.
19
+ - `worktree` can **read** its selected skill instructions and relative references inside their skill directory. Grants come only from the resolved, deny-filtered child skill list—not all discovered resources. Standalone Markdown skills and symlinked instruction files grant only that file. Traversal, Git administrative paths, and symlinks escaping a granted directory are rejected; `edit`, `write`, search/list tools and multi-file edits remain workspace-confined. Extension/package roots are never added to this read exception.
19
20
  - `worktree` rejects shell tools even when explicitly requested (a shell would escape the path guard). Inherited worktree tools are path-guarded file tools, named web tools and trusted extras. `multi_file_edit` is included in defaults only when active in the parent; each effective edit target, default path and recognized alternate path field is validated, including traversal and existing symlink escapes.
20
21
  - Named web capabilities are `web_enable`, `web_search`, `fetch_content`, `get_search_content` and `source_check`. Newly enabled tools must still be in the child's original capability list.
21
22
  - These are tool-call restrictions, not an OS sandbox. Extensions execute trusted code; named web tools can access external services, and legacy direct-write mode is not workspace-confined.
@@ -24,6 +25,10 @@ Subagents use Pi's standard `--tools` allowlist. Each `jar_delegate` task can cu
24
25
 
25
26
  Retained scout/fork/worktree children hibernate after 30 seconds idle: their Pi process exits, but the private child session and worktree remain for `jar_subagent resume` or `ask`. Hibernated records count against Max subagents. Unresolved worktree recovery records do not occupy a pool slot, but at most four active/recovery worktrees are admitted. `jar_subagent stop` retries reconciliation; `discard` explicitly removes an unresolved recovery workspace without applying it. On session shutdown pi-jar reports paths for worktrees it preserves on disk, including still-active worktrees that might contain edits.
26
27
 
28
+ ## Research before implementation
29
+
30
+ Use `scout` for external docs and other repositories, `fork` for a review that needs the conversation, and `worktree` for isolated edits. Give the scout the needed active web tools explicitly, including `web_enable` if bootstrap is required, then pass its findings to the implementer. Do not send a worktree worker to browse arbitrary local paths or run verification commands: the moderator runs checks after reconciliation. Initial main-agent routing guidance is present before a fleet exists; substantial independent tasks no longer require guessing a minimum tool-call count.
31
+
27
32
  ## `~/.pi/agent/pi-jar-subagents.json`
28
33
 
29
34
  User-owned and profile-independent; read on every launch. A missing file means no rules; malformed content fails the launch with an error. Each list takes `+entry` (or bare `entry`) to allow and `-entry` to disallow; a disallow wins over an allow, and user disallows always win over the moderator's task requests (which are then rejected with an error).
@@ -40,6 +45,7 @@ User-owned and profile-independent; read on every launch. A missing file means n
40
45
  - `extensions` — entries are Pi package sources (`npm:@scope/name`, `git:host/owner/repo`, URLs), short names (npm package, git repo, a top-level extension file/dir name, or a builtin such as `mcp`), or paths (`~/`, absolute, `./`, or `dir/file` relative to the agent dir). Sources and names resolve to the installed paths from the parent's own Pi resolution (settings filters included); an allowed source that is not installed is passed to Pi as-is (Pi installs it temporarily, slower).
41
46
  - allow: loaded by every child. Default read-only scouts/forks run `--no-extensions` plus pi-jar for fast startup, so a provider extension such as Anthropic OAuth compatibility must be allowed here or those children fail authentication. Children with normal discovery get it added only when it is not already loaded.
42
47
  - disallow: children that would use normal discovery instead get `--no-extensions` plus the parent's enabled extensions (top-level, packages and enabled builtins) minus the disallowed ones. A parent started with `--no-extensions` never gains discovered extensions.
48
+ - **Plugins/packages** use Pi's `extensions` list; there is no separate plugin loader. Grant both the extension that registers a capability and the needed active tool names. Loading an extension is permission to execute trusted code, not just read documentation.
43
49
  - `skills` — names or paths. allow: added to children's default skills, even when disabled in the parent. Disallow: never loaded by children; a task asking for one is rejected. When any skill rule (config or task) applies, children get an exact `--no-skills --skill ...` list; otherwise Pi's normal discovery is unchanged.
44
50
  - The running pi-jar is always loaded explicitly; another installed pi-jar copy is never loaded twice.
45
51
 
@@ -26,7 +26,6 @@ import { fetchQuota, QuotaCache, type QuotaProvider } from "../src/quota.ts";
26
26
  import { ModelRoleManager } from "../src/model-roles.ts";
27
27
  import { jarCommit } from "../src/commit.ts";
28
28
  import { registerAdvisor } from "../src/advisor.ts";
29
- import { registerContextDiet } from "../src/context-diet.ts";
30
29
  import { registerCacheBreaks } from "../src/cache-breaks.ts";
31
30
  import { registerInfoPanels } from "../src/info-panels.ts";
32
31
  import { SideUsage } from "../src/side-model.ts";
@@ -39,7 +38,6 @@ import { manageTasks } from "../src/tasks-ui.ts";
39
38
  import { registerTaskTool } from "../src/task-tool.ts";
40
39
  import { TASK_ENTRY, TodoStore, todoTotals } from "../src/tasks.ts";
41
40
  import { formatCost, sessionCost } from "../src/usage.ts";
42
- import { branchCalls, compactForBudget, CONTEXT_CONTINUATION, ContextBudgetGuard, providerCall, resumeAfterBudget } from "../src/context-budget.ts";
43
41
  import { WorkingState } from "../src/working.ts";
44
42
  import { ChangeTracker } from "../src/changes.ts";
45
43
  import { CHILD_ENV, DelegateRegistry, registerDelegate, SUBAGENT_MESSAGE, type DelegateRun } from "../src/delegate.ts";
@@ -55,7 +53,7 @@ import { registerChangeReview } from "../src/diff-view.ts";
55
53
  import { registerShells, ShellManager, type ShellEvent, type ShellJob } from "../src/shells.ts";
56
54
  import { registerShellNotifications } from "../src/shell-notifications.ts";
57
55
  import { loadResourceCatalog } from "../src/subagent-resources.ts";
58
- import { CHILD_TOOLS_ENV, childToolAllowlist, loadSubagentConfig, RECURSIVE_TOOLS, worktreeToolViolation } from "../src/subagent-tools.ts";
56
+ import { CHILD_SKILL_READS_ENV, CHILD_TOOLS_ENV, childSkillReadRoots, childToolAllowlist, loadSubagentConfig, RECURSIVE_TOOLS, worktreeToolViolation } from "../src/subagent-tools.ts";
59
57
  import { hopefulWelcomeMessage, welcomeHit, welcomeLines, type WelcomeAction } from "../src/welcome.ts";
60
58
 
61
59
  const WELCOME_KEY = "pi-jar.welcome";
@@ -75,7 +73,7 @@ export default function piJar(pi: ExtensionAPI): void {
75
73
  // and handlers are disabled. Remove only parent automation, not task/user context.
76
74
  const parentAutomation = new Set([
77
75
  "pi-jar.goal-context", "pi-jar.goal-continuation",
78
- "pi-jar.plan-context", "pi-jar.plan-reminder", "pi-jar.moderator-context", SUBAGENT_MESSAGE, DISCUSSION_NOTICE, CONTEXT_CONTINUATION
76
+ "pi-jar.plan-context", "pi-jar.plan-reminder", "pi-jar.moderator-context", SUBAGENT_MESSAGE, DISCUSSION_NOTICE, "pi-jar.context-continuation"
79
77
  ]);
80
78
  pi.on("context", (event) => {
81
79
  const messages = event.messages.filter((message) => !parentAutomation.has((message as { customType?: string }).customType ?? ""));
@@ -84,6 +82,7 @@ export default function piJar(pi: ExtensionAPI): void {
84
82
  }
85
83
  if (delegatedChild) {
86
84
  const allowed = childToolAllowlist(process.env[CHILD_TOOLS_ENV]);
85
+ const skillRoots = childSkillReadRoots(process.env[CHILD_SKILL_READS_ENV]);
87
86
  pi.on("tool_call", (event, ctx) => {
88
87
  // Enforce capabilities per call as well as CLI startup. A web/MCP bootstrap must not
89
88
  // accidentally activate unrelated tools or permit recursive orchestration.
@@ -91,7 +90,7 @@ export default function piJar(pi: ExtensionAPI): void {
91
90
  return { block: true, reason: "Tool was not granted to this subagent: " + event.toolName };
92
91
  }
93
92
  if (delegatedWorktree) {
94
- const reason = worktreeToolViolation(delegatedWorktree, ctx.cwd, event.toolName, event.input);
93
+ const reason = worktreeToolViolation(delegatedWorktree, ctx.cwd, event.toolName, event.input, skillRoots);
95
94
  if (reason) return { block: true, reason };
96
95
  }
97
96
  });
@@ -134,15 +133,6 @@ export default function piJar(pi: ExtensionAPI): void {
134
133
  if (footerTui && footerSettings.quota) quotaCache?.refresh(ctx.model?.provider, welcomeStatuses(), Date.now());
135
134
  };
136
135
  let cost = 0;
137
- /**
138
- * Context budget: footer chip, average cost per call, prompt hints and safe-point compaction.
139
- * Delegated children never run it; the parent session owns the budget.
140
- */
141
- const contextGuard = new ContextBudgetGuard(() => delegatedChild ? { ...visualSettings.contextBudget, action: "off" } : visualSettings.contextBudget);
142
- /** jar_todo calls in flight → completed count before each ran; a raised count marks a safe point. */
143
- const todoCalls = new Map<string, number>();
144
- /** Bumped per session: a compaction finishing after a session change resumes nothing. */
145
- let budgetGeneration = 0;
146
136
  let welcomeInterval: ReturnType<typeof setInterval> | undefined;
147
137
  let welcomeFrame = 0;
148
138
  let welcomeDismiss = 0;
@@ -295,7 +285,6 @@ export default function piJar(pi: ExtensionAPI): void {
295
285
  registerAskTool(pi);
296
286
  const modelRoles = new ModelRoleManager(pi);
297
287
  const sideUsage = new SideUsage();
298
- registerContextDiet(pi, () => visualSettings.contextDiet === true);
299
288
  // Observe-only: explains costly prompt-cache breaks; subagent processes have no UI to tell.
300
289
  const cacheBreaks = registerCacheBreaks(pi, { enabled: () => !delegatedChild && visualSettings.cacheDiagnostics !== false });
301
290
  const advisor = registerAdvisor(pi, modelRoles, { enabled: () => !delegatedChild && visualSettings.advisor, gates: () => !delegatedChild && visualSettings.advisorGates, usage: sideUsage });
@@ -632,7 +621,7 @@ export default function piJar(pi: ExtensionAPI): void {
632
621
  sessionName: footerSessionName,
633
622
  cwd: ctx.cwd, settings: footerSettings, branch: footerData.getGitBranch(),
634
623
  context: contextUsage?.label ?? "ctx ?", goal: goalLoop.progress(), chips: footerChips(), activity: footerActivity(now),
635
- memory, cost: formatCost(cost, contextGuard.perCall), overBudget: contextGuard.over, quota, roles, extras: live.extras,
624
+ memory, cost: formatCost(cost), quota, roles, extras: live.extras,
636
625
  demo, animations, frame, motionBudget: ctx.isIdle() ? 2 : 1
637
626
  }, width, ctx.ui.theme ?? theme);
638
627
  hits = layout.hits;
@@ -804,16 +793,6 @@ export default function piJar(pi: ExtensionAPI): void {
804
793
  const restoreGoal = (ctx: ExtensionContext, branch?: readonly unknown[]) => {
805
794
  try { goals?.restore(branch ?? sessionBranch(ctx)); } catch { goals?.restore([]); }
806
795
  };
807
- /** Awaited by agent_settled (see compactForBudget), whose handler is the last pi-jar settle handler. */
808
- const settleBudget = async (ctx: ExtensionContext) => {
809
- const job = contextGuard.settle({ idle: ctx.isIdle(), blocked: planMode.isEnabled() });
810
- if (!job) return;
811
- const open = (todos?.all() ?? []).filter((item) => !item.done).map((item) => item.title);
812
- if (!job.compact) { resumeAfterBudget(pi, ctx, open); return; }
813
- const generation = budgetGeneration;
814
- await compactForBudget(pi, ctx, { resume: job.resume, tokens: contextGuard.lastTokens, softTokens: visualSettings.contextBudget.softTokens, open },
815
- () => generation === budgetGeneration);
816
- };
817
796
  pi.on("session_start", (event, ctx) => {
818
797
  sideUsage.clear();
819
798
  if (!profileStore) initializeProfiles(ctx);
@@ -865,9 +844,6 @@ export default function piJar(pi: ExtensionAPI): void {
865
844
  restoreTodos(ctx, branch);
866
845
  restoreGoal(ctx, branch);
867
846
  }
868
- contextGuard.reset(branchCalls(branch, Date.now()));
869
- todoCalls.clear();
870
- budgetGeneration++;
871
847
  // Quota is enabled per session; no credentials or consent are persisted.
872
848
  quotaCache = new QuotaCache(
873
849
  (provider: QuotaProvider, signal) => fetchQuota(provider, (id) => ctx.modelRegistry.getProviderAuth(id), signal),
@@ -932,11 +908,6 @@ export default function piJar(pi: ExtensionAPI): void {
932
908
  }
933
909
  if (event.source !== "interactive") return;
934
910
  dismissWelcome(ctx);
935
- // Hints read a finished list before this prompt acknowledges (hides) it.
936
- if (!event.streamingBehavior) {
937
- const hint = contextGuard.prompt(Date.now(), todoCounts());
938
- if (hint) ctx.ui.notify(hint, "info");
939
- }
940
911
  if (!todosAcknowledged && todos?.all().every((item) => item.done)) { todosAcknowledged = true; updateTaskWidget(ctx); }
941
912
  });
942
913
  pi.on("agent_start", (_event, ctx) => {
@@ -954,46 +925,26 @@ export default function piJar(pi: ExtensionAPI): void {
954
925
  if (event.message.role === "assistant") {
955
926
  working.reportOutputTokens(event.message.usage?.output ?? 0);
956
927
  addMessageCost(event.message);
957
- // Each provider call moves the per-call average and the budget; a crossing turns the context chip amber.
958
- const notice = contextGuard.call(providerCall(event.message, Date.now()));
959
- if (notice) ctx.ui.notify(notice, "warning");
960
928
  applyWorking(ctx);
961
929
  repaint("cost", "background");
962
930
  }
963
931
  });
964
932
  pi.on("tool_execution_start", (event, ctx) => {
965
- if (event.toolName === "jar_todo") todoCalls.set(event.toolCallId, todoCounts().done);
966
933
  working.toolStart(event.toolCallId, event.toolName); applyWorking(ctx);
967
934
  });
968
935
  pi.on("tool_execution_end", (event, ctx) => {
969
- const before = todoCalls.get(event.toolCallId);
970
- if (before !== undefined) {
971
- todoCalls.delete(event.toolCallId);
972
- if (todoCounts().done > before) contextGuard.todoCompleted();
973
- }
974
936
  working.toolEnd(event.toolCallId); applyWorking(ctx);
975
937
  });
976
938
  pi.on("ui_prompt_start", (_event, ctx) => { working.prompt(true); applyWorking(ctx); });
977
939
  pi.on("ui_prompt_end", (_event, ctx) => { working.prompt(false); applyWorking(ctx); });
978
940
  // A request may span several tool turns; keep its elapsed time and reported tokens until agent_end.
979
- pi.on("turn_end", (event, ctx) => {
980
- applyWorking(ctx); refreshQuota(ctx);
981
- todoCalls.clear();
982
- // Safe point: every tool result of this turn is in. Stop before the next request; agent_settled compacts and resumes.
983
- const stop = event.message?.role === "assistant" ? event.message.stopReason : undefined;
984
- if (contextGuard.turnEnd({ open: () => { const { done, total } = todoCounts(); return total - done; },
985
- interrupted: stop === "error" || stop === "aborted" || ctx.signal?.aborted === true || ctx.hasPendingMessages?.() === true,
986
- blocked: planMode.isEnabled() })) ctx.abort();
987
- });
941
+ pi.on("turn_end", (_event, ctx) => { applyWorking(ctx); refreshQuota(ctx); });
988
942
  pi.on("agent_end", (_event, ctx) => { working.end(); applyWorking(ctx); sampleFooter(ctx); });
989
943
  pi.on("agent_before_settle", (event) => { if (event.outcome === "error") composer.flash("error"); });
990
944
  // Retries or compaction after agent_end may have added messages; otherwise the agent_end sample stands.
991
945
  pi.on("agent_settled", (_event, ctx) => {
992
946
  working.end(); applyWorking(ctx); if (contextUsage?.dirty) sampleFooter(ctx); refreshQuota(ctx);
993
- if (contextGuard.busy) return settleBudget(ctx);
994
947
  });
995
- // Pi's idle cache warming keeps the prompt cache alive; the cold-cache hint counts from its last refresh.
996
- pi.on("cache_warming_decision", (event) => { if (event.action === "warm") contextGuard.warmed(Date.now()); });
997
948
  pi.on("session_tree", (_event, ctx) => {
998
949
  let branch: readonly unknown[] = [];
999
950
  try { branch = sessionBranch(ctx); } catch { /* keep empty */ }
@@ -1002,7 +953,6 @@ export default function piJar(pi: ExtensionAPI): void {
1002
953
  restoreTodos(ctx, branch);
1003
954
  restoreGoal(ctx, branch);
1004
955
  }
1005
- contextGuard.reset(branchCalls(branch, Date.now()));
1006
956
  updateCost(ctx);
1007
957
  composer.refreshSession(ctx);
1008
958
  sampleFooter(ctx);
@@ -1012,7 +962,6 @@ export default function piJar(pi: ExtensionAPI): void {
1012
962
  try { branch = sessionBranch(ctx); } catch { /* keep empty */ }
1013
963
  renders?.setEntries(branch.length);
1014
964
  if (!delegatedChild) { restoreTodos(ctx, branch); restoreGoal(ctx, branch); }
1015
- contextGuard.compacted();
1016
965
  updateCost(ctx);
1017
966
  sampleFooter(ctx);
1018
967
  });
@@ -1035,9 +984,6 @@ export default function piJar(pi: ExtensionAPI): void {
1035
984
  renders = undefined;
1036
985
  contextUsage?.dispose();
1037
986
  contextUsage = undefined;
1038
- contextGuard.reset();
1039
- todoCalls.clear();
1040
- budgetGeneration++;
1041
987
  todos = undefined;
1042
988
  changes = undefined;
1043
989
  changeCount = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-jar",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "A warm, role-aware Pi terminal workspace with switchable profiles, animated UI, plan and goal workflows, tasks, change review, model roles, background shells and subagents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "typebox": "*"
55
55
  },
56
56
  "devDependencies": {
57
- "@earendil-works/pi-coding-agent": "^1.1.0",
58
- "@earendil-works/pi-tui": "^1.1.0",
57
+ "@earendil-works/pi-coding-agent": "^1.2.0",
58
+ "@earendil-works/pi-tui": "^1.2.0",
59
59
  "typebox": "^1.3.34",
60
60
  "typescript": "5.9.2"
61
61
  },
@@ -2,7 +2,6 @@ import { createHash } from "node:crypto";
2
2
  import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { getAgentDir, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
5
- import { CACHE_IDLE_MS, contextTokens, formatTokens } from "./context-budget.ts";
6
5
  import { duration, money } from "./panel.ts";
7
6
 
8
7
  /**
@@ -20,6 +19,18 @@ import { duration, money } from "./panel.ts";
20
19
  * Decisions here are pure; {@link registerCacheBreaks} feeds Pi events in.
21
20
  */
22
21
 
22
+ /** Approximate inactivity window used only to classify cache misses; never a compaction trigger. */
23
+ const CACHE_IDLE_MS = 5 * 60_000;
24
+ const amount = (value: unknown): number => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
25
+ function contextTokens(usage: { input?: unknown; cacheRead?: unknown; cacheWrite?: unknown } | undefined): number {
26
+ return amount(usage?.input) + amount(usage?.cacheRead) + amount(usage?.cacheWrite);
27
+ }
28
+ function formatTokens(tokens: number): string {
29
+ if (tokens < 1000) return String(Math.round(tokens));
30
+ if (tokens < 999_500) return `${Math.round(tokens / 1000)}k`;
31
+ return `${(tokens / 1_000_000).toFixed(1)}M`;
32
+ }
33
+
23
34
  /** A call is a costly break when its cache read falls short of the previous prompt by more than this... */
24
35
  export const BREAK_SHORTFALL_TOKENS = 4096;
25
36
  /** ...or reads nothing from the cache although its prompt is larger than this. */