@owrede/vault-memory 2.0.0-rc.2 → 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
  });
@@ -3525,7 +3624,7 @@ var init_retry = __esm({
3525
3624
  });
3526
3625
 
3527
3626
  // src/ollama/client.ts
3528
- import { z as z8 } from "zod";
3627
+ import { z as z9 } from "zod";
3529
3628
  function isRetryable(err) {
3530
3629
  if (err instanceof OllamaHttpError) {
3531
3630
  return err.status >= 500 && err.status < 600;
@@ -3548,26 +3647,26 @@ var init_client = __esm({
3548
3647
  DEFAULT_BATCH_SIZE = 10;
3549
3648
  DEFAULT_TIMEOUT_MS = 3e4;
3550
3649
  DEFAULT_RETRIES = 3;
3551
- EmbedResponseSchema = z8.object({
3552
- embeddings: z8.array(z8.array(z8.number())),
3553
- model: z8.string().optional()
3650
+ EmbedResponseSchema = z9.object({
3651
+ embeddings: z9.array(z9.array(z9.number())),
3652
+ model: z9.string().optional()
3554
3653
  });
3555
- TagsResponseSchema = z8.object({
3556
- models: z8.array(
3557
- z8.object({
3558
- name: z8.string()
3654
+ TagsResponseSchema = z9.object({
3655
+ models: z9.array(
3656
+ z9.object({
3657
+ name: z9.string()
3559
3658
  })
3560
3659
  )
3561
3660
  });
3562
- ChatResponseSchema = z8.object({
3563
- model: z8.string(),
3564
- message: z8.object({
3565
- role: z8.literal("assistant"),
3566
- content: z8.string()
3661
+ ChatResponseSchema = z9.object({
3662
+ model: z9.string(),
3663
+ message: z9.object({
3664
+ role: z9.literal("assistant"),
3665
+ content: z9.string()
3567
3666
  }),
3568
- done: z8.boolean().optional(),
3569
- total_duration: z8.number().optional(),
3570
- eval_count: z8.number().optional()
3667
+ done: z9.boolean().optional(),
3668
+ total_duration: z9.number().optional(),
3669
+ eval_count: z9.number().optional()
3571
3670
  });
3572
3671
  OllamaHttpError = class extends Error {
3573
3672
  status;
@@ -5434,7 +5533,16 @@ var init_obsidian_fs = __esm({
5434
5533
  if (since !== void 0 && mtime < since) continue;
5435
5534
  const body = await fs4.readFile(abs, "utf-8");
5436
5535
  const hash = computeBodyHash(body);
5437
- 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 };
5438
5546
  yielded++;
5439
5547
  }
5440
5548
  }
@@ -7395,7 +7503,7 @@ var init_validator = __esm({
7395
7503
  });
7396
7504
 
7397
7505
  // src/memory/contract/default-v1.ts
7398
- import { z as z9 } from "zod";
7506
+ import { z as z10 } from "zod";
7399
7507
  var requiredKeys, baseShape, DEFAULT_MEMORY_V1;
7400
7508
  var init_default_v1 = __esm({
7401
7509
  "src/memory/contract/default-v1.ts"() {
@@ -7410,15 +7518,15 @@ var init_default_v1 = __esm({
7410
7518
  "superseded_by",
7411
7519
  "type"
7412
7520
  ];
7413
- baseShape = z9.object({
7414
- source: z9.enum(["agent", "user", "imported"]),
7415
- confidence: z9.enum(["direct", "inferred", "uncertain"]),
7416
- evidence: z9.array(z9.string()),
7417
- status: z9.enum(["active", "superseded", "archived"]).default("active"),
7418
- observed_at: z9.string().datetime({ offset: true }),
7419
- superseded_by: z9.string().nullable().default(null),
7420
- type: z9.string().min(1),
7421
- 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()
7422
7530
  }).passthrough().superRefine((data, ctx) => {
7423
7531
  if (data.status === "superseded") {
7424
7532
  if (data.superseded_by === null || data.superseded_by === void 0) {
@@ -7451,7 +7559,7 @@ var init_default_v1 = __esm({
7451
7559
  });
7452
7560
 
7453
7561
  // src/memory/contract/default-brief-v1.ts
7454
- import { z as z10 } from "zod";
7562
+ import { z as z11 } from "zod";
7455
7563
  var requiredKeys2, baseShape2, DEFAULT_BRIEF_V1;
7456
7564
  var init_default_brief_v1 = __esm({
7457
7565
  "src/memory/contract/default-brief-v1.ts"() {
@@ -7473,29 +7581,29 @@ var init_default_brief_v1 = __esm({
7473
7581
  "compiled_at",
7474
7582
  "source_hashes"
7475
7583
  ];
7476
- baseShape2 = z10.object({
7584
+ baseShape2 = z11.object({
7477
7585
  // ── Base shape inherited from default-v1 ────────────────────────
7478
- source: z10.enum(["agent", "user", "imported"]),
7479
- confidence: z10.enum(["direct", "inferred", "uncertain"]),
7480
- 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()),
7481
7589
  // ── Status enum WIDENED for briefs: + "stale" ──────────────────
7482
- status: z10.enum(["active", "stale", "superseded", "archived"]).default("active"),
7483
- observed_at: z10.string().datetime({ offset: true }),
7484
- superseded_by: z10.string().nullable().default(null),
7485
- type: z10.string().min(1),
7486
- 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(),
7487
7595
  // ── Brief-specific properties (D-11 brief shape) ───────────────
7488
- target: z10.string().min(1),
7596
+ target: z11.string().min(1),
7489
7597
  /**
7490
7598
  * Brief purpose — free text but bounded at 500 chars so
7491
7599
  * `list_briefs` stays scannable. Lower bound `min(1)` matches
7492
7600
  * BRF-03 "no empty purpose".
7493
7601
  */
7494
- purpose: z10.string().min(1).max(500),
7602
+ purpose: z11.string().min(1).max(500),
7495
7603
  /** DocId list of all sources the brief was compiled from. */
7496
- compiled_from: z10.array(z10.string()).min(1),
7604
+ compiled_from: z11.array(z11.string()).min(1),
7497
7605
  /** ISO-8601 datetime with offset (mirrors observed_at). */
7498
- compiled_at: z10.string().datetime({ offset: true }),
7606
+ compiled_at: z11.string().datetime({ offset: true }),
7499
7607
  /**
7500
7608
  * Record<ChunkId, BriefSourceHash> — staleness contract. The map
7501
7609
  * key is the public ChunkId (`<DocId>#chunk-<7-hex>`); the value
@@ -7503,12 +7611,12 @@ var init_default_brief_v1 = __esm({
7503
7611
  * the cross-field invariant below only REQUIRES it on stale; the
7504
7612
  * validator still rejects `status: "stale"` writes that omit it.
7505
7613
  */
7506
- source_hashes: z10.record(z10.string(), z10.string()).optional(),
7614
+ source_hashes: z11.record(z11.string(), z11.string()).optional(),
7507
7615
  /**
7508
7616
  * Daemon-computed list of source DocIds whose hashes have
7509
7617
  * diverged. Populated when `status` flips to `"stale"`.
7510
7618
  */
7511
- changed_sources: z10.array(z10.string()).optional()
7619
+ changed_sources: z11.array(z11.string()).optional()
7512
7620
  }).passthrough().superRefine((data, ctx) => {
7513
7621
  if (data.status === "superseded") {
7514
7622
  if (data.superseded_by === null || data.superseded_by === void 0) {
@@ -7578,36 +7686,36 @@ var init_contract_yaml_read = __esm({
7578
7686
  });
7579
7687
 
7580
7688
  // src/memory/contract/schema.ts
7581
- import { z as z11 } from "zod";
7689
+ import { z as z12 } from "zod";
7582
7690
  var PropertyRuleSchema, CrossFieldRuleSchema, MemoryContractYamlSchema;
7583
7691
  var init_schema2 = __esm({
7584
7692
  "src/memory/contract/schema.ts"() {
7585
7693
  "use strict";
7586
7694
  init_esm_shims();
7587
- PropertyRuleSchema = z11.object({
7588
- type: z11.enum(["string", "datetime", "array", "doc_id", "number", "boolean", "reference", "date"]),
7589
- allowed: z11.array(z11.string()).optional(),
7590
- default: z11.unknown().optional(),
7591
- items: z11.object({ type: z11.string() }).optional(),
7592
- 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(),
7593
7701
  /** When true, the property accepts `null` as a sentinel value (in
7594
7702
  * addition to whatever `type` says). Used for required-but-null-by-
7595
7703
  * default properties like `superseded_by` on active observations. */
7596
- nullable: z11.boolean().optional()
7704
+ nullable: z12.boolean().optional()
7597
7705
  });
7598
- CrossFieldRuleSchema = z11.object({
7599
- when: z11.string(),
7600
- require: z11.string()
7706
+ CrossFieldRuleSchema = z12.object({
7707
+ when: z12.string(),
7708
+ require: z12.string()
7601
7709
  });
7602
- MemoryContractYamlSchema = z11.object({
7603
- name: z11.string().min(1),
7604
- version: z11.string().default("1.0"),
7605
- required_properties: z11.record(z11.string(), PropertyRuleSchema),
7606
- optional_properties: z11.record(z11.string(), PropertyRuleSchema).default({}),
7607
- cross_field_rules: z11.array(CrossFieldRuleSchema).default([]),
7608
- naming: z11.object({
7609
- strategy: z11.enum(["caller-provided", "date-slug", "adapter-assigned"]),
7610
- 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()
7611
7719
  })
7612
7720
  });
7613
7721
  }
@@ -7615,7 +7723,7 @@ var init_schema2 = __esm({
7615
7723
 
7616
7724
  // src/memory/contract/loader.ts
7617
7725
  import { parse as parseYaml } from "yaml";
7618
- import { z as z12 } from "zod";
7726
+ import { z as z13 } from "zod";
7619
7727
  function __cacheContract(name, contract) {
7620
7728
  contractCache.set(name, contract);
7621
7729
  }
@@ -9213,7 +9321,7 @@ var init_memory_stats = __esm({
9213
9321
  });
9214
9322
 
9215
9323
  // src/memory/resources/index.ts
9216
- 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;
9217
9325
  var init_resources = __esm({
9218
9326
  "src/memory/resources/index.ts"() {
9219
9327
  "use strict";
@@ -9225,6 +9333,7 @@ var init_resources = __esm({
9225
9333
  RESOURCE_URI_LIST_BRIEFS = "vault-memory://briefs";
9226
9334
  RESOURCE_URI_LIST_CONTRACTS = "vault-memory://contracts";
9227
9335
  RESOURCE_URI_LIST_CONTRACT_VERBS = "vault-memory://contract-verbs";
9336
+ RESOURCE_URI_SOURCES = "vault-memory://sources";
9228
9337
  RESOURCE_URI_VAULTS = "vault-memory://vaults";
9229
9338
  RESOURCE_URI_MODELS = "vault-memory://models";
9230
9339
  RESOURCE_URI_RECENT = "vault-memory://recent";
@@ -9286,6 +9395,25 @@ var init_resource_registry = __esm({
9286
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.",
9287
9396
  mimeType: "application/json"
9288
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
+ },
9289
9417
  // ─── Phase 8 (Plan 08-05 / REL-08) — promoted from v1 tools ─────────────
9290
9418
  {
9291
9419
  name: "vaults",
@@ -11797,11 +11925,11 @@ var init_obsidian_fs3 = __esm({
11797
11925
  });
11798
11926
 
11799
11927
  // src/tool-registry.ts
11800
- import { z as z13 } from "zod";
11928
+ import { z as z14 } from "zod";
11801
11929
  function buildToolSchema(name) {
11802
11930
  const builder = SCHEMA_BUILDERS[name];
11803
11931
  if (builder) return builder();
11804
- return z13.object(TOOL_SCHEMAS[name]);
11932
+ return z14.object(TOOL_SCHEMAS[name]);
11805
11933
  }
11806
11934
  var TOOLS, DOC_ID_PATTERN2, PredicateSchema, TOOL_SCHEMAS, SCHEMA_BUILDERS;
11807
11935
  var init_tool_registry = __esm({
@@ -12674,243 +12802,243 @@ var init_tool_registry = __esm({
12674
12802
  }
12675
12803
  ];
12676
12804
  DOC_ID_PATTERN2 = /^[a-z][a-z0-9-]*:\/\/[^/]+\/.+$/;
12677
- PredicateSchema = z13.union([
12678
- z13.string(),
12679
- z13.number(),
12680
- z13.boolean(),
12681
- z13.null(),
12682
- z13.object({ $in: z13.array(z13.union([z13.string(), z13.number(), z13.boolean(), z13.null()])) }),
12683
- z13.object({ $exists: z13.boolean() }),
12684
- 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()]) })
12685
12813
  ]);
12686
12814
  TOOL_SCHEMAS = {
12687
12815
  list_vaults: {},
12688
12816
  read_note: {
12689
- vault: z13.string(),
12690
- path: z13.string()
12817
+ vault: z14.string(),
12818
+ path: z14.string()
12691
12819
  },
12692
12820
  search_semantic: {
12693
- query: z13.string().min(1),
12694
- vaults: z13.array(z13.string()).optional(),
12695
- top_k: z13.number().int().positive().max(100).optional().default(10),
12696
- 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()
12697
12825
  },
12698
12826
  search_text: {
12699
- query: z13.string().min(1),
12700
- vaults: z13.array(z13.string()).optional(),
12701
- top_k: z13.number().int().positive().max(100).optional().default(10),
12702
- 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()
12703
12831
  },
12704
12832
  search_hybrid: {
12705
- query: z13.string().min(1),
12706
- vaults: z13.array(z13.string()).optional(),
12707
- top_k: z13.number().int().positive().max(100).optional().default(10),
12708
- rrf_k: z13.number().int().positive().max(1e3).optional().default(60),
12709
- exclude_paths: z13.array(z13.string()).optional(),
12710
- 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),
12711
12839
  // Phase 3 / 03-05 additive params — D-07, D-08, ASM-07, ASM-08.
12712
12840
  // All `.optional()` with defaults that vanish when unset, so v1
12713
12841
  // callers see no behavior change.
12714
- recency_weight: z13.number().optional().default(0),
12715
- authority_weight: z13.number().optional().default(0),
12716
- half_life_days: z13.number().positive().optional().default(30),
12717
- 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),
12718
12846
  // ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──
12719
12847
  // Nested under a single optional `expand` object per D-15. When
12720
12848
  // omitted, hybridSearch behavior is byte-identical to v1 (the
12721
12849
  // guard `if (opts.expand && opts.expandDeps && ...)` at the end of
12722
12850
  // `src/search/hybrid.ts` short-circuits entirely). The literal-
12723
12851
  // union for `hops` enforces the D-05 hop cap at the boundary.
12724
- expand: z13.object({
12725
- hops: z13.union([z13.literal(1), z13.literal(2)]),
12726
- direction: z13.enum(["forward", "backward", "both"]).optional(),
12727
- 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()
12728
12856
  }).optional()
12729
12857
  },
12730
12858
  list_backlinks: {
12731
- vault: z13.string(),
12732
- path: z13.string()
12859
+ vault: z14.string(),
12860
+ path: z14.string()
12733
12861
  },
12734
12862
  list_forward_links: {
12735
- vault: z13.string(),
12736
- path: z13.string(),
12737
- include_broken: z13.boolean().optional().default(true)
12863
+ vault: z14.string(),
12864
+ path: z14.string(),
12865
+ include_broken: z14.boolean().optional().default(true)
12738
12866
  },
12739
12867
  find_broken_links: {
12740
- vault: z13.string()
12868
+ vault: z14.string()
12741
12869
  },
12742
12870
  query_frontmatter: {
12743
- vault: z13.string(),
12744
- where: z13.record(z13.string(), PredicateSchema),
12745
- 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)
12746
12874
  },
12747
12875
  write_note: {
12748
- vault: z13.string(),
12749
- path: z13.string(),
12750
- content: z13.string(),
12751
- frontmatter: z13.record(z13.string(), z13.unknown()).nullable().optional(),
12752
- expected_hash: z13.string().optional(),
12753
- 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()
12754
12882
  },
12755
12883
  update_frontmatter: {
12756
- vault: z13.string(),
12757
- path: z13.string(),
12758
- merge: z13.record(z13.string(), z13.unknown()),
12759
- expected_hash: z13.string().optional(),
12760
- 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()
12761
12889
  },
12762
12890
  delete_note: {
12763
- vault: z13.string(),
12764
- path: z13.string(),
12765
- expected_hash: z13.string(),
12766
- client_id: z13.string().optional()
12891
+ vault: z14.string(),
12892
+ path: z14.string(),
12893
+ expected_hash: z14.string(),
12894
+ client_id: z14.string().optional()
12767
12895
  },
12768
12896
  audit_log: {
12769
- vault: z13.string(),
12770
- note_path: z13.string().optional(),
12771
- op: z13.enum(["create", "update", "delete"]).optional(),
12772
- since: z13.number().int().nonnegative().optional(),
12773
- 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),
12774
12902
  // Plan 02-06 (MEM-08): additive optional filter. The MCP tool's
12775
12903
  // `description` string is INTENTIONALLY unchanged — Phase 1 byte-identity
12776
12904
  // is preserved. New capability is documented in docs/tools/audit_log.md.
12777
- is_memory_sink_write: z13.boolean().optional()
12905
+ is_memory_sink_write: z14.boolean().optional()
12778
12906
  },
12779
12907
  list_models: {
12780
- vault: z13.string()
12908
+ vault: z14.string()
12781
12909
  },
12782
12910
  start_shadow_index: {
12783
- vault: z13.string(),
12784
- model: z13.string().min(1),
12785
- 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()
12786
12914
  },
12787
12915
  switch_active_model: {
12788
- vault: z13.string(),
12789
- model_name: z13.string().min(1)
12916
+ vault: z14.string(),
12917
+ model_name: z14.string().min(1)
12790
12918
  },
12791
12919
  vacuum_embeddings: {
12792
- vault: z13.string()
12920
+ vault: z14.string()
12793
12921
  },
12794
12922
  index_runs: {
12795
- vault: z13.string(),
12796
- 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)
12797
12925
  },
12798
12926
  search: {
12799
- query: z13.string().min(1),
12800
- 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)
12801
12929
  },
12802
12930
  fetch: {
12803
- id: z13.string().min(1)
12931
+ id: z14.string().min(1)
12804
12932
  },
12805
12933
  vault_stats: {
12806
- vault: z13.string().optional()
12934
+ vault: z14.string().optional()
12807
12935
  },
12808
12936
  recent_notes: {
12809
- vault: z13.string().optional(),
12810
- limit: z13.number().int().positive().max(200).optional().default(20),
12811
- 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()
12812
12940
  },
12813
12941
  suggest_frontmatter: {
12814
- vault: z13.string(),
12815
- path: z13.string().optional(),
12816
- content: z13.string().optional(),
12817
- title: z13.string().optional(),
12818
- 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()
12819
12947
  },
12820
12948
  // ── Phase 2 memory tools (Plan 02-04) ───────────────────────────────────
12821
12949
  record_observation: {
12822
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12823
- claim: z13.string().min(1).describe("Short natural-language statement of the observation (becomes title + body)"),
12824
- evidence: z13.array(z13.string()).describe("DocIds or quoted source spans supporting the claim; empty array allowed"),
12825
- confidence: z13.enum(["direct", "inferred", "uncertain"]).describe("How the agent arrived at this claim"),
12826
- 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(
12827
12955
  "Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision')"
12828
12956
  ),
12829
- sink: z13.string().min(1).optional().describe(
12957
+ sink: z14.string().min(1).optional().describe(
12830
12958
  "Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to the vault's default sink."
12831
12959
  ),
12832
- properties: z13.record(z13.string(), z13.unknown()).optional().describe(
12960
+ properties: z14.record(z14.string(), z14.unknown()).optional().describe(
12833
12961
  "Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins)"
12834
12962
  )
12835
12963
  },
12836
12964
  supersede: {
12837
- doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("DocId of the document being superseded"),
12838
- replacement_doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("DocId of the replacement document"),
12839
- 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")
12840
12968
  },
12841
12969
  // ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ───────────────────
12842
12970
  compile_brief: {
12843
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12844
- target: z13.string().min(1).describe("Stable cross-version handle for the brief (e.g. 'atlas-q3')"),
12845
- 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)"),
12846
- purpose: z13.string().min(1).max(500).describe("Free-form purpose; bounded so list_briefs stays scannable"),
12847
- max_tokens: z13.number().int().positive().optional().default(2e3).describe("Hint for the LLM ladder; default 2000"),
12848
- prepared_text: z13.string().min(1).optional().describe("D-10 tier 3 fallback when no LLM is reachable \u2014 verbatim body to stitch in"),
12849
- 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")
12850
12978
  },
12851
12979
  get_brief: {
12852
- vault: z13.string().min(1).describe("Vault name (registered in [vaults] config block)"),
12853
- target: z13.string().min(1).describe("Stable cross-version handle for the brief"),
12854
- max_age_days: z13.number().int().nonnegative().optional().describe("Reject briefs older than this many days unless allow_stale=true"),
12855
- 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(
12856
12984
  "When true, return briefs flagged stale or too_old with annotation rather than null"
12857
12985
  )
12858
12986
  },
12859
12987
  // ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ────────────────────────
12860
12988
  get_outline: {
12861
- doc_id: z13.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the document"),
12862
- 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)")
12863
12991
  },
12864
12992
  // ── Phase 3 assembly tools (Plan 03-03) ─────────────────────────────────
12865
12993
  search_sections: {
12866
- query: z13.string().min(1),
12867
- limit: z13.number().int().positive().max(50).optional().default(10),
12868
- 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(),
12869
12997
  // Forward-compat with slice 03-05's authority/staleness rescore.
12870
12998
  // Accepted today; ignored by the controller until 03-05 wires the
12871
12999
  // forwarding inside hybridSearch. See 03-03-DEVIATIONS.md.
12872
- recency_weight: z13.number().min(0).optional().default(0),
12873
- authority_weight: z13.number().min(0).optional().default(0),
12874
- 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)
12875
13003
  },
12876
13004
  // ── Phase 2 memory tools (Plan 02-05) ───────────────────────────────────
12877
13005
  recall: {
12878
- query: z13.string().min(1).describe("Natural-language query; routes through hybrid (semantic + BM25) search"),
12879
- 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(
12880
13008
  "Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1)"
12881
13009
  ),
12882
- types: z13.array(z13.string().min(1)).optional().describe("Restrict to docs whose `type` property is in this set"),
12883
- max_age_days: z13.number().int().positive().optional().describe("Exclude docs whose `observed_at` is older than this many days"),
12884
- 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(
12885
13013
  "Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to all configured sinks."
12886
13014
  ),
12887
- limit: z13.number().int().positive().max(200).optional().describe("Maximum results AFTER filter+sort; default 20"),
12888
- 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")
12889
13017
  },
12890
13018
  // ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ────────────────────────
12891
13019
  get_document_bundle: {
12892
- 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"),
12893
13021
  // v2.0.0 accepts only depth:1. The literal pin guarantees Zod
12894
13022
  // rejects any other value at the boundary so the controller does
12895
13023
  // not need to clamp. Phase 4 may widen additively (z.union of
12896
13024
  // literals, or `z.number().int().min(1).max(2)`).
12897
- depth: z13.literal(1).optional().default(1).describe("Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen."),
12898
- 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)")
12899
13027
  },
12900
13028
  // ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────
12901
13029
  expand: {
12902
- 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(
12903
13031
  "1+ opaque DocIds (e.g. obsidian-fs://<vault>/<path>) \u2014 seeds of the BFS."
12904
13032
  ),
12905
13033
  // Hops hard-capped at 2 (D-05) via Zod literal union — `hops: 3`
12906
13034
  // is rejected at the boundary; the controller does not clamp.
12907
- hops: z13.union([z13.literal(1), z13.literal(2)]).describe("Hop cap (1 or 2). v2.0.0 hard-caps at 2."),
12908
- direction: z13.enum(["forward", "backward", "both"]).optional().default("both").describe("Edge traversal direction; default 'both'."),
12909
- edge_types: z13.array(z13.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional().describe("Optional filter on edge types; default = all four types."),
12910
- 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(
12911
13039
  "Strict-equality predicate on document properties (e.g. {type: 'Project'})."
12912
13040
  ),
12913
- include_superseded: z13.boolean().optional().default(false).describe(
13041
+ include_superseded: z14.boolean().optional().default(false).describe(
12914
13042
  "When false (default), docs whose properties.status === 'superseded' are dropped."
12915
13043
  )
12916
13044
  },
@@ -12924,50 +13052,50 @@ var init_tool_registry = __esm({
12924
13052
  // works. The runtime path goes through `buildToolSchema("cluster")`
12925
13053
  // which calls the SCHEMA_BUILDERS entry.
12926
13054
  cluster: {
12927
- query: z13.string().min(1).optional(),
12928
- 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(),
12929
13057
  // CR-02: `vault` scopes the `query` path on multi-vault setups so
12930
13058
  // search_hybrid is not silently restricted to whichever vault
12931
13059
  // sorts first in VaultManager insertion order. Optional at the
12932
13060
  // schema layer; the runtime cluster() entry enforces the
12933
13061
  // multi-vault-without-vault error.
12934
- vault: z13.string().min(1).optional(),
12935
- method: z13.literal("edge-community"),
12936
- query_top_k: z13.number().int().positive().max(200).optional().default(50),
12937
- 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)
12938
13066
  },
12939
13067
  // ── Phase 3 assembly tools (Plan 03-06) ─────────────────────────────────
12940
13068
  assemble_dossier: {
12941
- type: z13.string().min(1).describe(
13069
+ type: z14.string().min(1).describe(
12942
13070
  "Exact-match value for properties.type on the anchor document (D-03 \u2014 no fuzzy match)"
12943
13071
  ),
12944
- key: z13.string().min(1).describe(
13072
+ key: z14.string().min(1).describe(
12945
13073
  "Candidate key \u2014 matches the document's title OR any entry in properties.aliases (D-04)"
12946
13074
  ),
12947
- 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")
12948
13076
  },
12949
13077
  // ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────
12950
13078
  register_contracts_as_tools: {
12951
- 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")
12952
13080
  },
12953
13081
  // ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────
12954
13082
  describe_contract: {
12955
- name: z13.string().min(1).describe("Registered contract name (see register_contracts_as_tools)"),
12956
- 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")
12957
13085
  },
12958
13086
  // ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────
12959
13087
  instantiate_contract: {
12960
- name: z13.string().min(1).describe("Registered contract name"),
12961
- inputs: z13.record(z13.string(), z13.unknown()).optional().default({}).describe("Contract inputs; validated against the contract's inputZodSchema"),
12962
- source_overrides: z13.record(z13.string(), z13.string()).optional().describe("Override declared source handles by handle name"),
12963
- 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(
12964
13092
  "Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c)."
12965
13093
  ),
12966
- 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")
12967
13095
  }
12968
13096
  };
12969
13097
  SCHEMA_BUILDERS = {
12970
- 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, {
12971
13099
  message: "suggest_frontmatter requires either `path` or `content`"
12972
13100
  }),
12973
13101
  // Plan 04-05 / D-15a — EXACTLY ONE of `query` or `seed_doc_ids` must
@@ -12976,7 +13104,7 @@ var init_tool_registry = __esm({
12976
13104
  // so this Zod refinement is the early-rejection gate at the MCP
12977
13105
  // boundary (cluster's internal validator handles the same case for
12978
13106
  // direct callers that bypass Zod).
12979
- cluster: () => z13.object(TOOL_SCHEMAS.cluster).refine(
13107
+ cluster: () => z14.object(TOOL_SCHEMAS.cluster).refine(
12980
13108
  (v) => v.query !== void 0 && v.seed_doc_ids === void 0 || v.query === void 0 && v.seed_doc_ids !== void 0,
12981
13109
  {
12982
13110
  message: "cluster requires EXACTLY ONE of `query` or `seed_doc_ids` (D-15a mutual exclusion)"
@@ -13071,7 +13199,7 @@ var init_json_schema_ref = __esm({
13071
13199
  });
13072
13200
 
13073
13201
  // src/contracts/input-schema.ts
13074
- import { z as z14 } from "zod";
13202
+ import { z as z15 } from "zod";
13075
13203
  function buildInputSchema(yamlInputs, required = []) {
13076
13204
  const resolvedProperties = resolveRefs(yamlInputs);
13077
13205
  const jsonSchema = {
@@ -13080,7 +13208,7 @@ function buildInputSchema(yamlInputs, required = []) {
13080
13208
  required,
13081
13209
  additionalProperties: false
13082
13210
  };
13083
- const zodSchema = z14.fromJSONSchema(
13211
+ const zodSchema = z15.fromJSONSchema(
13084
13212
  jsonSchema
13085
13213
  );
13086
13214
  return { zodSchema, jsonSchema };
@@ -13166,7 +13294,7 @@ var init_audit4 = __esm({
13166
13294
  });
13167
13295
 
13168
13296
  // src/contracts/schema.ts
13169
- import { z as z15 } from "zod";
13297
+ import { z as z16 } from "zod";
13170
13298
  var BASELINE_VERBS, MCP_VERB_RE, VerbSchema, StepSchema, HandleDeclSchema, WriteBackSchema, ContractFileSchema;
13171
13299
  var init_schema4 = __esm({
13172
13300
  "src/contracts/schema.ts"() {
@@ -13186,40 +13314,40 @@ var init_schema4 = __esm({
13186
13314
  "read_note"
13187
13315
  ];
13188
13316
  MCP_VERB_RE = /^mcp:\/\/[a-z][a-z0-9_-]*\/[a-z][a-z0-9_-]*$/;
13189
- VerbSchema = z15.union([
13190
- z15.enum([...BASELINE_VERBS, "literal"]),
13191
- z15.string().regex(MCP_VERB_RE)
13317
+ VerbSchema = z16.union([
13318
+ z16.enum([...BASELINE_VERBS, "literal"]),
13319
+ z16.string().regex(MCP_VERB_RE)
13192
13320
  ]);
13193
- StepSchema = z15.object({
13194
- 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"),
13195
13323
  verb: VerbSchema.describe(
13196
13324
  "Closed enum + literal + mcp:// extension (D-A2a / C-1)"
13197
13325
  ),
13198
- args: z15.record(z15.string(), z15.unknown()).optional(),
13199
- value: z15.unknown().optional()
13326
+ args: z16.record(z16.string(), z16.unknown()).optional(),
13327
+ value: z16.unknown().optional()
13200
13328
  }).describe("One step in an assembly: array");
13201
- HandleDeclSchema = z15.object({
13202
- handle: z15.string().min(1),
13203
- required: z15.boolean().default(true)
13329
+ HandleDeclSchema = z16.object({
13330
+ handle: z16.string().min(1),
13331
+ required: z16.boolean().default(true)
13204
13332
  }).describe("Source or sink handle declaration (D-A4a)");
13205
- WriteBackSchema = z15.object({
13206
- sink: z15.string().min(1).describe("Template expression OR literal sink handle"),
13207
- document_kind: z15.enum(["brief", "observation", "custom"]),
13208
- properties: z15.record(z15.string(), z15.unknown()).default({}),
13209
- 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")
13210
13338
  }).describe(
13211
13339
  "DeliveryAdapter.write chokepoint \u2014 only ground-truth DocId source (C-3)"
13212
13340
  );
13213
- ContractFileSchema = z15.object({
13214
- version: z15.literal(1).describe("v2.0.0 supports version 1 only; v2.x may extend additively"),
13215
- 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"),
13216
- description: z15.string().default(""),
13217
- inputs: z15.record(z15.string(), z15.unknown()).default({}),
13218
- required: z15.array(z15.string()).default([]),
13219
- sources: z15.record(z15.string(), HandleDeclSchema).default({}),
13220
- sinks: z15.record(z15.string(), HandleDeclSchema).default({}),
13221
- assembly: z15.array(StepSchema).min(1, "assembly must contain at least one step"),
13222
- 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(),
13223
13351
  write_back: WriteBackSchema.optional()
13224
13352
  }).superRefine((data, ctx) => {
13225
13353
  const aliases = /* @__PURE__ */ new Set();
@@ -13571,6 +13699,9 @@ var init_templates = __esm({
13571
13699
  // src/contracts/mcp-clients.ts
13572
13700
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
13573
13701
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
13702
+ function nowSeconds() {
13703
+ return Math.floor(Date.now() / 1e3);
13704
+ }
13574
13705
  function wrapAvailable(client, transport) {
13575
13706
  return {
13576
13707
  available: true,
@@ -13592,6 +13723,27 @@ function wrapAvailable(client, transport) {
13592
13723
  }
13593
13724
  return res;
13594
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
+ },
13595
13747
  [Symbol.dispose]() {
13596
13748
  transport.close();
13597
13749
  }
@@ -13603,6 +13755,9 @@ function wrapUnavailable() {
13603
13755
  async callTool() {
13604
13756
  throw new Error("peer-MCP client unavailable");
13605
13757
  },
13758
+ async listTools() {
13759
+ throw new Error("peer-MCP client unavailable");
13760
+ },
13606
13761
  [Symbol.dispose]() {
13607
13762
  }
13608
13763
  };
@@ -13613,49 +13768,155 @@ var init_mcp_clients = __esm({
13613
13768
  "use strict";
13614
13769
  init_esm_shims();
13615
13770
  PeerMcpRegistry = class {
13616
- clients = /* @__PURE__ */ new Map();
13771
+ entries = /* @__PURE__ */ new Map();
13617
13772
  clientFactory;
13618
13773
  constructor(clientFactory) {
13619
13774
  this.clientFactory = clientFactory;
13620
13775
  }
13621
13776
  get size() {
13622
- return this.clients.size;
13777
+ return this.entries.size;
13623
13778
  }
13624
13779
  /**
13625
13780
  * Boot every `[contracts.mcp_clients.<name>]` entry. Failures are
13626
13781
  * non-fatal: the name is recorded as unavailable and a WARN line is
13627
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.
13628
13788
  */
13629
13789
  async start(configs) {
13630
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) {
13631
13820
  try {
13632
- const { client, transport } = this.clientFactory ? await this.clientFactory(cfg) : await this.defaultConnect(cfg);
13633
- this.clients.set(name, wrapAvailable(client, transport));
13634
- } catch (err) {
13635
- const msg = err instanceof Error ? err.message : String(err);
13636
- process.stderr.write(
13637
- `[contracts] peer-MCP client '${name}' failed to start: ${msg}
13638
- `
13639
- );
13640
- this.clients.set(name, wrapUnavailable());
13821
+ existing.client[Symbol.dispose]();
13822
+ } catch {
13641
13823
  }
13642
13824
  }
13825
+ await this.connectAndStore(name, cfg);
13826
+ return this.getInfo(name);
13643
13827
  }
13644
- get(name) {
13645
- 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);
13646
13862
  }
13647
13863
  /** Dispose every client and clear the internal map. Idempotent. */
13648
13864
  async shutdown() {
13649
- for (const c of this.clients.values()) {
13865
+ for (const e of this.entries.values()) {
13650
13866
  try {
13651
- c[Symbol.dispose]();
13867
+ e.client[Symbol.dispose]();
13652
13868
  } catch (err) {
13653
13869
  const msg = err instanceof Error ? err.message : String(err);
13654
13870
  process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}
13655
13871
  `);
13656
13872
  }
13657
13873
  }
13658
- 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
+ }
13659
13920
  }
13660
13921
  async defaultConnect(cfg) {
13661
13922
  const transport = new StdioClientTransport({
@@ -13760,7 +14021,7 @@ var init_verbs = __esm({
13760
14021
  });
13761
14022
 
13762
14023
  // src/contracts/instantiate.ts
13763
- import { z as z16 } from "zod";
14024
+ import { z as z17 } from "zod";
13764
14025
  async function instantiateContract(deps, args2) {
13765
14026
  const parsed = deps.registry.get(args2.name);
13766
14027
  if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
@@ -13908,7 +14169,7 @@ async function instantiateContract(deps, args2) {
13908
14169
  };
13909
14170
  if (parsed.output_shape) {
13910
14171
  try {
13911
- const outputSchema = z16.fromJSONSchema(
14172
+ const outputSchema = z17.fromJSONSchema(
13912
14173
  parsed.output_shape
13913
14174
  );
13914
14175
  const check = outputSchema.safeParse(bundle);
@@ -13996,6 +14257,12 @@ var init_instantiate = __esm({
13996
14257
  });
13997
14258
 
13998
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
+ }
13999
14266
  function describeContract(deps, args2) {
14000
14267
  const parsed = deps.registry.get(args2.name);
14001
14268
  if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
@@ -14045,7 +14312,9 @@ function renderSummary(parsed) {
14045
14312
  lines.push("## Assembly");
14046
14313
  parsed.assembly.forEach((step, i) => {
14047
14314
  const argsRender = step.args ? `(${Object.keys(step.args).join(", ")})` : "()";
14048
- 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
+ );
14049
14318
  });
14050
14319
  lines.push("");
14051
14320
  }
@@ -14069,10 +14338,24 @@ function renderSummary(parsed) {
14069
14338
  }
14070
14339
  return lines.join("\n").trim() + "\n";
14071
14340
  }
14341
+ var VERB_GLOSS;
14072
14342
  var init_describe = __esm({
14073
14343
  "src/contracts/describe.ts"() {
14074
14344
  "use strict";
14075
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
+ };
14076
14359
  }
14077
14360
  });
14078
14361
 
@@ -14143,6 +14426,59 @@ var init_resources3 = __esm({
14143
14426
  }
14144
14427
  });
14145
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
+
14146
14482
  // src/contracts/index.ts
14147
14483
  var init_contracts = __esm({
14148
14484
  "src/contracts/index.ts"() {
@@ -14165,6 +14501,7 @@ var init_contracts = __esm({
14165
14501
  init_instantiate();
14166
14502
  init_describe();
14167
14503
  init_resources3();
14504
+ init_sources_resources();
14168
14505
  }
14169
14506
  });
14170
14507
 
@@ -14359,6 +14696,20 @@ async function serve(options = {}) {
14359
14696
  process.on("SIGTERM", () => {
14360
14697
  void shutdown().finally(() => process.exit(0));
14361
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"));
14362
14713
  const server = new McpServer(
14363
14714
  { name: "vault-memory", version: VERSION },
14364
14715
  // Plan 02-06 (MEM-09): advertise `resources` capability so MCP clients
@@ -15404,6 +15755,86 @@ async function serve(options = {}) {
15404
15755
  };
15405
15756
  }
15406
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
+ );
15407
15838
  const rel08Vaults = RESOURCES.find((r) => r.name === "vaults");
15408
15839
  const rel08Models = RESOURCES.find((r) => r.name === "models");
15409
15840
  const rel08Recent = RESOURCES.find((r) => r.name === "recent");
@@ -15716,6 +16147,9 @@ async function serve(options = {}) {
15716
16147
  // sees, so the plugin's `suppress_contract_write` call and the
15717
16148
  // change-feed handler observe the same entries.
15718
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,
15719
16153
  notifier: (notification) => {
15720
16154
  server.server.notification(notification);
15721
16155
  }