@pi-unipi/unipi 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [2.14.0] — 2026-08-29
10
+
11
+ ### Changed
12
+
13
+ - `image`: **`image_recognize` is now hidden while the session model has vision** — a model that accepts image input reads images natively through pi's own read tool, so the separate recognition round-trip only duplicated the ability and burned system-prompt context. The extension drops the tool from the active set on `session_start` and `model_select` when the session model declares image input (`input` includes `"image"`), and restores it automatically when a text-only model takes over mid-session. Models that do not declare input modalities are treated as non-vision and keep the tool, matching the package's existing convention. The info screen shows "Hidden (model has vision)" for the Recognize stat while suppressed.
14
+
9
15
  ## [2.13.0] — 2026-08-28
10
16
 
11
17
  ### Added
package/README.md CHANGED
@@ -110,7 +110,7 @@ Coexists triggers enhance behavior when packages are installed together. Workflo
110
110
  | `spawn_helper` / `get_helper_result` | subagents | Parallel agents |
111
111
  | `memory_store` / `memory_search` / `memory_delete` | memory | Memory CRUD |
112
112
  | `web_search` / `multi_web_content_read` / `web_llm_summarize` | web-api | Web research |
113
- | `image_generate` / `image_recognize` | image | Image generation and vision |
113
+ | `image_generate` / `image_recognize` | image | Image generation and vision (recognize auto-hides while the session model has vision) |
114
114
  | `notify_user` | notify | Push notifications |
115
115
  | `ask_user` | ask-user | User input |
116
116
  | `compact` / `session_recall` / `sandbox` | compactor | Context management |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -82,28 +82,28 @@
82
82
  "typebox": "^1.1.38"
83
83
  },
84
84
  "dependencies": {
85
- "@pi-unipi/ask-user": "2.12.0",
86
- "@pi-unipi/background-tasks": "2.13.0",
87
- "@pi-unipi/btw": "2.12.0",
88
- "@pi-unipi/command-enchantment": "2.12.0",
89
- "@pi-unipi/compactor": "2.12.0",
90
- "@pi-unipi/core": "2.12.0",
91
- "@pi-unipi/footer": "2.13.0",
92
- "@pi-unipi/image": "2.12.0",
93
- "@pi-unipi/info-screen": "2.12.0",
94
- "@pi-unipi/input-shortcuts": "2.12.0",
95
- "@pi-unipi/kanboard": "2.12.0",
96
- "@pi-unipi/mcp": "2.12.0",
97
- "@pi-unipi/memory": "2.12.0",
98
- "@pi-unipi/milestone": "2.12.0",
99
- "@pi-unipi/notify": "2.12.0",
100
- "@pi-unipi/ralph": "2.12.0",
101
- "@pi-unipi/subagents": "2.12.0",
102
- "@pi-unipi/trajectory": "2.12.0",
103
- "@pi-unipi/updater": "2.12.0",
104
- "@pi-unipi/utility": "2.12.0",
105
- "@pi-unipi/web-api": "2.12.0",
106
- "@pi-unipi/workflow": "2.12.0"
85
+ "@pi-unipi/ask-user": "2.14.0",
86
+ "@pi-unipi/background-tasks": "2.14.0",
87
+ "@pi-unipi/btw": "2.14.0",
88
+ "@pi-unipi/command-enchantment": "2.14.0",
89
+ "@pi-unipi/compactor": "2.14.0",
90
+ "@pi-unipi/core": "2.14.0",
91
+ "@pi-unipi/footer": "2.14.0",
92
+ "@pi-unipi/image": "2.14.0",
93
+ "@pi-unipi/info-screen": "2.14.0",
94
+ "@pi-unipi/input-shortcuts": "2.14.0",
95
+ "@pi-unipi/kanboard": "2.14.0",
96
+ "@pi-unipi/mcp": "2.14.0",
97
+ "@pi-unipi/memory": "2.14.0",
98
+ "@pi-unipi/milestone": "2.14.0",
99
+ "@pi-unipi/notify": "2.14.0",
100
+ "@pi-unipi/ralph": "2.14.0",
101
+ "@pi-unipi/subagents": "2.14.0",
102
+ "@pi-unipi/trajectory": "2.14.0",
103
+ "@pi-unipi/updater": "2.14.0",
104
+ "@pi-unipi/utility": "2.14.0",
105
+ "@pi-unipi/web-api": "2.14.0",
106
+ "@pi-unipi/workflow": "2.14.0"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@earendil-works/pi-agent-core": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
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": "2.12.0"
43
+ "@pi-unipi/core": "2.14.0"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
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",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Background tasks for UniPi — durable shell jobs, delegated agents, attested Pi runs, and fixed-purpose Fusion workflows",
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.84.0",
13
- "@pi-unipi/core": "2.12.0",
13
+ "@pi-unipi/core": "2.14.0",
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": "2.12.0",
3
+ "version": "2.14.0",
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.84.0"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "2.12.0"
40
+ "@pi-unipi/core": "2.14.0"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Context engine for Pi — zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,8 +34,8 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.12.0",
38
- "@pi-unipi/info-screen": "2.12.0",
37
+ "@pi-unipi/core": "2.14.0",
38
+ "@pi-unipi/info-screen": "2.14.0",
39
39
  "@earendil-works/pi-agent-core": "^0.84.0"
40
40
  },
41
41
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "2.13.0",
3
+ "version": "2.14.0",
4
4
  "description": "Persistent status bar for Unipi — subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "2.12.0",
36
- "@pi-unipi/background-tasks": "2.13.0"
35
+ "@pi-unipi/core": "2.14.0",
36
+ "@pi-unipi/background-tasks": "2.14.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -7,7 +7,7 @@ Image generation and image recognition tools for the agent.
7
7
  | Tool | Description |
8
8
  |------|-------------|
9
9
  | `image_generate` | Generate an image from a text prompt. Returned inline and saved to disk. |
10
- | `image_recognize` | Analyze an image with a vision model. Accepts a file path, `data:` URL, or base64. |
10
+ | `image_recognize` | Analyze an image with a vision model. Accepts a file path, `data:` URL, or base64. Automatically hidden while the session model itself has vision. |
11
11
 
12
12
  ## Commands
13
13
 
@@ -63,6 +63,13 @@ works. Supported: PNG, JPEG, GIF, WebP. Remote URLs are not fetched.
63
63
  Prefer file paths — inlining base64 into the conversation is far more
64
64
  expensive in tokens.
65
65
 
66
+ **Vision models don't get this tool.** When the session's current model already
67
+ accepts image input, `image_recognize` is dropped from the active tool set —
68
+ the model reads images natively through pi's own tools, so a separate
69
+ recognition round-trip through another model would only duplicate that ability
70
+ and burn context. Switch to a text-only model (via `/model`) and the tool comes
71
+ back automatically.
72
+
66
73
  ## Configuration
67
74
 
68
75
  `~/.unipi/config/image/config.json`:
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Image generation and image recognition tools for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.12.0"
37
+ "@pi-unipi/core": "2.14.0"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.84.0",
@@ -20,7 +20,13 @@ import {
20
20
 
21
21
  import { registerImageCommands } from "./commands.js";
22
22
  import { registerImageTools } from "./tools.js";
23
- import { listImageGenModels, listVisionModels, type ChatModelRegistry } from "./models.js";
23
+ import {
24
+ applyRecognizeGating,
25
+ isVisionModel,
26
+ listImageGenModels,
27
+ listVisionModels,
28
+ type ChatModelRegistry,
29
+ } from "./models.js";
24
30
  import { registerRegistryImageProviders } from "./register-providers.js";
25
31
  import { loadConfig } from "./settings.js";
26
32
 
@@ -37,10 +43,27 @@ function getInfoRegistry() {
37
43
  ).__unipi_info_registry;
38
44
  }
39
45
 
46
+ /**
47
+ * Hide image_recognize when the session model can natively see images, and
48
+ * restore it when a text-only model takes over. Returns whether the tool is
49
+ * provided after gating.
50
+ */
51
+ function applyVisionGating(pi: ExtensionAPI, model: unknown): boolean {
52
+ const active = pi.getActiveTools();
53
+ const next = applyRecognizeGating(active, model, IMAGE_TOOLS.RECOGNIZE);
54
+ if (next !== active) pi.setActiveTools(next);
55
+ return next.includes(IMAGE_TOOLS.RECOGNIZE);
56
+ }
57
+
40
58
  export default function (pi: ExtensionAPI) {
41
59
  registerImageTools(pi);
42
60
  registerImageCommands(pi);
43
61
 
62
+ pi.on("model_select", (event) => {
63
+ if (!loadConfig().recognize.enabled) return;
64
+ applyVisionGating(pi, event.model);
65
+ });
66
+
44
67
  pi.on("session_start", async (_event, ctx) => {
45
68
  const config = loadConfig();
46
69
 
@@ -51,9 +74,16 @@ export default function (pi: ExtensionAPI) {
51
74
  (ctx as unknown as { modelRegistry?: ChatModelRegistry }).modelRegistry,
52
75
  ).catch(() => undefined);
53
76
 
77
+ // A vision-capable session model reads images itself, so image_recognize
78
+ // would only duplicate that ability. Drop it from the active tool set;
79
+ // the model_select handler above restores it when a text-only model is
80
+ // chosen later in the same session.
81
+ const recognizeProvided =
82
+ config.recognize.enabled && applyVisionGating(pi, ctx.model);
83
+
54
84
  const tools: string[] = [];
55
85
  if (config.generate.enabled) tools.push(IMAGE_TOOLS.GENERATE);
56
- if (config.recognize.enabled) tools.push(IMAGE_TOOLS.RECOGNIZE);
86
+ if (recognizeProvided) tools.push(IMAGE_TOOLS.RECOGNIZE);
57
87
 
58
88
  emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
59
89
  name: MODULES.IMAGE,
@@ -92,9 +122,11 @@ export default function (pi: ExtensionAPI) {
92
122
  .modelRegistry;
93
123
  const vision = chatRegistry ? listVisionModels(chatRegistry) : [];
94
124
 
95
- const recognize = current.recognize.enabled
96
- ? current.recognize.model || "Session model"
97
- : "Disabled";
125
+ const recognize = !current.recognize.enabled
126
+ ? "Disabled"
127
+ : isVisionModel(ctx.model)
128
+ ? "Hidden (model has vision)"
129
+ : current.recognize.model || "Session model";
98
130
 
99
131
  return {
100
132
  generate: { value: generate },
@@ -383,7 +383,7 @@ export function listVisionModels(registry: ChatModelRegistry): VisionModel[] {
383
383
  return models.filter(isVisionModel);
384
384
  }
385
385
 
386
- function isVisionModel(model: unknown): model is VisionModel {
386
+ export function isVisionModel(model: unknown): model is VisionModel {
387
387
  if (model === null || typeof model !== "object") return false;
388
388
  const candidate = model as Partial<VisionModel>;
389
389
  if (typeof candidate.id !== "string" || typeof candidate.provider !== "string") {
@@ -392,6 +392,29 @@ function isVisionModel(model: unknown): model is VisionModel {
392
392
  return Array.isArray(candidate.input) && candidate.input.includes("image");
393
393
  }
394
394
 
395
+ /**
396
+ * Active tool names after hiding `recognizeTool` for a vision-capable model.
397
+ *
398
+ * A model that accepts image input can read images natively (pi's own read
399
+ * tool hands it the pixels), so a separate image_recognize round-trip through
400
+ * another model only duplicates the ability and burns system-prompt context.
401
+ * Text-only models get the tool back. Models that do not declare their input
402
+ * modalities are treated as non-vision, matching `isVisionModel`.
403
+ */
404
+ export function applyRecognizeGating(
405
+ active: string[],
406
+ model: unknown,
407
+ recognizeTool: string,
408
+ ): string[] {
409
+ const vision = isVisionModel(model);
410
+ const present = active.includes(recognizeTool);
411
+ // Already correct: hidden for a vision model, or provided for a text-only one.
412
+ if (vision !== present) return active;
413
+ return vision
414
+ ? active.filter((name) => name !== recognizeTool)
415
+ : [...active, recognizeTool];
416
+ }
417
+
395
418
  /**
396
419
  * Resolve a vision-model reference, restricted to image-capable models.
397
420
  *
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.12.0"
36
+ "@pi-unipi/core": "2.14.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/input-shortcuts",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle — chord-based overlay system",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,7 +33,7 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.12.0"
36
+ "@pi-unipi/core": "2.14.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/kanboard",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Visualization layer for unipi workflow — HTTP server with htmx/Alpine.js UI, modular parsers, TUI overlay, and kanban board",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@pi-unipi/core": "2.12.0"
42
+ "@pi-unipi/core": "2.14.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/mcp",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "MCP server management extension for Pi coding agent — browse, add, configure, and use MCP servers",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -30,7 +30,7 @@
30
30
  "test": "npx tsx --test tests/**/*.test.ts"
31
31
  },
32
32
  "dependencies": {
33
- "@pi-unipi/core": "2.12.0"
33
+ "@pi-unipi/core": "2.14.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/memory",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Persistent cross-session memory with MemPalace backend (auto-installed) and SQLite fallback for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -39,8 +39,8 @@
39
39
  "README.md"
40
40
  ],
41
41
  "dependencies": {
42
- "@pi-unipi/core": "2.12.0",
43
- "@pi-unipi/info-screen": "2.12.0",
42
+ "@pi-unipi/core": "2.14.0",
43
+ "@pi-unipi/info-screen": "2.14.0",
44
44
  "js-yaml": "^4.1.0"
45
45
  },
46
46
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/milestone",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Lifecycle layer for project-level goals — MILESTONES.md tracking, session hooks, auto-sync",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "2.12.0"
35
+ "@pi-unipi/core": "2.14.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-coding-agent": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Cross-platform notification extension for Pi — native OS, Gotify, and Telegram notifications for agent lifecycle events",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "2.12.0",
37
+ "@pi-unipi/core": "2.14.0",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ralph",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Long-running iterative development loops for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -31,8 +31,8 @@
31
31
  "test": "npx tsx --test reminder.test.ts"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "2.12.0",
35
- "@pi-unipi/info-screen": "2.12.0"
34
+ "@pi-unipi/core": "2.14.0",
35
+ "@pi-unipi/info-screen": "2.14.0"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-ai": "^0.84.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -9,7 +9,7 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "2.12.0",
12
+ "@pi-unipi/core": "2.14.0",
13
13
  "@earendil-works/pi-agent-core": "^0.84.0",
14
14
  "acorn": "8.18.0"
15
15
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/trajectory",
3
- "version": "2.12.0",
3
+ "version": "2.14.0",
4
4
  "description": "Live UniPi trajectory inspector for Pi sessions",
5
5
  "type": "module",
6
6
  "main": "index.ts",