@owrede/vault-memory 2.0.0-rc.1 → 2.0.0-rc.5

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/dist/cli.js CHANGED
@@ -760,6 +760,52 @@ var init_suppress_contract_write = __esm({
760
760
  }
761
761
  });
762
762
 
763
+ // src/plugin-tools/source-tools.ts
764
+ import { z as z8 } from "zod";
765
+ async function refreshHandler(args2, deps) {
766
+ const info = await deps.refresh(args2.name);
767
+ if (info === void 0) {
768
+ return { ok: false, name: args2.name, error: `unknown source: ${args2.name}` };
769
+ }
770
+ const result = {
771
+ ok: true,
772
+ name: args2.name,
773
+ status: info.status,
774
+ tool_count: info.tools.length
775
+ };
776
+ if (info.error !== void 0) result.error = info.error;
777
+ return result;
778
+ }
779
+ async function unsetHandler(args2, deps) {
780
+ const removed = deps.remove(args2.name);
781
+ return { ok: true, name: args2.name, removed };
782
+ }
783
+ var RefreshSourceArgs, refreshSourceTool, UnsetMcpClientArgs, unsetMcpClientTool;
784
+ var init_source_tools = __esm({
785
+ "src/plugin-tools/source-tools.ts"() {
786
+ "use strict";
787
+ init_esm_shims();
788
+ RefreshSourceArgs = z8.object({
789
+ name: z8.string().min(1).describe("Peer-MCP source name to refresh (re-poll tools/list).")
790
+ });
791
+ refreshSourceTool = {
792
+ name: "refresh_source",
793
+ description: "Re-poll tools/list against a live peer-MCP source and refresh its cached tool list. Returns the updated status (connected/unavailable/unreachable) and tool_count. SOURCES-REGISTRY \xA76.3.",
794
+ inputSchema: RefreshSourceArgs,
795
+ handler: refreshHandler
796
+ };
797
+ UnsetMcpClientArgs = z8.object({
798
+ name: z8.string().min(1).describe("Peer-MCP source name to disconnect + drop from the registry.")
799
+ });
800
+ unsetMcpClientTool = {
801
+ name: "unset_mcp_client",
802
+ description: "Disconnect a live peer-MCP source and drop it from the running registry. Idempotent (removed:false when the name is unknown). Affects the running process only \u2014 pair with set_mcp_client({name, remove:true}) to also delete the persisted config entry. SOURCES-REGISTRY \xA76.2.",
803
+ inputSchema: UnsetMcpClientArgs,
804
+ handler: unsetHandler
805
+ };
806
+ }
807
+ });
808
+
763
809
  // src/plugin-tools/index.ts
764
810
  function ok(data) {
765
811
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
@@ -921,6 +967,54 @@ function syncPluginTools(server, registered, opts) {
921
967
  }
922
968
  }
923
969
  )
970
+ },
971
+ {
972
+ name: "refresh_source",
973
+ reg: () => server.registerTool(
974
+ refreshSourceTool.name,
975
+ {
976
+ description: refreshSourceTool.description,
977
+ inputSchema: refreshSourceTool.inputSchema.shape
978
+ },
979
+ async (args2) => {
980
+ try {
981
+ const validated = refreshSourceTool.inputSchema.parse(
982
+ args2
983
+ );
984
+ const result = await refreshSourceTool.handler(
985
+ validated,
986
+ opts.sourceRegistry
987
+ );
988
+ return ok(result);
989
+ } catch (err) {
990
+ return errorResponse(err instanceof Error ? err.message : String(err));
991
+ }
992
+ }
993
+ )
994
+ },
995
+ {
996
+ name: "unset_mcp_client",
997
+ reg: () => server.registerTool(
998
+ unsetMcpClientTool.name,
999
+ {
1000
+ description: unsetMcpClientTool.description,
1001
+ inputSchema: unsetMcpClientTool.inputSchema.shape
1002
+ },
1003
+ async (args2) => {
1004
+ try {
1005
+ const validated = unsetMcpClientTool.inputSchema.parse(
1006
+ args2
1007
+ );
1008
+ const result = await unsetMcpClientTool.handler(
1009
+ validated,
1010
+ opts.sourceRegistry
1011
+ );
1012
+ return ok(result);
1013
+ } catch (err) {
1014
+ return errorResponse(err instanceof Error ? err.message : String(err));
1015
+ }
1016
+ }
1017
+ )
924
1018
  }
925
1019
  ];
926
1020
  for (const { name, reg } of adds) {
@@ -941,12 +1035,14 @@ var init_plugin_tools = __esm({
941
1035
  init_get_runtime_stats();
942
1036
  init_trigger_reindex();
943
1037
  init_suppress_contract_write();
1038
+ init_source_tools();
944
1039
  init_set_runtime_config();
945
1040
  init_resolve_secret();
946
1041
  init_set_mcp_client();
947
1042
  init_get_runtime_stats();
948
1043
  init_trigger_reindex();
949
1044
  init_suppress_contract_write();
1045
+ init_source_tools();
950
1046
  init_runtime_config();
951
1047
  PLUGIN_TOOL_NAMES = [
952
1048
  "set_runtime_config",
@@ -954,7 +1050,10 @@ var init_plugin_tools = __esm({
954
1050
  "set_mcp_client",
955
1051
  "get_runtime_stats",
956
1052
  "trigger_reindex",
957
- "suppress_contract_write"
1053
+ "suppress_contract_write",
1054
+ // SOURCES-REGISTRY.md §6 (Stage 2) — live-registry source management.
1055
+ "refresh_source",
1056
+ "unset_mcp_client"
958
1057
  ];
959
1058
  }
960
1059
  });
@@ -1207,13 +1306,16 @@ function backfillSectionsFromChunks(db) {
1207
1306
  "SELECT * FROM chunks WHERE note_id = ? ORDER BY id ASC"
1208
1307
  );
1209
1308
  const insertSection = db.prepare(`
1210
- INSERT INTO sections
1309
+ INSERT OR IGNORE INTO sections
1211
1310
  (note_id, anchor, heading_path, heading_text, level,
1212
1311
  parent_id, ord, chunk_id_first, chunk_id_last, created_at)
1213
1312
  VALUES
1214
1313
  (@note_id, @anchor, @heading_path, @heading_text, @level,
1215
1314
  @parent_id, @ord, @chunk_id_first, @chunk_id_last, @created_at)
1216
1315
  `);
1316
+ const lookupExistingSection = db.prepare(
1317
+ "SELECT id FROM sections WHERE note_id = ? AND anchor = ?"
1318
+ );
1217
1319
  let backfilled = 0;
1218
1320
  const now = Date.now();
1219
1321
  for (const note of notesRows) {
@@ -1249,7 +1351,12 @@ function backfillSectionsFromChunks(db) {
1249
1351
  created_at: now
1250
1352
  };
1251
1353
  const info = insertSection.run(row);
1252
- insertedIds.push(Number(info.lastInsertRowid));
1354
+ if (info.changes > 0) {
1355
+ insertedIds.push(Number(info.lastInsertRowid));
1356
+ } else {
1357
+ const existing2 = lookupExistingSection.get(note.id, s.anchor);
1358
+ insertedIds.push(existing2 ? Number(existing2.id) : null);
1359
+ }
1253
1360
  }
1254
1361
  backfilled++;
1255
1362
  }
@@ -3517,7 +3624,7 @@ var init_retry = __esm({
3517
3624
  });
3518
3625
 
3519
3626
  // src/ollama/client.ts
3520
- import { z as z8 } from "zod";
3627
+ import { z as z9 } from "zod";
3521
3628
  function isRetryable(err) {
3522
3629
  if (err instanceof OllamaHttpError) {
3523
3630
  return err.status >= 500 && err.status < 600;
@@ -3540,26 +3647,26 @@ var init_client = __esm({
3540
3647
  DEFAULT_BATCH_SIZE = 10;
3541
3648
  DEFAULT_TIMEOUT_MS = 3e4;
3542
3649
  DEFAULT_RETRIES = 3;
3543
- EmbedResponseSchema = z8.object({
3544
- embeddings: z8.array(z8.array(z8.number())),
3545
- model: z8.string().optional()
3650
+ EmbedResponseSchema = z9.object({
3651
+ embeddings: z9.array(z9.array(z9.number())),
3652
+ model: z9.string().optional()
3546
3653
  });
3547
- TagsResponseSchema = z8.object({
3548
- models: z8.array(
3549
- z8.object({
3550
- name: z8.string()
3654
+ TagsResponseSchema = z9.object({
3655
+ models: z9.array(
3656
+ z9.object({
3657
+ name: z9.string()
3551
3658
  })
3552
3659
  )
3553
3660
  });
3554
- ChatResponseSchema = z8.object({
3555
- model: z8.string(),
3556
- message: z8.object({
3557
- role: z8.literal("assistant"),
3558
- content: z8.string()
3661
+ ChatResponseSchema = z9.object({
3662
+ model: z9.string(),
3663
+ message: z9.object({
3664
+ role: z9.literal("assistant"),
3665
+ content: z9.string()
3559
3666
  }),
3560
- done: z8.boolean().optional(),
3561
- total_duration: z8.number().optional(),
3562
- eval_count: z8.number().optional()
3667
+ done: z9.boolean().optional(),
3668
+ total_duration: z9.number().optional(),
3669
+ eval_count: z9.number().optional()
3563
3670
  });
3564
3671
  OllamaHttpError = class extends Error {
3565
3672
  status;
@@ -5426,7 +5533,16 @@ var init_obsidian_fs = __esm({
5426
5533
  if (since !== void 0 && mtime < since) continue;
5427
5534
  const body = await fs4.readFile(abs, "utf-8");
5428
5535
  const hash = computeBodyHash(body);
5429
- yield { id: this.pathToDocId(rel), mtime, hash };
5536
+ let id;
5537
+ try {
5538
+ id = this.pathToDocId(rel);
5539
+ } catch (err) {
5540
+ console.error(
5541
+ `[obsidian-fs:${this.vault.name}] skipping un-addressable file ${JSON.stringify(rel)}: ${err instanceof Error ? err.message : String(err)}`
5542
+ );
5543
+ continue;
5544
+ }
5545
+ yield { id, mtime, hash };
5430
5546
  yielded++;
5431
5547
  }
5432
5548
  }
@@ -7387,7 +7503,7 @@ var init_validator = __esm({
7387
7503
  });
7388
7504
 
7389
7505
  // src/memory/contract/default-v1.ts
7390
- import { z as z9 } from "zod";
7506
+ import { z as z10 } from "zod";
7391
7507
  var requiredKeys, baseShape, DEFAULT_MEMORY_V1;
7392
7508
  var init_default_v1 = __esm({
7393
7509
  "src/memory/contract/default-v1.ts"() {
@@ -7402,15 +7518,15 @@ var init_default_v1 = __esm({
7402
7518
  "superseded_by",
7403
7519
  "type"
7404
7520
  ];
7405
- baseShape = z9.object({
7406
- source: z9.enum(["agent", "user", "imported"]),
7407
- confidence: z9.enum(["direct", "inferred", "uncertain"]),
7408
- evidence: z9.array(z9.string()),
7409
- status: z9.enum(["active", "superseded", "archived"]).default("active"),
7410
- observed_at: z9.string().datetime({ offset: true }),
7411
- superseded_by: z9.string().nullable().default(null),
7412
- type: z9.string().min(1),
7413
- superseded_reason: z9.string().optional()
7521
+ baseShape = z10.object({
7522
+ source: z10.enum(["agent", "user", "imported"]),
7523
+ confidence: z10.enum(["direct", "inferred", "uncertain"]),
7524
+ evidence: z10.array(z10.string()),
7525
+ status: z10.enum(["active", "superseded", "archived"]).default("active"),
7526
+ observed_at: z10.string().datetime({ offset: true }),
7527
+ superseded_by: z10.string().nullable().default(null),
7528
+ type: z10.string().min(1),
7529
+ superseded_reason: z10.string().optional()
7414
7530
  }).passthrough().superRefine((data, ctx) => {
7415
7531
  if (data.status === "superseded") {
7416
7532
  if (data.superseded_by === null || data.superseded_by === void 0) {
@@ -7443,7 +7559,7 @@ var init_default_v1 = __esm({
7443
7559
  });
7444
7560
 
7445
7561
  // src/memory/contract/default-brief-v1.ts
7446
- import { z as z10 } from "zod";
7562
+ import { z as z11 } from "zod";
7447
7563
  var requiredKeys2, baseShape2, DEFAULT_BRIEF_V1;
7448
7564
  var init_default_brief_v1 = __esm({
7449
7565
  "src/memory/contract/default-brief-v1.ts"() {
@@ -7465,29 +7581,29 @@ var init_default_brief_v1 = __esm({
7465
7581
  "compiled_at",
7466
7582
  "source_hashes"
7467
7583
  ];
7468
- baseShape2 = z10.object({
7584
+ baseShape2 = z11.object({
7469
7585
  // ── Base shape inherited from default-v1 ────────────────────────
7470
- source: z10.enum(["agent", "user", "imported"]),
7471
- confidence: z10.enum(["direct", "inferred", "uncertain"]),
7472
- evidence: z10.array(z10.string()),
7586
+ source: z11.enum(["agent", "user", "imported"]),
7587
+ confidence: z11.enum(["direct", "inferred", "uncertain"]),
7588
+ evidence: z11.array(z11.string()),
7473
7589
  // ── Status enum WIDENED for briefs: + "stale" ──────────────────
7474
- status: z10.enum(["active", "stale", "superseded", "archived"]).default("active"),
7475
- observed_at: z10.string().datetime({ offset: true }),
7476
- superseded_by: z10.string().nullable().default(null),
7477
- type: z10.string().min(1),
7478
- superseded_reason: z10.string().optional(),
7590
+ status: z11.enum(["active", "stale", "superseded", "archived"]).default("active"),
7591
+ observed_at: z11.string().datetime({ offset: true }),
7592
+ superseded_by: z11.string().nullable().default(null),
7593
+ type: z11.string().min(1),
7594
+ superseded_reason: z11.string().optional(),
7479
7595
  // ── Brief-specific properties (D-11 brief shape) ───────────────
7480
- target: z10.string().min(1),
7596
+ target: z11.string().min(1),
7481
7597
  /**
7482
7598
  * Brief purpose — free text but bounded at 500 chars so
7483
7599
  * `list_briefs` stays scannable. Lower bound `min(1)` matches
7484
7600
  * BRF-03 "no empty purpose".
7485
7601
  */
7486
- purpose: z10.string().min(1).max(500),
7602
+ purpose: z11.string().min(1).max(500),
7487
7603
  /** DocId list of all sources the brief was compiled from. */
7488
- compiled_from: z10.array(z10.string()).min(1),
7604
+ compiled_from: z11.array(z11.string()).min(1),
7489
7605
  /** ISO-8601 datetime with offset (mirrors observed_at). */
7490
- compiled_at: z10.string().datetime({ offset: true }),
7606
+ compiled_at: z11.string().datetime({ offset: true }),
7491
7607
  /**
7492
7608
  * Record<ChunkId, BriefSourceHash> — staleness contract. The map
7493
7609
  * key is the public ChunkId (`<DocId>#chunk-<7-hex>`); the value
@@ -7495,12 +7611,12 @@ var init_default_brief_v1 = __esm({
7495
7611
  * the cross-field invariant below only REQUIRES it on stale; the
7496
7612
  * validator still rejects `status: "stale"` writes that omit it.
7497
7613
  */
7498
- source_hashes: z10.record(z10.string(), z10.string()).optional(),
7614
+ source_hashes: z11.record(z11.string(), z11.string()).optional(),
7499
7615
  /**
7500
7616
  * Daemon-computed list of source DocIds whose hashes have
7501
7617
  * diverged. Populated when `status` flips to `"stale"`.
7502
7618
  */
7503
- changed_sources: z10.array(z10.string()).optional()
7619
+ changed_sources: z11.array(z11.string()).optional()
7504
7620
  }).passthrough().superRefine((data, ctx) => {
7505
7621
  if (data.status === "superseded") {
7506
7622
  if (data.superseded_by === null || data.superseded_by === void 0) {
@@ -7570,36 +7686,36 @@ var init_contract_yaml_read = __esm({
7570
7686
  });
7571
7687
 
7572
7688
  // src/memory/contract/schema.ts
7573
- import { z as z11 } from "zod";
7689
+ import { z as z12 } from "zod";
7574
7690
  var PropertyRuleSchema, CrossFieldRuleSchema, MemoryContractYamlSchema;
7575
7691
  var init_schema2 = __esm({
7576
7692
  "src/memory/contract/schema.ts"() {
7577
7693
  "use strict";
7578
7694
  init_esm_shims();
7579
- PropertyRuleSchema = z11.object({
7580
- type: z11.enum(["string", "datetime", "array", "doc_id", "number", "boolean", "reference", "date"]),
7581
- allowed: z11.array(z11.string()).optional(),
7582
- default: z11.unknown().optional(),
7583
- items: z11.object({ type: z11.string() }).optional(),
7584
- min_length: z11.number().optional(),
7695
+ PropertyRuleSchema = z12.object({
7696
+ type: z12.enum(["string", "datetime", "array", "doc_id", "number", "boolean", "reference", "date"]),
7697
+ allowed: z12.array(z12.string()).optional(),
7698
+ default: z12.unknown().optional(),
7699
+ items: z12.object({ type: z12.string() }).optional(),
7700
+ min_length: z12.number().optional(),
7585
7701
  /** When true, the property accepts `null` as a sentinel value (in
7586
7702
  * addition to whatever `type` says). Used for required-but-null-by-
7587
7703
  * default properties like `superseded_by` on active observations. */
7588
- nullable: z11.boolean().optional()
7704
+ nullable: z12.boolean().optional()
7589
7705
  });
7590
- CrossFieldRuleSchema = z11.object({
7591
- when: z11.string(),
7592
- require: z11.string()
7706
+ CrossFieldRuleSchema = z12.object({
7707
+ when: z12.string(),
7708
+ require: z12.string()
7593
7709
  });
7594
- MemoryContractYamlSchema = z11.object({
7595
- name: z11.string().min(1),
7596
- version: z11.string().default("1.0"),
7597
- required_properties: z11.record(z11.string(), PropertyRuleSchema),
7598
- optional_properties: z11.record(z11.string(), PropertyRuleSchema).default({}),
7599
- cross_field_rules: z11.array(CrossFieldRuleSchema).default([]),
7600
- naming: z11.object({
7601
- strategy: z11.enum(["caller-provided", "date-slug", "adapter-assigned"]),
7602
- pattern: z11.string().optional()
7710
+ MemoryContractYamlSchema = z12.object({
7711
+ name: z12.string().min(1),
7712
+ version: z12.string().default("1.0"),
7713
+ required_properties: z12.record(z12.string(), PropertyRuleSchema),
7714
+ optional_properties: z12.record(z12.string(), PropertyRuleSchema).default({}),
7715
+ cross_field_rules: z12.array(CrossFieldRuleSchema).default([]),
7716
+ naming: z12.object({
7717
+ strategy: z12.enum(["caller-provided", "date-slug", "adapter-assigned"]),
7718
+ pattern: z12.string().optional()
7603
7719
  })
7604
7720
  });
7605
7721
  }
@@ -7607,7 +7723,7 @@ var init_schema2 = __esm({
7607
7723
 
7608
7724
  // src/memory/contract/loader.ts
7609
7725
  import { parse as parseYaml } from "yaml";
7610
- import { z as z12 } from "zod";
7726
+ import { z as z13 } from "zod";
7611
7727
  function __cacheContract(name, contract) {
7612
7728
  contractCache.set(name, contract);
7613
7729
  }
@@ -9205,7 +9321,7 @@ var init_memory_stats = __esm({
9205
9321
  });
9206
9322
 
9207
9323
  // src/memory/resources/index.ts
9208
- var RESOURCE_URI_LIST_SINKS, RESOURCE_URI_MEMORY_STATS, RESOURCE_URI_LIST_BRIEFS, RESOURCE_URI_LIST_CONTRACTS, RESOURCE_URI_LIST_CONTRACT_VERBS, RESOURCE_URI_VAULTS, RESOURCE_URI_MODELS, RESOURCE_URI_RECENT, RESOURCE_URI_STATS, RESOURCE_URI_BACKLINKS;
9324
+ var RESOURCE_URI_LIST_SINKS, RESOURCE_URI_MEMORY_STATS, RESOURCE_URI_LIST_BRIEFS, RESOURCE_URI_LIST_CONTRACTS, RESOURCE_URI_LIST_CONTRACT_VERBS, RESOURCE_URI_SOURCES, RESOURCE_URI_VAULTS, RESOURCE_URI_MODELS, RESOURCE_URI_RECENT, RESOURCE_URI_STATS, RESOURCE_URI_BACKLINKS;
9209
9325
  var init_resources = __esm({
9210
9326
  "src/memory/resources/index.ts"() {
9211
9327
  "use strict";
@@ -9217,6 +9333,7 @@ var init_resources = __esm({
9217
9333
  RESOURCE_URI_LIST_BRIEFS = "vault-memory://briefs";
9218
9334
  RESOURCE_URI_LIST_CONTRACTS = "vault-memory://contracts";
9219
9335
  RESOURCE_URI_LIST_CONTRACT_VERBS = "vault-memory://contract-verbs";
9336
+ RESOURCE_URI_SOURCES = "vault-memory://sources";
9220
9337
  RESOURCE_URI_VAULTS = "vault-memory://vaults";
9221
9338
  RESOURCE_URI_MODELS = "vault-memory://models";
9222
9339
  RESOURCE_URI_RECENT = "vault-memory://recent";
@@ -9278,6 +9395,25 @@ var init_resource_registry = __esm({
9278
9395
  description: "List baseline assembly verbs + custom (mcp://) verbs in use, with invocation_count + last_seen aggregated from contract_audit (D-A2b). Baseline verbs are constant per ADR-006 \xA7Decision 3.",
9279
9396
  mimeType: "application/json"
9280
9397
  },
9398
+ // ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ───────
9399
+ {
9400
+ name: "sources",
9401
+ uriTemplate: "vault-memory://sources",
9402
+ description: "List peer MCP servers vault-memory connects to, with per-source status (connected/unavailable/unreachable), tool_count, and last_refreshed. vault-memory itself is not included. SOURCES-REGISTRY \xA75.1.",
9403
+ mimeType: "application/json"
9404
+ },
9405
+ {
9406
+ name: "source-tools",
9407
+ uriTemplate: "vault-memory://sources/{name}/tools",
9408
+ description: "List the cached tools/list for one peer MCP source. Empty when the source is not connected. SOURCES-REGISTRY \xA75.2.",
9409
+ mimeType: "application/json"
9410
+ },
9411
+ {
9412
+ name: "source-tool",
9413
+ uriTemplate: "vault-memory://sources/{name}/tools/{tool}",
9414
+ description: "Read a single tool's schema from one peer MCP source, inlined from the cached tools/list. SOURCES-REGISTRY \xA75.3.",
9415
+ mimeType: "application/json"
9416
+ },
9281
9417
  // ─── Phase 8 (Plan 08-05 / REL-08) — promoted from v1 tools ─────────────
9282
9418
  {
9283
9419
  name: "vaults",
@@ -11789,11 +11925,11 @@ var init_obsidian_fs3 = __esm({
11789
11925
  });
11790
11926
 
11791
11927
  // src/tool-registry.ts
11792
- import { z as z13 } from "zod";
11928
+ import { z as z14 } from "zod";
11793
11929
  function buildToolSchema(name) {
11794
11930
  const builder = SCHEMA_BUILDERS[name];
11795
11931
  if (builder) return builder();
11796
- return z13.object(TOOL_SCHEMAS[name]);
11932
+ return z14.object(TOOL_SCHEMAS[name]);
11797
11933
  }
11798
11934
  var TOOLS, DOC_ID_PATTERN2, PredicateSchema, TOOL_SCHEMAS, SCHEMA_BUILDERS;
11799
11935
  var init_tool_registry = __esm({
@@ -12666,243 +12802,243 @@ var init_tool_registry = __esm({
12666
12802
  }
12667
12803
  ];
12668
12804
  DOC_ID_PATTERN2 = /^[a-z][a-z0-9-]*:\/\/[^/]+\/.+$/;
12669
- PredicateSchema = z13.union([
12670
- z13.string(),
12671
- z13.number(),
12672
- z13.boolean(),
12673
- z13.null(),
12674
- z13.object({ $in: z13.array(z13.union([z13.string(), z13.number(), z13.boolean(), z13.null()])) }),
12675
- z13.object({ $exists: z13.boolean() }),
12676
- z13.object({ $contains: z13.union([z13.string(), z13.number(), z13.boolean(), z13.null()]) })
12805
+ PredicateSchema = z14.union([
12806
+ z14.string(),
12807
+ z14.number(),
12808
+ z14.boolean(),
12809
+ z14.null(),
12810
+ z14.object({ $in: z14.array(z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()])) }),
12811
+ z14.object({ $exists: z14.boolean() }),
12812
+ z14.object({ $contains: z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()]) })
12677
12813
  ]);
12678
12814
  TOOL_SCHEMAS = {
12679
12815
  list_vaults: {},
12680
12816
  read_note: {
12681
- vault: z13.string(),
12682
- path: z13.string()
12817
+ vault: z14.string(),
12818
+ path: z14.string()
12683
12819
  },
12684
12820
  search_semantic: {
12685
- query: z13.string().min(1),
12686
- vaults: z13.array(z13.string()).optional(),
12687
- top_k: z13.number().int().positive().max(100).optional().default(10),
12688
- exclude_paths: z13.array(z13.string()).optional()
12821
+ query: z14.string().min(1),
12822
+ vaults: z14.array(z14.string()).optional(),
12823
+ top_k: z14.number().int().positive().max(100).optional().default(10),
12824
+ exclude_paths: z14.array(z14.string()).optional()
12689
12825
  },
12690
12826
  search_text: {
12691
- query: z13.string().min(1),
12692
- vaults: z13.array(z13.string()).optional(),
12693
- top_k: z13.number().int().positive().max(100).optional().default(10),
12694
- exclude_paths: z13.array(z13.string()).optional()
12827
+ query: z14.string().min(1),
12828
+ vaults: z14.array(z14.string()).optional(),
12829
+ top_k: z14.number().int().positive().max(100).optional().default(10),
12830
+ exclude_paths: z14.array(z14.string()).optional()
12695
12831
  },
12696
12832
  search_hybrid: {
12697
- query: z13.string().min(1),
12698
- vaults: z13.array(z13.string()).optional(),
12699
- top_k: z13.number().int().positive().max(100).optional().default(10),
12700
- rrf_k: z13.number().int().positive().max(1e3).optional().default(60),
12701
- exclude_paths: z13.array(z13.string()).optional(),
12702
- rerank: z13.boolean().optional().default(false),
12833
+ query: z14.string().min(1),
12834
+ vaults: z14.array(z14.string()).optional(),
12835
+ top_k: z14.number().int().positive().max(100).optional().default(10),
12836
+ rrf_k: z14.number().int().positive().max(1e3).optional().default(60),
12837
+ exclude_paths: z14.array(z14.string()).optional(),
12838
+ rerank: z14.boolean().optional().default(false),
12703
12839
  // Phase 3 / 03-05 additive params — D-07, D-08, ASM-07, ASM-08.
12704
12840
  // All `.optional()` with defaults that vanish when unset, so v1
12705
12841
  // callers see no behavior change.
12706
- recency_weight: z13.number().optional().default(0),
12707
- authority_weight: z13.number().optional().default(0),
12708
- half_life_days: z13.number().positive().optional().default(30),
12709
- include_superseded: z13.boolean().optional().default(false),
12842
+ recency_weight: z14.number().optional().default(0),
12843
+ authority_weight: z14.number().optional().default(0),
12844
+ half_life_days: z14.number().positive().optional().default(30),
12845
+ include_superseded: z14.boolean().optional().default(false),
12710
12846
  // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──
12711
12847
  // Nested under a single optional `expand` object per D-15. When
12712
12848
  // omitted, hybridSearch behavior is byte-identical to v1 (the
12713
12849
  // guard `if (opts.expand && opts.expandDeps && ...)` at the end of
12714
12850
  // `src/search/hybrid.ts` short-circuits entirely). The literal-
12715
12851
  // union for `hops` enforces the D-05 hop cap at the boundary.
12716
- expand: z13.object({
12717
- hops: z13.union([z13.literal(1), z13.literal(2)]),
12718
- direction: z13.enum(["forward", "backward", "both"]).optional(),
12719
- edge_types: z13.array(z13.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional()
12852
+ expand: z14.object({
12853
+ hops: z14.union([z14.literal(1), z14.literal(2)]),
12854
+ direction: z14.enum(["forward", "backward", "both"]).optional(),
12855
+ edge_types: z14.array(z14.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional()
12720
12856
  }).optional()
12721
12857
  },
12722
12858
  list_backlinks: {
12723
- vault: z13.string(),
12724
- path: z13.string()
12859
+ vault: z14.string(),
12860
+ path: z14.string()
12725
12861
  },
12726
12862
  list_forward_links: {
12727
- vault: z13.string(),
12728
- path: z13.string(),
12729
- include_broken: z13.boolean().optional().default(true)
12863
+ vault: z14.string(),
12864
+ path: z14.string(),
12865
+ include_broken: z14.boolean().optional().default(true)
12730
12866
  },
12731
12867
  find_broken_links: {
12732
- vault: z13.string()
12868
+ vault: z14.string()
12733
12869
  },
12734
12870
  query_frontmatter: {
12735
- vault: z13.string(),
12736
- where: z13.record(z13.string(), PredicateSchema),
12737
- limit: z13.number().int().positive().max(1e3).optional().default(100)
12871
+ vault: z14.string(),
12872
+ where: z14.record(z14.string(), PredicateSchema),
12873
+ limit: z14.number().int().positive().max(1e3).optional().default(100)
12738
12874
  },
12739
12875
  write_note: {
12740
- vault: z13.string(),
12741
- path: z13.string(),
12742
- content: z13.string(),
12743
- frontmatter: z13.record(z13.string(), z13.unknown()).nullable().optional(),
12744
- expected_hash: z13.string().optional(),
12745
- client_id: z13.string().optional()
12876
+ vault: z14.string(),
12877
+ path: z14.string(),
12878
+ content: z14.string(),
12879
+ frontmatter: z14.record(z14.string(), z14.unknown()).nullable().optional(),
12880
+ expected_hash: z14.string().optional(),
12881
+ client_id: z14.string().optional()
12746
12882
  },
12747
12883
  update_frontmatter: {
12748
- vault: z13.string(),
12749
- path: z13.string(),
12750
- merge: z13.record(z13.string(), z13.unknown()),
12751
- expected_hash: z13.string().optional(),
12752
- client_id: z13.string().optional()
12884
+ vault: z14.string(),
12885
+ path: z14.string(),
12886
+ merge: z14.record(z14.string(), z14.unknown()),
12887
+ expected_hash: z14.string().optional(),
12888
+ client_id: z14.string().optional()
12753
12889
  },
12754
12890
  delete_note: {
12755
- vault: z13.string(),
12756
- path: z13.string(),
12757
- expected_hash: z13.string(),
12758
- client_id: z13.string().optional()
12891
+ vault: z14.string(),
12892
+ path: z14.string(),
12893
+ expected_hash: z14.string(),
12894
+ client_id: z14.string().optional()
12759
12895
  },
12760
12896
  audit_log: {
12761
- vault: z13.string(),
12762
- note_path: z13.string().optional(),
12763
- op: z13.enum(["create", "update", "delete"]).optional(),
12764
- since: z13.number().int().nonnegative().optional(),
12765
- limit: z13.number().int().positive().max(1e3).optional().default(50),
12897
+ vault: z14.string(),
12898
+ note_path: z14.string().optional(),
12899
+ op: z14.enum(["create", "update", "delete"]).optional(),
12900
+ since: z14.number().int().nonnegative().optional(),
12901
+ limit: z14.number().int().positive().max(1e3).optional().default(50),
12766
12902
  // Plan 02-06 (MEM-08): additive optional filter. The MCP tool's
12767
12903
  // `description` string is INTENTIONALLY unchanged — Phase 1 byte-identity
12768
12904
  // is preserved. New capability is documented in docs/tools/audit_log.md.
12769
- is_memory_sink_write: z13.boolean().optional()
12905
+ is_memory_sink_write: z14.boolean().optional()
12770
12906
  },
12771
12907
  list_models: {
12772
- vault: z13.string()
12908
+ vault: z14.string()
12773
12909
  },
12774
12910
  start_shadow_index: {
12775
- vault: z13.string(),
12776
- model: z13.string().min(1),
12777
- batch_size: z13.number().int().positive().max(256).optional()
12911
+ vault: z14.string(),
12912
+ model: z14.string().min(1),
12913
+ batch_size: z14.number().int().positive().max(256).optional()
12778
12914
  },
12779
12915
  switch_active_model: {
12780
- vault: z13.string(),
12781
- model_name: z13.string().min(1)
12916
+ vault: z14.string(),
12917
+ model_name: z14.string().min(1)
12782
12918
  },
12783
12919
  vacuum_embeddings: {
12784
- vault: z13.string()
12920
+ vault: z14.string()
12785
12921
  },
12786
12922
  index_runs: {
12787
- vault: z13.string(),
12788
- limit: z13.number().int().positive().max(200).optional().default(20)
12923
+ vault: z14.string(),
12924
+ limit: z14.number().int().positive().max(200).optional().default(20)
12789
12925
  },
12790
12926
  search: {
12791
- query: z13.string().min(1),
12792
- limit: z13.number().int().positive().max(50).optional().default(10)
12927
+ query: z14.string().min(1),
12928
+ limit: z14.number().int().positive().max(50).optional().default(10)
12793
12929
  },
12794
12930
  fetch: {
12795
- id: z13.string().min(1)
12931
+ id: z14.string().min(1)
12796
12932
  },
12797
12933
  vault_stats: {
12798
- vault: z13.string().optional()
12934
+ vault: z14.string().optional()
12799
12935
  },
12800
12936
  recent_notes: {
12801
- vault: z13.string().optional(),
12802
- limit: z13.number().int().positive().max(200).optional().default(20),
12803
- since: z13.number().int().nonnegative().optional()
12937
+ vault: z14.string().optional(),
12938
+ limit: z14.number().int().positive().max(200).optional().default(20),
12939
+ since: z14.number().int().nonnegative().optional()
12804
12940
  },
12805
12941
  suggest_frontmatter: {
12806
- vault: z13.string(),
12807
- path: z13.string().optional(),
12808
- content: z13.string().optional(),
12809
- title: z13.string().optional(),
12810
- folder_hint: z13.string().optional()
12942
+ vault: z14.string(),
12943
+ path: z14.string().optional(),
12944
+ content: z14.string().optional(),
12945
+ title: z14.string().optional(),
12946
+ folder_hint: z14.string().optional()
12811
12947
  },
12812
12948
  // ── Phase 2 memory tools (Plan 02-04) ───────────────────────────────────
12813
12949
  record_observation: {
12814
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12815
- claim: z13.string().min(1).describe("Short natural-language statement of the observation (becomes title + body)"),
12816
- evidence: z13.array(z13.string()).describe("DocIds or quoted source spans supporting the claim; empty array allowed"),
12817
- confidence: z13.enum(["direct", "inferred", "uncertain"]).describe("How the agent arrived at this claim"),
12818
- type: z13.string().min(1).describe(
12950
+ vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12951
+ claim: z14.string().min(1).describe("Short natural-language statement of the observation (becomes title + body)"),
12952
+ evidence: z14.array(z14.string()).describe("DocIds or quoted source spans supporting the claim; empty array allowed"),
12953
+ confidence: z14.enum(["direct", "inferred", "uncertain"]).describe("How the agent arrived at this claim"),
12954
+ type: z14.string().min(1).describe(
12819
12955
  "Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision')"
12820
12956
  ),
12821
- sink: z13.string().min(1).optional().describe(
12957
+ sink: z14.string().min(1).optional().describe(
12822
12958
  "Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to the vault's default sink."
12823
12959
  ),
12824
- properties: z13.record(z13.string(), z13.unknown()).optional().describe(
12960
+ properties: z14.record(z14.string(), z14.unknown()).optional().describe(
12825
12961
  "Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins)"
12826
12962
  )
12827
12963
  },
12828
12964
  supersede: {
12829
- doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("DocId of the document being superseded"),
12830
- replacement_doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("DocId of the replacement document"),
12831
- reason: z13.string().min(1).describe("Why the old document is being retired; written to superseded_reason")
12965
+ doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("DocId of the document being superseded"),
12966
+ replacement_doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("DocId of the replacement document"),
12967
+ reason: z14.string().min(1).describe("Why the old document is being retired; written to superseded_reason")
12832
12968
  },
12833
12969
  // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ───────────────────
12834
12970
  compile_brief: {
12835
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12836
- target: z13.string().min(1).describe("Stable cross-version handle for the brief (e.g. 'atlas-q3')"),
12837
- source_doc_ids: z13.array(z13.string().regex(DOC_ID_PATTERN2)).min(1).max(50).describe("DocIds the brief is compiled from; deduped, capped at 50 (D-03)"),
12838
- purpose: z13.string().min(1).max(500).describe("Free-form purpose; bounded so list_briefs stays scannable"),
12839
- max_tokens: z13.number().int().positive().optional().default(2e3).describe("Hint for the LLM ladder; default 2000"),
12840
- prepared_text: z13.string().min(1).optional().describe("D-10 tier 3 fallback when no LLM is reachable \u2014 verbatim body to stitch in"),
12841
- sink: z13.string().min(1).optional().describe("Override the default `_memory/_briefs` sink")
12971
+ vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12972
+ target: z14.string().min(1).describe("Stable cross-version handle for the brief (e.g. 'atlas-q3')"),
12973
+ source_doc_ids: z14.array(z14.string().regex(DOC_ID_PATTERN2)).min(1).max(50).describe("DocIds the brief is compiled from; deduped, capped at 50 (D-03)"),
12974
+ purpose: z14.string().min(1).max(500).describe("Free-form purpose; bounded so list_briefs stays scannable"),
12975
+ max_tokens: z14.number().int().positive().optional().default(2e3).describe("Hint for the LLM ladder; default 2000"),
12976
+ prepared_text: z14.string().min(1).optional().describe("D-10 tier 3 fallback when no LLM is reachable \u2014 verbatim body to stitch in"),
12977
+ sink: z14.string().min(1).optional().describe("Override the default `_memory/_briefs` sink")
12842
12978
  },
12843
12979
  get_brief: {
12844
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12845
- target: z13.string().min(1).describe("Stable cross-version handle for the brief"),
12846
- max_age_days: z13.number().int().nonnegative().optional().describe("Reject briefs older than this many days unless allow_stale=true"),
12847
- allow_stale: z13.boolean().optional().default(false).describe(
12980
+ vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12981
+ target: z14.string().min(1).describe("Stable cross-version handle for the brief"),
12982
+ max_age_days: z14.number().int().nonnegative().optional().describe("Reject briefs older than this many days unless allow_stale=true"),
12983
+ allow_stale: z14.boolean().optional().default(false).describe(
12848
12984
  "When true, return briefs flagged stale or too_old with annotation rather than null"
12849
12985
  )
12850
12986
  },
12851
12987
  // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ────────────────────────
12852
12988
  get_outline: {
12853
- doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the document"),
12854
- vaults: z13.array(z13.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
12989
+ doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the document"),
12990
+ vaults: z14.array(z14.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
12855
12991
  },
12856
12992
  // ── Phase 3 assembly tools (Plan 03-03) ─────────────────────────────────
12857
12993
  search_sections: {
12858
- query: z13.string().min(1),
12859
- limit: z13.number().int().positive().max(50).optional().default(10),
12860
- vaults: z13.array(z13.string().min(1)).optional(),
12994
+ query: z14.string().min(1),
12995
+ limit: z14.number().int().positive().max(50).optional().default(10),
12996
+ vaults: z14.array(z14.string().min(1)).optional(),
12861
12997
  // Forward-compat with slice 03-05's authority/staleness rescore.
12862
12998
  // Accepted today; ignored by the controller until 03-05 wires the
12863
12999
  // forwarding inside hybridSearch. See 03-03-DEVIATIONS.md.
12864
- recency_weight: z13.number().min(0).optional().default(0),
12865
- authority_weight: z13.number().min(0).optional().default(0),
12866
- include_superseded: z13.boolean().optional().default(false)
13000
+ recency_weight: z14.number().min(0).optional().default(0),
13001
+ authority_weight: z14.number().min(0).optional().default(0),
13002
+ include_superseded: z14.boolean().optional().default(false)
12867
13003
  },
12868
13004
  // ── Phase 2 memory tools (Plan 02-05) ───────────────────────────────────
12869
13005
  recall: {
12870
- query: z13.string().min(1).describe("Natural-language query; routes through hybrid (semantic + BM25) search"),
12871
- min_confidence: z13.enum(["direct", "inferred", "uncertain"]).optional().describe(
13006
+ query: z14.string().min(1).describe("Natural-language query; routes through hybrid (semantic + BM25) search"),
13007
+ min_confidence: z14.enum(["direct", "inferred", "uncertain"]).optional().describe(
12872
13008
  "Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1)"
12873
13009
  ),
12874
- types: z13.array(z13.string().min(1)).optional().describe("Restrict to docs whose `type` property is in this set"),
12875
- max_age_days: z13.number().int().positive().optional().describe("Exclude docs whose `observed_at` is older than this many days"),
12876
- sink: z13.string().min(1).optional().describe(
13010
+ types: z14.array(z14.string().min(1)).optional().describe("Restrict to docs whose `type` property is in this set"),
13011
+ max_age_days: z14.number().int().positive().optional().describe("Exclude docs whose `observed_at` is older than this many days"),
13012
+ sink: z14.string().min(1).optional().describe(
12877
13013
  "Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to all configured sinks."
12878
13014
  ),
12879
- limit: z13.number().int().positive().max(200).optional().describe("Maximum results AFTER filter+sort; default 20"),
12880
- vaults: z13.array(z13.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
13015
+ limit: z14.number().int().positive().max(200).optional().describe("Maximum results AFTER filter+sort; default 20"),
13016
+ vaults: z14.array(z14.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
12881
13017
  },
12882
13018
  // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ────────────────────────
12883
13019
  get_document_bundle: {
12884
- doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the anchor document"),
13020
+ doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the anchor document"),
12885
13021
  // v2.0.0 accepts only depth:1. The literal pin guarantees Zod
12886
13022
  // rejects any other value at the boundary so the controller does
12887
13023
  // not need to clamp. Phase 4 may widen additively (z.union of
12888
13024
  // literals, or `z.number().int().min(1).max(2)`).
12889
- depth: z13.literal(1).optional().default(1).describe("Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen."),
12890
- vaults: z13.array(z13.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
13025
+ depth: z14.literal(1).optional().default(1).describe("Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen."),
13026
+ vaults: z14.array(z14.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
12891
13027
  },
12892
13028
  // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────
12893
13029
  expand: {
12894
- seed_doc_ids: z13.array(z13.string().regex(DOC_ID_PATTERN2)).min(1).describe(
13030
+ seed_doc_ids: z14.array(z14.string().regex(DOC_ID_PATTERN2)).min(1).describe(
12895
13031
  "1+ opaque DocIds (e.g. obsidian-fs://<vault>/<path>) \u2014 seeds of the BFS."
12896
13032
  ),
12897
13033
  // Hops hard-capped at 2 (D-05) via Zod literal union — `hops: 3`
12898
13034
  // is rejected at the boundary; the controller does not clamp.
12899
- hops: z13.union([z13.literal(1), z13.literal(2)]).describe("Hop cap (1 or 2). v2.0.0 hard-caps at 2."),
12900
- direction: z13.enum(["forward", "backward", "both"]).optional().default("both").describe("Edge traversal direction; default 'both'."),
12901
- edge_types: z13.array(z13.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional().describe("Optional filter on edge types; default = all four types."),
12902
- filter_properties: z13.record(z13.string(), z13.unknown()).optional().describe(
13035
+ hops: z14.union([z14.literal(1), z14.literal(2)]).describe("Hop cap (1 or 2). v2.0.0 hard-caps at 2."),
13036
+ direction: z14.enum(["forward", "backward", "both"]).optional().default("both").describe("Edge traversal direction; default 'both'."),
13037
+ edge_types: z14.array(z14.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional().describe("Optional filter on edge types; default = all four types."),
13038
+ filter_properties: z14.record(z14.string(), z14.unknown()).optional().describe(
12903
13039
  "Strict-equality predicate on document properties (e.g. {type: 'Project'})."
12904
13040
  ),
12905
- include_superseded: z13.boolean().optional().default(false).describe(
13041
+ include_superseded: z14.boolean().optional().default(false).describe(
12906
13042
  "When false (default), docs whose properties.status === 'superseded' are dropped."
12907
13043
  )
12908
13044
  },
@@ -12916,50 +13052,50 @@ var init_tool_registry = __esm({
12916
13052
  // works. The runtime path goes through `buildToolSchema("cluster")`
12917
13053
  // which calls the SCHEMA_BUILDERS entry.
12918
13054
  cluster: {
12919
- query: z13.string().min(1).optional(),
12920
- seed_doc_ids: z13.array(z13.string().regex(DOC_ID_PATTERN2)).min(1).optional(),
13055
+ query: z14.string().min(1).optional(),
13056
+ seed_doc_ids: z14.array(z14.string().regex(DOC_ID_PATTERN2)).min(1).optional(),
12921
13057
  // CR-02: `vault` scopes the `query` path on multi-vault setups so
12922
13058
  // search_hybrid is not silently restricted to whichever vault
12923
13059
  // sorts first in VaultManager insertion order. Optional at the
12924
13060
  // schema layer; the runtime cluster() entry enforces the
12925
13061
  // multi-vault-without-vault error.
12926
- vault: z13.string().min(1).optional(),
12927
- method: z13.literal("edge-community"),
12928
- query_top_k: z13.number().int().positive().max(200).optional().default(50),
12929
- force: z13.boolean().optional().default(false)
13062
+ vault: z14.string().min(1).optional(),
13063
+ method: z14.literal("edge-community"),
13064
+ query_top_k: z14.number().int().positive().max(200).optional().default(50),
13065
+ force: z14.boolean().optional().default(false)
12930
13066
  },
12931
13067
  // ── Phase 3 assembly tools (Plan 03-06) ─────────────────────────────────
12932
13068
  assemble_dossier: {
12933
- type: z13.string().min(1).describe(
13069
+ type: z14.string().min(1).describe(
12934
13070
  "Exact-match value for properties.type on the anchor document (D-03 \u2014 no fuzzy match)"
12935
13071
  ),
12936
- key: z13.string().min(1).describe(
13072
+ key: z14.string().min(1).describe(
12937
13073
  "Candidate key \u2014 matches the document's title OR any entry in properties.aliases (D-04)"
12938
13074
  ),
12939
- vaults: z13.array(z13.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
13075
+ vaults: z14.array(z14.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
12940
13076
  },
12941
13077
  // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────
12942
13078
  register_contracts_as_tools: {
12943
- vault: z13.string().min(1).optional().describe("Vault name; omit to apply to all vaults")
13079
+ vault: z14.string().min(1).optional().describe("Vault name; omit to apply to all vaults")
12944
13080
  },
12945
13081
  // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────
12946
13082
  describe_contract: {
12947
- name: z13.string().min(1).describe("Registered contract name (see register_contracts_as_tools)"),
12948
- vault: z13.string().min(1).optional().describe("Vault name; omit on single-vault setups")
13083
+ name: z14.string().min(1).describe("Registered contract name (see register_contracts_as_tools)"),
13084
+ vault: z14.string().min(1).optional().describe("Vault name; omit on single-vault setups")
12949
13085
  },
12950
13086
  // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────
12951
13087
  instantiate_contract: {
12952
- name: z13.string().min(1).describe("Registered contract name"),
12953
- inputs: z13.record(z13.string(), z13.unknown()).optional().default({}).describe("Contract inputs; validated against the contract's inputZodSchema"),
12954
- source_overrides: z13.record(z13.string(), z13.string()).optional().describe("Override declared source handles by handle name"),
12955
- sink_overrides: z13.record(z13.string(), z13.string()).optional().describe(
13088
+ name: z14.string().min(1).describe("Registered contract name"),
13089
+ inputs: z14.record(z14.string(), z14.unknown()).optional().default({}).describe("Contract inputs; validated against the contract's inputZodSchema"),
13090
+ source_overrides: z14.record(z14.string(), z14.string()).optional().describe("Override declared source handles by handle name"),
13091
+ sink_overrides: z14.record(z14.string(), z14.string()).optional().describe(
12956
13092
  "Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c)."
12957
13093
  ),
12958
- vault: z13.string().min(1).optional().describe("Vault name; omit on single-vault setups")
13094
+ vault: z14.string().min(1).optional().describe("Vault name; omit on single-vault setups")
12959
13095
  }
12960
13096
  };
12961
13097
  SCHEMA_BUILDERS = {
12962
- suggest_frontmatter: () => z13.object(TOOL_SCHEMAS.suggest_frontmatter).refine((v) => v.path !== void 0 || v.content !== void 0, {
13098
+ suggest_frontmatter: () => z14.object(TOOL_SCHEMAS.suggest_frontmatter).refine((v) => v.path !== void 0 || v.content !== void 0, {
12963
13099
  message: "suggest_frontmatter requires either `path` or `content`"
12964
13100
  }),
12965
13101
  // Plan 04-05 / D-15a — EXACTLY ONE of `query` or `seed_doc_ids` must
@@ -12968,7 +13104,7 @@ var init_tool_registry = __esm({
12968
13104
  // so this Zod refinement is the early-rejection gate at the MCP
12969
13105
  // boundary (cluster's internal validator handles the same case for
12970
13106
  // direct callers that bypass Zod).
12971
- cluster: () => z13.object(TOOL_SCHEMAS.cluster).refine(
13107
+ cluster: () => z14.object(TOOL_SCHEMAS.cluster).refine(
12972
13108
  (v) => v.query !== void 0 && v.seed_doc_ids === void 0 || v.query === void 0 && v.seed_doc_ids !== void 0,
12973
13109
  {
12974
13110
  message: "cluster requires EXACTLY ONE of `query` or `seed_doc_ids` (D-15a mutual exclusion)"
@@ -13063,7 +13199,7 @@ var init_json_schema_ref = __esm({
13063
13199
  });
13064
13200
 
13065
13201
  // src/contracts/input-schema.ts
13066
- import { z as z14 } from "zod";
13202
+ import { z as z15 } from "zod";
13067
13203
  function buildInputSchema(yamlInputs, required = []) {
13068
13204
  const resolvedProperties = resolveRefs(yamlInputs);
13069
13205
  const jsonSchema = {
@@ -13072,7 +13208,7 @@ function buildInputSchema(yamlInputs, required = []) {
13072
13208
  required,
13073
13209
  additionalProperties: false
13074
13210
  };
13075
- const zodSchema = z14.fromJSONSchema(
13211
+ const zodSchema = z15.fromJSONSchema(
13076
13212
  jsonSchema
13077
13213
  );
13078
13214
  return { zodSchema, jsonSchema };
@@ -13158,7 +13294,7 @@ var init_audit4 = __esm({
13158
13294
  });
13159
13295
 
13160
13296
  // src/contracts/schema.ts
13161
- import { z as z15 } from "zod";
13297
+ import { z as z16 } from "zod";
13162
13298
  var BASELINE_VERBS, MCP_VERB_RE, VerbSchema, StepSchema, HandleDeclSchema, WriteBackSchema, ContractFileSchema;
13163
13299
  var init_schema4 = __esm({
13164
13300
  "src/contracts/schema.ts"() {
@@ -13178,40 +13314,40 @@ var init_schema4 = __esm({
13178
13314
  "read_note"
13179
13315
  ];
13180
13316
  MCP_VERB_RE = /^mcp:\/\/[a-z][a-z0-9_-]*\/[a-z][a-z0-9_-]*$/;
13181
- VerbSchema = z15.union([
13182
- z15.enum([...BASELINE_VERBS, "literal"]),
13183
- z15.string().regex(MCP_VERB_RE)
13317
+ VerbSchema = z16.union([
13318
+ z16.enum([...BASELINE_VERBS, "literal"]),
13319
+ z16.string().regex(MCP_VERB_RE)
13184
13320
  ]);
13185
- StepSchema = z15.object({
13186
- as: z15.string().min(1).regex(/^[a-z_][a-z0-9_]*$/, "alias must be snake_case").describe("D-A2c \u2014 unique snake_case alias for this step's output"),
13321
+ StepSchema = z16.object({
13322
+ as: z16.string().min(1).regex(/^[a-z_][a-z0-9_]*$/, "alias must be snake_case").describe("D-A2c \u2014 unique snake_case alias for this step's output"),
13187
13323
  verb: VerbSchema.describe(
13188
13324
  "Closed enum + literal + mcp:// extension (D-A2a / C-1)"
13189
13325
  ),
13190
- args: z15.record(z15.string(), z15.unknown()).optional(),
13191
- value: z15.unknown().optional()
13326
+ args: z16.record(z16.string(), z16.unknown()).optional(),
13327
+ value: z16.unknown().optional()
13192
13328
  }).describe("One step in an assembly: array");
13193
- HandleDeclSchema = z15.object({
13194
- handle: z15.string().min(1),
13195
- required: z15.boolean().default(true)
13329
+ HandleDeclSchema = z16.object({
13330
+ handle: z16.string().min(1),
13331
+ required: z16.boolean().default(true)
13196
13332
  }).describe("Source or sink handle declaration (D-A4a)");
13197
- WriteBackSchema = z15.object({
13198
- sink: z15.string().min(1).describe("Template expression OR literal sink handle"),
13199
- document_kind: z15.enum(["brief", "observation", "custom"]),
13200
- properties: z15.record(z15.string(), z15.unknown()).default({}),
13201
- body_from: z15.string().min(1).describe("Template expression that resolves to the body string")
13333
+ WriteBackSchema = z16.object({
13334
+ sink: z16.string().min(1).describe("Template expression OR literal sink handle"),
13335
+ document_kind: z16.enum(["brief", "observation", "custom"]),
13336
+ properties: z16.record(z16.string(), z16.unknown()).default({}),
13337
+ body_from: z16.string().min(1).describe("Template expression that resolves to the body string")
13202
13338
  }).describe(
13203
13339
  "DeliveryAdapter.write chokepoint \u2014 only ground-truth DocId source (C-3)"
13204
13340
  );
13205
- ContractFileSchema = z15.object({
13206
- version: z15.literal(1).describe("v2.0.0 supports version 1 only; v2.x may extend additively"),
13207
- name: z15.string().min(1).regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case").describe("Contract name \u2014 used by instantiate_contract and slugify"),
13208
- description: z15.string().default(""),
13209
- inputs: z15.record(z15.string(), z15.unknown()).default({}),
13210
- required: z15.array(z15.string()).default([]),
13211
- sources: z15.record(z15.string(), HandleDeclSchema).default({}),
13212
- sinks: z15.record(z15.string(), HandleDeclSchema).default({}),
13213
- assembly: z15.array(StepSchema).min(1, "assembly must contain at least one step"),
13214
- output_shape: z15.unknown().optional(),
13341
+ ContractFileSchema = z16.object({
13342
+ version: z16.literal(1).describe("v2.0.0 supports version 1 only; v2.x may extend additively"),
13343
+ name: z16.string().min(1).regex(/^[a-z][a-z0-9-]*$/, "name must be kebab-case").describe("Contract name \u2014 used by instantiate_contract and slugify"),
13344
+ description: z16.string().default(""),
13345
+ inputs: z16.record(z16.string(), z16.unknown()).default({}),
13346
+ required: z16.array(z16.string()).default([]),
13347
+ sources: z16.record(z16.string(), HandleDeclSchema).default({}),
13348
+ sinks: z16.record(z16.string(), HandleDeclSchema).default({}),
13349
+ assembly: z16.array(StepSchema).min(1, "assembly must contain at least one step"),
13350
+ output_shape: z16.unknown().optional(),
13215
13351
  write_back: WriteBackSchema.optional()
13216
13352
  }).superRefine((data, ctx) => {
13217
13353
  const aliases = /* @__PURE__ */ new Set();
@@ -13563,6 +13699,9 @@ var init_templates = __esm({
13563
13699
  // src/contracts/mcp-clients.ts
13564
13700
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
13565
13701
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
13702
+ function nowSeconds() {
13703
+ return Math.floor(Date.now() / 1e3);
13704
+ }
13566
13705
  function wrapAvailable(client, transport) {
13567
13706
  return {
13568
13707
  available: true,
@@ -13584,6 +13723,27 @@ function wrapAvailable(client, transport) {
13584
13723
  }
13585
13724
  return res;
13586
13725
  },
13726
+ async listTools() {
13727
+ if (typeof client.listTools !== "function") return [];
13728
+ const res = await client.listTools();
13729
+ const tools = res.tools;
13730
+ if (!Array.isArray(tools)) return [];
13731
+ const out = [];
13732
+ for (const t of tools) {
13733
+ if (!t || typeof t !== "object") continue;
13734
+ const name = t.name;
13735
+ if (typeof name !== "string") continue;
13736
+ const tool = { name };
13737
+ const description = t.description;
13738
+ if (typeof description === "string") tool.description = description;
13739
+ const inputSchema = t.inputSchema;
13740
+ if (inputSchema && typeof inputSchema === "object") {
13741
+ tool.inputSchema = inputSchema;
13742
+ }
13743
+ out.push(tool);
13744
+ }
13745
+ return out;
13746
+ },
13587
13747
  [Symbol.dispose]() {
13588
13748
  transport.close();
13589
13749
  }
@@ -13595,6 +13755,9 @@ function wrapUnavailable() {
13595
13755
  async callTool() {
13596
13756
  throw new Error("peer-MCP client unavailable");
13597
13757
  },
13758
+ async listTools() {
13759
+ throw new Error("peer-MCP client unavailable");
13760
+ },
13598
13761
  [Symbol.dispose]() {
13599
13762
  }
13600
13763
  };
@@ -13605,49 +13768,155 @@ var init_mcp_clients = __esm({
13605
13768
  "use strict";
13606
13769
  init_esm_shims();
13607
13770
  PeerMcpRegistry = class {
13608
- clients = /* @__PURE__ */ new Map();
13771
+ entries = /* @__PURE__ */ new Map();
13609
13772
  clientFactory;
13610
13773
  constructor(clientFactory) {
13611
13774
  this.clientFactory = clientFactory;
13612
13775
  }
13613
13776
  get size() {
13614
- return this.clients.size;
13777
+ return this.entries.size;
13615
13778
  }
13616
13779
  /**
13617
13780
  * Boot every `[contracts.mcp_clients.<name>]` entry. Failures are
13618
13781
  * non-fatal: the name is recorded as unavailable and a WARN line is
13619
13782
  * written to stderr. Returns when all attempts have settled.
13783
+ *
13784
+ * On a successful connect we prime the tools cache via tools/list. A
13785
+ * tools/list failure does NOT mark the source unavailable — the
13786
+ * connection is live and callTool may still work — but the status
13787
+ * becomes "unreachable" so the UI can prompt a retry.
13620
13788
  */
13621
13789
  async start(configs) {
13622
13790
  for (const [name, cfg] of Object.entries(configs)) {
13791
+ await this.connectAndStore(name, cfg);
13792
+ }
13793
+ }
13794
+ get(name) {
13795
+ return this.entries.get(name)?.client;
13796
+ }
13797
+ /** All registered source names, in insertion order. */
13798
+ names() {
13799
+ return Array.from(this.entries.keys());
13800
+ }
13801
+ /** Cached metadata projection for one source; undefined if unknown. */
13802
+ getInfo(name) {
13803
+ const e = this.entries.get(name);
13804
+ if (e === void 0) return void 0;
13805
+ return {
13806
+ status: e.status,
13807
+ tools: e.tools,
13808
+ lastRefreshed: e.lastRefreshed,
13809
+ ...e.error !== void 0 ? { error: e.error } : {}
13810
+ };
13811
+ }
13812
+ /**
13813
+ * Register a new source at runtime: spawn + connect, then prime the
13814
+ * tools cache. Replaces any existing entry of the same name (the old
13815
+ * client is disposed first). Returns the resulting info projection.
13816
+ */
13817
+ async add(name, cfg) {
13818
+ const existing = this.entries.get(name);
13819
+ if (existing !== void 0) {
13623
13820
  try {
13624
- const { client, transport } = this.clientFactory ? await this.clientFactory(cfg) : await this.defaultConnect(cfg);
13625
- this.clients.set(name, wrapAvailable(client, transport));
13626
- } catch (err) {
13627
- const msg = err instanceof Error ? err.message : String(err);
13628
- process.stderr.write(
13629
- `[contracts] peer-MCP client '${name}' failed to start: ${msg}
13630
- `
13631
- );
13632
- this.clients.set(name, wrapUnavailable());
13821
+ existing.client[Symbol.dispose]();
13822
+ } catch {
13633
13823
  }
13634
13824
  }
13825
+ await this.connectAndStore(name, cfg);
13826
+ return this.getInfo(name);
13635
13827
  }
13636
- get(name) {
13637
- return this.clients.get(name);
13828
+ /**
13829
+ * Dispose a source and drop it from the registry. Idempotent —
13830
+ * removing an unknown name is a no-op that returns false.
13831
+ */
13832
+ remove(name) {
13833
+ const e = this.entries.get(name);
13834
+ if (e === void 0) return false;
13835
+ try {
13836
+ e.client[Symbol.dispose]();
13837
+ } catch (err) {
13838
+ const msg = err instanceof Error ? err.message : String(err);
13839
+ process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}
13840
+ `);
13841
+ }
13842
+ this.entries.delete(name);
13843
+ return true;
13844
+ }
13845
+ /**
13846
+ * Re-issue tools/list against the live client and refresh the cache.
13847
+ * Returns the updated info, or undefined if the name is unknown.
13848
+ *
13849
+ * If the client is currently unavailable this only updates the error;
13850
+ * re-spawning a failed source requires `add(name, cfg)` with the
13851
+ * config (the registry does not retain configs).
13852
+ */
13853
+ async refresh(name) {
13854
+ const e = this.entries.get(name);
13855
+ if (e === void 0) return void 0;
13856
+ if (!e.client.available) {
13857
+ e.status = "unavailable";
13858
+ return this.getInfo(name);
13859
+ }
13860
+ await this.primeTools(e);
13861
+ return this.getInfo(name);
13638
13862
  }
13639
13863
  /** Dispose every client and clear the internal map. Idempotent. */
13640
13864
  async shutdown() {
13641
- for (const c of this.clients.values()) {
13865
+ for (const e of this.entries.values()) {
13642
13866
  try {
13643
- c[Symbol.dispose]();
13867
+ e.client[Symbol.dispose]();
13644
13868
  } catch (err) {
13645
13869
  const msg = err instanceof Error ? err.message : String(err);
13646
13870
  process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}
13647
13871
  `);
13648
13872
  }
13649
13873
  }
13650
- this.clients.clear();
13874
+ this.entries.clear();
13875
+ }
13876
+ // ─── internals ─────────────────────────────────────────────────────────
13877
+ /** Connect (factory or default), store the entry, prime tools cache. */
13878
+ async connectAndStore(name, cfg) {
13879
+ try {
13880
+ const { client, transport } = this.clientFactory ? await this.clientFactory(cfg) : await this.defaultConnect(cfg);
13881
+ const entry = {
13882
+ client: wrapAvailable(client, transport),
13883
+ status: "connected",
13884
+ tools: [],
13885
+ lastRefreshed: null
13886
+ };
13887
+ this.entries.set(name, entry);
13888
+ await this.primeTools(entry);
13889
+ } catch (err) {
13890
+ const msg = err instanceof Error ? err.message : String(err);
13891
+ process.stderr.write(
13892
+ `[contracts] peer-MCP client '${name}' failed to start: ${msg}
13893
+ `
13894
+ );
13895
+ this.entries.set(name, {
13896
+ client: wrapUnavailable(),
13897
+ status: "unavailable",
13898
+ tools: [],
13899
+ lastRefreshed: null,
13900
+ error: msg
13901
+ });
13902
+ }
13903
+ }
13904
+ /**
13905
+ * Call tools/list and update the entry's cache + status. A failure
13906
+ * keeps the connection (status → "unreachable") rather than tearing it
13907
+ * down — the source is reachable for callTool even if discovery failed.
13908
+ */
13909
+ async primeTools(entry) {
13910
+ try {
13911
+ const tools = await entry.client.listTools();
13912
+ entry.tools = tools;
13913
+ entry.lastRefreshed = nowSeconds();
13914
+ entry.status = "connected";
13915
+ delete entry.error;
13916
+ } catch (err) {
13917
+ entry.status = "unreachable";
13918
+ entry.error = err instanceof Error ? err.message : String(err);
13919
+ }
13651
13920
  }
13652
13921
  async defaultConnect(cfg) {
13653
13922
  const transport = new StdioClientTransport({
@@ -13752,7 +14021,7 @@ var init_verbs = __esm({
13752
14021
  });
13753
14022
 
13754
14023
  // src/contracts/instantiate.ts
13755
- import { z as z16 } from "zod";
14024
+ import { z as z17 } from "zod";
13756
14025
  async function instantiateContract(deps, args2) {
13757
14026
  const parsed = deps.registry.get(args2.name);
13758
14027
  if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
@@ -13900,7 +14169,7 @@ async function instantiateContract(deps, args2) {
13900
14169
  };
13901
14170
  if (parsed.output_shape) {
13902
14171
  try {
13903
- const outputSchema = z16.fromJSONSchema(
14172
+ const outputSchema = z17.fromJSONSchema(
13904
14173
  parsed.output_shape
13905
14174
  );
13906
14175
  const check = outputSchema.safeParse(bundle);
@@ -13988,6 +14257,12 @@ var init_instantiate = __esm({
13988
14257
  });
13989
14258
 
13990
14259
  // src/contracts/describe.ts
14260
+ function glossFor(verb) {
14261
+ if (VERB_GLOSS[verb]) return VERB_GLOSS[verb];
14262
+ if (verb === "literal") return "Use a fixed inline value";
14263
+ if (verb.startsWith("mcp://")) return `Call an external tool (${verb})`;
14264
+ return verb;
14265
+ }
13991
14266
  function describeContract(deps, args2) {
13992
14267
  const parsed = deps.registry.get(args2.name);
13993
14268
  if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
@@ -14037,7 +14312,9 @@ function renderSummary(parsed) {
14037
14312
  lines.push("## Assembly");
14038
14313
  parsed.assembly.forEach((step, i) => {
14039
14314
  const argsRender = step.args ? `(${Object.keys(step.args).join(", ")})` : "()";
14040
- lines.push(`${i + 1}. **${step.as}** \u2190 \`${step.verb}${argsRender}\``);
14315
+ lines.push(
14316
+ `${i + 1}. **${step.as}** \u2014 ${glossFor(step.verb)} _(\`${step.verb}${argsRender}\`)_`
14317
+ );
14041
14318
  });
14042
14319
  lines.push("");
14043
14320
  }
@@ -14061,10 +14338,24 @@ function renderSummary(parsed) {
14061
14338
  }
14062
14339
  return lines.join("\n").trim() + "\n";
14063
14340
  }
14341
+ var VERB_GLOSS;
14064
14342
  var init_describe = __esm({
14065
14343
  "src/contracts/describe.ts"() {
14066
14344
  "use strict";
14067
14345
  init_esm_shims();
14346
+ VERB_GLOSS = {
14347
+ read_note: "Read a note's content",
14348
+ search_hybrid: "Search the vault (semantic + keyword)",
14349
+ search_sections: "Search for matching sections within notes",
14350
+ query_frontmatter: "Find notes by their properties (frontmatter)",
14351
+ expand: "Gather notes linked to the starting note (follow the graph)",
14352
+ cluster: "Group the gathered notes into related communities",
14353
+ recall: "Recall earlier agent observations from memory",
14354
+ compile_brief: "Compile the gathered notes into a brief",
14355
+ get_brief: "Fetch an already-compiled brief",
14356
+ list_backlinks: "List notes that link back to this one",
14357
+ get_outline: "Read a note's heading outline"
14358
+ };
14068
14359
  }
14069
14360
  });
14070
14361
 
@@ -14135,6 +14426,59 @@ var init_resources3 = __esm({
14135
14426
  }
14136
14427
  });
14137
14428
 
14429
+ // src/contracts/sources-resources.ts
14430
+ function readListSources(reg, configMeta) {
14431
+ const sources = [];
14432
+ for (const name of reg.names()) {
14433
+ const info = reg.getInfo(name);
14434
+ if (info === void 0) continue;
14435
+ const meta = configMeta[name];
14436
+ const entry = {
14437
+ name,
14438
+ transport: "stdio",
14439
+ command: meta?.command ?? "",
14440
+ args: meta?.args ?? [],
14441
+ status: info.status,
14442
+ tool_count: info.tools.length,
14443
+ last_refreshed: info.lastRefreshed
14444
+ };
14445
+ if (info.error !== void 0) entry.error = info.error;
14446
+ sources.push(entry);
14447
+ }
14448
+ return { sources };
14449
+ }
14450
+ function readSourceTools(reg, name) {
14451
+ const info = reg.getInfo(name);
14452
+ if (info === void 0) {
14453
+ return { error: `unknown source: ${name}` };
14454
+ }
14455
+ const out = {
14456
+ name,
14457
+ status: info.status,
14458
+ last_refreshed: info.lastRefreshed,
14459
+ tools: info.tools
14460
+ };
14461
+ if (info.error !== void 0) out.error = info.error;
14462
+ return out;
14463
+ }
14464
+ function readSourceTool(reg, name, toolName) {
14465
+ const info = reg.getInfo(name);
14466
+ if (info === void 0) {
14467
+ return { found: false, error: `unknown source: ${name}` };
14468
+ }
14469
+ const tool = info.tools.find((t) => t.name === toolName);
14470
+ if (tool === void 0) {
14471
+ return { found: false, error: `unknown tool: ${name}/${toolName}` };
14472
+ }
14473
+ return { found: true, name, tool };
14474
+ }
14475
+ var init_sources_resources = __esm({
14476
+ "src/contracts/sources-resources.ts"() {
14477
+ "use strict";
14478
+ init_esm_shims();
14479
+ }
14480
+ });
14481
+
14138
14482
  // src/contracts/index.ts
14139
14483
  var init_contracts = __esm({
14140
14484
  "src/contracts/index.ts"() {
@@ -14157,6 +14501,7 @@ var init_contracts = __esm({
14157
14501
  init_instantiate();
14158
14502
  init_describe();
14159
14503
  init_resources3();
14504
+ init_sources_resources();
14160
14505
  }
14161
14506
  });
14162
14507
 
@@ -14351,6 +14696,20 @@ async function serve(options = {}) {
14351
14696
  process.on("SIGTERM", () => {
14352
14697
  void shutdown().finally(() => process.exit(0));
14353
14698
  });
14699
+ let stdinClosing = false;
14700
+ const onStdinClose = (reason) => {
14701
+ if (stdinClosing) return;
14702
+ stdinClosing = true;
14703
+ process.stderr.write(
14704
+ `[vault-memory] stdin ${reason} \u2014 parent process gone; shutting down.
14705
+ `
14706
+ );
14707
+ setTimeout(() => {
14708
+ void shutdown().finally(() => process.exit(0));
14709
+ }, 500);
14710
+ };
14711
+ process.stdin.on("end", () => onStdinClose("end"));
14712
+ process.stdin.on("close", () => onStdinClose("close"));
14354
14713
  const server = new McpServer(
14355
14714
  { name: "vault-memory", version: VERSION },
14356
14715
  // Plan 02-06 (MEM-09): advertise `resources` capability so MCP clients
@@ -15396,6 +15755,86 @@ async function serve(options = {}) {
15396
15755
  };
15397
15756
  }
15398
15757
  );
15758
+ const sourceConfigMeta = () => {
15759
+ const out = {};
15760
+ for (const [name, cfg] of Object.entries(config.contracts.mcp_clients)) {
15761
+ out[name] = { command: cfg.command, args: cfg.args ?? [] };
15762
+ }
15763
+ return out;
15764
+ };
15765
+ server.registerResource(
15766
+ "sources",
15767
+ RESOURCE_URI_SOURCES,
15768
+ {
15769
+ title: "Peer MCP sources",
15770
+ description: "List peer MCP servers vault-memory connects to, with per-source status (connected/unavailable/unreachable), tool_count, and last_refreshed. vault-memory itself is not included. SOURCES-REGISTRY \xA75.1.",
15771
+ mimeType: "application/json"
15772
+ },
15773
+ async (uri) => ({
15774
+ contents: [
15775
+ {
15776
+ uri: uri.href,
15777
+ mimeType: "application/json",
15778
+ text: JSON.stringify(
15779
+ readListSources(peerMcpRegistry, sourceConfigMeta()),
15780
+ null,
15781
+ 2
15782
+ )
15783
+ }
15784
+ ]
15785
+ })
15786
+ );
15787
+ server.registerResource(
15788
+ "source-tools",
15789
+ new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools`, {
15790
+ list: void 0
15791
+ }),
15792
+ {
15793
+ title: "Peer MCP source tools",
15794
+ description: "List the cached tools/list for one peer MCP source. Empty when the source is not connected. SOURCES-REGISTRY \xA75.2.",
15795
+ mimeType: "application/json"
15796
+ },
15797
+ async (uri, variables) => {
15798
+ const name = String(variables.name ?? "");
15799
+ return {
15800
+ contents: [
15801
+ {
15802
+ uri: uri.href,
15803
+ mimeType: "application/json",
15804
+ text: JSON.stringify(readSourceTools(peerMcpRegistry, name), null, 2)
15805
+ }
15806
+ ]
15807
+ };
15808
+ }
15809
+ );
15810
+ server.registerResource(
15811
+ "source-tool",
15812
+ new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools/{tool}`, {
15813
+ list: void 0
15814
+ }),
15815
+ {
15816
+ title: "Peer MCP source tool",
15817
+ description: "Read a single tool's schema from one peer MCP source, inlined from the cached tools/list (no extra peer call). SOURCES-REGISTRY \xA75.3.",
15818
+ mimeType: "application/json"
15819
+ },
15820
+ async (uri, variables) => {
15821
+ const name = String(variables.name ?? "");
15822
+ const tool = String(variables.tool ?? "");
15823
+ return {
15824
+ contents: [
15825
+ {
15826
+ uri: uri.href,
15827
+ mimeType: "application/json",
15828
+ text: JSON.stringify(
15829
+ readSourceTool(peerMcpRegistry, name, tool),
15830
+ null,
15831
+ 2
15832
+ )
15833
+ }
15834
+ ]
15835
+ };
15836
+ }
15837
+ );
15399
15838
  const rel08Vaults = RESOURCES.find((r) => r.name === "vaults");
15400
15839
  const rel08Models = RESOURCES.find((r) => r.name === "models");
15401
15840
  const rel08Recent = RESOURCES.find((r) => r.name === "recent");
@@ -15708,6 +16147,9 @@ async function serve(options = {}) {
15708
16147
  // sees, so the plugin's `suppress_contract_write` call and the
15709
16148
  // change-feed handler observe the same entries.
15710
16149
  suppression,
16150
+ // SOURCES-REGISTRY.md §6 (Stage 2) — live registry for refresh_source
16151
+ // + unset_mcp_client. The singleton booted above.
16152
+ sourceRegistry: peerMcpRegistry,
15711
16153
  notifier: (notification) => {
15712
16154
  server.server.notification(notification);
15713
16155
  }