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 +11 -0
- package/dist/src/commands.js +2 -1
- package/dist/src/personalize.d.ts +3 -1
- package/dist/src/personalize.js +8 -2
- package/dist/src/server.js +3 -1
- package/dist/src/utils.d.ts +3 -0
- package/dist/src/utils.js +13 -0
- package/dist/test/personalize.test.js +63 -0
- package/package.json +1 -1
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
|
package/dist/src/commands.js
CHANGED
|
@@ -1760,7 +1760,8 @@ export const registerCommands = (pi) => {
|
|
|
1760
1760
|
case "remote":
|
|
1761
1761
|
return handleModal(rest, ctx, pi);
|
|
1762
1762
|
case "personalize":
|
|
1763
|
-
|
|
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<
|
|
47
|
+
export declare const runPersonalize: (ctx: ExtensionContext, pi: ExtensionAPI) => Promise<{
|
|
48
|
+
completed: boolean;
|
|
49
|
+
}>;
|
package/dist/src/personalize.js
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/dist/src/server.js
CHANGED
|
@@ -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: {
|
package/dist/src/utils.d.ts
CHANGED
|
@@ -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.
|
|
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",
|