pi-vault-mind 0.16.36 → 0.16.37

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 (87) hide show
  1. package/README.md +3 -9
  2. package/dist/runtime/headless-sync-worker.d.ts +84 -0
  3. package/dist/runtime/headless-sync-worker.js +319 -0
  4. package/dist/src/agent-docs.d.ts +6 -0
  5. package/dist/src/agent-docs.js +7 -2
  6. package/dist/src/agent-queue.d.ts +1 -1
  7. package/dist/src/capabilities.d.ts +31 -0
  8. package/dist/src/capabilities.js +264 -0
  9. package/dist/src/collection-index.d.ts +10 -0
  10. package/dist/src/collection-index.js +15 -0
  11. package/dist/src/commands.js +561 -78
  12. package/dist/src/config-keys.d.ts +1 -1
  13. package/dist/src/config-keys.js +5 -0
  14. package/dist/src/dispatch-writeback.d.ts +5 -0
  15. package/dist/src/docling.d.ts +3 -0
  16. package/dist/src/embedding-providers.d.ts +9 -8
  17. package/dist/src/embedding-providers.js +22 -10
  18. package/dist/src/engine.js +2 -0
  19. package/dist/src/graph.d.ts +20 -0
  20. package/dist/src/graph.js +199 -21
  21. package/dist/src/headless-rebuild.d.ts +83 -0
  22. package/dist/src/headless-rebuild.js +654 -0
  23. package/dist/src/lance.js +16 -4
  24. package/dist/src/markdown-import.d.ts +18 -0
  25. package/dist/src/markdown-import.js +193 -0
  26. package/dist/src/modal-client.d.ts +104 -0
  27. package/dist/src/modal-client.js +73 -1
  28. package/dist/src/modal-config.d.ts +6 -4
  29. package/dist/src/modal-config.js +15 -6
  30. package/dist/src/personalize.js +1 -1
  31. package/dist/src/server.js +3 -2
  32. package/dist/src/status.d.ts +2 -0
  33. package/dist/src/status.js +6 -0
  34. package/dist/src/sync.d.ts +25 -1
  35. package/dist/src/sync.js +320 -0
  36. package/dist/src/tool-catalog.d.ts +2 -2
  37. package/dist/src/tool-catalog.js +42 -3
  38. package/dist/src/tools.js +58 -7
  39. package/dist/src/transcription/engine.d.ts +144 -0
  40. package/dist/src/transcription/engine.js +548 -0
  41. package/dist/src/transcription/local-server.d.ts +30 -0
  42. package/dist/src/transcription/local-server.js +92 -0
  43. package/dist/src/transcription/note-template.d.ts +32 -0
  44. package/dist/src/transcription/note-template.js +231 -0
  45. package/dist/src/transcription/notebooklm.d.ts +33 -0
  46. package/dist/src/transcription/notebooklm.js +145 -0
  47. package/dist/src/transcription/source-resolution.d.ts +51 -0
  48. package/dist/src/transcription/source-resolution.js +84 -0
  49. package/dist/src/transcription/types.d.ts +133 -0
  50. package/dist/src/transcription/types.js +11 -0
  51. package/dist/src/types.d.ts +69 -1
  52. package/dist/src/types.js +25 -0
  53. package/dist/src/utils.js +4 -0
  54. package/dist/src/vault-link-graph.d.ts +29 -0
  55. package/dist/src/vault-link-graph.js +119 -0
  56. package/dist/src/vault-tools.js +23 -2
  57. package/dist/src/vm-handlers.js +6 -3
  58. package/dist/src/vm-script.d.ts +39 -0
  59. package/dist/src/vm-script.js +3 -7
  60. package/dist/src/watcher.d.ts +5 -1
  61. package/dist/src/watcher.js +6 -8
  62. package/dist/test/agent-docs.test.js +1 -1
  63. package/dist/test/capabilities.test.js +87 -0
  64. package/dist/test/commands.test.js +210 -5
  65. package/dist/test/config-merge.test.js +14 -0
  66. package/dist/test/embedding-providers.test.js +27 -0
  67. package/dist/test/headless-rebuild.test.js +394 -0
  68. package/dist/test/headless-sync-worker.test.js +152 -0
  69. package/dist/test/markdown-import.test.js +66 -0
  70. package/dist/test/modal-client.test.js +51 -0
  71. package/dist/test/modal-config.test.js +22 -1
  72. package/dist/test/personalize.test.js +1 -1
  73. package/dist/test/pvm-setup.test.js +42 -1
  74. package/dist/test/tool-catalog.test.js +16 -4
  75. package/dist/test/transcription-engine.test.js +382 -0
  76. package/dist/test/transcription-local-server.test.js +65 -0
  77. package/dist/test/transcription-note-template.test.js +256 -0
  78. package/dist/test/transcription-source-resolution.test.js +194 -0
  79. package/dist/test/utils.test.js +7 -0
  80. package/dist/test/vault-pi-script.test.js +49 -0
  81. package/dist/test/vm-transcribe-tool.test.js +134 -0
  82. package/package.json +3 -1
  83. package/pi-vault-mind.config.example.json +8 -0
  84. package/scripts/pvm-setup.mjs +14 -2
  85. package/scripts/vault-pi.sh +36 -11
  86. package/skills/vault-mind/SKILL.md +39 -7
  87. package/skills/vault-mind-miner/SKILL.md +16 -2
package/README.md CHANGED
@@ -18,7 +18,7 @@ Passive Obsidian vault extension for the [pi](https://github.com/mariozechner/pi
18
18
  - **JSONL Source-of-Truth + LanceDB Index** — every fact lives in a durable, human-readable, version-control-friendly `collections/*.jsonl` file. The LanceDB index is derived and rebuildable via `/vm reindex --all --reembed`.
19
19
  - **Bidirectional Obsidian Sync** — substantial entries (`>200 chars` or tagged `decision`/`insight`/`requirement`) auto-write to `Vault/Agent/Inbox/`. Graph entities render as Obsidian Canvas files.
20
20
  - **Vault-Scoped Configuration Surface** — Vault Mind setup/config now lives under `<vault>/.vault-mind/`, with the Obsidian setup wizard and `/vm setup` both writing the vault-local surface instead of a shared global config.
21
- - **Interactive Setup Wizard** — the Obsidian setup wizard handles first-run runtime/install/provider/folder/preferences/review flow, and `/vm setup` remains available for CLI/repair use.
21
+ - **Audio Transcription Lane** — `vm_transcribe` handles audio/video separately from `vm_ingest`, preferring a vault-configured Modal HTTP endpoint for high-quality jobs when `vaultMind.transcription.remoteUrl` is set, while keeping a safe local `faster-whisper` fallback and `/vm transcribe status|config|use|local` ops surface for switching lanes and managing the local server.
22
22
 
23
23
  ## Architecture
24
24
 
@@ -177,13 +177,7 @@ The Broadcaster agent can generate podcasts, study guides, and slide decks from
177
177
  - `ollama` — requires Ollama running locally with `embeddinggemma` (higher quality)
178
178
  - `modal` — optional, **bring-your-own deploy**: offload embedding + bulk re-index to a cloud GPU service and sync vectors down for offline search (see [docs/integrations/MODAL_EMBEDDING.md](docs/integrations/MODAL_EMBEDDING.md)). Default behavior is unchanged until you opt in.
179
179
 
180
- > **On the Modal provider:** the local providers above work for everyone with
181
- > zero infrastructure — that's the default. `modal` is an **optional, self-hosted
182
- > tier**: you deploy your own copy of the service (`uvx modal deploy modal/app.py`
183
- > from a clone of this repo) to your own Modal account and point the extension at
184
- > *your* URL + token. There is no shared/hosted endpoint — a deployment's URL and
185
- > bearer token are private to whoever owns it. (A standalone PyPI package for the
186
- > server may come later.)
180
+ > **On the Modal transcription path:** this is **bring-your-own deploy**. You deploy your own Modal HTTP endpoint from the `audio-transcription` repo, store its URL under `vaultMind.transcription.remoteUrl`, and Vault Mind can prefer that remote high-quality path by default. If no remote transcription URL is configured, `vm_transcribe` falls back to the local `faster-whisper` lane.
187
181
 
188
182
  ### 1. Install and set up the vault-local runtime
189
183
 
@@ -555,8 +549,8 @@ belongs in `vaultMind.identities` or an explicit environment override.
555
549
  | `vm_stats` | Dashboard: counts, sizes, LanceDB status |
556
550
  | `vm_export` | Export to JSON, CSV, or Markdown |
557
551
  | `vm_ingest` | Convert URLs and documents to markdown via docling |
552
+ | `vm_transcribe` | Transcribe audio/video, generate transcript notes + raw artifacts |
558
553
  | `vm_script` | Execute sandboxed JS/TS with vm.*, vault.read, log.* |
559
-
560
554
  ## Commands
561
555
 
562
556
  | Command | Purpose |
@@ -0,0 +1,84 @@
1
+ import { type HeadlessRebuildAfterReindexDependencies, type HeadlessRebuildDependencies } from "../src/headless-rebuild.js";
2
+ import type { UniversalConfig } from "../src/types.js";
3
+ type WorkerPhase = "credentials" | "auth" | "sync" | "embed" | "graph" | "config";
4
+ export declare class HeadlessSyncWorkerError extends Error {
5
+ readonly phase: WorkerPhase;
6
+ constructor(phase: WorkerPhase);
7
+ }
8
+ export interface HeadlessSyncWorkerRequest {
9
+ operationId: string;
10
+ graphId: string;
11
+ collections: string[];
12
+ }
13
+ interface SecretClient {
14
+ secrets: {
15
+ resolve(reference: string): Promise<string>;
16
+ };
17
+ }
18
+ interface VectorRevision {
19
+ collection: string;
20
+ model: string;
21
+ dim: number;
22
+ watermark: number;
23
+ revision: string;
24
+ }
25
+ interface GraphRevisionRow {
26
+ graph_id: string;
27
+ revision: string;
28
+ source_fingerprint: string;
29
+ updated_at: string;
30
+ }
31
+ interface RevisionManifest {
32
+ vectors: Array<VectorRevision & {
33
+ updated_at: string;
34
+ }>;
35
+ graphs: GraphRevisionRow[];
36
+ }
37
+ export interface HeadlessSyncWorkerDependencies {
38
+ env: Record<string, string | undefined>;
39
+ workspaceRoot: string;
40
+ createClient(options: {
41
+ auth: string;
42
+ integrationName: string;
43
+ integrationVersion: string;
44
+ }): Promise<SecretClient>;
45
+ execFile(command: string, args: string[], options: {
46
+ cwd: string;
47
+ env: Record<string, string | undefined>;
48
+ }): Promise<unknown>;
49
+ runRebuild(request: {
50
+ graphId: string;
51
+ clonePath: string;
52
+ wait: true;
53
+ }, deps: Partial<HeadlessRebuildDependencies> & Pick<HeadlessRebuildAfterReindexDependencies, "reindex">): Promise<{
54
+ graphId: string;
55
+ sourceFingerprint: string;
56
+ jobId?: string;
57
+ jobStatus?: string;
58
+ }>;
59
+ reindexRemote(cfg: UniversalConfig, collections: string[], vaultPath: string, options: {
60
+ syncDown: false;
61
+ }): Promise<Array<{
62
+ collection: string;
63
+ job?: {
64
+ status: string;
65
+ };
66
+ error?: string;
67
+ model?: string;
68
+ dim?: number | null;
69
+ remote_collection?: string;
70
+ }>>;
71
+ readRevisionsManifest(baseUrl: string, token: string): Promise<{
72
+ vectors: VectorRevision[];
73
+ graphs: GraphRevisionRow[];
74
+ }>;
75
+ fetchNamespaceWatermark(baseUrl: string, token: string, collection: string, model: string, dim: number): Promise<number>;
76
+ publishRevisions(baseUrl: string, token: string, manifest: RevisionManifest): Promise<void>;
77
+ now(): string;
78
+ }
79
+ export declare function runHeadlessSyncWorker(request: HeadlessSyncWorkerRequest, deps: HeadlessSyncWorkerDependencies): Promise<{
80
+ graphId: string;
81
+ graphJobId?: string;
82
+ sourceFingerprint: string;
83
+ }>;
84
+ export {};
@@ -0,0 +1,319 @@
1
+ import { execFile as nodeExecFile } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { runHeadlessRebuildAfterReindex, } from "../src/headless-rebuild.js";
6
+ import { reindexRemote } from "../src/sync.js";
7
+ const execFileAsync = promisify(nodeExecFile);
8
+ const IDENTIFIER = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
9
+ export class HeadlessSyncWorkerError extends Error {
10
+ phase;
11
+ constructor(phase) {
12
+ super(`${phase} failed`);
13
+ this.name = "HeadlessSyncWorkerError";
14
+ this.phase = phase;
15
+ }
16
+ }
17
+ function required(env, key, phase) {
18
+ const value = env[key];
19
+ if (!value)
20
+ throw new HeadlessSyncWorkerError(phase);
21
+ return value;
22
+ }
23
+ function assertSafe(value, phase) {
24
+ if (!IDENTIFIER.test(value))
25
+ throw new HeadlessSyncWorkerError(phase);
26
+ return value;
27
+ }
28
+ function reference(env, key) {
29
+ const value = required(env, key, "config");
30
+ if (!value.startsWith("op://") || value.length > 512)
31
+ throw new HeadlessSyncWorkerError("config");
32
+ return value;
33
+ }
34
+ function optionalReference(env, key) {
35
+ const value = env[key];
36
+ if (!value)
37
+ return undefined;
38
+ return reference(env, key);
39
+ }
40
+ function selectConfig(raw, collections, token) {
41
+ let parsed;
42
+ try {
43
+ parsed = JSON.parse(raw);
44
+ }
45
+ catch {
46
+ throw new HeadlessSyncWorkerError("config");
47
+ }
48
+ if (!parsed || typeof parsed !== "object")
49
+ throw new HeadlessSyncWorkerError("config");
50
+ const cfg = parsed;
51
+ const embeddingUrl = cfg.vaultMind?.embedding?.remoteUrl;
52
+ if (!cfg.collections ||
53
+ typeof cfg.collections !== "object" ||
54
+ !cfg.vaultMind ||
55
+ typeof embeddingUrl !== "string" ||
56
+ embeddingUrl.length === 0) {
57
+ throw new HeadlessSyncWorkerError("config");
58
+ }
59
+ const selected = {};
60
+ for (const collection of collections) {
61
+ const definition = cfg.collections[collection];
62
+ if (!definition)
63
+ throw new HeadlessSyncWorkerError("config");
64
+ selected[collection] = definition;
65
+ }
66
+ cfg.collections = selected;
67
+ cfg.vaultMind.embedding = {
68
+ ...cfg.vaultMind.embedding,
69
+ remoteApiKey: token,
70
+ };
71
+ return { config: cfg, embeddingUrl };
72
+ }
73
+ function sanitizeError(error, phase) {
74
+ if (error instanceof HeadlessSyncWorkerError)
75
+ throw error;
76
+ throw new HeadlessSyncWorkerError(phase);
77
+ }
78
+ export async function runHeadlessSyncWorker(request, deps) {
79
+ assertSafe(request.operationId, "config");
80
+ assertSafe(request.graphId, "config");
81
+ if (request.collections.length === 0 ||
82
+ new Set(request.collections).size !== request.collections.length) {
83
+ throw new HeadlessSyncWorkerError("config");
84
+ }
85
+ for (const collection of request.collections)
86
+ assertSafe(collection, "config");
87
+ const workspace = path.resolve(deps.workspaceRoot, request.operationId);
88
+ const home = path.join(workspace, ".home");
89
+ const env = { ...deps.env, HOME: home };
90
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
91
+ required(deps.env, "OP_SERVICE_ACCOUNT_TOKEN", "credentials");
92
+ const embeddingToken = required(deps.env, "PVM_REMOTE_EMBEDDING_API_KEY", "credentials");
93
+ let email;
94
+ let password;
95
+ let mfa;
96
+ let e2ePassword;
97
+ let vault;
98
+ try {
99
+ const resolve = async (ref) => {
100
+ const result = (await deps.execFile("op", ["read", "--no-newline", ref], {
101
+ cwd: workspace,
102
+ env,
103
+ }));
104
+ if (typeof result.stdout !== "string" || result.stdout.length === 0) {
105
+ throw new HeadlessSyncWorkerError("credentials");
106
+ }
107
+ return result.stdout;
108
+ };
109
+ const mfaRef = optionalReference(deps.env, "PVM_HEADLESS_OP_MFA_REF");
110
+ const e2eRef = optionalReference(deps.env, "PVM_HEADLESS_OP_E2E_PASSWORD_REF");
111
+ [email, password, mfa, e2ePassword, vault] = await Promise.all([
112
+ resolve(reference(deps.env, "PVM_HEADLESS_OP_EMAIL_REF")),
113
+ resolve(reference(deps.env, "PVM_HEADLESS_OP_PASSWORD_REF")),
114
+ mfaRef ? resolve(mfaRef) : Promise.resolve(undefined),
115
+ e2eRef ? resolve(e2eRef) : Promise.resolve(undefined),
116
+ resolve(reference(deps.env, "PVM_HEADLESS_OP_VAULT_REF")),
117
+ ]);
118
+ let parsedVault;
119
+ try {
120
+ parsedVault = JSON.parse(vault);
121
+ }
122
+ catch {
123
+ throw new HeadlessSyncWorkerError("credentials");
124
+ }
125
+ if (!parsedVault ||
126
+ typeof parsedVault !== "object" ||
127
+ !("vault_id" in parsedVault) ||
128
+ typeof parsedVault.vault_id !== "string" ||
129
+ !IDENTIFIER.test(parsedVault.vault_id)) {
130
+ throw new HeadlessSyncWorkerError("credentials");
131
+ }
132
+ vault = parsedVault.vault_id;
133
+ }
134
+ catch (error) {
135
+ return sanitizeError(error, "credentials");
136
+ }
137
+ try {
138
+ const loginArgs = ["login", "--email", email, "--password", password];
139
+ if (mfa)
140
+ loginArgs.push("--mfa", mfa);
141
+ await deps.execFile("ob", loginArgs, { cwd: workspace, env });
142
+ const setupArgs = [
143
+ "sync-setup",
144
+ "--vault",
145
+ vault,
146
+ "--path",
147
+ workspace,
148
+ "--password",
149
+ e2ePassword,
150
+ "--device-name",
151
+ `pvm-headless-${request.operationId}`,
152
+ ];
153
+ if (!e2ePassword)
154
+ setupArgs.splice(setupArgs.indexOf("--password"), 2);
155
+ await deps.execFile("ob", setupArgs, { cwd: workspace, env });
156
+ await deps.execFile("ob", ["sync", "--path", workspace], { cwd: workspace, env });
157
+ }
158
+ catch (error) {
159
+ return sanitizeError(error, "auth");
160
+ }
161
+ let cfg;
162
+ let embeddingUrl;
163
+ try {
164
+ const configPath = path.join(workspace, ".vault-mind", "vault-mind.config.json");
165
+ const rawConfig = fs.readFileSync(configPath, "utf8");
166
+ ({ config: cfg, embeddingUrl } = selectConfig(rawConfig, request.collections, embeddingToken));
167
+ }
168
+ catch (error) {
169
+ return sanitizeError(error, "config");
170
+ }
171
+ let checkpoint;
172
+ let reindexRevisions;
173
+ try {
174
+ checkpoint = await deps.runRebuild({ graphId: request.graphId, clonePath: workspace, wait: true }, {
175
+ runObSync: async (clonePath) => {
176
+ await deps.execFile("ob", ["sync", "--path", clonePath], { cwd: clonePath, env });
177
+ },
178
+ reindex: async (resolvedConfig, clonePath) => {
179
+ const selectedConfig = {
180
+ ...cfg,
181
+ ...resolvedConfig,
182
+ collections: cfg.collections,
183
+ };
184
+ const results = await deps.reindexRemote(selectedConfig, request.collections, clonePath, {
185
+ syncDown: false,
186
+ });
187
+ if (results.some((result) => result.error || result.job?.status !== "done")) {
188
+ throw new HeadlessSyncWorkerError("embed");
189
+ }
190
+ reindexRevisions = results
191
+ .filter((result) => result.job?.status === "done" &&
192
+ !result.error &&
193
+ !!result.model &&
194
+ !!result.remote_collection)
195
+ .map((result) => ({
196
+ collection: result.remote_collection,
197
+ model: result.model,
198
+ dim: (result.dim ?? 0),
199
+ revision: result.job.status,
200
+ watermark: 0,
201
+ }));
202
+ },
203
+ });
204
+ }
205
+ catch (error) {
206
+ return sanitizeError(error, error instanceof HeadlessSyncWorkerError ? error.phase : "graph");
207
+ }
208
+ if (checkpoint.jobStatus !== "done" || !checkpoint.jobId)
209
+ throw new HeadlessSyncWorkerError("graph");
210
+ let vectors;
211
+ try {
212
+ vectors = reindexRevisions;
213
+ const remoteCollections = new Set(vectors.map((vector) => vector.collection));
214
+ if (vectors.length !== request.collections.length ||
215
+ vectors.some((vector) => !remoteCollections.has(vector.collection) ||
216
+ !(typeof vector.dim === "number" && vector.dim > 0) ||
217
+ !Number.isSafeInteger(vector.watermark) ||
218
+ vector.watermark < 0)) {
219
+ throw new HeadlessSyncWorkerError("embed");
220
+ }
221
+ const watermarks = await Promise.all(vectors.map(async (vector) => {
222
+ try {
223
+ return await deps.fetchNamespaceWatermark(embeddingUrl, embeddingToken, vector.collection, vector.model, vector.dim);
224
+ }
225
+ catch (error) {
226
+ throw new HeadlessSyncWorkerError("embed");
227
+ }
228
+ }));
229
+ vectors = vectors.map((vector, index) => ({
230
+ ...vector,
231
+ watermark: watermarks[index],
232
+ }));
233
+ }
234
+ catch (error) {
235
+ return sanitizeError(error, "embed");
236
+ }
237
+ try {
238
+ const existing = await deps.readRevisionsManifest(embeddingUrl, embeddingToken);
239
+ const selectedRemote = new Set(vectors.map((vector) => vector.collection));
240
+ const survivingVectors = existing.vectors
241
+ .filter((vector) => !selectedRemote.has(vector.collection))
242
+ .map((vector) => ({ ...vector, updated_at: deps.now() }));
243
+ await deps.publishRevisions(embeddingUrl, embeddingToken, {
244
+ vectors: [
245
+ ...survivingVectors,
246
+ ...vectors.map((vector) => ({
247
+ ...vector,
248
+ updated_at: deps.now(),
249
+ })),
250
+ ],
251
+ graphs: [
252
+ ...existing.graphs,
253
+ {
254
+ graph_id: checkpoint.graphId,
255
+ revision: checkpoint.jobId,
256
+ source_fingerprint: checkpoint.sourceFingerprint,
257
+ updated_at: deps.now(),
258
+ },
259
+ ],
260
+ });
261
+ }
262
+ catch (error) {
263
+ return sanitizeError(error, "graph");
264
+ }
265
+ return {
266
+ graphId: checkpoint.graphId,
267
+ graphJobId: checkpoint.jobId,
268
+ sourceFingerprint: checkpoint.sourceFingerprint,
269
+ };
270
+ }
271
+ const defaultDependencies = {
272
+ env: process.env,
273
+ workspaceRoot: process.env.PVM_HEADLESS_WORKSPACE_ROOT || "/pvm-vault/workspaces",
274
+ execFile: async (command, args, options) => execFileAsync(command, args, options),
275
+ runRebuild: runHeadlessRebuildAfterReindex,
276
+ reindexRemote: reindexRemote,
277
+ readRevisionsManifest: async (baseUrl, token) => {
278
+ const target = baseUrl.replace(/\/$/, "");
279
+ const response = await fetch(`${target}/sync/revisions`, {
280
+ method: "GET",
281
+ headers: { Authorization: `Bearer ${token}` },
282
+ });
283
+ if (!response.ok)
284
+ throw new Error("revision read failed");
285
+ return (await response.json());
286
+ },
287
+ fetchNamespaceWatermark: async (baseUrl, token, collection, model, dim) => {
288
+ const target = baseUrl.replace(/\/$/, "");
289
+ const params = new URLSearchParams({ collection, model, dim: String(dim) });
290
+ const response = await fetch(`${target}/sync/namespace-watermark?${params.toString()}`, {
291
+ method: "GET",
292
+ headers: { Authorization: `Bearer ${token}` },
293
+ });
294
+ if (!response.ok)
295
+ throw new Error("namespace watermark read failed");
296
+ const body = (await response.json());
297
+ return body.watermark;
298
+ },
299
+ publishRevisions: async (baseUrl, token, manifest) => {
300
+ const response = await fetch(`${baseUrl.replace(/\/$/, "")}/sync/revisions`, {
301
+ method: "POST",
302
+ headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
303
+ body: JSON.stringify(manifest),
304
+ });
305
+ if (!response.ok)
306
+ throw new Error("revision publish failed");
307
+ },
308
+ now: () => new Date().toISOString(),
309
+ };
310
+ if (import.meta.url === `file://${process.argv[1]}`) {
311
+ const [operationId, graphId, collectionsJson] = process.argv.slice(2);
312
+ void (async () => {
313
+ const collections = JSON.parse(collectionsJson || "[]");
314
+ const result = await runHeadlessSyncWorker({ operationId: operationId || "", graphId: graphId || "", collections }, defaultDependencies);
315
+ process.stdout.write(JSON.stringify(result));
316
+ })().catch(() => {
317
+ process.exitCode = 1;
318
+ });
319
+ }
@@ -0,0 +1,6 @@
1
+ export declare const BASELINE_START = "<!-- vm:baseline:start -->";
2
+ export declare const BASELINE_END = "<!-- vm:baseline:end -->";
3
+ export declare const PERSONALIZATION_START = "<!-- vm:personalization:start -->";
4
+ export declare const PERSONALIZATION_END = "<!-- vm:personalization:end -->";
5
+ export declare const buildDefaultSystemMd: (vaultPath: string) => string;
6
+ export declare const buildDefaultAgentsMd: () => string;
@@ -17,8 +17,10 @@ You are the unrestricted main interactive agent for this vault. Bundled speciali
17
17
  - Prefer \`uv\` for Python tools and CLIs. Use \`pnpm\`/\`npm\` for JavaScript and TypeScript packages such as \`run\`.
18
18
  - Use \`docling\` for document conversion in ingest flows.
19
19
  - Use \`vm_script\` for reusable sandboxed JS/TS workflows when the task fits the approved host-function surface.
20
+ - Use \`vm_transcribe\` for audio/video transcription (voice notes, podcasts, NotebookLM audio overviews). Defaults come from \`vaultMind.transcription\`: prefer the remote Modal HTTP endpoint when \`remoteUrl\` is configured, otherwise fall back to the local engine. Keep this lane separate from \`vm_ingest\`/docling.
21
+ - Use the \`nlm\` CLI for NotebookLM source inventory lookups. Do not use MCP as the primary path for NotebookLM notebook/source queries.
20
22
  - Never auto-publish, never fabricate facts, and keep human approval at external side effects.
21
- - The watcher recognizes \`@agent:<role>\` and \`@agent-<role>\` anywhere in watchable files. \`@agent:main\` is reserved for the main interactive flow and is not auto-dispatched.
23
+ - The watcher recognizes \`@agent:<role>\` and \`@agent-<role>\` anywhere in watchable files. \`@agent:main\` is reserved for human-authored review and follow-up notes; it is never auto-dispatched. Automated transcript preparation, source linking, and note population use \`@agent:audio-overviews\`.
22
24
  ${BASELINE_END}
23
25
 
24
26
  ${PERSONALIZATION_START}
@@ -36,16 +38,19 @@ ${BASELINE_START}
36
38
  - \`broadcaster\` — human-facing artifact generation.
37
39
  - \`heavy-lifter\` — large-context implementation and deeper delegated work.
38
40
  - \`personalization\` — resolves annotated feedback on generated system/agent docs.
41
+ - \`audio-overviews\` — automated worker for NotebookLM audio overview transcription, source resolution, and note population. Uses deterministic scripts/functions rather than freeform improvisation.
39
42
 
40
43
  ## Watcher + inline requests
41
44
  - Use \`@agent:<role>\` or \`@agent-<role>\` in watchable files to request agent help.
42
- - \`@agent:main\` is recognized but never auto-dispatched; it is for main-agent or human-led follow-up.
45
+ - \`@agent:main\` is recognized but never auto-dispatched; it is for human-authored review notes and follow-up only — not automated transcript generation.
43
46
  - Auto-dispatched watcher jobs preserve the request block in the source note and replace only the running-status line with the result summary.
44
47
  - Watcher dispatches should be logged to collections so they can be queried later.
45
48
 
46
49
  ## Tooling baseline
47
50
  - Prefer \`uv\` for Python tools.
48
51
  - Prefer reusable \`vm_script\` workflows over ad-hoc one-off scripts when the workflow should be repeatable.
52
+ - Use \`vm_transcribe\` for audio/video transcription; keep this lane separate from \`vm_ingest\`/docling.
53
+ - Use \`nlm\` for NotebookLM source inventory. Do not use MCP as the primary path for NotebookLM lookups.
49
54
  - Keep reusable content, test, and update workflows in skills/tools rather than scattered shell snippets.
50
55
  ${BASELINE_END}
51
56
 
@@ -75,7 +75,7 @@ export declare const markJobFailed: (vaultPath: string, id: string, error: strin
75
75
  export declare const markJobRunning: (vaultPath: string, id: string) => AgentJob | undefined;
76
76
  /** Mark a pending job as cancelled. */
77
77
  export declare const cancelJob: (vaultPath: string, id: string) => AgentJob | undefined;
78
- /** True if the file still contains the dispatch marker for this job. */
78
+ /** True if the file still contains the pending dispatch status placeholder. */
79
79
  export declare const markerStillPresent: (filePath: string, dispatchId: string) => boolean;
80
80
  /** Scan a vault's queue and mark any running job whose marker is gone as done. */
81
81
  export declare const sweepCompletedJobs: (vaultPath: string) => AgentJob[];
@@ -0,0 +1,31 @@
1
+ export type CapabilityClass = "extensions" | "skills" | "agents";
2
+ export interface DesiredCapability {
3
+ source: string;
4
+ provider?: string;
5
+ version: string;
6
+ enabled: boolean;
7
+ }
8
+ export interface DesiredState {
9
+ version: number;
10
+ extensions: Record<string, DesiredCapability>;
11
+ skills: Record<string, DesiredCapability>;
12
+ agents: Record<string, DesiredCapability>;
13
+ }
14
+ export interface ObservedCapability {
15
+ id: string;
16
+ class: CapabilityClass;
17
+ version: string;
18
+ enabled: boolean;
19
+ provider?: string;
20
+ }
21
+ export type DriftStatus = "Installed / Active" | "Disabled" | "Missing" | "Drifted" | "Unverifiable" | "Orphaned" | "Discoverable";
22
+ export interface DriftReportItem {
23
+ id: string;
24
+ class: CapabilityClass;
25
+ desired?: DesiredCapability;
26
+ observed?: ObservedCapability;
27
+ status: DriftStatus;
28
+ }
29
+ export declare const readCapabilities: (vaultPath: string) => DesiredState;
30
+ export declare const scanInventory: (vaultPath: string) => ObservedCapability[];
31
+ export declare const computeDrift: (desired: DesiredState, observed: ObservedCapability[]) => DriftReportItem[];