pi-vault-mind 0.16.5 → 0.16.6

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
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.6 / 0.6.8 — 2026-07-18
4
+
5
+ ### Fixed
6
+
7
+ - **First-run personalization handoff.** Configured vaults now remain on a dedicated personalization card until `/vm personalize` durably completes, including the valid all-suggestions-rejected path. The chat feed and composer are revealed only after the persisted completion status is observed.
8
+ - **Live Pi model selection.** The first-run card now loads the active model and catalog from typed Pi RPC state, preserves provider and model ID for selection, and starts Pi only when a configured panel refresh needs that catalog.
9
+
10
+ ### Tests
11
+
12
+ - Added regression coverage for first-run gating, model selection ordering, delayed personalization completion, and demand-driven Pi model loading.
13
+
3
14
  ## 0.16.5 / 0.6.7 — 2026-07-18
4
15
 
5
16
  ### Fixed
@@ -1760,7 +1760,8 @@ export const registerCommands = (pi) => {
1760
1760
  case "remote":
1761
1761
  return handleModal(rest, ctx, pi);
1762
1762
  case "personalize":
1763
- return runPersonalize(ctx, pi);
1763
+ await runPersonalize(ctx, pi);
1764
+ return;
1764
1765
  case "setup":
1765
1766
  return handleSetup(rest, ctx, pi);
1766
1767
  case "token":
@@ -44,4 +44,6 @@ export declare const presentAndApplyDiff: (suggestions: SuggestionResult, ctx: E
44
44
  rejected: string[];
45
45
  }>;
46
46
  /** Orchestrate /vm personalize: analyze, suggest, confirm, apply. */
47
- export declare const runPersonalize: (ctx: ExtensionContext, pi: ExtensionAPI) => Promise<void>;
47
+ export declare const runPersonalize: (ctx: ExtensionContext, pi: ExtensionAPI) => Promise<{
48
+ completed: boolean;
49
+ }>;
@@ -2,7 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { parseFrontmatter } from "@earendil-works/pi-coding-agent";
4
4
  import { getStatus } from "./lance.js";
5
- import { expandHome, loadConfig, resolveAgentDir } from "./utils.js";
5
+ import { expandHome, getPersonalizedMarkerPath, loadConfig, resolveAgentDir } from "./utils.js";
6
6
  /** Resolve the vault path: configured default vault wins, otherwise ctx.cwd. */
7
7
  export const resolveVaultPath = (ctx) => {
8
8
  const cfg = loadConfig(ctx.cwd);
@@ -359,8 +359,14 @@ export const runPersonalize = async (ctx, pi) => {
359
359
  try {
360
360
  const suggestions = await generateSuggestions(pi, profile, currentConfig);
361
361
  await presentAndApplyDiff(suggestions, ctx, currentConfig);
362
+ const markerPath = getPersonalizedMarkerPath(vaultPath);
363
+ await fs.promises.mkdir(path.dirname(markerPath), { recursive: true });
364
+ await fs.promises.writeFile(markerPath, JSON.stringify({ completed: true, completedAt: new Date().toISOString() }, null, 2));
365
+ return { completed: true };
362
366
  }
363
367
  catch (err) {
364
- ctx.ui.notify(`Personalization failed: ${err.message}`, "error");
368
+ const message = err instanceof Error ? err.message : String(err);
369
+ ctx.ui.notify(`Personalization failed: ${message}`, "error");
370
+ return { completed: false };
365
371
  }
366
372
  };
@@ -45,7 +45,7 @@ import { readModelProviders } from "./models.js";
45
45
  import { scaffoldVaultConfig } from "./scaffold.js";
46
46
  import { archiveSession, deleteSession, exportSession, getSession, listSessions, renameSession, resolveSessionsDir, searchSessions, } from "./session-search.js";
47
47
  import { listAllTools } from "./tool-catalog.js";
48
- import { ensureDir, expandHome, findConfig, getConfigPath, loadConfig, resolveVaultFile, resolveVaultMindPaths, shrinkHome, } from "./utils.js";
48
+ import { ensureDir, expandHome, findConfig, getConfigPath, isPersonalized, loadConfig, resolveVaultFile, resolveVaultMindPaths, shrinkHome, } from "./utils.js";
49
49
  import { classifySearchMode, normalizeGraphResult, searchVaultNative } from "./vault-tools.js";
50
50
  import { createManualDispatch, processQueue, scanFile, startWatcher, stopWatcher, } from "./watcher.js";
51
51
  export function createServerState(port = 11435) {
@@ -1100,12 +1100,14 @@ function handleVmStatus(res, serverState) {
1100
1100
  }
1101
1101
  const cfg = loadConfig(vaultPath);
1102
1102
  const configured = !!findConfig(vaultPath).project;
1103
+ const personalized = isPersonalized(vaultPath);
1103
1104
  const model = cfg.vaultMind.embedding[EMBEDDING_FLAT_KEYS[2]] || "embeddinggemma";
1104
1105
  const dim = cfg.vaultMind.embedding[EMBEDDING_FLAT_KEYS[4]] ?? 768;
1105
1106
  res.writeHead(200);
1106
1107
  res.end(JSON.stringify({
1107
1108
  ok: true,
1108
1109
  configured,
1110
+ personalized,
1109
1111
  vaultPath,
1110
1112
  watcher: ws?.running ?? false,
1111
1113
  embedding: {
@@ -13,6 +13,9 @@ export declare const resolveAgentDir: (cwd: string) => string;
13
13
  export declare const resolveVaultMindDir: (cwd: string) => string;
14
14
  /** Active pi-vault-mind config: `<vault>/.vault-mind/vault-mind.config.json`. */
15
15
  export declare const getConfigPath: (cwd: string) => string;
16
+ export declare const getPersonalizedMarkerPath: (cwd: string) => string;
17
+ /** Check if a vault has a valid personalization completion marker. */
18
+ export declare function isPersonalized(cwd: string): boolean;
16
19
  /** Token env file: `<vault>/.vault-mind/vault-mind.env`. */
17
20
  export declare const getTokenEnvPath: (cwd: string) => string;
18
21
  export declare const EXT_ROOT: string;
package/dist/src/utils.js CHANGED
@@ -18,6 +18,19 @@ export const resolveAgentDir = (cwd) => {
18
18
  export const resolveVaultMindDir = (cwd) => path.join(cwd, VAULT_MIND_DIR);
19
19
  /** Active pi-vault-mind config: `<vault>/.vault-mind/vault-mind.config.json`. */
20
20
  export const getConfigPath = (cwd) => path.join(resolveVaultMindDir(cwd), "vault-mind.config.json");
21
+ export const getPersonalizedMarkerPath = (cwd) => path.join(resolveVaultMindDir(cwd), "personalized.json");
22
+ /** Check if a vault has a valid personalization completion marker. */
23
+ export function isPersonalized(cwd) {
24
+ const p = getPersonalizedMarkerPath(cwd);
25
+ try {
26
+ const content = fs.readFileSync(p, "utf8");
27
+ const marker = JSON.parse(content);
28
+ return marker && typeof marker === "object" && marker.completed === true;
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
21
34
  /** Token env file: `<vault>/.vault-mind/vault-mind.env`. */
22
35
  export const getTokenEnvPath = (cwd) => path.join(resolveVaultMindDir(cwd), "vault-mind.env");
23
36
  export const EXT_ROOT = path.join(import.meta.dirname, "..", "..");
@@ -192,6 +192,69 @@ describe("personalize", () => {
192
192
  // AGENTS.md was never created, so rejection means it still does not exist.
193
193
  assert.equal(fs.existsSync(path.join(testDir, "AGENTS.md")), false);
194
194
  });
195
+ it("persists a completion marker even when all suggestions are rejected or skipped", async () => {
196
+ const { runPersonalize } = await import("../src/personalize.js");
197
+ const pi = makePi(JSON.stringify({
198
+ commentary: "No changes needed.",
199
+ files: [
200
+ { path: ".pi/agent/system.md", action: "skip" },
201
+ { path: "AGENTS.md", action: "skip" },
202
+ ],
203
+ }));
204
+ const ctx = makeCtx(testDir, []);
205
+ await runPersonalize(ctx, pi);
206
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
207
+ assert.ok(fs.existsSync(markerPath), "runPersonalize must write a durable completion marker after the flow finishes, even when every diff is rejected");
208
+ const marker = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
209
+ assert.strictEqual(marker.completed, true);
210
+ assert.ok(typeof marker.completedAt === "string");
211
+ });
212
+ it("persists a completion marker after approved suggestions", async () => {
213
+ const { runPersonalize } = await import("../src/personalize.js");
214
+ const pi = makePi(JSON.stringify({
215
+ commentary: "Updated tuning.",
216
+ files: [{ path: ".pi/agent/system.md", action: "create", content: "# New system" }],
217
+ }));
218
+ const ctx = makeCtx(testDir, [true]);
219
+ await runPersonalize(ctx, pi);
220
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
221
+ assert.ok(fs.existsSync(markerPath), "runPersonalize must write a durable completion marker after approved suggestions are applied");
222
+ const marker = JSON.parse(fs.readFileSync(markerPath, "utf-8"));
223
+ assert.strictEqual(marker.completed, true);
224
+ assert.ok(typeof marker.completedAt === "string");
225
+ });
226
+ it("returns a successful result when all suggestions are rejected", async () => {
227
+ const { runPersonalize: raw } = await import("../src/personalize.js");
228
+ const runPersonalize = raw;
229
+ const pi = makePi(JSON.stringify({
230
+ commentary: "No changes needed.",
231
+ files: [
232
+ { path: ".pi/agent/system.md", action: "skip" },
233
+ { path: "AGENTS.md", action: "skip" },
234
+ ],
235
+ }));
236
+ const ctx = makeCtx(testDir, []);
237
+ const result = await runPersonalize(ctx, pi);
238
+ assert.ok(result, "runPersonalize must return a result, not void");
239
+ assert.strictEqual(result.completed, true, "all-rejected personalization must resolve as a successful completion");
240
+ });
241
+ it("returns a failed result when suggestion generation fails", async () => {
242
+ const { runPersonalize: raw } = await import("../src/personalize.js");
243
+ const runPersonalize = raw;
244
+ const pi = makePi("not valid JSON");
245
+ const ctx = makeCtx(testDir, []);
246
+ const result = await runPersonalize(ctx, pi);
247
+ assert.ok(result, "runPersonalize must return a result, not void");
248
+ assert.strictEqual(result.completed, false, "a generation failure must resolve as a failed personalization, distinguishable from success");
249
+ });
250
+ it("does not persist a completion marker when suggestion generation fails", async () => {
251
+ const { runPersonalize } = await import("../src/personalize.js");
252
+ const pi = makePi("not valid JSON");
253
+ const ctx = makeCtx(testDir, []);
254
+ await runPersonalize(ctx, pi);
255
+ const markerPath = path.join(testDir, ".vault-mind", "personalized.json");
256
+ assert.strictEqual(fs.existsSync(markerPath), false, "runPersonalize must not leave a completion marker after a failed personalization");
257
+ });
195
258
  it("presentAndApplyDiff creates missing directories for new files", async () => {
196
259
  const { presentAndApplyDiff } = await import("../src/personalize.js");
197
260
  const ctx = makeCtx(testDir, [true]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.16.5",
3
+ "version": "0.16.6",
4
4
  "description": "Passive Obsidian vault extension for pi. Watches @agent markers, dispatches forked subagents (Miner, Broadcaster, Heavy-Lifter), stores in LanceDB with vector + FTS + graph. Multi-agent 'Drop & Forget' workflow for the pi agent ecosystem.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",