@pi-unipi/unipi 3.0.0-alpha.22 → 3.0.0-alpha.23

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 (61) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +12 -13
  3. package/package.json +22 -22
  4. package/packages/ask-user/config.ts +12 -12
  5. package/packages/ask-user/package.json +2 -2
  6. package/packages/autocomplete/package.json +1 -1
  7. package/packages/autocomplete/src/settings.ts +1 -1
  8. package/packages/background-tasks/package.json +2 -2
  9. package/packages/background-tasks/src/config.ts +5 -18
  10. package/packages/btw/package.json +2 -2
  11. package/packages/compactor/package.json +3 -3
  12. package/packages/compactor/src/compaction/hooks.ts +3 -3
  13. package/packages/compactor/src/config/manager.ts +85 -30
  14. package/packages/compactor/src/config/schema.ts +20 -5
  15. package/packages/compactor/src/types.ts +2 -6
  16. package/packages/core/package.json +1 -1
  17. package/packages/core/src/hints/index.ts +3 -3
  18. package/packages/core/src/jev/settings.ts +11 -10
  19. package/packages/core/src/settings/hub.ts +133 -1
  20. package/packages/core/src/settings/schema.ts +24 -5
  21. package/packages/core/src/tui/hub-kit.ts +10 -1
  22. package/packages/footer/package.json +3 -3
  23. package/packages/footer/src/config.ts +39 -19
  24. package/packages/fusion/package.json +4 -4
  25. package/packages/fusion/src/preset.ts +5 -5
  26. package/packages/info-screen/config.ts +11 -6
  27. package/packages/info-screen/package.json +2 -2
  28. package/packages/input-shortcuts/package.json +2 -2
  29. package/packages/input-shortcuts/src/settings.ts +4 -0
  30. package/packages/kanboard/package.json +7 -7
  31. package/packages/kanboard/src/settings.ts +36 -30
  32. package/packages/long-horizon/package.json +2 -2
  33. package/packages/long-horizon/src/settings.ts +16 -10
  34. package/packages/mcp/package.json +2 -2
  35. package/packages/mcp/src/index.ts +1 -42
  36. package/packages/mcp/src/settings.ts +52 -0
  37. package/packages/memory/index.ts +2 -1
  38. package/packages/memory/package.json +3 -3
  39. package/packages/memory/settings.ts +103 -26
  40. package/packages/notify/package.json +2 -2
  41. package/packages/notify/settings.ts +58 -26
  42. package/packages/skill-registry/package.json +2 -2
  43. package/packages/skill-registry/src/settings.ts +9 -9
  44. package/packages/subagents/package.json +3 -3
  45. package/packages/subagents/src/index.ts +2 -1
  46. package/packages/unipi/bundled.js +732 -402
  47. package/packages/updater/package.json +2 -2
  48. package/packages/updater/src/settings.ts +8 -2
  49. package/packages/utility/package.json +2 -2
  50. package/packages/utility/src/image/settings.ts +74 -22
  51. package/packages/utility/src/settings.ts +16 -16
  52. package/packages/watchdog/package.json +3 -3
  53. package/packages/watchdog/src/config.ts +14 -13
  54. package/packages/web-api/package.json +2 -2
  55. package/packages/web-api/src/index.ts +17 -1
  56. package/packages/web-api/src/settings.ts +27 -14
  57. package/packages/web-api/src/tools.ts +50 -40
  58. package/packages/workflow/package.json +2 -2
  59. package/packages/workflow/src/permission/decide.ts +2 -3
  60. package/packages/workflow/src/permission/index.ts +0 -1
  61. package/packages/workflow/src/permission/settings.ts +10 -19
package/CHANGELOG.md CHANGED
@@ -6,6 +6,27 @@ 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.23] — 2026-10-04
10
+
11
+ ### Added
12
+ - `core`: the `/unipi:settings` hub explains the selected setting in a fixed 4-row info panel — wrapped description, the highlighted (or stored) enum option's meaning, and a meta line (`default … · range … · scope`). Enum and multiselect options carry their own descriptions, shown while the option list is open; number fields take `unit` (`6000 ms`, `80%`); option descriptions join the search haystack. (UNI-50)
13
+ - guard test (`tests/settings-metadata.test.ts`): every settings field and option must be described (labels ≤ 24 chars, sentences end with a period, units live in `unit`, option fragments don't) across all 23 namespaces.
14
+
15
+ ### Changed
16
+ - `web-api`: web tools that cannot work are no longer exposed to the agent — `web_search`/`web_llm_summarize` drop out of the active tools when no enabled provider can serve them (re-checked every prompt), `multi_web_content_read` always stays (smart-fetch is local). The phantom `llm-summarize` provider is gone and `web_llm_summarize` lost its useless `source` parameter. Tool failures now throw instead of returning ignored `isError` results. (UNI-72)
17
+ - settings: shorter labels (explanations moved into descriptions), and pruned knobs —
18
+ - `background-tasks`: removed `defaultTimeoutSeconds` (nothing read it).
19
+ - `compactor`: the nine `sections.*` booleans became one **Summary sections** multiselect (stored booleans still work); `cooldownMs`/`repeatMinGrowthTokens` fixed at their defaults; `debug` moved to `UNIPI_COMPACTOR_DEBUG=1`.
20
+ - `memory`: `recallAtStart` + `wakeUp` merged into one **Recall at start** enum (`off`/`reminder`/`wake-up`); stored booleans migrate per scope.
21
+ - `permission`: removed `jevJudge` — auto mode always consults jev.
22
+ - `footer`: classic-only options (`preset`, `separator`, `zoneSeparator`, `full labels`, `color mode`) moved under an advanced **Classic footer** section; preset list now offers the real presets (`compact`, `full`, `ascii` instead of the dead `dense`/`devops`/`zen`).
23
+ - `image`: custom endpoint fields moved into per-tool **Custom endpoint…** pages (summary shows `off` or the host).
24
+ - `web-api`: Smart fetch section is advanced; `os`/`batchConcurrency` removed from the schema (engine defaults remain).
25
+ - `kanboard`: reorganized into Agent / Housekeeping / **Board server** (advanced, plus open/stop/rotate-token actions); dead `blocking` setting deleted.
26
+
27
+ ### Docs
28
+ - README: dropped the "Why we have it" column from the Proof table.
29
+
9
30
  ## [3.0.0-alpha.22] — 2026-10-03
10
31
 
11
32
  ### Changed
package/README.md CHANGED
@@ -68,19 +68,18 @@ runs commands. UniPi adds the parts that a long session needs.
68
68
  ## Proof
69
69
 
70
70
  Each feature below exists because published results support it. The numbers
71
- come from the linked sources. They are not UniPi benchmarks. The "why" column
72
- is our opinion.
73
-
74
- | Feature | Benchmark | Why we have it | Source |
75
- |---|---|---|---|
76
- | **Memory** | 96.6% recall@5 on LongMemEval, with raw semantic search and 0 API calls. | Chat assistants lose about 30% accuracy when facts span many sessions. We want the agent to find last week's decision without a model call. MemPalace does this on your machine. | [MemPalace](https://github.com/MemPalace/mempalace) · [LongMemEval](https://arxiv.org/abs/2410.10813) |
77
- | **Fusion** | Frontier-level FrontierCode score at up to 60% lower cost. | Most turns in a coding session are routine edits and commands. We think a frontier model should plan and review, and a lower-cost model should do the typing. | [Devin Fusion](https://cognition.com/blog/devin-fusion) |
78
- | **Fusion with two frontier models** | 69.0% on DRACO for two frontier models together. The best single model scored 65.3%. | Different models make different mistakes. A second strong model finds errors that one model misses. Use two frontier models when quality matters more than cost. | [OpenRouter Fusion](https://openrouter.ai/blog/announcements/fusion-beats-frontier/) · [Mixture-of-Agents](https://arxiv.org/abs/2406.04692) |
79
- | **Goal** | HumanEval pass@1 went from 80% to 91% with a loop on test feedback. | An agent that grades its own work stops too early or claims success. Self-written checks can hide real failures. Our goal loop continues until a separate verifier agrees, inside turn and stall budgets. | [Reflexion](https://arxiv.org/abs/2303.11366) · [Self-authored verification](https://arxiv.org/abs/2607.24300) |
80
- | **Ralph** | Six repositories overnight for $297 in API cost (a reported hackathon result). | Long tasks do not fit in one context window. A checklist file keeps the progress on disk. Each iteration works the next items and checks them off. A verifier reads the file before the loop ends. We think a simple loop is the right tool for checklist work. | [Ralph loop](https://ghuntley.com/ralph/) · [YC hackathon report](https://paddo.dev/blog/ralph-wiggum-autonomous-loops/) |
81
- | **Swarm** | 90.2% higher than a single agent on Anthropic's research eval. | Independent items do not need to wait for each other. Each worker gets a clean context, and the lead reads only the summaries. A swarm uses more tokens, so we keep it for wide work. | [Anthropic multi-agent research](https://www.anthropic.com/engineering/multi-agent-research-system) |
82
- | **Graph** | Up to 3.7× lower latency, 6.7× lower cost and about 9% higher accuracy than ReAct. | Many tasks have steps that need earlier results. A graph runs the independent steps at the same time. Each step gets only the inputs it needs. | [LLMCompiler (ICML 2024)](https://arxiv.org/abs/2312.04511) |
83
- | **Prefix cache** | Up to 90% lower cost and 85% lower latency for long prompts. | Each agent turn sends the full conversation again. In our UniPi 2.4 logs, each cache miss sent about 62,700 tokens again at the full price. UniPi keeps the prefix byte-identical, so a turn pays full price only for the new tail. | [Anthropic prompt caching](https://claude.com/blog/prompt-caching) · [Modular handbook](https://handbook.modular.com/inference-optimization/prefix-caching/) · [our cache study](docs/deepseek-cache-rate-research.md) |
71
+ come from the linked sources. They are not UniPi benchmarks.
72
+
73
+ | Feature | Benchmark | Source |
74
+ |---|---|---|
75
+ | **Memory** | 96.6% recall@5 on LongMemEval, with raw semantic search and 0 API calls. | [MemPalace](https://github.com/MemPalace/mempalace) · [LongMemEval](https://arxiv.org/abs/2410.10813) |
76
+ | **Fusion** | Frontier-level FrontierCode score at up to 60% lower cost. | [Devin Fusion](https://cognition.com/blog/devin-fusion) |
77
+ | **Fusion with two frontier models** | 69.0% on DRACO for two frontier models together. The best single model scored 65.3%. | [OpenRouter Fusion](https://openrouter.ai/blog/announcements/fusion-beats-frontier/) · [Mixture-of-Agents](https://arxiv.org/abs/2406.04692) |
78
+ | **Goal** | HumanEval pass@1 went from 80% to 91% with a loop on test feedback. | [Reflexion](https://arxiv.org/abs/2303.11366) · [Self-authored verification](https://arxiv.org/abs/2607.24300) |
79
+ | **Ralph** | Six repositories overnight for $297 in API cost (a reported hackathon result). | [Ralph loop](https://ghuntley.com/ralph/) · [YC hackathon report](https://paddo.dev/blog/ralph-wiggum-autonomous-loops/) |
80
+ | **Swarm** | 90.2% higher than a single agent on Anthropic's research eval. | [Anthropic multi-agent research](https://www.anthropic.com/engineering/multi-agent-research-system) |
81
+ | **Graph** | Up to 3.7× lower latency, 6.7× lower cost and about 9% higher accuracy than ReAct. | [LLMCompiler (ICML 2024)](https://arxiv.org/abs/2312.04511) |
82
+ | **Prefix cache** | Up to 90% lower cost and 85% lower latency for long prompts. | [Anthropic prompt caching](https://claude.com/blog/prompt-caching) · [Modular handbook](https://handbook.modular.com/inference-optimization/prefix-caching/) · [our cache study](docs/deepseek-cache-rate-research.md) |
84
83
 
85
84
  Long work matters more each year. METR measured that the length of software
86
85
  tasks that agents can finish doubles about every 7 months
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,7 +27,7 @@
27
27
  "prepare": "node scripts/build-bundle.mjs --if-missing",
28
28
  "prepublishOnly": "npm run build",
29
29
  "publish:all": "npm publish --workspaces --access public",
30
- "test": "npx tsx --test tests/*.test.js packages/memory/tests/*.test.ts && npm test --workspaces --if-present",
30
+ "test": "npx tsx --test tests/*.test.js tests/*.test.ts packages/memory/tests/*.test.ts && npm test --workspaces --if-present",
31
31
  "typecheck": "npx tsc --noEmit --skipLibCheck",
32
32
  "tui:gallery": "npx tsx scripts/tui-gallery/index.ts",
33
33
  "publish:kanboard": "node scripts/publish-kanboard-platforms.mjs"
@@ -82,26 +82,26 @@
82
82
  "typebox": "^1.1.38"
83
83
  },
84
84
  "dependencies": {
85
- "@pi-unipi/ask-user": "3.0.0-alpha.22",
86
- "@pi-unipi/background-tasks": "3.0.0-alpha.22",
87
- "@pi-unipi/btw": "3.0.0-alpha.22",
88
- "@pi-unipi/command-enchantment": "3.0.0-alpha.22",
89
- "@pi-unipi/compactor": "3.0.0-alpha.22",
90
- "@pi-unipi/core": "3.0.0-alpha.22",
91
- "@pi-unipi/footer": "3.0.0-alpha.22",
92
- "@pi-unipi/fusion": "3.0.0-alpha.22",
93
- "@pi-unipi/info-screen": "3.0.0-alpha.22",
94
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.22",
95
- "@pi-unipi/kanboard": "3.0.0-alpha.22",
96
- "@pi-unipi/mcp": "3.0.0-alpha.22",
97
- "@pi-unipi/memory": "3.0.0-alpha.22",
98
- "@pi-unipi/notify": "3.0.0-alpha.22",
99
- "@pi-unipi/skill-registry": "3.0.0-alpha.22",
100
- "@pi-unipi/subagents": "3.0.0-alpha.22",
101
- "@pi-unipi/updater": "3.0.0-alpha.22",
102
- "@pi-unipi/utility": "3.0.0-alpha.22",
103
- "@pi-unipi/web-api": "3.0.0-alpha.22",
104
- "@pi-unipi/workflow": "3.0.0-alpha.22"
85
+ "@pi-unipi/ask-user": "3.0.0-alpha.23",
86
+ "@pi-unipi/background-tasks": "3.0.0-alpha.23",
87
+ "@pi-unipi/btw": "3.0.0-alpha.23",
88
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.23",
89
+ "@pi-unipi/compactor": "3.0.0-alpha.23",
90
+ "@pi-unipi/core": "3.0.0-alpha.23",
91
+ "@pi-unipi/footer": "3.0.0-alpha.23",
92
+ "@pi-unipi/fusion": "3.0.0-alpha.23",
93
+ "@pi-unipi/info-screen": "3.0.0-alpha.23",
94
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.23",
95
+ "@pi-unipi/kanboard": "3.0.0-alpha.23",
96
+ "@pi-unipi/mcp": "3.0.0-alpha.23",
97
+ "@pi-unipi/memory": "3.0.0-alpha.23",
98
+ "@pi-unipi/notify": "3.0.0-alpha.23",
99
+ "@pi-unipi/skill-registry": "3.0.0-alpha.23",
100
+ "@pi-unipi/subagents": "3.0.0-alpha.23",
101
+ "@pi-unipi/updater": "3.0.0-alpha.23",
102
+ "@pi-unipi/utility": "3.0.0-alpha.23",
103
+ "@pi-unipi/web-api": "3.0.0-alpha.23",
104
+ "@pi-unipi/workflow": "3.0.0-alpha.23"
105
105
  },
106
106
  "devDependencies": {
107
107
  "@earendil-works/pi-agent-core": "^0.87.1",
@@ -44,9 +44,9 @@ registerSettings({
44
44
  {
45
45
  title: "Tool",
46
46
  fields: [
47
- { key: "enabled", type: "boolean", label: "Enable ask_user", description: "Let the agent stop and ask you multiple-choice questions" },
48
- { key: "notifyOnAsk", type: "boolean", label: "Notify when asked", description: "Send a notification when the agent is waiting for your answer" },
49
- { key: "maxQuestions", type: "number", label: "Questions per call", min: 1, max: 4, description: "How many questions one dialog may hold (applies to new sessions)" },
47
+ { key: "enabled", type: "boolean", label: "Enable ask_user", description: "Let the agent stop and ask you multiple-choice questions." },
48
+ { key: "notifyOnAsk", type: "boolean", label: "Notify when asked", description: "Send a notification when the agent is waiting for your answer." },
49
+ { key: "maxQuestions", type: "number", label: "Questions per call", min: 1, max: 4, description: "How many questions one dialog may hold. Applies to new sessions." },
50
50
  ],
51
51
  },
52
52
  {
@@ -58,24 +58,24 @@ registerSettings({
58
58
  type: "enum",
59
59
  label: "Esc",
60
60
  options: [
61
- { value: "stop", label: "stop the agent's turn" },
62
- { value: "send", label: "send what's answered (rest skipped)" },
61
+ { value: "stop", label: "stop", description: "stop the agent's turn" },
62
+ { value: "send", label: "send", description: "send what's answered, skip the rest" },
63
63
  ],
64
- description: "What Esc does in the dialog",
64
+ description: "What Esc does in the dialog.",
65
65
  },
66
- { key: "digitAdvance", type: "boolean", label: "Number keys move on", description: "In a single-choice question, picking with 1–9 also goes to the next question" },
66
+ { key: "digitAdvance", type: "boolean", label: "Number keys move on", description: "In a single-choice question, picking with 1–9 also advances." },
67
67
  {
68
68
  key: "other",
69
69
  type: "enum",
70
70
  label: "\"Other\" choice",
71
71
  options: [
72
- { value: "agent", label: "agent decides (default on)" },
73
- { value: "always", label: "always offer it" },
74
- { value: "never", label: "never offer it" },
72
+ { value: "agent", label: "agent decides", description: "on unless the agent opts out" },
73
+ { value: "always", label: "always", description: "always offer the Other row" },
74
+ { value: "never", label: "never", description: "never offer the Other row" },
75
75
  ],
76
- description: "The free-text \"Other (type your own)\" row",
76
+ description: "The free-text \"Other (type your own)\" row.",
77
77
  },
78
- { key: "helpLine", type: "boolean", label: "\"Not ready\" line", description: "Show \"? Not ready to answer, help me out!\" under the dialog" },
78
+ { key: "helpLine", type: "boolean", label: "\"Not ready\" line", description: "Show \"? Not ready to answer, help me out!\" under the dialog." },
79
79
  ],
80
80
  },
81
81
  ],
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
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.22"
43
+ "@pi-unipi/core": "3.0.0-alpha.23"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
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",
@@ -29,7 +29,7 @@ registerSettings({
29
29
  key: "autocompleteEnhanced",
30
30
  type: "boolean",
31
31
  label: "Enhanced autocomplete",
32
- description: "Fuzzy /unipi:* command suggestions",
32
+ description: "Fuzzy /unipi:* command suggestions while you type.",
33
33
  },
34
34
  ],
35
35
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
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.22",
13
+ "@pi-unipi/core": "3.0.0-alpha.23",
14
14
  "turndown": "^7.2.4"
15
15
  },
16
16
  "devDependencies": {
@@ -21,8 +21,6 @@ export interface BackgroundTasksConfig {
21
21
  notifyOnCompletion: boolean;
22
22
  /** Default triggerOnCompletion (follow-up wake) for bg_run tasks. */
23
23
  triggerOnCompletion: boolean;
24
- /** Default timeoutSeconds for shell tasks (0 = none). */
25
- defaultTimeoutSeconds: number;
26
24
  /** Max finished tasks retained in memory. */
27
25
  maxFinishedTasks: number;
28
26
  /** Output cap in bytes before a task is killed+failed (default 20 MiB). */
@@ -33,7 +31,6 @@ export const DEFAULT_CONFIG: BackgroundTasksConfig = {
33
31
  enabled: true,
34
32
  notifyOnCompletion: true,
35
33
  triggerOnCompletion: true,
36
- defaultTimeoutSeconds: 0,
37
34
  maxFinishedTasks: 30,
38
35
  maxOutputBytes: 20 * 1024 * 1024,
39
36
  };
@@ -114,11 +111,6 @@ export function validateBackgroundTasksConfig(config: unknown): string[] {
114
111
  problems.push(`${key} must be a boolean`);
115
112
  }
116
113
  }
117
- if (c.defaultTimeoutSeconds !== undefined) {
118
- if (typeof c.defaultTimeoutSeconds !== "number" || !Number.isFinite(c.defaultTimeoutSeconds) || c.defaultTimeoutSeconds < 0) {
119
- problems.push("defaultTimeoutSeconds must be a non-negative number");
120
- }
121
- }
122
114
  if (c.maxFinishedTasks !== undefined) {
123
115
  if (typeof c.maxFinishedTasks !== "number" || !Number.isInteger(c.maxFinishedTasks) || c.maxFinishedTasks < 1) {
124
116
  problems.push("maxFinishedTasks must be a positive integer");
@@ -154,12 +146,11 @@ registerSettings({
154
146
  {
155
147
  title: "Tasks",
156
148
  fields: [
157
- { key: "enabled", type: "boolean", label: "Enabled", description: "Master toggle — off registers nothing" },
158
- { key: "notifyOnCompletion", type: "boolean", label: "Notify on completion" },
159
- { key: "triggerOnCompletion", type: "boolean", label: "Follow-up wake", description: "Terminal state wakes the agent" },
160
- { key: "defaultTimeoutSeconds", type: "number", label: "Timeout s", min: 0, zeroLabel: "∞ none" },
161
- { key: "maxFinishedTasks", type: "number", label: "Max finished kept", min: 1 },
162
- { key: "maxOutputBytes", type: "number", label: "Output cap bytes", min: 1024, description: "Kill+fail a task past this output size" },
149
+ { key: "enabled", type: "boolean", label: "Enabled", description: "Master toggle — off registers no tools, commands, hooks or UI." },
150
+ { key: "notifyOnCompletion", type: "boolean", label: "Notify on completion", description: "Default for tool-launched tasks; bg_run can override per call." },
151
+ { key: "triggerOnCompletion", type: "boolean", label: "Follow-up wake", description: "A finished task's terminal state wakes the agent for a follow-up turn." },
152
+ { key: "maxFinishedTasks", type: "number", label: "Max finished kept", min: 1, description: "Finished tasks kept in memory before the oldest is dropped." },
153
+ { key: "maxOutputBytes", type: "number", label: "Output cap", unit: "bytes", min: 1024, description: "Kill and fail a task that outputs more than this." },
163
154
  ],
164
155
  },
165
156
  ],
@@ -202,10 +193,6 @@ export function loadBackgroundTasksConfig(cwd: string): LoadedBackgroundTasksCon
202
193
  typeof merged.notifyOnCompletion === "boolean" ? merged.notifyOnCompletion : DEFAULT_CONFIG.notifyOnCompletion,
203
194
  triggerOnCompletion:
204
195
  typeof merged.triggerOnCompletion === "boolean" ? merged.triggerOnCompletion : DEFAULT_CONFIG.triggerOnCompletion,
205
- defaultTimeoutSeconds:
206
- typeof merged.defaultTimeoutSeconds === "number" && Number.isFinite(merged.defaultTimeoutSeconds) && merged.defaultTimeoutSeconds >= 0
207
- ? merged.defaultTimeoutSeconds
208
- : DEFAULT_CONFIG.defaultTimeoutSeconds,
209
196
  maxFinishedTasks:
210
197
  typeof merged.maxFinishedTasks === "number" && Number.isInteger(merged.maxFinishedTasks) && merged.maxFinishedTasks >= 1
211
198
  ? merged.maxFinishedTasks
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/btw",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
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.22"
40
+ "@pi-unipi/core": "3.0.0-alpha.23"
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.22",
3
+ "version": "3.0.0-alpha.23",
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.22",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.22",
36
+ "@pi-unipi/core": "3.0.0-alpha.23",
37
+ "@pi-unipi/info-screen": "3.0.0-alpha.23",
38
38
  "@earendil-works/pi-agent-core": "^0.87.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -362,7 +362,7 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
362
362
  } catch (err) {
363
363
  if (event.signal?.aborted) return { cancel: true };
364
364
  // Fall back to Pi's own path (same summarizer, Pi's auth handling).
365
- dbg(config.debug, { method: "llm", fallback: true, error: String(err) });
365
+ dbg(process.env.UNIPI_COMPACTOR_DEBUG === "1", { method: "llm", fallback: true, error: String(err) });
366
366
  return;
367
367
  }
368
368
  }
@@ -394,14 +394,14 @@ export function registerCompactionHooks(pi: ExtensionAPI, deps: CompactionHookDe
394
394
  ctx?.ui?.notify?.(plan.reason === "no_live_messages" ? "compactor: nothing to compact" : "compactor: too few messages to compact", "warning");
395
395
  } catch {}
396
396
  }
397
- dbg(config.debug, { cancelled: true, reason: plan.reason });
397
+ dbg(process.env.UNIPI_COMPACTOR_DEBUG === "1", { cancelled: true, reason: plan.reason });
398
398
  return { cancel: true };
399
399
  }
400
400
 
401
401
  lastStats = plan.stats;
402
402
  lastDetails = plan.details;
403
403
  if (!parsed.isCompactor && parsed.followUpPrompt && reason === "manual") pendingFollowUpPrompt = parsed.followUpPrompt;
404
- dbg(config.debug, { method: "vcc", reason, stats: plan.stats, details: plan.details, summaryPreview: plan.summary.slice(0, 800) });
404
+ dbg(process.env.UNIPI_COMPACTOR_DEBUG === "1", { method: "vcc", reason, stats: plan.stats, details: plan.details, summaryPreview: plan.summary.slice(0, 800) });
405
405
  return {
406
406
  compaction: {
407
407
  summary: plan.summary,
@@ -6,11 +6,24 @@ import { homedir } from "node:os";
6
6
  import { join } from "node:path";
7
7
  import { getSettingsScoped, registerSettings, setSettings } from "@pi-unipi/core";
8
8
  import type { CompactorConfig } from "../types.js";
9
- import { DEFAULT_COMPACTOR_CONFIG } from "./schema.js";
9
+ import { DEFAULT_COMPACTOR_CONFIG, SUMMARY_SECTION_IDS } from "./schema.js";
10
10
 
11
11
  const METHOD_OPTIONS = [
12
- { value: "vcc", label: "lossless (no model)" },
13
- { value: "llm", label: "model summary" },
12
+ { value: "vcc", label: "lossless", description: "instant structured summary; history stays searchable" },
13
+ { value: "llm", label: "model summary", description: "pi's model writes the summary — costs one model call" },
14
+ ] as const;
15
+
16
+ /** The `summarySections` multiselect: labels + explanations per section id. */
17
+ const SUMMARY_SECTION_OPTIONS = [
18
+ { value: "activeWork", label: "Active work", description: "goal, ralph and kanboard state" },
19
+ { value: "requests", label: "Your requests", description: "the user requests this session answered" },
20
+ { value: "state", label: "Latest state", description: "the agent's most recent progress reports" },
21
+ { value: "decisions", label: "Decisions", description: "decisions and constraints reached so far" },
22
+ { value: "files", label: "Files", description: "files touched and what was done to them" },
23
+ { value: "commits", label: "Commits", description: "commit history of this session" },
24
+ { value: "errors", label: "Open errors", description: "unresolved errors worth remembering" },
25
+ { value: "lessons", label: "Lessons", description: "memory notes and diagnoses written down" },
26
+ { value: "transcript", label: "Recent transcript", description: "the last turns kept verbatim" },
14
27
  ] as const;
15
28
 
16
29
  // Registered with the unified settings hub. Canonical paths:
@@ -29,28 +42,28 @@ registerSettings({
29
42
  key: "method",
30
43
  type: "enum",
31
44
  label: "Method",
32
- description: "Lossless: instant structured summary, full history stays searchable. Model summary: Pi's model-written summary (costs a model call).",
45
+ description: "How the context is summarized. Takes effect on the next compaction.",
33
46
  options: METHOD_OPTIONS,
34
47
  },
35
48
  {
36
49
  key: "piCompact",
37
50
  type: "enum",
38
51
  label: "Pi's /compact",
39
- description: "What Pi's built-in /compact command does",
40
- options: [{ value: "follow", label: "same as Method" }, ...METHOD_OPTIONS],
52
+ description: "What pi's built-in /compact command does: follow Method, or force one.",
53
+ options: [{ value: "follow", label: "same as Method", description: "reuse whatever Method is set to" }, ...METHOD_OPTIONS],
41
54
  },
42
55
  {
43
56
  key: "trigger",
44
57
  type: "enum",
45
58
  label: "When",
46
- description: "Pi's limit: compact when the context nears the model's window (Pi's compaction settings). Percentage: compact at a set % of the window.",
59
+ description: "What trips a compaction while you work.",
47
60
  options: [
48
- { value: "pi", label: "Pi's context limit" },
49
- { value: "percent", label: "at a percentage" },
61
+ { value: "pi", label: "pi's context limit", description: "compact when pi itself nears the model's window" },
62
+ { value: "percent", label: "at a percentage", description: "compact when usage crosses the threshold below" },
50
63
  ],
51
64
  },
52
- { key: "thresholdPercent", type: "number", label: "Percentage", min: 30, max: 95, description: "Used when When = at a percentage" },
53
- { key: "notify", type: "boolean", label: "Notifications", description: "Show a notice when compaction runs or fails" },
65
+ { key: "thresholdPercent", type: "number", label: "Trigger threshold", unit: "%", min: 30, max: 95, description: "Context share that trips a compaction when When = at a percentage." },
66
+ { key: "notify", type: "boolean", label: "Notifications", description: "Notice when a compaction runs or fails." },
54
67
  ],
55
68
  },
56
69
  {
@@ -58,21 +71,17 @@ registerSettings({
58
71
  description: "Tuned defaults — rarely worth changing",
59
72
  advanced: true,
60
73
  fields: [
61
- { key: "smartKeepTail", type: "boolean", label: "Smart keep tail", description: "Keep more recent turns when the kept tail would be tiny (≤5k tokens, up to 25k)" },
62
- { key: "summaryBudgetTokens", type: "number", label: "Summary budget", min: 0, max: 20000, zeroLabel: "auto", description: "Lossless summary size in tokens (auto scales 1.5k–4k with session size)" },
63
- { key: "sections.activeWork", type: "boolean", label: "Section: active work", description: "Goal, ralph and kanboard state from those modules" },
64
- { key: "sections.requests", type: "boolean", label: "Section: your requests" },
65
- { key: "sections.state", type: "boolean", label: "Section: latest state", description: "The agent's most recent progress reports" },
66
- { key: "sections.decisions", type: "boolean", label: "Section: decisions & constraints" },
67
- { key: "sections.files", type: "boolean", label: "Section: files" },
68
- { key: "sections.commits", type: "boolean", label: "Section: commits" },
69
- { key: "sections.errors", type: "boolean", label: "Section: open errors" },
70
- { key: "sections.lessons", type: "boolean", label: "Section: lessons", description: "Lessons the agent wrote down: memory notes, # comments, diagnoses" },
71
- { key: "sections.transcript", type: "boolean", label: "Section: recent transcript" },
72
- { key: "cooldownMs", type: "number", label: "Percentage cooldown ms", min: 0, zeroLabel: "none", description: "Minimum delay between percentage-triggered compactions" },
73
- { key: "repeatMinGrowthTokens", type: "number", label: "Percentage repeat growth", min: 0, zeroLabel: "off", description: "New tokens needed to compact again while still above the percentage" },
74
- { key: "llmInstructions", type: "string", label: "Model summary instructions", emptyLabel: "none", description: "Extra instructions passed to model-written summaries" },
75
- { key: "debug", type: "boolean", label: "Debug output", description: "Write compaction diagnostics to /tmp/compactor-debug.json" },
74
+ { key: "smartKeepTail", type: "boolean", label: "Smart keep tail", description: "Keep more recent turns when the kept tail would be tiny (≤5k tokens, up to 25k)." },
75
+ { key: "summaryBudgetTokens", type: "number", label: "Summary budget", unit: "tokens", min: 0, max: 20000, zeroLabel: "auto", description: "Lossless summary size in tokens (auto scales 1.5k–4k with session size)." },
76
+ {
77
+ key: "summarySections",
78
+ type: "multiselect",
79
+ label: "Summary sections",
80
+ description: "What the summary keeps — deselect to drop it from every new summary.",
81
+ options: SUMMARY_SECTION_OPTIONS,
82
+ emptyLabel: "none",
83
+ },
84
+ { key: "llmInstructions", type: "string", label: "Summary instructions", emptyLabel: "none", description: "Extra instructions passed to model-written summaries." },
76
85
  ],
77
86
  },
78
87
  ],
@@ -103,8 +112,6 @@ export function translateLegacyConfig(raw: Raw): Raw {
103
112
  if (!("trigger" in raw) && auto.enabled === true) out.trigger = "percent";
104
113
  const carry: Array<[keyof CompactorConfig, string]> = [
105
114
  ["thresholdPercent", "thresholdPercent"],
106
- ["cooldownMs", "cooldownMs"],
107
- ["repeatMinGrowthTokens", "repeatMinGrowthTokens"],
108
115
  ["notify", "notify"],
109
116
  ];
110
117
  for (const [to, from] of carry) {
@@ -137,9 +144,49 @@ export function loadConfig(cwd: string = process.cwd()): CompactorConfig {
137
144
  }
138
145
  if (config.method === "jev") config.method = "vcc";
139
146
  if (config.piCompact === "jev") config.piCompact = "vcc";
147
+ // The defaults-merged object always carries summarySections, so the stored
148
+ // layers decide: new array wins, else legacy sections booleans, else default.
149
+ const selection = storedSummarySelection(cwd);
150
+ if (selection !== undefined) config.summarySections = selection;
151
+ normalizeSummarySections(config);
140
152
  return config as unknown as CompactorConfig;
141
153
  }
142
154
 
155
+ /**
156
+ * The stored section selection: first layer (project wins) with the new
157
+ * array, else the first with a legacy sections object (membership = every id
158
+ * not explicitly false). undefined = nothing stored — the default applies.
159
+ */
160
+ function storedSummarySelection(cwd: string): string[] | undefined {
161
+ for (const scope of ["project", "global"] as const) {
162
+ let layer: Raw | undefined;
163
+ try {
164
+ layer = getSettingsScoped("compactor", scope, cwd);
165
+ } catch {
166
+ layer = undefined;
167
+ }
168
+ if (!layer) continue;
169
+ if (Array.isArray(layer.summarySections)) return layer.summarySections.map(String);
170
+ if (isRecord(layer.sections)) {
171
+ const legacy = layer.sections;
172
+ return SUMMARY_SECTION_IDS.filter((id) => legacy[id] !== false);
173
+ }
174
+ }
175
+ return undefined;
176
+ }
177
+
178
+ /**
179
+ * `summarySections` (string[]) present → the internal `sections` object
180
+ * derives from membership; absent → the default selection stands.
181
+ */
182
+ function normalizeSummarySections(config: Raw): void {
183
+ if (!Array.isArray(config.summarySections)) return;
184
+ const on = new Set(config.summarySections.map(String));
185
+ const sections: Raw = {};
186
+ for (const id of SUMMARY_SECTION_IDS) sections[id] = on.has(id);
187
+ config.sections = sections;
188
+ }
189
+
143
190
  /**
144
191
  * Write translated legacy keys into each scope's file once, so the settings
145
192
  * hub shows the effective values. Additive only: old keys stay, untouched.
@@ -148,10 +195,18 @@ export function migrateLegacyConfigFiles(cwd: string = process.cwd()): void {
148
195
  for (const scope of ["global", "project"] as const) {
149
196
  try {
150
197
  const raw = getSettingsScoped("compactor", scope, cwd);
151
- if (!raw || (!("overrideDefaultCompaction" in raw) && !("autoCompaction" in raw))) continue;
198
+ if (!raw) continue;
199
+ // summarySections: derive from a stored legacy sections object once, so
200
+ // the hub shows the effective selection. Additive — booleans stay.
201
+ if (!Array.isArray(raw.summarySections) && isRecord(raw.sections)) {
202
+ const derived = SUMMARY_SECTION_IDS.filter((id) => (raw.sections as Raw)[id] !== false);
203
+ setSettings("compactor", { summarySections: [...derived] }, scope, cwd);
204
+ raw.summarySections = derived;
205
+ }
206
+ if (!("overrideDefaultCompaction" in raw) && !("autoCompaction" in raw)) continue;
152
207
  const translated = translateLegacyConfig(raw);
153
208
  const patch: Raw = {};
154
- for (const key of ["method", "trigger", "thresholdPercent", "cooldownMs", "repeatMinGrowthTokens", "notify"]) {
209
+ for (const key of ["method", "trigger", "thresholdPercent", "notify", "summarySections"]) {
155
210
  if (!(key in raw) && key in translated) patch[key] = translated[key];
156
211
  }
157
212
  if (Object.keys(patch).length > 0) setSettings("compactor", patch, scope, cwd);
@@ -4,6 +4,23 @@
4
4
 
5
5
  import type { AutoCompactionConfig, CompactorConfig } from "../types.js";
6
6
 
7
+ /** Percentage-trigger tuning, fixed since the settings were pruned (UNI-50). */
8
+ export const COOLDOWN_MS = 60_000;
9
+ export const REPEAT_MIN_GROWTH_TOKENS = 4_000;
10
+
11
+ /** Summary-section ids in canonical order (hub multiselect `summarySections`). */
12
+ export const SUMMARY_SECTION_IDS = [
13
+ "activeWork",
14
+ "requests",
15
+ "state",
16
+ "decisions",
17
+ "files",
18
+ "commits",
19
+ "errors",
20
+ "lessons",
21
+ "transcript",
22
+ ] as const;
23
+
7
24
  export const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
8
25
  method: "vcc",
9
26
  piCompact: "follow",
@@ -23,10 +40,8 @@ export const DEFAULT_COMPACTOR_CONFIG: CompactorConfig = {
23
40
  lessons: true,
24
41
  transcript: true,
25
42
  },
26
- cooldownMs: 60_000,
27
- repeatMinGrowthTokens: 4_000,
43
+ summarySections: [...SUMMARY_SECTION_IDS],
28
44
  llmInstructions: "",
29
- debug: false,
30
45
  };
31
46
 
32
47
  /** The percentage-trigger view of a config (input to decideAutoCompaction). */
@@ -34,8 +49,8 @@ export function autoCompactionOf(config: CompactorConfig): AutoCompactionConfig
34
49
  return {
35
50
  enabled: config.trigger === "percent",
36
51
  thresholdPercent: config.thresholdPercent,
37
- cooldownMs: config.cooldownMs,
38
- repeatMinGrowthTokens: config.repeatMinGrowthTokens,
52
+ cooldownMs: COOLDOWN_MS,
53
+ repeatMinGrowthTokens: REPEAT_MIN_GROWTH_TOKENS,
39
54
  notify: config.notify,
40
55
  };
41
56
  }
@@ -88,14 +88,10 @@ export interface CompactorConfig {
88
88
  /** Lossless summary budget in tokens; 0 = auto (scales with session size). */
89
89
  summaryBudgetTokens: number;
90
90
  sections: SummarySections;
91
- /** Percentage trigger: minimum delay between compactions. */
92
- cooldownMs: number;
93
- /** Percentage trigger: new tokens needed to compact again while still above threshold. */
94
- repeatMinGrowthTokens: number;
91
+ /** Hub multiselect form of `sections`; present → sections derives from it. */
92
+ summarySections?: string[];
95
93
  /** Extra instructions for model-written summaries. */
96
94
  llmInstructions: string;
97
- /** Write compaction diagnostics to /tmp/compactor-debug.json. */
98
- debug: boolean;
99
95
  }
100
96
 
101
97
  /** Percentage auto-compaction trigger settings (decision input). */
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.22",
3
+ "version": "3.0.0-alpha.23",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",