pi-vault-mind 0.15.0 → 0.16.4

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.
Files changed (151) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/README.md +4 -5
  3. package/agents/broadcaster.agent.md +2 -2
  4. package/agents/heavy-lifter.agent.md +1 -1
  5. package/agents/main.agent.md +29 -0
  6. package/agents/manager.agent.md +1 -1
  7. package/agents/miner.agent.md +1 -1
  8. package/dist/packages/obsidian/src/client.js +331 -0
  9. package/dist/packages/obsidian/src/types.js +1 -0
  10. package/dist/src/activity.d.ts +49 -0
  11. package/dist/src/activity.js +175 -0
  12. package/dist/src/agent-bus/Agent.d.ts +9 -1
  13. package/dist/src/agent-bus/Agent.js +18 -2
  14. package/dist/src/agent-bus/index.d.ts +5 -5
  15. package/dist/src/agent-bus/index.js +1 -1
  16. package/dist/src/agent-queue.js +1 -1
  17. package/dist/src/agents/BroadcasterAgent.d.ts +1 -1
  18. package/dist/src/agents/BroadcasterAgent.js +2 -2
  19. package/dist/src/agents/HeavyLifterAgent.d.ts +1 -1
  20. package/dist/src/agents/HeavyLifterAgent.js +2 -2
  21. package/dist/src/agents/ManagerAgent.js +1 -1
  22. package/dist/src/agents/MinerAgent.d.ts +1 -1
  23. package/dist/src/agents/MinerAgent.js +2 -2
  24. package/dist/src/agents/index.d.ts +2 -2
  25. package/dist/src/agents/index.js +2 -2
  26. package/dist/src/auth.d.ts +7 -5
  27. package/dist/src/auth.js +12 -19
  28. package/dist/src/autosync.js +1 -1
  29. package/dist/src/commands.d.ts +2 -25
  30. package/dist/src/commands.js +223 -288
  31. package/dist/src/config-keys.d.ts +16 -0
  32. package/dist/src/config-keys.js +45 -0
  33. package/dist/src/edits.d.ts +21 -0
  34. package/dist/src/edits.js +75 -0
  35. package/dist/src/embedding-probe.d.ts +34 -0
  36. package/dist/src/embedding-probe.js +142 -0
  37. package/dist/src/embedding-providers.d.ts +133 -0
  38. package/dist/src/embedding-providers.js +173 -0
  39. package/dist/src/embedding-secrets.d.ts +38 -0
  40. package/dist/src/embedding-secrets.js +291 -0
  41. package/dist/src/engine.d.ts +6 -0
  42. package/dist/src/engine.js +78 -6
  43. package/dist/src/events.js +1 -1
  44. package/dist/src/extension-packages.d.ts +9 -0
  45. package/dist/src/extension-packages.js +14 -0
  46. package/dist/src/git.d.ts +19 -0
  47. package/dist/src/git.js +78 -0
  48. package/dist/src/graph.d.ts +2 -1
  49. package/dist/src/graph.js +47 -13
  50. package/dist/src/identities-config.d.ts +6 -0
  51. package/dist/src/identities-config.js +39 -0
  52. package/dist/src/identity-injector.js +1 -1
  53. package/dist/src/index.js +4 -8
  54. package/dist/src/lance.d.ts +9 -0
  55. package/dist/src/lance.js +143 -203
  56. package/dist/src/modal-client.d.ts +16 -0
  57. package/dist/src/modal-client.js +16 -0
  58. package/dist/src/modal-config.d.ts +16 -10
  59. package/dist/src/modal-config.js +31 -25
  60. package/dist/src/model-router.js +10 -8
  61. package/dist/src/models.d.ts +42 -0
  62. package/dist/src/models.js +78 -0
  63. package/dist/src/scaffold.d.ts +48 -0
  64. package/dist/src/scaffold.js +246 -0
  65. package/dist/src/server.d.ts +46 -3
  66. package/dist/src/server.js +962 -183
  67. package/dist/src/session-search.d.ts +63 -0
  68. package/dist/src/session-search.js +379 -0
  69. package/dist/src/settings-ui.d.ts +1 -1
  70. package/dist/src/settings-ui.js +54 -107
  71. package/dist/src/sync.js +3 -3
  72. package/dist/src/tool-catalog.d.ts +18 -0
  73. package/dist/src/tool-catalog.js +232 -0
  74. package/dist/src/tools/marksman.js +1 -1
  75. package/dist/src/tools.js +108 -62
  76. package/dist/src/types.d.ts +106 -73
  77. package/dist/src/utils.d.ts +2 -12
  78. package/dist/src/utils.js +7 -60
  79. package/dist/src/vault-tools.d.ts +34 -0
  80. package/dist/src/vault-tools.js +99 -0
  81. package/dist/src/vm-handlers.js +138 -41
  82. package/dist/src/watcher.js +7 -7
  83. package/dist/test/activity-tool.test.js +54 -0
  84. package/dist/test/agent-bus/Agent.test.js +174 -0
  85. package/dist/test/agent-bus/AgentLoader.test.js +180 -0
  86. package/dist/test/agent-bus/AgentRegistry.test.js +86 -0
  87. package/dist/test/agent-bus/LLMProvider.test.js +162 -0
  88. package/dist/test/agent-bus/TaskQueue.test.js +256 -0
  89. package/dist/test/agent-dispatch-context.test.js +73 -0
  90. package/dist/test/agent-queue.test.js +1 -1
  91. package/dist/test/agents/Agents.test.js +282 -0
  92. package/dist/test/auth.test.js +28 -25
  93. package/dist/test/bridge-identity.test.js +31 -2
  94. package/dist/test/classify-search-mode.test.js +51 -0
  95. package/dist/test/commands.test.js +410 -0
  96. package/dist/test/config-merge.test.js +98 -0
  97. package/dist/test/context-capture.test.js +56 -0
  98. package/dist/test/dispatch.test.js +39 -89
  99. package/dist/test/edits.test.js +144 -0
  100. package/dist/test/embedding-probe.test.js +424 -0
  101. package/dist/test/embedding-providers.test.js +157 -0
  102. package/dist/test/embedding-secrets.test.js +210 -0
  103. package/dist/test/engine-identity.test.js +1 -1
  104. package/dist/test/events.test.js +675 -0
  105. package/dist/test/git-bridge.test.js +160 -0
  106. package/dist/test/graph.test.js +464 -0
  107. package/dist/test/helpers/server.js +81 -0
  108. package/dist/test/identity-injector.test.js +197 -0
  109. package/dist/test/index.test.js +1 -5
  110. package/dist/test/lance-modal.test.js +67 -2
  111. package/dist/test/lance.test.js +429 -0
  112. package/dist/test/modal-client.test.js +1 -1
  113. package/dist/test/modal-config.test.js +92 -79
  114. package/dist/test/model-readiness.test.js +122 -0
  115. package/dist/test/model-router.test.js +14 -9
  116. package/dist/test/obsidian-client.test.js +42 -0
  117. package/dist/test/personalize.test.js +1 -1
  118. package/dist/test/rest-models.test.js +153 -0
  119. package/dist/test/rest-queue.test.js +128 -79
  120. package/dist/test/rest-setup.test.js +613 -101
  121. package/dist/test/rest-vm.test.js +343 -79
  122. package/dist/test/server-cors.test.js +48 -0
  123. package/dist/test/server-ws.test.js +55 -31
  124. package/dist/test/session-crud.test.js +323 -0
  125. package/dist/test/session-search.test.js +139 -0
  126. package/dist/test/settings-ui.test.js +61 -15
  127. package/dist/test/sync.test.js +2 -1
  128. package/dist/test/tombstone.test.js +1 -1
  129. package/dist/test/tool-catalog.test.js +86 -0
  130. package/dist/test/utils.test.js +588 -0
  131. package/dist/test/vault-tools.test.js +96 -3
  132. package/dist/test/vault-writer.test.js +371 -0
  133. package/dist/test/vm-handlers.test.js +620 -0
  134. package/extension-packages.json +9 -0
  135. package/package.json +20 -10
  136. package/pi-vault-mind.config.example.json +9 -1
  137. package/scripts/check-deps.sh +35 -32
  138. package/scripts/cli-e2e.mjs +215 -0
  139. package/scripts/configuration-e2e.mjs +783 -0
  140. package/scripts/e2e-commands.mjs +391 -0
  141. package/scripts/generate-config-keys-json.mjs +45 -0
  142. package/scripts/generate-extension-packages-json.mjs +55 -0
  143. package/scripts/setup-vault-pi.sh +34 -17
  144. package/scripts/test-slash-commands.mjs +2 -2
  145. package/skills/github-release-notes-file/SKILL.md +42 -0
  146. package/skills/obsidian-guided-test-session/SKILL.md +110 -0
  147. package/skills/obsidian-interactive-test/SKILL.md +88 -0
  148. package/skills/obsidian-plugin-publish/SKILL.md +58 -0
  149. package/skills/pi-vault-mind-release/SKILL.md +141 -0
  150. package/skills/pre-test-release/SKILL.md +116 -0
  151. package/dist/test/auto-indexer.test.js +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,239 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.16.4 / 0.6.6 — 2026-07-17
4
+
5
+ ### Fixed
6
+
7
+ - **Obsidian-contained popovers.** Shared folder pickers now convert viewport anchor coordinates into the workspace leaf's fixed containing block, so their menus stay aligned with their input fields in the real Obsidian shell.
8
+ - **Cloud-only default chat routing.** New vault model-router configurations use `ollama/gemma4:31b-cloud` for every auto profile tier and explicit cloud fallbacks only. ReturnVape's active vault-local runtime configuration was migrated to remove local Gemma chat routes; local embedding models remain unchanged.
9
+
10
+ ### Tests
11
+
12
+ - Added focused regression coverage for contained popover placement and cloud-only model-router defaults/profiles.
13
+
14
+ ## 0.16.3 / 0.6.5 — 2026-07-16
15
+
16
+ ### Added
17
+
18
+ - **`GET /vault-mind/models` endpoint.** Auth-gated route returns pi's
19
+ provider/model catalog (`{ providers: ProviderInfo[] }`, plus an
20
+ `error` field when a `models.json` exists but fails to parse), merged
21
+ from the agent-dir and project-level `models.json` (project overrides
22
+ agent-dir for same-named providers). `src/models.ts`
23
+ `readModelProviders()` mirrors the plugin's `pi-config-reader.ts`
24
+ flattened shape (`$`-stripped `envVarName`, model `name` falls back to
25
+ `id`, providers sorted by name). New `VaultMindClient.getModels()`
26
+ (typed `VmModelsResponse`) is the client bridge so the plugin can
27
+ replace synchronous `models.json` disk reads with a live call. Covered
28
+ by `test/rest-models.test.ts` (auth, empty, parse, override, malformed).
29
+
30
+ - **Extension-owned setup and configuration surfaces.** Added masked
31
+ provider-credential status and atomic extension-owned secret storage,
32
+ Local/Remote live embedding probes, complete typed setup/configuration
33
+ client methods, default-vault sibling preservation, and a deterministic
34
+ configuration E2E smoke. Added the portable seven-step Arrow setup
35
+ wizard and twelve-category section-save Settings UI, then ported it into
36
+ the Obsidian plugin behind a thin `RestConfigurationAdapter`. The plugin
37
+ now uses typed runtime model discovery instead of synchronous
38
+ `models.json` reads or writes, routes model selection through Pi RPC,
39
+ mounts Settings through one adapter boundary, renders retryable offline
40
+ recovery rather than a blank pane, and keeps plugin/extension bridge
41
+ credentials separate from embedding-provider credentials. Added
42
+ port-parity canonicalization for type re-exports, CSS/import guards,
43
+ intrinsic SVG icon bounds, and focused regression coverage across the
44
+ extension, sandbox, and plugin.
45
+
46
+ ### Fixed
47
+
48
+ - **Un-imported `modalUrl` in `src/server.ts`** — the
49
+ `handleVaultMindConfig` function destructured `modalUrl` from
50
+ `../modal-config.js` without ever importing it. Would have crashed
51
+ with `TypeError: Cannot read properties of undefined (reading 'space')`
52
+ if the modal config branch was hit. Caught by
53
+ `/tmp/pvm-e2e-http-setup-test.mjs`. Import is now in place.
54
+
55
+ ### Documentation
56
+
57
+ - **`vault-mind-config-index.md` §12 — Modal extraction question.** New
58
+ section documenting the open architectural question: should
59
+ `pi-vault-mind`'s Modal-specific code (HTTP client, sync engine,
60
+ `ModalProvider`, `/vm remote *` slash commands, `embedding.sync`
61
+ config block, Python `modal/` app) move into a separate package
62
+ (`pi-vault-mind-remote-modal/`) so the extension is truly
63
+ provider-agnostic? Three options documented (A: namespace in-tree /
64
+ B: extract to a package / C: server+client out, CLI stays). Smaller
65
+ win called out regardless of option: drop `/vm remote *` in favor of
66
+ `--remoteUrl` / `--remoteApiKey` flags on `/vm setup`. Awaiting
67
+ user decision; no work committed.
68
+ - **§7.1 scope column.** Every `/vm *` slash command is now tagged
69
+ `🟢 agnostic` / `🟡 Modal-specific` / `🔵 token` so the reader can
70
+ see exactly which commands depend on Modal.
71
+
72
+ ## 0.16.2 — 2026-07-07
73
+
74
+ ### Changed
75
+
76
+ - **`ModalEmbeddingConfig` flattened into `EmbeddingConfig`.** Modal
77
+ is just an OpenAI-compatible endpoint, so the `embedding.modal: {
78
+ baseUrl, workspace, apiToken, readToken, writeToken, model, dim,
79
+ fallback, sync }` wrapper is gone. All fields are now top-level on
80
+ `EmbeddingConfig` itself: `workspace`, `remoteApiKey`,
81
+ `remoteReadApiKey`, `remoteWriteApiKey`, `model`, `dim`, `fallback`
82
+ (now a simplified `{ enabled?: boolean }` — the `provider`
83
+ discriminator was already removed in 0.16.1+), and `sync`. **`apiKey`
84
+ is now explicitly the local-Ollama bearer** (defaults to `"ollama"`
85
+ in `OpenAICompatibleProvider`); the remote-endpoint token is
86
+ `remoteApiKey` so a single config can carry distinct local-vs-remote
87
+ credentials. The Modal-only blocks that remain as their own
88
+ inline objects are `sync` (pull-down watermark behavior) and
89
+ `fallback` (offline degrade policy). The `MODAL_APP_NAME` URL
90
+ helper stays in `src/modal-config.ts` (`modalUrl(workspace)`); the
91
+ Obsidian plugin keeps a small local mirror because it can't import
92
+ from the extension at runtime. `isModal()` is now simply
93
+ `!!cfg.embedding.remoteUrl`. The Obsidian plugin's
94
+ `provider: "ollama" | "modal" | "skip"` UI discriminator was renamed
95
+ to `"local" | "remote" | "skip"` (display labels stay user-friendly).
96
+ On save, the Modal wizard removes the old `embedding.modal` block
97
+ so existing configs upgrade cleanly on next `/vm setup`. The index
98
+ doc `vault-mind-config-index.md` §2.4 is updated; the per-setting
99
+ plan `vault-mind-config-plugin-surface.md` is reconciled.
100
+
101
+ ### Removed
102
+
103
+ - **`ModalEmbeddingConfig` and `ModalFallbackConfig` types removed
104
+ from `src/types.ts`.** Fields are inlined on `EmbeddingConfig` (see
105
+ above). `FallbackConfig` (`{ enabled?: boolean }`) is the new
106
+ simplified fallback shape; `ModalSyncConfig` is unchanged.
107
+
108
+ ### Tests
109
+
110
+ - 268 tests pass (no change in count — the migration was mechanical).
111
+ Three test files migrated from `modal: { ... }` fixtures to the
112
+ flat shape: `test/auth.test.ts`, `test/lance-modal.test.ts`,
113
+ `test/modal-config.test.ts`. The `ModalFallbackConfig["provider"]`
114
+ type-level regression test was removed (the type no longer exists).
115
+ One collateral fixture in `test/sync.test.ts`. One collateral
116
+ fixture in `test/vault-tools.test.ts` (the `writeConfig` helper
117
+ needed `model: "testmodel"` added so `resolveModel` returns the
118
+ right value for the test's `searchFts` assertion). The provider-cache
119
+ leak in `src/lance.ts`'s `resetConnection()` was fixed (the cache
120
+ was module-level and survived across tests).
121
+
122
+
123
+ ## 0.16.1 — 2026-07-07
124
+
125
+ ### Added
126
+
127
+ - **`src/embedding-providers.ts`** — new store-agnostic `EmbeddingProvider`
128
+ interface (`embed` / `dim` / `init`) and the three concrete providers
129
+ (`OpenAICompatibleProvider`, `TransformersProvider`, `ModalProvider`)
130
+ plus a `createProvider(cfg)` factory. The vector-store layer
131
+ (`src/lance.ts`) now consumes providers through a 35-line
132
+ `LanceEmbeddingAdapter` that wraps them into LanceDB's
133
+ `TextEmbeddingFunction`. **Adding a new vector store (Qdrant, Pinecone,
134
+ pgvector, …) means writing a new adapter; adding a new embedding
135
+ provider means implementing the interface.** No downstream code (lance,
136
+ coalescer, plugin) needs to change for either.
137
+ - **`/vm remote config fallback`** accepts `"ollama"` as a friendlier
138
+ alias for `"openai-compatible"` (rewritten to the enum value before
139
+ write). Help text and error messages updated.
140
+
141
+ ### Removed
142
+
143
+ - **`ModalFallbackConfig.provider` enum reduced to `"openai-compatible"`.**
144
+ The legacy `"transformers"` value was dead code (no runtime path
145
+ selected it). The offline transformers path lives separately under
146
+ `EmbeddingConfig.useTransformers`. `/vm remote config fallback` parser
147
+ no longer accepts `"transformers"`. Type-level regression test
148
+ prevents the dead value from being added back.
149
+
150
+ ### Fixed
151
+
152
+ - **`LanceEmbeddingFunction` now defaults `apiKey` to `"ollama"`** for
153
+ local endpoints, matching `AgentModelProvider`'s behavior and the
154
+ JSDoc contract. The previous `string | undefined` field would have
155
+ sent `Authorization: Bearer undefined` for any vault without an
156
+ explicit `apiKey`; Ollama happened to ignore the malformed header,
157
+ but stricter `/v1/embeddings`-compatible servers would have rejected
158
+ it. Pass `apiKey: null` to opt out (some local servers reject
159
+ `Bearer ollama` as invalid auth).
160
+ - **`IdentityConfig.role` made optional.** It was required at the type
161
+ level but `mergeIdentityProfiles` always pulls `role` (and `id`) from
162
+ the base — config cannot rename an agent. Required type was dead.
163
+ - **Index doc §2.6 `vaultMind.identities` reframed as "wired but
164
+ unenforced"** — the block is read at engine start
165
+ (`engine.ts:124-137` calls `mergeIdentityProfiles` and
166
+ `bridge.ts:100` registers the merged identity), but the registered
167
+ identity is never read back (`getAgentIdentity` in `bridge.ts:117`
168
+ has no callers). The block stays typed; future runtime enforcement
169
+ reuses it. New regression test in `test/bridge-identity.test.ts`
170
+ pins the merge contract (layer values override, empty array is
171
+ explicit deny, role/id always from base, undefined layer is a no-op).
172
+ - **Config example drift closed.** `pi-vault-mind.config.example.json`
173
+ now shows the scaffolded `draft-context` injector instead of an
174
+ empty array — matches the live `src/scaffold.ts` output so a user
175
+ hand-rolling a config from the example doesn't silently miss a
176
+ built-in.
177
+
178
+ ## 0.16.0 — 2026-07-06
179
+
180
+ ### Added
181
+
182
+ - **New `src/scaffold.ts`** — the single, UI-agnostic implementation of vault
183
+ config scaffolding (default collections/injectors, `.gitignore`), shared
184
+ by the CLI `/vm setup` command, the HTTP `POST /vm/setup` route, and any
185
+ future consumer. Robust to caller order: seeds default collections/
186
+ injectors whenever they're empty, not only when the config file is brand
187
+ new, so no future caller can silently end up with `"collections": {}`
188
+ again by writing embedding/vault fields before calling it.
189
+
190
+ ### Removed
191
+
192
+ - **Legacy config/token migration dropped entirely.** `migrateLegacyState()`
193
+ and the `CONFIG_FILES` legacy-filename list are gone; pi-vault-mind no
194
+ longer rescues config/token from a vault-root `pi-vault-mind.config.json`,
195
+ `.pi/vault-mind.config.json`, or the global `~/.pi/agent/`. `.vault-mind/`
196
+ is now the only place pi-vault-mind ever reads its own config/token from —
197
+ no backward-compat fallback path.
198
+ - **`/vm init` removed as a standalone command.** It only ever duplicated
199
+ part of `/vm setup`'s work with a deprecation notice; fully consolidated
200
+ onto `/vm setup`, which now also accepts `--collection <name>` (previously
201
+ only available via `/vm init --collection`). Every "Run /vm init first"
202
+ message across `commands.ts`, `settings-ui.ts`, `tools.ts`,
203
+ `vm-handlers.ts`, and `tools/marksman.ts` now says `/vm setup`.
204
+ - **`POST /vm/init` removed from the HTTP API.** It duplicated a third copy
205
+ of the collections/injectors scaffold template (alongside the CLI's and
206
+ the Obsidian plugin's own copies). `POST /vm/setup` now calls the same
207
+ shared `scaffoldVaultConfig()` after writing embedding/vault fields, so it
208
+ does everything `/vm/init` did plus its own job, and its response now
209
+ includes `created`/`updated`/`skipped`/`collectionName`. The Obsidian
210
+ plugin's `VaultMindClient.init()` (only caller: the chat composer's
211
+ `/reindex` command) is removed too — it now calls `client.setup({})`
212
+ directly, since the reindex flow never used `init()`'s return value.
213
+
214
+ ### Fixed
215
+
216
+ - **`/vm setup` never actually scaffolded default collections.** `handleInit`
217
+ (the scaffold logic) only populates the default `main`/`pending`/
218
+ `context_events` collections and the `draft-context` injector when the
219
+ config file doesn't exist yet — but `setupWizard` always wrote the config
220
+ file *first*, so `handleInit` permanently saw an existing file and took its
221
+ merge-safe (no-op-for-collections) branch instead. Every `/vm setup` run,
222
+ CLI or interactive, ended with `"collections": {}`. Reordered so
223
+ `handleInit` runs before `setupWizard`; verified end-to-end (fresh vault →
224
+ `/vm setup` → all three default collections + injector scaffolded with the
225
+ vault-derived file name, confirmed via direct JSONL read-back).
226
+ - **`loadConfig` returned duplicate injector entries, causing them to fire
227
+ twice.** `mergeConfigLayer` concatenated `DEFAULT_CONFIG.injectors` with
228
+ the vault's own config-file injectors with zero deduplication. Since every
229
+ vault's config now always has its own `draft-context` injector (scaffolded
230
+ by `/vm setup`), `loadConfig()` returned it twice — and `events.ts`
231
+ processes every injector's regex match independently, so a matching note
232
+ would trigger the injector's capture/artifact-sync logic twice per prompt.
233
+ Fixed by deduplicating by `name` in the merge (layer's entry wins on a
234
+ collision), mirroring the existing dedupe pattern in `auditConfig`.
235
+ Verified via `loadConfig` on a freshly-scaffolded vault: exactly one
236
+ `draft-context` injector, not two.
3
237
 
4
238
  ## 0.15.0 / 0.6.4 — 2026-07-06
5
239
 
package/README.md CHANGED
@@ -260,9 +260,9 @@ Edit `pi-vault-mind.config.json` to match your domain:
260
260
  "vaultMind": {
261
261
  "dataDir": ".lancedb",
262
262
  "embedding": {
263
- "provider": "transformers",
264
- "ollamaModel": "embeddinggemma",
265
- "ollamaHost": "http://127.0.0.1:11434"
263
+ "remoteUrl": "http://127.0.0.1:11434",
264
+ "model": "embeddinggemma",
265
+ "dim": 768
266
266
  },
267
267
  "ftsEnabled": true,
268
268
  "graph": { "enabled": true, "canvasSync": false }
@@ -347,7 +347,6 @@ Edit `pi-vault-mind.config.json` to match your domain:
347
347
  | ------------------------------ | -------------------------------------------------------- |
348
348
  | `/vm help` | Show usage help |
349
349
  | `/vm setup` | **Interactive global config wizard** (vault, embedding) |
350
- | `/vm init` | Scaffold project config + collections |
351
350
  | `/vm validate` | Health check LanceDB, config, and all collection paths |
352
351
  | `/vm approve [collection]` | Batch-review pending entries |
353
352
  | `/vm settings` | Open interactive settings dashboard |
@@ -358,7 +357,7 @@ Edit `pi-vault-mind.config.json` to match your domain:
358
357
  | `/vm injector create` | Interactive wizard to create a new injector |
359
358
  | `/vm context enable \| disable \| status` | Manage pi-context integration |
360
359
  | `/vm embedding status \| use \| model \| models \| pull` | Manage embedding provider |
361
- | `/vm modal status \| config \| sync \| jobs \| migrate` | Manage Modal embedding + vector sync |
360
+ | `/vm remote status \| config \| sync \| jobs \| migrate` | Manage remote embedding + vector sync |
362
361
  | `/vm watcher start \| stop \| status` | Manage the passive file watcher |
363
362
  | `/vm server status` | Show HTTP server status, port, and uptime |
364
363
 
@@ -2,7 +2,7 @@
2
2
  type: BroadcasterAgent
3
3
  role: broadcaster
4
4
  capabilities: [read, write, edit]
5
- allowed_tools: [read, write, edit, vm_search, vm_fts_search]
5
+ allowed_tools: [read, write, edit, vm_search]
6
6
  write_collections: []
7
7
  can_publish: false
8
8
  llm_provider: ollama
@@ -16,7 +16,7 @@ and external-facing content from vault knowledge.
16
16
  ## Capability Boundary
17
17
 
18
18
  - **MAY**: read files, write files, edit files
19
- - **MAY**: search collections via vm_search, vm_fts_search
19
+ - **MAY**: search collections via vm_search
20
20
  - **MUST NOT**: run bash commands, grep, or find
21
21
  - **MUST NOT**: write to the durable knowledge store
22
22
  - **MUST NOT**: publish or spawn sub-agents
@@ -2,7 +2,7 @@
2
2
  type: HeavyLifterAgent
3
3
  role: heavy-lifter
4
4
  capabilities: [read, write, edit, grep, find, ls, bash]
5
- allowed_tools: [read, write, edit, grep, find, ls, bash, vm_search, vm_fts_search]
5
+ allowed_tools: [read, write, edit, grep, find, ls, bash, vm_search]
6
6
  write_collections: []
7
7
  can_publish: false
8
8
  llm_provider: ollama
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: ManagerAgent
3
+ role: main
4
+ capabilities: [read, write, edit, bash, grep, find, ls]
5
+ allowed_tools: [read, write, edit, bash, grep, find, ls, vm_search, vm_query, vm_append, vm_stats, vm_status, vm_describe, vm_configure]
6
+ write_collections: []
7
+ can_publish: false
8
+ llm_provider: ollama
9
+ ---
10
+
11
+ # Main Agent
12
+
13
+ Primary interactive agent for pi-vault-mind. The default role used by the
14
+ Obsidian plugin's chat interface and the CLI `/vm chat` command.
15
+
16
+ ## Capability Boundary
17
+
18
+ - **MAY**: all 7 built-in tools (read, write, edit, bash, grep, find, ls)
19
+ - **MAY**: vm_search, vm_query (read paths)
20
+ - **MAY**: vm_append, vm_stats, vm_status, vm_describe, vm_configure (write/config paths)
21
+ - **MAY**: read from the "main" collection
22
+ - **MUST NOT**: write to collections, publish, or access arbitrary vault paths
23
+
24
+ ## Tool Calling Convention
25
+
26
+ When calling pi-vault-mind tools, always include your role:
27
+ ```
28
+ vm_search({ query: "...", role: "main" })
29
+ ```
@@ -2,7 +2,7 @@
2
2
  type: ManagerAgent
3
3
  role: manager
4
4
  capabilities: [read, write, edit, grep, find, ls]
5
- allowed_tools: [read, write, edit, grep, find, ls, vm_search, vm_fts_search, vm_append, vm_sync, vm_promote, vm_query, vm_configure, vm_describe, vm_stats, vm_export, vm_ingest, vm_status, vm_graph_query]
5
+ allowed_tools: [read, write, edit, grep, find, ls, vm_search, vm_append, vm_sync, vm_promote, vm_query, vm_configure, vm_describe, vm_stats, vm_export, vm_ingest, vm_status]
6
6
  write_collections: [main, research, presentations]
7
7
  can_publish: true
8
8
  llm_provider: ollama
@@ -2,7 +2,7 @@
2
2
  type: MinerAgent
3
3
  role: miner
4
4
  capabilities: [read, write, edit, grep, find, ls]
5
- allowed_tools: [read, write, edit, grep, find, ls, vm_search, vm_fts_search, vm_append]
5
+ allowed_tools: [read, write, edit, grep, find, ls, vm_search, vm_append]
6
6
  write_collections: [main, research]
7
7
  can_publish: false
8
8
  llm_provider: ollama
@@ -0,0 +1,331 @@
1
+ /**
2
+ * HTTP + WebSocket client for the pi-vault-mind extension.
3
+ *
4
+ * REST calls use `fetch`. WebSocket events use the native `WebSocket` global
5
+ * with exponential backoff on disconnect (1, 2, 4, 8, 16, 30 s).
6
+ */
7
+ export class VaultMindClient {
8
+ config;
9
+ ws = null;
10
+ reconnectTimer = null;
11
+ reconnectDelay = 1000;
12
+ intentionalClose = false;
13
+ eventHandlers = new Set();
14
+ stateHandlers = new Set();
15
+ _state = { connected: false };
16
+ constructor(config) {
17
+ this.config = config;
18
+ }
19
+ get baseUrl() {
20
+ return `http://${this.config.host}:${this.config.port}`;
21
+ }
22
+ get wsUrl() {
23
+ return `ws://${this.config.host}:${this.config.port}/agent/stream`;
24
+ }
25
+ get authHeaders() {
26
+ return {
27
+ "Content-Type": "application/json",
28
+ Authorization: `Bearer ${this.config.token}`,
29
+ };
30
+ }
31
+ setState(state) {
32
+ this._state = state;
33
+ for (const h of this.stateHandlers)
34
+ h(state);
35
+ }
36
+ get state() {
37
+ return this._state;
38
+ }
39
+ subscribeEvents(handler) {
40
+ this.eventHandlers.add(handler);
41
+ return () => this.eventHandlers.delete(handler);
42
+ }
43
+ subscribeState(handler) {
44
+ this.stateHandlers.add(handler);
45
+ return () => this.stateHandlers.delete(handler);
46
+ }
47
+ emit(event) {
48
+ for (const h of this.eventHandlers) {
49
+ try {
50
+ h(event);
51
+ }
52
+ catch {
53
+ // never let a bad listener break others
54
+ }
55
+ }
56
+ }
57
+ connect() {
58
+ if (this.ws)
59
+ return;
60
+ try {
61
+ this.ws = new WebSocket(this.wsUrl, [`Authorization: Bearer ${this.config.token}`]);
62
+ }
63
+ catch (err) {
64
+ this.setState({ connected: false, error: String(err), reconnecting: true });
65
+ this.scheduleReconnect();
66
+ return;
67
+ }
68
+ this.ws.onopen = () => {
69
+ this.reconnectDelay = 1000;
70
+ this.setState({ connected: true, reconnecting: false });
71
+ };
72
+ this.ws.onmessage = (event) => {
73
+ try {
74
+ const parsed = JSON.parse(event.data);
75
+ this.emit(parsed);
76
+ }
77
+ catch {
78
+ // ignore malformed events
79
+ }
80
+ };
81
+ this.ws.onclose = (event) => {
82
+ this.ws = null;
83
+ if (this.intentionalClose) {
84
+ this.setState({ connected: false, error: undefined, reconnecting: false });
85
+ return;
86
+ }
87
+ const wasConnected = this._state.connected;
88
+ this.setState({ connected: false, error: `closed ${event.code}`, reconnecting: true });
89
+ if (wasConnected || event.code !== 4401) {
90
+ this.scheduleReconnect();
91
+ }
92
+ };
93
+ this.ws.onerror = () => {
94
+ this.setState({ connected: this.ws?.readyState === WebSocket.OPEN, error: "socket error" });
95
+ };
96
+ }
97
+ disconnect() {
98
+ if (this.reconnectTimer) {
99
+ clearTimeout(this.reconnectTimer);
100
+ this.reconnectTimer = null;
101
+ }
102
+ this.intentionalClose = true;
103
+ if (this.ws) {
104
+ this.ws.close();
105
+ this.ws = null;
106
+ }
107
+ this.setState({ connected: false, error: undefined, reconnecting: false });
108
+ this.intentionalClose = false;
109
+ }
110
+ scheduleReconnect() {
111
+ if (this.reconnectTimer)
112
+ return;
113
+ this.reconnectTimer = setTimeout(() => {
114
+ this.reconnectTimer = null;
115
+ this.connect();
116
+ }, this.reconnectDelay);
117
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, 30000);
118
+ }
119
+ async httpJson(method, path, body) {
120
+ const res = await fetch(`${this.baseUrl}${path}`, {
121
+ method,
122
+ headers: this.authHeaders,
123
+ body: body ? JSON.stringify(body) : undefined,
124
+ });
125
+ const text = await res.text();
126
+ if (!res.ok) {
127
+ let serverMessage = text;
128
+ try {
129
+ const parsed = JSON.parse(text);
130
+ if (typeof parsed.message === "string")
131
+ serverMessage = parsed.message;
132
+ }
133
+ catch {
134
+ // keep raw text as the server message
135
+ }
136
+ // Never echo the request body or secret material in the thrown error.
137
+ throw new Error(`${res.status} ${serverMessage}`);
138
+ }
139
+ return text ? JSON.parse(text) : undefined;
140
+ }
141
+ async status() {
142
+ return (await this.httpJson("GET", "/vm/status"));
143
+ }
144
+ async setup(body) {
145
+ return (await this.httpJson("POST", "/vm/setup", body));
146
+ }
147
+ async probeEmbedding(body) {
148
+ return (await this.httpJson("POST", "/vm/embedding/probe", body));
149
+ }
150
+ async putEmbeddingSecrets(body) {
151
+ return (await this.httpJson("PUT", "/vm/embedding/secrets", body));
152
+ }
153
+ async startServer() {
154
+ return (await this.httpJson("POST", "/server/start"));
155
+ }
156
+ async toggleWatcher() {
157
+ return (await this.httpJson("POST", "/vm/watcher/toggle"));
158
+ }
159
+ async listQueue(status) {
160
+ const query = status ? `?status=${status}` : "";
161
+ return (await this.httpJson("GET", `/agent/queue${query}`));
162
+ }
163
+ async retryJob(id) {
164
+ return (await this.httpJson("POST", `/agent/jobs/${encodeURIComponent(id)}/retry`));
165
+ }
166
+ async cancelJob(id) {
167
+ return (await this.httpJson("POST", `/agent/jobs/${encodeURIComponent(id)}/cancel`));
168
+ }
169
+ /**
170
+ * Unified search with mode dispatch. Calls POST /vm/search with mode param.
171
+ * mode='hybrid' (default) = vector+FTS rank-merge, 'semantic' = vector-only,
172
+ * 'fts' = keyword, 'graph' = entity traversal, 'full' = all merged.
173
+ */
174
+ async searchWithMode(params) {
175
+ return (await this.httpJson("POST", "/vm/search", {
176
+ collection: params.collection ?? "main",
177
+ query: params.query,
178
+ mode: params.mode ?? "hybrid",
179
+ limit: params.limit ?? 5,
180
+ ...(params.entity ? { entity: params.entity } : {}),
181
+ ...(params.depth ? { depth: params.depth } : {}),
182
+ ...(params.sources ? { sources: params.sources } : {}),
183
+ }));
184
+ }
185
+ async gitStatus() {
186
+ return (await this.httpJson("GET", "/git/status"));
187
+ }
188
+ async gitBranches() {
189
+ return (await this.httpJson("GET", "/git/branches"));
190
+ }
191
+ async gitCheckout(branch) {
192
+ return (await this.httpJson("POST", "/git/checkout", { branch }));
193
+ }
194
+ async listSessions() {
195
+ return (await this.httpJson("GET", "/sessions"));
196
+ }
197
+ async getSession(id) {
198
+ return (await this.httpJson("GET", `/sessions/${encodeURIComponent(id)}`));
199
+ }
200
+ async renameSession(id, name) {
201
+ return (await this.httpJson("PATCH", `/sessions/${encodeURIComponent(id)}`, { name }));
202
+ }
203
+ async deleteSession(id) {
204
+ return (await this.httpJson("DELETE", `/sessions/${encodeURIComponent(id)}`));
205
+ }
206
+ async exportSession(id) {
207
+ return (await this.httpJson("POST", `/sessions/${encodeURIComponent(id)}/export`));
208
+ }
209
+ async archiveSession(id) {
210
+ return (await this.httpJson("POST", `/sessions/${encodeURIComponent(id)}/archive`));
211
+ }
212
+ /**
213
+ * List pending edits, optionally filtered by vault-relative file path.
214
+ */
215
+ async listEdits(filePath) {
216
+ const query = filePath ? `?path=${encodeURIComponent(filePath)}` : "";
217
+ const res = await this.httpJson("GET", `/vm/edits${query}`);
218
+ return res.edits;
219
+ }
220
+ /**
221
+ * Get a specific pending edit by ID.
222
+ */
223
+ async getEdit(id) {
224
+ const res = await this.httpJson("GET", `/vm/edits/${encodeURIComponent(id)}`);
225
+ return res.edit;
226
+ }
227
+ /**
228
+ * Apply a pending edit to the target file.
229
+ */
230
+ async applyEdit(id) {
231
+ return (await this.httpJson("POST", `/vm/edits/${encodeURIComponent(id)}/apply`));
232
+ }
233
+ /**
234
+ * Reject a pending edit.
235
+ */
236
+ async rejectEdit(id) {
237
+ return (await this.httpJson("POST", `/vm/edits/${encodeURIComponent(id)}/reject`));
238
+ }
239
+ async listPending() {
240
+ const res = await this.httpJson("GET", "/vm/pending");
241
+ return res.pending;
242
+ }
243
+ async approveEntry(id, collection, action) {
244
+ await this.httpJson("POST", "/vm/approve", { id, collection, action });
245
+ }
246
+ async pushContext(filePath, selection, cursor) {
247
+ return (await this.httpJson("POST", "/vault-mind/context", {
248
+ filePath,
249
+ selection,
250
+ cursor,
251
+ }));
252
+ }
253
+ async scan(file, vault) {
254
+ try {
255
+ return (await this.httpJson("POST", "/vault-mind/scan", { file, vault }));
256
+ }
257
+ catch (err) {
258
+ return { error: String(err) };
259
+ }
260
+ }
261
+ async dispatch(role, instruction, file, vault) {
262
+ try {
263
+ return (await this.httpJson("POST", "/vault-mind/dispatch", {
264
+ role,
265
+ instruction,
266
+ file,
267
+ vault,
268
+ }));
269
+ }
270
+ catch (err) {
271
+ return { error: String(err) };
272
+ }
273
+ }
274
+ async vmStats() {
275
+ return (await this.httpJson("GET", "/vm/stats"));
276
+ }
277
+ /** GET /vault-mind/config — full config + hasToken + remote block */
278
+ async getConfig() {
279
+ return (await this.httpJson("GET", "/vault-mind/config"));
280
+ }
281
+ /** GET /vault-mind/paths — vault/agent/env paths */
282
+ async paths() {
283
+ return (await this.httpJson("GET", "/vault-mind/paths"));
284
+ }
285
+ /** GET /vault-mind/models — provider/model catalog parsed from pi's models.json */
286
+ async getModels() {
287
+ return (await this.httpJson("GET", "/vault-mind/models"));
288
+ }
289
+ /** POST /vm/token — write PVM_API_TOKEN to vault-mind.env */
290
+ async writeToken(token) {
291
+ return (await this.httpJson("POST", "/vm/token", { token }));
292
+ }
293
+ /** PATCH /vm/config — partial config update (deep-merged on vaultMind) */
294
+ async updateConfig(partial) {
295
+ return (await this.httpJson("PATCH", "/vm/config", partial));
296
+ }
297
+ /** GET /vault-mind/tools — full tool catalog + per-role allowlists */
298
+ async listTools() {
299
+ return (await this.httpJson("GET", "/vault-mind/tools"));
300
+ }
301
+ /** GET /vault-mind/identities — identity registry */
302
+ async listIdentities() {
303
+ return (await this.httpJson("GET", "/vault-mind/identities"));
304
+ }
305
+ /** PUT /vault-mind/identities/:role/allowedTools — update per-role allowlist */
306
+ async setAllowedTools(role, allowedTools) {
307
+ return (await this.httpJson("PUT", `/vault-mind/identities/${encodeURIComponent(role)}/allowedTools`, { allowedTools }));
308
+ }
309
+ /** POST /vault-mind/reload-identities — reload bridge registry */
310
+ async reloadIdentities() {
311
+ return (await this.httpJson("POST", "/vault-mind/reload-identities"));
312
+ }
313
+ /** POST /vm/reindex — trigger a remote reindex of all collections */
314
+ async reindex() {
315
+ return (await this.httpJson("POST", "/vm/reindex"));
316
+ }
317
+ /** GET /agent/activity — list activity events, optionally filtered by jobId and limited. */
318
+ async listActivity(opts = {}) {
319
+ const params = new URLSearchParams();
320
+ if (opts.jobId !== undefined)
321
+ params.set("jobId", opts.jobId);
322
+ if (opts.limit !== undefined)
323
+ params.set("limit", String(opts.limit));
324
+ const qs = params.toString();
325
+ return (await this.httpJson("GET", `/agent/activity${qs ? `?${qs}` : ""}`));
326
+ }
327
+ /** GET /vault-mind/model-readiness — returns model registry status and current selected model */
328
+ async getModelReadiness() {
329
+ return (await this.httpJson("GET", "/vault-mind/model-readiness"));
330
+ }
331
+ }