@pi-unipi/unipi 2.2.5 → 2.2.6

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 ADDED
@@ -0,0 +1,403 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [2.2.6] — 2026-08-04
10
+
11
+ ### Fixed
12
+ - `updater`: **the changelog never appeared after an update.** `CHANGELOG.md` was not in the published tarball at all, and both the update prompt and `/unipi:changelog` resolved it from `process.cwd()` — which only exists when pi happens to be running inside the UniPi checkout. The file now ships, and a new `resolveChangelogPath()` resolves it from the installed package location with a working-directory fallback.
13
+ - `updater`: `getNewerVersions()` stopped only on an *exact* version match, so when the installed version was absent from the changelog (a local build, or a version newer than any entry) it reported every historical release as new. It now compares versions properly via `isNewerVersion()`.
14
+ - `image`: `image_generate` failed with a bare `Unknown provider: omniroute`. pi-ai's images collection carries its own provider set (currently only `openrouter`) and is separate from pi's chat model registry, so a chat provider's image models were selectable but could never generate. The error now names the providers that can generate and points at `/unipi:image-settings`, models that cannot generate are marked `(cannot generate)` in the picker, and selecting one requires a confirming second Enter.
15
+
16
+ ## [2.2.5] — 2026-08-04
17
+
18
+ ### Fixed
19
+ - `image`/`notify`/`compactor`/`footer`/`info-screen`: **Escape did not close overlays or exit a filter/search field.** Every overlay detected Escape with `data === "\x1b"`, but under the kitty keyboard protocol Escape arrives as `\x1b[27u` (and as `\x1b[27;1;27~` with modifyOtherKeys), so the comparison silently failed and the overlay could not be cancelled. All 19 occurrences across 8 files now use pi-tui's `matchesKey(data, "escape")`, which understands every encoding. Arrow keys, Enter and Ctrl+C in the image model selector were converted to `matchesKey` for the same reason.
20
+ - `image`: the model selector's filter now supports arrow-key navigation without leaving the filter, and escape sequences can no longer be typed into the filter as literal text.
21
+
22
+ ## [2.2.4] — 2026-08-04
23
+
24
+ ### Fixed
25
+ - `image`: `image_recognize` failed with `Unexpected token 'd', "data: {"id"... is not valid JSON` against OpenAI-compatible gateways that reply with `text/event-stream` even when streaming was not requested (omniroute does this). Both the OpenAI and Anthropic paths called `response.json()` on an SSE body. Responses are now read as text and, when the body is a `data:`-framed stream, the deltas are parsed and concatenated — handling OpenAI `choices[].delta.content` and Anthropic `content_block_delta` shapes, skipping malformed frames rather than failing the request. Both requests now also send `stream: false`. A non-SSE body that still cannot be parsed reports its first 200 characters instead of a bare parser error.
26
+
27
+ ## [2.2.3] — 2026-08-04
28
+
29
+ ### Added
30
+ - `image`: the model pickers now accept a **custom model reference** — press `c` in `/unipi:image-settings` and type any `provider/model-id`. Image-generator detection is necessarily heuristic (third-party providers publish no image metadata), so this guarantees no model is ever unreachable. `resolveImageGenModel()` and `resolveVisionModel()` accept a well-formed reference even when it is absent from the catalog, while a bare typo with no provider segment still gets the "Unknown model" list, and a registered text-only model still gets the precise "does not accept image input" error. An empty catalog is no longer a dead end — the picker opens anyway so a model can be entered by hand.
31
+
32
+ ## [2.2.2] — 2026-08-04
33
+
34
+ ### Fixed
35
+ - `image`: the model pickers only offered pi-ai's built-in OpenRouter catalog, so image models from providers registered by other extensions (such as `pi-omniroute-bridge`) could not be selected. The obvious filter — `output.includes("image")` — matches nothing, because third-party providers surface text-to-image endpoints as ordinary chat models with no declared output modality. `listAllImageGenModels()` now merges the built-in catalog with registry-contributed generators, de-duplicated by `provider/id`; `image_generate` resolves against the same merged list. Against a real 393-model omniroute registry this goes from 34 to 46 selectable generation models with no false positives.
36
+ - `image`: **critical** — opening a model picker left both the overlay and the settings menu holding keyboard focus, so arrow keys drove the hidden menu and neither could be closed or toggled. `pickModel()` called `ctx.ui.custom()` without awaiting the promise it returns, letting the settings loop mount the next `ctx.ui.select` while the overlay was still on screen. It is now awaited, and the chosen model is persisted only after the overlay closes so cancelling leaves the config untouched.
37
+ - `image`: the model-selector overlay swallowed `Ctrl+C`, leaving no way out, and deferred its close by 500ms after selection — which let the caller resume while the overlay still had focus. `Ctrl+C` now always closes, including mid-filter, and selection closes synchronously.
38
+
39
+ ## [2.2.1] — 2026-08-04
40
+
41
+ ### Fixed
42
+ - `notify`: `permission-prompt-message.ts` was missing from the published tarball, so `@pi-unipi/notify@2.2.0` failed at load with `Cannot find module './permission-prompt-message.js'` — which took the whole umbrella extension down on a clean install. The package's `files` array was an explicit per-file whitelist and the new module was never added to it; it is now a `*.ts` glob so root-level modules cannot be omitted again. Caught by booting the published tarball in a clean directory rather than testing the workspace copy.
43
+
44
+ ## [2.2.0] — 2026-08-04
45
+
46
+ ### Breaking Changes
47
+ - **BREAKING:** `web-api` provider `source:` numbers changed, because wigolo was inserted at rank 1 for both search and read. Search is now `1`=wigolo `2`=DuckDuckGo `3`=Jina Search `4`=SerpAPI `5`=Tavily `6`=Perplexity (was `1`=DuckDuckGo `2`=Jina `3`=SerpAPI `4`=Tavily `5`=Perplexity). Read is now `1`=wigolo `2`=Jina Reader `3`=Firecrawl `4`=Perplexity (was `1`=Jina Reader `2`=Firecrawl `3`=Perplexity); read `0` is still the built-in smart-fetch engine. Migration: if you pass an explicit `source:` to `web_search` or `multi_web_content_read`, add 1 to the old number — or simply omit `source:` and let auto-selection pick, which now also falls through on provider failure. Omitting `source:` requires no change.
48
+ - **BREAKING:** `web_llm_summarize`'s `source:` range is corrected to `1`=Perplexity `2`=LLM Summarize (`maximum` was previously documented as `3` and listed read providers, which never matched the registered summarize providers). Migration: use `1` or `2`, or omit `source:`.
49
+
50
+ ### Added
51
+ - `image`: new `@pi-unipi/image` package with two agent tools. `image_generate` creates images from a text prompt using pi-ai's image catalog (34 models — FLUX.2, Gemini 3 Pro Image, GPT-5 Image, Recraft, Riverflow — served through OpenRouter), returning them inline and saving them to disk (default `~/.unipi/images`). `image_recognize` analyzes an image with any model whose input modality includes `image`, accepting a local file path, `data:` URL, or base64, with a customizable system prompt. Both are configured via `/unipi:image-settings`, which includes a filterable model picker.
52
+ - `web-api`: [wigolo](https://github.com/KnockOutEZ/wigolo) is now the default search and read provider (rank 1 for both, enabled by default) — a local-first engine with multi-engine search, rank fusion and on-device reranking, at $0/query with no API key. It is an **optional** dependency loaded through a dynamic `import()`, because wigolo-sdk is AGPL-3.0-only while UniPi is MIT; UniPi therefore ships no AGPL code. Install with `npm install -g wigolo && npx wigolo init`. Existing providers renumbered: search `2`=DuckDuckGo `3`=Jina `4`=SerpAPI `5`=Tavily `6`=Perplexity; read `2`=Jina Reader `3`=Firecrawl `4`=Perplexity.
53
+ - `web-api`: auto-selection now falls through to the next-ranked provider when one fails, so an enabled-but-uninitialized wigolo cannot break every web call. An explicit `source:` stays strict so the user's choice is respected and reported.
54
+ - `notify`: new built-in `permission_request` event bound to `@gotgenes/pi-permission-system`'s `permissions:ui_prompt` broadcast, which fires only when a human-facing permission prompt is about to be shown (no spam from policy auto-allow/deny or session approvals). Forwarded subagent prompts are marked `(forwarded)`. Disabled by default (closes #25).
55
+ - `core`: new `tui-width` helpers (`normalizeWidth`, `boxInnerWidth`, `adaptiveInnerWidth`, `shouldRenderBorder`, `contentWidth`, `safeRepeat`, `WidthKeyedCache`) encoding the invariant that a rendered line must never exceed the terminal width.
56
+
57
+ ### Fixed
58
+ - `ask-user`/`tui`: Pi no longer crashes on terminals narrower than 42 columns. Every box-drawing component floored its content width at `Math.max(40, width - 2)`, emitting lines of at least 42 columns regardless of the real terminal width; pi-tui's differential renderer throws on any over-wide line, stopping the TUI and taking the agent down. `ask-ui` and `launcher-ui` were fully exposed since they mount in the editor container at the raw terminal width. Components now clamp to the available width and drop the border below 12 columns. Applied to the same pattern in 15 other overlays across updater, info-screen, compactor, footer, mcp, notify and utility.
59
+ - `ask-user`/`mcp`: render caches are now keyed on width. `requestRender()` does not invalidate on resize, so shrinking the terminal previously returned stale, over-wide lines and the next frame threw.
60
+ - `ask-user`: `launcher-ui` measured its header with `String.length` on a string containing an astral emoji; now uses `visibleWidth`.
61
+ - `subagents`: Pi no longer crashes on exit with "This extension ctx is stale after session replacement or reload" when a background agent (typically the session-name generator) is still running. Pi disposes the session immediately after `session_shutdown` resolves, which invalidates the extension runtime; `abortAll()` only signals the AbortController, so the in-flight promise settled a microtask later and called `pi.sendMessage()` on a dead runtime, throwing from an async continuation with nothing to catch it. Late completions are now dropped via a `sessionEnded` guard set before `abortAll()`. The guard is scoped to the extension factory, so `/new`, `/fork` and `/resume` (which also emit `session_shutdown`) reset it when pi re-invokes the factory.
62
+ - `web-api`: the DuckDuckGo provider returned zero results against live markup. Snippet bodies contain `<b>` query highlights so the `[^<]*` pattern never matched; results were paired by index across two independent match streams, so one snippet-less result shifted every later snippet onto the wrong title; and URLs were returned as `//duckduckgo.com/l/?uddg=…` redirect wrappers rather than real destinations, with HTML entities left undecoded.
63
+
64
+ ## [2.1.3] — 2026-07-22
65
+
66
+ ### Added
67
+ - `notify`: support Pi's `agent_settled` lifecycle event as a separate configurable notification from `agent_end`, so users can route retry/error runs and final settled completion to different platforms or sounds (fixes #24).
68
+
69
+ ### Fixed
70
+ - `tests`: run TypeScript test suites through `tsx` instead of Node's built-in TypeScript stripping so the workspace test suite passes on Node builds without `--experimental-strip-types` support.
71
+ - `tests/package-manifest`: allow split packages such as `@pi-unipi/compactor` to declare package-internal `pi.skills` while still blocking hoisted `node_modules` resource paths.
72
+
73
+ ## [2.1.2] — 2026-07-06
74
+
75
+ ### Fixed
76
+ - `compactor`: registered the package extension and skills in `packages/compactor/package.json` (`pi.extensions` pointing at `./index.ts`, `pi.skills` at `./skills`) so the standalone `@pi-unipi/compactor` package loads its extension and skills without relying on umbrella auto-discovery (fixes #23). Mirrors the notify fix from #18.
77
+
78
+ ## [2.1.1] — 2026-07-02
79
+
80
+ ### Added
81
+ - `core`: new `withHerdrBlocked(pi, label, fn)` helper that emits `herdr:blocked` active/inactive around an awaited blocking UI so the herdr integration surfaces `blocked` agent status.
82
+ - `ask-user`/`subagents`: agent-driven blocking overlays now report `blocked` to herdr — `ask_user` (label `ask_user`), session launcher (label `ask_user: launch`), and the `get_helper_result` live conversation viewer (label `helper viewer`).
83
+ - `info-screen`: new "Show on boot" toggle at the top of `/unipi:info-settings` (persisted via `saveInfoSettings`).
84
+
85
+ ### Fixed
86
+ - `info-screen`: the info overlay no longer blocks startup. `fetchAllBackground()` now defers each group's `dataProvider()` to a background macrotask with a `_destroyed` guard instead of running them synchronously before the first `await`. Startup cost dropped from ~4750ms to ~1ms; data loads reactively after the TUI is up.
87
+ - `memory`: skip the Python `ping` `spawnSync` when recently verified via `~/.unipi/memory/.mempalace-ping-verified` (24h TTL). New `memPalaceCall<T>()` wrapper invalidates the flag on a null result so a broken palace gets re-verified next session. Warm-start saving ~484ms.
88
+
89
+ ## [2.1.0] — 2026-06-27
90
+
91
+ ### Breaking Changes
92
+ - **BREAKING:** `@pi-unipi/*` peer dependencies on `@earendil-works/pi-coding-agent`, `pi-ai`, `pi-tui`, and `pi-agent-core` are now `^0.80.0` (was `^0.78.0`). Users must run Pi `0.80.0` or newer. Pi `0.80` moved pi-ai's global API to `@earendil-works/pi-ai/compat` (unipi only imports core types from the root, so extensions keep working at runtime), removed the `/base` selective-provider entry points, and renamed `ExecutionEnvExecOptions` to `ShellExecOptions`. Migration: update Pi (`pi update`) to 0.80+ before updating unipi.
93
+ - **BREAKING:** `@pi-unipi/memory` now uses [MemPalace](https://github.com/mempalace/mempalace) as its primary backend. On first load it auto-installs MemPalace via `uv` and migrates all existing memories into a MemPalace palace at `~/.mempalace/palace`. **Prerequisite:** install [`uv`](https://docs.astral.sh/uv/) (`curl -LsSf https://astral.sh/uv/install.sh | sh`) before upgrading if you want the new backend — without `uv`, memory silently falls back to the existing SQLite store (nothing breaks, but migration will not run until `uv` is available). Legacy `~/.unipi/memory/` files are never modified or deleted; rollback by removing `~/.mempalace/palace` and `~/.unipi/memory/.mempalace-*` flags. First-run also downloads MemPalace's default ONNX embedding model (~80 MB, cached at `~/.cache/chroma/onnx_models/`).
94
+
95
+ ### Changed
96
+ - `memory`: MemPalace is now the primary storage backend. On first load the package auto-installs MemPalace via `uv`, performs a one-way read-only migration of all legacy SQLite + markdown memories into MemPalace drawers, and serves all reads/writes/searches from MemPalace. Falls back to the bundled SQLite + sqlite-vec store when MemPalace or `uv` is unavailable. Markdown files remain the durable human-readable tier.
97
+ - Bumped `@earendil-works/*` peer dependencies from `^0.78.0` to `^0.80.0` (resolves to 0.80.2).
98
+
99
+ ### Added
100
+ - `@pi-unipi/memory` now ships `bridge/mempalace_bridge.py` — a single-command JSON bridge to MemPalace's Python API (palace drawers) with deterministic drawer IDs, auto-install, and one-way auto-migration.
101
+
102
+ ### Fixed
103
+ - `memory`/`ask-user`/`info-screen`/`mcp`/`milestone`/`notify`/`ralph`/`updater`/`utility`/`web-api`/`workflow`: replaced `new URL('.', import.meta.url).pathname` with `dirname(fileURLToPath(import.meta.url))` so package-internal paths resolve correctly on Windows (the old pattern produced malformed `E:\C:\Users\...` paths).
104
+ - `notify`: registered the package extension in `packages/notify/package.json` so notify loads without relying on umbrella auto-discovery (fixes #18).
105
+ - `tests/package-manifest.test.js`: allow split packages to register package-internal extensions (e.g. notify's `./index.ts`) instead of asserting all `pi` fields are empty.
106
+
107
+ ## [2.0.13] — 2026-06-01
108
+
109
+ ### Fixed
110
+ - Removed duplicate `resources_discover` skill registration from split extension modules. The umbrella package manifest is now the sole source of skill resources, preventing collisions between `@pi-unipi/unipi/packages/*/skills` and sibling `@pi-unipi/*/skills`.
111
+
112
+ ## [2.0.12] — 2026-06-01
113
+
114
+ ### Fixed
115
+ - `unipi`: pin internal `@pi-unipi/*` dependencies to the exact release version so fresh installs cannot resolve stale split packages with conventional skill discovery.
116
+
117
+ ## [2.0.11] — 2026-06-01
118
+
119
+ ### Fixed
120
+ - `packages`: added explicit empty Pi manifests to split dependency packages so Pi treats them as non-resource packages instead of falling back to conventional `skills/` discovery.
121
+
122
+ ## [2.0.10] — 2026-06-01
123
+
124
+ ### Fixed
125
+ - `updater`: resolve the installed `@pi-unipi/unipi` package from sibling `node_modules/@pi-unipi/*` packages so nested installs report the real version instead of `0.0.0`.
126
+ - `packages`: removed Pi resource manifests from split dependency packages so installing the umbrella `@pi-unipi/unipi` package no longer double-loads skills from both `packages/*/skills` and sibling `@pi-unipi/*/skills` packages.
127
+
128
+ ### Changed
129
+ - `deps`: updated Pi peer and development dependencies to the current `@earendil-works/*` `0.78.0` package family.
130
+
131
+ ## [2.0.9] — 2026-05-28
132
+
133
+ ### Fixed
134
+ - `kanboard`: updated the npm package `files` manifest so runtime sources imported by `index.ts` (`commands.ts`, `parser/`, `server/`, `tui/`, and `types.ts`) are included in the published tarball.
135
+
136
+ ## [2.0.8] — 2026-05-24
137
+
138
+ ### Added
139
+ - `ci`: added a GitHub Actions workflow that installs dependencies, typechecks, and runs the workspace test suite on push and pull request.
140
+ - `repo`: added the MIT license file to the repository.
141
+
142
+ ### Changed
143
+ - `unipi`: migrated Pi extension imports and peer dependencies from the legacy `@mariozechner/*` packages to the current `@earendil-works/*` Pi base.
144
+ - `tools`: migrated tool schemas from `@sinclair/typebox` to Pi's current `typebox` package.
145
+ - `deps`: updated the Pi peer dependency family to `0.75.5` and aligned lockfile resolution across workspaces.
146
+
147
+ ### Fixed
148
+ - `info-screen` and `utility`: updated Pi package discovery paths to look under `@earendil-works/pi-coding-agent`.
149
+ - `web-api`: added missing `@types/mime-types` for clean typechecking with the updated dependency graph.
150
+
151
+ ## [2.0.7] — 2026-05-24
152
+
153
+ ### Fixed
154
+ - `unipi`: changed the umbrella package manifest to load its package-internal all-in-one extension instead of hoisted `node_modules/@pi-unipi/*` paths, fixing npm installs where no `/unipi:*` commands or skills loaded.
155
+ - `unipi`: ships package metadata and root `SKILL.md` files needed by package-internal extension and skill paths.
156
+ - `notify`: supports the lossless `rpiv:ask-user:prompt` payload shape while preserving UniPi's existing flat `ask_user_prompt` payload formatting.
157
+ - `notify`: avoids a type-only dependency on an unpublished `@juicesharp/rpiv-ask-user-question/events` subpath so `tsc --noEmit` passes with the current npm package.
158
+
159
+ ### Changed
160
+ - `notify`: extracted ask-user prompt notification formatting into `ask-user-prompt-message.ts` with regression coverage for lossless and legacy payloads.
161
+ - `unipi`: added a package-manifest regression test that verifies npm pack output contains every path declared in the Pi manifest.
162
+
163
+ ## [2.0.6] — 2026-05-19
164
+
165
+ ### Fixed
166
+ - `updater`: compare versions numerically instead of using string inequality so stale cache entries never prompt downgrades like `2.0.5 → 2.0.4`.
167
+ - `updater`: bypass the check interval when the cached npm version is older than the installed version, forcing a fresh registry check after local/source releases.
168
+
169
+ ## [2.0.5] — 2026-05-19
170
+
171
+ ### Added
172
+ - `footer`: added terminal-aware `colorMode` support (`auto`, `truecolor`, `256`, `none`) with xterm-256 downgrading for terminals that do not support 24-bit color.
173
+ - `footer`: added Apple Terminal detection so footer hex colors render with 256-color ANSI escapes instead of being swallowed.
174
+
175
+ ### Fixed
176
+ - `autocomplete`: stripped Pi source tags like `[u:npm:@pi-unipi/unipi]` from enchanted `/unipi:*` suggestions while keeping package tags such as `[workflow]`.
177
+ - `footer`: preserved distinct workflow/category colors on non-truecolor terminals and expanded color-mode tests for truecolor, 256-color, no-color, and Apple Terminal paths.
178
+ - `utility`: aligned terminal capability detection with footer color fallback behavior, including Apple Terminal truecolor suppression.
179
+
180
+ ### Changed
181
+ - `unipi`: regenerated the bundled all-in-one extension for the release.
182
+
183
+ ## [2.0.4] — 2026-05-18
184
+
185
+ ### Added
186
+ - `notify`: added `suppressWhenFocused` support for native notifications so noisy completion/attention alerts can be skipped when Pi is already focused.
187
+ - `compactor`: added optional UniPi-managed percentage auto-compaction with configurable threshold, cooldown, repeat-growth safeguards, and notifications.
188
+
189
+ ### Fixed
190
+ - `notify`: validated Linux focus detection and corrected Windows foreground PID typing for reliable focus suppression.
191
+ - `compactor`: fixed preset/profile application so the Pipeline tab reflects selected profiles (`precise`, `balanced`, `thorough`, `lean`) instead of staying all `off`.
192
+ - `compactor`: handled Pi's post-compaction `null` context usage state and repeated long-session auto-compactions without triggering compaction loops.
193
+
194
+ ### Changed
195
+ - `compactor`: expanded README and skill docs with user commands, settings, agent tools/skills, benchmark notes, and chronological Pi hook flow.
196
+ - `unipi`: regenerated the bundled all-in-one extension for the release.
197
+
198
+ ## [2.0.3] — 2026-05-16
199
+
200
+ ### Fixed
201
+ - `ask-user`: expanded historical `ask_user` results now show the previous question, context, and available options when toggled with Ctrl+O.
202
+ - `workflow`: cached worktree argument completions per working directory so `/unipi:worktree-merge` suggestions do not rescan `.unipi/worktrees` on every autocomplete call.
203
+
204
+ ### Changed
205
+ - `ask-user`: tool result details now preserve normalized options and input-mode metadata for richer TUI history rendering.
206
+ - `unipi`: regenerated the bundled all-in-one extension after the ask-user and workflow fixes.
207
+
208
+ ## [2.0.2] — 2026-05-16
209
+
210
+ ### Breaking Changes
211
+ - BREAKING: `btw` commands moved from `/btw`, `/btw:new`, `/btw:tangent`, `/btw:clear`, `/btw:inject`, and `/btw:summarize` to `/unipi:btw`, `/unipi:btw-new`, `/unipi:btw-tangent`, `/unipi:btw-clear`, `/unipi:btw-inject`, and `/unipi:btw-summarize`.
212
+
213
+ ### Fixed
214
+ - `compactor`: `/unipi:session-recall` now searches the append-only session branch so messages omitted by compaction remain searchable.
215
+ - `compactor`: `/unipi:compact-recall` keeps working as a deprecated alias and points users to `/unipi:session-recall`.
216
+ - `autocomplete`: command registry now includes all working compactor commands, including `session-recall` and `compact-help`.
217
+ - `autocomplete`: registry now includes BTW commands and the CocoIndex package label.
218
+
219
+ ### Changed
220
+ - `btw`: moved public commands from the bare `/btw*` namespace to `/unipi:btw*` for consistency with the Unipi command registry.
221
+ - `full-release`: replaced fragile manual command-registry checks with an automated autocomplete registry audit test.
222
+
223
+ ## [2.0.1] — 2026-05-15
224
+
225
+ ### Fixed
226
+ - `workflow`: require `@pi-unipi/core@^2.0.0` so npm installs do not load stale nested core 0.1.x copies that lack `getBlockedToolsForLevel`.
227
+ - `ralph`: align the internal `@pi-unipi/core` dependency range with the 2.x suite to avoid the same stale nested-core risk.
228
+ - `cocoindex`: extend update timeout handling with clearer timeout/error output and make `COCOINDEX_UPDATE_TIMEOUT_MS` configurable.
229
+ - `cocoindex`: skip huge generated/lock files in the default pipeline template to avoid runaway indexing and noisy generated artifacts.
230
+ - `ask-user`: render full questions, context, options, descriptions, actions, and prefill text in the tool-call display with wrapping for long prompts.
231
+
232
+ ## [2.0.0] — 2026-05-06
233
+
234
+ ### Breaking Changes
235
+ - BREAKING: `compactor` no longer provides project content indexing/search tools or commands (`content_index`, `ctx_index`, `content_search`, `ctx_search`, `content_fetch`, `ctx_fetch_and_index`). Use `/unipi:cocoindex-init`, `/unipi:cocoindex-update`, `/unipi:cocoindex-status`, `/unipi:cocoindex-search`, and `cocoindex_status` instead.
236
+ - BREAKING: `@pi-unipi/compactor` removed public content-command surface in favor of `@pi-unipi/cocoindex`; this is a breaking command and dependency migration.
237
+
238
+ ### Added
239
+ - `@pi-unipi/cocoindex` package now includes optional `@lancedb/lancedb` dependency and first-class CLI bridge for workspace indexing/search.
240
+ - Added `/unipi:cocoindex-search` command and `/unipi:cocoindex-settings` status/management flow.
241
+ - Added `cocoindex_status` and `cocoindex_search` agent tools for index diagnostics/search.
242
+ - Added deprecation-aware UX and autocomplete registry checks in full-release checks.
243
+
244
+ ### Changed
245
+ - `autocomplete` now explicitly includes CocoIndex + footer + ralph helper command suggestions and validates `/unipi:*` completeness.
246
+ - `worktree-merge/review` and ask-user documentation updates for workflow consistency.
247
+
248
+ ### Fixed
249
+ - `autocomplete`: add missing suggestions for `/unipi:cocoindex-search`, `/unipi:footer-help`, and `/unipi:ralph-start`.
250
+ - `full-release`: add an enhanced autocomplete registry audit to catch registered commands missing from command suggestions.
251
+
252
+ ## [0.1.18] — 2026-05-06
253
+
254
+ ### Breaking Changes
255
+ - BREAKING: `compactor` no longer provides project content indexing/search tools or commands (`content_index`, `ctx_index`, `content_search`, `ctx_search`, `content_fetch`, `ctx_fetch_and_index`, `/unipi:compact-index`, `/unipi:compact-search`, `/unipi:compact-purge`). Use `/unipi:cocoindex-init`, `/unipi:cocoindex-update`, `/unipi:cocoindex-search`, `cocoindex_search`, and `cocoindex_status` instead.
256
+ - BREAKING: `sandbox_batch` no longer accepts embedded search items from the removed compactor content store; run `cocoindex_search` as a separate search tool call.
257
+
258
+ ### Added
259
+ - `@pi-unipi/cocoindex` package — CocoIndex CLI bridge with LanceDB-backed project indexing and semantic search.
260
+ - `/unipi:cocoindex-init`, `/unipi:cocoindex-update`, `/unipi:cocoindex-status`, `/unipi:cocoindex-settings`, and `/unipi:cocoindex-search` commands.
261
+ - `cocoindex_search` and `cocoindex_status` tools for agent-accessible indexed content search and status diagnostics.
262
+ - Consent-based CocoIndex auto-install flow using `uv tool install 'cocoindex[lancedb]>=1.0'`, with `mise` fallback and shell-aware manual guidance.
263
+ - Default `.unipi/cocoindex/main.py` pipeline template using CocoIndex v1.0+ APIs, local filesystem ingestion, recursive splitting, OpenRouter embeddings, and LanceDB output.
264
+
265
+ ### Changed
266
+ - `compactor`: project content indexing responsibilities moved to `@pi-unipi/cocoindex`; compactor now focuses on session compaction, recall, sandbox execution, diagnostics, and context budgeting.
267
+ - `footer`: replaced the old indexed-docs content-store segment with a CocoIndex status segment.
268
+ - `autocomplete`: removed old compactor content commands and added CocoIndex command suggestions.
269
+ - `workflow`: sandbox tool filtering now preserves safe extension tools (memory, web, ask-user, notifications) while removing only blocked tools for the active workflow level.
270
+ - `memory`: lifecycle reminders now respect the currently active tool set and track recall/store activity independently.
271
+ - `full-release` chore updated with CocoIndex package inventory, command registry checks, and explicit breaking-change changelog guidance.
272
+
273
+ ### Fixed
274
+ - `cocoindex`: command registration now happens synchronously at extension load time instead of during `session_start`.
275
+ - `cocoindex`: pipeline template updated for CocoIndex v1.0+ APIs after `flow_def` removal.
276
+ - `cocoindex`: parsed v1.0+ files-processed output correctly during indexing.
277
+ - `cocoindex`: all-in-one `@pi-unipi/unipi` entry now imports and registers the CocoIndex extension.
278
+
279
+ ## [0.1.17] — 2026-05-02
280
+
281
+ ### Added
282
+ - `ask-user`: session launcher overlay for `new_session` action — spawns a new pi session with the selected model
283
+ - `footer`: zone-aware renderer with `zone`, `description`, `shortLabel` on every `FooterSegment`; extended `SemanticColor` with TPS tiers, zone colors, and workflow types
284
+ - `footer`: TPS (turns-per-second) tracker segment showing real-time agent throughput
285
+ - `footer`: clock and duration segments with live 1-second refresh timer
286
+ - `footer`: `hexColor` palette from spec — semantic colors mapped to exact hex values for consistent rendering
287
+ - `footer`: `/unipi:footer-help` command with full-label mode and help overlay showing all segments and their meanings
288
+ - `footer`: unified 3-category settings TUI (`Groups`, `Segments`, `Theme`) — simplifies the `/unipi:footer-settings` experience
289
+ - `autocomplete`: 4-tier sorting for cross-group command suggestions — unipi matches first, then unipi non-matches, system matches, system non-matches
290
+ - `autocomplete`: 37 tests for sorting logic, match quality, and cross-group behavior
291
+
292
+ ### Fixed
293
+ - `compactor`: compaction stats always zero — fixed 5 interrelated bugs in stats tracking pipeline
294
+ - `updater`: resolve `@pi-unipi/unipi` version by package name instead of hardcoded relative path
295
+ - `unipi`: include all package `.ts` files in npm bundle (was missing source files)
296
+ - `notify`: add `ntfy-config.ts` to `package.json` files array so it ships on npm
297
+ - `autocomplete`: sort by match quality across unipi/system items — exact matches ranked above partial
298
+ - `footer`: apply hex color palette from spec for consistent segment colors
299
+ - `footer`: update workflow color mapping and add thinking level segment color
300
+ - `footer`: add TPS tracker icon entries and clock/duration segment definitions
301
+
302
+ ### Changed
303
+ - Docs: all package READMEs rewritten with consistent 5-section format
304
+ - Docs: package titles deep-linked to their individual README files
305
+ - Footer preset updates and label mode support for compact display
306
+
307
+ ## [0.1.16] — 2026-05-01
308
+
309
+ ### Added
310
+ - `@pi-unipi/updater` package — auto-updater, changelog browser, and readme browser
311
+ - `/unipi:readme` command — browse package README.md files in TUI overlay
312
+ - `/unipi:changelog` command — browse CHANGELOG.md with version list and detail view
313
+ - `/unipi:updater-settings` command — configure check interval and auto-update mode
314
+ - Automatic npm registry check on session start (configurable interval: 30min/1h/6h/1d)
315
+ - Update notification overlay with changelog diff and one-key install
316
+ - Skip-version persistence — skip a version and re-prompt only when newer version appears
317
+ - Auto-update mode with countdown and cancel option
318
+ - Markdown terminal renderer for changelog and readme content
319
+ - `@pi-unipi/input-shortcuts` package — keyboard shortcuts with chord overlay, undo/redo, clipboard
320
+ - `/unipi:stash-settings` command — configure keyboard shortcuts and input behavior
321
+ - Project-level ntfy configuration — each project can use its own ntfy.json
322
+ - Theme-aware Markdown rendering in updater TUI overlays
323
+
324
+ ### Fixed
325
+ - Updater TUI overlays (`readme-overlay.ts`, `changelog-overlay.ts`, `update-overlay.ts`) — replaced `data.toLowerCase()` with `matchesKey()` to fix arrow key sequences and uppercase keys like `G`
326
+ - Updater TUI overlays — replaced raw ANSI codes with `theme.fg()`, `theme.bold()`, `theme.bg()` for consistent styling
327
+ - `input-shortcuts`: suppress input listener while overlay is open to prevent background input
328
+ - `input-shortcuts`: suppress input listener during undo/redo operations
329
+ - `input-shortcuts`: remove undo throttle — allow consecutive undos without delay
330
+ - `input-shortcuts`: redo undo snapshot logic — 3 independent triggers for reliable state capture
331
+ - `input-shortcuts`: undo for typed text + cut/copy deferred action pattern
332
+ - `input-shortcuts`: overlay blocks editor API — refactor to deferred action pattern
333
+ - `input-shortcuts`: remove chord timeout — overlay stays open until ESC or action
334
+ - `input-shortcuts`: use `unipi:` prefix in `registerCommand()` calls
335
+ - `input-shortcuts`: register extension — add barrel file, unipi entry, command registry, info-screen
336
+ - `footer`: use icon style system in ralph and workflow segments
337
+ - `footer`: remove duplicate icon from WEB segment
338
+ - `footer`: add 1-second refresh timer so time segment updates
339
+ - `footer`: uppercase status short labels and fix duplicate memory entry
340
+
341
+ ### Changed
342
+ - Updater TUI overlays use `truncateToWidth()` and `visibleWidth()` from `@mariozechner/pi-tui` instead of custom implementations
343
+ - Updater TUI overlays use proper box drawing frame (`╭╮╰╯│├┤`) matching other overlays
344
+
345
+ ## [0.1.15] — 2026-04-30
346
+
347
+ ### Added
348
+ - `@pi-unipi/footer` package — persistent status bar with live stats from all packages
349
+ - Footer settings overlay (`/unipi:footer-settings`) with group and segment toggles
350
+ - Thinking level colors and rainbow border for xhigh thinking
351
+ - Diff renderer with syntax highlighting via shiki
352
+ - Smart-fetch engine for web-api package (default read path)
353
+
354
+ ### Fixed
355
+ - Notification dispatch made non-blocking (fire-and-forget)
356
+ - Diff renderer return types and shiki import corrections
357
+ - Footer extension path alignment with other packages
358
+ - Null returns in renderResult replaced with valid components
359
+ - Console.log/warn/error calls removed that caused TUI rendering issues
360
+
361
+ ### Changed
362
+ - Footer segment icons and labels restructured
363
+ - Footer workflow/ralph/memory icons refined
364
+
365
+ ## [0.1.14] — 2026-04-29
366
+
367
+ ### Added
368
+ - Compactor UX overhaul — settings overlay, BM25 cache, auto-injection
369
+ - Context budget management with `context_budget` config option
370
+ - Dry run mode for compaction
371
+ - Two-tier skill system (project skills + bundled skills)
372
+ - Context savings analytics bridged to info-screen
373
+ - Compactor preset system (minimal/balanced/full/custom)
374
+ - Compactor search with proximity reranking
375
+ - Progressive throttling for large project indexing
376
+
377
+ ### Fixed
378
+ - Compactor settings overlay type errors
379
+ - Context-mode AnalyticsEngine bridge to info-screen
380
+ - Stash artifacts resolved — merged compactor files restored
381
+
382
+ ### Changed
383
+ - Compactor token stats info-screen integration improved
384
+ - Ralph loop guidance wiring into skill prompts
385
+
386
+ ## [0.1.13] — 2026-04-28
387
+
388
+ ### Added
389
+ - Info-screen module status response handling
390
+ - MCP catalog sync on session start
391
+ - Notify recap model selection (`/unipi:notify-recap-model`)
392
+ - ntfy push notification platform support
393
+ - Milestone tracking with `/unipi:milestone-onboard` and `/unipi:milestone-update`
394
+
395
+ ### Fixed
396
+ - MCP server startup timeout handling
397
+ - Notify Gotify header bug
398
+ - Compactor init timing issues
399
+ - Footer command argument autocomplete
400
+
401
+ ### Changed
402
+ - Compactor commands need `unipi:` prefix
403
+ - Footer icon style now configurable
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,6 +28,7 @@
28
28
  "publish:all": "npm publish --workspaces --access public"
29
29
  },
30
30
  "files": [
31
+ "CHANGELOG.md",
31
32
  "packages/*/package.json",
32
33
  "packages/*/*.ts",
33
34
  "packages/*/src/**/*.ts",
@@ -83,7 +84,7 @@
83
84
  "@pi-unipi/web-api": "2.2.0",
84
85
  "@pi-unipi/workflow": "2.2.0",
85
86
  "@pi-unipi/footer": "2.2.1",
86
- "@pi-unipi/updater": "2.2.0",
87
+ "@pi-unipi/updater": "2.2.1",
87
88
  "@pi-unipi/input-shortcuts": "2.2.0",
88
89
  "@pi-unipi/cocoindex": "2.2.0"
89
90
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.2.4",
3
+ "version": "2.2.5",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -103,6 +103,26 @@ export function saveImage(
103
103
  }
104
104
  }
105
105
 
106
+ /** Providers pi-ai's images collection can actually generate with. */
107
+ function supportedProviders(imagesApi: ImagesModelsLike): string[] {
108
+ try {
109
+ return [...new Set(imagesApi.getModels().map((m) => m.provider))];
110
+ } catch {
111
+ return [];
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Whether generation can route to a provider. Unknown/empty catalogs are
117
+ * treated as capable so a stubbed or future pi-ai is never blocked by this
118
+ * check — the real call still reports its own error.
119
+ */
120
+ function providerCanGenerate(imagesApi: ImagesModelsLike, provider: string): boolean {
121
+ const providers = supportedProviders(imagesApi);
122
+ if (providers.length === 0) return true;
123
+ return providers.includes(provider);
124
+ }
125
+
106
126
  export interface GenerateOptions {
107
127
  prompt: string;
108
128
  model: ImageGenModel;
@@ -134,6 +154,22 @@ export async function generateImage(options: GenerateOptions): Promise<GenerateR
134
154
  );
135
155
  }
136
156
 
157
+ // pi-ai's images collection carries its own provider set (currently only
158
+ // `openrouter`) and is entirely separate from pi's chat model registry.
159
+ // A chat provider registered by another extension can therefore list image
160
+ // models that generation cannot actually drive — pi-ai answers with a bare
161
+ // "Unknown provider: x". Detect that here and say something useful.
162
+ if (!providerCanGenerate(imagesApi, model.provider)) {
163
+ const supported = supportedProviders(imagesApi);
164
+ throw new Error(
165
+ `Provider "${model.provider}" cannot generate images.\n` +
166
+ `→ Image generation is served by: ${supported.join(", ") || "openrouter"}.\n` +
167
+ `→ "${model.provider}" is a chat provider; its image models are listed for ` +
168
+ `recognition and reference, but generation must go through a supported provider.\n` +
169
+ "→ Pick one with /unipi:image-settings.",
170
+ );
171
+ }
172
+
137
173
  // Prefer pi-ai's own credential store, then the caller-supplied fallback so
138
174
  // a bare OPENROUTER_API_KEY still works.
139
175
  let apiKey: string | undefined;
@@ -105,6 +105,23 @@ export async function getImagesModels(): Promise<ImagesModelsLike | null> {
105
105
  return cachedImagesModels;
106
106
  }
107
107
 
108
+ /**
109
+ * Providers pi-ai's images collection can actually generate with.
110
+ *
111
+ * This is NOT the same set as pi's chat model registry: a chat provider
112
+ * registered by another extension may list image models that image generation
113
+ * cannot drive. Empty means "unknown", which callers treat as permissive.
114
+ */
115
+ export async function getGeneratingProviders(): Promise<string[]> {
116
+ const images = await getImagesModels();
117
+ if (!images) return [];
118
+ try {
119
+ return [...new Set(images.getModels().map((m) => m.provider))];
120
+ } catch {
121
+ return [];
122
+ }
123
+ }
124
+
108
125
  /** List available image-generation models. Empty when unavailable. */
109
126
  export async function listImageGenModels(): Promise<ImageGenModel[]> {
110
127
  const images = await getImagesModels();
@@ -14,6 +14,8 @@ export interface SelectableModel {
14
14
  provider: string;
15
15
  id: string;
16
16
  name?: string;
17
+ /** Set when the model is listed but not usable, with the reason. */
18
+ unavailable?: string;
17
19
  }
18
20
 
19
21
  export type ModelSelectorKind = "generate" | "recognize";
@@ -212,6 +214,12 @@ export class ImageModelSelectorOverlay implements Component {
212
214
  this.error = "No model selected";
213
215
  return;
214
216
  }
217
+ // Require a deliberate second Enter on a model that cannot be used, rather
218
+ // than silently saving a choice that will fail at call time.
219
+ if (model.unavailable && this.error === null) {
220
+ this.error = `${model.id} ${model.unavailable} — press Enter again to select anyway`;
221
+ return;
222
+ }
215
223
 
216
224
  this.onSelect?.(`${model.provider}/${model.id}`);
217
225
  this.saved = true;
@@ -335,9 +343,12 @@ export class ImageModelSelectorOverlay implements Component {
335
343
  const marker = isSelected ? this.fg("accent", "▸") : " ";
336
344
  const label = model.name || model.id;
337
345
  const providerTag = this.fg("dim", `[${model.provider}]`);
338
- const display = isSelected
346
+ const base = isSelected
339
347
  ? `${providerTag} ${this.bold(label)}`
340
348
  : `${providerTag} ${this.fg("dim", label)}`;
349
+ const display = model.unavailable
350
+ ? `${base} ${this.fg("warning", `(${model.unavailable})`)}`
351
+ : base;
341
352
  lines.push(this.frameLine(` ${marker} ${display}`, innerWidth));
342
353
  }
343
354
  }
@@ -17,6 +17,7 @@ import {
17
17
  } from "../settings.js";
18
18
  import {
19
19
  formatModelRef,
20
+ getGeneratingProviders,
20
21
  listAllImageGenModels,
21
22
  listVisionModels,
22
23
  type ChatModelRegistry,
@@ -241,7 +242,20 @@ async function collectModels(
241
242
  // Include models from providers registered by other extensions, not just
242
243
  // pi-ai's built-in OpenRouter catalog.
243
244
  const models = await listAllImageGenModels(registry);
244
- return models.map((m) => ({ provider: m.provider, id: m.id, name: m.name }));
245
+ const generating = await getGeneratingProviders();
246
+
247
+ return models.map((m) => ({
248
+ provider: m.provider,
249
+ id: m.id,
250
+ name: m.name,
251
+ // pi-ai's images collection has its own provider set. A chat provider's
252
+ // image models are listed for reference but cannot actually generate,
253
+ // so flag them rather than letting the user pick a dead option.
254
+ unavailable:
255
+ generating.length > 0 && !generating.includes(m.provider)
256
+ ? "cannot generate"
257
+ : undefined,
258
+ }));
245
259
  }
246
260
 
247
261
  if (!registry) return [];
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -6,8 +6,35 @@
6
6
  */
7
7
 
8
8
  import { existsSync, readFileSync } from "fs";
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { findPackageRoot } from "@pi-unipi/core";
12
+ import { isNewerVersion } from "./version.js";
9
13
  import type { ChangelogEntry } from "../types.js";
10
14
 
15
+ /**
16
+ * Locate the shipped CHANGELOG.md.
17
+ *
18
+ * Resolving from `process.cwd()` only works when pi happens to be running
19
+ * inside the UniPi checkout — for everyone else the update prompt and
20
+ * `/unipi:changelog` came up empty. Resolve from this module's own location
21
+ * instead, and fall back to the working directory so a repo checkout still
22
+ * shows its local (possibly unreleased) notes.
23
+ */
24
+ export function resolveChangelogPath(): string {
25
+ try {
26
+ const dir = dirname(fileURLToPath(import.meta.url));
27
+ const root = findPackageRoot(dir, "@pi-unipi/unipi");
28
+ if (root) {
29
+ const packaged = join(root, "CHANGELOG.md");
30
+ if (existsSync(packaged)) return packaged;
31
+ }
32
+ } catch {
33
+ // Fall through to the working directory.
34
+ }
35
+ return join(process.cwd(), "CHANGELOG.md");
36
+ }
37
+
11
38
  /** Regex for version headers: ## [x.y.z] — YYYY-MM-DD or ## [Unreleased] */
12
39
  const VERSION_HEADER_RE = /^## \[(.+?)\](?:\s*[-—–]\s*(.+))?$/;
13
40
 
@@ -119,7 +146,11 @@ export function getNewerVersions(
119
146
  result.push(entry);
120
147
  continue;
121
148
  }
122
- if (entry.version === installedVersion) break;
149
+ // Compare rather than test for equality. Stopping only on an exact match
150
+ // meant that when the installed version was absent from the changelog
151
+ // (a local build, a yanked release, or simply a newer version) every
152
+ // historical entry was reported as "new".
153
+ if (!isNewerVersion(entry.version, installedVersion)) break;
123
154
  result.push(entry);
124
155
  }
125
156
  return result;
@@ -9,7 +9,7 @@ import { existsSync } from "fs";
9
9
  import { join } from "path";
10
10
  import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
11
  import type { Theme } from "@earendil-works/pi-coding-agent";
12
- import { parseChangelog } from "../changelog.js";
12
+ import { parseChangelog, resolveChangelogPath } from "../changelog.js";
13
13
  import { renderMarkdown } from "../markdown.js";
14
14
  import { getInstalledPackageVersion, boxInnerWidth } from "@pi-unipi/core";
15
15
  import type { ChangelogEntry } from "../../types.js";
@@ -62,7 +62,7 @@ export function renderChangelogOverlay() {
62
62
  let loaded = false;
63
63
  const ensureLoaded = () => {
64
64
  if (loaded) return;
65
- const changelogPath = join(process.cwd(), "CHANGELOG.md");
65
+ const changelogPath = resolveChangelogPath();
66
66
  if (existsSync(changelogPath)) {
67
67
  state.entries = parseChangelog(changelogPath);
68
68
  }
@@ -9,7 +9,7 @@
9
9
  import { join } from "path";
10
10
  import { Key, matchesKey, truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
11
11
  import type { Theme } from "@earendil-works/pi-coding-agent";
12
- import { parseChangelog, getNewerVersions } from "../changelog.js";
12
+ import { parseChangelog, getNewerVersions, resolveChangelogPath } from "../changelog.js";
13
13
  import { renderMarkdown } from "../markdown.js";
14
14
  import { installUpdate } from "../installer.js";
15
15
  import { writeSkippedVersion } from "../cache.js";
@@ -51,7 +51,7 @@ export function renderUpdateOverlay(checkResult: UpdateCheckResult) {
51
51
 
52
52
  // Load changelog for newer versions
53
53
  let newerVersions: ChangelogEntry[] = [];
54
- const changelogPath = join(process.cwd(), "CHANGELOG.md");
54
+ const changelogPath = resolveChangelogPath();
55
55
  try {
56
56
  const entries = parseChangelog(changelogPath);
57
57
  newerVersions = getNewerVersions(entries, checkResult.currentVersion);