@pi-unipi/unipi 3.0.0-alpha.16 → 3.0.0-alpha.18
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 +43 -0
- package/package.json +21 -21
- package/packages/ask-user/README.md +1 -1
- package/packages/ask-user/package.json +2 -2
- package/packages/ask-user/tools.ts +19 -0
- package/packages/autocomplete/package.json +1 -1
- package/packages/autocomplete/src/constants.ts +4 -10
- package/packages/background-tasks/package.json +2 -2
- package/packages/btw/package.json +2 -2
- package/packages/compactor/README.md +3 -7
- package/packages/compactor/package.json +3 -3
- package/packages/compactor/src/card.ts +3 -8
- package/packages/compactor/src/commands/index.ts +2 -25
- package/packages/compactor/src/compaction/hooks.ts +6 -51
- package/packages/compactor/src/compaction/summarize.ts +19 -137
- package/packages/compactor/src/config/manager.ts +8 -5
- package/packages/compactor/src/info-screen.ts +1 -1
- package/packages/compactor/src/stats.ts +3 -3
- package/packages/compactor/src/tools/ctx-doctor.ts +1 -1
- package/packages/compactor/src/types.ts +2 -3
- package/packages/core/constants.ts +0 -6
- package/packages/core/global.d.ts +10 -0
- package/packages/core/index.ts +2 -0
- package/packages/core/package.json +1 -1
- package/packages/core/src/child-agent/runtime.ts +14 -1
- package/packages/core/src/tips/index.ts +257 -0
- package/packages/core/src/tips/lines.ts +233 -0
- package/packages/core/src/tips/store.ts +53 -0
- package/packages/footer/package.json +3 -3
- package/packages/fusion/README.md +14 -4
- package/packages/fusion/package.json +4 -3
- package/packages/fusion/src/index.ts +10 -0
- package/packages/fusion/src/picker.ts +42 -10
- package/packages/fusion/src/transcript.ts +86 -33
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/README.md +18 -30
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/input-shortcuts/src/burst.ts +88 -0
- package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
- package/packages/input-shortcuts/src/index.ts +95 -193
- package/packages/input-shortcuts/src/last-response.ts +40 -0
- package/packages/input-shortcuts/src/registers.ts +8 -23
- package/packages/input-shortcuts/src/types.ts +4 -16
- package/packages/input-shortcuts/src/undo-redo.ts +59 -47
- package/packages/kanboard/index.ts +11 -0
- package/packages/kanboard/package.json +7 -7
- package/packages/kanboard/skills/kanboard/SKILL.md +19 -10
- package/packages/kanboard/src/commands.ts +90 -16
- package/packages/kanboard/src/guard.ts +3 -3
- package/packages/kanboard/src/monitor.ts +1 -1
- package/packages/kanboard/src/settings.ts +0 -19
- package/packages/kanboard/src/shapes.ts +3 -0
- package/packages/long-horizon/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/index.ts +157 -19
- package/packages/memory/package.json +3 -3
- package/packages/memory/save-session.ts +264 -0
- package/packages/memory/settings.ts +17 -0
- package/packages/memory/tools.ts +142 -99
- package/packages/notify/package.json +2 -2
- package/packages/skill-registry/package.json +2 -2
- package/packages/subagents/README.md +2 -2
- package/packages/subagents/package.json +2 -2
- package/packages/subagents/src/cards.ts +15 -5
- package/packages/subagents/src/index.ts +21 -10
- package/packages/subagents/src/manager.ts +11 -1
- package/packages/subagents/src/ui.ts +67 -6
- package/packages/unipi/bundled.js +2429 -1553
- package/packages/updater/package.json +2 -2
- package/packages/utility/README.md +10 -1
- package/packages/utility/package.json +2 -2
- package/packages/utility/src/index.ts +6 -0
- package/packages/utility/src/render/simple.ts +97 -10
- package/packages/utility/src/render/spacing.ts +181 -0
- package/packages/utility/src/render/styled.ts +251 -0
- package/packages/watchdog/package.json +3 -3
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/README.md +3 -0
- package/packages/workflow/package.json +2 -2
- package/packages/workflow/src/permission/decide.ts +4 -1
- package/packages/workflow/src/permission/index.ts +5 -3
- package/packages/workflow/src/plan/index.ts +7 -1
- package/packages/compactor/src/compaction/jev-prune.ts +0 -114
- package/packages/input-shortcuts/src/clipboard.ts +0 -119
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,49 @@ 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.18] — 2026-10-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `kanboard` (board UI): **Summarize** in the header runs a board-wide summary with an optional prompt; a task's **Resummarize** takes an extra note. (UNI-38)
|
|
13
|
+
- `memory`: save runs in a prefix-cache-matched side session (`saveMode`: side · reminder · off); the recall reminder is injected once per session/compaction, not re-added on /reload. (UNI-32)
|
|
14
|
+
- `subagents`: a live stat line per subagent — profile, model, duration, tool calls, tokens, cost — in all render styles. (UNI-26)
|
|
15
|
+
- `core`: onboarding tips — a least-shown tip at startup plus event tips after compaction and other moments; `/unipi:tips-reset` resets show counts. (UNI-27)
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- `input-shortcuts`: ALT+S chord rework — burst-based O(1) undo/redo; A appends to the stash, Y copies the last response, K adds the input to the kanboard backlog; the registers (0-9), cut and toggle-thinking keys are gone.
|
|
19
|
+
- `kanboard`: a task needs a title **or** a description — `displayTitle` is derived from the body everywhere; `captureToBacklog` API for other extensions. (UNI-42)
|
|
20
|
+
- `permission`: full mode no longer prompts on writes outside the workspace — everything short of deny rules runs. (UNI-40)
|
|
21
|
+
- render: sidekick steps use the active style's tool/prose format in a tinted rail, grouped like the lead's tree; one blank row between transcript blocks; simple mode gains a live tool timer and keeps ask_user answers. (UNI-2, UNI-43, UNI-24, UNI-39)
|
|
22
|
+
- `workflow`: `/unipi:plan` opens with a plain-language Summary for the approver. (UNI-28)
|
|
23
|
+
- `fusion`: sidekick effort is set in the `/unipi:model` fusion row — Space toggles the lead/sidekick target, applied live. (UNI-15)
|
|
24
|
+
|
|
25
|
+
### Removed
|
|
26
|
+
- `compactor`: removed `/unipi:compact-jev` and the jev compaction method — vcc and LLM compaction remain; a saved `method: jev` falls back to vcc.
|
|
27
|
+
- `compactor`: removed the deprecated `/unipi:compact`, `/unipi:lossless-compact` and `/unipi:compact-recall`. (UNI-23)
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
- `kanboard show`: width-safe board rows — CJK-aware budgets and truncation on every line; a huge blockedReason no longer crashes pi.
|
|
31
|
+
|
|
32
|
+
## [3.0.0-alpha.17] — 2026-09-30
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- `kanboard`: **status reports** — task JSON carries `statusReport` (the full finish summary for In Review, the block reason for Blocked). The board panel shows it as a banner ("Ready for review" / "Blocked") with a markdown preview and an eye button that opens the whole report in a reader; timeline entries get Finished / Blocked / Rework tags and a Read button. (UNI-25, UNI-8)
|
|
36
|
+
- `kanboard`: **`--attach <file>`** on `finish`, `move` and `note` (repeatable): the file is stored beside the board and embedded in the comment; a path written in the comment is replaced by the embed. `attach` on a task this session started is free. (UNI-35)
|
|
37
|
+
- `kanboard` (board UI): **`@` task mentions** in the new-task description, the description editor and the comment box — recent tasks newest first, filter as you type, Enter/Tab inserts the id. (UNI-36)
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- `kanboard` (board UI): every activity entry renders as markdown; the "Runs after" picker lists the newest tasks first; the whole New task dialog accepts dropped files, with a drop overlay, and a stray drop no longer navigates the browser away. (UNI-5, UNI-36)
|
|
41
|
+
- `kanboard` skill: the finish / block comment is written as the report the user reads (lead sentence, short paragraphs or bullets, questions under **Need from you:**).
|
|
42
|
+
- `kanboard` (board UI): the Runner settings section is now Sessions; lane tooltips and the in-progress chip describe `start` / `finish` instead of the runner.
|
|
43
|
+
|
|
44
|
+
### Removed
|
|
45
|
+
- `kanboard` binary (phase 2 of the runner removal): the `claim-next`, `queue`, `unqueue` and `set-run` subcommands, the per-session queue and `UNIPI_KANBOARD_QUEUE_MAX`, `--strategy` / `--plan` on `add` and `edit`, strategy/plan in the API, task JSON and board settings (`defaultStrategy`, `defaultPlan`, `queueMax`), and the Strategy / Plan controls in the board UI. Old task files with `strategy:`, `plan:` or runner `run:` blocks still load and validate. Error and help text now describe `start` / `finish` / `move … blocked`. (UNI-37)
|
|
46
|
+
- `kanboard`: the unused `defaultStrategy`, `jevThreshold` and `defaultPlan` settings.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- `kanboard`: blank lines inside activity entries were dropped when the task file was written, gluing an agent's paragraphs and lists into one block; paragraph breaks now survive. (UNI-8)
|
|
50
|
+
- `kanboard`: a subagent or fusion sidekick shutting down released the lead's open claims back to Todo ("session ended mid-task"), because children inherit the lead's session id; shutdown release now runs in the lead only.
|
|
51
|
+
|
|
9
52
|
## [3.0.0-alpha.16] — 2026-09-30
|
|
10
53
|
|
|
11
54
|
### Added
|
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.18",
|
|
4
4
|
"description": "All-in-one extension suite for Pi coding agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -82,26 +82,26 @@
|
|
|
82
82
|
"typebox": "^1.1.38"
|
|
83
83
|
},
|
|
84
84
|
"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.
|
|
85
|
+
"@pi-unipi/ask-user": "3.0.0-alpha.18",
|
|
86
|
+
"@pi-unipi/background-tasks": "3.0.0-alpha.18",
|
|
87
|
+
"@pi-unipi/btw": "3.0.0-alpha.18",
|
|
88
|
+
"@pi-unipi/command-enchantment": "3.0.0-alpha.18",
|
|
89
|
+
"@pi-unipi/compactor": "3.0.0-alpha.18",
|
|
90
|
+
"@pi-unipi/core": "3.0.0-alpha.18",
|
|
91
|
+
"@pi-unipi/footer": "3.0.0-alpha.18",
|
|
92
|
+
"@pi-unipi/fusion": "3.0.0-alpha.18",
|
|
93
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.18",
|
|
94
|
+
"@pi-unipi/input-shortcuts": "3.0.0-alpha.18",
|
|
95
|
+
"@pi-unipi/kanboard": "3.0.0-alpha.18",
|
|
96
|
+
"@pi-unipi/mcp": "3.0.0-alpha.18",
|
|
97
|
+
"@pi-unipi/memory": "3.0.0-alpha.18",
|
|
98
|
+
"@pi-unipi/notify": "3.0.0-alpha.18",
|
|
99
|
+
"@pi-unipi/skill-registry": "3.0.0-alpha.18",
|
|
100
|
+
"@pi-unipi/subagents": "3.0.0-alpha.18",
|
|
101
|
+
"@pi-unipi/updater": "3.0.0-alpha.18",
|
|
102
|
+
"@pi-unipi/utility": "3.0.0-alpha.18",
|
|
103
|
+
"@pi-unipi/web-api": "3.0.0-alpha.18",
|
|
104
|
+
"@pi-unipi/workflow": "3.0.0-alpha.18"
|
|
105
105
|
},
|
|
106
106
|
"devDependencies": {
|
|
107
107
|
"@earendil-works/pi-agent-core": "^0.87.1",
|
|
@@ -31,7 +31,7 @@ The `ask_user` tool (off by default — enable it in `/unipi:settings` → Ask U
|
|
|
31
31
|
|
|
32
32
|
Questions you leave unanswered are sent as **skipped** — skipping never blocks. The header chips show progress: `✓` answered, a number for multi-select picks.
|
|
33
33
|
|
|
34
|
-
In the transcript the answers stay as a short tree (`● Asked user 3 questions` / `│ Planet: Mars` / `└ Last book: Dune`).
|
|
34
|
+
In the transcript the answers stay as a short tree (`● Asked user 3 questions` / `│ Planet: Mars` / `└ Last book: Dune`). In the **simple** render style the collapsed `• Asked user` row keeps the same answers as compact `header: answer` rows underneath — answering never makes them disappear.
|
|
35
35
|
|
|
36
36
|
## For the agent
|
|
37
37
|
|
|
@@ -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.18",
|
|
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.18"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.87.1",
|
|
@@ -169,9 +169,28 @@ export function registerAskUserTools(pi: ExtensionAPI): void {
|
|
|
169
169
|
renderCall: () => new Text("", 0, 0),
|
|
170
170
|
renderResult: (result, _options, theme, _context) => renderAskResult(result.details as AskDetails | undefined, theme as Theme),
|
|
171
171
|
renderShell: "self",
|
|
172
|
+
// The simple render style collapses every tool to one row — the Q→A is
|
|
173
|
+
// the whole point of ask_user, so keep compact `header: answer` rows
|
|
174
|
+
// under the `Asked user` line (utility's simpleResult hook), and hide the
|
|
175
|
+
// `· N output lines` meta (the answers are the output).
|
|
176
|
+
...( {
|
|
177
|
+
simpleMeta: () => "",
|
|
178
|
+
simpleResult: (result: { details?: AskDetails }, theme: Theme) => askRows(result.details, theme),
|
|
179
|
+
} as object),
|
|
172
180
|
});
|
|
173
181
|
}
|
|
174
182
|
|
|
183
|
+
/** Compact Q→A rows for the simple render style's collapsed view. */
|
|
184
|
+
export function askRows(details: AskDetails | undefined, theme: Theme): string[] {
|
|
185
|
+
const questions = details?.questions ?? [];
|
|
186
|
+
if (details === undefined || questions.length === 0) return [];
|
|
187
|
+
if (details.outcome === "cancelled") return [` ${theme.fg("error", "Canceled by the user")}`];
|
|
188
|
+
if (details.outcome === "clarify") return [` ${theme.fg("warning", "Not ready to answer — wants to clarify first")}`];
|
|
189
|
+
if (details.outcome === "unavailable") return [` ${theme.fg("dim", "not shown (no interactive UI or turned off)")}`];
|
|
190
|
+
if (details.outcome === "action") return [` ${theme.fg("muted", "picked an action")}`];
|
|
191
|
+
return questions.map((q, i) => ` ${theme.fg("muted", `${q.header}: ${answerSummary(q, details.answers?.[i])}`)}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
175
194
|
/** end_turn / new_session options (workflow handoffs). */
|
|
176
195
|
async function runAction(pi: ExtensionAPI, ctx: ExtensionContext, questions: AskQuestion[], result: Extract<PanelResult, { type: "action" }>, legacy: Record<string, unknown>) {
|
|
177
196
|
const opt = result.option;
|
|
@@ -36,7 +36,7 @@ export const PACKAGE_ORDER: string[] = [
|
|
|
36
36
|
];
|
|
37
37
|
|
|
38
38
|
// ─── Command Registry ────────────────────────────────────────────────
|
|
39
|
-
/** Mapping of full command name → package name (
|
|
39
|
+
/** Mapping of full command name → package name (46 verified commands) */
|
|
40
40
|
export const COMMAND_REGISTRY: Record<string, string> = {
|
|
41
41
|
// workflow (2 commands)
|
|
42
42
|
"unipi:plan": "workflow",
|
|
@@ -95,17 +95,14 @@ export const COMMAND_REGISTRY: Record<string, string> = {
|
|
|
95
95
|
|
|
96
96
|
// info (2 commands)
|
|
97
97
|
"unipi:info": "info",
|
|
98
|
+
"unipi:tips-reset": "info",
|
|
98
99
|
|
|
99
100
|
// web-api (2 commands)
|
|
100
101
|
|
|
101
|
-
// compact (
|
|
102
|
+
// compact (6 commands)
|
|
102
103
|
"unipi:compact-vcc": "compact",
|
|
103
|
-
"unipi:compact-jev": "compact",
|
|
104
104
|
"unipi:compact-by-llm": "compact",
|
|
105
|
-
"unipi:lossless-compact": "compact",
|
|
106
|
-
"unipi:compact": "compact",
|
|
107
105
|
"unipi:session-recall": "compact",
|
|
108
|
-
"unipi:compact-recall": "compact",
|
|
109
106
|
"unipi:compact-stats": "compact",
|
|
110
107
|
"unipi:compact-doctor": "compact",
|
|
111
108
|
"unipi:compact-help": "compact",
|
|
@@ -173,15 +170,12 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
|
|
|
173
170
|
"unipi:settings": "Configure all unipi modules in one panel",
|
|
174
171
|
|
|
175
172
|
"unipi:info": "Show system information",
|
|
173
|
+
"unipi:tips-reset": "Reset info-tip show counts so every tip is eligible again",
|
|
176
174
|
|
|
177
175
|
|
|
178
176
|
"unipi:compact-vcc": "Lossless compaction now — no model call (keep:N)",
|
|
179
|
-
"unipi:compact-jev": "Lossless compaction, pruned by jev of what is no longer in force",
|
|
180
177
|
"unipi:compact-by-llm": "Compact now with a model-written summary",
|
|
181
|
-
"unipi:lossless-compact": "(DEPRECATED) Use /unipi:compact-vcc instead",
|
|
182
|
-
"unipi:compact": "(DEPRECATED) Use /unipi:compact-vcc instead",
|
|
183
178
|
"unipi:session-recall": "Search session history, including compacted-away messages",
|
|
184
|
-
"unipi:compact-recall": "(DEPRECATED) Use /unipi:session-recall instead",
|
|
185
179
|
"unipi:compact-stats": "Show this session's compaction savings",
|
|
186
180
|
"unipi:compact-doctor": "Check compaction settings",
|
|
187
181
|
"unipi:compact-help": "Show compactor command help",
|
|
@@ -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.18",
|
|
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.18",
|
|
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.18",
|
|
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.18"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|
|
@@ -7,11 +7,8 @@ Compaction for Pi that keeps work going. When the context fills up, the compacto
|
|
|
7
7
|
| Method | What it does | Cost |
|
|
8
8
|
|---|---|---|
|
|
9
9
|
| **Lossless** (default) | Builds a structured summary without a model call, in milliseconds. | Free |
|
|
10
|
-
| **Lossless + jev** | The lossless summary, then jev (the Decision model, TypeSafe jev) drops items no longer in force: completed one-off requests, reversed decisions, fixed errors, an outdated report. Their room goes to other items. | ~0.5–2 s, fractions of a cent |
|
|
11
10
|
| **Model summary** | Pi's own model-written summary, with the active-work block added on top. | One model call |
|
|
12
11
|
|
|
13
|
-
jev only drops what it is confident about: decisions (including your answers to agent questions) need ≥92% certainty, one-off requests ≥80%, errors ≥75%. Your first and latest requests are never dropped. With no jev key or on timeout it falls back to the plain lossless summary. It uses the shared Decision Model (`/unipi:settings → Decision Model`); `Compactor → jev pruning — Decision model` can switch it to a custom one for the compactor only.
|
|
14
|
-
|
|
15
12
|
### The lossless summary
|
|
16
13
|
|
|
17
14
|
It is rebuilt from the **full** session history on every compaction, never merged onto the previous summary, so it cannot drift or grow over time. Sections, in the order a resuming model needs them:
|
|
@@ -30,7 +27,7 @@ Each section has its own share of a hard budget (auto: 1.5k–4k tokens, scaling
|
|
|
30
27
|
|
|
31
28
|
## In the TUI
|
|
32
29
|
|
|
33
|
-
- **Compaction card:** one line under Pi's own `[compaction]` block, e.g. `▌ Compacted 7.9k → 2.2k tokens · lossless
|
|
30
|
+
- **Compaction card:** one line under Pi's own `[compaction]` block, e.g. `▌ Compacted 7.9k → 2.2k tokens · lossless · at 3%`. ctrl+o expands it: method, trigger, what was kept verbatim, summary sections. It is a custom entry: shown, never sent to the model.
|
|
34
31
|
- **Footer strip** (under the input): `5 compactions · 47k→15k · just now`, hidden until the first compaction. With the glance frame off, the `compactions` segment shows the same (`cmp 5× 47k→15k · 1m`).
|
|
35
32
|
- **Info screen** (`/unipi:info` → Compactor): method and trigger settings, compactions by method, tokens before → after, the last compaction.
|
|
36
33
|
|
|
@@ -45,7 +42,7 @@ Each section has its own share of a hard budget (auto: 1.5k–4k tokens, scaling
|
|
|
45
42
|
|
|
46
43
|
| Setting | Default | |
|
|
47
44
|
|---|---|---|
|
|
48
|
-
| Method | lossless | lossless ·
|
|
45
|
+
| Method | lossless | lossless · model summary |
|
|
49
46
|
| Pi's /compact | same as Method | What Pi's built-in `/compact` does |
|
|
50
47
|
| When | Pi's context limit | Or: at a percentage |
|
|
51
48
|
| Percentage | 80 | Used when When = at a percentage |
|
|
@@ -60,14 +57,13 @@ Configs from before the rework are translated automatically: `overrideDefaultCom
|
|
|
60
57
|
| Command | |
|
|
61
58
|
|---|---|
|
|
62
59
|
| `/unipi:compact-vcc [keep:N]` | Lossless compaction now; `keep:N` keeps the last N user turns |
|
|
63
|
-
| `/unipi:compact-jev [keep:N]` | Lossless compaction pruned by jev |
|
|
64
60
|
| `/unipi:compact-by-llm [focus]` | Model-summary compaction now; optional text focuses the summary |
|
|
65
61
|
| `/unipi:session-recall <query>` | Search the full session history (`scope:all`, `page:N`) |
|
|
66
62
|
| `/unipi:compact-stats` | This session's compactions and savings |
|
|
67
63
|
| `/unipi:compact-doctor` | Check settings, Pi's compaction switch, leftovers |
|
|
68
64
|
| `/unipi:compact-help` | Command summary |
|
|
69
65
|
|
|
70
|
-
|
|
66
|
+
Removed: `/unipi:compact` and `/unipi:lossless-compact` (use `/unipi:compact-vcc`), `/unipi:compact-recall` (use `/unipi:session-recall`), `/unipi:compact-jev` and the jev method (a saved `method: "jev"` behaves as lossless).
|
|
71
67
|
|
|
72
68
|
## Tools
|
|
73
69
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.18",
|
|
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.18",
|
|
37
|
+
"@pi-unipi/info-screen": "3.0.0-alpha.18",
|
|
38
38
|
"@earendil-works/pi-agent-core": "^0.87.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Pi draws its own `[compaction] Compacted from N tokens` block (expands to
|
|
6
6
|
* the summary); this card adds what Pi does not say: the size after, the
|
|
7
|
-
* method, what triggered it, what was kept
|
|
7
|
+
* method, what triggered it, and what was kept.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { truncateToWidth, type Component } from "@earendil-works/pi-tui";
|
|
@@ -18,7 +18,7 @@ export type CompactionTrigger = "manual" | "threshold" | "overflow" | "percent";
|
|
|
18
18
|
export interface CompactionCardData {
|
|
19
19
|
method: CompactionMethod;
|
|
20
20
|
trigger: CompactionTrigger;
|
|
21
|
-
/** The command that ran it (manual only), e.g. "unipi:compact-
|
|
21
|
+
/** The command that ran it (manual only), e.g. "unipi:compact-vcc". */
|
|
22
22
|
command?: string;
|
|
23
23
|
tokensBefore: number;
|
|
24
24
|
tokensAfter?: number;
|
|
@@ -27,14 +27,12 @@ export interface CompactionCardData {
|
|
|
27
27
|
totalTurns?: number;
|
|
28
28
|
keptTokens?: number;
|
|
29
29
|
sections?: string[];
|
|
30
|
-
jev?: { asked: number; dropped: number; items: string[]; unavailable?: boolean };
|
|
31
30
|
percent?: number;
|
|
32
31
|
threshold?: number;
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
const METHOD_NAME: Record<CompactionMethod, string> = {
|
|
36
35
|
vcc: "lossless",
|
|
37
|
-
jev: "lossless + jev",
|
|
38
36
|
llm: "model summary",
|
|
39
37
|
};
|
|
40
38
|
|
|
@@ -58,8 +56,6 @@ function triggerLong(d: CompactionCardData): string {
|
|
|
58
56
|
export function cardHeadline(d: CompactionCardData): string {
|
|
59
57
|
const size = d.tokensAfter != null ? `${formatTokens(d.tokensBefore)} → ${formatTokens(d.tokensAfter)}` : `from ${formatTokens(d.tokensBefore)}`;
|
|
60
58
|
const parts = [`Compacted ${size} tokens`, METHOD_NAME[d.method]];
|
|
61
|
-
if (d.jev?.unavailable) parts.push("jev unavailable");
|
|
62
|
-
else if (d.jev && d.jev.dropped > 0) parts.push(`${d.jev.dropped} stale dropped`);
|
|
63
59
|
parts.push(triggerShort(d));
|
|
64
60
|
return parts.join(" · ");
|
|
65
61
|
}
|
|
@@ -67,7 +63,7 @@ export function cardHeadline(d: CompactionCardData): string {
|
|
|
67
63
|
/** Label/value rows for the expanded card. */
|
|
68
64
|
export function cardDetails(d: CompactionCardData): Array<[string, string]> {
|
|
69
65
|
const rows: Array<[string, string]> = [
|
|
70
|
-
["Method",
|
|
66
|
+
["Method", METHOD_NAME[d.method]],
|
|
71
67
|
["Trigger", triggerLong(d)],
|
|
72
68
|
];
|
|
73
69
|
if (d.keptTurns != null && d.totalTurns != null) {
|
|
@@ -76,7 +72,6 @@ export function cardDetails(d: CompactionCardData): Array<[string, string]> {
|
|
|
76
72
|
if (d.summaryTokens != null) {
|
|
77
73
|
rows.push(["Summary", `~${formatTokens(d.summaryTokens)} tokens${d.sections?.length ? ` · ${d.sections.join(", ")}` : ""}`]);
|
|
78
74
|
}
|
|
79
|
-
for (const [i, item] of (d.jev?.items ?? []).slice(0, 6).entries()) rows.push([i === 0 ? "Dropped" : "", item]);
|
|
80
75
|
rows.push(["Recall", "everything before stays searchable: /unipi:session-recall"]);
|
|
81
76
|
return rows;
|
|
82
77
|
}
|
|
@@ -5,9 +5,6 @@
|
|
|
5
5
|
* /unipi:compact-by-llm [focus] model-written summary (Pi's summarizer)
|
|
6
6
|
* /unipi:session-recall <query> search the full session history
|
|
7
7
|
* /unipi:compact-stats | -doctor | -help
|
|
8
|
-
*
|
|
9
|
-
* Deprecated: /unipi:compact, /unipi:lossless-compact (→ compact-vcc),
|
|
10
|
-
* /unipi:compact-recall (→ session-recall).
|
|
11
8
|
*/
|
|
12
9
|
|
|
13
10
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
@@ -44,28 +41,14 @@ export function registerCommands(pi: ExtensionAPI): void {
|
|
|
44
41
|
description: "Lossless compaction now — instant structured summary, no model call (keep:N keeps N recent turns)",
|
|
45
42
|
handler: async (args: string, ctx: ExtensionCommandContext) => runCompaction(ctx, "vcc", args, "unipi:compact-vcc"),
|
|
46
43
|
});
|
|
47
|
-
pi.registerCommand("unipi:compact-jev", {
|
|
48
|
-
description: "Lossless compaction, then jev drops items no longer in force (done requests, reversed decisions, fixed errors)",
|
|
49
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => runCompaction(ctx, "jev", args, "unipi:compact-jev"),
|
|
50
|
-
});
|
|
51
44
|
pi.registerCommand("unipi:compact-by-llm", {
|
|
52
45
|
description: "Compact now with a model-written summary (optional text focuses the summary)",
|
|
53
46
|
handler: async (args: string, ctx: ExtensionCommandContext) => runCompaction(ctx, "llm", args, "unipi:compact-by-llm"),
|
|
54
47
|
});
|
|
55
|
-
const deprecated = (name: string) => ({
|
|
56
|
-
description: "(DEPRECATED) Use /unipi:compact-vcc",
|
|
57
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => {
|
|
58
|
-
ctx.ui.notify(`/${name} is deprecated — use /unipi:compact-vcc (or /unipi:compact-by-llm).`, "warning");
|
|
59
|
-
runCompaction(ctx, "vcc", args, "unipi:compact-vcc");
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
pi.registerCommand("unipi:compact", deprecated("unipi:compact"));
|
|
63
|
-
pi.registerCommand("unipi:lossless-compact", deprecated("unipi:lossless-compact"));
|
|
64
|
-
|
|
65
48
|
// pi-vcc parity: results are shown AND fed to the agent (triggerTurn).
|
|
66
|
-
const sessionRecallHandler = async (args: string, ctx: ExtensionCommandContext
|
|
49
|
+
const sessionRecallHandler = async (args: string, ctx: ExtensionCommandContext) => {
|
|
67
50
|
const raw = args.trim();
|
|
68
|
-
const usage =
|
|
51
|
+
const usage = "Usage: /unipi:session-recall <query>";
|
|
69
52
|
if (!raw) {
|
|
70
53
|
ctx.ui.notify(usage, "warning");
|
|
71
54
|
return;
|
|
@@ -90,11 +73,6 @@ export function registerCommands(pi: ExtensionAPI): void {
|
|
|
90
73
|
description: "Recall earlier parts of this session. Plain keywords work best; add scope:all to reach edited or retried turns.",
|
|
91
74
|
handler: sessionRecallHandler,
|
|
92
75
|
});
|
|
93
|
-
pi.registerCommand("unipi:compact-recall", {
|
|
94
|
-
description: "(DEPRECATED) Search session history — use /unipi:session-recall instead",
|
|
95
|
-
handler: async (args: string, ctx: ExtensionCommandContext) => sessionRecallHandler(args, ctx, "/unipi:compact-recall"),
|
|
96
|
-
});
|
|
97
|
-
|
|
98
76
|
pi.registerCommand("unipi:compact-stats", {
|
|
99
77
|
description: "Show this session's compaction savings",
|
|
100
78
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
@@ -130,7 +108,6 @@ export function registerCommands(pi: ExtensionAPI): void {
|
|
|
130
108
|
[
|
|
131
109
|
"Compactor",
|
|
132
110
|
" /unipi:compact-vcc [keep:N] lossless compaction now (no model call)",
|
|
133
|
-
" /unipi:compact-jev [keep:N] lossless, then jev prunes what is no longer in force",
|
|
134
111
|
" /unipi:compact-by-llm [focus] compact now with a model-written summary",
|
|
135
112
|
" /unipi:session-recall <query> search everything in this session, including compacted parts",
|
|
136
113
|
" /unipi:compact-stats this session's savings",
|
|
@@ -14,15 +14,14 @@
|
|
|
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,
|
|
17
|
+
import { collectCompactionContext, emitEvent, formatTokens, 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";
|
|
21
21
|
import { parseCompactionInstructions } from "./compact-args.js";
|
|
22
22
|
import { calibrateCharsPerToken, estimateMessageContentChars, estimateTokensFromChars } from "./token-estimate.js";
|
|
23
23
|
import { collectSummarySource, ORIGIN_ENTRY_TYPE, originKey } from "./source.js";
|
|
24
|
-
import { autoBudgetTokens, buildLosslessSummary,
|
|
25
|
-
import { pruneWithJev } from "./jev-prune.js";
|
|
24
|
+
import { autoBudgetTokens, buildLosslessSummary, type LosslessSummaryInput } from "./summarize.js";
|
|
26
25
|
import { CARD_TYPE, type CompactionCardData, type CompactionTrigger } from "../card.js";
|
|
27
26
|
import {
|
|
28
27
|
createAutoCompactionState,
|
|
@@ -77,7 +76,6 @@ export function buildCardData(opts: {
|
|
|
77
76
|
threshold?: number;
|
|
78
77
|
}): CompactionCardData {
|
|
79
78
|
const { stats, details } = opts;
|
|
80
|
-
const jev = details?.jev as { asked?: number; dropped?: number; droppedItems?: string[]; jev?: string } | undefined;
|
|
81
79
|
const summaryChars = typeof opts.summary === "string" ? opts.summary.length : 0;
|
|
82
80
|
return {
|
|
83
81
|
method: opts.method,
|
|
@@ -88,9 +86,6 @@ export function buildCardData(opts: {
|
|
|
88
86
|
...(summaryChars ? { summaryTokens: Math.ceil(summaryChars / SUMMARY_CHARS_PER_TOKEN) } : {}),
|
|
89
87
|
...(stats ? { keptTurns: stats.keptUserTurns, totalTurns: stats.totalUserTurns, keptTokens: stats.keptTokensEst } : {}),
|
|
90
88
|
...(Array.isArray(details?.sections) ? { sections: details!.sections as string[] } : {}),
|
|
91
|
-
...(opts.method === "jev" && jev
|
|
92
|
-
? { jev: { asked: jev.asked ?? 0, dropped: jev.dropped ?? 0, items: (jev.droppedItems ?? []).map((i) => i.replace(/^\w+: /, "")), ...(jev.jev === "unavailable" ? { unavailable: true } : {}) } }
|
|
93
|
-
: {}),
|
|
94
89
|
...(opts.percent != null ? { percent: opts.percent } : {}),
|
|
95
90
|
...(opts.threshold != null ? { threshold: opts.threshold } : {}),
|
|
96
91
|
};
|
|
@@ -139,10 +134,6 @@ export interface LosslessPlanInput {
|
|
|
139
134
|
config: CompactorConfig;
|
|
140
135
|
cwd?: string;
|
|
141
136
|
reason?: string;
|
|
142
|
-
/** Item keys to leave out of the summary (jev pruning). */
|
|
143
|
-
drop?: ReadonlySet<string>;
|
|
144
|
-
/** Receives the summary input before it is built (jev pruning reads it). */
|
|
145
|
-
onSummaryInput?: (input: LosslessSummaryInput) => void;
|
|
146
137
|
}
|
|
147
138
|
|
|
148
139
|
export type LosslessPlan =
|
|
@@ -203,9 +194,7 @@ export function planLosslessCompaction(input: LosslessPlanInput): LosslessPlan {
|
|
|
203
194
|
cwd: input.cwd,
|
|
204
195
|
fileOps: input.fileOps,
|
|
205
196
|
sections: config.sections,
|
|
206
|
-
drop: input.drop,
|
|
207
197
|
};
|
|
208
|
-
input.onSummaryInput?.(summaryInput);
|
|
209
198
|
const summary = buildLosslessSummary(summaryInput);
|
|
210
199
|
|
|
211
200
|
const tokensAfter = keptTokens + estimateTokensFromChars(summary.text.length, SUMMARY_CHARS_PER_TOKEN);
|
|
@@ -246,38 +235,6 @@ export function planLosslessCompaction(input: LosslessPlanInput): LosslessPlan {
|
|
|
246
235
|
};
|
|
247
236
|
}
|
|
248
237
|
|
|
249
|
-
/**
|
|
250
|
-
* Lossless compaction pruned by jev: build once to collect the candidates,
|
|
251
|
-
* ask jev which are no longer in force, rebuild without them (their room goes
|
|
252
|
-
* to other items). Falls back to the plain lossless plan when jev is silent.
|
|
253
|
-
*/
|
|
254
|
-
export async function planJevCompaction(
|
|
255
|
-
input: LosslessPlanInput,
|
|
256
|
-
prune: (candidates: SummaryCandidate[], state: string) => Promise<{ drop: Set<string>; asked: number; answered: number }>,
|
|
257
|
-
): Promise<LosslessPlan> {
|
|
258
|
-
let captured: LosslessSummaryInput | null = null;
|
|
259
|
-
const first = planLosslessCompaction({ ...input, onSummaryInput: (si) => (captured = si) });
|
|
260
|
-
if (!first.ok || !captured) return first;
|
|
261
|
-
const candidates = summaryCandidates(captured);
|
|
262
|
-
if (candidates.length === 0) return withMethod(first, "jev", { asked: 0, dropped: 0 });
|
|
263
|
-
const result = await prune(candidates, pruneState(captured));
|
|
264
|
-
if (result.answered === 0) return withMethod(first, "jev", { asked: result.asked, dropped: 0, jev: "unavailable" });
|
|
265
|
-
const plan = result.drop.size > 0 ? planLosslessCompaction({ ...input, drop: result.drop }) : first;
|
|
266
|
-
return withMethod(plan, "jev", {
|
|
267
|
-
asked: result.asked,
|
|
268
|
-
dropped: result.drop.size,
|
|
269
|
-
droppedItems: candidates.filter((c) => result.drop.has(c.key)).map((c) => `${c.kind}: ${(c.earlier ?? c.text).slice(0, 120)}`),
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function withMethod(plan: LosslessPlan, method: CompactionMethod, extra: Record<string, unknown>): LosslessPlan {
|
|
274
|
-
return plan.ok ? { ...plan, details: { ...plan.details, method, jev: extra } } : plan;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/** jev pruning with the Decision Model (shared, or the compactor's custom override). */
|
|
278
|
-
export const jevPruner = (cwd: string, signal?: AbortSignal) => (candidates: SummaryCandidate[], state: string) =>
|
|
279
|
-
pruneWithJev(candidates, state, resolveDecisionModel(cwd, "compactor"), { signal });
|
|
280
|
-
|
|
281
238
|
// ── model summary ────────────────────────────────────────
|
|
282
239
|
|
|
283
240
|
async function requestAuth(ctx: ExtensionContext) {
|
|
@@ -384,7 +341,7 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
|
|
|
384
341
|
const parsed = parseCompactionInstructions(event.customInstructions);
|
|
385
342
|
const method: CompactionMethod = pendingMethod
|
|
386
343
|
?? (parsed.isCompactor
|
|
387
|
-
?
|
|
344
|
+
? "vcc"
|
|
388
345
|
: reason === "manual" && config.piCompact !== "follow"
|
|
389
346
|
? config.piCompact
|
|
390
347
|
: config.method);
|
|
@@ -427,9 +384,7 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
|
|
|
427
384
|
reason,
|
|
428
385
|
contextWindow: ctx?.model?.contextWindow,
|
|
429
386
|
};
|
|
430
|
-
const plan =
|
|
431
|
-
? await planJevCompaction(planInput, jevPruner(cwd, event.signal))
|
|
432
|
-
: planLosslessCompaction(planInput);
|
|
387
|
+
const plan = planLosslessCompaction(planInput);
|
|
433
388
|
|
|
434
389
|
if (!plan.ok) {
|
|
435
390
|
// Overflow must still recover: let Pi's own summarizer handle it.
|
|
@@ -460,7 +415,7 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
|
|
|
460
415
|
pi.on("session_compact", (event: SessionCompactEvent, ctx) => {
|
|
461
416
|
const details = (event.compactionEntry as { details?: { compactor?: string; method?: CompactionMethod } } | undefined)?.details;
|
|
462
417
|
const ours = details?.compactor === COMPACTOR_ID;
|
|
463
|
-
const method: CompactionMethod = !ours ? "llm" : details?.method === "llm"
|
|
418
|
+
const method: CompactionMethod = !ours ? "llm" : details?.method === "llm" ? "llm" : "vcc";
|
|
464
419
|
afterCompaction(ours ? lastStats : null, method);
|
|
465
420
|
|
|
466
421
|
const wasCommand = commandCompaction;
|
|
@@ -556,7 +511,7 @@ type BoundaryDraft = {
|
|
|
556
511
|
|
|
557
512
|
async function boundaryLosslessDraft(branch: any[], config: CompactorConfig, cwd: string, tokensBefore?: number, contextWindow?: number): Promise<BoundaryDraft | null> {
|
|
558
513
|
const input: LosslessPlanInput = { branchEntries: branch, tokensBefore, config, cwd, reason: "percent", contextWindow };
|
|
559
|
-
const plan =
|
|
514
|
+
const plan = planLosslessCompaction(input);
|
|
560
515
|
if (!plan.ok) return null;
|
|
561
516
|
lastStats = plan.stats;
|
|
562
517
|
return {
|