@pi-unipi/unipi 2.12.2 → 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 +17 -0
- package/README.md +1 -1
- package/package.json +23 -23
- package/packages/ask-user/package.json +2 -2
- package/packages/autocomplete/package.json +1 -1
- package/packages/background-tasks/README.md +16 -0
- package/packages/background-tasks/package.json +2 -2
- package/packages/background-tasks/src/index.ts +8 -0
- package/packages/background-tasks/src/registry-shared.ts +32 -0
- package/packages/btw/package.json +2 -2
- package/packages/compactor/package.json +3 -3
- package/packages/core/package.json +1 -1
- package/packages/footer/README.md +1 -0
- package/packages/footer/package.json +3 -2
- package/packages/footer/src/index.ts +7 -5
- package/packages/footer/src/process-line.ts +74 -0
- package/packages/image/README.md +8 -1
- package/packages/image/package.json +2 -2
- package/packages/image/src/index.ts +37 -5
- package/packages/image/src/models.ts +24 -1
- package/packages/info-screen/package.json +2 -2
- package/packages/input-shortcuts/package.json +2 -2
- package/packages/kanboard/package.json +2 -2
- package/packages/mcp/package.json +2 -2
- package/packages/memory/package.json +3 -3
- package/packages/milestone/package.json +2 -2
- package/packages/notify/package.json +2 -2
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +2 -2
- package/packages/trajectory/package.json +1 -1
- package/packages/unipi/bundled.js +4009 -1336
- package/packages/updater/package.json +2 -2
- package/packages/utility/package.json +2 -2
- package/packages/web-api/package.json +2 -2
- package/packages/workflow/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ 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
|
+
|
|
15
|
+
## [2.13.0] — 2026-08-28
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `footer`: **bg-process one-liner above the glance footer** — a centered strip directly above the frame showing live background-task counts with status dots: green ● running, yellow ● stopped (killed), red ● failed, gray ● done. Zero-count buckets are omitted and the line hides entirely when idle. Reads `BackgroundTaskRegistry.allTasks()` directly (no events, no polling channels) and re-renders on the footer's 1s refresh timer; counts reset per session.
|
|
20
|
+
- `background-tasks`: **shared registry accessor** (`getSharedTaskRegistry` / `setSharedTaskRegistry` / `clearSharedTaskRegistry` in `src/registry-shared.ts`) — lets sibling extensions read the live task registry synchronously. Stored on `globalThis` under a `Symbol.for` key so the singleton survives duplicate module instances; published at extension init and `session_start`, cleared on `session_shutdown`.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- `footer`: new dependency on `@pi-unipi/background-tasks` for the process one-liner's direct registry reads.
|
|
25
|
+
|
|
9
26
|
## [2.12.0] — 2026-08-27
|
|
10
27
|
|
|
11
28
|
### 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.
|
|
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.
|
|
86
|
-
"@pi-unipi/background-tasks": "2.
|
|
87
|
-
"@pi-unipi/btw": "2.
|
|
88
|
-
"@pi-unipi/command-enchantment": "2.
|
|
89
|
-
"@pi-unipi/compactor": "2.
|
|
90
|
-
"@pi-unipi/core": "2.
|
|
91
|
-
"@pi-unipi/footer": "2.
|
|
92
|
-
"@pi-unipi/image": "2.
|
|
93
|
-
"@pi-unipi/info-screen": "2.
|
|
94
|
-
"@pi-unipi/input-shortcuts": "2.
|
|
95
|
-
"@pi-unipi/kanboard": "2.
|
|
96
|
-
"@pi-unipi/mcp": "2.
|
|
97
|
-
"@pi-unipi/memory": "2.
|
|
98
|
-
"@pi-unipi/milestone": "2.
|
|
99
|
-
"@pi-unipi/notify": "2.
|
|
100
|
-
"@pi-unipi/ralph": "2.
|
|
101
|
-
"@pi-unipi/subagents": "2.
|
|
102
|
-
"@pi-unipi/trajectory": "2.
|
|
103
|
-
"@pi-unipi/updater": "2.
|
|
104
|
-
"@pi-unipi/utility": "2.
|
|
105
|
-
"@pi-unipi/web-api": "2.
|
|
106
|
-
"@pi-unipi/workflow": "2.
|
|
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.
|
|
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.
|
|
43
|
+
"@pi-unipi/core": "2.14.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -74,6 +74,22 @@ subscription OAuth attribution, exact-match system-prompt sanitization, and
|
|
|
74
74
|
cache-retention policy for Anthropic routes. Duplicate installed copies resolve
|
|
75
75
|
ownership through an EventBus claim; later copies go inert.
|
|
76
76
|
|
|
77
|
+
## Shared registry (for sibling extensions)
|
|
78
|
+
|
|
79
|
+
Other packages can read the live task registry synchronously — no events, no polling:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { getSharedTaskRegistry } from "@pi-unipi/background-tasks";
|
|
83
|
+
|
|
84
|
+
const tasks = getSharedTaskRegistry()?.allTasks() ?? [];
|
|
85
|
+
const running = tasks.filter((t) => t.status === "running").length;
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
The registry is published on `globalThis` under a `Symbol.for` key at extension
|
|
89
|
+
init and `session_start`, and cleared on `session_shutdown` (counts are
|
|
90
|
+
per-session). Returns `undefined` when the module is disabled — callers must
|
|
91
|
+
treat that as "no data", e.g. the footer's glance process line does.
|
|
92
|
+
|
|
77
93
|
## Differences from the reference
|
|
78
94
|
|
|
79
95
|
- Commands live in the `/unipi:*` namespace; env prefix is `UNIPI_BG_*`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/background-tasks",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
13
|
+
"@pi-unipi/core": "2.14.0",
|
|
14
14
|
"turndown": "^7.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -19,10 +19,14 @@ import {
|
|
|
19
19
|
import { registerToolsAndCommands } from "./tools.js";
|
|
20
20
|
import { registerFusionExtension } from "./fusion-extension.js";
|
|
21
21
|
import { registerDelegateExtension } from "./delegate-extension.js";
|
|
22
|
+
import { setSharedTaskRegistry, clearSharedTaskRegistry } from "./registry-shared.js";
|
|
22
23
|
import { taskDisplayName, type BgTask, type StartAttestedPiTaskOptions, type StartTaskOptions } from "./types.js";
|
|
23
24
|
|
|
24
25
|
const STATUS_INTERVAL_MS = 1000;
|
|
25
26
|
|
|
27
|
+
// Direct synchronous access for sibling extensions (footer process one-liner).
|
|
28
|
+
export { getSharedTaskRegistry } from "./registry-shared.js";
|
|
29
|
+
|
|
26
30
|
export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
27
31
|
const { config, warnings } = loadBackgroundTasksConfig(process.cwd());
|
|
28
32
|
|
|
@@ -53,6 +57,8 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
53
57
|
eventService.publishTerminal(task);
|
|
54
58
|
},
|
|
55
59
|
});
|
|
60
|
+
setSharedTaskRegistry(registry);
|
|
61
|
+
|
|
56
62
|
const eventService: BackgroundTaskExtensionService = installBackgroundTaskExtensionApi({
|
|
57
63
|
events: pi.events,
|
|
58
64
|
registry,
|
|
@@ -253,6 +259,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
253
259
|
|
|
254
260
|
pi.on("session_start", async (_event, ctx) => {
|
|
255
261
|
registry.setShuttingDown(false);
|
|
262
|
+
setSharedTaskRegistry(registry);
|
|
256
263
|
currentCtx = ctx;
|
|
257
264
|
await registry.ensureRuntimeDir(ctx);
|
|
258
265
|
updateUi(ctx);
|
|
@@ -264,6 +271,7 @@ export default function backgroundTasksExtension(pi: ExtensionAPI): void {
|
|
|
264
271
|
|
|
265
272
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
266
273
|
registry.setShuttingDown(true);
|
|
274
|
+
clearSharedTaskRegistry();
|
|
267
275
|
currentCtx = undefined;
|
|
268
276
|
if (statusInterval) {
|
|
269
277
|
clearInterval(statusInterval);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared BackgroundTaskRegistry accessor
|
|
3
|
+
*
|
|
4
|
+
* Exposes the live registry to sibling extensions (e.g. @pi-unipi/footer)
|
|
5
|
+
* without events or request/response channels — direct synchronous reads of
|
|
6
|
+
* `allTasks()`.
|
|
7
|
+
*
|
|
8
|
+
* Stored on globalThis under a `Symbol.for` key so the singleton is shared
|
|
9
|
+
* even if this package ends up instantiated more than once (duplicate
|
|
10
|
+
* node_modules copies would otherwise each hold their own module state).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { BackgroundTaskRegistry } from "./registry.js";
|
|
14
|
+
|
|
15
|
+
const SHARED_REGISTRY_KEY = Symbol.for("unipi.background-tasks.shared-registry");
|
|
16
|
+
|
|
17
|
+
/** Publish the live registry (idempotent; later calls overwrite). */
|
|
18
|
+
export function setSharedTaskRegistry(registry: BackgroundTaskRegistry): void {
|
|
19
|
+
(globalThis as unknown as Record<symbol, unknown>)[SHARED_REGISTRY_KEY] = registry;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Read the live registry, or undefined when background-tasks is not loaded. */
|
|
23
|
+
export function getSharedTaskRegistry(): BackgroundTaskRegistry | undefined {
|
|
24
|
+
return (globalThis as unknown as Record<symbol, unknown>)[SHARED_REGISTRY_KEY] as
|
|
25
|
+
| BackgroundTaskRegistry
|
|
26
|
+
| undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Drop the shared reference (used on session shutdown so readers see a clean slate). */
|
|
30
|
+
export function clearSharedTaskRegistry(): void {
|
|
31
|
+
delete (globalThis as unknown as Record<symbol, unknown>)[SHARED_REGISTRY_KEY];
|
|
32
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "2.
|
|
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.
|
|
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.
|
|
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.
|
|
38
|
-
"@pi-unipi/info-screen": "2.
|
|
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": {
|
|
@@ -18,6 +18,7 @@ An experimental input surface, on by default and toggleable in `/unipi:footer-se
|
|
|
18
18
|
- **Top border:** animated lolcat-gradient UNIPI brand + git branch (turns rainbow-frame animated while thinking is max/xhigh)
|
|
19
19
|
- **Bottom border:** workspace · context %/window · model · thinking level
|
|
20
20
|
- **Session strip:** turns/steps, wall + tool wall time, average TTFT, tok/s, cache hit % — colored per stat, honest across restarts (derived from persisted session timestamps when live hooks are unavailable; provider-reported `usage.output` anchors token counts whenever present)
|
|
21
|
+
- **Process line (new in 2.13):** centered one-liner directly above the frame while background work is in flight — `● 3 running ● 1 stopped ● 1 failed ● 2 done` — green ● running, yellow ● stopped (killed), red ● failed, gray ● done. Covers every task type (shell jobs, delegates, fusion workflows) via direct registry reads; zero-count buckets are omitted and the line hides when idle. Counts reset per session.
|
|
21
22
|
- The classic segment status line is suppressed while glance mode is on; toggle it back for the classic footer
|
|
22
23
|
|
|
23
24
|
## Commands
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "2.
|
|
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,7 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "2.
|
|
35
|
+
"@pi-unipi/core": "2.14.0",
|
|
36
|
+
"@pi-unipi/background-tasks": "2.14.0"
|
|
36
37
|
},
|
|
37
38
|
"peerDependencies": {
|
|
38
39
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -29,6 +29,7 @@ import { STATUS_EXT_SEGMENTS } from "./segments/status-ext.js";
|
|
|
29
29
|
|
|
30
30
|
import type { FooterGroup, FooterSegment } from "./types.js";
|
|
31
31
|
import { tpsTracker } from "./tps-tracker.js";
|
|
32
|
+
import { renderProcessLine } from "./process-line.js";
|
|
32
33
|
|
|
33
34
|
/** All segment groups */
|
|
34
35
|
const ALL_GROUPS: FooterGroup[] = [
|
|
@@ -328,9 +329,10 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
328
329
|
};
|
|
329
330
|
});
|
|
330
331
|
|
|
331
|
-
// Top row widget —
|
|
332
|
-
//
|
|
333
|
-
//
|
|
332
|
+
// Top row widget — dual role. Classic mode: status segment line. Glance
|
|
333
|
+
// mode: the bg-process one-liner, rendered directly above the glance frame
|
|
334
|
+
// (the frame replaces the editor, so this aboveEditor slot sits right above
|
|
335
|
+
// the footer); the frame's own borders show branch/context/model/thinking.
|
|
334
336
|
ctx.ui.setWidget("footer-top", (_tui, theme) => {
|
|
335
337
|
// Update the renderer's theme-like
|
|
336
338
|
const themeLike = { fg: (color: string, text: string) => theme.fg(color as any, text) };
|
|
@@ -344,8 +346,8 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
344
346
|
},
|
|
345
347
|
render(width: number): string[] {
|
|
346
348
|
if (!state.enabled || !state.piContext || width <= 0) return [];
|
|
347
|
-
// Glance mode
|
|
348
|
-
if (state.glanceMode) return
|
|
349
|
+
// Glance mode: this slot becomes the bg-process one-liner.
|
|
350
|
+
if (state.glanceMode) return renderProcessLine(width);
|
|
349
351
|
const layout = state.renderer.computeLayout(width);
|
|
350
352
|
if (!layout.topContent) return [];
|
|
351
353
|
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @pi-unipi/footer — Background process one-liner
|
|
3
|
+
*
|
|
4
|
+
* Glance-mode strip rendered above the footer frame: one colored dot + count
|
|
5
|
+
* per background-task status. Reads DIRECTLY from the
|
|
6
|
+
* @pi-unipi/background-tasks shared registry (no events, no polling
|
|
7
|
+
* channels); re-renders on the footer's existing 1s refresh timer.
|
|
8
|
+
*
|
|
9
|
+
* Dot → status mapping:
|
|
10
|
+
* green ● running yellow ● stopped (killed) red ● failed gray ● done (completed)
|
|
11
|
+
*
|
|
12
|
+
* Buckets with zero count are omitted; with nothing in flight the line is
|
|
13
|
+
* empty so the footer stays clean.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
17
|
+
import { getSharedTaskRegistry } from "@pi-unipi/background-tasks";
|
|
18
|
+
|
|
19
|
+
const GREEN_DOT = "\x1b[38;5;82m●\x1b[0m"; // running — active work
|
|
20
|
+
const YELLOW_DOT = "\x1b[38;5;220m●\x1b[0m"; // stopped (killed) — needs attention
|
|
21
|
+
const RED_DOT = "\x1b[38;5;196m●\x1b[0m"; // failed — needs attention
|
|
22
|
+
const GRAY_DOT = "\x1b[38;5;245m●\x1b[0m"; // done (completed) — idle info
|
|
23
|
+
|
|
24
|
+
export interface BgProcessCounts {
|
|
25
|
+
running: number;
|
|
26
|
+
stopped: number;
|
|
27
|
+
failed: number;
|
|
28
|
+
done: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Count background tasks by display status straight from the registry.
|
|
33
|
+
* Returns null when background-tasks has not published a registry (module
|
|
34
|
+
* disabled, before first load, or after session shutdown).
|
|
35
|
+
*/
|
|
36
|
+
export function countBgProcesses(): BgProcessCounts | null {
|
|
37
|
+
try {
|
|
38
|
+
const tasks = getSharedTaskRegistry()?.allTasks();
|
|
39
|
+
if (!tasks) return null;
|
|
40
|
+
const counts: BgProcessCounts = { running: 0, stopped: 0, failed: 0, done: 0 };
|
|
41
|
+
for (const task of tasks) {
|
|
42
|
+
if (task.status === "running") counts.running++;
|
|
43
|
+
else if (task.status === "killed") counts.stopped++;
|
|
44
|
+
else if (task.status === "failed") counts.failed++;
|
|
45
|
+
else if (task.status === "completed") counts.done++;
|
|
46
|
+
}
|
|
47
|
+
return counts;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Render the centered one-liner for the given terminal width.
|
|
55
|
+
* Returns [] when there is nothing to show.
|
|
56
|
+
*/
|
|
57
|
+
export function renderProcessLine(width: number): string[] {
|
|
58
|
+
if (width <= 0) return [];
|
|
59
|
+
const counts = countBgProcesses();
|
|
60
|
+
if (!counts) return [];
|
|
61
|
+
|
|
62
|
+
const parts: string[] = [];
|
|
63
|
+
if (counts.running > 0) parts.push(`${GREEN_DOT} ${counts.running} running`);
|
|
64
|
+
if (counts.stopped > 0) parts.push(`${YELLOW_DOT} ${counts.stopped} stopped`);
|
|
65
|
+
if (counts.failed > 0) parts.push(`${RED_DOT} ${counts.failed} failed`);
|
|
66
|
+
if (counts.done > 0) parts.push(`${GRAY_DOT} ${counts.done} done`);
|
|
67
|
+
if (parts.length === 0) return [];
|
|
68
|
+
|
|
69
|
+
const line = parts.join(" ");
|
|
70
|
+
const w = visibleWidth(line);
|
|
71
|
+
if (w >= width) return [truncateToWidth(line, width)];
|
|
72
|
+
const leftPad = Math.floor((width - w) / 2);
|
|
73
|
+
return [" ".repeat(leftPad) + line];
|
|
74
|
+
}
|
package/packages/image/README.md
CHANGED
|
@@ -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.
|
|
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.
|
|
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 {
|
|
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 (
|
|
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
|
-
?
|
|
97
|
-
:
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
33
|
+
"@pi-unipi/core": "2.14.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|