@pi-unipi/unipi 2.0.13 → 2.1.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 (165) hide show
  1. package/LICENSE +0 -0
  2. package/package.json +28 -28
  3. package/packages/ask-user/handoff.ts +0 -0
  4. package/packages/ask-user/index.ts +3 -1
  5. package/packages/ask-user/launcher-ui.ts +0 -0
  6. package/packages/ask-user/package.json +4 -4
  7. package/packages/ask-user/tools.ts +0 -0
  8. package/packages/autocomplete/README.md +0 -0
  9. package/packages/autocomplete/package.json +3 -3
  10. package/packages/autocomplete/src/__tests__/command-registry.audit.test.ts +0 -0
  11. package/packages/autocomplete/src/__tests__/provider.sorting.test.ts +0 -0
  12. package/packages/autocomplete/src/constants.ts +0 -0
  13. package/packages/autocomplete/src/provider.ts +0 -0
  14. package/packages/autocomplete/src/sorting.ts +0 -0
  15. package/packages/btw/package.json +5 -5
  16. package/packages/cocoindex/README.md +0 -0
  17. package/packages/cocoindex/bridge.ts +0 -0
  18. package/packages/cocoindex/commands.ts +0 -0
  19. package/packages/cocoindex/index.ts +0 -0
  20. package/packages/cocoindex/installer.ts +0 -0
  21. package/packages/cocoindex/package.json +5 -5
  22. package/packages/cocoindex/skills/cocoindex/SKILL.md +0 -0
  23. package/packages/cocoindex/tools.ts +0 -0
  24. package/packages/compactor/package.json +6 -6
  25. package/packages/compactor/src/commands/index.ts +0 -0
  26. package/packages/compactor/src/compaction/auto-trigger.ts +0 -0
  27. package/packages/compactor/src/compaction/hooks.ts +0 -0
  28. package/packages/compactor/src/config/manager.ts +0 -0
  29. package/packages/compactor/src/config/presets.ts +0 -0
  30. package/packages/compactor/src/config/schema.ts +0 -0
  31. package/packages/compactor/src/display/diff-presentation.ts +0 -0
  32. package/packages/compactor/src/display/diff-renderer.ts +0 -0
  33. package/packages/compactor/src/display/diff-width-safety.ts +0 -0
  34. package/packages/compactor/src/display/line-width-safety.ts +0 -0
  35. package/packages/compactor/src/index.ts +0 -0
  36. package/packages/compactor/src/info-screen.ts +0 -0
  37. package/packages/compactor/src/session/analytics.ts +0 -0
  38. package/packages/compactor/src/session/db.ts +0 -0
  39. package/packages/compactor/src/session/recall-blocks.ts +0 -0
  40. package/packages/compactor/src/tools/ctx-batch-execute.ts +0 -0
  41. package/packages/compactor/src/tools/ctx-doctor.ts +0 -0
  42. package/packages/compactor/src/tools/ctx-stats.ts +0 -0
  43. package/packages/compactor/src/tools/register.ts +0 -0
  44. package/packages/compactor/src/tui/settings-overlay.ts +0 -0
  45. package/packages/compactor/src/types.ts +0 -0
  46. package/packages/core/constants.ts +0 -0
  47. package/packages/core/events.ts +0 -0
  48. package/packages/core/global-types.ts +0 -0
  49. package/packages/core/global.d.ts +0 -0
  50. package/packages/core/package.json +3 -3
  51. package/packages/core/sandbox.ts +0 -0
  52. package/packages/footer/README.md +0 -0
  53. package/packages/footer/index.ts +0 -0
  54. package/packages/footer/package.json +4 -4
  55. package/packages/footer/src/commands.ts +0 -0
  56. package/packages/footer/src/config.ts +0 -0
  57. package/packages/footer/src/events.ts +0 -0
  58. package/packages/footer/src/help.ts +0 -0
  59. package/packages/footer/src/index.ts +0 -0
  60. package/packages/footer/src/presets.ts +0 -0
  61. package/packages/footer/src/registry/index.ts +0 -0
  62. package/packages/footer/src/rendering/icons.ts +0 -0
  63. package/packages/footer/src/rendering/renderer.ts +0 -0
  64. package/packages/footer/src/rendering/separators.ts +0 -0
  65. package/packages/footer/src/rendering/theme.ts +0 -0
  66. package/packages/footer/src/segments/compactor.ts +0 -0
  67. package/packages/footer/src/segments/core.ts +0 -0
  68. package/packages/footer/src/segments/kanboard.ts +0 -0
  69. package/packages/footer/src/segments/mcp.ts +0 -0
  70. package/packages/footer/src/segments/memory.ts +0 -0
  71. package/packages/footer/src/segments/notify.ts +0 -0
  72. package/packages/footer/src/segments/ralph.ts +0 -0
  73. package/packages/footer/src/segments/status-ext.ts +0 -0
  74. package/packages/footer/src/segments/workflow.ts +0 -0
  75. package/packages/footer/src/tps-tracker.ts +0 -0
  76. package/packages/footer/src/tui/settings-tui.ts +0 -0
  77. package/packages/footer/src/types.ts +0 -0
  78. package/packages/footer/types.ts +0 -0
  79. package/packages/info-screen/index.ts +3 -1
  80. package/packages/info-screen/package.json +4 -4
  81. package/packages/input-shortcuts/README.md +0 -0
  82. package/packages/input-shortcuts/index.ts +0 -0
  83. package/packages/input-shortcuts/package.json +4 -4
  84. package/packages/input-shortcuts/src/chord-overlay.ts +0 -0
  85. package/packages/input-shortcuts/src/clipboard.ts +0 -0
  86. package/packages/input-shortcuts/src/index.ts +0 -0
  87. package/packages/input-shortcuts/src/registers.ts +0 -0
  88. package/packages/input-shortcuts/src/settings-overlay.ts +0 -0
  89. package/packages/input-shortcuts/src/status.ts +0 -0
  90. package/packages/input-shortcuts/src/types.ts +0 -0
  91. package/packages/input-shortcuts/src/undo-redo.ts +0 -0
  92. package/packages/kanboard/index.ts +0 -0
  93. package/packages/kanboard/package.json +4 -4
  94. package/packages/mcp/package.json +4 -4
  95. package/packages/mcp/src/index.ts +3 -1
  96. package/packages/mcp/src/tui/settings-overlay.ts +0 -0
  97. package/packages/memory/README.md +51 -7
  98. package/packages/memory/index.ts +7 -4
  99. package/packages/memory/mempalace.ts +232 -0
  100. package/packages/memory/package.json +8 -5
  101. package/packages/memory/storage.ts +237 -22
  102. package/packages/memory/tools.ts +0 -0
  103. package/packages/milestone/commands.ts +3 -2
  104. package/packages/milestone/package.json +4 -4
  105. package/packages/notify/ask-user-prompt-message.ts +0 -0
  106. package/packages/notify/commands.ts +0 -0
  107. package/packages/notify/events.ts +0 -0
  108. package/packages/notify/index.ts +3 -1
  109. package/packages/notify/ntfy-config.ts +0 -0
  110. package/packages/notify/package.json +8 -6
  111. package/packages/notify/platforms/focus-win.ts +0 -0
  112. package/packages/notify/platforms/focus.ts +0 -0
  113. package/packages/notify/platforms/native.ts +0 -0
  114. package/packages/notify/settings.ts +0 -0
  115. package/packages/notify/src/__tests__/ask-user-prompt-message.test.ts +0 -0
  116. package/packages/notify/src/__tests__/event-bus.test.ts +0 -0
  117. package/packages/notify/tui/settings-overlay.ts +0 -0
  118. package/packages/notify/types.ts +0 -0
  119. package/packages/ralph/index.ts +3 -1
  120. package/packages/ralph/package.json +6 -6
  121. package/packages/subagents/package.json +7 -7
  122. package/packages/unipi/index.ts +0 -0
  123. package/packages/updater/README.md +0 -0
  124. package/packages/updater/index.ts +0 -0
  125. package/packages/updater/package.json +4 -4
  126. package/packages/updater/skills/configure-updater/SKILL.md +0 -0
  127. package/packages/updater/src/cache.ts +0 -0
  128. package/packages/updater/src/changelog.ts +0 -0
  129. package/packages/updater/src/checker.ts +0 -0
  130. package/packages/updater/src/commands.ts +0 -0
  131. package/packages/updater/src/installer.ts +0 -0
  132. package/packages/updater/src/markdown.ts +0 -0
  133. package/packages/updater/src/readme.ts +3 -1
  134. package/packages/updater/src/settings.ts +0 -0
  135. package/packages/updater/src/tui/changelog-overlay.ts +0 -0
  136. package/packages/updater/src/tui/readme-overlay.ts +0 -0
  137. package/packages/updater/src/tui/settings-overlay.ts +0 -0
  138. package/packages/updater/src/tui/update-overlay.ts +0 -0
  139. package/packages/updater/src/version.ts +0 -0
  140. package/packages/updater/types.ts +0 -0
  141. package/packages/utility/package.json +3 -3
  142. package/packages/utility/src/diff/renderer.ts +0 -0
  143. package/packages/utility/src/diff/theme.ts +0 -0
  144. package/packages/utility/src/diff/wrapper.ts +0 -0
  145. package/packages/utility/src/display/capabilities.ts +0 -0
  146. package/packages/utility/src/index.ts +3 -1
  147. package/packages/web-api/package.json +4 -4
  148. package/packages/web-api/skills/web/SKILL.md +0 -0
  149. package/packages/web-api/src/engine/constants.ts +0 -0
  150. package/packages/web-api/src/engine/dependencies.ts +0 -0
  151. package/packages/web-api/src/engine/dom.ts +0 -0
  152. package/packages/web-api/src/engine/extract.ts +0 -0
  153. package/packages/web-api/src/engine/format.ts +0 -0
  154. package/packages/web-api/src/engine/profiles.ts +0 -0
  155. package/packages/web-api/src/engine/types.ts +0 -0
  156. package/packages/web-api/src/index.ts +3 -1
  157. package/packages/web-api/src/providers/base.ts +0 -0
  158. package/packages/web-api/src/settings.ts +0 -0
  159. package/packages/web-api/src/tools.ts +0 -0
  160. package/packages/web-api/src/tui/progress.ts +0 -0
  161. package/packages/web-api/src/tui/result.ts +0 -0
  162. package/packages/web-api/src/tui/settings-dialog.ts +0 -0
  163. package/packages/workflow/commands.ts +0 -0
  164. package/packages/workflow/index.ts +3 -1
  165. package/packages/workflow/package.json +5 -5
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * @unipi/memory — Storage layer
3
3
  *
4
- * Two-tier storage: SQLite + sqlite-vec for vector search,
5
- * markdown files for human-readable memory.
4
+ * Primary backend: MemPalace (auto-installed via uv, auto-migrated from
5
+ * legacy data). Falls back to SQLite + sqlite-vec when MemPalace/uv is
6
+ * unavailable, so memory never hard-fails. Markdown files remain the
7
+ * durable human-readable tier and the migration source.
6
8
  */
7
9
 
8
10
  import Database from "better-sqlite3";
@@ -12,6 +14,44 @@ import * as fs from "node:fs";
12
14
  import * as path from "node:path";
13
15
  import * as os from "node:os";
14
16
  import { randomUUID } from "node:crypto";
17
+ import {
18
+ ensureMempalace,
19
+ runBridge,
20
+ isMigrated,
21
+ markMigrated,
22
+ DEFAULT_PALACE,
23
+ type MempalaceInstall,
24
+ type MempalaceRecord,
25
+ type MempalaceSearchResult,
26
+ type MempalaceListItem,
27
+ type MempalaceListItemAll,
28
+ } from "./mempalace.js";
29
+
30
+ export type MemoryBackend = "mempalace" | "sqlite";
31
+
32
+ /** Convert a MemPalace record (plain JSON) into a MemoryRecord. */
33
+ function toMemoryRecord(r: MempalaceRecord): MemoryRecord {
34
+ return {
35
+ id: r.id,
36
+ title: r.title,
37
+ content: r.content,
38
+ tags: Array.isArray(r.tags) ? r.tags : [],
39
+ project: r.project,
40
+ type: (r.type as MemoryRecord["type"]) || "summary",
41
+ created: r.created || "",
42
+ updated: r.updated || "",
43
+ embedding: null,
44
+ };
45
+ }
46
+
47
+ /** Whether MemPalace is detectable on this machine (for status display). */
48
+ export function isMempalaceAvailable(): boolean {
49
+ try {
50
+ return ensureMempalace() !== null;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
15
55
 
16
56
  /** Memory row from SQLite queries */
17
57
  interface MemoryRow {
@@ -203,27 +243,43 @@ export class MemoryStorage {
203
243
  private db: Database.Database | null = null;
204
244
  private projectName: string;
205
245
  private scopeDir: string;
246
+ private backend: MemoryBackend = "sqlite";
247
+ private mempalaceInstall: MempalaceInstall | null = null;
248
+ private palacePath: string = DEFAULT_PALACE;
206
249
 
207
250
  constructor(projectName: string) {
208
251
  this.projectName = projectName;
209
252
  this.scopeDir = getProjectDir(projectName);
210
253
  }
211
254
 
255
+ /** Active backend ("mempalace" when available, else "sqlite"). */
256
+ getBackend(): MemoryBackend {
257
+ return this.backend;
258
+ }
259
+
260
+ /** True when the MemPalace backend is active for this instance. */
261
+ isMempalace(): boolean {
262
+ return this.backend === "mempalace" && this.mempalaceInstall !== null;
263
+ }
264
+
212
265
  /**
213
- * Initialize the storage (create DB, tables, load extension).
214
- *
215
- * Uses retry logic to handle concurrent access from multiple Pi sessions,
216
- * especially on WSL/Windows filesystem where SQLite locking can be flaky.
217
- *
218
- * IMPORTANT: We never delete the DB here — another session may have it open.
219
- * If all retries fail, we throw and let this session run without memory.
266
+ * Initialize storage. Tries MemPalace first (auto-install + one-way
267
+ * auto-migration of legacy memories); falls back to SQLite if MemPalace
268
+ * is unavailable. Never throws for backend unavailability only throws
269
+ * if the SQLite fallback itself fails to open.
220
270
  */
221
271
  init(): void {
222
- // Ensure directory exists
272
+ // Ensure directory exists (used by both backends for markdown tier).
223
273
  if (!fs.existsSync(this.scopeDir)) {
224
274
  fs.mkdirSync(this.scopeDir, { recursive: true });
225
275
  }
226
276
 
277
+ if (this.tryInitMempalace()) {
278
+ return;
279
+ }
280
+
281
+ // Fallback: SQLite + sqlite-vec.
282
+ this.backend = "sqlite";
227
283
  const dbPath = path.join(this.scopeDir, MEMORY_DB_NAME);
228
284
  const maxRetries = 5;
229
285
 
@@ -243,27 +299,54 @@ export class MemoryStorage {
243
299
  this.close();
244
300
 
245
301
  if (isTransient && attempt < maxRetries) {
246
- // Likely concurrent access — back off and retry.
247
- // Do NOT delete the DB: another session may have it open
248
- // and deleting open files on WSL/Windows is unsafe.
249
302
  const delayMs = 50 * Math.pow(2, attempt - 1); // 50, 100, 200, 400
250
- // Removed console.warn — transient retries are normal during concurrent access.
251
- // Memory availability visible via info-screen memory group.
252
303
  const end = Date.now() + delayMs;
253
304
  while (Date.now() < end) { /* busy wait */ }
254
305
  continue;
255
306
  }
256
307
 
257
- // Either non-transient error, or retries exhausted.
258
- // Log and throw — this session will run without memory.
259
- if (isTransient) {
260
- // Removed console.warn — memory unavailable status visible via info-screen.
261
- }
262
308
  throw err;
263
309
  }
264
310
  }
265
311
  }
266
312
 
313
+ /**
314
+ * Attempt to initialize the MemPalace backend. Returns true on success.
315
+ * Handles auto-install and one-way auto-migration of legacy memories.
316
+ * Never throws — any failure returns false so the SQLite fallback runs.
317
+ */
318
+ private tryInitMempalace(): boolean {
319
+ let install: MempalaceInstall | null;
320
+ try {
321
+ install = ensureMempalace();
322
+ } catch {
323
+ return false;
324
+ }
325
+ if (!install) return false;
326
+
327
+ // Sanity ping — if the palace/bridge is broken, fall back.
328
+ const ok = runBridge<string>(install, this.palacePath, "ping");
329
+ if (ok !== "pong") return false;
330
+
331
+ this.mempalaceInstall = install;
332
+ this.backend = "mempalace";
333
+
334
+ // One-way auto-migration of legacy memories (idempotent).
335
+ if (!isMigrated()) {
336
+ try {
337
+ runBridge(install, this.palacePath, "migrate", {
338
+ source_dir: getMemoryBaseDir(),
339
+ });
340
+ markMigrated();
341
+ } catch {
342
+ // Migration failed — palace still usable for new stores; legacy
343
+ // memories can be re-migrated later. Do not block.
344
+ }
345
+ }
346
+
347
+ return true;
348
+ }
349
+
267
350
  /**
268
351
  * Open database and set up schema. Called by init() with retry logic.
269
352
  */
@@ -341,6 +424,7 @@ export class MemoryStorage {
341
424
  * Check if database is healthy.
342
425
  */
343
426
  isHealthy(): boolean {
427
+ if (this.isMempalace()) return true;
344
428
  if (!this.db) return false;
345
429
  try {
346
430
  this.db.prepare("SELECT 1").get();
@@ -355,8 +439,6 @@ export class MemoryStorage {
355
439
  * Uses transaction to ensure atomicity — either all writes succeed or none do.
356
440
  */
357
441
  store(record: MemoryRecord): void {
358
- if (!this.db) throw new Error("Storage not initialized");
359
-
360
442
  // Generate ID from title if not provided
361
443
  if (!record.id) {
362
444
  record.id = record.title.toLowerCase().replace(/[^a-z0-9]+/g, "_");
@@ -370,6 +452,13 @@ export class MemoryStorage {
370
452
  // Set project if not provided
371
453
  if (!record.project) record.project = this.projectName;
372
454
 
455
+ if (this.isMempalace()) {
456
+ this.storeMempalace(record);
457
+ return;
458
+ }
459
+
460
+ if (!this.db) throw new Error("Storage not initialized");
461
+
373
462
  // Prepare markdown content BEFORE transaction (fail fast)
374
463
  const mdPath = path.join(this.scopeDir, `${record.id}.md`);
375
464
  const frontmatter: MemoryFrontmatter = {
@@ -444,6 +533,37 @@ export class MemoryStorage {
444
533
  }
445
534
  }
446
535
 
536
+ /**
537
+ * Store a record via the MemPalace backend. Also writes the markdown
538
+ * tier so the human-readable file and legacy migration source stay
539
+ * consistent and durable as a fallback source.
540
+ */
541
+ private storeMempalace(record: MemoryRecord): void {
542
+ const install = this.mempalaceInstall!;
543
+ runBridge(install, this.palacePath, "store", {
544
+ record: {
545
+ id: record.id,
546
+ title: record.title,
547
+ content: record.content,
548
+ tags: record.tags,
549
+ project: record.project,
550
+ type: record.type,
551
+ created: record.created,
552
+ updated: record.updated,
553
+ source_kind: "markdown",
554
+ },
555
+ });
556
+ // Markdown tier (durable human copy + fallback source).
557
+ try {
558
+ const mdPath = path.join(this.scopeDir, `${record.id}.md`);
559
+ const dir = path.dirname(mdPath);
560
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
561
+ writeMemoryFile(mdPath, record);
562
+ } catch {
563
+ // Palace write succeeded; markdown is best-effort.
564
+ }
565
+ }
566
+
447
567
  /**
448
568
  * Sync orphaned markdown files into the database.
449
569
  * Reads all .md files in the project dir, parses frontmatter,
@@ -451,6 +571,15 @@ export class MemoryStorage {
451
571
  * Returns count of synced files.
452
572
  */
453
573
  syncOrphanedFiles(): number {
574
+ if (this.isMempalace()) {
575
+ const install = this.mempalaceInstall!;
576
+ const synced = runBridge<number>(install, this.palacePath, "sync_orphaned", {
577
+ project_dir: this.scopeDir,
578
+ wing: this.projectName,
579
+ });
580
+ return synced ?? 0;
581
+ }
582
+
454
583
  if (!this.db) throw new Error("Storage not initialized");
455
584
 
456
585
  const files = fs.readdirSync(this.scopeDir)
@@ -506,6 +635,13 @@ export class MemoryStorage {
506
635
  * Check if a memory with the given title already exists.
507
636
  */
508
637
  hasByTitle(title: string): boolean {
638
+ if (this.isMempalace()) {
639
+ const install = this.mempalaceInstall!;
640
+ return runBridge<boolean>(install, this.palacePath, "has_title", {
641
+ wing: this.projectName,
642
+ title,
643
+ }) ?? false;
644
+ }
509
645
  if (!this.db) throw new Error("Storage not initialized");
510
646
  const id = title.toLowerCase().replace(/[^a-z0-9]+/g, "_");
511
647
  const row = this.db.prepare("SELECT 1 FROM memories WHERE id = ?").get(id);
@@ -517,6 +653,14 @@ export class MemoryStorage {
517
653
  * Returns array of { record, similarity } sorted by similarity desc.
518
654
  */
519
655
  findSimilarByTitle(title: string, threshold = 0.6): Array<{ record: MemoryRecord; similarity: number }> {
656
+ if (this.isMempalace()) {
657
+ const install = this.mempalaceInstall!;
658
+ const rows = runBridge<Array<{ record: MempalaceRecord; similarity: number }>>(
659
+ install, this.palacePath, "find_similar",
660
+ { wing: this.projectName, title, threshold },
661
+ ) ?? [];
662
+ return rows.map((r) => ({ record: toMemoryRecord(r.record), similarity: r.similarity }));
663
+ }
520
664
  if (!this.db) throw new Error("Storage not initialized");
521
665
 
522
666
  const allRows = this.db.prepare("SELECT id, title FROM memories").all() as MemoryRow[];
@@ -549,6 +693,11 @@ export class MemoryStorage {
549
693
  * Get a memory record by ID.
550
694
  */
551
695
  getById(id: string): MemoryRecord | null {
696
+ if (this.isMempalace()) {
697
+ const install = this.mempalaceInstall!;
698
+ const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get", { id });
699
+ return rec ? toMemoryRecord(rec) : null;
700
+ }
552
701
  if (!this.db) throw new Error("Storage not initialized");
553
702
 
554
703
  const row = this.db.prepare("SELECT * FROM memories WHERE id = ?").get(id) as MemoryRow | undefined;
@@ -571,6 +720,14 @@ export class MemoryStorage {
571
720
  * Get a memory record by title (fuzzy match).
572
721
  */
573
722
  getByTitle(title: string): MemoryRecord | null {
723
+ if (this.isMempalace()) {
724
+ const install = this.mempalaceInstall!;
725
+ const rec = runBridge<MempalaceRecord | null>(install, this.palacePath, "get_by_title", {
726
+ wing: this.projectName,
727
+ title,
728
+ });
729
+ return rec ? toMemoryRecord(rec) : null;
730
+ }
574
731
  if (!this.db) throw new Error("Storage not initialized");
575
732
 
576
733
  // Try exact match first
@@ -610,6 +767,13 @@ export class MemoryStorage {
610
767
  * List all memories (titles only).
611
768
  */
612
769
  listAll(): Array<{ id: string; title: string; type: string }> {
770
+ if (this.isMempalace()) {
771
+ const install = this.mempalaceInstall!;
772
+ const items = runBridge<MempalaceListItem[]>(install, this.palacePath, "list", {
773
+ wing: this.projectName,
774
+ }) ?? [];
775
+ return items;
776
+ }
613
777
  if (!this.db) throw new Error("Storage not initialized");
614
778
 
615
779
  const rows = this.db.prepare("SELECT id, title, type FROM memories ORDER BY updated DESC").all() as MemoryRow[];
@@ -620,6 +784,16 @@ export class MemoryStorage {
620
784
  * Delete a memory by ID.
621
785
  */
622
786
  delete(id: string): boolean {
787
+ if (this.isMempalace()) {
788
+ const install = this.mempalaceInstall!;
789
+ const ok = runBridge<boolean>(install, this.palacePath, "delete", { id }) ?? false;
790
+ // Also remove the markdown tier if present.
791
+ try {
792
+ const mdPath = path.join(this.scopeDir, `${id}.md`);
793
+ if (fs.existsSync(mdPath)) fs.unlinkSync(mdPath);
794
+ } catch { /* ignore */ }
795
+ return ok;
796
+ }
623
797
  if (!this.db) throw new Error("Storage not initialized");
624
798
 
625
799
  // Delete from vector table
@@ -658,6 +832,19 @@ export class MemoryStorage {
658
832
  * Search memories using hybrid approach.
659
833
  */
660
834
  search(query: string, limit = 10, embedding?: Float32Array | null): SearchResult[] {
835
+ if (this.isMempalace()) {
836
+ const install = this.mempalaceInstall!;
837
+ const rows = runBridge<MempalaceSearchResult[]>(install, this.palacePath, "search", {
838
+ query,
839
+ wing: this.projectName,
840
+ limit,
841
+ }) ?? [];
842
+ return rows.map((r) => ({
843
+ record: toMemoryRecord(r),
844
+ score: r.score,
845
+ snippet: r.snippet,
846
+ }));
847
+ }
661
848
  if (!this.db) throw new Error("Storage not initialized");
662
849
 
663
850
  const results: Map<string, SearchResult> = new Map();
@@ -801,6 +988,21 @@ export function searchAllProjects(
801
988
  query: string,
802
989
  limit = 10
803
990
  ): SearchResult[] {
991
+ // MemPalace global path: query across all wings in one call.
992
+ const install = ensureMempalace();
993
+ if (install) {
994
+ const rows = runBridge<MempalaceSearchResult[]>(install, DEFAULT_PALACE, "search", {
995
+ query,
996
+ limit,
997
+ }) ?? [];
998
+ return rows.map((r) => ({
999
+ record: toMemoryRecord(r),
1000
+ score: r.score,
1001
+ snippet: r.snippet,
1002
+ }));
1003
+ }
1004
+
1005
+ // SQLite fallback: iterate project directories.
804
1006
  const projectDirs = getAllProjectDirs();
805
1007
  const allResults: SearchResult[] = [];
806
1008
 
@@ -835,6 +1037,19 @@ export function listAllProjects(): Array<{
835
1037
  title: string;
836
1038
  type: string;
837
1039
  }> {
1040
+ // MemPalace global path: list all drawers across wings.
1041
+ const install = ensureMempalace();
1042
+ if (install) {
1043
+ const items = runBridge<MempalaceListItemAll[]>(install, DEFAULT_PALACE, "list_all", {}) ?? [];
1044
+ return items.map((m) => ({
1045
+ project: m.project,
1046
+ id: m.id,
1047
+ title: m.title,
1048
+ type: m.type,
1049
+ }));
1050
+ }
1051
+
1052
+ // SQLite fallback: iterate project directories.
838
1053
  const projectDirs = getAllProjectDirs();
839
1054
  const allMemories: Array<{
840
1055
  project: string;
File without changes
@@ -6,14 +6,15 @@
6
6
  * loads SKILL.md content and sends it as a user message.
7
7
  */
8
8
 
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
9
11
  import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
10
12
  import { UNIPI_PREFIX, MILESTONE_COMMANDS, MILESTONE_DIRS } from "@pi-unipi/core";
11
13
  import { parseMilestones } from "./milestone.js";
12
14
  import { readFileSync } from "node:fs";
13
- import { join } from "node:path";
14
15
 
15
16
  /** Resolve the skills directory relative to this file */
16
- const SKILLS_DIR = join(new URL(".", import.meta.url).pathname, "skills");
17
+ const SKILLS_DIR = join(dirname(fileURLToPath(import.meta.url)), "skills");
17
18
 
18
19
  /**
19
20
  * Load SKILL.md content for a given skill name.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/milestone",
3
- "version": "2.0.13",
3
+ "version": "2.1.0",
4
4
  "description": "Lifecycle layer for project-level goals \u2014 MILESTONES.md tracking, session hooks, auto-sync",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -29,11 +29,11 @@
29
29
  "access": "public"
30
30
  },
31
31
  "dependencies": {
32
- "@pi-unipi/core": "2.0.13"
32
+ "@pi-unipi/core": "2.1.0"
33
33
  },
34
34
  "peerDependencies": {
35
- "@earendil-works/pi-coding-agent": "^0.78.0",
36
- "@earendil-works/pi-tui": "^0.78.0",
35
+ "@earendil-works/pi-coding-agent": "^0.80.0",
36
+ "@earendil-works/pi-tui": "^0.80.0",
37
37
  "typebox": "^1.1.38"
38
38
  },
39
39
  "devDependencies": {
File without changes
File without changes
File without changes
@@ -5,6 +5,8 @@
5
5
  * Bridges agent lifecycle events to external platforms (native OS, Gotify, Telegram).
6
6
  */
7
7
 
8
+ import { dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
9
11
  import {
10
12
  UNIPI_EVENTS,
@@ -24,7 +26,7 @@ import {
24
26
  } from "./events.js";
25
27
 
26
28
  /** Package version */
27
- const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
29
+ const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
28
30
 
29
31
  export default function (pi: ExtensionAPI) {
30
32
 
File without changes
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "2.0.13",
4
- "description": "Cross-platform notification extension for Pi \u2014 native OS, Gotify, and Telegram notifications for agent lifecycle events",
3
+ "version": "2.1.0",
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",
7
7
  "license": "MIT",
@@ -42,16 +42,18 @@
42
42
  "access": "public"
43
43
  },
44
44
  "dependencies": {
45
- "@pi-unipi/core": "2.0.13",
45
+ "@pi-unipi/core": "*",
46
46
  "node-notifier": "^10.0.1"
47
47
  },
48
48
  "peerDependencies": {
49
- "@earendil-works/pi-coding-agent": "^0.78.0",
50
- "@earendil-works/pi-tui": "^0.78.0",
49
+ "@earendil-works/pi-coding-agent": "^0.80.0",
50
+ "@earendil-works/pi-tui": "^0.80.0",
51
51
  "typebox": "^1.1.38"
52
52
  },
53
53
  "pi": {
54
- "extensions": [],
54
+ "extensions": [
55
+ "./index.ts"
56
+ ],
55
57
  "skills": [],
56
58
  "prompts": [],
57
59
  "themes": []
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -5,6 +5,8 @@
5
5
  * Emits MODULE_READY, RALPH_LOOP_START/END events.
6
6
  */
7
7
 
8
+ import { dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
8
10
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
9
11
  import {
10
12
  UNIPI_EVENTS,
@@ -23,7 +25,7 @@ import { RalphLoopManager } from "./ralph-loop.js";
23
25
  import { registerRalphTools } from "./tools.js";
24
26
 
25
27
  /** Package version */
26
- const VERSION = getPackageVersion(new URL(".", import.meta.url).pathname);
28
+ const VERSION = getPackageVersion(dirname(fileURLToPath(import.meta.url)));
27
29
 
28
30
  /** Current loop manager instance (recreated on session reload) */
29
31
  let manager: RalphLoopManager | null = null;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/ralph",
3
- "version": "2.0.13",
3
+ "version": "2.1.0",
4
4
  "description": "Long-running iterative development loops for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -27,13 +27,13 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@pi-unipi/core": "2.0.13",
31
- "@pi-unipi/info-screen": "2.0.13"
30
+ "@pi-unipi/core": "2.1.0",
31
+ "@pi-unipi/info-screen": "2.1.0"
32
32
  },
33
33
  "peerDependencies": {
34
- "@earendil-works/pi-ai": "^0.78.0",
35
- "@earendil-works/pi-coding-agent": "^0.78.0",
36
- "@earendil-works/pi-tui": "^0.78.0",
34
+ "@earendil-works/pi-ai": "^0.80.0",
35
+ "@earendil-works/pi-coding-agent": "^0.80.0",
36
+ "@earendil-works/pi-tui": "^0.80.0",
37
37
  "typebox": "^1.1.38"
38
38
  },
39
39
  "pi": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "2.0.13",
3
+ "version": "2.1.0",
4
4
  "description": "Subagents for UniPi \u2014 parallel execution, file locking, workflow integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -17,17 +17,17 @@
17
17
  "test": "node --test src/__tests__/*.test.ts"
18
18
  },
19
19
  "dependencies": {
20
- "@pi-unipi/core": "2.0.13",
21
- "@pi-unipi/workflow": "2.0.13",
22
- "@pi-unipi/info-screen": "2.0.13",
23
- "@earendil-works/pi-agent-core": "^0.78.0"
20
+ "@pi-unipi/core": "2.1.0",
21
+ "@pi-unipi/workflow": "2.1.0",
22
+ "@pi-unipi/info-screen": "2.1.0",
23
+ "@earendil-works/pi-agent-core": "^0.80.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "typescript": "^5.8.3"
27
27
  },
28
28
  "peerDependencies": {
29
- "@earendil-works/pi-coding-agent": "^0.78.0",
30
- "@earendil-works/pi-tui": "^0.78.0",
29
+ "@earendil-works/pi-coding-agent": "^0.80.0",
30
+ "@earendil-works/pi-tui": "^0.80.0",
31
31
  "typebox": "^1.1.38"
32
32
  },
33
33
  "pi": {
File without changes
File without changes
File without changes
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "2.0.13",
3
+ "version": "2.1.0",
4
4
  "description": "Auto-updater, changelog browser, and readme browser for Unipi \u2014 checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -33,11 +33,11 @@
33
33
  "access": "public"
34
34
  },
35
35
  "dependencies": {
36
- "@pi-unipi/core": "2.0.13"
36
+ "@pi-unipi/core": "2.1.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@earendil-works/pi-coding-agent": "^0.78.0",
40
- "@earendil-works/pi-tui": "^0.78.0"
39
+ "@earendil-works/pi-coding-agent": "^0.80.0",
40
+ "@earendil-works/pi-tui": "^0.80.0"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "^25.6.0",
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -6,6 +6,8 @@
6
6
  * node_modules/@pi-unipi/{name}/README.md.
7
7
  */
8
8
 
9
+ import { dirname } from "node:path";
10
+ import { fileURLToPath } from "node:url";
9
11
  import { existsSync, readFileSync } from "fs";
10
12
  import { join } from "path";
11
13
  import { findPackageRoot } from "@pi-unipi/core";
@@ -39,7 +41,7 @@ const PACKAGE_MAP: Record<string, string> = {
39
41
  /** Resolve the unipi package root directory */
40
42
  function resolveUnipiRoot(): string {
41
43
  // Walk up from this file to find @pi-unipi/unipi by name
42
- const dir = new URL(".", import.meta.url).pathname;
44
+ const dir = dirname(fileURLToPath(import.meta.url));
43
45
  const root = findPackageRoot(dir, "@pi-unipi/unipi");
44
46
  return root ?? join(dir, ".."); // fallback to parent
45
47
  }