@pi-unipi/unipi 3.0.0-alpha.24 → 3.0.0-alpha.26
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 +40 -0
- package/README.md +71 -1
- package/package.json +22 -21
- package/packages/ask-user/handoff.ts +2 -1
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +1 -1
- package/packages/background-tasks/package.json +2 -2
- package/packages/btw/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/compaction/hooks.ts +2 -2
- package/packages/compactor/src/info-screen.ts +32 -2
- package/packages/core/README.md +7 -1
- package/packages/core/harness-messages.ts +28 -4
- package/packages/core/index.ts +3 -0
- package/packages/core/package.json +1 -1
- package/packages/core/src/compaction-savings.ts +122 -0
- package/packages/core/src/hints/index.ts +42 -0
- package/packages/core/src/hints/lines.ts +268 -6
- package/packages/core/src/tui/viz.ts +511 -0
- package/packages/footer/package.json +3 -3
- package/packages/fusion/package.json +4 -4
- package/packages/fusion/src/index.ts +9 -0
- package/packages/info-screen/README.md +113 -34
- package/packages/info-screen/config.ts +16 -14
- package/packages/info-screen/core-groups.ts +281 -483
- package/packages/info-screen/index.ts +197 -165
- package/packages/info-screen/package.json +7 -3
- package/packages/info-screen/pages/context.ts +211 -0
- package/packages/info-screen/pages/inventory.ts +156 -0
- package/packages/info-screen/pages/modules.ts +358 -0
- package/packages/info-screen/pages/session.ts +356 -0
- package/packages/info-screen/pages/usage.ts +170 -0
- package/packages/info-screen/palette.ts +80 -0
- package/packages/info-screen/registry.ts +183 -105
- package/packages/info-screen/tui/info-overlay.ts +287 -499
- package/packages/info-screen/tui/page-kit.ts +222 -0
- package/packages/info-screen/tui/self-dismiss.ts +55 -0
- package/packages/info-screen/tui/splash.ts +204 -0
- package/packages/info-screen/types.ts +39 -10
- package/packages/info-screen/usage-parser.ts +147 -11
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/index.ts +1 -0
- package/packages/kanboard/package.json +7 -7
- package/packages/long-horizon/index.ts +8 -0
- package/packages/long-horizon/package.json +2 -2
- package/packages/long-horizon/src/commands.ts +7 -1
- package/packages/long-horizon/src/gate.ts +70 -1
- package/packages/mcp/package.json +2 -2
- package/packages/mcp/src/index.ts +13 -0
- package/packages/memory/index.ts +19 -0
- package/packages/memory/package.json +3 -3
- package/packages/notify/package.json +2 -2
- package/packages/skill-registry/index.ts +6 -4
- package/packages/skill-registry/package.json +2 -2
- package/packages/subagents/package.json +3 -3
- package/packages/unipi/bundled.js +4188 -1921
- package/packages/unipi/index.ts +27 -1
- package/packages/updater/package.json +2 -2
- package/packages/updater/src/index.ts +22 -9
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/answer/index.ts +7 -2
- package/packages/utility/src/render/harness.ts +12 -2
- package/packages/utility/src/summarize/index.ts +8 -2
- package/packages/watchdog/package.json +3 -3
- package/packages/web-api/package.json +2 -2
- package/packages/web-api/src/index.ts +21 -0
- package/packages/workflow/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,46 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [3.0.0-alpha.26] — 2026-10-04
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `fusion`: the "Non-trivial shell work since last handoff" nudge fires at most once per agent run instead of repeating on every 4th non-trivial shell command; a sidekick handoff still resets the streak but no longer re-arms the nudge, and a new prompt arms it again. (UNI-87)
|
|
13
|
+
- `long-horizon`: mode-control tools are now truly OFF in pi's active tool set outside their mode (previously registered/active and only hidden from the provider payload), so pi's tool snippets and other modules no longer see them. Fresh and resumed sessions start with them off; resolving into a mode (command, judge, or active owner) turns that mode's tools on. (UNI-90)
|
|
14
|
+
- `info-screen`: scope order now reads biggest first — footer legend `g global p project s session`, Compactor savings tiles global → project → session, Memory tiles global → project → share.
|
|
15
|
+
- `info-screen`: Skills page tags each source with its real scope (`g` for user/agents/unipi/package skills, `p` for project skills) instead of implying session scope; the Tools page's tool list is tagged `s` (the active set is per session).
|
|
16
|
+
- `info-screen`: Usage page leaves one blank line between the model share bar and the ranked model rows.
|
|
17
|
+
|
|
18
|
+
## [3.0.0-alpha.25] — 2026-10-04
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- `info-screen`: **Unicrab splash** at startup — the existing Unicrab half-block art, a shimmering block wordmark with version chip, greeting, how fast Pi got ready, and live facts (resumed session totals or cwd/branch, today's spend from the cached snapshot, update available), with a draining countdown edge. Non-capturing and stack-safe in `auto-close` mode. Replaces the boot-time dashboard; the hints start-screen header is unchanged. (UNI-78)
|
|
22
|
+
- `info-screen`: redesigned **`/unipi:info`** dashboard — opaque panel coloured per page, pill tab strip, fixed body height, page dots, `1`–`9` jump, `/unipi:info <page>`. Pages draw themselves with a new Unicode toolkit, no emoji. (UNI-79)
|
|
23
|
+
- **This session** (first page): cost / tokens / replies / time in 3-row gradient digits, context meter, token mix + cache hit, tokens-per-reply braille chart, ranked tools with failures, directory/branch/files/compactions. (UNI-80)
|
|
24
|
+
- **Usage**: today / week / month spend, 30-day column chart with Monday ticks, model share bar + ranked models. (UNI-81)
|
|
25
|
+
- **Tools / Skills / Modules**: live from pi (`getAllTools`, `getCommands`), source share bars, column grids, active vs. off; modules with versions and measured load time. (UNI-82)
|
|
26
|
+
- **MCP, Compactor, Updates, Web, Memory, Keys**: status dots, compaction before→after bars, version track, provider × capability matrix, memory types, keycaps. (UNI-83)
|
|
27
|
+
- `core`: `viz` kit (`Paint`, `bigText`, `gradient`, `spark`, `columns`, `brailleArea`, `gauge`, `shareBar`, `chip`, `grid`, …) — every piece returns exact-width lines in truecolor or 256-colour. (UNI-77)
|
|
28
|
+
- `scripts/info-preview`: print the splash and every page at any width without Pi.
|
|
29
|
+
- `info-screen`: **scope tags** `s` session / `p` project / `g` global on every scoped number (legend in the footer); project pages (memory, mcp, compactor) are cached per workspace, so one project's numbers never show in another. MCP servers show whether they come from global or project config. (UNI-91)
|
|
30
|
+
- `info-screen` / `core`: **compaction savings** — tokens not re-sent (saved × replies after each compaction) and ≈ money not spent at the context rate actually paid, for the session, the project and all projects (`core/src/compaction-savings.ts`; the usage parser collects it per session file). Compactor page: savings tiles, without-vs-with bars, per-compaction timeline. (UNI-92)
|
|
31
|
+
- `core`: 37 new Unicrab hints (127 in total). Catch-up tips for what shipped since the hints release — `/unipi:summarize`, `/unipi:info`, `/unipi:regular`, `Ctrl+O` in simple mode, **E**/**M** in `/unipi:skills`, the splash setting, the `s`/`p`/`g` tags — and 8 "what's new" tips tied to alpha.22–alpha.25 (shown once after you update). Eight new lore lines.
|
|
32
|
+
- `core`: **moment hints** — Unicrab reacts once a session to a late-night turn, 50 clean tool calls in a row, an `rm -rf`, a force push, a 40k+ character write, a ≥95% cache-hit reply, a finished goal, a deleted memory, new MCP tools and a ralph lap. The hint-count test is gone; only the "every named command exists" check stays.
|
|
33
|
+
- `long-horizon`: the owner-changed event now carries `reason` when an owner finishes.
|
|
34
|
+
- `info-screen`: **context breakdown** on This session — a bucket split into system prompt, tool schemas, your prompts, replies, tool results and summaries (from what pi will send next), window gauge on its edge, heaviest tool outputs. (UNI-93)
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- `info-screen`: Usage chart labels its peak and today over the bars with a real date axis; one model → one line; model bars are true shares. (UNI-94)
|
|
38
|
+
- `info-screen`: Modules page shows what each module adds (tools, commands, settings, keys — recorded exactly by `packages/unipi` during load) instead of millisecond bars; load time is flagged only past 20 ms. (UNI-95)
|
|
39
|
+
- harness: prompts that UniPi sends for you (`/unipi:goal`/`ralph`/`swarm`/`graph` kickoffs, `/unipi:summarize`, `/unipi:answer`, the compactor follow-up, the ask-user handoff) render as a UniPi harness panel instead of a plain user message. The skills reveal names the skills in simple mode and lists them in advanced mode. (UNI-88, UNI-89)
|
|
40
|
+
- `info-screen`: `bootMode` / `bootTimeoutMs` now control the splash (default 2.5 s). `groupOrder` and per-page show/hide are honoured and re-read when the dashboard opens. Duplicate tab priorities fixed by one page-style table.
|
|
41
|
+
|
|
42
|
+
### Docs
|
|
43
|
+
- README: new **See your session** section with screenshots of the splash and the dashboard (demo data), a **What's new** table for the recent releases, and two new rows in "Why UniPi". The Info Screen README explains the context bucket and how compaction savings are counted.
|
|
44
|
+
- `scripts/info-preview/demo.ts`: demo session and history for screenshots — no real paths or spend.
|
|
45
|
+
|
|
46
|
+
### Performance
|
|
47
|
+
- `info-screen`: page data is saved to `~/.unipi/cache/info-screen.json`, so the dashboard opens filled; page bodies are memoised; the session page walks the transcript once per new entry. Usage refresh on a 1,500-session history went from ~190 ms to ~35 ms warm (in-memory parser cache, debounced cache writes, no per-record string keys). (UNI-84)
|
|
48
|
+
|
|
9
49
|
## [3.0.0-alpha.24] — 2026-10-04
|
|
10
50
|
|
|
11
51
|
### Added
|
package/README.md
CHANGED
|
@@ -64,6 +64,8 @@ runs commands. UniPi adds the parts that a long session needs.
|
|
|
64
64
|
| **A board for deferred work** | Kanboard keeps tasks in Markdown files, with a web UI and a terminal UI. |
|
|
65
65
|
| **Hang detection** | Watchdog asks a small judge model about long tool calls. |
|
|
66
66
|
| **A short transcript** | Simple mode shows one line for each tool call. Ctrl+O expands it. |
|
|
67
|
+
| **A view of the session** | `/unipi:info` shows cost, context use, spend history and what compaction saved. |
|
|
68
|
+
| **A recap at the end** | The `summarize` skill gives the answer first, then open items, in plain words. |
|
|
67
69
|
|
|
68
70
|
## Proof
|
|
69
71
|
|
|
@@ -118,6 +120,61 @@ review or answer a blocked question in one click.
|
|
|
118
120
|
|
|
119
121
|
The screenshots use demo data. Read the [Kanboard README](packages/kanboard/README.md).
|
|
120
122
|
|
|
123
|
+
## See your session: `/unipi:info`
|
|
124
|
+
|
|
125
|
+
Pi starts. Unicrab says hello and gives you three facts: how fast Pi got
|
|
126
|
+
ready, the session you came back to, and what you spent today. Then it goes
|
|
127
|
+
away. It does not take your keys, so you can type your first prompt at once.
|
|
128
|
+
|
|
129
|
+
<p align="center">
|
|
130
|
+
<img src="docs/assets/screenshots/info-splash.png" width="760" alt="The Unicrab startup splash: mascot, UNIPI wordmark, version, ready time, resumed session and today's spend">
|
|
131
|
+
</p>
|
|
132
|
+
|
|
133
|
+
Run `/unipi:info` when you want the full picture. The first page is about
|
|
134
|
+
**this session**. It answers the questions you ask during long work:
|
|
135
|
+
|
|
136
|
+
- **What did this cost?** Cost, tokens, replies and time, in large digits.
|
|
137
|
+
- **What fills my context?** A bucket shows each part in its own colour:
|
|
138
|
+
system prompt, tool schemas, summaries, your prompts, replies and tool
|
|
139
|
+
results. The edge of the bucket shows how full the window is. It turns
|
|
140
|
+
amber at 70% and red at 90%.
|
|
141
|
+
- **Which tool output is the heaviest?** The page names the three largest.
|
|
142
|
+
- **Which tools failed?** Each tool gets a bar, and failures show in red.
|
|
143
|
+
|
|
144
|
+
<p align="center">
|
|
145
|
+
<img src="docs/assets/screenshots/info-session.png" alt="The This session page: cost, tokens, replies, time, the context bucket, billed tokens and tools">
|
|
146
|
+
</p>
|
|
147
|
+
|
|
148
|
+
The other pages answer the questions that come later:
|
|
149
|
+
|
|
150
|
+
| Page | It shows |
|
|
151
|
+
|---|---|
|
|
152
|
+
| Usage | Spend today, this week and this month, a 30-day chart, and the share of each model. |
|
|
153
|
+
| Compactor | Tokens that compaction kept out of your requests, and the money that saved. |
|
|
154
|
+
| Tools · Skills · Modules | What Pi loaded, where it came from, and what each UniPi module adds. |
|
|
155
|
+
| MCP · Memory · Web · Updates · Keys | The state of each module. |
|
|
156
|
+
|
|
157
|
+
<table>
|
|
158
|
+
<tr>
|
|
159
|
+
<td><img src="docs/assets/screenshots/info-usage.png" alt="The Usage page: spend tiles, a 30-day chart with the peak labelled, and models this month"></td>
|
|
160
|
+
<td><img src="docs/assets/screenshots/info-compactor.png" alt="The Compactor page: money saved for this session, this project and all projects, and a without-versus-with bar"></td>
|
|
161
|
+
</tr>
|
|
162
|
+
</table>
|
|
163
|
+
|
|
164
|
+
A small letter before a number tells you its scope: **`s`** is this session,
|
|
165
|
+
**`p`** is this project and **`g`** is every project on this machine. Numbers
|
|
166
|
+
from one project never show in another.
|
|
167
|
+
|
|
168
|
+
How the Compactor page counts: each compaction makes the context smaller.
|
|
169
|
+
Every reply after it sends that smaller context. The page multiplies the
|
|
170
|
+
tokens removed by the number of replies that followed. Then it prices them at
|
|
171
|
+
the rate you paid for context. A free model saves tokens but no money.
|
|
172
|
+
|
|
173
|
+
Keys: `←`/`→` or `1`–`9` change the page, `r` refreshes it, `q` closes it.
|
|
174
|
+
`/unipi:info usage` opens one page. The dashboard opens at once on cached
|
|
175
|
+
numbers, then updates them in the background. The screenshots use demo data.
|
|
176
|
+
Read the [Info Screen README](packages/info-screen/README.md).
|
|
177
|
+
|
|
121
178
|
## Harness architecture
|
|
122
179
|
|
|
123
180
|
UniPi adds these mechanisms to the Pi harness. Each page gives the problem, a
|
|
@@ -167,7 +224,9 @@ each package.
|
|
|
167
224
|
|
|
168
225
|
Unicrab is the UniPi mascot. It started as the author's pet crab. Now it says
|
|
169
226
|
hello when Pi starts and leaves hints above the input box. Press `Alt+H` for the
|
|
170
|
-
next hint.
|
|
227
|
+
next hint, or run `/unipi:hint` to read all 127 of them.
|
|
228
|
+
|
|
229
|
+
After an update, the first hint tells you what is new in that release.
|
|
171
230
|
|
|
172
231
|
Read [The making of Unicrab](docs/story/unicrab.md): from a real crab, to pixel
|
|
173
232
|
art, to a terminal character that is 7 columns wide.
|
|
@@ -176,6 +235,17 @@ art, to a terminal character that is 7 columns wide.
|
|
|
176
235
|
<img src="docs/assets/screenshots/unicrab-start.png" alt="The UniPi start screen with Unicrab in half-block pixel art">
|
|
177
236
|
</p>
|
|
178
237
|
|
|
238
|
+
## What's new
|
|
239
|
+
|
|
240
|
+
The recent releases, in short. The [changelog](CHANGELOG.md) has the details.
|
|
241
|
+
|
|
242
|
+
| Release | What changed for you |
|
|
243
|
+
|---|---|
|
|
244
|
+
| alpha.25 | The Unicrab splash at startup. A new `/unipi:info`: this session, the context bucket, compaction savings and `s`/`p`/`g` scope tags. Prompts that UniPi sends for you (goal, summarize, answer) show as a UniPi panel, not as your own message. |
|
|
245
|
+
| alpha.24 | The `summarize` skill and `/unipi:summarize [focus]`. Long work ends with a short recap: the answer first, then findings, open items and questions. |
|
|
246
|
+
| alpha.23 | `/unipi:settings` explains each setting and each option in a panel below the list. Web tools with no working provider hide from the agent. |
|
|
247
|
+
| alpha.22 | Simple mode is the default. Each tool call is one line, and `Ctrl+O` opens it. Images no longer overlap the text below them. |
|
|
248
|
+
|
|
179
249
|
## Docs
|
|
180
250
|
|
|
181
251
|
- [Getting started](docs/guide/getting-started.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.26",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"packages/*/bridge/**/*.py",
|
|
45
45
|
"packages/*/platforms/*",
|
|
46
46
|
"packages/*/tui/*",
|
|
47
|
+
"packages/*/pages/*.ts",
|
|
47
48
|
"packages/*/SKILL.md",
|
|
48
49
|
"packages/*/skills/**/*",
|
|
49
50
|
"packages/*/README.md",
|
|
@@ -82,26 +83,26 @@
|
|
|
82
83
|
"typebox": "^1.1.38"
|
|
83
84
|
},
|
|
84
85
|
"dependencies": {
|
|
85
|
-
"@pi-unipi/ask-user": "3.0.0-alpha.
|
|
86
|
-
"@pi-unipi/background-tasks": "3.0.0-alpha.
|
|
87
|
-
"@pi-unipi/btw": "3.0.0-alpha.
|
|
88
|
-
"@pi-unipi/command-enchantment": "3.0.0-alpha.
|
|
89
|
-
"@pi-unipi/compactor": "3.0.0-alpha.
|
|
90
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
91
|
-
"@pi-unipi/footer": "3.0.0-alpha.
|
|
92
|
-
"@pi-unipi/fusion": "3.0.0-alpha.
|
|
93
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
94
|
-
"@pi-unipi/input-shortcuts": "3.0.0-alpha.
|
|
95
|
-
"@pi-unipi/kanboard": "3.0.0-alpha.
|
|
96
|
-
"@pi-unipi/mcp": "3.0.0-alpha.
|
|
97
|
-
"@pi-unipi/memory": "3.0.0-alpha.
|
|
98
|
-
"@pi-unipi/notify": "3.0.0-alpha.
|
|
99
|
-
"@pi-unipi/skill-registry": "3.0.0-alpha.
|
|
100
|
-
"@pi-unipi/subagents": "3.0.0-alpha.
|
|
101
|
-
"@pi-unipi/updater": "3.0.0-alpha.
|
|
102
|
-
"@pi-unipi/utility": "3.0.0-alpha.
|
|
103
|
-
"@pi-unipi/web-api": "3.0.0-alpha.
|
|
104
|
-
"@pi-unipi/workflow": "3.0.0-alpha.
|
|
86
|
+
"@pi-unipi/ask-user": "3.0.0-alpha.26",
|
|
87
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.26",
|
|
88
|
+
"@pi-unipi/btw": "3.0.0-alpha.26",
|
|
89
|
+
"@pi-unipi/command-enchantment": "3.0.0-alpha.26",
|
|
90
|
+
"@pi-unipi/compactor": "3.0.0-alpha.26",
|
|
91
|
+
"@pi-unipi/core": "3.0.0-alpha.26",
|
|
92
|
+
"@pi-unipi/footer": "3.0.0-alpha.26",
|
|
93
|
+
"@pi-unipi/fusion": "3.0.0-alpha.26",
|
|
94
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.26",
|
|
95
|
+
"@pi-unipi/input-shortcuts": "3.0.0-alpha.26",
|
|
96
|
+
"@pi-unipi/kanboard": "3.0.0-alpha.26",
|
|
97
|
+
"@pi-unipi/mcp": "3.0.0-alpha.26",
|
|
98
|
+
"@pi-unipi/memory": "3.0.0-alpha.26",
|
|
99
|
+
"@pi-unipi/notify": "3.0.0-alpha.26",
|
|
100
|
+
"@pi-unipi/skill-registry": "3.0.0-alpha.26",
|
|
101
|
+
"@pi-unipi/subagents": "3.0.0-alpha.26",
|
|
102
|
+
"@pi-unipi/updater": "3.0.0-alpha.26",
|
|
103
|
+
"@pi-unipi/utility": "3.0.0-alpha.26",
|
|
104
|
+
"@pi-unipi/web-api": "3.0.0-alpha.26",
|
|
105
|
+
"@pi-unipi/workflow": "3.0.0-alpha.26"
|
|
105
106
|
},
|
|
106
107
|
"devDependencies": {
|
|
107
108
|
"@earendil-works/pi-agent-core": "^0.87.1",
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { sendHarnessUserMessage } from "@pi-unipi/core";
|
|
8
9
|
import type { SessionLaunchReason, SessionLaunchStatus } from "./types.js";
|
|
9
10
|
|
|
10
11
|
/** Compact handoff fallback timer. Keeps the launcher from stalling if callbacks wait for the tool turn to finish. */
|
|
@@ -80,7 +81,7 @@ function deliverFollowUpMessage(
|
|
|
80
81
|
reason: SessionLaunchReason,
|
|
81
82
|
): HandoffResult {
|
|
82
83
|
try {
|
|
83
|
-
pi
|
|
84
|
+
sendHarnessUserMessage(pi, prefill, { source: "Ask user", title: "Handoff", synopsis: "Prefill from the launcher" }, { deliverAs: "followUp" });
|
|
84
85
|
return { status: "queued", reason, prefill };
|
|
85
86
|
} catch (error) {
|
|
86
87
|
return fallbackToEditor(ctx, prefill, reason, error);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ask-user",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.26",
|
|
4
4
|
"description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
43
|
+
"@pi-unipi/core": "3.0.0-alpha.26"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -171,7 +171,7 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
171
171
|
|
|
172
172
|
"unipi:settings": "Configure all unipi modules in one panel",
|
|
173
173
|
|
|
174
|
-
"unipi:info": "
|
|
174
|
+
"unipi:info": "Dashboard — this session, context, usage, compaction savings, modules",
|
|
175
175
|
"unipi:hint": "Browse and search Unicrab hints, or cycle/reset",
|
|
176
176
|
|
|
177
177
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/background-tasks",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.26",
|
|
4
4
|
"description": "Background tasks for UniPi — durable shell jobs and delegated background agents with a footer dock",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@earendil-works/pi-ai": "^0.87.1",
|
|
13
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
13
|
+
"@pi-unipi/core": "3.0.0-alpha.26",
|
|
14
14
|
"turndown": "^7.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.26",
|
|
4
4
|
"description": "A pi extension for parallel side conversations with /unipi:btw — part of the Unipi suite",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "extensions/btw.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@earendil-works/pi-tui": "^0.87.1"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
40
|
+
"@pi-unipi/core": "3.0.0-alpha.26"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.26",
|
|
4
4
|
"description": "Compaction for Pi — lossless zero-LLM or model summaries, rebuilt from full history, with session recall",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "3.0.0-alpha.
|
|
37
|
-
"@pi-unipi/info-screen": "3.0.0-alpha.
|
|
36
|
+
"@pi-unipi/core": "3.0.0-alpha.26",
|
|
37
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.26",
|
|
38
38
|
"@earendil-works/pi-agent-core": "^0.87.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import type { ExtensionAPI, ExtensionContext, SessionBeforeCompactEvent, SessionCompactEvent } from "@earendil-works/pi-coding-agent";
|
|
16
16
|
import { compact as piCompact, generateSummaryWithUsage, DEFAULT_COMPACTION_SETTINGS } from "@earendil-works/pi-coding-agent";
|
|
17
|
-
import { collectCompactionContext, emitEvent, formatTokens, UNIPI_EVENTS } from "@pi-unipi/core";
|
|
17
|
+
import { collectCompactionContext, emitEvent, formatTokens, sendHarnessUserMessage, UNIPI_EVENTS } from "@pi-unipi/core";
|
|
18
18
|
import { loadConfig } from "../config/manager.js";
|
|
19
19
|
import { autoCompactionOf } from "../config/schema.js";
|
|
20
20
|
import { buildOwnCut, resolveSmartKeepUserTurns, applyTailBudget, MAX_SMART_TAIL_TOKENS } from "./cut.js";
|
|
@@ -444,7 +444,7 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
|
|
|
444
444
|
if (followUp) {
|
|
445
445
|
setTimeout(() => {
|
|
446
446
|
try {
|
|
447
|
-
pi
|
|
447
|
+
sendHarnessUserMessage(pi, followUp, { source: "Compactor", title: "Follow-up", synopsis: "Prompt queued after compaction" }, { deliverAs: "followUp" });
|
|
448
448
|
} catch {}
|
|
449
449
|
}, 0);
|
|
450
450
|
}
|
|
@@ -3,17 +3,33 @@
|
|
|
3
3
|
* compactions, read from the session branch.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { formatTokens } from "@pi-unipi/core";
|
|
6
|
+
import { compactionSavings, formatTokens, type CompactionSaving, type SavingsTotals } from "@pi-unipi/core";
|
|
7
7
|
import { loadConfig } from "./config/manager.js";
|
|
8
8
|
import { methodBreakdown, sessionCompactionStats } from "./stats.js";
|
|
9
9
|
|
|
10
|
-
type Stat = { value: string; detail: string };
|
|
10
|
+
type Stat = { value: string; detail: string; raw?: unknown };
|
|
11
|
+
|
|
12
|
+
/** Structured payload for the info-screen Compactor page renderer. */
|
|
13
|
+
export interface CompactorPageRaw {
|
|
14
|
+
method: string;
|
|
15
|
+
trigger: string;
|
|
16
|
+
piCompact: string;
|
|
17
|
+
history: Array<{ before: number; after: number; method: string; at?: number }>;
|
|
18
|
+
before: number;
|
|
19
|
+
after: number;
|
|
20
|
+
saved: number;
|
|
21
|
+
/** This session's savings (tokens not re-sent + money). */
|
|
22
|
+
session: SavingsTotals;
|
|
23
|
+
/** Per-compaction savings for the timeline. */
|
|
24
|
+
items: CompactionSaving[];
|
|
25
|
+
}
|
|
11
26
|
|
|
12
27
|
export interface CompactorInfoData {
|
|
13
28
|
settings: Stat;
|
|
14
29
|
compactions: Stat;
|
|
15
30
|
tokens: Stat;
|
|
16
31
|
last: Stat;
|
|
32
|
+
raw: Stat;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
35
|
const METHOD_NAME = { vcc: "lossless", llm: "model summary" } as const;
|
|
@@ -32,7 +48,21 @@ export function getInfoScreenData(branch: readonly any[], cwd: string = process.
|
|
|
32
48
|
const count = stats.compactions.length;
|
|
33
49
|
const last = stats.compactions[count - 1];
|
|
34
50
|
const pct = stats.tokensBefore > 0 ? Math.round((stats.tokensSaved / stats.tokensBefore) * 100) : 0;
|
|
51
|
+
const raw: CompactorPageRaw = {
|
|
52
|
+
method: METHOD_NAME[config.method],
|
|
53
|
+
trigger: config.trigger === "pi" ? "at Pi's context limit" : `at ${config.thresholdPercent}% of context`,
|
|
54
|
+
piCompact: config.piCompact === "follow" ? "same as method" : METHOD_NAME[config.piCompact],
|
|
55
|
+
history: stats.compactions.slice(-24).map((c) => ({ before: c.tokensBefore, after: c.tokensAfter, method: METHOD_NAME[c.method] ?? c.method, at: c.at })),
|
|
56
|
+
before: stats.tokensBefore,
|
|
57
|
+
after: stats.tokensAfter,
|
|
58
|
+
saved: stats.tokensSaved,
|
|
59
|
+
...(() => {
|
|
60
|
+
const s = compactionSavings(branch);
|
|
61
|
+
return { session: s.totals, items: s.items.slice(-24) };
|
|
62
|
+
})(),
|
|
63
|
+
};
|
|
35
64
|
return {
|
|
65
|
+
raw: { value: "", detail: "", raw },
|
|
36
66
|
settings: {
|
|
37
67
|
value: METHOD_NAME[config.method],
|
|
38
68
|
detail: `when: ${config.trigger === "pi" ? "Pi's context limit" : `${config.thresholdPercent}% of context`} · Pi's /compact: ${config.piCompact === "follow" ? "same as method" : METHOD_NAME[config.piCompact]}`,
|
package/packages/core/README.md
CHANGED
|
@@ -70,7 +70,13 @@ Unicrab is the UniPi mascot. The hints engine shows one short tip above the
|
|
|
70
70
|
editor.
|
|
71
71
|
|
|
72
72
|
- At startup, the engine shows the tip with the lowest show count. After a
|
|
73
|
-
version upgrade, it shows a "what's new" tip first.
|
|
73
|
+
version upgrade, it shows a "what's new" tip first. Each release adds its
|
|
74
|
+
own "what's new" tips, so you learn about a feature when it arrives.
|
|
75
|
+
- The engine has 127 tips in 9 categories: commands, shortcuts, settings,
|
|
76
|
+
capabilities, explanations, troubleshooting, what's new, workflows and lore.
|
|
77
|
+
- Some tips react to a moment, one time per session. Examples are a late-night
|
|
78
|
+
turn, 50 tool calls with no error, an `rm -rf`, a force push, a hot prompt
|
|
79
|
+
cache and a finished goal.
|
|
74
80
|
- The start screen shows the Unicrab pixel art, the version and a random lore
|
|
75
81
|
line. It needs 40 columns or more. At 72 columns or more, it shows the large crab.
|
|
76
82
|
- Some events show a tip. Examples are context use of 70% or more, a `bash`
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* boundaries purge everything outstanding — the failure mode is always native
|
|
32
32
|
* fallback, never a human message mislabelled.
|
|
33
33
|
*/
|
|
34
|
-
import type
|
|
34
|
+
import { parseSkillBlock, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
35
35
|
|
|
36
36
|
export type HarnessDelivery =
|
|
37
37
|
| "direct"
|
|
@@ -47,6 +47,8 @@ export interface HarnessMessageMeta {
|
|
|
47
47
|
source: string;
|
|
48
48
|
title: string;
|
|
49
49
|
synopsis?: string;
|
|
50
|
+
/** Optional pre-rendered summary rows (advanced style renders one row each). */
|
|
51
|
+
lines?: string[];
|
|
50
52
|
delivery: HarnessDelivery;
|
|
51
53
|
severity?: "warning";
|
|
52
54
|
}
|
|
@@ -172,6 +174,10 @@ export function readHarnessMeta(source: unknown): HarnessMessageMeta | undefined
|
|
|
172
174
|
) {
|
|
173
175
|
return undefined;
|
|
174
176
|
}
|
|
177
|
+
// Optional summary lines: malformed ones are dropped, the meta stays valid.
|
|
178
|
+
if (m.lines !== undefined && !(Array.isArray(m.lines) && m.lines.every((l) => typeof l === "string"))) {
|
|
179
|
+
return { ...m, lines: undefined } as HarnessMessageMeta;
|
|
180
|
+
}
|
|
175
181
|
return m as HarnessMessageMeta;
|
|
176
182
|
}
|
|
177
183
|
|
|
@@ -185,6 +191,24 @@ export function userMessageText(content: unknown): string {
|
|
|
185
191
|
.join("");
|
|
186
192
|
}
|
|
187
193
|
|
|
194
|
+
/** Match an admission record's text against a dispatched text. Exact by
|
|
195
|
+
* default; a `/skill:<name> <rest>` record additionally matches its
|
|
196
|
+
* POST-EXPANSION form — pi turns the command into a `<skill …>` block AFTER
|
|
197
|
+
* the input handlers ran (AgentSession._expandSkillCommand), so the
|
|
198
|
+
* before_agent_start prompt and the message user text no longer equal the
|
|
199
|
+
* armed text. Everything else stays exact; no match = native (fail closed). */
|
|
200
|
+
function recordMatchesText(recordText: string, text: string): boolean {
|
|
201
|
+
if (recordText === text) return true;
|
|
202
|
+
if (!recordText.startsWith("/skill:")) return false;
|
|
203
|
+
const skill = parseSkillBlock(text);
|
|
204
|
+
if (!skill) return false;
|
|
205
|
+
// Mirror _expandSkillCommand's split: name up to the first space, rest trimmed.
|
|
206
|
+
const spaceIndex = recordText.indexOf(" ");
|
|
207
|
+
const name = spaceIndex === -1 ? recordText.slice(7) : recordText.slice(7, spaceIndex);
|
|
208
|
+
const rest = spaceIndex === -1 ? "" : recordText.slice(spaceIndex + 1).trim();
|
|
209
|
+
return skill.name === name && (skill.userMessage ?? "") === rest;
|
|
210
|
+
}
|
|
211
|
+
|
|
188
212
|
function userTextFromMessage(message: unknown): string {
|
|
189
213
|
if (!message || typeof message !== "object") return "";
|
|
190
214
|
const m = message as { role?: unknown; content?: unknown };
|
|
@@ -203,7 +227,7 @@ export function sendHarnessUserMessage(
|
|
|
203
227
|
pi: ExtensionAPI,
|
|
204
228
|
content: string,
|
|
205
229
|
meta: HarnessMessageMetaInput,
|
|
206
|
-
options?: { deliverAs?: "steer" | "followUp" },
|
|
230
|
+
options?: { deliverAs?: "steer" | "followUp"; expandPromptTemplates?: boolean },
|
|
207
231
|
): ReturnType<ExtensionAPI["sendUserMessage"]> {
|
|
208
232
|
let arm: Arm | undefined;
|
|
209
233
|
try {
|
|
@@ -253,7 +277,7 @@ function prune(state: HarnessRegistryState): void {
|
|
|
253
277
|
* matching group. */
|
|
254
278
|
function selectRecord(state: HarnessRegistryState, text: string): HarnessMessageMeta | undefined {
|
|
255
279
|
const candidates = state.records.filter(
|
|
256
|
-
(r) => r.state === "admitted" && r.text
|
|
280
|
+
(r) => r.state === "admitted" && recordMatchesText(r.text, text) && (r.kind !== "harness" || r.eligible),
|
|
257
281
|
);
|
|
258
282
|
if (candidates.length === 0) return undefined;
|
|
259
283
|
const origins = new Set(candidates.map((r) => r.origin));
|
|
@@ -336,7 +360,7 @@ export function installHarnessProvenance(pi: ExtensionAPI): void {
|
|
|
336
360
|
// unconfirmed direct records are stale and dropped. Queued untouched.
|
|
337
361
|
for (const r of state.records) {
|
|
338
362
|
if (r.kind === "harness" && r.delivery === "direct" && r.state === "admitted") {
|
|
339
|
-
if (r.text
|
|
363
|
+
if (recordMatchesText(r.text, prompt)) r.eligible = true;
|
|
340
364
|
}
|
|
341
365
|
}
|
|
342
366
|
state.records = state.records.filter(
|
package/packages/core/index.ts
CHANGED
|
@@ -28,6 +28,8 @@ export * from "./src/package-colors.js";
|
|
|
28
28
|
export * from "./src/tui/hub-kit.js";
|
|
29
29
|
export * from "./src/tui/kit.js";
|
|
30
30
|
export * from "./src/tui/progress.js";
|
|
31
|
+
export * from "./src/tui/viz.js";
|
|
32
|
+
export * from "./src/compaction-savings.js";
|
|
31
33
|
export * from "./src/jev/client.js";
|
|
32
34
|
export * from "./src/jev/settings.js";
|
|
33
35
|
export * from "./src/jev/chatter.js";
|
|
@@ -45,4 +47,5 @@ export * from "./compaction-context.js";
|
|
|
45
47
|
export * from "./src/settings/migrations.js";
|
|
46
48
|
export * from "./src/hints/index.js";
|
|
47
49
|
export * from "./src/hints/store.js";
|
|
50
|
+
export { CRAB_14_LINES_256, CRAB_14_LINES_TRUECOLOR, CRAB_22_LINES_256, CRAB_22_LINES_TRUECOLOR } from "./src/hints/crab-data.js";
|
|
48
51
|
export * from "./harness-messages.js";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/core — what compaction saved, from session entries.
|
|
3
|
+
*
|
|
4
|
+
* For every compaction:
|
|
5
|
+
* saved = context before − context after
|
|
6
|
+
* replies = assistant replies sent after it (until the next compaction)
|
|
7
|
+
* avoided = saved × replies tokens that were NOT re-sent
|
|
8
|
+
* rate = Σ paid for context / Σ context tokens of those replies
|
|
9
|
+
* (input + cache read + cache write — the blended price the
|
|
10
|
+
* user actually paid per context token, so cache discounts
|
|
11
|
+
* are already in it)
|
|
12
|
+
* dollars = avoided × rate ≈ money not spent
|
|
13
|
+
*
|
|
14
|
+
* "after" is the compactor's own `details.tokensAfter` when present, else the
|
|
15
|
+
* context size the first reply after the compaction actually reported — the
|
|
16
|
+
* real number, not an estimate.
|
|
17
|
+
*
|
|
18
|
+
* Feed entries in file/branch order; works streaming (usage parser) or over
|
|
19
|
+
* a branch array (info screen).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export interface CompactionSaving {
|
|
23
|
+
at: number;
|
|
24
|
+
before: number;
|
|
25
|
+
after: number;
|
|
26
|
+
replies: number;
|
|
27
|
+
/** Σ cost paid for context tokens by the replies after this compaction. */
|
|
28
|
+
paid: number;
|
|
29
|
+
/** Σ context tokens of those replies. */
|
|
30
|
+
context: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface SavingsTotals {
|
|
34
|
+
count: number;
|
|
35
|
+
saved: number;
|
|
36
|
+
avoided: number;
|
|
37
|
+
dollars: number;
|
|
38
|
+
/** Σ context actually sent after compactions (the "with" bar). */
|
|
39
|
+
sent: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export class SavingsAccumulator {
|
|
43
|
+
readonly items: CompactionSaving[] = [];
|
|
44
|
+
private seen = new Set<string>();
|
|
45
|
+
private open: CompactionSaving | null = null;
|
|
46
|
+
private afterKnown = false;
|
|
47
|
+
|
|
48
|
+
feed(entry: unknown): void {
|
|
49
|
+
const e = entry as { type?: string; timestamp?: string; tokensBefore?: number; details?: { tokensAfter?: number }; message?: any };
|
|
50
|
+
if (!e || typeof e !== "object") return;
|
|
51
|
+
if (e.type === "compaction") {
|
|
52
|
+
const at = Date.parse(e.timestamp ?? "") || 0;
|
|
53
|
+
const before = Number(e.tokensBefore ?? 0);
|
|
54
|
+
const key = `${at}:${before}`;
|
|
55
|
+
// Forked session files copy history; count each compaction once.
|
|
56
|
+
if (this.seen.has(key)) {
|
|
57
|
+
this.open = null;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.seen.add(key);
|
|
61
|
+
const after = Number(e.details?.tokensAfter ?? 0);
|
|
62
|
+
this.open = { at, before, after, replies: 0, paid: 0, context: 0 };
|
|
63
|
+
this.afterKnown = after > 0;
|
|
64
|
+
this.items.push(this.open);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (e.type !== "message" || !this.open) return;
|
|
68
|
+
const m = e.message;
|
|
69
|
+
if (m?.role !== "assistant" || m.stopReason === "error" || m.stopReason === "aborted") return;
|
|
70
|
+
const u = m.usage ?? {};
|
|
71
|
+
const ctx = (u.input ?? 0) + (u.cacheRead ?? 0) + (u.cacheWrite ?? 0);
|
|
72
|
+
if (ctx <= 0) return;
|
|
73
|
+
if (!this.afterKnown) {
|
|
74
|
+
this.open.after = ctx;
|
|
75
|
+
this.afterKnown = true;
|
|
76
|
+
}
|
|
77
|
+
this.open.replies++;
|
|
78
|
+
this.open.context += ctx;
|
|
79
|
+
const c = u.cost ?? {};
|
|
80
|
+
this.open.paid += (c.input ?? 0) + (c.cacheRead ?? 0) + (c.cacheWrite ?? 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
totals(): SavingsTotals {
|
|
84
|
+
return totalsOf(this.items);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function savedOf(c: CompactionSaving): number {
|
|
89
|
+
return Math.max(0, c.before - (c.after || c.before));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function avoidedOf(c: CompactionSaving): number {
|
|
93
|
+
return savedOf(c) * c.replies;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function dollarsOf(c: CompactionSaving): number {
|
|
97
|
+
return c.context > 0 ? avoidedOf(c) * (c.paid / c.context) : 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function totalsOf(items: readonly CompactionSaving[]): SavingsTotals {
|
|
101
|
+
let saved = 0, avoided = 0, dollars = 0, sent = 0;
|
|
102
|
+
for (const c of items) {
|
|
103
|
+
saved += savedOf(c);
|
|
104
|
+
avoided += avoidedOf(c);
|
|
105
|
+
dollars += dollarsOf(c);
|
|
106
|
+
sent += c.context;
|
|
107
|
+
}
|
|
108
|
+
return { count: items.length, saved, avoided, dollars, sent };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function addTotals(a: SavingsTotals, b: SavingsTotals): SavingsTotals {
|
|
112
|
+
return { count: a.count + b.count, saved: a.saved + b.saved, avoided: a.avoided + b.avoided, dollars: a.dollars + b.dollars, sent: a.sent + b.sent };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export const EMPTY_SAVINGS: SavingsTotals = { count: 0, saved: 0, avoided: 0, dollars: 0, sent: 0 };
|
|
116
|
+
|
|
117
|
+
/** Convenience: savings for a whole branch. */
|
|
118
|
+
export function compactionSavings(branch: readonly unknown[]): { items: CompactionSaving[]; totals: SavingsTotals } {
|
|
119
|
+
const acc = new SavingsAccumulator();
|
|
120
|
+
for (const e of branch) acc.feed(e);
|
|
121
|
+
return { items: acc.items, totals: acc.totals() };
|
|
122
|
+
}
|