pi-vault-mind 0.12.10 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+
4
+ ## 0.13.0 / 0.5.0 — 2026-06-28
5
+
6
+ ### Added
7
+
8
+ - **Arrow.js chat UI refactor** — reactive composer (`chat/arrow/`), model chip in the input bar, mentionable badges with preview popup, per-assistant-message action row (copy/info/rewind), keyboard-navigable session popover, and Arrow-based permission modals.
9
+ - **`@agent` dispatch command** — Obsidian palette command that picks a vault-mind role, prompts for an instruction, and calls `POST /vault-mind/dispatch`; also adds `VaultMindClient.scan()` for immediate tag-based scanning.
10
+ - **Marksman LSP tool wrappers** — `vm_backlinks`, `vm_broken_links`, `vm_related` with graceful degradation when `marksman` is unavailable.
11
+ - **Setup model-router picker** — vault init now lets users choose the primary chat model and fallback model sequence, writing choices directly into `.pi/model-router.json`.
12
+ - **`op://` 1Password resolution** — Modal setup resolves `op://` references via the 1Password CLI and stores the actual token in the Obsidian keychain; test-connection now sends the bearer token and reports detailed errors.
13
+
14
+ ### Changed
15
+
16
+ - `docs/ROADMAP.md` is now the single source of truth for project direction; `docs/NEXT_STEPS.md` is deprecated.
17
+ - `packages/obsidian/package.json` version now tracks the plugin manifest version.
18
+
19
+ ### Fixed
20
+
21
+ - PVM_API_TOKEN injection into the `pi` subprocess via `resolveToken()` in `ChatTab.mount()`.
22
+ - Modal test connection failing silently because it did not send an `Authorization` header.
23
+
3
24
  ## 0.8.0 - 2026-06-24
4
25
 
5
26
  ### Added
@@ -4,7 +4,7 @@
4
4
  * Endpoints:
5
5
  * GET /vault-mind/status → { running, dispatches, vaults, uptime }
6
6
  * POST /vault-mind/scan → scan a file for @agent markers { file }
7
- * POST /vault-mind/dispatch → fire a manual dispatch
7
+ * POST /vault-mind/dispatch → fire a manual dispatch { role, instruction, file?, vault? }
8
8
  * POST /vault-mind/context → push active editor context from Obsidian { filePath, selection, cursor }
9
9
  * GET /vault-mind/context → latest editor context (file, cursor, selection) for pi agents
10
10
  *
@@ -4,7 +4,7 @@
4
4
  * Endpoints:
5
5
  * GET /vault-mind/status → { running, dispatches, vaults, uptime }
6
6
  * POST /vault-mind/scan → scan a file for @agent markers { file }
7
- * POST /vault-mind/dispatch → fire a manual dispatch
7
+ * POST /vault-mind/dispatch → fire a manual dispatch { role, instruction, file?, vault? }
8
8
  * POST /vault-mind/context → push active editor context from Obsidian { filePath, selection, cursor }
9
9
  * GET /vault-mind/context → latest editor context (file, cursor, selection) for pi agents
10
10
  *
@@ -33,7 +33,7 @@ import { queryGraph } from "./graph.js";
33
33
  import { searchFts, searchHybrid, upsertEntry } from "./lance.js";
34
34
  import { DEFAULT_CONFIG } from "./types.js";
35
35
  import { ensureDir, expandHome, findConfig, loadConfig, resolveInitCollectionName, shrinkHome, } from "./utils.js";
36
- import { processQueue, scanFile, startWatcher, stopWatcher } from "./watcher.js";
36
+ import { createManualDispatch, processQueue, scanFile, startWatcher, stopWatcher, } from "./watcher.js";
37
37
  export function createServerState(port = 11435) {
38
38
  return { server: null, wss: null, port, startTime: 0 };
39
39
  }
@@ -85,7 +85,7 @@ export function startServer(pi, serverState, watcherState) {
85
85
  }
86
86
  }
87
87
  serverState.vaultPath = vaultPath;
88
- serverState.server = http.createServer((req, res) => {
88
+ serverState.server = http.createServer(async (req, res) => {
89
89
  // CORS for localhost-only access
90
90
  res.setHeader("Access-Control-Allow-Origin", `http://localhost:${serverState.port}`);
91
91
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -116,7 +116,7 @@ export function startServer(pi, serverState, watcherState) {
116
116
  res.end(JSON.stringify({ error: "Method not allowed" }));
117
117
  return;
118
118
  }
119
- handleDispatch(req, res, pi, watcherState);
119
+ await handleDispatch(req, res, pi, watcherState, serverState);
120
120
  break;
121
121
  case "/vault-mind/context":
122
122
  if (req.method === "POST") {
@@ -427,11 +427,61 @@ function handleScan(req, res, pi, watcherState) {
427
427
  }
428
428
  });
429
429
  }
430
- function handleDispatch(req, res, _pi, _watcherState) {
430
+ async function handleDispatch(req, res, pi, watcherState, serverState) {
431
+ let body;
432
+ try {
433
+ body = await readJsonBody(req);
434
+ }
435
+ catch {
436
+ res.writeHead(400);
437
+ res.end(JSON.stringify({ error: "Invalid JSON body" }));
438
+ return;
439
+ }
440
+ if (!body || typeof body !== "object") {
441
+ res.writeHead(400);
442
+ res.end(JSON.stringify({ error: "Expected a JSON object body" }));
443
+ return;
444
+ }
445
+ if (!("role" in body) || typeof body.role !== "string" || body.role.trim() === "") {
446
+ res.writeHead(400);
447
+ res.end(JSON.stringify({ error: "Missing or invalid 'role' field" }));
448
+ return;
449
+ }
450
+ if (!("instruction" in body) ||
451
+ typeof body.instruction !== "string" ||
452
+ body.instruction.trim() === "") {
453
+ res.writeHead(400);
454
+ res.end(JSON.stringify({ error: "Missing or invalid 'instruction' field" }));
455
+ return;
456
+ }
457
+ if ("file" in body && body.file !== undefined && typeof body.file !== "string") {
458
+ res.writeHead(400);
459
+ res.end(JSON.stringify({ error: "'file' must be a string when provided" }));
460
+ return;
461
+ }
462
+ if ("vault" in body && body.vault !== undefined && typeof body.vault !== "string") {
463
+ res.writeHead(400);
464
+ res.end(JSON.stringify({ error: "'vault' must be a string when provided" }));
465
+ return;
466
+ }
467
+ const role = body.role.trim();
468
+ const instruction = body.instruction.trim();
469
+ const file = "file" in body && typeof body.file === "string" ? body.file : undefined;
470
+ const vault = "vault" in body && typeof body.vault === "string" ? body.vault : undefined;
471
+ const vaultPath = vault || serverState.vaultPath || process.cwd();
472
+ const result = createManualDispatch(pi, watcherState, {
473
+ role,
474
+ instruction,
475
+ filePath: file,
476
+ vaultPath,
477
+ });
478
+ if ("error" in result) {
479
+ res.writeHead(500);
480
+ res.end(JSON.stringify({ error: result.error }));
481
+ return;
482
+ }
431
483
  res.writeHead(200);
432
- res.end(JSON.stringify({
433
- message: "Manual dispatch not yet implemented. Use /vm watcher status for state.",
434
- }));
484
+ res.end(JSON.stringify({ ok: true, jobId: result.jobId, message: result.message }));
435
485
  }
436
486
  async function handleVaultMindContextUpdate(req, res, serverState) {
437
487
  const body = (await readJsonBody(req));
@@ -0,0 +1,93 @@
1
+ import { type ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import { Type } from "typebox";
3
+ export declare const isMarksmanAvailable: () => Promise<boolean>;
4
+ export type LspPosition = {
5
+ line: number;
6
+ character: number;
7
+ };
8
+ export type LspRange = {
9
+ start: LspPosition;
10
+ end: LspPosition;
11
+ };
12
+ export type LspLocation = {
13
+ uri: string;
14
+ range: LspRange;
15
+ };
16
+ export type LspDocumentLink = {
17
+ range: LspRange;
18
+ target?: string;
19
+ tooltip?: string;
20
+ };
21
+ export type LspDiagnostic = {
22
+ range: LspRange;
23
+ severity?: number;
24
+ code?: string | number;
25
+ source?: string;
26
+ message: string;
27
+ };
28
+ export type WorkspaceDiagnosticItem = {
29
+ uri: string;
30
+ kind?: number;
31
+ items: LspDiagnostic[];
32
+ };
33
+ export interface IMarksmanClient {
34
+ initialize(): Promise<void>;
35
+ openDocument(uri: string, text?: string): Promise<void>;
36
+ closeDocument(uri: string): Promise<void>;
37
+ documentLinks(uri: string): Promise<LspDocumentLink[]>;
38
+ documentSymbols(uri: string): Promise<unknown[]>;
39
+ references(uri: string, position: LspPosition): Promise<LspLocation[]>;
40
+ workspaceDiagnostics(): Promise<WorkspaceDiagnosticItem[]>;
41
+ shutdown(): Promise<void>;
42
+ }
43
+ export declare class MarksmanClient implements IMarksmanClient {
44
+ #private;
45
+ constructor(vaultPath: string);
46
+ initialize(): Promise<void>;
47
+ openDocument(uri: string, text?: string): Promise<void>;
48
+ closeDocument(uri: string): Promise<void>;
49
+ documentLinks(uri: string): Promise<LspDocumentLink[]>;
50
+ documentSymbols(uri: string): Promise<unknown[]>;
51
+ references(uri: string, position: LspPosition): Promise<LspLocation[]>;
52
+ workspaceDiagnostics(): Promise<WorkspaceDiagnosticItem[]>;
53
+ shutdown(): Promise<void>;
54
+ }
55
+ type Backlink = {
56
+ file: string;
57
+ target?: string;
58
+ range?: LspRange;
59
+ };
60
+ export declare const backlinksForNote: (client: IMarksmanClient, vaultPath: string, note: string) => Promise<{
61
+ note: string;
62
+ backlinks: Backlink[];
63
+ }>;
64
+ type BrokenLink = {
65
+ file: string;
66
+ message: string;
67
+ range?: LspRange;
68
+ };
69
+ export declare const brokenLinks: (client: IMarksmanClient, vaultPath: string) => Promise<{
70
+ broken: BrokenLink[];
71
+ }>;
72
+ type Edge = {
73
+ from: string;
74
+ to: string;
75
+ type: "backlink" | "forward";
76
+ };
77
+ export declare const relatedNotes: (client: IMarksmanClient, vaultPath: string, entity: string, depth?: number) => Promise<{
78
+ entity: string;
79
+ depth: number;
80
+ nodes: string[];
81
+ edges: Edge[];
82
+ titles: Record<string, string>;
83
+ }>;
84
+ export declare const vmBacklinksTool: import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
85
+ note: Type.TString;
86
+ }>, Record<string, unknown>, any> & import("@earendil-works/pi-coding-agent").ToolDefinition<any, any, any>;
87
+ export declare const vmBrokenLinksTool: import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{}>, Record<string, unknown>, any> & import("@earendil-works/pi-coding-agent").ToolDefinition<any, any, any>;
88
+ export declare const vmRelatedTool: import("@earendil-works/pi-coding-agent").ToolDefinition<Type.TObject<{
89
+ entity: Type.TString;
90
+ depth: Type.TOptional<Type.TNumber>;
91
+ }>, Record<string, unknown>, any> & import("@earendil-works/pi-coding-agent").ToolDefinition<any, any, any>;
92
+ export declare const registerMarksmanTools: (pi: ExtensionAPI) => void;
93
+ export {};