@pi-unipi/unipi 3.0.0-alpha.1 → 3.0.0-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [3.0.0-alpha.2] — 2026-09-26
10
+
11
+ ### Fixed
12
+
13
+ - **kanboard: the crate reports the right version.** `crates/kanboard/Cargo.toml` (and its `Cargo.lock`) stayed at alpha.0 through the alpha.1 bump, so `unipi-kanboard --version` lied. `scripts/sync-pins.mjs` is now the single bump entry point — one run sets every `version` field (root, workspaces, `packages/kanboard-bin/<platform>`) and the `[package] version =` line of every `crates/*/Cargo.toml`.
14
+ - **memory: the migration counter now counts every finished record.** `/unipi:memory status` and the completion notify showed `done 4281/4288` on a clean run because records with no old drawer were finished without incrementing `done`. A clean migration now ends `4288/4288 · 0 failed` (failed units reported separately as `· N failed`).
15
+ - **tests: no more real-HOME/env leakage.** The workflow permission-settings test and the utility skill-judge test now sandbox `HOME` and clear `OPENROUTER_API_KEY`/`TYPESAFE_API_KEY`, so `npm test` passes in a normal shell; the kanboard binary-resolution test honors the env → platform-package → dev-build precedence when a published platform package is installed.
16
+
17
+ ### Removed
18
+
19
+ - `scripts/test-subagents-tarball.mjs` — stale v2-era script expecting a `dist/` build `@pi-unipi/subagents` never produces (the package ships `src/`); unreferenced by package.json, CI, or the release chore.
20
+
9
21
  ## [3.0.0-alpha.1] — 2026-09-25
10
22
 
11
23
  ### Breaking Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/unipi",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "All-in-one extension suite for Pi coding agent",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -83,27 +83,27 @@
83
83
  "typebox": "^1.1.38"
84
84
  },
85
85
  "dependencies": {
86
- "@pi-unipi/ask-user": "3.0.0-alpha.1",
87
- "@pi-unipi/background-tasks": "3.0.0-alpha.1",
88
- "@pi-unipi/btw": "3.0.0-alpha.1",
89
- "@pi-unipi/command-enchantment": "3.0.0-alpha.1",
90
- "@pi-unipi/compactor": "3.0.0-alpha.1",
91
- "@pi-unipi/core": "3.0.0-alpha.1",
92
- "@pi-unipi/footer": "3.0.0-alpha.1",
93
- "@pi-unipi/image": "3.0.0-alpha.1",
94
- "@pi-unipi/fusion": "3.0.0-alpha.1",
95
- "@pi-unipi/info-screen": "3.0.0-alpha.1",
96
- "@pi-unipi/input-shortcuts": "3.0.0-alpha.1",
97
- "@pi-unipi/kanboard": "3.0.0-alpha.1",
98
- "@pi-unipi/mcp": "3.0.0-alpha.1",
99
- "@pi-unipi/memory": "3.0.0-alpha.1",
100
- "@pi-unipi/notify": "3.0.0-alpha.1",
101
- "@pi-unipi/skill-registry": "3.0.0-alpha.1",
102
- "@pi-unipi/subagents": "3.0.0-alpha.1",
103
- "@pi-unipi/updater": "3.0.0-alpha.1",
104
- "@pi-unipi/utility": "3.0.0-alpha.1",
105
- "@pi-unipi/web-api": "3.0.0-alpha.1",
106
- "@pi-unipi/workflow": "3.0.0-alpha.1"
86
+ "@pi-unipi/ask-user": "3.0.0-alpha.2",
87
+ "@pi-unipi/background-tasks": "3.0.0-alpha.2",
88
+ "@pi-unipi/btw": "3.0.0-alpha.2",
89
+ "@pi-unipi/command-enchantment": "3.0.0-alpha.2",
90
+ "@pi-unipi/compactor": "3.0.0-alpha.2",
91
+ "@pi-unipi/core": "3.0.0-alpha.2",
92
+ "@pi-unipi/footer": "3.0.0-alpha.2",
93
+ "@pi-unipi/image": "3.0.0-alpha.2",
94
+ "@pi-unipi/fusion": "3.0.0-alpha.2",
95
+ "@pi-unipi/info-screen": "3.0.0-alpha.2",
96
+ "@pi-unipi/input-shortcuts": "3.0.0-alpha.2",
97
+ "@pi-unipi/kanboard": "3.0.0-alpha.2",
98
+ "@pi-unipi/mcp": "3.0.0-alpha.2",
99
+ "@pi-unipi/memory": "3.0.0-alpha.2",
100
+ "@pi-unipi/notify": "3.0.0-alpha.2",
101
+ "@pi-unipi/skill-registry": "3.0.0-alpha.2",
102
+ "@pi-unipi/subagents": "3.0.0-alpha.2",
103
+ "@pi-unipi/updater": "3.0.0-alpha.2",
104
+ "@pi-unipi/utility": "3.0.0-alpha.2",
105
+ "@pi-unipi/web-api": "3.0.0-alpha.2",
106
+ "@pi-unipi/workflow": "3.0.0-alpha.2"
107
107
  },
108
108
  "devDependencies": {
109
109
  "@earendil-works/pi-agent-core": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/ask-user",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Structured user input tool for Pi coding agent \u2014 single-select, multi-select, freeform",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1"
43
+ "@pi-unipi/core": "3.0.0-alpha.2"
44
44
  },
45
45
  "peerDependencies": {
46
46
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/command-enchantment",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Enhanced TUI autocomplete for /unipi:* commands \u2014 colored, sorted, and grouped by package",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Enhanced TUI autocomplete for /unipi:* commands — colored, sorted, and grouped by package",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/background-tasks",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Background tasks for UniPi \u2014 durable shell jobs and delegated background agents with a footer dock",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Background tasks for UniPi — durable shell jobs and delegated background agents with a footer dock",
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.87.1",
13
- "@pi-unipi/core": "3.0.0-alpha.1",
13
+ "@pi-unipi/core": "3.0.0-alpha.2",
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": "3.0.0-alpha.1",
4
- "description": "A pi extension for parallel side conversations with /unipi:btw \u2014 part of the Unipi suite",
3
+ "version": "3.0.0-alpha.2",
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.87.1"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.1"
40
+ "@pi-unipi/core": "3.0.0-alpha.2"
41
41
  },
42
42
  "pi": {
43
43
  "extensions": [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/compactor",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Context engine for Pi \u2014 zero-LLM compaction, session continuity, sandbox execution, and tool display optimization",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1",
38
- "@pi-unipi/info-screen": "3.0.0-alpha.1",
37
+ "@pi-unipi/core": "3.0.0-alpha.2",
38
+ "@pi-unipi/info-screen": "3.0.0-alpha.2",
39
39
  "@earendil-works/pi-agent-core": "^0.87.1"
40
40
  },
41
41
  "peerDependencies": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/core",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Shared utilities, event types, and constants for Unipi extension suite",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/footer",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Persistent status bar for Unipi \u2014 subscribes to UNIPI_EVENTS and renders key stats from all unipi packages",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1",
36
- "@pi-unipi/background-tasks": "3.0.0-alpha.1"
35
+ "@pi-unipi/core": "3.0.0-alpha.2",
36
+ "@pi-unipi/background-tasks": "3.0.0-alpha.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/fusion",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Devin-style model picker, fusion presets (lead + sidekick), and Local Fusion runtime for UniPi",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -32,8 +32,8 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.1",
36
- "@pi-unipi/subagents": "3.0.0-alpha.1"
35
+ "@pi-unipi/core": "3.0.0-alpha.2",
36
+ "@pi-unipi/subagents": "3.0.0-alpha.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-ai": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/image",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1"
37
+ "@pi-unipi/core": "3.0.0-alpha.2"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-ai": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/info-screen",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Dashboard and module registry for Unipi \u2014 configurable info overlay with tabbed groups",
3
+ "version": "3.0.0-alpha.2",
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",
@@ -32,7 +32,7 @@
32
32
  "access": "public"
33
33
  },
34
34
  "dependencies": {
35
- "@pi-unipi/core": "3.0.0-alpha.1"
35
+ "@pi-unipi/core": "3.0.0-alpha.2"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/input-shortcuts",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Keyboard shortcuts for stash/restore, undo/redo, clipboard, and thinking toggle \u2014 chord-based overlay system",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1"
36
+ "@pi-unipi/core": "3.0.0-alpha.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/kanboard",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Kanboard v3 \u2014 deferred-work board: CLI/daemon bridge, task runner and skill",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Kanboard v3 — deferred-work board: CLI/daemon bridge, task runner and skill",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "license": "MIT",
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@pi-unipi/core": "3.0.0-alpha.1"
34
+ "@pi-unipi/core": "3.0.0-alpha.2"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -48,10 +48,10 @@
48
48
  "themes": []
49
49
  },
50
50
  "optionalDependencies": {
51
- "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.1",
52
- "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.1",
53
- "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.1",
54
- "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.1",
55
- "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.1"
51
+ "@pi-unipi/kanboard-linux-x64": "3.0.0-alpha.2",
52
+ "@pi-unipi/kanboard-linux-arm64": "3.0.0-alpha.2",
53
+ "@pi-unipi/kanboard-darwin-x64": "3.0.0-alpha.2",
54
+ "@pi-unipi/kanboard-darwin-arm64": "3.0.0-alpha.2",
55
+ "@pi-unipi/kanboard-win32-x64": "3.0.0-alpha.2"
56
56
  }
57
57
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/long-horizon",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Long-horizon execution for Pi \u2014 mode-gated /goal /ralph /swarm /graph orchestration with a TypeSafe prompt judge",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Long-horizon execution for Pi — mode-gated /goal /ralph /swarm /graph orchestration with a TypeSafe prompt judge",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@pi-unipi/core": "3.0.0-alpha.1"
40
+ "@pi-unipi/core": "3.0.0-alpha.2"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/mcp",
3
- "version": "3.0.0-alpha.1",
4
- "description": "MCP server management extension for Pi coding agent \u2014 browse, add, configure, and use MCP servers",
3
+ "version": "3.0.0-alpha.2",
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": "3.0.0-alpha.1"
33
+ "@pi-unipi/core": "3.0.0-alpha.2"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -124,7 +124,7 @@ export function registerMemoryCommands(
124
124
  ];
125
125
  if (b?.installIssue) lines.push(`mode: ${b.installIssue}`);
126
126
  if (conv) {
127
- lines.push(`migration: ${conv.phase} ${conv.done}/${conv.total} (${conv.errors.length} errors)`);
127
+ lines.push(`migration: ${conv.phase} ${conv.done}/${conv.total} · ${conv.failedUnits ?? 0} failed${conv.errors.length ? ` (${conv.errors.length} errors)` : ""}`);
128
128
  if (conv.backupPath) lines.push(`palace backup: ${conv.backupPath}`);
129
129
  if (conv.mdBackupPath) lines.push(`memory backup: ${conv.mdBackupPath}`);
130
130
  } else if (needsMigration()) {
@@ -187,7 +187,7 @@ export function registerMemoryCommands(
187
187
  : "";
188
188
  ctx.ui.notify(
189
189
  final?.phase === "done"
190
- ? `Memory migration complete — ${final.done}/${final.total} records.${backups}`
190
+ ? `Memory migration complete — ${final.done}/${final.total} records${final.failedUnits ? ` · ${final.failedUnits} failed` : ""}.${backups}`
191
191
  : `Memory migration ${final?.phase ?? "failed"}${final?.errors.length ? ` — ${final.errors.length} errors` : ""}.${backups}`,
192
192
  final?.phase === "done" ? "info" : "warning",
193
193
  );
@@ -50,6 +50,8 @@ export interface ConversionState {
50
50
  deletedSources?: Record<string, string>;
51
51
  /** Unverified units already got their one re-mine. */
52
52
  remined?: boolean;
53
+ /** Units that finished with a failure (kept after the units file is gone). */
54
+ failedUnits?: number;
53
55
  /** ~/.unipi/memory tree backup (parallel to the palace backup). */
54
56
  mdBackupPath?: string;
55
57
  /** True when the conversion itself started the daemon — stopped on finish
@@ -741,6 +743,14 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
741
743
  u.deleted = true;
742
744
  }
743
745
  }
746
+ // `done` counts EVERY finished unit (deleted old drawer or not); failed
747
+ // units stay separate so a clean run ends at total/total.
748
+ const recount = (): void => {
749
+ const { done, failed } = countFinished(units);
750
+ state.done = done;
751
+ state.failedUnits = failed;
752
+ };
753
+ recount();
744
754
  const pending = units.filter((u) => u.verified && !u.deleted && !u.failed);
745
755
  // Bulk delete in ≤500-id jobs keyed by drawer id (each removes its chunk group).
746
756
  const idToUnit = new Map<string, UnitState>();
@@ -748,7 +758,6 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
748
758
  for (const id of u.drawerIds ?? []) idToUnit.set(id, u);
749
759
  }
750
760
  const batches = planDeleteBatches(pending);
751
- let done = state.done;
752
761
  for (const batch of batches) {
753
762
  const res = await deleteDrawers(batch);
754
763
  if (res === "unsupported") break; // older MemPalace — per-source fallback below
@@ -758,13 +767,13 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
758
767
  if (u && !u.deleted) {
759
768
  u.deleted = true;
760
769
  if (u.oldSource) state.deletedSources![`${u.project}/${u.rec.id}`] = u.oldSource;
761
- done += 1;
762
770
  }
763
771
  }
764
772
  } else {
765
773
  state.errors.push("bulk delete batch failed");
766
774
  }
767
775
  writeUnitsState(units);
776
+ recount();
768
777
  writeConversionState(state);
769
778
  }
770
779
  // Fallback 1: exact drawer ids collected at scan time, pipelined.
@@ -777,12 +786,12 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
777
786
  if (outs.every((o) => o === "deleted" || o === "absent")) {
778
787
  u.deleted = true;
779
788
  if (u.oldSource) state.deletedSources![`${u.project}/${u.rec.id}`] = u.oldSource;
780
- done += 1;
781
789
  } else {
782
790
  state.errors.push(`delete drawers of ${u.project}/${u.rec.id} failed`);
783
791
  }
784
792
  }
785
793
  writeUnitsState(units);
794
+ recount();
786
795
  writeConversionState(state);
787
796
  // Fallback 2 (units with no known drawer ids): delete_by_source on the
788
797
  // stamped source, then the synthesized legacy URI.
@@ -802,17 +811,17 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
802
811
  if (n !== null && n > 0) {
803
812
  u.deleted = true;
804
813
  if (u.oldSource) state.deletedSources![`${u.project}/${u.rec.id}`] = u.oldSource;
805
- done += 1;
806
814
  }
807
815
  });
808
816
  remaining = remaining.filter((u) => !u.deleted);
809
817
  writeUnitsState(units);
818
+ recount();
810
819
  writeConversionState(state);
811
820
  }
812
821
  // Nothing matched any candidate but a job succeeded: the old drawer is
813
822
  // already gone — done. Units whose every job failed stay for a later run.
814
823
  for (const u of remaining) {
815
- if (anyOk.has(u)) { u.deleted = true; done += 1; }
824
+ if (anyOk.has(u)) u.deleted = true;
816
825
  }
817
826
  // Orphaned flat-path drawers: any earlier direct `mempalace mine <dir>`
818
827
  // indexed the flat file under its old absolute path — now orphaned.
@@ -840,7 +849,7 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
840
849
  if (failed) state.errors.push(`flat-path delete_by_source: ${failed}/${flatSrcs.length} jobs failed`);
841
850
  }
842
851
  writeUnitsState(units);
843
- state.done = done;
852
+ recount();
844
853
  state.phase = units.every((u) => u.deleted || u.failed) ? "done" : "delete";
845
854
  writeConversionState(state);
846
855
  }
@@ -857,6 +866,16 @@ async function runConversionInner(deps: ConversionDeps): Promise<ConversionState
857
866
  return state;
858
867
  }
859
868
 
869
+ /** Finished-unit counters for the delete phase: `done` = every unit that
870
+ * reached `deleted` (whether or not it had an old drawer to remove);
871
+ * `failed` = units that errored. Exported for tests. */
872
+ export function countFinished(units: Array<{ deleted?: boolean; failed?: boolean }>): { done: number; failed: number } {
873
+ return {
874
+ done: units.filter((u) => u.deleted && !u.failed).length,
875
+ failed: units.filter((u) => u.failed).length,
876
+ };
877
+ }
878
+
860
879
  /** Drop the legacy state markers once conversion is fully done. */
861
880
  function finishConversion(): void {
862
881
  const root = memoryRoot();
@@ -336,7 +336,7 @@ export default function (pi: ExtensionAPI) {
336
336
  pending: { value: String(pendingCount()) },
337
337
  migrate: {
338
338
  value: conv && conv.phase !== "done"
339
- ? `${conv.phase} ${conv.done}/${conv.total}`
339
+ ? `${conv.phase} ${conv.done}/${conv.total}${conv.failedUnits ? ` · ${conv.failedUnits} failed` : ""}`
340
340
  : needsMigration() ? "needed" : "—",
341
341
  },
342
342
  };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/memory",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Persistent cross-session memory on MemPalace (auto-installed): daemon-routed writes, shared palace with other agents, for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -42,8 +42,8 @@
42
42
  "README.md"
43
43
  ],
44
44
  "dependencies": {
45
- "@pi-unipi/core": "3.0.0-alpha.1",
46
- "@pi-unipi/info-screen": "3.0.0-alpha.1",
45
+ "@pi-unipi/core": "3.0.0-alpha.2",
46
+ "@pi-unipi/info-screen": "3.0.0-alpha.2",
47
47
  "js-yaml": "^4.1.0"
48
48
  },
49
49
  "peerDependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/notify",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Cross-platform notification extension for Pi \u2014 native OS, Gotify, and Telegram notifications for agent lifecycle events",
3
+ "version": "3.0.0-alpha.2",
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",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "3.0.0-alpha.1",
37
+ "@pi-unipi/core": "3.0.0-alpha.2",
38
38
  "node-notifier": "^10.0.1"
39
39
  },
40
40
  "peerDependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/skill-registry",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Placeholder for the upcoming unipi skill registry \u2014 hosts the bundled workflow skills",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Placeholder for the upcoming unipi skill registry — hosts the bundled workflow skills",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Neuron Mr White",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/subagents",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Subagents for UniPi \u2014 parallel execution, file locking, workflow integration",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Subagents for UniPi — parallel execution, file locking, workflow integration",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "types": "src/index.ts",
@@ -9,7 +9,7 @@
9
9
  "test": "npx tsx --test src/__tests__/*.test.ts"
10
10
  },
11
11
  "dependencies": {
12
- "@pi-unipi/core": "3.0.0-alpha.1",
12
+ "@pi-unipi/core": "3.0.0-alpha.2",
13
13
  "@earendil-works/pi-agent-core": "^0.87.1",
14
14
  "acorn": "8.18.0"
15
15
  },
@@ -14168,13 +14168,18 @@ async function runConversionInner(deps) {
14168
14168
  u.deleted = true;
14169
14169
  }
14170
14170
  }
14171
+ const recount = () => {
14172
+ const { done, failed } = countFinished(units);
14173
+ state2.done = done;
14174
+ state2.failedUnits = failed;
14175
+ };
14176
+ recount();
14171
14177
  const pending = units.filter((u) => u.verified && !u.deleted && !u.failed);
14172
14178
  const idToUnit = /* @__PURE__ */ new Map();
14173
14179
  for (const u of pending) {
14174
14180
  for (const id of u.drawerIds ?? []) idToUnit.set(id, u);
14175
14181
  }
14176
14182
  const batches = planDeleteBatches(pending);
14177
- let done = state2.done;
14178
14183
  for (const batch of batches) {
14179
14184
  const res = await deleteDrawers(batch);
14180
14185
  if (res === "unsupported") break;
@@ -14184,13 +14189,13 @@ async function runConversionInner(deps) {
14184
14189
  if (u && !u.deleted) {
14185
14190
  u.deleted = true;
14186
14191
  if (u.oldSource) state2.deletedSources[`${u.project}/${u.rec.id}`] = u.oldSource;
14187
- done += 1;
14188
14192
  }
14189
14193
  }
14190
14194
  } else {
14191
14195
  state2.errors.push("bulk delete batch failed");
14192
14196
  }
14193
14197
  writeUnitsState(units);
14198
+ recount();
14194
14199
  writeConversionState(state2);
14195
14200
  }
14196
14201
  const byId = pending.filter((u) => !u.deleted && u.drawerIds?.length);
@@ -14202,12 +14207,12 @@ async function runConversionInner(deps) {
14202
14207
  if (outs.every((o) => o === "deleted" || o === "absent")) {
14203
14208
  u.deleted = true;
14204
14209
  if (u.oldSource) state2.deletedSources[`${u.project}/${u.rec.id}`] = u.oldSource;
14205
- done += 1;
14206
14210
  } else {
14207
14211
  state2.errors.push(`delete drawers of ${u.project}/${u.rec.id} failed`);
14208
14212
  }
14209
14213
  }
14210
14214
  writeUnitsState(units);
14215
+ recount();
14211
14216
  writeConversionState(state2);
14212
14217
  const candidatesOf = (u) => [u.oldSource, legacySourceUri(u.origProject ?? u.project, u.rec.id)].filter((s) => !!s).filter((s, i, a) => a.indexOf(s) === i);
14213
14218
  let remaining = pending.filter((u) => !u.deleted && !u.drawerIds?.length);
@@ -14222,18 +14227,15 @@ async function runConversionInner(deps) {
14222
14227
  if (n !== null && n > 0) {
14223
14228
  u.deleted = true;
14224
14229
  if (u.oldSource) state2.deletedSources[`${u.project}/${u.rec.id}`] = u.oldSource;
14225
- done += 1;
14226
14230
  }
14227
14231
  });
14228
14232
  remaining = remaining.filter((u) => !u.deleted);
14229
14233
  writeUnitsState(units);
14234
+ recount();
14230
14235
  writeConversionState(state2);
14231
14236
  }
14232
14237
  for (const u of remaining) {
14233
- if (anyOk.has(u)) {
14234
- u.deleted = true;
14235
- done += 1;
14236
- }
14238
+ if (anyOk.has(u)) u.deleted = true;
14237
14239
  }
14238
14240
  const flatSrcs = units.filter((u) => u.deleted && u.rec.filePath).flatMap((u) => {
14239
14241
  const p = u.rec.filePath;
@@ -14252,7 +14254,7 @@ async function runConversionInner(deps) {
14252
14254
  if (failed) state2.errors.push(`flat-path delete_by_source: ${failed}/${flatSrcs.length} jobs failed`);
14253
14255
  }
14254
14256
  writeUnitsState(units);
14255
- state2.done = done;
14257
+ recount();
14256
14258
  state2.phase = units.every((u) => u.deleted || u.failed) ? "done" : "delete";
14257
14259
  writeConversionState(state2);
14258
14260
  }
@@ -14265,6 +14267,12 @@ async function runConversionInner(deps) {
14265
14267
  }
14266
14268
  return state2;
14267
14269
  }
14270
+ function countFinished(units) {
14271
+ return {
14272
+ done: units.filter((u) => u.deleted && !u.failed).length,
14273
+ failed: units.filter((u) => u.failed).length
14274
+ };
14275
+ }
14268
14276
  function finishConversion() {
14269
14277
  const root = memoryRoot();
14270
14278
  for (const name of [...LEGACY_MARKERS, path9.basename(UNITS_PATH())]) {
@@ -14501,7 +14509,7 @@ function registerMemoryCommands(pi, backend, overrides) {
14501
14509
  ];
14502
14510
  if (b?.installIssue) lines.push(`mode: ${b.installIssue}`);
14503
14511
  if (conv) {
14504
- lines.push(`migration: ${conv.phase} ${conv.done}/${conv.total} (${conv.errors.length} errors)`);
14512
+ lines.push(`migration: ${conv.phase} ${conv.done}/${conv.total} \xB7 ${conv.failedUnits ?? 0} failed${conv.errors.length ? ` (${conv.errors.length} errors)` : ""}`);
14505
14513
  if (conv.backupPath) lines.push(`palace backup: ${conv.backupPath}`);
14506
14514
  if (conv.mdBackupPath) lines.push(`memory backup: ${conv.mdBackupPath}`);
14507
14515
  } else if (needsMigration()) {
@@ -14555,7 +14563,7 @@ Backups: ${conv.backupPath}${conv.mdBackupPath ? `, ${conv.mdBackupPath}` : ""}`
14555
14563
  const backups = final?.backupPath || final?.mdBackupPath ? `
14556
14564
  Backups: ${[final?.backupPath, final?.mdBackupPath].filter(Boolean).join(", ")}` : "";
14557
14565
  ctx.ui.notify(
14558
- final?.phase === "done" ? `Memory migration complete \u2014 ${final.done}/${final.total} records.${backups}` : `Memory migration ${final?.phase ?? "failed"}${final?.errors.length ? ` \u2014 ${final.errors.length} errors` : ""}.${backups}`,
14566
+ final?.phase === "done" ? `Memory migration complete \u2014 ${final.done}/${final.total} records${final.failedUnits ? ` \xB7 ${final.failedUnits} failed` : ""}.${backups}` : `Memory migration ${final?.phase ?? "failed"}${final?.errors.length ? ` \u2014 ${final.errors.length} errors` : ""}.${backups}`,
14559
14567
  final?.phase === "done" ? "info" : "warning"
14560
14568
  );
14561
14569
  })();
@@ -14990,7 +14998,7 @@ function memory_default(pi) {
14990
14998
  write: { value: cfg.write && overrides.write !== false ? "on" : "off" },
14991
14999
  pending: { value: String(pendingCount()) },
14992
15000
  migrate: {
14993
- value: conv && conv.phase !== "done" ? `${conv.phase} ${conv.done}/${conv.total}` : needsMigration() ? "needed" : "\u2014"
15001
+ value: conv && conv.phase !== "done" ? `${conv.phase} ${conv.done}/${conv.total}${conv.failedUnits ? ` \xB7 ${conv.failedUnits} failed` : ""}` : needsMigration() ? "needed" : "\u2014"
14994
15002
  }
14995
15003
  };
14996
15004
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/updater",
3
- "version": "3.0.0-alpha.1",
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",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Auto-updater, changelog browser, and readme browser for Unipi — checks npm registry, renders CHANGELOG.md and README.md files in TUI overlays",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  "access": "public"
35
35
  },
36
36
  "dependencies": {
37
- "@pi-unipi/core": "3.0.0-alpha.1"
37
+ "@pi-unipi/core": "3.0.0-alpha.2"
38
38
  },
39
39
  "peerDependencies": {
40
40
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/utility",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Utility commands and tools for Pi coding agent \u2014 lifecycle, diagnostics, cache, analytics, display, batch execution",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Utility commands and tools for Pi coding agent — lifecycle, diagnostics, cache, analytics, display, batch execution",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
7
7
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "access": "public"
37
37
  },
38
38
  "dependencies": {
39
- "@pi-unipi/core": "3.0.0-alpha.1"
39
+ "@pi-unipi/core": "3.0.0-alpha.2"
40
40
  },
41
41
  "devDependencies": {},
42
42
  "peerDependencies": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pi-unipi/watchdog",
3
- "version": "3.0.0-alpha.1",
4
- "description": "Jev watchdog for long-running tool calls and background tasks \u2014 judges stuck work via TypeSafe System One and kills or warns",
3
+ "version": "3.0.0-alpha.2",
4
+ "description": "Jev watchdog for long-running tool calls and background tasks — judges stuck work via TypeSafe System One and kills or warns",
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/background-tasks": "3.0.0-alpha.1",
36
- "@pi-unipi/core": "3.0.0-alpha.1"
35
+ "@pi-unipi/background-tasks": "3.0.0-alpha.2",
36
+ "@pi-unipi/core": "3.0.0-alpha.2"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "@earendil-works/pi-coding-agent": "^0.87.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/web-api",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Web search, read, and summarize tools with provider-based backend selection for Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "README.md"
32
32
  ],
33
33
  "dependencies": {
34
- "@pi-unipi/core": "3.0.0-alpha.1",
34
+ "@pi-unipi/core": "3.0.0-alpha.2",
35
35
  "defuddle": "^0.18.1",
36
36
  "linkedom": "^0.18.12",
37
37
  "wreq-js": "^2.3.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-unipi/workflow",
3
- "version": "3.0.0-alpha.1",
3
+ "version": "3.0.0-alpha.2",
4
4
  "description": "Plan mode and permission modes for the Pi coding agent",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -27,7 +27,7 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@pi-unipi/core": "3.0.0-alpha.1"
30
+ "@pi-unipi/core": "3.0.0-alpha.2"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@earendil-works/pi-ai": "^0.87.1",