@pi-unipi/unipi 3.0.0-alpha.4 → 3.0.0-alpha.5

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 (99) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +1 -4
  3. package/package.json +21 -23
  4. package/packages/ask-user/package.json +2 -2
  5. package/packages/autocomplete/package.json +1 -1
  6. package/packages/autocomplete/src/constants.ts +15 -13
  7. package/packages/autocomplete/src/sorting.ts +8 -0
  8. package/packages/background-tasks/package.json +2 -2
  9. package/packages/btw/package.json +2 -2
  10. package/packages/compactor/package.json +3 -3
  11. package/packages/core/constants.ts +4 -23
  12. package/packages/core/events.ts +0 -53
  13. package/packages/core/index.ts +2 -0
  14. package/packages/core/model-cache.ts +81 -7
  15. package/packages/core/package.json +2 -2
  16. package/packages/core/src/jev/chatter.ts +24 -0
  17. package/packages/core/src/package-colors.ts +2 -0
  18. package/packages/core/src/settings/catalog.ts +39 -15
  19. package/packages/core/src/settings/hub.ts +44 -10
  20. package/packages/core/src/settings/open.ts +49 -0
  21. package/packages/core/src/settings/schema.ts +8 -4
  22. package/packages/footer/package.json +3 -3
  23. package/packages/fusion/package.json +3 -3
  24. package/packages/fusion/src/picker.ts +36 -12
  25. package/packages/info-screen/package.json +2 -2
  26. package/packages/input-shortcuts/package.json +2 -2
  27. package/packages/kanboard/package.json +7 -7
  28. package/packages/long-horizon/package.json +2 -2
  29. package/packages/mcp/package.json +2 -2
  30. package/packages/memory/package.json +3 -3
  31. package/packages/notify/package.json +2 -2
  32. package/packages/skill-registry/README.md +55 -16
  33. package/packages/skill-registry/index.ts +223 -0
  34. package/packages/skill-registry/package.json +20 -4
  35. package/packages/skill-registry/src/editor.ts +208 -0
  36. package/packages/skill-registry/src/judge.ts +293 -0
  37. package/packages/skill-registry/src/registry.ts +98 -0
  38. package/packages/skill-registry/src/settings.ts +258 -0
  39. package/packages/skill-registry/src/vault.ts +80 -0
  40. package/packages/subagents/README.md +0 -1
  41. package/packages/subagents/package.json +2 -2
  42. package/packages/subagents/src/index.ts +0 -6
  43. package/packages/unipi/bundled.js +20556 -20048
  44. package/packages/unipi/index.ts +2 -7
  45. package/packages/updater/package.json +2 -2
  46. package/packages/utility/README.md +67 -69
  47. package/packages/utility/package.json +2 -2
  48. package/packages/utility/skills/image/SKILL.md +37 -0
  49. package/packages/utility/skills/utility/SKILL.md +17 -60
  50. package/packages/utility/src/answer/extract.ts +128 -0
  51. package/packages/utility/src/answer/index.ts +213 -0
  52. package/packages/utility/src/answer/reply.ts +133 -0
  53. package/packages/utility/src/answer/web.ts +167 -0
  54. package/packages/utility/src/attach/detect.ts +163 -0
  55. package/packages/utility/src/attach/index.ts +184 -0
  56. package/packages/utility/src/commands.ts +38 -147
  57. package/packages/utility/src/diagnostics/engine.ts +50 -5
  58. package/packages/utility/src/image/index.ts +33 -0
  59. package/packages/utility/src/image/models.ts +221 -0
  60. package/packages/{image/src → utility/src/image}/recognize.ts +2 -2
  61. package/packages/utility/src/image/settings.ts +178 -0
  62. package/packages/{image/src/image-source.ts → utility/src/image/source.ts} +1 -1
  63. package/packages/utility/src/image/tools.ts +168 -0
  64. package/packages/utility/src/image/transports.ts +186 -0
  65. package/packages/utility/src/index.ts +47 -389
  66. package/packages/utility/src/lifecycle/cleanup.ts +116 -311
  67. package/packages/utility/src/rename/gate.ts +102 -0
  68. package/packages/utility/src/rename/index.ts +191 -0
  69. package/packages/utility/src/rename/session.ts +170 -0
  70. package/packages/utility/src/render/parse.ts +156 -0
  71. package/packages/utility/src/render/pi-settings.ts +38 -0
  72. package/packages/utility/src/render/tools.ts +316 -0
  73. package/packages/utility/src/settings.ts +163 -231
  74. package/packages/utility/src/types.ts +1 -222
  75. package/packages/watchdog/index.ts +0 -1
  76. package/packages/watchdog/package.json +3 -3
  77. package/packages/web-api/package.json +2 -2
  78. package/packages/workflow/package.json +2 -2
  79. package/packages/workflow/src/permission/decide.ts +1 -1
  80. package/packages/image/README.md +0 -99
  81. package/packages/image/package.json +0 -61
  82. package/packages/image/skills/image/SKILL.md +0 -76
  83. package/packages/image/src/generate.ts +0 -256
  84. package/packages/image/src/index.ts +0 -135
  85. package/packages/image/src/models.ts +0 -519
  86. package/packages/image/src/openai-images-api.ts +0 -282
  87. package/packages/image/src/register-providers.ts +0 -209
  88. package/packages/image/src/settings.ts +0 -173
  89. package/packages/image/src/tools.ts +0 -326
  90. package/packages/subagents/src/badge-session.ts +0 -46
  91. package/packages/subagents/src/badge.ts +0 -43
  92. package/packages/utility/src/analytics/collector.ts +0 -293
  93. package/packages/utility/src/info-screen.ts +0 -80
  94. package/packages/utility/src/lifecycle/process.ts +0 -162
  95. package/packages/utility/src/prefix-cache.ts +0 -263
  96. package/packages/utility/src/skill-discovery.ts +0 -542
  97. package/packages/utility/src/tools/env.ts +0 -113
  98. package/packages/utility/src/tui/name-badge-state.ts +0 -315
  99. package/packages/utility/src/tui/name-badge.ts +0 -109
package/CHANGELOG.md CHANGED
@@ -6,6 +6,37 @@ 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.5] — 2026-09-28
10
+
11
+ ### Breaking Changes
12
+
13
+ - **BREAKING: `@pi-unipi/image` removed.** Image generation, editing and recognition now live in `@pi-unipi/utility` (same `image` settings namespace, so existing configs keep working). **Migration path:** nothing to do with the umbrella package; if you installed `@pi-unipi/image` on its own, remove it and use `@pi-unipi/utility`.
14
+ - **BREAKING: utility slimmed.** Removed `/unipi:reload` (use pi's `/reload`), `/unipi:status`, `/unipi:env`, `/unipi:prefix-cache`, the agent tools `ctx_env` and `set_session_name`, the top name-badge bar, the analytics collector and the process-lifecycle signal handlers. **Migration path:** `/unipi:doctor` covers the health checks; sessions are named automatically (below) or with pi's `/name`. Old `badge.*` settings carry over to `rename.*`.
15
+ - **BREAKING: skill exposure moved** from `utility.skills` to the new `skills` namespace (`@pi-unipi/skill-registry`). Migrated automatically on first read.
16
+
17
+ ### Added
18
+
19
+ - `utility`: `/unipi:retry`, an alias of `/unipi:continue`.
20
+ - `utility`: **automatic session naming.** After each round, jev checks whether the round (your message plus what the agent did) established a subject and, for a named session, whether it moved to a different task; a throwaway session with a single `rename_session` tool writes the name. Greetings never rename; names set with `/name` are never overwritten; Herdr pane/tab sync kept.
21
+ - `utility`: **`/unipi:answer`** — answer the last reply without scrolling: `reply` (default: the reply scrolls above a fixed input box, like `/unipi:btw`), `questions` (one answer per question, Q/A template) or `web` (local form; over SSH it prints the `ssh -L` forward). A hint above the editor says when a reply asks questions.
22
+ - `utility`: **pasted images and files** become `[Image #N]` / `[File #N]` attachments with a chip row above the editor and inline previews in Kitty, Ghostty, iTerm2 and WezTerm; images are sent to the model as images.
23
+ - `utility`: **image tools** `image_generate`, `image_edit` (new) and `image_recognize`, with OpenRouter, fal and custom-endpoint routes, a key per provider, and model pickers filtered by input/output modality. Default model FLUX.2 [klein] 4B.
24
+ - `utility`: **response formatting** — Settings → Utility → Response formatting: `simple` (one line per tool), `regular` (pi's own) or `advanced` (highlighted commands incl. embedded heredoc/`-c` code, tinted syntax-highlighted diffs, test summaries, exit lines).
25
+ - `utility`: `/unipi:settings <search>` opens the hub filtered; typing `/settings` lists `/unipi:settings` first.
26
+ - `skill-registry`: now a real extension. **Skill settings…** grid (`/unipi:skills` → Skill settings…): per skill Enabled / Discoverable / Must show, per global or project scope, plus a skill vault at `~/.unipi/skill-vault` whose skills stay off until a scope turns them on (skill proxy, off by default).
27
+ - `core`: the shared model cache (`~/.unipi/config/models-cache.json`) records input/output modalities; the settings hub pickers and kanboard read it.
28
+
29
+ ### Changed
30
+
31
+ - `skill-registry`: skill judging keeps skills named in the request, lists hidden skills by name in the system prompt, waits for the first real request instead of freezing on a greeting, and caps generic workflow skills.
32
+ - `fusion`: `/unipi:model` lists only your preset lead/sidekick models; typing searches the whole catalogue (also inside the lead/sidekick dropdowns).
33
+ - `utility`: `/unipi:cleanup` only removes files on an explicit list, previews them and asks first; v2 memory backups can never be removed. `/unipi:doctor` checks the model cache, the Decision Model key and skill exposure.
34
+
35
+ ### Fixed
36
+
37
+ - `image`: generation failed for most users (only a pi-ai OpenRouter route with a key pi often couldn't see) and failures were reported to the agent as successes.
38
+ - `core`: capability-filtered model pickers hide entries with no declared input again.
39
+
9
40
  ## [3.0.0-alpha.4] — 2026-09-28
10
41
 
11
42
  ### Breaking Changes
package/README.md CHANGED
@@ -33,7 +33,6 @@ pi install npm:@pi-unipi/unipi
33
33
 
34
34
  **[Web API](./packages/web-api/README.md)** — Web search, page reading, content summarization. Defaults to [wigolo](https://github.com/KnockOutEZ/wigolo), a local-first engine with multi-engine search and on-device reranking — $0/query, no API key. Plus a smart-fetch engine with browser-grade TLS fingerprinting. Paid providers as fallbacks, and auto-selection falls through when a provider is unavailable.
35
35
 
36
- **[Image](./packages/image/README.md)** — Generate images from a text prompt and analyze existing ones. 34 image models, plus vision analysis on any model that accepts image input, with a customizable system prompt.
37
36
 
38
37
  **[MCP](./packages/mcp/README.md)** — Browse 7,800+ MCP servers, add them interactively. Tools from servers register automatically as Pi tools.
39
38
 
@@ -116,14 +115,12 @@ Coexists triggers enhance behavior when packages are installed together. Workflo
116
115
  | `spawn_helper` / `get_helper_result` | subagents | Parallel agents |
117
116
  | `memory_store` / `memory_search` / `memory_delete` | memory | Memory CRUD |
118
117
  | `web_search` / `multi_web_content_read` / `web_llm_summarize` | web-api | Web research |
119
- | `image_generate` / `image_recognize` | image | Image generation and vision (recognize auto-hides while the session model has vision) |
118
+ | `image_generate` / `image_edit` / `image_recognize` | utility | Image generation, editing and vision (recognize hides while the session model has vision) |
120
119
  | `notify_user` | notify | Push notifications |
121
120
  | `sidekick` / `read_subagent` | fusion | Delegate to the persistent sidekick and collect its report |
122
121
  | `bg_run` / `bg_status` / `bg_logs` / `bg_kill` / `bg_delegate` / `bg_result` | background-tasks | Long-running commands and background delegate agents |
123
122
  | `ask_user` | ask-user | User input |
124
123
  | `session_recall` / `context_budget` | compactor | Context management |
125
- | `ctx_env` | utility | Environment info |
126
- | `set_session_name` | utility | Session name for badge |
127
124
 
128
125
  ## Development
129
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.5",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -70,7 +70,6 @@
70
70
  "packages/notify/skills",
71
71
  "packages/kanboard/skills",
72
72
  "packages/updater/skills",
73
- "packages/image/skills",
74
73
  "packages/subagents/skills"
75
74
  ],
76
75
  "prompts": [
@@ -84,27 +83,26 @@
84
83
  "typebox": "^1.1.38"
85
84
  },
86
85
  "dependencies": {
87
- "@pi-unipi/ask-user": "3.0.0-alpha.4",
88
- "@pi-unipi/background-tasks": "3.0.0-alpha.4",
89
- "@pi-unipi/btw": "3.0.0-alpha.4",
90
- "@pi-unipi/command-enchantment": "3.0.0-alpha.4",
91
- "@pi-unipi/compactor": "3.0.0-alpha.4",
92
- "@pi-unipi/core": "3.0.0-alpha.4",
93
- "@pi-unipi/footer": "3.0.0-alpha.4",
94
- "@pi-unipi/image": "3.0.0-alpha.4",
95
- "@pi-unipi/fusion": "3.0.0-alpha.4",
96
- "@pi-unipi/info-screen": "3.0.0-alpha.4",
97
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.4",
98
- "@pi-unipi/kanboard": "3.0.0-alpha.4",
99
- "@pi-unipi/mcp": "3.0.0-alpha.4",
100
- "@pi-unipi/memory": "3.0.0-alpha.4",
101
- "@pi-unipi/notify": "3.0.0-alpha.4",
102
- "@pi-unipi/skill-registry": "3.0.0-alpha.4",
103
- "@pi-unipi/subagents": "3.0.0-alpha.4",
104
- "@pi-unipi/updater": "3.0.0-alpha.4",
105
- "@pi-unipi/utility": "3.0.0-alpha.4",
106
- "@pi-unipi/web-api": "3.0.0-alpha.4",
107
- "@pi-unipi/workflow": "3.0.0-alpha.4"
86
+ "@pi-unipi/ask-user": "3.0.0-alpha.5",
87
+ "@pi-unipi/background-tasks": "3.0.0-alpha.5",
88
+ "@pi-unipi/btw": "3.0.0-alpha.5",
89
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.5",
90
+ "@pi-unipi/compactor": "3.0.0-alpha.5",
91
+ "@pi-unipi/core": "3.0.0-alpha.5",
92
+ "@pi-unipi/footer": "3.0.0-alpha.5",
93
+ "@pi-unipi/fusion": "3.0.0-alpha.5",
94
+ "@pi-unipi/info-screen": "3.0.0-alpha.5",
95
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.5",
96
+ "@pi-unipi/kanboard": "3.0.0-alpha.5",
97
+ "@pi-unipi/mcp": "3.0.0-alpha.5",
98
+ "@pi-unipi/memory": "3.0.0-alpha.5",
99
+ "@pi-unipi/notify": "3.0.0-alpha.5",
100
+ "@pi-unipi/skill-registry": "3.0.0-alpha.5",
101
+ "@pi-unipi/subagents": "3.0.0-alpha.5",
102
+ "@pi-unipi/updater": "3.0.0-alpha.5",
103
+ "@pi-unipi/utility": "3.0.0-alpha.5",
104
+ "@pi-unipi/web-api": "3.0.0-alpha.5",
105
+ "@pi-unipi/workflow": "3.0.0-alpha.5"
108
106
  },
109
107
  "devDependencies": {
110
108
  "@earendil-works/pi-agent-core": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.5",
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.4"
43
+ "@pi-unipi/core": "3.0.0-alpha.5"
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.4",
3
+ "version": "3.0.0-alpha.5",
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",
@@ -19,6 +19,7 @@ export const PACKAGE_ORDER: string[] = [
19
19
  "btw",
20
20
  "mcp",
21
21
  "utility",
22
+ "skill-registry",
22
23
  "ask-user",
23
24
  "info",
24
25
  "web-api",
@@ -41,8 +42,6 @@ export const COMMAND_REGISTRY: Record<string, string> = {
41
42
  "unipi:plan": "workflow",
42
43
  "unipi:permission": "workflow",
43
44
 
44
- "unipi:prefix-cache": "utility",
45
-
46
45
  // long-horizon (4 commands)
47
46
  "unipi:goal": "long-horizon",
48
47
  "unipi:ralph": "long-horizon",
@@ -68,13 +67,15 @@ export const COMMAND_REGISTRY: Record<string, string> = {
68
67
  // mcp (5 commands)
69
68
  "unipi:mcp-status": "mcp",
70
69
 
71
- // utility (11 commands)
70
+ // utility
72
71
  "unipi:continue": "utility",
73
- "unipi:reload": "utility",
74
- "unipi:status": "utility",
72
+ "unipi:retry": "utility",
75
73
  "unipi:cleanup": "utility",
76
- "unipi:env": "utility",
77
74
  "unipi:doctor": "utility",
75
+ "unipi:answer": "utility",
76
+
77
+ // skill-registry
78
+ "unipi:skills": "skill-registry",
78
79
 
79
80
  // ask-user (1 command)
80
81
 
@@ -134,7 +135,6 @@ export const COMMAND_REGISTRY: Record<string, string> = {
134
135
  export const COMMAND_DESCRIPTIONS: Record<string, string> = {
135
136
  "unipi:plan": "Plan mode — investigate read-only, then approve a plan",
136
137
  "unipi:permission": "Permission mode — ask · auto (jev-judged) · full",
137
- "unipi:prefix-cache": "Show privacy-safe provider prefix-cache diagnostics",
138
138
 
139
139
  "unipi:goal": "One objective until verifiably true · medium complexity · pareto cost/success",
140
140
  "unipi:ralph": "Checklist grind over iterations · enumerable chores · low cost, solid success",
@@ -156,12 +156,13 @@ export const COMMAND_DESCRIPTIONS: Record<string, string> = {
156
156
 
157
157
  "unipi:mcp-status": "Show MCP server status",
158
158
 
159
- "unipi:continue": "Continue the last conversation",
160
- "unipi:reload": "Reload extensions and settings",
161
- "unipi:status": "Show system status",
162
- "unipi:cleanup": "Clean up old sessions and cache",
163
- "unipi:env": "Show environment info",
164
- "unipi:doctor": "Run diagnostics",
159
+ "unipi:continue": "Continue from where the agent stopped (/unipi:retry)",
160
+ "unipi:retry": "Retry the last turn (alias of /unipi:continue)",
161
+ "unipi:cleanup": "Remove stale UniPi temp files and leftovers (preview first)",
162
+ "unipi:doctor": "Check UniPi's runtime: config, model cache, Decision Model, skills",
163
+ "unipi:answer": "Answer the last reply — fixed input under the scrollable reply, per question, or web form",
164
+ "unipi:skills": "Manage skills — on/off per project, listed or not, the skill vault",
165
+
165
166
  "unipi:kanboard": "Kanboard — capture tasks, run them, open the board",
166
167
  "unipi:kanboard-add": "Kanboard — capture a task into Backlog",
167
168
  "unipi:kanboard-do": "Kanboard — let the agent work the board for a turn",
@@ -207,6 +208,7 @@ export const PACKAGE_LABELS: Record<string, string> = {
207
208
  btw: "btw",
208
209
  mcp: "mcp",
209
210
  utility: "utility",
211
+ "skill-registry": "skills",
210
212
  "ask-user": "ask-user",
211
213
  info: "info",
212
214
  "web-api": "web-api",
@@ -16,6 +16,12 @@ export interface TaggedItem {
16
16
 
17
17
  // ─── 4-tier priority ─────────────────────────────────────────────────
18
18
 
19
+ /**
20
+ * UniPi commands that supersede a same-named pi built-in: typing the bare
21
+ * name ranks the unipi command FIRST (`/settings` → `/unipi:settings`).
22
+ */
23
+ export const PREFERRED_OVER_BUILTIN = new Set(["settings"]);
24
+
19
25
  /**
20
26
  * Compute the match-quality tier for an autocomplete item.
21
27
  *
@@ -38,6 +44,8 @@ export function crossItemPriority(
38
44
 
39
45
  const q = query.toLowerCase();
40
46
 
47
+ // Tier -1: a unipi command that supersedes the same-named built-in
48
+ if (isUnipi && short === q && PREFERRED_OVER_BUILTIN.has(q)) return -1;
41
49
  // Tier 0: exact full-value match
42
50
  if (full === q) return 0;
43
51
  // Tier 1: unipi short-name exact match
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.5",
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.4",
13
+ "@pi-unipi/core": "3.0.0-alpha.5",
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.4",
3
+ "version": "3.0.0-alpha.5",
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.4"
40
+ "@pi-unipi/core": "3.0.0-alpha.5"
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.4",
3
+ "version": "3.0.0-alpha.5",
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.4",
37
- "@pi-unipi/info-screen": "3.0.0-alpha.4",
36
+ "@pi-unipi/core": "3.0.0-alpha.5",
37
+ "@pi-unipi/info-screen": "3.0.0-alpha.5",
38
38
  "@earendil-works/pi-agent-core": "^0.87.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -37,8 +37,8 @@ export const MODULES = {
37
37
  FOOTER: "@pi-unipi/footer",
38
38
  UPDATER: "@pi-unipi/updater",
39
39
  INPUT_SHORTCUTS: "@pi-unipi/input-shortcuts",
40
- IMAGE: "@pi-unipi/image",
41
40
  WATCHDOG: "@pi-unipi/watchdog",
41
+ SKILL_REGISTRY: "@pi-unipi/skill-registry",
42
42
  } as const;
43
43
 
44
44
  /** Workflow command names — plan mode + permission modes */
@@ -104,30 +104,10 @@ export const RALPH_STATUS_ICONS = {
104
104
  /** Utility command names */
105
105
  export const UTILITY_COMMANDS = {
106
106
  CONTINUE: "continue",
107
- RELOAD: "reload",
108
- STATUS: "status",
107
+ RETRY: "retry",
109
108
  CLEANUP: "cleanup",
110
- ENV: "env",
111
109
  DOCTOR: "doctor",
112
- PREFIX_CACHE: "prefix-cache",
113
- } as const;
114
-
115
- /** Utility tool names */
116
- export const UTILITY_TOOLS = {
117
- CONTINUE: "continue_task",
118
- BATCH: "ctx_batch",
119
- ENV: "ctx_env",
120
- SET_SESSION_NAME: "set_session_name",
121
- } as const;
122
-
123
- /** Badge config path */
124
- export const BADGE_CONFIG_FILE = ".unipi/config/badge.json" as const;
125
-
126
- /** Utility directory paths */
127
- export const UTILITY_DIRS = {
128
- CACHE: "~/.unipi/cache",
129
- ANALYTICS: "~/.unipi/analytics",
130
- TEMP: "/tmp/unipi",
110
+ ANSWER: "answer",
131
111
  } as const;
132
112
 
133
113
  /** Ask-user tool names */
@@ -138,6 +118,7 @@ export const ASK_USER_TOOLS = {
138
118
  /** Image tool names */
139
119
  export const IMAGE_TOOLS = {
140
120
  GENERATE: "image_generate",
121
+ EDIT: "image_edit",
141
122
  RECOGNIZE: "image_recognize",
142
123
  } as const;
143
124
 
@@ -35,8 +35,6 @@ export const UNIPI_EVENTS = {
35
35
  LONG_HORIZON_TODO_UPDATED: "unipi:long-horizon:todo:updated",
36
36
 
37
37
 
38
- /** Info screen group registered */
39
- INFO_GROUP_REGISTERED: "unipi:info:group:registered",
40
38
  /** Info screen data updated */
41
39
  INFO_DATA_UPDATED: "unipi:info:data:updated",
42
40
 
@@ -64,19 +62,10 @@ export const UNIPI_EVENTS = {
64
62
  COMPACTOR_COMPACTED: "unipi:compactor:compacted",
65
63
  /** @deprecated Footer reads live Pi session data; retained for compatibility. */
66
64
  COMPACTOR_STATS_UPDATED: "unipi:compactor:stats:updated",
67
- /** Utility module cleanup completed */
68
- UTILITY_CLEANUP_DONE: "unipi:utility:cleanup:done",
69
- /** Utility diagnostics started */
70
- UTILITY_DIAGNOSTICS_START: "unipi:utility:diagnostics:start",
71
- /** Utility diagnostics completed */
72
- UTILITY_DIAGNOSTICS_DONE: "unipi:utility:diagnostics:done",
73
65
 
74
66
  /** Notification sent */
75
67
  NOTIFICATION_SENT: "unipi:notify:sent",
76
68
 
77
- /** Badge generation requested (from kanboard or other module) */
78
- BADGE_GENERATE_REQUEST: "unipi:badge:generate:request",
79
-
80
69
  /** Agent asked user a question (ask_user tool invoked) */
81
70
  ASK_USER_PROMPT: "unipi:ask-user:prompt",
82
71
 
@@ -178,15 +167,6 @@ export interface UnipiMemoryConsolidatedEvent {
178
167
  projectName: string;
179
168
  }
180
169
 
181
- /** Payload for INFO_GROUP_REGISTERED */
182
- export interface UnipiInfoGroupEvent {
183
- /** Group id */
184
- groupId: string;
185
- /** Group display name */
186
- groupName: string;
187
- /** Module that registered the group */
188
- module: string;
189
- }
190
170
 
191
171
  /** Payload for INFO_DATA_UPDATED */
192
172
  export interface UnipiInfoDataEvent {
@@ -252,38 +232,9 @@ export interface UnipiCompactorStatsEvent {
252
232
  searchQueries: number;
253
233
  }
254
234
 
255
- /** Payload for UTILITY_CLEANUP_START / UTILITY_CLEANUP_DONE */
256
- export interface UnipiUtilityCleanupEvent {
257
- /** Whether this is a dry run */
258
- dryRun: boolean;
259
- /** Categories being cleaned */
260
- categories: string[];
261
- /** Results (present on DONE) */
262
- results?: Array<{
263
- category: string;
264
- removed: number;
265
- bytesFreed: number;
266
- }>;
267
- }
268
235
 
269
- /** Payload for UTILITY_DIAGNOSTICS_START / UTILITY_DIAGNOSTICS_DONE */
270
- export interface UnipiUtilityDiagnosticsEvent {
271
- /** Overall health status */
272
- overall: "healthy" | "warning" | "error" | "unknown";
273
- /** Number of checks run */
274
- checkCount: number;
275
- /** Report (present on DONE) */
276
- report?: unknown;
277
- }
278
236
 
279
237
 
280
- /** Payload for BADGE_GENERATE_REQUEST */
281
- export interface UnipiBadgeGenerateRequestEvent {
282
- /** Source of the request (e.g., "kanboard", "input-hook") */
283
- source: string;
284
- /** First user message for context (optional) */
285
- conversationSummary?: string;
286
- }
287
238
 
288
239
  /** Payload for ASK_USER_PROMPT */
289
240
  export interface UnipiAskUserPromptEvent {
@@ -361,17 +312,13 @@ export type UnipiEventPayload =
361
312
  | UnipiMemoryStoredEvent
362
313
  | UnipiMemoryDeletedEvent
363
314
  | UnipiMemoryConsolidatedEvent
364
- | UnipiInfoGroupEvent
365
315
  | UnipiInfoDataEvent
366
316
  | UnipiMcpServerEvent
367
317
  | UnipiMcpToolsEvent
368
318
  | UnipiMcpCatalogSyncedEvent
369
319
  | UnipiCompactionEvent
370
320
  | UnipiCompactorStatsEvent
371
- | UnipiUtilityCleanupEvent
372
- | UnipiUtilityDiagnosticsEvent
373
321
  | UnipiNotificationSentEvent
374
- | UnipiBadgeGenerateRequestEvent
375
322
  | UnipiAskUserPromptEvent
376
323
  | UnipiUpdateCheckEvent
377
324
  | UnipiUpdateAvailableEvent
@@ -26,12 +26,14 @@ export * from "./src/tui/kit.js";
26
26
  export * from "./src/tui/progress.js";
27
27
  export * from "./src/jev/client.js";
28
28
  export * from "./src/jev/settings.js";
29
+ export * from "./src/jev/chatter.js";
29
30
  export * from "./src/workspace/state-migration.js";
30
31
  // v3 settings engine + migration (canonical ~/.unipi/config layout)
31
32
  export * from "./src/settings/paths.js";
32
33
  export * from "./src/settings/engine.js";
33
34
  export * from "./src/settings/schema.js";
34
35
  export { SettingsHub, type SettingsHubDeps } from "./src/settings/hub.js";
36
+ export { openSettingsHub } from "./src/settings/open.js";
35
37
  export * from "./src/settings/catalog.js";
36
38
  export * from "./command-runner.js";
37
39
  export * from "./compaction-context.js";
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * @pi-unipi/core — Model Cache
3
3
  *
4
- * File-based model list cache at ~/.unipi/config/models-cache.json.
5
- * Allows TUI components and other packages to list available models
6
- * without needing ctx.modelRegistry at runtime.
4
+ * File-based model list cache at ~/.unipi/config/models-cache.json — the one
5
+ * catalog every unipi surface reads (settings hub pickers, kanboard, image
6
+ * pickers) without needing ctx.modelRegistry. Written by utility on every
7
+ * session start from pi's live registry (models with credentials), plus the
8
+ * image-generation models pi-ai publishes separately.
7
9
  */
8
10
 
9
11
  import * as fs from "node:fs";
@@ -18,10 +20,12 @@ function cacheDir(): string {
18
20
  }
19
21
 
20
22
  /** Resolve the model cache file path at call time. */
21
- function cacheFile(): string {
23
+ export function modelCachePath(): string {
22
24
  return path.join(cacheDir(), "models-cache.json");
23
25
  }
24
26
 
27
+ export type Modality = "text" | "image";
28
+
25
29
  /** A single cached model entry */
26
30
  export interface CachedModel {
27
31
  /** Model provider (e.g. "openai", "anthropic") */
@@ -30,6 +34,14 @@ export interface CachedModel {
30
34
  id: string;
31
35
  /** Optional display name */
32
36
  name?: string;
37
+ /** Input modalities. Absent in pre-v3 caches → treat as ["text"]. */
38
+ input?: Modality[];
39
+ /** Output modalities. Chat models → ["text"]; image generators include "image". */
40
+ output?: Modality[];
41
+ /** "chat" = pi chat registry, "images" = pi-ai images collection / generator. */
42
+ kind?: "chat" | "images";
43
+ contextWindow?: number;
44
+ reasoning?: boolean;
33
45
  }
34
46
 
35
47
  /** The full model cache structure */
@@ -40,13 +52,64 @@ export interface ModelCache {
40
52
  models: CachedModel[];
41
53
  }
42
54
 
55
+ /** `provider/id` reference for a cached model. */
56
+ export function modelRef(model: Pick<CachedModel, "provider" | "id">): string {
57
+ return `${model.provider}/${model.id}`;
58
+ }
59
+
60
+ export function modelInputs(model: CachedModel): Modality[] {
61
+ return model.input?.length ? model.input : ["text"];
62
+ }
63
+
64
+ export function modelOutputs(model: CachedModel): Modality[] {
65
+ return model.output?.length ? model.output : ["text"];
66
+ }
67
+
68
+ /**
69
+ * Filter by modality. `input` lists what the model must accept (all of them),
70
+ * `output` what it must produce (any of them).
71
+ */
72
+ export function filterModels(
73
+ models: readonly CachedModel[],
74
+ want: { input?: Modality[]; output?: Modality[] },
75
+ ): CachedModel[] {
76
+ return models.filter((m) => {
77
+ const inputs = modelInputs(m);
78
+ const outputs = modelOutputs(m);
79
+ if (want.input && !want.input.every((i) => inputs.includes(i))) return false;
80
+ if (want.output && !want.output.some((o) => outputs.includes(o))) return false;
81
+ return true;
82
+ });
83
+ }
84
+
85
+ /** Map pi registry models (chat) to cache entries. Tolerates partial shapes. */
86
+ export function chatModelsToCache(models: readonly unknown[]): CachedModel[] {
87
+ const out: CachedModel[] = [];
88
+ for (const raw of models) {
89
+ const m = raw as { provider?: unknown; id?: unknown; name?: unknown; input?: unknown; contextWindow?: unknown; reasoning?: unknown };
90
+ if (typeof m?.provider !== "string" || typeof m?.id !== "string") continue;
91
+ const input = Array.isArray(m.input) ? (m.input.filter((x) => x === "text" || x === "image") as Modality[]) : ["text" as const];
92
+ out.push({
93
+ provider: m.provider,
94
+ id: m.id,
95
+ ...(typeof m.name === "string" ? { name: m.name } : {}),
96
+ input: input.length ? input : ["text"],
97
+ output: ["text"],
98
+ kind: "chat",
99
+ ...(typeof m.contextWindow === "number" ? { contextWindow: m.contextWindow } : {}),
100
+ ...(typeof m.reasoning === "boolean" ? { reasoning: m.reasoning } : {}),
101
+ });
102
+ }
103
+ return out;
104
+ }
105
+
43
106
  /**
44
107
  * Read cached model list from disk.
45
108
  * Returns empty array if no cache file exists or it's malformed.
46
109
  */
47
110
  export function readModelCache(): CachedModel[] {
48
111
  try {
49
- const file = cacheFile();
112
+ const file = modelCachePath();
50
113
  if (!fs.existsSync(file)) return [];
51
114
  const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
52
115
  return Array.isArray(parsed.models) ? parsed.models : [];
@@ -58,6 +121,8 @@ export function readModelCache(): CachedModel[] {
58
121
  /**
59
122
  * Write model list to cache file.
60
123
  * Creates directory if needed. Best effort — silently ignores errors.
124
+ * Entries are de-duplicated by `provider/id` (first wins) and sorted, so the
125
+ * file is stable across sessions with the same catalog.
61
126
  */
62
127
  export function writeModelCache(models: CachedModel[]): void {
63
128
  try {
@@ -65,11 +130,20 @@ export function writeModelCache(models: CachedModel[]): void {
65
130
  if (!fs.existsSync(dir)) {
66
131
  fs.mkdirSync(dir, { recursive: true });
67
132
  }
133
+ const seen = new Set<string>();
134
+ const unique = models.filter((m) => {
135
+ const key = modelRef(m);
136
+ if (seen.has(key)) return false;
137
+ seen.add(key);
138
+ return true;
139
+ }).sort((a, b) => modelRef(a).localeCompare(modelRef(b)));
68
140
  const cache: ModelCache = {
69
141
  updatedAt: new Date().toISOString(),
70
- models,
142
+ models: unique,
71
143
  };
72
- fs.writeFileSync(cacheFile(), JSON.stringify(cache, null, 2) + "\n", "utf-8");
144
+ const tmp = `${modelCachePath()}.${process.pid}.tmp`;
145
+ fs.writeFileSync(tmp, JSON.stringify(cache, null, 2) + "\n", "utf-8");
146
+ fs.renameSync(tmp, modelCachePath());
73
147
  } catch {
74
148
  // Best effort — cache is optional
75
149
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.4",
3
+ "version": "3.0.0-alpha.5",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -41,6 +41,6 @@
41
41
  "themes": []
42
42
  },
43
43
  "scripts": {
44
- "test": "npx tsx --test *.test.ts"
44
+ "test": "npx tsx --test *.test.ts src/*/__tests__/*.test.ts"
45
45
  }
46
46
  }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Chatter detection shared by jev consumers (auto-rename, skill judging):
3
+ * greetings, thanks, acknowledgements and bare "continue"-style nudges carry
4
+ * no subject, so no decision should be made from them.
5
+ */
6
+
7
+ const CHATTER = new RegExp(
8
+ "^(?:" + [
9
+ "h(?:i|ello|ey|owdy)(?: there)?", "yo", "sup", "good (?:morning|afternoon|evening)",
10
+ "thanks?(?: you)?(?: so much)?", "thx", "ty", "cheers", "nice", "great", "cool", "perfect", "awesome",
11
+ "ok(?:ay)?", "k", "yes", "yep", "yeah", "no", "nope", "sure", "lgtm", "sounds good", "go(?: on| ahead)?",
12
+ "continue", "proceed", "retry", "again", "do it", "next", "done",
13
+ ].join("|") + ")[\\s.!?,]*$",
14
+ "i",
15
+ );
16
+
17
+ /** True for messages with no subject of their own (no jev call needed). */
18
+ export function isChatter(text: string): boolean {
19
+ const t = text.trim();
20
+ if (t.length < 3) return true;
21
+ if (t.startsWith("/")) return true;
22
+ return CHATTER.test(t);
23
+ }
24
+