@pi-unipi/unipi 2.14.1 → 2.15.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 +14 -0
- package/package.json +23 -23
- package/packages/ask-user/package.json +3 -3
- package/packages/autocomplete/package.json +1 -1
- package/packages/background-tasks/package.json +3 -3
- package/packages/btw/package.json +3 -3
- package/packages/compactor/package.json +4 -4
- package/packages/core/package.json +1 -1
- package/packages/footer/package.json +4 -4
- package/packages/footer/src/glance-editor.ts +20 -1
- package/packages/footer/src/index.ts +7 -2
- package/packages/footer/src/process-line.ts +6 -2
- package/packages/image/package.json +2 -2
- package/packages/info-screen/package.json +3 -3
- package/packages/input-shortcuts/package.json +3 -3
- package/packages/kanboard/package.json +3 -3
- package/packages/mcp/package.json +3 -3
- package/packages/memory/index.ts +17 -0
- package/packages/memory/mempalace.ts +185 -0
- package/packages/memory/package.json +3 -3
- package/packages/memory/settings.ts +3 -0
- package/packages/memory/tui/settings-tui.ts +14 -0
- package/packages/milestone/package.json +3 -3
- package/packages/notify/package.json +3 -3
- package/packages/ralph/README.md +2 -0
- package/packages/ralph/SKILL.md +2 -0
- package/packages/ralph/completions.ts +170 -0
- package/packages/ralph/index.ts +3 -0
- package/packages/ralph/package.json +3 -3
- package/packages/subagents/package.json +3 -3
- package/packages/trajectory/package.json +1 -1
- package/packages/unipi/bundled.js +452 -180
- package/packages/updater/package.json +3 -3
- package/packages/utility/package.json +3 -3
- package/packages/web-api/package.json +2 -2
- package/packages/web-api/src/engine/errors.ts +30 -0
- package/packages/web-api/src/engine/extract.ts +18 -15
- package/packages/web-api/src/providers/wigolo-client.ts +3 -1
- package/packages/web-api/src/tools.ts +6 -5
- package/packages/workflow/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [2.15.0] — 2026-08-31
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `ralph`: **argument completions for `/unipi:ralph`** — the command now autosuggests subcommands with one-line descriptions, live loop names from `.unipi/ralph/*.state.json` (status-annotated, filtered per subcommand: `resume` hides completed loops, `archive` hides active ones, `cancel` shows all), `start` flags (`--max-iterations`, `--items-per-iteration`, `--reflect-every`, already-used ones excluded), and `--archived` / `--all` / `--yes` for `list` / `clean` / `nuke`. Implemented as a pure builder (`packages/ralph/completions.ts`, 12 tests) wired via `registerCommand`'s `getArgumentCompletions`; pi replaces the entire argument text with the selected item's value, so nested suggestions carry full replacement strings (`resume myloop`) to preserve already-typed flags. Exact-match tokens suppress the popup.
|
|
14
|
+
- `memory`: **MemPalace auto-update** — when MemPalace is the active backend, a TTL-gated (24h, state file `~/.unipi/memory/.mempalace-update`) PyPI release check runs in the background at session start; on a newer `mempalace` release it upgrades via `uv` fire-and-forget (paused while the daemon is running) and emits `unipi:update:applied`. Never blocks startup, all failures swallowed; toggle in `/unipi:memory-settings` (`mempalaceAutoUpdate`, default on).
|
|
15
|
+
|
|
16
|
+
## [2.14.2] — 2026-08-31
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `footer`: **glance frame no longer writes the terminal's last column** (GitHub [#31](https://github.com/Neuron-Mr-White/UniPi/issues/31)) — the glance frame, session strip, process one-liner, and classic status line now cap emitted lines at `width - 1` instead of exactly `width`. pi-tui joins per-tick rewrites with `\r\n`; a line at EXACTLY the terminal width trips auto-wrap on terminals that wrap immediately (or whose glyph widths disagree with `visibleWidth()`, e.g. missing Nerd Fonts / ambiguous-width settings), silently desyncing the differential renderer so every 1s footer refresh painted a fresh input-box copy one block lower until the screen filled. Verified by PTY capture + VT replay: identical byte stream renders 1 stable frame on deferred-wrap terminals vs 12 stacked frames on immediate-wrap ones; the cap removes the footer's contribution entirely. Full resolution of terminal-side drift from pi's own boot/info lines is upstream (pi-tui main-screen wrap-safe bookkeeping).
|
|
21
|
+
- `web-api`: **fetch errors surface their real cause instead of "[object Object]"** — engine errors were plain objects (`{ error, code, phase, retryable }`), so every tool-level catch serialized them via `String()` and both the agent and the user saw only "Read failed: [object Object]" with no diagnosis. `createError()` now returns a real `Error` instance carrying the FetchError fields (interface-compatible: `.error`/`.code`/`.phase`/`.retryable` preserved, classification untouched), and a new `describeError()` normalizes Error instances, FetchError-shaped objects, `{message}`/`{reason}`/`{detail}` shapes, strings, and unserializable values at every tool boundary and in the provider fallthrough. Regression tests cover all shapes; a provider throwing `{ error: "HTTP 429" }` now reports the 429 instead of nothing.
|
|
22
|
+
|
|
9
23
|
## [2.14.1] — 2026-08-28
|
|
10
24
|
|
|
11
25
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/unipi",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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.15.0",
|
|
86
|
+
"@pi-unipi/background-tasks": "2.15.0",
|
|
87
|
+
"@pi-unipi/btw": "2.15.0",
|
|
88
|
+
"@pi-unipi/command-enchantment": "2.15.0",
|
|
89
|
+
"@pi-unipi/compactor": "2.15.0",
|
|
90
|
+
"@pi-unipi/core": "2.15.0",
|
|
91
|
+
"@pi-unipi/footer": "2.15.0",
|
|
92
|
+
"@pi-unipi/image": "2.15.0",
|
|
93
|
+
"@pi-unipi/info-screen": "2.15.0",
|
|
94
|
+
"@pi-unipi/input-shortcuts": "2.15.0",
|
|
95
|
+
"@pi-unipi/kanboard": "2.15.0",
|
|
96
|
+
"@pi-unipi/mcp": "2.15.0",
|
|
97
|
+
"@pi-unipi/memory": "2.15.0",
|
|
98
|
+
"@pi-unipi/milestone": "2.15.0",
|
|
99
|
+
"@pi-unipi/notify": "2.15.0",
|
|
100
|
+
"@pi-unipi/ralph": "2.15.0",
|
|
101
|
+
"@pi-unipi/subagents": "2.15.0",
|
|
102
|
+
"@pi-unipi/trajectory": "2.15.0",
|
|
103
|
+
"@pi-unipi/updater": "2.15.0",
|
|
104
|
+
"@pi-unipi/utility": "2.15.0",
|
|
105
|
+
"@pi-unipi/web-api": "2.15.0",
|
|
106
|
+
"@pi-unipi/workflow": "2.15.0"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
109
|
"@earendil-works/pi-agent-core": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/ask-user",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Structured user input tool for Pi coding agent
|
|
3
|
+
"version": "2.15.0",
|
|
4
|
+
"description": "Structured user input tool for Pi coding agent — single-select, multi-select, freeform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"access": "public"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@pi-unipi/core": "2.
|
|
43
|
+
"@pi-unipi/core": "2.15.0"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/background-tasks",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Background tasks for UniPi
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"types": "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.15.0",
|
|
14
14
|
"turndown": "^7.2.4"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/btw",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "A pi extension for parallel side conversations with /unipi:btw
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -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.15.0"
|
|
41
41
|
},
|
|
42
42
|
"pi": {
|
|
43
43
|
"extensions": [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/compactor",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Context engine for Pi
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -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.15.0",
|
|
38
|
+
"@pi-unipi/info-screen": "2.15.0",
|
|
39
39
|
"@earendil-works/pi-agent-core": "^0.84.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/footer",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Persistent status bar for Unipi
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"access": "public"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@pi-unipi/core": "2.
|
|
36
|
-
"@pi-unipi/background-tasks": "2.
|
|
35
|
+
"@pi-unipi/core": "2.15.0",
|
|
36
|
+
"@pi-unipi/background-tasks": "2.15.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -63,6 +63,24 @@ function repeat(s: string, n: number): string {
|
|
|
63
63
|
return s.repeat(Math.max(0, n));
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Frame width for a terminal of `terminalWidth` columns: one column SHORT of
|
|
68
|
+
* the terminal (legacy 8-column floor preserved below 9 columns).
|
|
69
|
+
*
|
|
70
|
+
* Issue #31 invariant — the frame is repainted every second and pi-tui joins
|
|
71
|
+
* the rewritten lines with "\r\n". A line at EXACTLY the terminal width hits
|
|
72
|
+
* the auto-wrap threshold: on terminals that wrap immediately (and whenever
|
|
73
|
+
* a glyph renders wider than visibleWidth() assumed) the cursor silently
|
|
74
|
+
* advances one extra row per line, desyncing the differential renderer —
|
|
75
|
+
* every refresh then paints a fresh frame copy one block lower until the
|
|
76
|
+
* screen fills. Never writing the last column keeps every frame line below
|
|
77
|
+
* the wrap threshold on every terminal.
|
|
78
|
+
*/
|
|
79
|
+
export function glanceFrameWidth(terminalWidth: number): number {
|
|
80
|
+
if (!Number.isFinite(terminalWidth)) return 8;
|
|
81
|
+
return Math.max(8, terminalWidth - 1);
|
|
82
|
+
}
|
|
83
|
+
|
|
66
84
|
function padLine(line: string, width: number): string {
|
|
67
85
|
const w = visibleWidth(line);
|
|
68
86
|
if (w === width) return line;
|
|
@@ -160,7 +178,8 @@ export class GlanceEditor extends CustomEditor {
|
|
|
160
178
|
}
|
|
161
179
|
|
|
162
180
|
override render(width: number): string[] {
|
|
163
|
-
|
|
181
|
+
// One column short of the terminal — see glanceFrameWidth() / issue #31.
|
|
182
|
+
const safe = glanceFrameWidth(width);
|
|
164
183
|
const inner = Math.max(0, safe - 2);
|
|
165
184
|
|
|
166
185
|
// Base render at inner width: harvest content rows only (rules dropped).
|
|
@@ -354,8 +354,11 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
354
354
|
// Hard safety net: never return a line wider than the terminal.
|
|
355
355
|
// This catches any edge cases in layout math or visibleWidth()
|
|
356
356
|
// inconsistencies with PUA characters + ANSI codes.
|
|
357
|
+
// Cap at width - 1 (issue #31): a line at EXACTLY the terminal width
|
|
358
|
+
// trips immediate-wrap terminals and desyncs the diff renderer.
|
|
357
359
|
const line = layout.topContent;
|
|
358
|
-
|
|
360
|
+
const cap = Math.max(1, width - 1);
|
|
361
|
+
return [visibleWidth(line) > cap ? truncateToWidth(line, cap) : line];
|
|
359
362
|
},
|
|
360
363
|
};
|
|
361
364
|
}, { placement: "aboveEditor" });
|
|
@@ -373,7 +376,9 @@ function setupFooterUI(pi: ExtensionAPI, ctx: ExtensionContext, state: FooterSta
|
|
|
373
376
|
if (!strip) return [];
|
|
374
377
|
// Centered under the input box.
|
|
375
378
|
const w = visibleWidth(strip);
|
|
376
|
-
|
|
379
|
+
// Truncate one column short of the terminal (issue #31 — same wrap
|
|
380
|
+
// desync as the glance frame; see glanceFrameWidth()).
|
|
381
|
+
if (w >= width) return [truncateToWidth(strip, Math.max(1, width - 1))];
|
|
377
382
|
const leftPad = Math.floor((width - w) / 2);
|
|
378
383
|
return [" ".repeat(leftPad) + strip];
|
|
379
384
|
},
|
|
@@ -55,7 +55,9 @@ export function countBgProcesses(): BgProcessCounts | null {
|
|
|
55
55
|
* Returns [] when there is nothing to show.
|
|
56
56
|
*/
|
|
57
57
|
export function renderProcessLine(width: number): string[] {
|
|
58
|
-
|
|
58
|
+
// One column can never host a meaningful one-liner, and any content would
|
|
59
|
+
// be exactly-full-width (issue #31 wrap desync) — render nothing.
|
|
60
|
+
if (width <= 1) return [];
|
|
59
61
|
const counts = countBgProcesses();
|
|
60
62
|
if (!counts) return [];
|
|
61
63
|
|
|
@@ -68,7 +70,9 @@ export function renderProcessLine(width: number): string[] {
|
|
|
68
70
|
|
|
69
71
|
const line = parts.join(" ");
|
|
70
72
|
const w = visibleWidth(line);
|
|
71
|
-
|
|
73
|
+
// One column short of the terminal (issue #31 — same wrap desync as the
|
|
74
|
+
// glance frame; see glanceFrameWidth() in glance-editor.ts).
|
|
75
|
+
if (w >= width) return [truncateToWidth(line, Math.max(1, width - 1))];
|
|
72
76
|
const leftPad = Math.floor((width - w) / 2);
|
|
73
77
|
return [" ".repeat(leftPad) + line];
|
|
74
78
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/image",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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.15.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@earendil-works/pi-ai": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/info-screen",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Dashboard and module registry for Unipi
|
|
3
|
+
"version": "2.15.0",
|
|
4
|
+
"description": "Dashboard and module registry for Unipi — configurable info overlay with tabbed groups",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.
|
|
36
|
+
"@pi-unipi/core": "2.15.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/input-shortcuts",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@pi-unipi/core": "2.
|
|
36
|
+
"@pi-unipi/core": "2.15.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/kanboard",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Visualization layer for unipi workflow
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@pi-unipi/core": "2.
|
|
42
|
+
"@pi-unipi/core": "2.15.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/mcp",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "MCP server management extension for Pi coding agent
|
|
3
|
+
"version": "2.15.0",
|
|
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",
|
|
7
7
|
"license": "MIT",
|
|
@@ -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.15.0"
|
|
34
34
|
},
|
|
35
35
|
"peerDependencies": {
|
|
36
36
|
"@earendil-works/pi-coding-agent": "^0.84.0",
|
package/packages/memory/index.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { registerMemoryTools, MEMORY_TOOLS, GLOBAL_SEARCH_ALIAS } from "./tools.js";
|
|
32
32
|
import { registerMemoryCommands } from "./commands.js";
|
|
33
33
|
import { isEmbeddingReady, hasModelChanged } from "./settings.js";
|
|
34
|
+
import { maybeAutoUpdateMempalace } from "./mempalace.js";
|
|
34
35
|
|
|
35
36
|
/** Package version */
|
|
36
37
|
const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
|
|
@@ -157,6 +158,22 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
158
|
projectStorage = null;
|
|
158
159
|
}
|
|
159
160
|
|
|
161
|
+
// Keep the MemPalace backend current — TTL-gated (~daily) background
|
|
162
|
+
// check; upgrades via uv only when a newer release exists. Never blocks
|
|
163
|
+
// startup and never throws into the session path.
|
|
164
|
+
if (projectStorage?.isMempalace()) {
|
|
165
|
+
void maybeAutoUpdateMempalace()
|
|
166
|
+
.then((outcome) => {
|
|
167
|
+
if (outcome.updated) {
|
|
168
|
+
emitEvent(pi, UNIPI_EVENTS.UPDATE_APPLIED, {
|
|
169
|
+
previousVersion: outcome.currentVersion ?? "",
|
|
170
|
+
newVersion: outcome.latestVersion ?? "",
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch(() => {});
|
|
175
|
+
}
|
|
176
|
+
|
|
160
177
|
|
|
161
178
|
// Announce module
|
|
162
179
|
emitEvent(pi, UNIPI_EVENTS.MODULE_READY, {
|
|
@@ -18,6 +18,7 @@ import * as fs from "node:fs";
|
|
|
18
18
|
import * as path from "node:path";
|
|
19
19
|
import * as os from "node:os";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
21
|
+
import { loadEmbeddingConfig } from "./settings.js";
|
|
21
22
|
|
|
22
23
|
/** Default MemPalace palace path. */
|
|
23
24
|
export const DEFAULT_PALACE = path.join(os.homedir(), ".mempalace", "palace");
|
|
@@ -458,3 +459,187 @@ export function runBridgeAsync<T = unknown>(
|
|
|
458
459
|
export function ping(install: MempalaceInstall, palace: string): boolean {
|
|
459
460
|
return runBridge<string>(install, palace, "ping") === "pong";
|
|
460
461
|
}
|
|
462
|
+
|
|
463
|
+
// ── Auto-update (TTL-gated PyPI check + `uv tool upgrade`) ────────────────
|
|
464
|
+
|
|
465
|
+
/** Update check state file — records the last check so we hit PyPI ~daily. */
|
|
466
|
+
const UPDATE_FLAG = path.join(os.homedir(), ".unipi", "memory", ".mempalace-update");
|
|
467
|
+
export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000; // 24h
|
|
468
|
+
const PYPI_URL = "https://pypi.org/pypi/mempalace/json";
|
|
469
|
+
|
|
470
|
+
export interface MempalaceUpdateState {
|
|
471
|
+
checkedAt: number;
|
|
472
|
+
latestVersion: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export interface MempalaceUpdateOutcome {
|
|
476
|
+
checked: boolean;
|
|
477
|
+
updated: boolean;
|
|
478
|
+
currentVersion?: string;
|
|
479
|
+
latestVersion?: string;
|
|
480
|
+
reason?: "disabled" | "not-installed" | "recent" | "lookup-failed" | "up-to-date" | "uv-missing" | "upgrade-failed";
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/** Read the cached update-check state (null when missing/corrupt). */
|
|
484
|
+
export function readUpdateState(flagPath = UPDATE_FLAG): MempalaceUpdateState | null {
|
|
485
|
+
try {
|
|
486
|
+
const parsed = JSON.parse(fs.readFileSync(flagPath, "utf-8")) as MempalaceUpdateState;
|
|
487
|
+
if (typeof parsed?.checkedAt !== "number" || typeof parsed?.latestVersion !== "string") return null;
|
|
488
|
+
return parsed;
|
|
489
|
+
} catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/** Persist the update-check state atomically. */
|
|
495
|
+
export function writeUpdateState(state: MempalaceUpdateState, flagPath = UPDATE_FLAG): void {
|
|
496
|
+
try {
|
|
497
|
+
fs.mkdirSync(path.dirname(flagPath), { recursive: true });
|
|
498
|
+
const temp = `${flagPath}.${process.pid}.tmp`;
|
|
499
|
+
fs.writeFileSync(temp, JSON.stringify(state, null, 2), "utf-8");
|
|
500
|
+
fs.renameSync(temp, flagPath);
|
|
501
|
+
} catch { /* ignore */ }
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** Is a PyPI lookup due? (no state yet, or the TTL has elapsed) */
|
|
505
|
+
export function isUpdateCheckDue(
|
|
506
|
+
flagPath = UPDATE_FLAG,
|
|
507
|
+
now = Date.now(),
|
|
508
|
+
ttlMs = UPDATE_CHECK_TTL_MS,
|
|
509
|
+
): boolean {
|
|
510
|
+
const state = readUpdateState(flagPath);
|
|
511
|
+
if (!state) return true;
|
|
512
|
+
return now - state.checkedAt >= ttlMs;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/** Numeric dotted-version compare: >0 if a is newer, <0 if older, 0 if equal. */
|
|
516
|
+
export function compareVersions(a: string, b: string): number {
|
|
517
|
+
const pa = String(a ?? "").trim().split(".");
|
|
518
|
+
const pb = String(b ?? "").trim().split(".");
|
|
519
|
+
const len = Math.max(pa.length, pb.length);
|
|
520
|
+
for (let i = 0; i < len; i++) {
|
|
521
|
+
const na = Number.parseInt(pa[i] ?? "0", 10) || 0;
|
|
522
|
+
const nb = Number.parseInt(pb[i] ?? "0") || 0;
|
|
523
|
+
if (na !== nb) return na - nb;
|
|
524
|
+
}
|
|
525
|
+
return 0;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** Latest MemPalace version on PyPI, or null on any failure. */
|
|
529
|
+
export async function fetchLatestMempalaceVersion(
|
|
530
|
+
fetchImpl: typeof fetch = fetch,
|
|
531
|
+
): Promise<string | null> {
|
|
532
|
+
try {
|
|
533
|
+
const res = await fetchImpl(PYPI_URL, { signal: AbortSignal.timeout(8_000) });
|
|
534
|
+
if (!res.ok) return null;
|
|
535
|
+
const body = (await res.json()) as { info?: { version?: string } };
|
|
536
|
+
return typeof body?.info?.version === "string" ? body.info.version : null;
|
|
537
|
+
} catch {
|
|
538
|
+
return null;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** Is the opt-in MemPalace daemon currently running? */
|
|
543
|
+
function daemonRunning(): boolean {
|
|
544
|
+
try {
|
|
545
|
+
const res = spawnSync("mempalace", ["daemon", "status"], { encoding: "utf-8", timeout: 10_000 });
|
|
546
|
+
return /is running/i.test(res.stdout || "");
|
|
547
|
+
} catch {
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** Fire-and-forget process run; resolves null on spawn failure or non-zero exit. */
|
|
553
|
+
function runProcess(bin: string, args: string[], timeoutMs: number): Promise<boolean> {
|
|
554
|
+
return new Promise((resolve) => {
|
|
555
|
+
let child;
|
|
556
|
+
try {
|
|
557
|
+
child = spawn(bin, args, { stdio: ["ignore", "ignore", "ignore"] });
|
|
558
|
+
} catch {
|
|
559
|
+
resolve(false);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
let settled = false;
|
|
563
|
+
const finish = (ok: boolean) => {
|
|
564
|
+
if (settled) return;
|
|
565
|
+
settled = true;
|
|
566
|
+
clearTimeout(timer);
|
|
567
|
+
resolve(ok);
|
|
568
|
+
};
|
|
569
|
+
const timer = setTimeout(() => {
|
|
570
|
+
try { child.kill(); } catch { /* already gone */ }
|
|
571
|
+
finish(false);
|
|
572
|
+
}, timeoutMs);
|
|
573
|
+
timer.unref?.();
|
|
574
|
+
child.on("error", () => finish(false));
|
|
575
|
+
child.on("close", (code) => finish(code === 0));
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Upgrade MemPalace via `uv tool install --upgrade`. The daemon (if running)
|
|
581
|
+
* is stopped first and restarted after, so the long-lived process picks up
|
|
582
|
+
* the new venv instead of straddling versions.
|
|
583
|
+
*/
|
|
584
|
+
async function upgradeMempalace(): Promise<boolean> {
|
|
585
|
+
if (!which("uv")) return false;
|
|
586
|
+
const wasRunning = daemonRunning();
|
|
587
|
+
if (wasRunning) await runProcess("mempalace", ["daemon", "stop"], 30_000);
|
|
588
|
+
const upgraded = await runProcess("uv", ["tool", "upgrade", "mempalace"], 300_000);
|
|
589
|
+
if (wasRunning) await runProcess("mempalace", ["daemon", "start"], 30_000);
|
|
590
|
+
return upgraded;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
export interface MempalaceUpdateOptions {
|
|
594
|
+
/** Skip the TTL gate and force a PyPI lookup. */
|
|
595
|
+
force?: boolean;
|
|
596
|
+
/** Injectable fetch for tests. */
|
|
597
|
+
fetchImpl?: typeof fetch;
|
|
598
|
+
/** Override "now" for TTL math (tests). */
|
|
599
|
+
now?: number;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Keep the user's MemPalace install current.
|
|
604
|
+
*
|
|
605
|
+
* TTL-gated (~daily) PyPI lookup; when a newer release exists and the install
|
|
606
|
+
* came from uv, runs `uv tool upgrade mempalace` in the background. Never
|
|
607
|
+
* throws — callers fire-and-forget this from session_start.
|
|
608
|
+
*/
|
|
609
|
+
export async function maybeAutoUpdateMempalace(
|
|
610
|
+
options: MempalaceUpdateOptions = {},
|
|
611
|
+
): Promise<MempalaceUpdateOutcome> {
|
|
612
|
+
const now = options.now ?? Date.now();
|
|
613
|
+
if (loadEmbeddingConfig().mempalaceAutoUpdate === false) {
|
|
614
|
+
return { checked: false, updated: false, reason: "disabled" };
|
|
615
|
+
}
|
|
616
|
+
const install = readCachedInstall();
|
|
617
|
+
if (!install) return { checked: false, updated: false, reason: "not-installed" };
|
|
618
|
+
|
|
619
|
+
if (!options.force && !isUpdateCheckDue(UPDATE_FLAG, now, UPDATE_CHECK_TTL_MS)) {
|
|
620
|
+
return { checked: false, updated: false, reason: "recent" };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const latest = await fetchLatestMempalaceVersion(options.fetchImpl).catch(() => null);
|
|
624
|
+
const previous = readUpdateState()?.latestVersion ?? "";
|
|
625
|
+
writeUpdateState({ checkedAt: now, latestVersion: latest ?? previous });
|
|
626
|
+
if (!latest) {
|
|
627
|
+
return { checked: true, updated: false, currentVersion: install.version, reason: "lookup-failed" };
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const current = detectVersion(install.python);
|
|
631
|
+
if (compareVersions(latest, current) <= 0) {
|
|
632
|
+
return { checked: true, updated: false, currentVersion: current, latestVersion: latest, reason: "up-to-date" };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
const upgraded = await upgradeMempalace();
|
|
636
|
+
if (!upgraded) {
|
|
637
|
+
return { checked: true, updated: false, currentVersion: current, latestVersion: latest, reason: "upgrade-failed" };
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Refresh the cached install record so the new version is used next bridge call.
|
|
641
|
+
const python = findVenvPython();
|
|
642
|
+
if (python) writeCachedInstall({ python, version: detectVersion(python) });
|
|
643
|
+
invalidatePingVerified();
|
|
644
|
+
return { checked: true, updated: true, currentVersion: current, latestVersion: latest };
|
|
645
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pi-unipi/memory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.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.
|
|
43
|
-
"@pi-unipi/info-screen": "2.
|
|
42
|
+
"@pi-unipi/core": "2.15.0",
|
|
43
|
+
"@pi-unipi/info-screen": "2.15.0",
|
|
44
44
|
"js-yaml": "^4.1.0"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -26,6 +26,8 @@ export interface EmbeddingConfig {
|
|
|
26
26
|
lastModel?: string;
|
|
27
27
|
/** Whether to show migration warning on startup */
|
|
28
28
|
suppressMigrationWarning?: boolean;
|
|
29
|
+
/** Keep the MemPalace backend current via a daily PyPI check + uv upgrade */
|
|
30
|
+
mempalaceAutoUpdate?: boolean;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
/** Default configuration */
|
|
@@ -34,6 +36,7 @@ const DEFAULT_CONFIG: EmbeddingConfig = {
|
|
|
34
36
|
model: "openai/text-embedding-3-small",
|
|
35
37
|
dimensions: 384,
|
|
36
38
|
suppressMigrationWarning: false,
|
|
39
|
+
mempalaceAutoUpdate: true,
|
|
37
40
|
};
|
|
38
41
|
|
|
39
42
|
/** Known embedding models on OpenRouter */
|