@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.
Files changed (84) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/package.json +21 -21
  3. package/packages/ask-user/README.md +1 -1
  4. package/packages/ask-user/package.json +2 -2
  5. package/packages/ask-user/tools.ts +19 -0
  6. package/packages/autocomplete/package.json +1 -1
  7. package/packages/autocomplete/src/constants.ts +4 -10
  8. package/packages/background-tasks/package.json +2 -2
  9. package/packages/btw/package.json +2 -2
  10. package/packages/compactor/README.md +3 -7
  11. package/packages/compactor/package.json +3 -3
  12. package/packages/compactor/src/card.ts +3 -8
  13. package/packages/compactor/src/commands/index.ts +2 -25
  14. package/packages/compactor/src/compaction/hooks.ts +6 -51
  15. package/packages/compactor/src/compaction/summarize.ts +19 -137
  16. package/packages/compactor/src/config/manager.ts +8 -5
  17. package/packages/compactor/src/info-screen.ts +1 -1
  18. package/packages/compactor/src/stats.ts +3 -3
  19. package/packages/compactor/src/tools/ctx-doctor.ts +1 -1
  20. package/packages/compactor/src/types.ts +2 -3
  21. package/packages/core/constants.ts +0 -6
  22. package/packages/core/global.d.ts +10 -0
  23. package/packages/core/index.ts +2 -0
  24. package/packages/core/package.json +1 -1
  25. package/packages/core/src/child-agent/runtime.ts +14 -1
  26. package/packages/core/src/tips/index.ts +257 -0
  27. package/packages/core/src/tips/lines.ts +233 -0
  28. package/packages/core/src/tips/store.ts +53 -0
  29. package/packages/footer/package.json +3 -3
  30. package/packages/fusion/README.md +14 -4
  31. package/packages/fusion/package.json +4 -3
  32. package/packages/fusion/src/index.ts +10 -0
  33. package/packages/fusion/src/picker.ts +42 -10
  34. package/packages/fusion/src/transcript.ts +86 -33
  35. package/packages/info-screen/package.json +2 -2
  36. package/packages/input-shortcuts/README.md +18 -30
  37. package/packages/input-shortcuts/package.json +2 -2
  38. package/packages/input-shortcuts/src/burst.ts +88 -0
  39. package/packages/input-shortcuts/src/chord-overlay.ts +20 -70
  40. package/packages/input-shortcuts/src/index.ts +95 -193
  41. package/packages/input-shortcuts/src/last-response.ts +40 -0
  42. package/packages/input-shortcuts/src/registers.ts +8 -23
  43. package/packages/input-shortcuts/src/types.ts +4 -16
  44. package/packages/input-shortcuts/src/undo-redo.ts +59 -47
  45. package/packages/kanboard/index.ts +11 -0
  46. package/packages/kanboard/package.json +7 -7
  47. package/packages/kanboard/skills/kanboard/SKILL.md +19 -10
  48. package/packages/kanboard/src/commands.ts +90 -16
  49. package/packages/kanboard/src/guard.ts +3 -3
  50. package/packages/kanboard/src/monitor.ts +1 -1
  51. package/packages/kanboard/src/settings.ts +0 -19
  52. package/packages/kanboard/src/shapes.ts +3 -0
  53. package/packages/long-horizon/package.json +2 -2
  54. package/packages/mcp/package.json +2 -2
  55. package/packages/memory/index.ts +157 -19
  56. package/packages/memory/package.json +3 -3
  57. package/packages/memory/save-session.ts +264 -0
  58. package/packages/memory/settings.ts +17 -0
  59. package/packages/memory/tools.ts +142 -99
  60. package/packages/notify/package.json +2 -2
  61. package/packages/skill-registry/package.json +2 -2
  62. package/packages/subagents/README.md +2 -2
  63. package/packages/subagents/package.json +2 -2
  64. package/packages/subagents/src/cards.ts +15 -5
  65. package/packages/subagents/src/index.ts +21 -10
  66. package/packages/subagents/src/manager.ts +11 -1
  67. package/packages/subagents/src/ui.ts +67 -6
  68. package/packages/unipi/bundled.js +2429 -1553
  69. package/packages/updater/package.json +2 -2
  70. package/packages/utility/README.md +10 -1
  71. package/packages/utility/package.json +2 -2
  72. package/packages/utility/src/index.ts +6 -0
  73. package/packages/utility/src/render/simple.ts +97 -10
  74. package/packages/utility/src/render/spacing.ts +181 -0
  75. package/packages/utility/src/render/styled.ts +251 -0
  76. package/packages/watchdog/package.json +3 -3
  77. package/packages/web-api/package.json +2 -2
  78. package/packages/workflow/README.md +3 -0
  79. package/packages/workflow/package.json +2 -2
  80. package/packages/workflow/src/permission/decide.ts +4 -1
  81. package/packages/workflow/src/permission/index.ts +5 -3
  82. package/packages/workflow/src/plan/index.ts +7 -1
  83. package/packages/compactor/src/compaction/jev-prune.ts +0 -114
  84. 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.16",
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.16",
86
- "@pi-unipi/background-tasks": "3.0.0-alpha.16",
87
- "@pi-unipi/btw": "3.0.0-alpha.16",
88
- "@pi-unipi/command-enchantment": "3.0.0-alpha.16",
89
- "@pi-unipi/compactor": "3.0.0-alpha.16",
90
- "@pi-unipi/core": "3.0.0-alpha.16",
91
- "@pi-unipi/footer": "3.0.0-alpha.16",
92
- "@pi-unipi/fusion": "3.0.0-alpha.16",
93
- "@pi-unipi/info-screen": "3.0.0-alpha.16",
94
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.16",
95
- "@pi-unipi/kanboard": "3.0.0-alpha.16",
96
- "@pi-unipi/mcp": "3.0.0-alpha.16",
97
- "@pi-unipi/memory": "3.0.0-alpha.16",
98
- "@pi-unipi/notify": "3.0.0-alpha.16",
99
- "@pi-unipi/skill-registry": "3.0.0-alpha.16",
100
- "@pi-unipi/subagents": "3.0.0-alpha.16",
101
- "@pi-unipi/updater": "3.0.0-alpha.16",
102
- "@pi-unipi/utility": "3.0.0-alpha.16",
103
- "@pi-unipi/web-api": "3.0.0-alpha.16",
104
- "@pi-unipi/workflow": "3.0.0-alpha.16"
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.16",
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.16"
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;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.16",
3
+ "version": "3.0.0-alpha.18",
4
4
  "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -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 (48 verified commands) */
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 (9 commands)
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.16",
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.16",
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.16",
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.16"
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 + jev · 1 stale dropped · at 3%`. ctrl+o expands it: method, trigger, what was kept verbatim, summary sections, what jev dropped. It is a custom entry: shown, never sent to the model.
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 · lossless + jev · model summary |
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
- Deprecated: `/unipi:compact` and `/unipi:lossless-compact` (use `/unipi:compact-vcc`), `/unipi:compact-recall` (use `/unipi:session-recall`).
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.16",
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.16",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.16",
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 and what jev dropped.
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-jev". */
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", d.method === "jev" && d.jev ? `${METHOD_NAME.jev} (asked ${d.jev.asked}, dropped ${d.jev.dropped})` : METHOD_NAME[d.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, commandName = "/unipi:session-recall") => {
49
+ const sessionRecallHandler = async (args: string, ctx: ExtensionCommandContext) => {
67
50
  const raw = args.trim();
68
- const usage = `Usage: ${commandName} <query>${commandName === "/unipi:compact-recall" ? " (deprecated; use /unipi:session-recall)" : ""}`;
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, resolveDecisionModel, UNIPI_EVENTS } from "@pi-unipi/core";
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, pruneState, summaryCandidates, type LosslessSummaryInput, type SummaryCandidate } from "./summarize.js";
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
- ? (config.method === "jev" ? "jev" : "vcc")
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 = method === "jev"
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" || details?.method === "jev" ? details.method : "vcc";
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 = config.method === "jev" ? await planJevCompaction(input, jevPruner(cwd)) : planLosslessCompaction(input);
514
+ const plan = planLosslessCompaction(input);
560
515
  if (!plan.ok) return null;
561
516
  lastStats = plan.stats;
562
517
  return {