@pi-unipi/unipi 2.4.2 → 2.5.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/package.json +23 -22
  3. package/packages/ask-user/package.json +2 -2
  4. package/packages/autocomplete/package.json +1 -1
  5. package/packages/btw/package.json +2 -2
  6. package/packages/cocoindex/package.json +2 -2
  7. package/packages/compactor/package.json +3 -3
  8. package/packages/core/package.json +1 -1
  9. package/packages/core/sandbox.ts +7 -6
  10. package/packages/footer/package.json +2 -2
  11. package/packages/image/package.json +2 -2
  12. package/packages/info-screen/package.json +2 -2
  13. package/packages/input-shortcuts/package.json +2 -2
  14. package/packages/kanboard/package.json +2 -2
  15. package/packages/mcp/README.md +9 -0
  16. package/packages/mcp/package.json +5 -2
  17. package/packages/mcp/src/bridge/registry.ts +244 -137
  18. package/packages/mcp/src/bridge/translator.ts +77 -9
  19. package/packages/mcp/src/index.ts +43 -67
  20. package/packages/mcp/src/tui/settings-overlay.ts +3 -9
  21. package/packages/memory/README.md +15 -11
  22. package/packages/memory/bridge/mempalace_bridge.py +636 -0
  23. package/packages/memory/mempalace.ts +169 -18
  24. package/packages/memory/package.json +3 -3
  25. package/packages/memory/storage.ts +27 -10
  26. package/packages/milestone/README.md +11 -3
  27. package/packages/milestone/hooks.ts +103 -29
  28. package/packages/milestone/index.ts +1 -1
  29. package/packages/milestone/package.json +5 -2
  30. package/packages/notify/package.json +2 -2
  31. package/packages/ralph/package.json +3 -3
  32. package/packages/subagents/package.json +1 -1
  33. package/packages/unipi/bundled.js +693 -408
  34. package/packages/updater/package.json +2 -2
  35. package/packages/utility/package.json +2 -2
  36. package/packages/web-api/package.json +2 -2
  37. package/packages/workflow/README.md +8 -0
  38. package/packages/workflow/commands.ts +16 -26
  39. package/packages/workflow/index.ts +165 -85
  40. package/packages/workflow/package.json +5 -2
@@ -18,6 +18,7 @@ import type { ResolvedServer } from "./types.js";
18
18
  import { loadAndResolve, getGlobalConfigDir } from "./config/manager.js";
19
19
  import { syncCatalog, loadCatalog } from "./config/sync.js";
20
20
  import { ServerRegistry } from "./bridge/registry.js";
21
+ import { compareCodeUnits } from "./bridge/translator.js";
21
22
  import { renderMcpAddOverlay } from "./tui/add-overlay.js";
22
23
  import { renderMcpSettingsOverlay } from "./tui/settings-overlay.js";
23
24
 
@@ -42,24 +43,32 @@ export default function (pi: ExtensionAPI) {
42
43
  // Session start — load configs, start servers
43
44
  pi.on("session_start", async (_event, ctx) => {
44
45
  // Create registry with pi integration callbacks
46
+ const toolApi = pi as ExtensionAPI & {
47
+ registerExternalTool?: (tool: unknown) => void;
48
+ unregisterTool?: (toolName: string) => void;
49
+ unregisterExternalTool?: (toolName: string) => void;
50
+ };
51
+ const registerTool = typeof toolApi.registerTool === "function"
52
+ ? (tool: unknown) => toolApi.registerTool(tool as Parameters<typeof toolApi.registerTool>[0])
53
+ : typeof toolApi.registerExternalTool === "function"
54
+ ? (tool: unknown) => toolApi.registerExternalTool!(tool)
55
+ : () => {
56
+ throw new Error("Pi does not expose a supported MCP tool registration API");
57
+ };
58
+ const canUnregisterTools =
59
+ typeof toolApi.unregisterTool === "function" ||
60
+ typeof toolApi.unregisterExternalTool === "function";
61
+ const unregisterTool = typeof toolApi.unregisterTool === "function"
62
+ ? (toolName: string) => toolApi.unregisterTool!(toolName)
63
+ : typeof toolApi.unregisterExternalTool === "function"
64
+ ? (toolName: string) => toolApi.unregisterExternalTool!(toolName)
65
+ : () => {};
66
+
45
67
  registry = new ServerRegistry({
46
68
  emitEvent: (event, payload) => emitEvent(pi, event, payload),
47
- registerTool: (tool) => {
48
- try {
49
- (pi as any).registerTool?.(tool) ??
50
- (pi as any).registerExternalTool?.(tool);
51
- } catch {
52
- // Tool registration may not be available in all contexts
53
- }
54
- },
55
- unregisterTool: (toolName) => {
56
- try {
57
- (pi as any).unregisterTool?.(toolName) ??
58
- (pi as any).unregisterExternalTool?.(toolName);
59
- } catch {
60
- // Ignore
61
- }
62
- },
69
+ registerTool,
70
+ unregisterTool,
71
+ canUnregisterTools,
63
72
  });
64
73
 
65
74
  // Load and resolve server configs
@@ -73,21 +82,13 @@ export default function (pi: ExtensionAPI) {
73
82
  // Config load failure — servers will be empty, visible via /unipi:mcp-status.
74
83
  }
75
84
 
76
- // Start enabled servers (parallel, non-blocking errors)
77
- const startPromises = servers
78
- .filter((s) => s.enabled)
79
- .map(async (server) => {
80
- try {
81
- await registry!.startServer(server);
82
- // Removed console.log — startup logs cause layout shift in TUI.
83
- // Server status visible via /unipi:mcp-status or info screen.
84
- } catch (err) {
85
- // Removed console.error — errors surfaced via info-screen MCP group.
86
- // Server failure tracked in registry state.
87
- }
88
- });
89
-
90
- await Promise.allSettled(startPromises);
85
+ // Connect/discover in parallel, then register the successful combined set
86
+ // after a barrier so tool order is stable across runs.
87
+ try {
88
+ await registry.startServers(servers.filter((server) => server.enabled));
89
+ } catch (_err) {
90
+ // Errors are tracked in registry state and surfaced by the info screen.
91
+ }
91
92
 
92
93
  // Register info-screen group
93
94
  const infoRegistry = getInfoRegistry();
@@ -153,16 +154,17 @@ export default function (pi: ExtensionAPI) {
153
154
  `unipi:${MCP_COMMANDS.STATUS}`,
154
155
  `unipi:${MCP_COMMANDS.RELOAD}`,
155
156
  ],
156
- tools: activeServers.flatMap((s) =>
157
- registry?.getEntry(s.name)?.toolNames ?? [],
158
- ),
157
+ tools: activeServers
158
+ .flatMap((server) => registry?.getEntry(server.name)?.toolNames ?? [])
159
+ .sort(compareCodeUnits),
159
160
  });
160
161
  });
161
162
 
162
- // Session shutdown — stop all servers
163
+ // Session shutdown tears down clients. Pi tears down this extension's tool
164
+ // registry itself, so do not claim per-tool unregistration here.
163
165
  pi.on("session_shutdown", async (_event, _ctx) => {
164
166
  if (registry) {
165
- await registry.stopAll();
167
+ await registry.disconnectAll();
166
168
  registry = null;
167
169
  }
168
170
  });
@@ -305,38 +307,12 @@ export default function (pi: ExtensionAPI) {
305
307
 
306
308
  // /unipi:mcp-reload — restart all MCP servers
307
309
  pi.registerCommand(`unipi:${MCP_COMMANDS.RELOAD}`, {
308
- description: "Reload all MCP servers (restart with current config)",
310
+ description: "Explain how to reload MCP servers safely",
309
311
  handler: async (_args: string, ctx: ExtensionCommandContext) => {
310
- const reg = getRegistry();
311
- if (!reg) {
312
- ctx.ui.notify("MCP extension not initialized", "warning");
313
- return;
314
- }
315
-
316
- const all = reg.getAll();
317
- if (all.length === 0) {
318
- ctx.ui.notify("No MCP servers configured. Use /unipi:mcp-add to add one.", "info");
319
- return;
320
- }
321
-
322
- ctx.ui.notify(`Reloading ${all.length} MCP server(s)...`, "info");
323
-
324
- let restarted = 0;
325
- let failed = 0;
326
- for (const state of all) {
327
- try {
328
- await reg.restartServer(state.name);
329
- restarted++;
330
- } catch (_err) {
331
- failed++;
332
- // Silently ignore — restart failure tracked in failed count.
333
- }
334
- }
335
-
336
- const msg = failed > 0
337
- ? `Reloaded: ${restarted} ok, ${failed} failed`
338
- : `Reloaded ${restarted} MCP server(s) successfully`;
339
- ctx.ui.notify(msg, failed > 0 ? "warning" : "info");
312
+ // Pi 0.80 does not expose dynamic tool removal. Restarting in place can
313
+ // leave stale schemas in the provider-visible tool list, so require a
314
+ // process/extension restart to establish a clean cache epoch.
315
+ ctx.ui.notify("Restart Pi to reload MCP servers and tool schemas safely.", "info");
340
316
  },
341
317
  });
342
318
  }
@@ -141,15 +141,9 @@ export function renderMcpSettingsOverlay(params?: {
141
141
  };
142
142
  saveMetadata(configDir, meta);
143
143
 
144
- // Try to stop if disabling
145
- if (!newEnabled && registry) {
146
- try {
147
- await registry.stopServer(server.name);
148
- } catch {
149
- // Ignore stop errors
150
- }
151
- }
152
-
144
+ // Pi 0.80 cannot remove a registered tool definition at runtime.
145
+ // Persist the setting now; the next Pi restart applies the new set as
146
+ // one deterministic cache epoch.
153
147
  refreshServers();
154
148
  refresh();
155
149
  } catch (err) {
@@ -2,7 +2,7 @@
2
2
 
3
3
  Persistent memory that survives across sessions. Stores facts, preferences, and decisions with semantic vector search, so the agent remembers what you told it last week.
4
4
 
5
- **Primary backend: [MemPalace](https://github.com/mempalace/mempalace)** — auto-installed via `uv` on first load, with one-way auto-migration of any existing legacy memories. If MemPalace or `uv` is unavailable, the package transparently falls back to the bundled SQLite + sqlite-vec store, so memory never hard-fails.
5
+ **Primary backend: [MemPalace](https://github.com/mempalace/mempalace)** — auto-installed via `uv` on first load, with verified, resumable migration of existing legacy memories. If MemPalace or `uv` is unavailable, the package transparently falls back to the bundled SQLite + sqlite-vec store, so memory never hard-fails.
6
6
 
7
7
  Two storage tiers: MemPalace (or SQLite) for vector similarity search, markdown files for a durable human-readable copy you can edit by hand. Project-scoped memories stay separate per codebase, global memories are accessible everywhere.
8
8
 
@@ -76,7 +76,7 @@ Memory has no configuration file. Storage paths are fixed:
76
76
  ```
77
77
  ~/.unipi/memory/ # UniPi memory root (legacy + markdown tier)
78
78
  ├── .mempalace-install # Cached MemPalace venv detection
79
- ├── .mempalace-migrated # One-way migration completion flag
79
+ ├── .mempalace-migrated # Versioned migration verification state
80
80
  ├── global/
81
81
  │ ├── memory.db # Global vector DB (SQLite fallback)
82
82
  │ └── *.md # Global memory files
@@ -94,14 +94,18 @@ On first load, the memory package:
94
94
  `uv tool install mempalace` once (caches the venv python path in
95
95
  `~/.unipi/memory/.mempalace-install`).
96
96
  2. Pings the bridge to confirm the palace is usable.
97
- 3. If `~/.unipi/memory/.mempalace-migrated` is absent, performs a one-way
98
- read-only migration of every legacy memory (SQLite rows + markdown files
99
- across all projects) into MemPalace drawers, then writes the flag.
100
- Migration is idempotent (deterministic drawer IDs) and never deletes or
101
- mutates legacy files.
102
-
103
- Each memory operation invokes a bundled Python bridge
104
- (`bridge/mempalace_bridge.py`) once via `spawnSync` (~0.5s per call). The
97
+ 3. Fingerprints the durable SQLite and markdown sources. If they differ from
98
+ the verified state in `~/.unipi/memory/.mempalace-migrated`, performs an
99
+ idempotent read-only migration into MemPalace drawers. Unchanged drawers are
100
+ skipped, new or changed memories are upserted, and the versioned state is
101
+ written only after every discovered record is verified in MemPalace.
102
+ Failed or partial migrations remain unmarked and retry on a later session.
103
+ Legacy files are never deleted or mutated.
104
+
105
+ Each memory operation invokes the packaged Python bridge
106
+ (`bridge/mempalace_bridge.py`) once via `spawnSync` (~0.5s per call). Both the
107
+ standalone memory package and the all-in-one umbrella tarball ship and resolve
108
+ this bridge. The
105
109
  first MemPalace use on a machine also downloads the default ONNX embedding
106
110
  model (~80MB, cached at `~/.cache/chroma/onnx_models/`).
107
111
 
@@ -109,7 +113,7 @@ model (~80MB, cached at `~/.cache/chroma/onnx_models/`).
109
113
 
110
114
  ```bash
111
115
  rm ~/.unipi/memory/.mempalace-install # re-detect MemPalace next session
112
- rm ~/.unipi/memory/.mempalace-migrated # re-run one-way migration next session
116
+ rm ~/.unipi/memory/.mempalace-migrated # force a full verified migration pass next session
113
117
  ```
114
118
 
115
119
  ### Backend override