@owrede/vault-memory 2.0.0-rc.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1314 -0
- package/dist/cli.js +737 -269
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
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
|
});
|
|
@@ -2957,7 +3056,7 @@ var init_sections = __esm({
|
|
|
2957
3056
|
constructor(db) {
|
|
2958
3057
|
this.db = db;
|
|
2959
3058
|
this._insert = db.prepare(`
|
|
2960
|
-
INSERT INTO sections
|
|
3059
|
+
INSERT OR IGNORE INTO sections
|
|
2961
3060
|
(note_id, anchor, heading_path, heading_text, level,
|
|
2962
3061
|
parent_id, ord, chunk_id_first, chunk_id_last, created_at)
|
|
2963
3062
|
VALUES
|
|
@@ -3027,6 +3126,33 @@ var init_sections = __esm({
|
|
|
3027
3126
|
tx(rows);
|
|
3028
3127
|
return ids;
|
|
3029
3128
|
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Insert one section, collision-safe. Returns the id of the row that
|
|
3131
|
+
* now owns (note_id, anchor): the freshly inserted row, or — when a
|
|
3132
|
+
* same-anchor sibling already won the unique slot — that surviving
|
|
3133
|
+
* row's id (so callers can resolve parent_id linkage). Mirrors the
|
|
3134
|
+
* backfill behavior in src/sections/backfill.ts. The live indexer
|
|
3135
|
+
* uses this instead of `insertMany` so duplicate-anchor sibling
|
|
3136
|
+
* headings can't abort the whole index run
|
|
3137
|
+
* (see ISSUE-indexer-duplicate-anchor.md).
|
|
3138
|
+
*/
|
|
3139
|
+
insertOneResolving(r) {
|
|
3140
|
+
const info = this._insert.run({
|
|
3141
|
+
note_id: r.note_id,
|
|
3142
|
+
anchor: r.anchor,
|
|
3143
|
+
heading_path: r.heading_path,
|
|
3144
|
+
heading_text: r.heading_text,
|
|
3145
|
+
level: r.level,
|
|
3146
|
+
parent_id: r.parent_id,
|
|
3147
|
+
ord: r.ord,
|
|
3148
|
+
chunk_id_first: r.chunk_id_first,
|
|
3149
|
+
chunk_id_last: r.chunk_id_last,
|
|
3150
|
+
created_at: Date.now()
|
|
3151
|
+
});
|
|
3152
|
+
if (info.changes > 0) return Number(info.lastInsertRowid);
|
|
3153
|
+
const existing = this._getByAnchor.get(r.note_id, r.anchor);
|
|
3154
|
+
return existing ? Number(existing.id) : null;
|
|
3155
|
+
}
|
|
3030
3156
|
deleteByNote(noteId) {
|
|
3031
3157
|
return this._deleteByNote.run(noteId).changes;
|
|
3032
3158
|
}
|
|
@@ -3525,7 +3651,7 @@ var init_retry = __esm({
|
|
|
3525
3651
|
});
|
|
3526
3652
|
|
|
3527
3653
|
// src/ollama/client.ts
|
|
3528
|
-
import { z as
|
|
3654
|
+
import { z as z9 } from "zod";
|
|
3529
3655
|
function isRetryable(err) {
|
|
3530
3656
|
if (err instanceof OllamaHttpError) {
|
|
3531
3657
|
return err.status >= 500 && err.status < 600;
|
|
@@ -3548,26 +3674,26 @@ var init_client = __esm({
|
|
|
3548
3674
|
DEFAULT_BATCH_SIZE = 10;
|
|
3549
3675
|
DEFAULT_TIMEOUT_MS = 3e4;
|
|
3550
3676
|
DEFAULT_RETRIES = 3;
|
|
3551
|
-
EmbedResponseSchema =
|
|
3552
|
-
embeddings:
|
|
3553
|
-
model:
|
|
3677
|
+
EmbedResponseSchema = z9.object({
|
|
3678
|
+
embeddings: z9.array(z9.array(z9.number())),
|
|
3679
|
+
model: z9.string().optional()
|
|
3554
3680
|
});
|
|
3555
|
-
TagsResponseSchema =
|
|
3556
|
-
models:
|
|
3557
|
-
|
|
3558
|
-
name:
|
|
3681
|
+
TagsResponseSchema = z9.object({
|
|
3682
|
+
models: z9.array(
|
|
3683
|
+
z9.object({
|
|
3684
|
+
name: z9.string()
|
|
3559
3685
|
})
|
|
3560
3686
|
)
|
|
3561
3687
|
});
|
|
3562
|
-
ChatResponseSchema =
|
|
3563
|
-
model:
|
|
3564
|
-
message:
|
|
3565
|
-
role:
|
|
3566
|
-
content:
|
|
3688
|
+
ChatResponseSchema = z9.object({
|
|
3689
|
+
model: z9.string(),
|
|
3690
|
+
message: z9.object({
|
|
3691
|
+
role: z9.literal("assistant"),
|
|
3692
|
+
content: z9.string()
|
|
3567
3693
|
}),
|
|
3568
|
-
done:
|
|
3569
|
-
total_duration:
|
|
3570
|
-
eval_count:
|
|
3694
|
+
done: z9.boolean().optional(),
|
|
3695
|
+
total_duration: z9.number().optional(),
|
|
3696
|
+
eval_count: z9.number().optional()
|
|
3571
3697
|
});
|
|
3572
3698
|
OllamaHttpError = class extends Error {
|
|
3573
3699
|
status;
|
|
@@ -5434,7 +5560,16 @@ var init_obsidian_fs = __esm({
|
|
|
5434
5560
|
if (since !== void 0 && mtime < since) continue;
|
|
5435
5561
|
const body = await fs4.readFile(abs, "utf-8");
|
|
5436
5562
|
const hash = computeBodyHash(body);
|
|
5437
|
-
|
|
5563
|
+
let id;
|
|
5564
|
+
try {
|
|
5565
|
+
id = this.pathToDocId(rel);
|
|
5566
|
+
} catch (err) {
|
|
5567
|
+
console.error(
|
|
5568
|
+
`[obsidian-fs:${this.vault.name}] skipping un-addressable file ${JSON.stringify(rel)}: ${err instanceof Error ? err.message : String(err)}`
|
|
5569
|
+
);
|
|
5570
|
+
continue;
|
|
5571
|
+
}
|
|
5572
|
+
yield { id, mtime, hash };
|
|
5438
5573
|
yielded++;
|
|
5439
5574
|
}
|
|
5440
5575
|
}
|
|
@@ -6244,7 +6379,14 @@ async function indexVault(vault, options) {
|
|
|
6244
6379
|
chunkIdFragment: computeChunkIdFragment(c.text)
|
|
6245
6380
|
}));
|
|
6246
6381
|
const chunkIds = vault.db.chunks.insertBatch(noteId, chunkInputs);
|
|
6247
|
-
|
|
6382
|
+
try {
|
|
6383
|
+
buildSectionsForNote(vault, noteId, parsed.content, chunkIds);
|
|
6384
|
+
} catch (err) {
|
|
6385
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
6386
|
+
console.error(
|
|
6387
|
+
`[indexer:${vault.config.name}] section build failed for ${parsed.relativePath}: ${message} \u2014 skipping sections for this note`
|
|
6388
|
+
);
|
|
6389
|
+
}
|
|
6248
6390
|
const embedResult = await options.ollama.embed({
|
|
6249
6391
|
model: options.embeddingModel,
|
|
6250
6392
|
texts: chunks.map((c) => c.text)
|
|
@@ -6409,15 +6551,15 @@ function buildSectionsForNote(vault, noteId, content, insertedChunkIds) {
|
|
|
6409
6551
|
chunk_id_first: pair.first,
|
|
6410
6552
|
chunk_id_last: pair.last
|
|
6411
6553
|
};
|
|
6412
|
-
|
|
6413
|
-
insertedIds.push(ids[0]);
|
|
6554
|
+
insertedIds.push(vault.db.sections.insertOneResolving(row));
|
|
6414
6555
|
}
|
|
6415
6556
|
return insertedIds.length;
|
|
6416
6557
|
}
|
|
6417
6558
|
function mapChunksToSections(chunks, sectionRanges) {
|
|
6418
|
-
const out = sectionRanges.map(
|
|
6419
|
-
|
|
6420
|
-
|
|
6559
|
+
const out = sectionRanges.map(() => ({
|
|
6560
|
+
first: null,
|
|
6561
|
+
last: null
|
|
6562
|
+
}));
|
|
6421
6563
|
for (const chunk of chunks) {
|
|
6422
6564
|
const offset = chunk.start_offset;
|
|
6423
6565
|
let chosenIdx = null;
|
|
@@ -7395,7 +7537,7 @@ var init_validator = __esm({
|
|
|
7395
7537
|
});
|
|
7396
7538
|
|
|
7397
7539
|
// src/memory/contract/default-v1.ts
|
|
7398
|
-
import { z as
|
|
7540
|
+
import { z as z10 } from "zod";
|
|
7399
7541
|
var requiredKeys, baseShape, DEFAULT_MEMORY_V1;
|
|
7400
7542
|
var init_default_v1 = __esm({
|
|
7401
7543
|
"src/memory/contract/default-v1.ts"() {
|
|
@@ -7410,15 +7552,15 @@ var init_default_v1 = __esm({
|
|
|
7410
7552
|
"superseded_by",
|
|
7411
7553
|
"type"
|
|
7412
7554
|
];
|
|
7413
|
-
baseShape =
|
|
7414
|
-
source:
|
|
7415
|
-
confidence:
|
|
7416
|
-
evidence:
|
|
7417
|
-
status:
|
|
7418
|
-
observed_at:
|
|
7419
|
-
superseded_by:
|
|
7420
|
-
type:
|
|
7421
|
-
superseded_reason:
|
|
7555
|
+
baseShape = z10.object({
|
|
7556
|
+
source: z10.enum(["agent", "user", "imported"]),
|
|
7557
|
+
confidence: z10.enum(["direct", "inferred", "uncertain"]),
|
|
7558
|
+
evidence: z10.array(z10.string()),
|
|
7559
|
+
status: z10.enum(["active", "superseded", "archived"]).default("active"),
|
|
7560
|
+
observed_at: z10.string().datetime({ offset: true }),
|
|
7561
|
+
superseded_by: z10.string().nullable().default(null),
|
|
7562
|
+
type: z10.string().min(1),
|
|
7563
|
+
superseded_reason: z10.string().optional()
|
|
7422
7564
|
}).passthrough().superRefine((data, ctx) => {
|
|
7423
7565
|
if (data.status === "superseded") {
|
|
7424
7566
|
if (data.superseded_by === null || data.superseded_by === void 0) {
|
|
@@ -7451,7 +7593,7 @@ var init_default_v1 = __esm({
|
|
|
7451
7593
|
});
|
|
7452
7594
|
|
|
7453
7595
|
// src/memory/contract/default-brief-v1.ts
|
|
7454
|
-
import { z as
|
|
7596
|
+
import { z as z11 } from "zod";
|
|
7455
7597
|
var requiredKeys2, baseShape2, DEFAULT_BRIEF_V1;
|
|
7456
7598
|
var init_default_brief_v1 = __esm({
|
|
7457
7599
|
"src/memory/contract/default-brief-v1.ts"() {
|
|
@@ -7473,29 +7615,29 @@ var init_default_brief_v1 = __esm({
|
|
|
7473
7615
|
"compiled_at",
|
|
7474
7616
|
"source_hashes"
|
|
7475
7617
|
];
|
|
7476
|
-
baseShape2 =
|
|
7618
|
+
baseShape2 = z11.object({
|
|
7477
7619
|
// ── Base shape inherited from default-v1 ────────────────────────
|
|
7478
|
-
source:
|
|
7479
|
-
confidence:
|
|
7480
|
-
evidence:
|
|
7620
|
+
source: z11.enum(["agent", "user", "imported"]),
|
|
7621
|
+
confidence: z11.enum(["direct", "inferred", "uncertain"]),
|
|
7622
|
+
evidence: z11.array(z11.string()),
|
|
7481
7623
|
// ── Status enum WIDENED for briefs: + "stale" ──────────────────
|
|
7482
|
-
status:
|
|
7483
|
-
observed_at:
|
|
7484
|
-
superseded_by:
|
|
7485
|
-
type:
|
|
7486
|
-
superseded_reason:
|
|
7624
|
+
status: z11.enum(["active", "stale", "superseded", "archived"]).default("active"),
|
|
7625
|
+
observed_at: z11.string().datetime({ offset: true }),
|
|
7626
|
+
superseded_by: z11.string().nullable().default(null),
|
|
7627
|
+
type: z11.string().min(1),
|
|
7628
|
+
superseded_reason: z11.string().optional(),
|
|
7487
7629
|
// ── Brief-specific properties (D-11 brief shape) ───────────────
|
|
7488
|
-
target:
|
|
7630
|
+
target: z11.string().min(1),
|
|
7489
7631
|
/**
|
|
7490
7632
|
* Brief purpose — free text but bounded at 500 chars so
|
|
7491
7633
|
* `list_briefs` stays scannable. Lower bound `min(1)` matches
|
|
7492
7634
|
* BRF-03 "no empty purpose".
|
|
7493
7635
|
*/
|
|
7494
|
-
purpose:
|
|
7636
|
+
purpose: z11.string().min(1).max(500),
|
|
7495
7637
|
/** DocId list of all sources the brief was compiled from. */
|
|
7496
|
-
compiled_from:
|
|
7638
|
+
compiled_from: z11.array(z11.string()).min(1),
|
|
7497
7639
|
/** ISO-8601 datetime with offset (mirrors observed_at). */
|
|
7498
|
-
compiled_at:
|
|
7640
|
+
compiled_at: z11.string().datetime({ offset: true }),
|
|
7499
7641
|
/**
|
|
7500
7642
|
* Record<ChunkId, BriefSourceHash> — staleness contract. The map
|
|
7501
7643
|
* key is the public ChunkId (`<DocId>#chunk-<7-hex>`); the value
|
|
@@ -7503,12 +7645,12 @@ var init_default_brief_v1 = __esm({
|
|
|
7503
7645
|
* the cross-field invariant below only REQUIRES it on stale; the
|
|
7504
7646
|
* validator still rejects `status: "stale"` writes that omit it.
|
|
7505
7647
|
*/
|
|
7506
|
-
source_hashes:
|
|
7648
|
+
source_hashes: z11.record(z11.string(), z11.string()).optional(),
|
|
7507
7649
|
/**
|
|
7508
7650
|
* Daemon-computed list of source DocIds whose hashes have
|
|
7509
7651
|
* diverged. Populated when `status` flips to `"stale"`.
|
|
7510
7652
|
*/
|
|
7511
|
-
changed_sources:
|
|
7653
|
+
changed_sources: z11.array(z11.string()).optional()
|
|
7512
7654
|
}).passthrough().superRefine((data, ctx) => {
|
|
7513
7655
|
if (data.status === "superseded") {
|
|
7514
7656
|
if (data.superseded_by === null || data.superseded_by === void 0) {
|
|
@@ -7578,36 +7720,36 @@ var init_contract_yaml_read = __esm({
|
|
|
7578
7720
|
});
|
|
7579
7721
|
|
|
7580
7722
|
// src/memory/contract/schema.ts
|
|
7581
|
-
import { z as
|
|
7723
|
+
import { z as z12 } from "zod";
|
|
7582
7724
|
var PropertyRuleSchema, CrossFieldRuleSchema, MemoryContractYamlSchema;
|
|
7583
7725
|
var init_schema2 = __esm({
|
|
7584
7726
|
"src/memory/contract/schema.ts"() {
|
|
7585
7727
|
"use strict";
|
|
7586
7728
|
init_esm_shims();
|
|
7587
|
-
PropertyRuleSchema =
|
|
7588
|
-
type:
|
|
7589
|
-
allowed:
|
|
7590
|
-
default:
|
|
7591
|
-
items:
|
|
7592
|
-
min_length:
|
|
7729
|
+
PropertyRuleSchema = z12.object({
|
|
7730
|
+
type: z12.enum(["string", "datetime", "array", "doc_id", "number", "boolean", "reference", "date"]),
|
|
7731
|
+
allowed: z12.array(z12.string()).optional(),
|
|
7732
|
+
default: z12.unknown().optional(),
|
|
7733
|
+
items: z12.object({ type: z12.string() }).optional(),
|
|
7734
|
+
min_length: z12.number().optional(),
|
|
7593
7735
|
/** When true, the property accepts `null` as a sentinel value (in
|
|
7594
7736
|
* addition to whatever `type` says). Used for required-but-null-by-
|
|
7595
7737
|
* default properties like `superseded_by` on active observations. */
|
|
7596
|
-
nullable:
|
|
7738
|
+
nullable: z12.boolean().optional()
|
|
7597
7739
|
});
|
|
7598
|
-
CrossFieldRuleSchema =
|
|
7599
|
-
when:
|
|
7600
|
-
require:
|
|
7740
|
+
CrossFieldRuleSchema = z12.object({
|
|
7741
|
+
when: z12.string(),
|
|
7742
|
+
require: z12.string()
|
|
7601
7743
|
});
|
|
7602
|
-
MemoryContractYamlSchema =
|
|
7603
|
-
name:
|
|
7604
|
-
version:
|
|
7605
|
-
required_properties:
|
|
7606
|
-
optional_properties:
|
|
7607
|
-
cross_field_rules:
|
|
7608
|
-
naming:
|
|
7609
|
-
strategy:
|
|
7610
|
-
pattern:
|
|
7744
|
+
MemoryContractYamlSchema = z12.object({
|
|
7745
|
+
name: z12.string().min(1),
|
|
7746
|
+
version: z12.string().default("1.0"),
|
|
7747
|
+
required_properties: z12.record(z12.string(), PropertyRuleSchema),
|
|
7748
|
+
optional_properties: z12.record(z12.string(), PropertyRuleSchema).default({}),
|
|
7749
|
+
cross_field_rules: z12.array(CrossFieldRuleSchema).default([]),
|
|
7750
|
+
naming: z12.object({
|
|
7751
|
+
strategy: z12.enum(["caller-provided", "date-slug", "adapter-assigned"]),
|
|
7752
|
+
pattern: z12.string().optional()
|
|
7611
7753
|
})
|
|
7612
7754
|
});
|
|
7613
7755
|
}
|
|
@@ -7615,7 +7757,7 @@ var init_schema2 = __esm({
|
|
|
7615
7757
|
|
|
7616
7758
|
// src/memory/contract/loader.ts
|
|
7617
7759
|
import { parse as parseYaml } from "yaml";
|
|
7618
|
-
import { z as
|
|
7760
|
+
import { z as z13 } from "zod";
|
|
7619
7761
|
function __cacheContract(name, contract) {
|
|
7620
7762
|
contractCache.set(name, contract);
|
|
7621
7763
|
}
|
|
@@ -9213,7 +9355,7 @@ var init_memory_stats = __esm({
|
|
|
9213
9355
|
});
|
|
9214
9356
|
|
|
9215
9357
|
// 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;
|
|
9358
|
+
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
9359
|
var init_resources = __esm({
|
|
9218
9360
|
"src/memory/resources/index.ts"() {
|
|
9219
9361
|
"use strict";
|
|
@@ -9225,6 +9367,7 @@ var init_resources = __esm({
|
|
|
9225
9367
|
RESOURCE_URI_LIST_BRIEFS = "vault-memory://briefs";
|
|
9226
9368
|
RESOURCE_URI_LIST_CONTRACTS = "vault-memory://contracts";
|
|
9227
9369
|
RESOURCE_URI_LIST_CONTRACT_VERBS = "vault-memory://contract-verbs";
|
|
9370
|
+
RESOURCE_URI_SOURCES = "vault-memory://sources";
|
|
9228
9371
|
RESOURCE_URI_VAULTS = "vault-memory://vaults";
|
|
9229
9372
|
RESOURCE_URI_MODELS = "vault-memory://models";
|
|
9230
9373
|
RESOURCE_URI_RECENT = "vault-memory://recent";
|
|
@@ -9286,6 +9429,25 @@ var init_resource_registry = __esm({
|
|
|
9286
9429
|
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
9430
|
mimeType: "application/json"
|
|
9288
9431
|
},
|
|
9432
|
+
// ─── SOURCES-REGISTRY.md §5 (Stage 2) — peer-MCP source discovery ───────
|
|
9433
|
+
{
|
|
9434
|
+
name: "sources",
|
|
9435
|
+
uriTemplate: "vault-memory://sources",
|
|
9436
|
+
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.",
|
|
9437
|
+
mimeType: "application/json"
|
|
9438
|
+
},
|
|
9439
|
+
{
|
|
9440
|
+
name: "source-tools",
|
|
9441
|
+
uriTemplate: "vault-memory://sources/{name}/tools",
|
|
9442
|
+
description: "List the cached tools/list for one peer MCP source. Empty when the source is not connected. SOURCES-REGISTRY \xA75.2.",
|
|
9443
|
+
mimeType: "application/json"
|
|
9444
|
+
},
|
|
9445
|
+
{
|
|
9446
|
+
name: "source-tool",
|
|
9447
|
+
uriTemplate: "vault-memory://sources/{name}/tools/{tool}",
|
|
9448
|
+
description: "Read a single tool's schema from one peer MCP source, inlined from the cached tools/list. SOURCES-REGISTRY \xA75.3.",
|
|
9449
|
+
mimeType: "application/json"
|
|
9450
|
+
},
|
|
9289
9451
|
// ─── Phase 8 (Plan 08-05 / REL-08) — promoted from v1 tools ─────────────
|
|
9290
9452
|
{
|
|
9291
9453
|
name: "vaults",
|
|
@@ -11797,11 +11959,11 @@ var init_obsidian_fs3 = __esm({
|
|
|
11797
11959
|
});
|
|
11798
11960
|
|
|
11799
11961
|
// src/tool-registry.ts
|
|
11800
|
-
import { z as
|
|
11962
|
+
import { z as z14 } from "zod";
|
|
11801
11963
|
function buildToolSchema(name) {
|
|
11802
11964
|
const builder = SCHEMA_BUILDERS[name];
|
|
11803
11965
|
if (builder) return builder();
|
|
11804
|
-
return
|
|
11966
|
+
return z14.object(TOOL_SCHEMAS[name]);
|
|
11805
11967
|
}
|
|
11806
11968
|
var TOOLS, DOC_ID_PATTERN2, PredicateSchema, TOOL_SCHEMAS, SCHEMA_BUILDERS;
|
|
11807
11969
|
var init_tool_registry = __esm({
|
|
@@ -12674,243 +12836,243 @@ var init_tool_registry = __esm({
|
|
|
12674
12836
|
}
|
|
12675
12837
|
];
|
|
12676
12838
|
DOC_ID_PATTERN2 = /^[a-z][a-z0-9-]*:\/\/[^/]+\/.+$/;
|
|
12677
|
-
PredicateSchema =
|
|
12678
|
-
|
|
12679
|
-
|
|
12680
|
-
|
|
12681
|
-
|
|
12682
|
-
|
|
12683
|
-
|
|
12684
|
-
|
|
12839
|
+
PredicateSchema = z14.union([
|
|
12840
|
+
z14.string(),
|
|
12841
|
+
z14.number(),
|
|
12842
|
+
z14.boolean(),
|
|
12843
|
+
z14.null(),
|
|
12844
|
+
z14.object({ $in: z14.array(z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()])) }),
|
|
12845
|
+
z14.object({ $exists: z14.boolean() }),
|
|
12846
|
+
z14.object({ $contains: z14.union([z14.string(), z14.number(), z14.boolean(), z14.null()]) })
|
|
12685
12847
|
]);
|
|
12686
12848
|
TOOL_SCHEMAS = {
|
|
12687
12849
|
list_vaults: {},
|
|
12688
12850
|
read_note: {
|
|
12689
|
-
vault:
|
|
12690
|
-
path:
|
|
12851
|
+
vault: z14.string(),
|
|
12852
|
+
path: z14.string()
|
|
12691
12853
|
},
|
|
12692
12854
|
search_semantic: {
|
|
12693
|
-
query:
|
|
12694
|
-
vaults:
|
|
12695
|
-
top_k:
|
|
12696
|
-
exclude_paths:
|
|
12855
|
+
query: z14.string().min(1),
|
|
12856
|
+
vaults: z14.array(z14.string()).optional(),
|
|
12857
|
+
top_k: z14.number().int().positive().max(100).optional().default(10),
|
|
12858
|
+
exclude_paths: z14.array(z14.string()).optional()
|
|
12697
12859
|
},
|
|
12698
12860
|
search_text: {
|
|
12699
|
-
query:
|
|
12700
|
-
vaults:
|
|
12701
|
-
top_k:
|
|
12702
|
-
exclude_paths:
|
|
12861
|
+
query: z14.string().min(1),
|
|
12862
|
+
vaults: z14.array(z14.string()).optional(),
|
|
12863
|
+
top_k: z14.number().int().positive().max(100).optional().default(10),
|
|
12864
|
+
exclude_paths: z14.array(z14.string()).optional()
|
|
12703
12865
|
},
|
|
12704
12866
|
search_hybrid: {
|
|
12705
|
-
query:
|
|
12706
|
-
vaults:
|
|
12707
|
-
top_k:
|
|
12708
|
-
rrf_k:
|
|
12709
|
-
exclude_paths:
|
|
12710
|
-
rerank:
|
|
12867
|
+
query: z14.string().min(1),
|
|
12868
|
+
vaults: z14.array(z14.string()).optional(),
|
|
12869
|
+
top_k: z14.number().int().positive().max(100).optional().default(10),
|
|
12870
|
+
rrf_k: z14.number().int().positive().max(1e3).optional().default(60),
|
|
12871
|
+
exclude_paths: z14.array(z14.string()).optional(),
|
|
12872
|
+
rerank: z14.boolean().optional().default(false),
|
|
12711
12873
|
// Phase 3 / 03-05 additive params — D-07, D-08, ASM-07, ASM-08.
|
|
12712
12874
|
// All `.optional()` with defaults that vanish when unset, so v1
|
|
12713
12875
|
// callers see no behavior change.
|
|
12714
|
-
recency_weight:
|
|
12715
|
-
authority_weight:
|
|
12716
|
-
half_life_days:
|
|
12717
|
-
include_superseded:
|
|
12876
|
+
recency_weight: z14.number().optional().default(0),
|
|
12877
|
+
authority_weight: z14.number().optional().default(0),
|
|
12878
|
+
half_life_days: z14.number().positive().optional().default(30),
|
|
12879
|
+
include_superseded: z14.boolean().optional().default(false),
|
|
12718
12880
|
// ── Phase 4 / 04-04 / GRA-03 (D-15): additive auto-expansion ──
|
|
12719
12881
|
// Nested under a single optional `expand` object per D-15. When
|
|
12720
12882
|
// omitted, hybridSearch behavior is byte-identical to v1 (the
|
|
12721
12883
|
// guard `if (opts.expand && opts.expandDeps && ...)` at the end of
|
|
12722
12884
|
// `src/search/hybrid.ts` short-circuits entirely). The literal-
|
|
12723
12885
|
// union for `hops` enforces the D-05 hop cap at the boundary.
|
|
12724
|
-
expand:
|
|
12725
|
-
hops:
|
|
12726
|
-
direction:
|
|
12727
|
-
edge_types:
|
|
12886
|
+
expand: z14.object({
|
|
12887
|
+
hops: z14.union([z14.literal(1), z14.literal(2)]),
|
|
12888
|
+
direction: z14.enum(["forward", "backward", "both"]).optional(),
|
|
12889
|
+
edge_types: z14.array(z14.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional()
|
|
12728
12890
|
}).optional()
|
|
12729
12891
|
},
|
|
12730
12892
|
list_backlinks: {
|
|
12731
|
-
vault:
|
|
12732
|
-
path:
|
|
12893
|
+
vault: z14.string(),
|
|
12894
|
+
path: z14.string()
|
|
12733
12895
|
},
|
|
12734
12896
|
list_forward_links: {
|
|
12735
|
-
vault:
|
|
12736
|
-
path:
|
|
12737
|
-
include_broken:
|
|
12897
|
+
vault: z14.string(),
|
|
12898
|
+
path: z14.string(),
|
|
12899
|
+
include_broken: z14.boolean().optional().default(true)
|
|
12738
12900
|
},
|
|
12739
12901
|
find_broken_links: {
|
|
12740
|
-
vault:
|
|
12902
|
+
vault: z14.string()
|
|
12741
12903
|
},
|
|
12742
12904
|
query_frontmatter: {
|
|
12743
|
-
vault:
|
|
12744
|
-
where:
|
|
12745
|
-
limit:
|
|
12905
|
+
vault: z14.string(),
|
|
12906
|
+
where: z14.record(z14.string(), PredicateSchema),
|
|
12907
|
+
limit: z14.number().int().positive().max(1e3).optional().default(100)
|
|
12746
12908
|
},
|
|
12747
12909
|
write_note: {
|
|
12748
|
-
vault:
|
|
12749
|
-
path:
|
|
12750
|
-
content:
|
|
12751
|
-
frontmatter:
|
|
12752
|
-
expected_hash:
|
|
12753
|
-
client_id:
|
|
12910
|
+
vault: z14.string(),
|
|
12911
|
+
path: z14.string(),
|
|
12912
|
+
content: z14.string(),
|
|
12913
|
+
frontmatter: z14.record(z14.string(), z14.unknown()).nullable().optional(),
|
|
12914
|
+
expected_hash: z14.string().optional(),
|
|
12915
|
+
client_id: z14.string().optional()
|
|
12754
12916
|
},
|
|
12755
12917
|
update_frontmatter: {
|
|
12756
|
-
vault:
|
|
12757
|
-
path:
|
|
12758
|
-
merge:
|
|
12759
|
-
expected_hash:
|
|
12760
|
-
client_id:
|
|
12918
|
+
vault: z14.string(),
|
|
12919
|
+
path: z14.string(),
|
|
12920
|
+
merge: z14.record(z14.string(), z14.unknown()),
|
|
12921
|
+
expected_hash: z14.string().optional(),
|
|
12922
|
+
client_id: z14.string().optional()
|
|
12761
12923
|
},
|
|
12762
12924
|
delete_note: {
|
|
12763
|
-
vault:
|
|
12764
|
-
path:
|
|
12765
|
-
expected_hash:
|
|
12766
|
-
client_id:
|
|
12925
|
+
vault: z14.string(),
|
|
12926
|
+
path: z14.string(),
|
|
12927
|
+
expected_hash: z14.string(),
|
|
12928
|
+
client_id: z14.string().optional()
|
|
12767
12929
|
},
|
|
12768
12930
|
audit_log: {
|
|
12769
|
-
vault:
|
|
12770
|
-
note_path:
|
|
12771
|
-
op:
|
|
12772
|
-
since:
|
|
12773
|
-
limit:
|
|
12931
|
+
vault: z14.string(),
|
|
12932
|
+
note_path: z14.string().optional(),
|
|
12933
|
+
op: z14.enum(["create", "update", "delete"]).optional(),
|
|
12934
|
+
since: z14.number().int().nonnegative().optional(),
|
|
12935
|
+
limit: z14.number().int().positive().max(1e3).optional().default(50),
|
|
12774
12936
|
// Plan 02-06 (MEM-08): additive optional filter. The MCP tool's
|
|
12775
12937
|
// `description` string is INTENTIONALLY unchanged — Phase 1 byte-identity
|
|
12776
12938
|
// is preserved. New capability is documented in docs/tools/audit_log.md.
|
|
12777
|
-
is_memory_sink_write:
|
|
12939
|
+
is_memory_sink_write: z14.boolean().optional()
|
|
12778
12940
|
},
|
|
12779
12941
|
list_models: {
|
|
12780
|
-
vault:
|
|
12942
|
+
vault: z14.string()
|
|
12781
12943
|
},
|
|
12782
12944
|
start_shadow_index: {
|
|
12783
|
-
vault:
|
|
12784
|
-
model:
|
|
12785
|
-
batch_size:
|
|
12945
|
+
vault: z14.string(),
|
|
12946
|
+
model: z14.string().min(1),
|
|
12947
|
+
batch_size: z14.number().int().positive().max(256).optional()
|
|
12786
12948
|
},
|
|
12787
12949
|
switch_active_model: {
|
|
12788
|
-
vault:
|
|
12789
|
-
model_name:
|
|
12950
|
+
vault: z14.string(),
|
|
12951
|
+
model_name: z14.string().min(1)
|
|
12790
12952
|
},
|
|
12791
12953
|
vacuum_embeddings: {
|
|
12792
|
-
vault:
|
|
12954
|
+
vault: z14.string()
|
|
12793
12955
|
},
|
|
12794
12956
|
index_runs: {
|
|
12795
|
-
vault:
|
|
12796
|
-
limit:
|
|
12957
|
+
vault: z14.string(),
|
|
12958
|
+
limit: z14.number().int().positive().max(200).optional().default(20)
|
|
12797
12959
|
},
|
|
12798
12960
|
search: {
|
|
12799
|
-
query:
|
|
12800
|
-
limit:
|
|
12961
|
+
query: z14.string().min(1),
|
|
12962
|
+
limit: z14.number().int().positive().max(50).optional().default(10)
|
|
12801
12963
|
},
|
|
12802
12964
|
fetch: {
|
|
12803
|
-
id:
|
|
12965
|
+
id: z14.string().min(1)
|
|
12804
12966
|
},
|
|
12805
12967
|
vault_stats: {
|
|
12806
|
-
vault:
|
|
12968
|
+
vault: z14.string().optional()
|
|
12807
12969
|
},
|
|
12808
12970
|
recent_notes: {
|
|
12809
|
-
vault:
|
|
12810
|
-
limit:
|
|
12811
|
-
since:
|
|
12971
|
+
vault: z14.string().optional(),
|
|
12972
|
+
limit: z14.number().int().positive().max(200).optional().default(20),
|
|
12973
|
+
since: z14.number().int().nonnegative().optional()
|
|
12812
12974
|
},
|
|
12813
12975
|
suggest_frontmatter: {
|
|
12814
|
-
vault:
|
|
12815
|
-
path:
|
|
12816
|
-
content:
|
|
12817
|
-
title:
|
|
12818
|
-
folder_hint:
|
|
12976
|
+
vault: z14.string(),
|
|
12977
|
+
path: z14.string().optional(),
|
|
12978
|
+
content: z14.string().optional(),
|
|
12979
|
+
title: z14.string().optional(),
|
|
12980
|
+
folder_hint: z14.string().optional()
|
|
12819
12981
|
},
|
|
12820
12982
|
// ── Phase 2 memory tools (Plan 02-04) ───────────────────────────────────
|
|
12821
12983
|
record_observation: {
|
|
12822
|
-
vault:
|
|
12823
|
-
claim:
|
|
12824
|
-
evidence:
|
|
12825
|
-
confidence:
|
|
12826
|
-
type:
|
|
12984
|
+
vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
|
|
12985
|
+
claim: z14.string().min(1).describe("Short natural-language statement of the observation (becomes title + body)"),
|
|
12986
|
+
evidence: z14.array(z14.string()).describe("DocIds or quoted source spans supporting the claim; empty array allowed"),
|
|
12987
|
+
confidence: z14.enum(["direct", "inferred", "uncertain"]).describe("How the agent arrived at this claim"),
|
|
12988
|
+
type: z14.string().min(1).describe(
|
|
12827
12989
|
"Observation type per the sink contract (e.g. 'observation', 'hypothesis', 'decision')"
|
|
12828
12990
|
),
|
|
12829
|
-
sink:
|
|
12991
|
+
sink: z14.string().min(1).optional().describe(
|
|
12830
12992
|
"Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to the vault's default sink."
|
|
12831
12993
|
),
|
|
12832
|
-
properties:
|
|
12994
|
+
properties: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
12833
12995
|
"Escape-hatch: contract-allowed extra properties; merged AFTER sugar args (caller wins)"
|
|
12834
12996
|
)
|
|
12835
12997
|
},
|
|
12836
12998
|
supersede: {
|
|
12837
|
-
doc_id:
|
|
12838
|
-
replacement_doc_id:
|
|
12839
|
-
reason:
|
|
12999
|
+
doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("DocId of the document being superseded"),
|
|
13000
|
+
replacement_doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("DocId of the replacement document"),
|
|
13001
|
+
reason: z14.string().min(1).describe("Why the old document is being retired; written to superseded_reason")
|
|
12840
13002
|
},
|
|
12841
13003
|
// ── Phase 5 brief tools (Plan 05-02 / BRF-03, BRF-04) ───────────────────
|
|
12842
13004
|
compile_brief: {
|
|
12843
|
-
vault:
|
|
12844
|
-
target:
|
|
12845
|
-
source_doc_ids:
|
|
12846
|
-
purpose:
|
|
12847
|
-
max_tokens:
|
|
12848
|
-
prepared_text:
|
|
12849
|
-
sink:
|
|
13005
|
+
vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
|
|
13006
|
+
target: z14.string().min(1).describe("Stable cross-version handle for the brief (e.g. 'atlas-q3')"),
|
|
13007
|
+
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)"),
|
|
13008
|
+
purpose: z14.string().min(1).max(500).describe("Free-form purpose; bounded so list_briefs stays scannable"),
|
|
13009
|
+
max_tokens: z14.number().int().positive().optional().default(2e3).describe("Hint for the LLM ladder; default 2000"),
|
|
13010
|
+
prepared_text: z14.string().min(1).optional().describe("D-10 tier 3 fallback when no LLM is reachable \u2014 verbatim body to stitch in"),
|
|
13011
|
+
sink: z14.string().min(1).optional().describe("Override the default `_memory/_briefs` sink")
|
|
12850
13012
|
},
|
|
12851
13013
|
get_brief: {
|
|
12852
|
-
vault:
|
|
12853
|
-
target:
|
|
12854
|
-
max_age_days:
|
|
12855
|
-
allow_stale:
|
|
13014
|
+
vault: z14.string().min(1).describe("Vault name (registered in [vaults] config block)"),
|
|
13015
|
+
target: z14.string().min(1).describe("Stable cross-version handle for the brief"),
|
|
13016
|
+
max_age_days: z14.number().int().nonnegative().optional().describe("Reject briefs older than this many days unless allow_stale=true"),
|
|
13017
|
+
allow_stale: z14.boolean().optional().default(false).describe(
|
|
12856
13018
|
"When true, return briefs flagged stale or too_old with annotation rather than null"
|
|
12857
13019
|
)
|
|
12858
13020
|
},
|
|
12859
13021
|
// ── Phase 3 assembly tools (Plan 03-02 / ASM-02) ────────────────────────
|
|
12860
13022
|
get_outline: {
|
|
12861
|
-
doc_id:
|
|
12862
|
-
vaults:
|
|
13023
|
+
doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the document"),
|
|
13024
|
+
vaults: z14.array(z14.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
|
|
12863
13025
|
},
|
|
12864
13026
|
// ── Phase 3 assembly tools (Plan 03-03) ─────────────────────────────────
|
|
12865
13027
|
search_sections: {
|
|
12866
|
-
query:
|
|
12867
|
-
limit:
|
|
12868
|
-
vaults:
|
|
13028
|
+
query: z14.string().min(1),
|
|
13029
|
+
limit: z14.number().int().positive().max(50).optional().default(10),
|
|
13030
|
+
vaults: z14.array(z14.string().min(1)).optional(),
|
|
12869
13031
|
// Forward-compat with slice 03-05's authority/staleness rescore.
|
|
12870
13032
|
// Accepted today; ignored by the controller until 03-05 wires the
|
|
12871
13033
|
// forwarding inside hybridSearch. See 03-03-DEVIATIONS.md.
|
|
12872
|
-
recency_weight:
|
|
12873
|
-
authority_weight:
|
|
12874
|
-
include_superseded:
|
|
13034
|
+
recency_weight: z14.number().min(0).optional().default(0),
|
|
13035
|
+
authority_weight: z14.number().min(0).optional().default(0),
|
|
13036
|
+
include_superseded: z14.boolean().optional().default(false)
|
|
12875
13037
|
},
|
|
12876
13038
|
// ── Phase 2 memory tools (Plan 02-05) ───────────────────────────────────
|
|
12877
13039
|
recall: {
|
|
12878
|
-
query:
|
|
12879
|
-
min_confidence:
|
|
13040
|
+
query: z14.string().min(1).describe("Natural-language query; routes through hybrid (semantic + BM25) search"),
|
|
13041
|
+
min_confidence: z14.enum(["direct", "inferred", "uncertain"]).optional().describe(
|
|
12880
13042
|
"Exclude docs whose confidence ordinal is lower than this (direct=3, inferred=2, uncertain=1)"
|
|
12881
13043
|
),
|
|
12882
|
-
types:
|
|
12883
|
-
max_age_days:
|
|
12884
|
-
sink:
|
|
13044
|
+
types: z14.array(z14.string().min(1)).optional().describe("Restrict to docs whose `type` property is in this set"),
|
|
13045
|
+
max_age_days: z14.number().int().positive().optional().describe("Exclude docs whose `observed_at` is older than this many days"),
|
|
13046
|
+
sink: z14.string().min(1).optional().describe(
|
|
12885
13047
|
"Memory sink name OR full obsidian-fs://\u2026 handle. Defaults to all configured sinks."
|
|
12886
13048
|
),
|
|
12887
|
-
limit:
|
|
12888
|
-
vaults:
|
|
13049
|
+
limit: z14.number().int().positive().max(200).optional().describe("Maximum results AFTER filter+sort; default 20"),
|
|
13050
|
+
vaults: z14.array(z14.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
|
|
12889
13051
|
},
|
|
12890
13052
|
// ── Phase 3 assembly tools (Plan 03-04 / ASM-01) ────────────────────────
|
|
12891
13053
|
get_document_bundle: {
|
|
12892
|
-
doc_id:
|
|
13054
|
+
doc_id: z14.string().regex(DOC_ID_PATTERN2).describe("Opaque DocId (obsidian-fs://<vault>/<path>) of the anchor document"),
|
|
12893
13055
|
// v2.0.0 accepts only depth:1. The literal pin guarantees Zod
|
|
12894
13056
|
// rejects any other value at the boundary so the controller does
|
|
12895
13057
|
// not need to clamp. Phase 4 may widen additively (z.union of
|
|
12896
13058
|
// literals, or `z.number().int().min(1).max(2)`).
|
|
12897
|
-
depth:
|
|
12898
|
-
vaults:
|
|
13059
|
+
depth: z14.literal(1).optional().default(1).describe("Link-walk depth. v2.0.0: only 1 (one-hop). Phase 4 may widen."),
|
|
13060
|
+
vaults: z14.array(z14.string().min(1)).optional().describe("Optional vault filter; usually omitted (the DocId names a vault)")
|
|
12899
13061
|
},
|
|
12900
13062
|
// ── Phase 4 graph tools (Plan 04-03 / GRA-01) ───────────────────────────
|
|
12901
13063
|
expand: {
|
|
12902
|
-
seed_doc_ids:
|
|
13064
|
+
seed_doc_ids: z14.array(z14.string().regex(DOC_ID_PATTERN2)).min(1).describe(
|
|
12903
13065
|
"1+ opaque DocIds (e.g. obsidian-fs://<vault>/<path>) \u2014 seeds of the BFS."
|
|
12904
13066
|
),
|
|
12905
13067
|
// Hops hard-capped at 2 (D-05) via Zod literal union — `hops: 3`
|
|
12906
13068
|
// is rejected at the boundary; the controller does not clamp.
|
|
12907
|
-
hops:
|
|
12908
|
-
direction:
|
|
12909
|
-
edge_types:
|
|
12910
|
-
filter_properties:
|
|
13069
|
+
hops: z14.union([z14.literal(1), z14.literal(2)]).describe("Hop cap (1 or 2). v2.0.0 hard-caps at 2."),
|
|
13070
|
+
direction: z14.enum(["forward", "backward", "both"]).optional().default("both").describe("Edge traversal direction; default 'both'."),
|
|
13071
|
+
edge_types: z14.array(z14.enum(["wikilink", "mention", "frontmatter-ref", "hyperlink"])).optional().describe("Optional filter on edge types; default = all four types."),
|
|
13072
|
+
filter_properties: z14.record(z14.string(), z14.unknown()).optional().describe(
|
|
12911
13073
|
"Strict-equality predicate on document properties (e.g. {type: 'Project'})."
|
|
12912
13074
|
),
|
|
12913
|
-
include_superseded:
|
|
13075
|
+
include_superseded: z14.boolean().optional().default(false).describe(
|
|
12914
13076
|
"When false (default), docs whose properties.status === 'superseded' are dropped."
|
|
12915
13077
|
)
|
|
12916
13078
|
},
|
|
@@ -12924,50 +13086,50 @@ var init_tool_registry = __esm({
|
|
|
12924
13086
|
// works. The runtime path goes through `buildToolSchema("cluster")`
|
|
12925
13087
|
// which calls the SCHEMA_BUILDERS entry.
|
|
12926
13088
|
cluster: {
|
|
12927
|
-
query:
|
|
12928
|
-
seed_doc_ids:
|
|
13089
|
+
query: z14.string().min(1).optional(),
|
|
13090
|
+
seed_doc_ids: z14.array(z14.string().regex(DOC_ID_PATTERN2)).min(1).optional(),
|
|
12929
13091
|
// CR-02: `vault` scopes the `query` path on multi-vault setups so
|
|
12930
13092
|
// search_hybrid is not silently restricted to whichever vault
|
|
12931
13093
|
// sorts first in VaultManager insertion order. Optional at the
|
|
12932
13094
|
// schema layer; the runtime cluster() entry enforces the
|
|
12933
13095
|
// multi-vault-without-vault error.
|
|
12934
|
-
vault:
|
|
12935
|
-
method:
|
|
12936
|
-
query_top_k:
|
|
12937
|
-
force:
|
|
13096
|
+
vault: z14.string().min(1).optional(),
|
|
13097
|
+
method: z14.literal("edge-community"),
|
|
13098
|
+
query_top_k: z14.number().int().positive().max(200).optional().default(50),
|
|
13099
|
+
force: z14.boolean().optional().default(false)
|
|
12938
13100
|
},
|
|
12939
13101
|
// ── Phase 3 assembly tools (Plan 03-06) ─────────────────────────────────
|
|
12940
13102
|
assemble_dossier: {
|
|
12941
|
-
type:
|
|
13103
|
+
type: z14.string().min(1).describe(
|
|
12942
13104
|
"Exact-match value for properties.type on the anchor document (D-03 \u2014 no fuzzy match)"
|
|
12943
13105
|
),
|
|
12944
|
-
key:
|
|
13106
|
+
key: z14.string().min(1).describe(
|
|
12945
13107
|
"Candidate key \u2014 matches the document's title OR any entry in properties.aliases (D-04)"
|
|
12946
13108
|
),
|
|
12947
|
-
vaults:
|
|
13109
|
+
vaults: z14.array(z14.string().min(1)).optional().describe("Restrict to these vault names; defaults to all configured")
|
|
12948
13110
|
},
|
|
12949
13111
|
// ── Phase 6 task-contract DSL (Plan 06-02 / D-A1 escape valve) ─────────
|
|
12950
13112
|
register_contracts_as_tools: {
|
|
12951
|
-
vault:
|
|
13113
|
+
vault: z14.string().min(1).optional().describe("Vault name; omit to apply to all vaults")
|
|
12952
13114
|
},
|
|
12953
13115
|
// ── Phase 6 task-contract DSL (Plan 06-03 / CON-05, Q-DESCRIBE) ────────
|
|
12954
13116
|
describe_contract: {
|
|
12955
|
-
name:
|
|
12956
|
-
vault:
|
|
13117
|
+
name: z14.string().min(1).describe("Registered contract name (see register_contracts_as_tools)"),
|
|
13118
|
+
vault: z14.string().min(1).optional().describe("Vault name; omit on single-vault setups")
|
|
12957
13119
|
},
|
|
12958
13120
|
// ── Phase 6 task-contract DSL (Plan 06-03 / CON-06) ────────────────────
|
|
12959
13121
|
instantiate_contract: {
|
|
12960
|
-
name:
|
|
12961
|
-
inputs:
|
|
12962
|
-
source_overrides:
|
|
12963
|
-
sink_overrides:
|
|
13122
|
+
name: z14.string().min(1).describe("Registered contract name"),
|
|
13123
|
+
inputs: z14.record(z14.string(), z14.unknown()).optional().default({}).describe("Contract inputs; validated against the contract's inputZodSchema"),
|
|
13124
|
+
source_overrides: z14.record(z14.string(), z14.string()).optional().describe("Override declared source handles by handle name"),
|
|
13125
|
+
sink_overrides: z14.record(z14.string(), z14.string()).optional().describe(
|
|
12964
13126
|
"Override declared sink handles by handle name. Targets MUST resolve through MemorySinkRegistry (D-A4c)."
|
|
12965
13127
|
),
|
|
12966
|
-
vault:
|
|
13128
|
+
vault: z14.string().min(1).optional().describe("Vault name; omit on single-vault setups")
|
|
12967
13129
|
}
|
|
12968
13130
|
};
|
|
12969
13131
|
SCHEMA_BUILDERS = {
|
|
12970
|
-
suggest_frontmatter: () =>
|
|
13132
|
+
suggest_frontmatter: () => z14.object(TOOL_SCHEMAS.suggest_frontmatter).refine((v) => v.path !== void 0 || v.content !== void 0, {
|
|
12971
13133
|
message: "suggest_frontmatter requires either `path` or `content`"
|
|
12972
13134
|
}),
|
|
12973
13135
|
// Plan 04-05 / D-15a — EXACTLY ONE of `query` or `seed_doc_ids` must
|
|
@@ -12976,7 +13138,7 @@ var init_tool_registry = __esm({
|
|
|
12976
13138
|
// so this Zod refinement is the early-rejection gate at the MCP
|
|
12977
13139
|
// boundary (cluster's internal validator handles the same case for
|
|
12978
13140
|
// direct callers that bypass Zod).
|
|
12979
|
-
cluster: () =>
|
|
13141
|
+
cluster: () => z14.object(TOOL_SCHEMAS.cluster).refine(
|
|
12980
13142
|
(v) => v.query !== void 0 && v.seed_doc_ids === void 0 || v.query === void 0 && v.seed_doc_ids !== void 0,
|
|
12981
13143
|
{
|
|
12982
13144
|
message: "cluster requires EXACTLY ONE of `query` or `seed_doc_ids` (D-15a mutual exclusion)"
|
|
@@ -13071,7 +13233,7 @@ var init_json_schema_ref = __esm({
|
|
|
13071
13233
|
});
|
|
13072
13234
|
|
|
13073
13235
|
// src/contracts/input-schema.ts
|
|
13074
|
-
import { z as
|
|
13236
|
+
import { z as z15 } from "zod";
|
|
13075
13237
|
function buildInputSchema(yamlInputs, required = []) {
|
|
13076
13238
|
const resolvedProperties = resolveRefs(yamlInputs);
|
|
13077
13239
|
const jsonSchema = {
|
|
@@ -13080,7 +13242,7 @@ function buildInputSchema(yamlInputs, required = []) {
|
|
|
13080
13242
|
required,
|
|
13081
13243
|
additionalProperties: false
|
|
13082
13244
|
};
|
|
13083
|
-
const zodSchema =
|
|
13245
|
+
const zodSchema = z15.fromJSONSchema(
|
|
13084
13246
|
jsonSchema
|
|
13085
13247
|
);
|
|
13086
13248
|
return { zodSchema, jsonSchema };
|
|
@@ -13166,7 +13328,7 @@ var init_audit4 = __esm({
|
|
|
13166
13328
|
});
|
|
13167
13329
|
|
|
13168
13330
|
// src/contracts/schema.ts
|
|
13169
|
-
import { z as
|
|
13331
|
+
import { z as z16 } from "zod";
|
|
13170
13332
|
var BASELINE_VERBS, MCP_VERB_RE, VerbSchema, StepSchema, HandleDeclSchema, WriteBackSchema, ContractFileSchema;
|
|
13171
13333
|
var init_schema4 = __esm({
|
|
13172
13334
|
"src/contracts/schema.ts"() {
|
|
@@ -13186,40 +13348,40 @@ var init_schema4 = __esm({
|
|
|
13186
13348
|
"read_note"
|
|
13187
13349
|
];
|
|
13188
13350
|
MCP_VERB_RE = /^mcp:\/\/[a-z][a-z0-9_-]*\/[a-z][a-z0-9_-]*$/;
|
|
13189
|
-
VerbSchema =
|
|
13190
|
-
|
|
13191
|
-
|
|
13351
|
+
VerbSchema = z16.union([
|
|
13352
|
+
z16.enum([...BASELINE_VERBS, "literal"]),
|
|
13353
|
+
z16.string().regex(MCP_VERB_RE)
|
|
13192
13354
|
]);
|
|
13193
|
-
StepSchema =
|
|
13194
|
-
as:
|
|
13355
|
+
StepSchema = z16.object({
|
|
13356
|
+
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
13357
|
verb: VerbSchema.describe(
|
|
13196
13358
|
"Closed enum + literal + mcp:// extension (D-A2a / C-1)"
|
|
13197
13359
|
),
|
|
13198
|
-
args:
|
|
13199
|
-
value:
|
|
13360
|
+
args: z16.record(z16.string(), z16.unknown()).optional(),
|
|
13361
|
+
value: z16.unknown().optional()
|
|
13200
13362
|
}).describe("One step in an assembly: array");
|
|
13201
|
-
HandleDeclSchema =
|
|
13202
|
-
handle:
|
|
13203
|
-
required:
|
|
13363
|
+
HandleDeclSchema = z16.object({
|
|
13364
|
+
handle: z16.string().min(1),
|
|
13365
|
+
required: z16.boolean().default(true)
|
|
13204
13366
|
}).describe("Source or sink handle declaration (D-A4a)");
|
|
13205
|
-
WriteBackSchema =
|
|
13206
|
-
sink:
|
|
13207
|
-
document_kind:
|
|
13208
|
-
properties:
|
|
13209
|
-
body_from:
|
|
13367
|
+
WriteBackSchema = z16.object({
|
|
13368
|
+
sink: z16.string().min(1).describe("Template expression OR literal sink handle"),
|
|
13369
|
+
document_kind: z16.enum(["brief", "observation", "custom"]),
|
|
13370
|
+
properties: z16.record(z16.string(), z16.unknown()).default({}),
|
|
13371
|
+
body_from: z16.string().min(1).describe("Template expression that resolves to the body string")
|
|
13210
13372
|
}).describe(
|
|
13211
13373
|
"DeliveryAdapter.write chokepoint \u2014 only ground-truth DocId source (C-3)"
|
|
13212
13374
|
);
|
|
13213
|
-
ContractFileSchema =
|
|
13214
|
-
version:
|
|
13215
|
-
name:
|
|
13216
|
-
description:
|
|
13217
|
-
inputs:
|
|
13218
|
-
required:
|
|
13219
|
-
sources:
|
|
13220
|
-
sinks:
|
|
13221
|
-
assembly:
|
|
13222
|
-
output_shape:
|
|
13375
|
+
ContractFileSchema = z16.object({
|
|
13376
|
+
version: z16.literal(1).describe("v2.0.0 supports version 1 only; v2.x may extend additively"),
|
|
13377
|
+
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"),
|
|
13378
|
+
description: z16.string().default(""),
|
|
13379
|
+
inputs: z16.record(z16.string(), z16.unknown()).default({}),
|
|
13380
|
+
required: z16.array(z16.string()).default([]),
|
|
13381
|
+
sources: z16.record(z16.string(), HandleDeclSchema).default({}),
|
|
13382
|
+
sinks: z16.record(z16.string(), HandleDeclSchema).default({}),
|
|
13383
|
+
assembly: z16.array(StepSchema).min(1, "assembly must contain at least one step"),
|
|
13384
|
+
output_shape: z16.unknown().optional(),
|
|
13223
13385
|
write_back: WriteBackSchema.optional()
|
|
13224
13386
|
}).superRefine((data, ctx) => {
|
|
13225
13387
|
const aliases = /* @__PURE__ */ new Set();
|
|
@@ -13571,6 +13733,9 @@ var init_templates = __esm({
|
|
|
13571
13733
|
// src/contracts/mcp-clients.ts
|
|
13572
13734
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13573
13735
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
13736
|
+
function nowSeconds() {
|
|
13737
|
+
return Math.floor(Date.now() / 1e3);
|
|
13738
|
+
}
|
|
13574
13739
|
function wrapAvailable(client, transport) {
|
|
13575
13740
|
return {
|
|
13576
13741
|
available: true,
|
|
@@ -13592,6 +13757,27 @@ function wrapAvailable(client, transport) {
|
|
|
13592
13757
|
}
|
|
13593
13758
|
return res;
|
|
13594
13759
|
},
|
|
13760
|
+
async listTools() {
|
|
13761
|
+
if (typeof client.listTools !== "function") return [];
|
|
13762
|
+
const res = await client.listTools();
|
|
13763
|
+
const tools = res.tools;
|
|
13764
|
+
if (!Array.isArray(tools)) return [];
|
|
13765
|
+
const out = [];
|
|
13766
|
+
for (const t of tools) {
|
|
13767
|
+
if (!t || typeof t !== "object") continue;
|
|
13768
|
+
const name = t.name;
|
|
13769
|
+
if (typeof name !== "string") continue;
|
|
13770
|
+
const tool = { name };
|
|
13771
|
+
const description = t.description;
|
|
13772
|
+
if (typeof description === "string") tool.description = description;
|
|
13773
|
+
const inputSchema = t.inputSchema;
|
|
13774
|
+
if (inputSchema && typeof inputSchema === "object") {
|
|
13775
|
+
tool.inputSchema = inputSchema;
|
|
13776
|
+
}
|
|
13777
|
+
out.push(tool);
|
|
13778
|
+
}
|
|
13779
|
+
return out;
|
|
13780
|
+
},
|
|
13595
13781
|
[Symbol.dispose]() {
|
|
13596
13782
|
transport.close();
|
|
13597
13783
|
}
|
|
@@ -13603,6 +13789,9 @@ function wrapUnavailable() {
|
|
|
13603
13789
|
async callTool() {
|
|
13604
13790
|
throw new Error("peer-MCP client unavailable");
|
|
13605
13791
|
},
|
|
13792
|
+
async listTools() {
|
|
13793
|
+
throw new Error("peer-MCP client unavailable");
|
|
13794
|
+
},
|
|
13606
13795
|
[Symbol.dispose]() {
|
|
13607
13796
|
}
|
|
13608
13797
|
};
|
|
@@ -13613,49 +13802,155 @@ var init_mcp_clients = __esm({
|
|
|
13613
13802
|
"use strict";
|
|
13614
13803
|
init_esm_shims();
|
|
13615
13804
|
PeerMcpRegistry = class {
|
|
13616
|
-
|
|
13805
|
+
entries = /* @__PURE__ */ new Map();
|
|
13617
13806
|
clientFactory;
|
|
13618
13807
|
constructor(clientFactory) {
|
|
13619
13808
|
this.clientFactory = clientFactory;
|
|
13620
13809
|
}
|
|
13621
13810
|
get size() {
|
|
13622
|
-
return this.
|
|
13811
|
+
return this.entries.size;
|
|
13623
13812
|
}
|
|
13624
13813
|
/**
|
|
13625
13814
|
* Boot every `[contracts.mcp_clients.<name>]` entry. Failures are
|
|
13626
13815
|
* non-fatal: the name is recorded as unavailable and a WARN line is
|
|
13627
13816
|
* written to stderr. Returns when all attempts have settled.
|
|
13817
|
+
*
|
|
13818
|
+
* On a successful connect we prime the tools cache via tools/list. A
|
|
13819
|
+
* tools/list failure does NOT mark the source unavailable — the
|
|
13820
|
+
* connection is live and callTool may still work — but the status
|
|
13821
|
+
* becomes "unreachable" so the UI can prompt a retry.
|
|
13628
13822
|
*/
|
|
13629
13823
|
async start(configs) {
|
|
13630
13824
|
for (const [name, cfg] of Object.entries(configs)) {
|
|
13825
|
+
await this.connectAndStore(name, cfg);
|
|
13826
|
+
}
|
|
13827
|
+
}
|
|
13828
|
+
get(name) {
|
|
13829
|
+
return this.entries.get(name)?.client;
|
|
13830
|
+
}
|
|
13831
|
+
/** All registered source names, in insertion order. */
|
|
13832
|
+
names() {
|
|
13833
|
+
return Array.from(this.entries.keys());
|
|
13834
|
+
}
|
|
13835
|
+
/** Cached metadata projection for one source; undefined if unknown. */
|
|
13836
|
+
getInfo(name) {
|
|
13837
|
+
const e = this.entries.get(name);
|
|
13838
|
+
if (e === void 0) return void 0;
|
|
13839
|
+
return {
|
|
13840
|
+
status: e.status,
|
|
13841
|
+
tools: e.tools,
|
|
13842
|
+
lastRefreshed: e.lastRefreshed,
|
|
13843
|
+
...e.error !== void 0 ? { error: e.error } : {}
|
|
13844
|
+
};
|
|
13845
|
+
}
|
|
13846
|
+
/**
|
|
13847
|
+
* Register a new source at runtime: spawn + connect, then prime the
|
|
13848
|
+
* tools cache. Replaces any existing entry of the same name (the old
|
|
13849
|
+
* client is disposed first). Returns the resulting info projection.
|
|
13850
|
+
*/
|
|
13851
|
+
async add(name, cfg) {
|
|
13852
|
+
const existing = this.entries.get(name);
|
|
13853
|
+
if (existing !== void 0) {
|
|
13631
13854
|
try {
|
|
13632
|
-
|
|
13633
|
-
|
|
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());
|
|
13855
|
+
existing.client[Symbol.dispose]();
|
|
13856
|
+
} catch {
|
|
13641
13857
|
}
|
|
13642
13858
|
}
|
|
13859
|
+
await this.connectAndStore(name, cfg);
|
|
13860
|
+
return this.getInfo(name);
|
|
13643
13861
|
}
|
|
13644
|
-
|
|
13645
|
-
|
|
13862
|
+
/**
|
|
13863
|
+
* Dispose a source and drop it from the registry. Idempotent —
|
|
13864
|
+
* removing an unknown name is a no-op that returns false.
|
|
13865
|
+
*/
|
|
13866
|
+
remove(name) {
|
|
13867
|
+
const e = this.entries.get(name);
|
|
13868
|
+
if (e === void 0) return false;
|
|
13869
|
+
try {
|
|
13870
|
+
e.client[Symbol.dispose]();
|
|
13871
|
+
} catch (err) {
|
|
13872
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13873
|
+
process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}
|
|
13874
|
+
`);
|
|
13875
|
+
}
|
|
13876
|
+
this.entries.delete(name);
|
|
13877
|
+
return true;
|
|
13878
|
+
}
|
|
13879
|
+
/**
|
|
13880
|
+
* Re-issue tools/list against the live client and refresh the cache.
|
|
13881
|
+
* Returns the updated info, or undefined if the name is unknown.
|
|
13882
|
+
*
|
|
13883
|
+
* If the client is currently unavailable this only updates the error;
|
|
13884
|
+
* re-spawning a failed source requires `add(name, cfg)` with the
|
|
13885
|
+
* config (the registry does not retain configs).
|
|
13886
|
+
*/
|
|
13887
|
+
async refresh(name) {
|
|
13888
|
+
const e = this.entries.get(name);
|
|
13889
|
+
if (e === void 0) return void 0;
|
|
13890
|
+
if (!e.client.available) {
|
|
13891
|
+
e.status = "unavailable";
|
|
13892
|
+
return this.getInfo(name);
|
|
13893
|
+
}
|
|
13894
|
+
await this.primeTools(e);
|
|
13895
|
+
return this.getInfo(name);
|
|
13646
13896
|
}
|
|
13647
13897
|
/** Dispose every client and clear the internal map. Idempotent. */
|
|
13648
13898
|
async shutdown() {
|
|
13649
|
-
for (const
|
|
13899
|
+
for (const e of this.entries.values()) {
|
|
13650
13900
|
try {
|
|
13651
|
-
|
|
13901
|
+
e.client[Symbol.dispose]();
|
|
13652
13902
|
} catch (err) {
|
|
13653
13903
|
const msg = err instanceof Error ? err.message : String(err);
|
|
13654
13904
|
process.stderr.write(`[contracts] peer-MCP dispose error: ${msg}
|
|
13655
13905
|
`);
|
|
13656
13906
|
}
|
|
13657
13907
|
}
|
|
13658
|
-
this.
|
|
13908
|
+
this.entries.clear();
|
|
13909
|
+
}
|
|
13910
|
+
// ─── internals ─────────────────────────────────────────────────────────
|
|
13911
|
+
/** Connect (factory or default), store the entry, prime tools cache. */
|
|
13912
|
+
async connectAndStore(name, cfg) {
|
|
13913
|
+
try {
|
|
13914
|
+
const { client, transport } = this.clientFactory ? await this.clientFactory(cfg) : await this.defaultConnect(cfg);
|
|
13915
|
+
const entry = {
|
|
13916
|
+
client: wrapAvailable(client, transport),
|
|
13917
|
+
status: "connected",
|
|
13918
|
+
tools: [],
|
|
13919
|
+
lastRefreshed: null
|
|
13920
|
+
};
|
|
13921
|
+
this.entries.set(name, entry);
|
|
13922
|
+
await this.primeTools(entry);
|
|
13923
|
+
} catch (err) {
|
|
13924
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
13925
|
+
process.stderr.write(
|
|
13926
|
+
`[contracts] peer-MCP client '${name}' failed to start: ${msg}
|
|
13927
|
+
`
|
|
13928
|
+
);
|
|
13929
|
+
this.entries.set(name, {
|
|
13930
|
+
client: wrapUnavailable(),
|
|
13931
|
+
status: "unavailable",
|
|
13932
|
+
tools: [],
|
|
13933
|
+
lastRefreshed: null,
|
|
13934
|
+
error: msg
|
|
13935
|
+
});
|
|
13936
|
+
}
|
|
13937
|
+
}
|
|
13938
|
+
/**
|
|
13939
|
+
* Call tools/list and update the entry's cache + status. A failure
|
|
13940
|
+
* keeps the connection (status → "unreachable") rather than tearing it
|
|
13941
|
+
* down — the source is reachable for callTool even if discovery failed.
|
|
13942
|
+
*/
|
|
13943
|
+
async primeTools(entry) {
|
|
13944
|
+
try {
|
|
13945
|
+
const tools = await entry.client.listTools();
|
|
13946
|
+
entry.tools = tools;
|
|
13947
|
+
entry.lastRefreshed = nowSeconds();
|
|
13948
|
+
entry.status = "connected";
|
|
13949
|
+
delete entry.error;
|
|
13950
|
+
} catch (err) {
|
|
13951
|
+
entry.status = "unreachable";
|
|
13952
|
+
entry.error = err instanceof Error ? err.message : String(err);
|
|
13953
|
+
}
|
|
13659
13954
|
}
|
|
13660
13955
|
async defaultConnect(cfg) {
|
|
13661
13956
|
const transport = new StdioClientTransport({
|
|
@@ -13760,7 +14055,7 @@ var init_verbs = __esm({
|
|
|
13760
14055
|
});
|
|
13761
14056
|
|
|
13762
14057
|
// src/contracts/instantiate.ts
|
|
13763
|
-
import { z as
|
|
14058
|
+
import { z as z17 } from "zod";
|
|
13764
14059
|
async function instantiateContract(deps, args2) {
|
|
13765
14060
|
const parsed = deps.registry.get(args2.name);
|
|
13766
14061
|
if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
|
|
@@ -13908,7 +14203,7 @@ async function instantiateContract(deps, args2) {
|
|
|
13908
14203
|
};
|
|
13909
14204
|
if (parsed.output_shape) {
|
|
13910
14205
|
try {
|
|
13911
|
-
const outputSchema =
|
|
14206
|
+
const outputSchema = z17.fromJSONSchema(
|
|
13912
14207
|
parsed.output_shape
|
|
13913
14208
|
);
|
|
13914
14209
|
const check = outputSchema.safeParse(bundle);
|
|
@@ -13996,6 +14291,12 @@ var init_instantiate = __esm({
|
|
|
13996
14291
|
});
|
|
13997
14292
|
|
|
13998
14293
|
// src/contracts/describe.ts
|
|
14294
|
+
function glossFor(verb) {
|
|
14295
|
+
if (VERB_GLOSS[verb]) return VERB_GLOSS[verb];
|
|
14296
|
+
if (verb === "literal") return "Use a fixed inline value";
|
|
14297
|
+
if (verb.startsWith("mcp://")) return `Call an external tool (${verb})`;
|
|
14298
|
+
return verb;
|
|
14299
|
+
}
|
|
13999
14300
|
function describeContract(deps, args2) {
|
|
14000
14301
|
const parsed = deps.registry.get(args2.name);
|
|
14001
14302
|
if (!parsed) return { ok: false, reason: "unknown_contract", name: args2.name };
|
|
@@ -14045,7 +14346,9 @@ function renderSummary(parsed) {
|
|
|
14045
14346
|
lines.push("## Assembly");
|
|
14046
14347
|
parsed.assembly.forEach((step, i) => {
|
|
14047
14348
|
const argsRender = step.args ? `(${Object.keys(step.args).join(", ")})` : "()";
|
|
14048
|
-
lines.push(
|
|
14349
|
+
lines.push(
|
|
14350
|
+
`${i + 1}. **${step.as}** \u2014 ${glossFor(step.verb)} _(\`${step.verb}${argsRender}\`)_`
|
|
14351
|
+
);
|
|
14049
14352
|
});
|
|
14050
14353
|
lines.push("");
|
|
14051
14354
|
}
|
|
@@ -14069,10 +14372,24 @@ function renderSummary(parsed) {
|
|
|
14069
14372
|
}
|
|
14070
14373
|
return lines.join("\n").trim() + "\n";
|
|
14071
14374
|
}
|
|
14375
|
+
var VERB_GLOSS;
|
|
14072
14376
|
var init_describe = __esm({
|
|
14073
14377
|
"src/contracts/describe.ts"() {
|
|
14074
14378
|
"use strict";
|
|
14075
14379
|
init_esm_shims();
|
|
14380
|
+
VERB_GLOSS = {
|
|
14381
|
+
read_note: "Read a note's content",
|
|
14382
|
+
search_hybrid: "Search the vault (semantic + keyword)",
|
|
14383
|
+
search_sections: "Search for matching sections within notes",
|
|
14384
|
+
query_frontmatter: "Find notes by their properties (frontmatter)",
|
|
14385
|
+
expand: "Gather notes linked to the starting note (follow the graph)",
|
|
14386
|
+
cluster: "Group the gathered notes into related communities",
|
|
14387
|
+
recall: "Recall earlier agent observations from memory",
|
|
14388
|
+
compile_brief: "Compile the gathered notes into a brief",
|
|
14389
|
+
get_brief: "Fetch an already-compiled brief",
|
|
14390
|
+
list_backlinks: "List notes that link back to this one",
|
|
14391
|
+
get_outline: "Read a note's heading outline"
|
|
14392
|
+
};
|
|
14076
14393
|
}
|
|
14077
14394
|
});
|
|
14078
14395
|
|
|
@@ -14143,6 +14460,59 @@ var init_resources3 = __esm({
|
|
|
14143
14460
|
}
|
|
14144
14461
|
});
|
|
14145
14462
|
|
|
14463
|
+
// src/contracts/sources-resources.ts
|
|
14464
|
+
function readListSources(reg, configMeta) {
|
|
14465
|
+
const sources = [];
|
|
14466
|
+
for (const name of reg.names()) {
|
|
14467
|
+
const info = reg.getInfo(name);
|
|
14468
|
+
if (info === void 0) continue;
|
|
14469
|
+
const meta = configMeta[name];
|
|
14470
|
+
const entry = {
|
|
14471
|
+
name,
|
|
14472
|
+
transport: "stdio",
|
|
14473
|
+
command: meta?.command ?? "",
|
|
14474
|
+
args: meta?.args ?? [],
|
|
14475
|
+
status: info.status,
|
|
14476
|
+
tool_count: info.tools.length,
|
|
14477
|
+
last_refreshed: info.lastRefreshed
|
|
14478
|
+
};
|
|
14479
|
+
if (info.error !== void 0) entry.error = info.error;
|
|
14480
|
+
sources.push(entry);
|
|
14481
|
+
}
|
|
14482
|
+
return { sources };
|
|
14483
|
+
}
|
|
14484
|
+
function readSourceTools(reg, name) {
|
|
14485
|
+
const info = reg.getInfo(name);
|
|
14486
|
+
if (info === void 0) {
|
|
14487
|
+
return { error: `unknown source: ${name}` };
|
|
14488
|
+
}
|
|
14489
|
+
const out = {
|
|
14490
|
+
name,
|
|
14491
|
+
status: info.status,
|
|
14492
|
+
last_refreshed: info.lastRefreshed,
|
|
14493
|
+
tools: info.tools
|
|
14494
|
+
};
|
|
14495
|
+
if (info.error !== void 0) out.error = info.error;
|
|
14496
|
+
return out;
|
|
14497
|
+
}
|
|
14498
|
+
function readSourceTool(reg, name, toolName) {
|
|
14499
|
+
const info = reg.getInfo(name);
|
|
14500
|
+
if (info === void 0) {
|
|
14501
|
+
return { found: false, error: `unknown source: ${name}` };
|
|
14502
|
+
}
|
|
14503
|
+
const tool = info.tools.find((t) => t.name === toolName);
|
|
14504
|
+
if (tool === void 0) {
|
|
14505
|
+
return { found: false, error: `unknown tool: ${name}/${toolName}` };
|
|
14506
|
+
}
|
|
14507
|
+
return { found: true, name, tool };
|
|
14508
|
+
}
|
|
14509
|
+
var init_sources_resources = __esm({
|
|
14510
|
+
"src/contracts/sources-resources.ts"() {
|
|
14511
|
+
"use strict";
|
|
14512
|
+
init_esm_shims();
|
|
14513
|
+
}
|
|
14514
|
+
});
|
|
14515
|
+
|
|
14146
14516
|
// src/contracts/index.ts
|
|
14147
14517
|
var init_contracts = __esm({
|
|
14148
14518
|
"src/contracts/index.ts"() {
|
|
@@ -14165,6 +14535,7 @@ var init_contracts = __esm({
|
|
|
14165
14535
|
init_instantiate();
|
|
14166
14536
|
init_describe();
|
|
14167
14537
|
init_resources3();
|
|
14538
|
+
init_sources_resources();
|
|
14168
14539
|
}
|
|
14169
14540
|
});
|
|
14170
14541
|
|
|
@@ -14359,6 +14730,20 @@ async function serve(options = {}) {
|
|
|
14359
14730
|
process.on("SIGTERM", () => {
|
|
14360
14731
|
void shutdown().finally(() => process.exit(0));
|
|
14361
14732
|
});
|
|
14733
|
+
let stdinClosing = false;
|
|
14734
|
+
const onStdinClose = (reason) => {
|
|
14735
|
+
if (stdinClosing) return;
|
|
14736
|
+
stdinClosing = true;
|
|
14737
|
+
process.stderr.write(
|
|
14738
|
+
`[vault-memory] stdin ${reason} \u2014 parent process gone; shutting down.
|
|
14739
|
+
`
|
|
14740
|
+
);
|
|
14741
|
+
setTimeout(() => {
|
|
14742
|
+
void shutdown().finally(() => process.exit(0));
|
|
14743
|
+
}, 500);
|
|
14744
|
+
};
|
|
14745
|
+
process.stdin.on("end", () => onStdinClose("end"));
|
|
14746
|
+
process.stdin.on("close", () => onStdinClose("close"));
|
|
14362
14747
|
const server = new McpServer(
|
|
14363
14748
|
{ name: "vault-memory", version: VERSION },
|
|
14364
14749
|
// Plan 02-06 (MEM-09): advertise `resources` capability so MCP clients
|
|
@@ -15404,6 +15789,86 @@ async function serve(options = {}) {
|
|
|
15404
15789
|
};
|
|
15405
15790
|
}
|
|
15406
15791
|
);
|
|
15792
|
+
const sourceConfigMeta = () => {
|
|
15793
|
+
const out = {};
|
|
15794
|
+
for (const [name, cfg] of Object.entries(config.contracts.mcp_clients)) {
|
|
15795
|
+
out[name] = { command: cfg.command, args: cfg.args ?? [] };
|
|
15796
|
+
}
|
|
15797
|
+
return out;
|
|
15798
|
+
};
|
|
15799
|
+
server.registerResource(
|
|
15800
|
+
"sources",
|
|
15801
|
+
RESOURCE_URI_SOURCES,
|
|
15802
|
+
{
|
|
15803
|
+
title: "Peer MCP sources",
|
|
15804
|
+
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.",
|
|
15805
|
+
mimeType: "application/json"
|
|
15806
|
+
},
|
|
15807
|
+
async (uri) => ({
|
|
15808
|
+
contents: [
|
|
15809
|
+
{
|
|
15810
|
+
uri: uri.href,
|
|
15811
|
+
mimeType: "application/json",
|
|
15812
|
+
text: JSON.stringify(
|
|
15813
|
+
readListSources(peerMcpRegistry, sourceConfigMeta()),
|
|
15814
|
+
null,
|
|
15815
|
+
2
|
|
15816
|
+
)
|
|
15817
|
+
}
|
|
15818
|
+
]
|
|
15819
|
+
})
|
|
15820
|
+
);
|
|
15821
|
+
server.registerResource(
|
|
15822
|
+
"source-tools",
|
|
15823
|
+
new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools`, {
|
|
15824
|
+
list: void 0
|
|
15825
|
+
}),
|
|
15826
|
+
{
|
|
15827
|
+
title: "Peer MCP source tools",
|
|
15828
|
+
description: "List the cached tools/list for one peer MCP source. Empty when the source is not connected. SOURCES-REGISTRY \xA75.2.",
|
|
15829
|
+
mimeType: "application/json"
|
|
15830
|
+
},
|
|
15831
|
+
async (uri, variables) => {
|
|
15832
|
+
const name = String(variables.name ?? "");
|
|
15833
|
+
return {
|
|
15834
|
+
contents: [
|
|
15835
|
+
{
|
|
15836
|
+
uri: uri.href,
|
|
15837
|
+
mimeType: "application/json",
|
|
15838
|
+
text: JSON.stringify(readSourceTools(peerMcpRegistry, name), null, 2)
|
|
15839
|
+
}
|
|
15840
|
+
]
|
|
15841
|
+
};
|
|
15842
|
+
}
|
|
15843
|
+
);
|
|
15844
|
+
server.registerResource(
|
|
15845
|
+
"source-tool",
|
|
15846
|
+
new ResourceTemplate(`${RESOURCE_URI_SOURCES}/{name}/tools/{tool}`, {
|
|
15847
|
+
list: void 0
|
|
15848
|
+
}),
|
|
15849
|
+
{
|
|
15850
|
+
title: "Peer MCP source tool",
|
|
15851
|
+
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.",
|
|
15852
|
+
mimeType: "application/json"
|
|
15853
|
+
},
|
|
15854
|
+
async (uri, variables) => {
|
|
15855
|
+
const name = String(variables.name ?? "");
|
|
15856
|
+
const tool = String(variables.tool ?? "");
|
|
15857
|
+
return {
|
|
15858
|
+
contents: [
|
|
15859
|
+
{
|
|
15860
|
+
uri: uri.href,
|
|
15861
|
+
mimeType: "application/json",
|
|
15862
|
+
text: JSON.stringify(
|
|
15863
|
+
readSourceTool(peerMcpRegistry, name, tool),
|
|
15864
|
+
null,
|
|
15865
|
+
2
|
|
15866
|
+
)
|
|
15867
|
+
}
|
|
15868
|
+
]
|
|
15869
|
+
};
|
|
15870
|
+
}
|
|
15871
|
+
);
|
|
15407
15872
|
const rel08Vaults = RESOURCES.find((r) => r.name === "vaults");
|
|
15408
15873
|
const rel08Models = RESOURCES.find((r) => r.name === "models");
|
|
15409
15874
|
const rel08Recent = RESOURCES.find((r) => r.name === "recent");
|
|
@@ -15716,6 +16181,9 @@ async function serve(options = {}) {
|
|
|
15716
16181
|
// sees, so the plugin's `suppress_contract_write` call and the
|
|
15717
16182
|
// change-feed handler observe the same entries.
|
|
15718
16183
|
suppression,
|
|
16184
|
+
// SOURCES-REGISTRY.md §6 (Stage 2) — live registry for refresh_source
|
|
16185
|
+
// + unset_mcp_client. The singleton booted above.
|
|
16186
|
+
sourceRegistry: peerMcpRegistry,
|
|
15719
16187
|
notifier: (notification) => {
|
|
15720
16188
|
server.server.notification(notification);
|
|
15721
16189
|
}
|