pi-vault-mind 0.14.2 → 0.16.3
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 +293 -0
- package/README.md +5 -5
- package/agents/broadcaster.agent.md +2 -2
- package/agents/heavy-lifter.agent.md +1 -1
- package/agents/main.agent.md +29 -0
- package/agents/manager.agent.md +1 -1
- package/agents/miner.agent.md +1 -1
- package/dist/packages/obsidian/src/client.js +331 -0
- package/dist/packages/obsidian/src/types.js +1 -0
- package/dist/src/activity.d.ts +49 -0
- package/dist/src/activity.js +175 -0
- package/dist/src/agent-bus/Agent.d.ts +9 -1
- package/dist/src/agent-bus/Agent.js +18 -2
- package/dist/src/agent-bus/index.d.ts +5 -5
- package/dist/src/agent-bus/index.js +1 -1
- package/dist/src/agent-queue.js +12 -3
- package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
- package/dist/src/agents/BroadcasterAgent.js +2 -2
- package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
- package/dist/src/agents/HeavyLifterAgent.js +2 -2
- package/dist/src/agents/ManagerAgent.js +1 -1
- package/dist/src/agents/MinerAgent.d.ts +1 -1
- package/dist/src/agents/MinerAgent.js +2 -2
- package/dist/src/agents/index.d.ts +2 -2
- package/dist/src/agents/index.js +2 -2
- package/dist/src/auth.d.ts +17 -15
- package/dist/src/auth.js +26 -37
- package/dist/src/autosync.js +2 -2
- package/dist/src/commands.d.ts +2 -19
- package/dist/src/commands.js +301 -231
- package/dist/src/config-keys.d.ts +16 -0
- package/dist/src/config-keys.js +45 -0
- package/dist/src/discover-schema.js +4 -4
- package/dist/src/edits.d.ts +21 -0
- package/dist/src/edits.js +75 -0
- package/dist/src/embedding-probe.d.ts +34 -0
- package/dist/src/embedding-probe.js +142 -0
- package/dist/src/embedding-providers.d.ts +133 -0
- package/dist/src/embedding-providers.js +173 -0
- package/dist/src/embedding-secrets.d.ts +38 -0
- package/dist/src/embedding-secrets.js +291 -0
- package/dist/src/engine.d.ts +6 -0
- package/dist/src/engine.js +78 -6
- package/dist/src/events.js +7 -10
- package/dist/src/extension-packages.d.ts +9 -0
- package/dist/src/extension-packages.js +14 -0
- package/dist/src/git.d.ts +19 -0
- package/dist/src/git.js +78 -0
- package/dist/src/graph.d.ts +2 -1
- package/dist/src/graph.js +47 -13
- package/dist/src/identities-config.d.ts +6 -0
- package/dist/src/identities-config.js +39 -0
- package/dist/src/identity-injector.js +1 -1
- package/dist/src/index.js +9 -26
- package/dist/src/lance.d.ts +9 -0
- package/dist/src/lance.js +143 -203
- package/dist/src/modal-client.d.ts +16 -0
- package/dist/src/modal-client.js +16 -0
- package/dist/src/modal-config.d.ts +37 -14
- package/dist/src/modal-config.js +59 -32
- package/dist/src/model-router.d.ts +27 -0
- package/dist/src/model-router.js +67 -0
- package/dist/src/models.d.ts +42 -0
- package/dist/src/models.js +78 -0
- package/dist/src/personalize.js +17 -3
- package/dist/src/scaffold.d.ts +48 -0
- package/dist/src/scaffold.js +246 -0
- package/dist/src/server.d.ts +47 -4
- package/dist/src/server.js +988 -221
- package/dist/src/session-search.d.ts +63 -0
- package/dist/src/session-search.js +379 -0
- package/dist/src/settings-ui.d.ts +2 -2
- package/dist/src/settings-ui.js +76 -135
- package/dist/src/sync.d.ts +3 -3
- package/dist/src/sync.js +19 -15
- package/dist/src/tool-catalog.d.ts +18 -0
- package/dist/src/tool-catalog.js +232 -0
- package/dist/src/tools/marksman.js +1 -1
- package/dist/src/tools.js +111 -65
- package/dist/src/types.d.ts +127 -71
- package/dist/src/utils.d.ts +57 -6
- package/dist/src/utils.js +114 -28
- package/dist/src/vault-tools.d.ts +34 -0
- package/dist/src/vault-tools.js +99 -0
- package/dist/src/vault-writer.js +4 -1
- package/dist/src/vm-handlers.js +140 -43
- package/dist/src/watcher.js +9 -9
- package/dist/test/activity-tool.test.js +54 -0
- package/dist/test/agent-bus/Agent.test.js +174 -0
- package/dist/test/agent-bus/AgentLoader.test.js +180 -0
- package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
- package/dist/test/agent-bus/LLMProvider.test.js +162 -0
- package/dist/test/agent-bus/TaskQueue.test.js +256 -0
- package/dist/test/agent-dispatch-context.test.js +73 -0
- package/dist/test/agent-queue.test.js +1 -1
- package/dist/test/agents/Agents.test.js +282 -0
- package/dist/test/auth.test.js +57 -56
- package/dist/test/bridge-identity.test.js +34 -3
- package/dist/test/classify-search-mode.test.js +51 -0
- package/dist/test/commands.test.js +410 -0
- package/dist/test/config-merge.test.js +98 -0
- package/dist/test/context-capture.test.js +56 -0
- package/dist/test/discover-schema.test.js +8 -5
- package/dist/test/dispatch.test.js +45 -91
- package/dist/test/edits.test.js +144 -0
- package/dist/test/embedding-probe.test.js +424 -0
- package/dist/test/embedding-providers.test.js +157 -0
- package/dist/test/embedding-secrets.test.js +210 -0
- package/dist/test/engine-identity.test.js +1 -1
- package/dist/test/events.test.js +675 -0
- package/dist/test/git-bridge.test.js +160 -0
- package/dist/test/graph.test.js +464 -0
- package/dist/test/helpers/server.js +81 -0
- package/dist/test/identity-injector.test.js +197 -0
- package/dist/test/index.test.js +5 -7
- package/dist/test/lance-modal.test.js +67 -2
- package/dist/test/lance.test.js +429 -0
- package/dist/test/modal-client.test.js +1 -1
- package/dist/test/modal-config.test.js +92 -79
- package/dist/test/model-readiness.test.js +122 -0
- package/dist/test/model-router.test.js +99 -0
- package/dist/test/obsidian-client.test.js +42 -0
- package/dist/test/personalize.test.js +1 -1
- package/dist/test/rest-models.test.js +153 -0
- package/dist/test/rest-queue.test.js +135 -87
- package/dist/test/rest-setup.test.js +625 -115
- package/dist/test/rest-vm.test.js +355 -94
- package/dist/test/server-cors.test.js +48 -0
- package/dist/test/server-ws.test.js +61 -34
- package/dist/test/session-crud.test.js +323 -0
- package/dist/test/session-search.test.js +139 -0
- package/dist/test/settings-ui.test.js +113 -4
- package/dist/test/sync.test.js +12 -12
- package/dist/test/tombstone.test.js +8 -10
- package/dist/test/tool-catalog.test.js +86 -0
- package/dist/test/utils.test.js +588 -0
- package/dist/test/vault-tools.test.js +103 -12
- package/dist/test/vault-writer.test.js +371 -0
- package/dist/test/vm-handlers.test.js +620 -0
- package/extension-packages.json +9 -0
- package/package.json +107 -90
- package/pi-vault-mind.config.example.json +24 -2
- package/scripts/check-deps.sh +35 -32
- package/scripts/cli-e2e.mjs +215 -0
- package/scripts/configuration-e2e.mjs +783 -0
- package/scripts/e2e-commands.mjs +391 -0
- package/scripts/e2e-ws.mjs +3 -2
- package/scripts/fetch-modal-token.sh +4 -3
- package/scripts/generate-config-keys-json.mjs +45 -0
- package/scripts/generate-extension-packages-json.mjs +55 -0
- package/scripts/pi-session-query +0 -0
- package/scripts/publish-obsidian-plugin.sh +0 -0
- package/scripts/reset-test-vault.sh +2 -2
- package/scripts/setup-test-config.sh +0 -0
- package/scripts/setup-vault-pi.sh +48 -9
- package/scripts/test-slash-commands.mjs +2 -2
- package/scripts/vault-pi.sh +12 -3
- package/skills/github-release-notes-file/SKILL.md +42 -0
- package/skills/obsidian-guided-test-session/SKILL.md +110 -0
- package/skills/obsidian-interactive-test/SKILL.md +88 -0
- package/skills/obsidian-plugin-publish/SKILL.md +58 -0
- package/skills/pi-vault-mind-release/SKILL.md +141 -0
- package/skills/pre-test-release/SKILL.md +116 -0
- package/dist/test/auto-indexer.test.js +0 -2
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* E2E smoke for /vm slash commands + BC-2 rewind/fork RPC — real pi CLI.
|
|
4
|
+
*
|
|
5
|
+
* Creates a temp vault, scaffolds config, installs the extension via `pi install`,
|
|
6
|
+
* spawns `pi --mode rpc`, drives commands via JSON-line stdin, asserts on
|
|
7
|
+
* responses and file side-effects, then tears down. No mocks.
|
|
8
|
+
*
|
|
9
|
+
* Prerequisites: pi binary on PATH, pnpm build completed.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* pnpm build
|
|
13
|
+
* pnpm e2e:commands
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import * as fs from "node:fs";
|
|
19
|
+
import * as os from "node:os";
|
|
20
|
+
import * as path from "node:path";
|
|
21
|
+
|
|
22
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
let failures = 0;
|
|
25
|
+
const pass = (msg) => console.log(` \x1b[32m✓\x1b[0m ${msg}`);
|
|
26
|
+
const fail = (msg) => {
|
|
27
|
+
failures++;
|
|
28
|
+
console.log(` \x1b[31m✗\x1b[0m ${msg}`);
|
|
29
|
+
};
|
|
30
|
+
const step = (msg) => console.log(`\n\x1b[1m${msg}\x1b[0m`);
|
|
31
|
+
|
|
32
|
+
function tmpDir(prefix) {
|
|
33
|
+
const dir = path.join(os.tmpdir(), `${prefix}-${randomUUID()}`);
|
|
34
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
35
|
+
return dir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── Vault setup ──────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
async function hasOllamaEmbeddingModel() {
|
|
41
|
+
try {
|
|
42
|
+
const res = await fetch("http://localhost:11434/api/embeddings", {
|
|
43
|
+
method: "POST",
|
|
44
|
+
headers: { "Content-Type": "application/json" },
|
|
45
|
+
body: JSON.stringify({ model: "all-minilm", prompt: "health check" }),
|
|
46
|
+
signal: AbortSignal.timeout(5000),
|
|
47
|
+
});
|
|
48
|
+
if (!res.ok) return false;
|
|
49
|
+
const data = await res.json();
|
|
50
|
+
return Array.isArray(data.embedding) && data.embedding.length === 384;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function setupVault(vaultPath, seedSearchIndex) {
|
|
57
|
+
const cfgDir = path.join(vaultPath, ".vault-mind");
|
|
58
|
+
fs.mkdirSync(cfgDir, { recursive: true });
|
|
59
|
+
const collectionsDir = path.join(vaultPath, "collections");
|
|
60
|
+
fs.mkdirSync(collectionsDir, { recursive: true });
|
|
61
|
+
|
|
62
|
+
const entries = [
|
|
63
|
+
{ id: "1", domain: "test", fact: "First test entry", tag: "alpha" },
|
|
64
|
+
{ id: "2", domain: "test", fact: "Second test entry", tag: "beta" },
|
|
65
|
+
{ id: "3", domain: "prod", fact: "Production entry", tag: "alpha" },
|
|
66
|
+
];
|
|
67
|
+
const jsonl = `${entries.map((e) => JSON.stringify(e)).join("\n")}\n`;
|
|
68
|
+
|
|
69
|
+
const config = {
|
|
70
|
+
version: 2,
|
|
71
|
+
collections: {
|
|
72
|
+
main: {
|
|
73
|
+
path: "collections/main.jsonl",
|
|
74
|
+
schema: ["id", "domain", "source", "fact", "tag"],
|
|
75
|
+
dedupField: "fact",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
injectors: [],
|
|
79
|
+
vaultMind: {
|
|
80
|
+
dataDir: ".lancedb",
|
|
81
|
+
embedding: {
|
|
82
|
+
localUrl: "http://localhost:11434",
|
|
83
|
+
model: "all-minilm",
|
|
84
|
+
dim: 384,
|
|
85
|
+
useTransformers: false,
|
|
86
|
+
},
|
|
87
|
+
ftsEnabled: true,
|
|
88
|
+
graph: { enabled: false },
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
fs.writeFileSync(
|
|
92
|
+
path.join(cfgDir, "vault-mind.config.json"),
|
|
93
|
+
JSON.stringify(config, null, 2),
|
|
94
|
+
"utf-8"
|
|
95
|
+
);
|
|
96
|
+
fs.writeFileSync(path.join(collectionsDir, "main.jsonl"), jsonl, "utf-8");
|
|
97
|
+
|
|
98
|
+
if (!seedSearchIndex) return;
|
|
99
|
+
|
|
100
|
+
// Build LanceDB index from the seeded JSONL entries so /vm search can find them
|
|
101
|
+
const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
102
|
+
const { upsertEntry } = await import(path.join(repoRoot, "dist/src/lance.js"));
|
|
103
|
+
const { loadConfig } = await import(path.join(repoRoot, "dist/src/utils.js"));
|
|
104
|
+
const cfg = loadConfig(vaultPath);
|
|
105
|
+
const dataDir = path.resolve(vaultPath, cfg.vaultMind.dataDir);
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
await upsertEntry(dataDir, "main", entry, cfg.vaultMind);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Pi RPC ───────────────────────────────────────────────────────────────────
|
|
112
|
+
|
|
113
|
+
async function runPiRpc(vaultPath, commands) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
const piAgentDir = path.join(vaultPath, ".pi", "agent");
|
|
116
|
+
const env = {
|
|
117
|
+
...process.env,
|
|
118
|
+
PI_CODING_AGENT_DIR: piAgentDir,
|
|
119
|
+
HOME: vaultPath,
|
|
120
|
+
USERPROFILE: vaultPath,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const child = spawn("pi", ["--mode", "rpc"], {
|
|
124
|
+
cwd: vaultPath,
|
|
125
|
+
env,
|
|
126
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const responses = [];
|
|
130
|
+
let stderr = "";
|
|
131
|
+
let commandIndex = 0;
|
|
132
|
+
let resolved = false;
|
|
133
|
+
const TIMEOUT_MS = 120_000;
|
|
134
|
+
|
|
135
|
+
let buffer = "";
|
|
136
|
+
child.stdout.on("data", (chunk) => {
|
|
137
|
+
buffer += chunk.toString();
|
|
138
|
+
const lines = buffer.split("\n");
|
|
139
|
+
buffer = lines.pop() ?? "";
|
|
140
|
+
for (const line of lines) {
|
|
141
|
+
const trimmed = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
142
|
+
if (!trimmed) continue;
|
|
143
|
+
try {
|
|
144
|
+
const event = JSON.parse(trimmed);
|
|
145
|
+
responses.push(event);
|
|
146
|
+
if (event.type === "response" && commandIndex < commands.length) {
|
|
147
|
+
const cmd = commands[commandIndex++];
|
|
148
|
+
child.stdin.write(`${JSON.stringify(cmd)}\n`);
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
responses.push({ _text: trimmed });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
child.stderr.on("data", (data) => {
|
|
157
|
+
stderr += data.toString();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
// Send first command after extension loads
|
|
161
|
+
const readyTimer = setTimeout(() => {
|
|
162
|
+
if (commandIndex < commands.length) {
|
|
163
|
+
const cmd = commands[commandIndex++];
|
|
164
|
+
child.stdin.write(`${JSON.stringify(cmd)}\n`);
|
|
165
|
+
}
|
|
166
|
+
}, 3000);
|
|
167
|
+
|
|
168
|
+
// Kill after all commands sent + grace period
|
|
169
|
+
const doneTimer = setTimeout(() => {
|
|
170
|
+
if (!resolved) {
|
|
171
|
+
resolved = true;
|
|
172
|
+
child.kill();
|
|
173
|
+
resolve({ responses, stderr, timedOut: false });
|
|
174
|
+
}
|
|
175
|
+
}, TIMEOUT_MS);
|
|
176
|
+
|
|
177
|
+
child.on("close", () => {
|
|
178
|
+
clearTimeout(readyTimer);
|
|
179
|
+
clearTimeout(doneTimer);
|
|
180
|
+
if (!resolved) {
|
|
181
|
+
resolved = true;
|
|
182
|
+
resolve({ responses, stderr, timedOut: false });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
child.on("error", (err) => {
|
|
186
|
+
clearTimeout(readyTimer);
|
|
187
|
+
clearTimeout(doneTimer);
|
|
188
|
+
if (!resolved) {
|
|
189
|
+
resolved = true;
|
|
190
|
+
reject(err);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
197
|
+
|
|
198
|
+
async function main() {
|
|
199
|
+
console.log("pi-vault-mind /vm commands + BC-2 RPC E2E\n");
|
|
200
|
+
let piVersion = "";
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
({ stdout: piVersion } = await new Promise((resolve, reject) => {
|
|
204
|
+
const child = spawn("pi", ["--version"], { stdio: "pipe" });
|
|
205
|
+
let out = "";
|
|
206
|
+
child.stdout.on("data", (d) => (out += d));
|
|
207
|
+
child.on("close", (code) =>
|
|
208
|
+
code === 0 ? resolve({ stdout: out }) : reject(new Error(`pi --version exited ${code}`))
|
|
209
|
+
);
|
|
210
|
+
child.on("error", reject);
|
|
211
|
+
}));
|
|
212
|
+
console.log(` pi version: ${piVersion.trim()}`);
|
|
213
|
+
} catch {
|
|
214
|
+
console.error(" pi binary not found on PATH");
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const vaultPath = tmpDir("pvm-e2e-commands");
|
|
219
|
+
const origHome = process.env.HOME;
|
|
220
|
+
const searchE2EAvailable = await hasOllamaEmbeddingModel();
|
|
221
|
+
if (!searchE2EAvailable) {
|
|
222
|
+
pass("Ollama all-minilm unavailable — skipping search e2e");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
await setupVault(vaultPath, searchE2EAvailable);
|
|
227
|
+
|
|
228
|
+
step("Installing pi-vault-mind via pi install");
|
|
229
|
+
const repoRoot = path.resolve(import.meta.dirname, "..");
|
|
230
|
+
const piAgentDir = path.join(vaultPath, ".pi", "agent");
|
|
231
|
+
await new Promise((resolve, reject) => {
|
|
232
|
+
const child = spawn("pi", ["install", repoRoot], {
|
|
233
|
+
cwd: vaultPath,
|
|
234
|
+
env: {
|
|
235
|
+
...process.env,
|
|
236
|
+
PI_CODING_AGENT_DIR: piAgentDir,
|
|
237
|
+
HOME: vaultPath,
|
|
238
|
+
USERPROFILE: vaultPath,
|
|
239
|
+
},
|
|
240
|
+
stdio: "pipe",
|
|
241
|
+
});
|
|
242
|
+
let out = "";
|
|
243
|
+
child.stdout.on("data", (d) => (out += d));
|
|
244
|
+
child.stderr.on("data", (d) => (out += d));
|
|
245
|
+
child.on("close", (code) =>
|
|
246
|
+
code === 0
|
|
247
|
+
? resolve()
|
|
248
|
+
: reject(new Error(`pi install exited ${code}: ${out.slice(0, 200)}`))
|
|
249
|
+
);
|
|
250
|
+
child.on("error", reject);
|
|
251
|
+
});
|
|
252
|
+
pass("pi install succeeded");
|
|
253
|
+
|
|
254
|
+
step("Running commands via pi --mode rpc");
|
|
255
|
+
const commands = [
|
|
256
|
+
{ type: "prompt", message: "/vm doctor", id: "e2e-1" },
|
|
257
|
+
...(searchE2EAvailable
|
|
258
|
+
? [
|
|
259
|
+
{ type: "prompt", message: "/vm reindex", id: "e2e-reindex" },
|
|
260
|
+
{ type: "prompt", message: "/vm search test", id: "e2e-search" },
|
|
261
|
+
{ type: "prompt", message: "/vm search test --mode=fts", id: "e2e-search-fts" },
|
|
262
|
+
]
|
|
263
|
+
: []),
|
|
264
|
+
{ type: "get_fork_messages", id: "e2e-fork" },
|
|
265
|
+
{ type: "get_tree", id: "e2e-tree" },
|
|
266
|
+
{ type: "get_entries", id: "e2e-entries" },
|
|
267
|
+
{ type: "clone", id: "e2e-clone" },
|
|
268
|
+
];
|
|
269
|
+
const { responses, stderr, timedOut } = await runPiRpc(vaultPath, commands);
|
|
270
|
+
|
|
271
|
+
if (timedOut) fail("pi RPC timed out (60s)");
|
|
272
|
+
|
|
273
|
+
step("Verifying responses");
|
|
274
|
+
const allText = responses.map((r) => JSON.stringify(r)).join(" ");
|
|
275
|
+
|
|
276
|
+
if (allText.includes("vault-mind") || allText.includes(".vault-mind"))
|
|
277
|
+
pass("/vm doctor references vault-mind paths");
|
|
278
|
+
else {
|
|
279
|
+
fail("/vm doctor references vault-mind paths");
|
|
280
|
+
console.log(` responses: ${allText.slice(0, 200)}`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// BC-2: rewind/fork RPC commands
|
|
284
|
+
const byId = (id) => responses.find((r) => r.id === id);
|
|
285
|
+
const byCmd = (cmd) => responses.find((r) => r.command === cmd);
|
|
286
|
+
const supportsTreeRpc = !piVersion.trim().startsWith("0.79.");
|
|
287
|
+
const treeResp = byId("e2e-tree") || byCmd("get_tree");
|
|
288
|
+
if (!supportsTreeRpc) pass("get_tree skipped (pi < 0.80.0 in e2e runtime)");
|
|
289
|
+
else if (treeResp && treeResp.success !== false) pass("get_tree returns successfully");
|
|
290
|
+
else {
|
|
291
|
+
fail("get_tree failed or missing");
|
|
292
|
+
console.log(` treeResp: ${JSON.stringify(treeResp).slice(0, 200)}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const entriesResp = byId("e2e-entries") || byCmd("get_entries");
|
|
296
|
+
if (!supportsTreeRpc) pass("get_entries skipped (pi < 0.80.0 in e2e runtime)");
|
|
297
|
+
else if (entriesResp && entriesResp.success !== false) pass("get_entries returns successfully");
|
|
298
|
+
else {
|
|
299
|
+
fail("get_entries failed or missing");
|
|
300
|
+
console.log(` entriesResp: ${JSON.stringify(entriesResp).slice(0, 200)}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const cloneResp = byId("e2e-clone") || byCmd("clone");
|
|
304
|
+
if (!supportsTreeRpc) pass("clone skipped (pi < 0.80.0 in e2e runtime)");
|
|
305
|
+
else if (cloneResp && cloneResp.success !== false) pass("clone returns successfully");
|
|
306
|
+
else {
|
|
307
|
+
fail("clone failed or missing");
|
|
308
|
+
console.log(` cloneResp: ${JSON.stringify(cloneResp).slice(0, 200)}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (searchE2EAvailable) {
|
|
312
|
+
// /vm reindex — builds LanceDB index from JSONL with real embeddings
|
|
313
|
+
const reindexResp = byId("e2e-reindex");
|
|
314
|
+
if (reindexResp && reindexResp.success !== false) pass("/vm reindex runs successfully");
|
|
315
|
+
else {
|
|
316
|
+
fail("/vm reindex failed or missing");
|
|
317
|
+
console.log(` reindexResp: ${JSON.stringify(reindexResp).slice(0, 200)}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// /vm search — verify both slash commands ran and the search notifications
|
|
321
|
+
// include seeded facts after reindex. The RPC stream can interleave UI
|
|
322
|
+
// notifications with prompt responses, so assert result content globally.
|
|
323
|
+
const searchResp = byId("e2e-search");
|
|
324
|
+
if (searchResp && searchResp.success !== false)
|
|
325
|
+
pass("/vm search command returns successfully");
|
|
326
|
+
else {
|
|
327
|
+
fail("/vm search command failed or missing");
|
|
328
|
+
console.log(` searchResp: ${JSON.stringify(searchResp).slice(0, 200)}`);
|
|
329
|
+
}
|
|
330
|
+
const searchFtsResp = byId("e2e-search-fts");
|
|
331
|
+
if (searchFtsResp && searchFtsResp.success !== false)
|
|
332
|
+
pass("/vm search --mode=fts command returns successfully");
|
|
333
|
+
else {
|
|
334
|
+
fail("/vm search --mode=fts command failed or missing");
|
|
335
|
+
console.log(` searchFtsResp: ${JSON.stringify(searchFtsResp).slice(0, 200)}`);
|
|
336
|
+
}
|
|
337
|
+
const repoRootForSearch = path.resolve(import.meta.dirname, "..");
|
|
338
|
+
const { searchHybrid, searchFts } = await import(
|
|
339
|
+
path.join(repoRootForSearch, "dist/src/lance.js")
|
|
340
|
+
);
|
|
341
|
+
const { loadConfig } = await import(path.join(repoRootForSearch, "dist/src/utils.js"));
|
|
342
|
+
const cfg = loadConfig(vaultPath);
|
|
343
|
+
const dataDir = path.resolve(vaultPath, cfg.vaultMind.dataDir);
|
|
344
|
+
const hybridResults = JSON.stringify(
|
|
345
|
+
await searchHybrid(dataDir, "main", "test", 5, cfg.vaultMind)
|
|
346
|
+
);
|
|
347
|
+
if (hybridResults.includes("First test entry") || hybridResults.includes("Second test entry"))
|
|
348
|
+
pass("real hybrid search returns seeded fact data");
|
|
349
|
+
else {
|
|
350
|
+
fail("real hybrid search did not return seeded fact data");
|
|
351
|
+
console.log(` hybridResults: ${hybridResults.slice(0, 300)}`);
|
|
352
|
+
}
|
|
353
|
+
const ftsResults = JSON.stringify(await searchFts(dataDir, "main", "test", 5, cfg.vaultMind));
|
|
354
|
+
if (ftsResults.includes("First test entry") || ftsResults.includes("Second test entry"))
|
|
355
|
+
pass("real FTS search returns seeded fact data");
|
|
356
|
+
else {
|
|
357
|
+
fail("real FTS search did not return seeded fact data");
|
|
358
|
+
console.log(` ftsResults: ${ftsResults.slice(0, 300)}`);
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
pass("search e2e assertions skipped — Ollama all-minilm unavailable");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
step("Verifying file side-effects");
|
|
365
|
+
const cfgPath = path.join(vaultPath, ".vault-mind", "vault-mind.config.json");
|
|
366
|
+
const collPath = path.join(vaultPath, "collections", "main.jsonl");
|
|
367
|
+
if (fs.existsSync(cfgPath)) pass("vault-mind.config.json still exists");
|
|
368
|
+
else fail("vault-mind.config.json still exists");
|
|
369
|
+
if (fs.existsSync(collPath)) pass("main.jsonl collection still exists");
|
|
370
|
+
else fail("main.jsonl collection still exists");
|
|
371
|
+
|
|
372
|
+
console.log(
|
|
373
|
+
`\n${failures === 0 ? "\x1b[32mAll passed\x1b[0m" : `\x1b[31m${failures} failed\x1b[0m`}`
|
|
374
|
+
);
|
|
375
|
+
if (stderr && !stderr.includes("ExperimentalWarning"))
|
|
376
|
+
console.log(`\nstderr:\n${stderr.slice(0, 500)}`);
|
|
377
|
+
} finally {
|
|
378
|
+
process.env.HOME = origHome;
|
|
379
|
+
try {
|
|
380
|
+
fs.rmSync(vaultPath, { recursive: true, force: true });
|
|
381
|
+
} catch {
|
|
382
|
+
/* ignore */
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
process.exit(failures > 0 ? 1 : 0);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
main().catch((err) => {
|
|
389
|
+
console.error(err);
|
|
390
|
+
process.exit(1);
|
|
391
|
+
});
|
package/scripts/e2e-ws.mjs
CHANGED
|
@@ -54,8 +54,9 @@ function defaultVaultPath() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
function ensureProjectConfig(vaultPath) {
|
|
57
|
-
const cfgPath = path.join(vaultPath, "
|
|
57
|
+
const cfgPath = path.join(vaultPath, ".vault-mind", "vault-mind.config.json");
|
|
58
58
|
if (!fs.existsSync(cfgPath)) {
|
|
59
|
+
fs.mkdirSync(path.dirname(cfgPath), { recursive: true });
|
|
59
60
|
fs.writeFileSync(
|
|
60
61
|
cfgPath,
|
|
61
62
|
JSON.stringify(
|
|
@@ -69,7 +70,7 @@ function ensureProjectConfig(vaultPath) {
|
|
|
69
70
|
},
|
|
70
71
|
},
|
|
71
72
|
injectors: [],
|
|
72
|
-
|
|
73
|
+
vaultMind: {
|
|
73
74
|
dataDir: ".lancedb",
|
|
74
75
|
embedding: {
|
|
75
76
|
provider: "modal",
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Fetch the Modal API token from 1Password and export or persist it.
|
|
3
3
|
# Usage:
|
|
4
|
-
# ./scripts/fetch-modal-token.sh --export
|
|
5
|
-
# ./scripts/fetch-modal-token.sh --write
|
|
4
|
+
# ./scripts/fetch-modal-token.sh --export # prints an export line
|
|
5
|
+
# ./scripts/fetch-modal-token.sh --write [vault-path] # writes to <vault>/.vault-mind/vault-mind.env
|
|
6
6
|
#
|
|
7
7
|
# The 1Password item must be titled "pi-vault-mind-auth" with the token in the
|
|
8
8
|
# password field.
|
|
@@ -11,7 +11,8 @@ set -euo pipefail
|
|
|
11
11
|
|
|
12
12
|
OP_ITEM="pi-vault-mind-auth"
|
|
13
13
|
OP_FIELD="password"
|
|
14
|
-
|
|
14
|
+
VAULT="${2:-$(pwd)}"
|
|
15
|
+
ENV_DIR="${VAULT}/.vault-mind"
|
|
15
16
|
ENV_FILE="${ENV_DIR}/vault-mind.env"
|
|
16
17
|
|
|
17
18
|
mode="write"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generates config-keys.json from the canonical TS source.
|
|
4
|
+
*
|
|
5
|
+
* Reads src/config-keys.ts and writes JSON to:
|
|
6
|
+
* 1. config-keys.json (repo root — for reference)
|
|
7
|
+
* 2. packages/obsidian/src/config-keys.json (for plugin import)
|
|
8
|
+
*
|
|
9
|
+
* Run before building the plugin.
|
|
10
|
+
* Usage: node scripts/generate-config-keys-json.mjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const repoRoot = resolve(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
const src = readFileSync(resolve(repoRoot, "src/config-keys.ts"), "utf-8");
|
|
21
|
+
|
|
22
|
+
function extractArray(name) {
|
|
23
|
+
const re = new RegExp(`export const ${name} = \\[([\\s\\S]*?)\\] as const;`, "m");
|
|
24
|
+
const arrMatch = src.match(re);
|
|
25
|
+
if (!arrMatch) throw new Error(`Could not find ${name} in src/config-keys.ts`);
|
|
26
|
+
const items = [];
|
|
27
|
+
for (const m of arrMatch[1].matchAll(/"([^"]+)"/g)) {
|
|
28
|
+
items.push(m[1]);
|
|
29
|
+
}
|
|
30
|
+
return items;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const embeddingFlatKeys = extractArray("EMBEDDING_FLAT_KEYS");
|
|
34
|
+
const vaultMindConfigKeys = extractArray("VAULT_MIND_CONFIG_KEYS");
|
|
35
|
+
|
|
36
|
+
const json = `${JSON.stringify({ embeddingFlatKeys, vaultMindConfigKeys }, null, "\t")}\n`;
|
|
37
|
+
|
|
38
|
+
const rootOut = resolve(repoRoot, "config-keys.json");
|
|
39
|
+
const pluginOut = resolve(repoRoot, "packages/obsidian/src/config-keys.json");
|
|
40
|
+
|
|
41
|
+
writeFileSync(rootOut, json);
|
|
42
|
+
console.log(`Wrote ${rootOut}`);
|
|
43
|
+
|
|
44
|
+
writeFileSync(pluginOut, json);
|
|
45
|
+
console.log(`Wrote ${pluginOut}`);
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generates extension-packages.json from the canonical TS source.
|
|
4
|
+
*
|
|
5
|
+
* Reads src/extension-packages.ts and writes JSON to:
|
|
6
|
+
* 1. extension-packages.json (repo root — for shell scripts)
|
|
7
|
+
* 2. packages/obsidian/src/extension-packages.json (for plugin import)
|
|
8
|
+
* 3. packages/obsidian-ui/src/extension-packages.json (for sandbox import)
|
|
9
|
+
* Run before building the plugin or running shell scripts.
|
|
10
|
+
* Usage: node scripts/generate-extension-packages-json.mjs
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { writeFileSync } from "node:fs";
|
|
14
|
+
import { dirname, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const repoRoot = resolve(__dirname, "..");
|
|
19
|
+
|
|
20
|
+
// Dynamic import of the TS source — Node 20+ with --experimental-strip-types
|
|
21
|
+
// or tsx handles this. We use a simple regex-based extraction as a fallback
|
|
22
|
+
// that works without any loader flags.
|
|
23
|
+
import { readFileSync } from "node:fs";
|
|
24
|
+
|
|
25
|
+
const src = readFileSync(resolve(repoRoot, "src/extension-packages.ts"), "utf-8");
|
|
26
|
+
|
|
27
|
+
function extractArray(name) {
|
|
28
|
+
const re = new RegExp(`export const ${name} = \\[([\\s\\S]*?)\\] as const;`, "m");
|
|
29
|
+
const arrMatch = src.match(re);
|
|
30
|
+
if (!arrMatch) throw new Error(`Could not find ${name} in src/extension-packages.ts`);
|
|
31
|
+
// Parse each quoted string from the array body
|
|
32
|
+
const items = [];
|
|
33
|
+
for (const m of arrMatch[1].matchAll(/"([^"]+)"/g)) {
|
|
34
|
+
items.push(m[1]);
|
|
35
|
+
}
|
|
36
|
+
return items;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const required = extractArray("PI_REQUIRED_EXTENSIONS");
|
|
40
|
+
const optional = extractArray("PI_OPTIONAL_EXTENSIONS");
|
|
41
|
+
|
|
42
|
+
const json = `${JSON.stringify({ required, optional }, null, "\t")}\n`;
|
|
43
|
+
|
|
44
|
+
const rootOut = resolve(repoRoot, "extension-packages.json");
|
|
45
|
+
const pluginOut = resolve(repoRoot, "packages/obsidian/src/extension-packages.json");
|
|
46
|
+
const sandboxOut = resolve(repoRoot, "packages/obsidian-ui/src/extension-packages.json");
|
|
47
|
+
|
|
48
|
+
writeFileSync(rootOut, json);
|
|
49
|
+
console.log(`Wrote ${rootOut}`);
|
|
50
|
+
|
|
51
|
+
writeFileSync(pluginOut, json);
|
|
52
|
+
console.log(`Wrote ${pluginOut}`);
|
|
53
|
+
|
|
54
|
+
writeFileSync(sandboxOut, json);
|
|
55
|
+
console.log(`Wrote ${sandboxOut}`);
|
package/scripts/pi-session-query
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -45,8 +45,8 @@ fi
|
|
|
45
45
|
|
|
46
46
|
echo ""
|
|
47
47
|
echo "✓ Vault reset complete. State removed:"
|
|
48
|
-
echo " - .
|
|
49
|
-
echo " - pi
|
|
48
|
+
echo " - .vault-mind/ (config, token, framework agent dir, queue)"
|
|
49
|
+
echo " - .pi/ (legacy agent dir, if present)"
|
|
50
50
|
echo " - .lancedb/ (vector index)"
|
|
51
51
|
echo " - collections/ (JSONL data)"
|
|
52
52
|
echo ""
|
|
File without changes
|
|
@@ -1,32 +1,71 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# setup-vault-pi.sh — Bootstrap a vault-local pi agent config.
|
|
3
3
|
#
|
|
4
|
-
# Run from the vault root. Creates .pi/agent/ with pi-vault-mind
|
|
5
|
-
#
|
|
4
|
+
# Run from the vault root. Creates .vault-mind/.pi/agent/ with pi-vault-mind,
|
|
5
|
+
# pi-context, pi-model-discovery, and pi-model-router installed locally,
|
|
6
|
+
# isolated from your global ~/.pi/agent/. If the 1Password CLI (op) is
|
|
7
|
+
# detected, also installs pi-1password so .vault-mind/.env.1pass (op://
|
|
8
|
+
# references) resolves at pi startup.
|
|
6
9
|
#
|
|
7
10
|
# Usage:
|
|
8
11
|
# cd /path/to/vault
|
|
9
12
|
# bash scripts/setup-vault-pi.sh
|
|
10
13
|
#
|
|
11
14
|
# After setup, start pi with:
|
|
12
|
-
# PI_CODING_AGENT_DIR="$(pwd)/.pi/agent" pi
|
|
15
|
+
# PI_CODING_AGENT_DIR="$(pwd)/.vault-mind/.pi/agent" pi
|
|
13
16
|
|
|
14
17
|
set -euo pipefail
|
|
15
18
|
|
|
16
19
|
VAULT_ROOT="${1:-$(pwd)}"
|
|
17
|
-
PI_DIR="${VAULT_ROOT}/.pi/agent"
|
|
20
|
+
PI_DIR="${VAULT_ROOT}/.vault-mind/.pi/agent"
|
|
18
21
|
|
|
19
22
|
echo "==> Setting up vault-local pi config at ${PI_DIR}"
|
|
20
23
|
|
|
21
24
|
mkdir -p "${PI_DIR}"
|
|
22
25
|
|
|
23
|
-
#
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
# Locate the canonical package list (generated from src/extension-packages.ts).
|
|
27
|
+
# The JSON ships next to the scripts/ directory in the npm package.
|
|
28
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
29
|
+
PKGS_JSON="${SCRIPT_DIR}/../extension-packages.json"
|
|
30
|
+
|
|
31
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
32
|
+
echo "ERROR: jq is required to parse extension-packages.json. Install it: brew install jq"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Install required pi extensions from the canonical list.
|
|
37
|
+
echo "==> Installing required pi extensions..."
|
|
38
|
+
for pkg in $(jq -r '.required[]' "${PKGS_JSON}"); do
|
|
39
|
+
echo " ${pkg}"
|
|
40
|
+
PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
# --op-env is a flag registered by pi-1password; only install optional
|
|
44
|
+
# extensions when their prerequisites are met.
|
|
45
|
+
OP_ENV_HINT=""
|
|
46
|
+
for pkg in $(jq -r '.optional[]' "${PKGS_JSON}"); do
|
|
47
|
+
case "${pkg}" in
|
|
48
|
+
npm:pi-1password)
|
|
49
|
+
if command -v op >/dev/null 2>&1; then
|
|
50
|
+
echo "==> 1Password CLI detected — installing ${pkg}"
|
|
51
|
+
PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
|
|
52
|
+
OP_ENV_HINT=" --op-env .vault-mind/.env.1pass"
|
|
53
|
+
fi
|
|
54
|
+
;;
|
|
55
|
+
*)
|
|
56
|
+
echo "==> Installing optional extension: ${pkg}"
|
|
57
|
+
PI_CODING_AGENT_DIR="${PI_DIR}" pi install "${pkg}"
|
|
58
|
+
;;
|
|
59
|
+
esac
|
|
60
|
+
done
|
|
26
61
|
|
|
27
62
|
echo ""
|
|
28
63
|
echo "==> Done. Start pi for this vault with:"
|
|
29
|
-
echo " PI_CODING_AGENT_DIR=\"${PI_DIR}\" pi"
|
|
64
|
+
echo " PI_CODING_AGENT_DIR=\"${PI_DIR}\" pi${OP_ENV_HINT}"
|
|
30
65
|
echo ""
|
|
31
66
|
echo " Or from Obsidian Shell Commands:"
|
|
32
|
-
echo " PI_CODING_AGENT_DIR=${PI_DIR} pi"
|
|
67
|
+
echo " PI_CODING_AGENT_DIR=${PI_DIR} pi${OP_ENV_HINT}"
|
|
68
|
+
echo ""
|
|
69
|
+
echo " Then inside the pi session, run /vm setup — it scaffolds"
|
|
70
|
+
echo " .vault-mind/vault-mind.config.json AND .vault-mind/.pi/model-router.json"
|
|
71
|
+
echo " (tuned primary/fallback model defaults) in one step."
|
|
@@ -19,10 +19,10 @@
|
|
|
19
19
|
import * as fs from "node:fs";
|
|
20
20
|
import * as path from "node:path";
|
|
21
21
|
import {
|
|
22
|
-
DefaultResourceLoader,
|
|
23
|
-
SessionManager,
|
|
24
22
|
createAgentSession,
|
|
23
|
+
DefaultResourceLoader,
|
|
25
24
|
getAgentDir,
|
|
25
|
+
SessionManager,
|
|
26
26
|
} from "@earendil-works/pi-coding-agent";
|
|
27
27
|
|
|
28
28
|
const VAULT_PATH = "/Users/kylebrodeur/workspace/recycvape/ReturnVape";
|