pi-vault-mind 0.11.0 → 0.12.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.
@@ -18,7 +18,8 @@
18
18
  * GET /vm/status, POST /vm/init, POST /vm/setup, POST /server/start,
19
19
  * GET /vault-mind/config, GET /agent/queue, GET /agent/jobs/:id,
20
20
  * POST /agent/jobs/:id/retry, POST /agent/jobs/:id/cancel,
21
- * POST /vm/search, POST /vm/append, WS /agent/stream.
21
+ * POST /vm/search, POST /vm/append, WS /agent/stream,
22
+ * GET /vm/pending, POST /vm/approve.
22
23
  */
23
24
  import * as http from "node:http";
24
25
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
@@ -18,7 +18,8 @@
18
18
  * GET /vm/status, POST /vm/init, POST /vm/setup, POST /server/start,
19
19
  * GET /vault-mind/config, GET /agent/queue, GET /agent/jobs/:id,
20
20
  * POST /agent/jobs/:id/retry, POST /agent/jobs/:id/cancel,
21
- * POST /vm/search, POST /vm/append, WS /agent/stream.
21
+ * POST /vm/search, POST /vm/append, WS /agent/stream,
22
+ * GET /vm/pending, POST /vm/approve.
22
23
  */
23
24
  import * as fs from "node:fs";
24
25
  import * as http from "node:http";
@@ -28,7 +29,8 @@ import { WebSocket, WebSocketServer } from "ws";
28
29
  import { cancelJobHelper, getJob, listJobs, retryJob, subscribeQueue, } from "./agent-queue.js";
29
30
  import { authoriseRequest, resolveAuthToken } from "./auth.js";
30
31
  import { GITIGNORE_ENTRIES } from "./commands.js";
31
- import { searchHybrid, upsertEntry } from "./lance.js";
32
+ import { queryGraph } from "./graph.js";
33
+ import { searchFts, searchHybrid, upsertEntry } from "./lance.js";
32
34
  import { DEFAULT_CONFIG } from "./types.js";
33
35
  import { ensureDir, expandHome, findConfig, loadConfig, resolveInitCollectionName, shrinkHome, } from "./utils.js";
34
36
  import { processQueue, scanFile, startWatcher, stopWatcher } from "./watcher.js";
@@ -190,6 +192,38 @@ export function startServer(pi, serverState, watcherState) {
190
192
  }
191
193
  withAuth(req, res, () => handleVmAppend(req, res, serverState), { requireWrite: true });
192
194
  break;
195
+ case "/vm/fts-search":
196
+ if (req.method !== "POST") {
197
+ res.writeHead(405);
198
+ res.end(JSON.stringify({ error: "Method not allowed" }));
199
+ return;
200
+ }
201
+ withAuth(req, res, () => handleVmFtsSearch(req, res, serverState));
202
+ break;
203
+ case "/vm/graph":
204
+ if (req.method !== "POST") {
205
+ res.writeHead(405);
206
+ res.end(JSON.stringify({ error: "Method not allowed" }));
207
+ return;
208
+ }
209
+ withAuth(req, res, () => handleVmGraph(req, res, serverState));
210
+ break;
211
+ case "/vm/approve":
212
+ if (req.method !== "POST") {
213
+ res.writeHead(405);
214
+ res.end(JSON.stringify({ error: "Method not allowed" }));
215
+ return;
216
+ }
217
+ withAuth(req, res, () => handleVmApprove(req, res, serverState), { requireWrite: true });
218
+ break;
219
+ case "/vm/pending":
220
+ if (req.method !== "GET") {
221
+ res.writeHead(405);
222
+ res.end(JSON.stringify({ error: "Method not allowed" }));
223
+ return;
224
+ }
225
+ withAuth(req, res, () => handleVmPending(res, serverState));
226
+ break;
193
227
  default:
194
228
  if (url.pathname.startsWith("/agent/jobs/")) {
195
229
  withAuth(req, res, () => handleAgentJob(req, res, url.pathname), {
@@ -838,3 +872,140 @@ async function handleVmAppend(req, res, serverState) {
838
872
  res.end(JSON.stringify({ error: message }));
839
873
  }
840
874
  }
875
+ async function handleVmFtsSearch(req, res, serverState) {
876
+ const body = (await readJsonBody(req));
877
+ if (!body.query) {
878
+ res.writeHead(400);
879
+ res.end(JSON.stringify({ error: "query required" }));
880
+ return;
881
+ }
882
+ const vaultPath = serverState.vaultPath || process.cwd();
883
+ const cfg = loadConfig(vaultPath);
884
+ const collection = body.collection || "main";
885
+ const limit = body.limit ?? 10;
886
+ try {
887
+ const results = await searchFts(cfg.vaultMind.dataDir, collection, body.query, limit, cfg.vaultMind);
888
+ res.writeHead(200);
889
+ res.end(JSON.stringify({ hits: results, collection, query: body.query }));
890
+ }
891
+ catch (err) {
892
+ const message = err instanceof Error ? err.message : String(err);
893
+ res.writeHead(500);
894
+ res.end(JSON.stringify({ error: message }));
895
+ }
896
+ }
897
+ async function handleVmGraph(req, res, serverState) {
898
+ const body = (await readJsonBody(req));
899
+ if (!body.entity) {
900
+ res.writeHead(400);
901
+ res.end(JSON.stringify({ error: "entity required" }));
902
+ return;
903
+ }
904
+ const vaultPath = serverState.vaultPath || process.cwd();
905
+ const cfg = loadConfig(vaultPath);
906
+ const depth = body.depth ?? 1;
907
+ try {
908
+ const results = await queryGraph(cfg.vaultMind.dataDir, cfg.vaultMind, body.entity, depth);
909
+ res.writeHead(200);
910
+ res.end(JSON.stringify({ entity: body.entity, depth, results }));
911
+ }
912
+ catch (err) {
913
+ const message = err instanceof Error ? err.message : String(err);
914
+ res.writeHead(500);
915
+ res.end(JSON.stringify({ error: message }));
916
+ }
917
+ }
918
+ // ── Pending review route ─────────────────────────────────────────────────────
919
+ async function handleVmApprove(req, res, serverState) {
920
+ const body = (await readJsonBody(req));
921
+ const id = typeof body.id === "string" ? body.id : undefined;
922
+ const collection = typeof body.collection === "string" ? body.collection : undefined;
923
+ const action = body.action === "approve" || body.action === "reject" ? body.action : undefined;
924
+ if (!id || !collection || !action) {
925
+ res.writeHead(400);
926
+ res.end(JSON.stringify({ error: "Missing required fields: id, collection, action (approve|reject)." }));
927
+ return;
928
+ }
929
+ const vaultPath = serverState.vaultPath || process.cwd();
930
+ const cfg = loadConfig(vaultPath);
931
+ const def = cfg.collections[collection];
932
+ if (!def) {
933
+ res.writeHead(400);
934
+ res.end(JSON.stringify({
935
+ error: `Unknown collection "${collection}".`,
936
+ available: Object.keys(cfg.collections),
937
+ }));
938
+ return;
939
+ }
940
+ if (!fs.existsSync(def.path)) {
941
+ res.writeHead(404);
942
+ res.end(JSON.stringify({ error: `Collection file not found: ${def.path}` }));
943
+ return;
944
+ }
945
+ const raw = fs.readFileSync(def.path, "utf-8");
946
+ const hasTrailingNewline = raw.endsWith("\n");
947
+ const lines = raw.split("\n");
948
+ const entries = hasTrailingNewline ? lines.slice(0, -1) : lines;
949
+ let foundIndex = -1;
950
+ for (let i = 0; i < entries.length; i++) {
951
+ const line = entries[i].trim();
952
+ if (!line)
953
+ continue;
954
+ try {
955
+ const entry = JSON.parse(line);
956
+ if (entry && entry.id === id) {
957
+ foundIndex = i;
958
+ break;
959
+ }
960
+ }
961
+ catch {
962
+ /* skip malformed lines */
963
+ }
964
+ }
965
+ if (foundIndex === -1) {
966
+ res.writeHead(404);
967
+ res.end(JSON.stringify({ error: `Entry "${id}" not found in collection "${collection}".` }));
968
+ return;
969
+ }
970
+ const entry = JSON.parse(entries[foundIndex]);
971
+ entry.status = action === "approve" ? "approved" : "rejected";
972
+ entries[foundIndex] = JSON.stringify(entry);
973
+ fs.writeFileSync(def.path, `${entries.join("\n")}\n`, "utf-8");
974
+ res.writeHead(200);
975
+ res.end(JSON.stringify({ ok: true, id, action }));
976
+ }
977
+ function handleVmPending(res, serverState) {
978
+ const vaultPath = serverState.vaultPath || process.cwd();
979
+ const cfg = loadConfig(vaultPath);
980
+ const pending = [];
981
+ for (const [collectionName, def] of Object.entries(cfg.collections)) {
982
+ if (!fs.existsSync(def.path))
983
+ continue;
984
+ let raw;
985
+ try {
986
+ raw = fs.readFileSync(def.path, "utf-8");
987
+ }
988
+ catch {
989
+ continue;
990
+ }
991
+ const lines = raw.split("\n");
992
+ for (let i = 0; i < lines.length; i++) {
993
+ const trimmed = lines[i].trim();
994
+ if (!trimmed)
995
+ continue;
996
+ let entry;
997
+ try {
998
+ entry = JSON.parse(trimmed);
999
+ }
1000
+ catch {
1001
+ continue;
1002
+ }
1003
+ if (entry.status !== "pending")
1004
+ continue;
1005
+ const id = typeof entry.id === "string" ? entry.id : String(i + 1);
1006
+ pending.push({ id, collection: collectionName, entry });
1007
+ }
1008
+ }
1009
+ res.writeHead(200);
1010
+ res.end(JSON.stringify({ pending }));
1011
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-vault-mind",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
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",
@@ -26,6 +26,8 @@ rm -f "$VAULT/pi-vault-mind.config.json"
26
26
  rm -rf "$VAULT/.lancedb"
27
27
  rm -rf "$VAULT/collections"
28
28
  rm -f "$VAULT/_sync_state.json"
29
+ rm -rf "$VAULT/.vault-mind"
30
+ rm -f "$VAULT/.obsidian/plugins/vault-mind/data.json"
29
31
 
30
32
  # Update plugin to latest build (if in the pi-vault-mind repo)
31
33
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"