@sunub/obsidian-mcp-server 0.3.15 → 0.3.17
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/{build → dist}/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +59 -0
- package/dist/config.js.map +1 -0
- package/dist/db.js +58 -0
- package/dist/db.js.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/server.js +42 -0
- package/dist/server.js.map +1 -0
- package/dist/setup.js +114 -0
- package/dist/setup.js.map +1 -0
- package/{build → dist}/tools/create_document_with_properties/index.d.ts +1 -1
- package/dist/tools/create_document_with_properties/index.js +115 -0
- package/dist/tools/create_document_with_properties/index.js.map +1 -0
- package/{build → dist}/tools/create_document_with_properties/params.d.ts +2 -2
- package/dist/tools/create_document_with_properties/params.js +33 -0
- package/dist/tools/create_document_with_properties/params.js.map +1 -0
- package/{build → dist}/tools/generate_property/index.d.ts +1 -1
- package/dist/tools/generate_property/index.js +86 -0
- package/dist/tools/generate_property/index.js.map +1 -0
- package/dist/tools/generate_property/params.js +55 -0
- package/dist/tools/generate_property/params.js.map +1 -0
- package/dist/tools/index.js +13 -0
- package/dist/tools/index.js.map +1 -0
- package/{build → dist}/tools/organize_attachments/index.d.ts +1 -1
- package/dist/tools/organize_attachments/index.js +86 -0
- package/dist/tools/organize_attachments/index.js.map +1 -0
- package/dist/tools/organize_attachments/params.js +61 -0
- package/dist/tools/organize_attachments/params.js.map +1 -0
- package/{build → dist}/tools/organize_attachments/utils.d.ts +1 -1
- package/dist/tools/organize_attachments/utils.js +66 -0
- package/dist/tools/organize_attachments/utils.js.map +1 -0
- package/{build → dist}/tools/vault/index.d.ts +1 -1
- package/dist/tools/vault/index.js +104 -0
- package/dist/tools/vault/index.js.map +1 -0
- package/dist/tools/vault/metrics.js +130 -0
- package/dist/tools/vault/metrics.js.map +1 -0
- package/{build → dist}/tools/vault/params.d.ts +2 -2
- package/dist/tools/vault/params.js +148 -0
- package/dist/tools/vault/params.js.map +1 -0
- package/dist/tools/vault/types/collect_context.js +103 -0
- package/dist/tools/vault/types/collect_context.js.map +1 -0
- package/dist/tools/vault/types/list_all.js +53 -0
- package/dist/tools/vault/types/list_all.js.map +1 -0
- package/dist/tools/vault/types/read_specific.js +41 -0
- package/dist/tools/vault/types/read_specific.js.map +1 -0
- package/dist/tools/vault/types/search.js +59 -0
- package/dist/tools/vault/types/search.js.map +1 -0
- package/dist/tools/vault/utils/actions/collect_context.d.ts +5 -0
- package/dist/tools/vault/utils/actions/collect_context.js +765 -0
- package/dist/tools/vault/utils/actions/collect_context.js.map +1 -0
- package/dist/tools/vault/utils/actions/index_rag.js +27 -0
- package/dist/tools/vault/utils/actions/index_rag.js.map +1 -0
- package/dist/tools/vault/utils/actions/list_all.d.ts +5 -0
- package/dist/tools/vault/utils/actions/list_all.js +43 -0
- package/dist/tools/vault/utils/actions/list_all.js.map +1 -0
- package/dist/tools/vault/utils/actions/load_memory.d.ts +5 -0
- package/dist/tools/vault/utils/actions/load_memory.js +186 -0
- package/dist/tools/vault/utils/actions/load_memory.js.map +1 -0
- package/{build → dist}/tools/vault/utils/actions/read.d.ts +2 -2
- package/dist/tools/vault/utils/actions/read.js +68 -0
- package/dist/tools/vault/utils/actions/read.js.map +1 -0
- package/{build → dist}/tools/vault/utils/actions/search.d.ts +2 -2
- package/dist/tools/vault/utils/actions/search.js +104 -0
- package/dist/tools/vault/utils/actions/search.js.map +1 -0
- package/{build → dist}/tools/vault/utils/actions/stats.d.ts +1 -1
- package/dist/tools/vault/utils/actions/stats.js +9 -0
- package/dist/tools/vault/utils/actions/stats.js.map +1 -0
- package/dist/tools/vault/utils/constants.js +3 -0
- package/dist/tools/vault/utils/constants.js.map +1 -0
- package/{build → dist}/tools/vault/utils/document.d.ts +3 -3
- package/dist/tools/vault/utils/document.js +50 -0
- package/dist/tools/vault/utils/document.js.map +1 -0
- package/{build → dist}/tools/vault/utils/shared.d.ts +1 -1
- package/dist/tools/vault/utils/shared.js +75 -0
- package/dist/tools/vault/utils/shared.js.map +1 -0
- package/dist/tools/vault/utils.d.ts +8 -0
- package/dist/tools/vault/utils.js +8 -0
- package/dist/tools/vault/utils.js.map +1 -0
- package/{build → dist}/tools/write_property/index.d.ts +1 -1
- package/dist/tools/write_property/index.js +120 -0
- package/dist/tools/write_property/index.js.map +1 -0
- package/dist/tools/write_property/params.js +52 -0
- package/dist/tools/write_property/params.js.map +1 -0
- package/{build → dist}/utils/DirectoryWalker.d.ts +1 -1
- package/dist/utils/DirectoryWalker.js +52 -0
- package/dist/utils/DirectoryWalker.js.map +1 -0
- package/dist/utils/Embedder.js +46 -0
- package/dist/utils/Embedder.js.map +1 -0
- package/{build → dist}/utils/Indexer.d.ts +2 -2
- package/dist/utils/Indexer.js +169 -0
- package/dist/utils/Indexer.js.map +1 -0
- package/dist/utils/LLMClient.js +112 -0
- package/dist/utils/LLMClient.js.map +1 -0
- package/dist/utils/LocalReranker.js +71 -0
- package/dist/utils/LocalReranker.js.map +1 -0
- package/{build → dist}/utils/RAGIndexer.d.ts +1 -1
- package/dist/utils/RAGIndexer.js +238 -0
- package/dist/utils/RAGIndexer.js.map +1 -0
- package/dist/utils/RerankerClient.js +89 -0
- package/dist/utils/RerankerClient.js.map +1 -0
- package/{build → dist}/utils/VaultManger/VaultManager.d.ts +3 -3
- package/dist/utils/VaultManger/VaultManager.js +459 -0
- package/dist/utils/VaultManger/VaultManager.js.map +1 -0
- package/dist/utils/VaultManger/VaultPathError.js +13 -0
- package/dist/utils/VaultManger/VaultPathError.js.map +1 -0
- package/dist/utils/VaultManger/index.d.ts +4 -0
- package/dist/utils/VaultManger/index.js +3 -0
- package/dist/utils/VaultManger/index.js.map +1 -0
- package/{build → dist}/utils/VaultManger/types.d.ts +1 -1
- package/dist/utils/VaultManger/types.js +2 -0
- package/dist/utils/VaultManger/types.js.map +1 -0
- package/dist/utils/VaultWatcher.js +60 -0
- package/dist/utils/VaultWatcher.js.map +1 -0
- package/dist/utils/VectorDB.js +268 -0
- package/dist/utils/VectorDB.js.map +1 -0
- package/dist/utils/constants.js +14 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/createToolError.js +16 -0
- package/dist/utils/createToolError.js.map +1 -0
- package/dist/utils/getVaultManager.d.ts +3 -0
- package/dist/utils/getVaultManager.js +15 -0
- package/dist/utils/getVaultManager.js.map +1 -0
- package/dist/utils/processor/LinkExtractor.js +31 -0
- package/dist/utils/processor/LinkExtractor.js.map +1 -0
- package/dist/utils/processor/MatterParser.d.ts +3 -0
- package/dist/utils/processor/MatterParser.js +28 -0
- package/dist/utils/processor/MatterParser.js.map +1 -0
- package/dist/utils/processor/types.js +22 -0
- package/dist/utils/processor/types.js.map +1 -0
- package/dist/utils/semaphore.js +26 -0
- package/dist/utils/semaphore.js.map +1 -0
- package/package.json +19 -12
- package/build/config.d.ts.map +0 -1
- package/build/index.js +0 -127331
- package/build/index.js.map +0 -828
- package/build/lancedb.linux-x64-gnu-1e1af8yx.node +0 -0
- package/build/tools/vault/utils/actions/collect_context.d.ts +0 -5
- package/build/tools/vault/utils/actions/list_all.d.ts +0 -5
- package/build/tools/vault/utils/actions/load_memory.d.ts +0 -5
- package/build/tools/vault/utils.d.ts +0 -8
- package/build/utils/VaultManger/index.d.ts +0 -4
- package/build/utils/getVaultManager.d.ts +0 -3
- package/build/utils/processor/MatterParser.d.ts +0 -3
- /package/{build → dist}/db.d.ts +0 -0
- /package/{build → dist}/db.d.ts.map +0 -0
- /package/{build → dist}/index.d.ts +0 -0
- /package/{build → dist}/index.d.ts.map +0 -0
- /package/{build → dist}/server.d.ts +0 -0
- /package/{build → dist}/server.d.ts.map +0 -0
- /package/{build → dist}/setup.d.ts +0 -0
- /package/{build → dist}/setup.d.ts.map +0 -0
- /package/{build → dist}/tools/create_document_with_properties/index.d.ts.map +0 -0
- /package/{build → dist}/tools/create_document_with_properties/params.d.ts.map +0 -0
- /package/{build → dist}/tools/generate_property/index.d.ts.map +0 -0
- /package/{build → dist}/tools/generate_property/params.d.ts +0 -0
- /package/{build → dist}/tools/generate_property/params.d.ts.map +0 -0
- /package/{build → dist}/tools/index.d.ts +0 -0
- /package/{build → dist}/tools/index.d.ts.map +0 -0
- /package/{build → dist}/tools/organize_attachments/index.d.ts.map +0 -0
- /package/{build → dist}/tools/organize_attachments/params.d.ts +0 -0
- /package/{build → dist}/tools/organize_attachments/params.d.ts.map +0 -0
- /package/{build → dist}/tools/organize_attachments/utils.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/index.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/metrics.d.ts +0 -0
- /package/{build → dist}/tools/vault/metrics.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/params.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/types/collect_context.d.ts +0 -0
- /package/{build → dist}/tools/vault/types/collect_context.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/types/list_all.d.ts +0 -0
- /package/{build → dist}/tools/vault/types/list_all.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/types/read_specific.d.ts +0 -0
- /package/{build → dist}/tools/vault/types/read_specific.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/types/search.d.ts +0 -0
- /package/{build → dist}/tools/vault/types/search.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/collect_context.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/index_rag.d.ts +0 -0
- /package/{build → dist}/tools/vault/utils/actions/index_rag.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/list_all.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/load_memory.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/read.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/search.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/actions/stats.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/constants.d.ts +0 -0
- /package/{build → dist}/tools/vault/utils/constants.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/document.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils/shared.d.ts.map +0 -0
- /package/{build → dist}/tools/vault/utils.d.ts.map +0 -0
- /package/{build → dist}/tools/write_property/index.d.ts.map +0 -0
- /package/{build → dist}/tools/write_property/params.d.ts +0 -0
- /package/{build → dist}/tools/write_property/params.d.ts.map +0 -0
- /package/{build → dist}/utils/DirectoryWalker.d.ts.map +0 -0
- /package/{build → dist}/utils/Embedder.d.ts +0 -0
- /package/{build → dist}/utils/Embedder.d.ts.map +0 -0
- /package/{build → dist}/utils/Indexer.d.ts.map +0 -0
- /package/{build → dist}/utils/LLMClient.d.ts +0 -0
- /package/{build → dist}/utils/LLMClient.d.ts.map +0 -0
- /package/{build → dist}/utils/LocalReranker.d.ts +0 -0
- /package/{build → dist}/utils/LocalReranker.d.ts.map +0 -0
- /package/{build → dist}/utils/RAGIndexer.d.ts.map +0 -0
- /package/{build → dist}/utils/RerankerClient.d.ts +0 -0
- /package/{build → dist}/utils/RerankerClient.d.ts.map +0 -0
- /package/{build → dist}/utils/VaultManger/VaultManager.d.ts.map +0 -0
- /package/{build → dist}/utils/VaultManger/VaultPathError.d.ts +0 -0
- /package/{build → dist}/utils/VaultManger/VaultPathError.d.ts.map +0 -0
- /package/{build → dist}/utils/VaultManger/index.d.ts.map +0 -0
- /package/{build → dist}/utils/VaultManger/types.d.ts.map +0 -0
- /package/{build → dist}/utils/VaultWatcher.d.ts +0 -0
- /package/{build → dist}/utils/VaultWatcher.d.ts.map +0 -0
- /package/{build → dist}/utils/VectorDB.d.ts +0 -0
- /package/{build → dist}/utils/VectorDB.d.ts.map +0 -0
- /package/{build → dist}/utils/constants.d.ts +0 -0
- /package/{build → dist}/utils/constants.d.ts.map +0 -0
- /package/{build → dist}/utils/createToolError.d.ts +0 -0
- /package/{build → dist}/utils/createToolError.d.ts.map +0 -0
- /package/{build → dist}/utils/getVaultManager.d.ts.map +0 -0
- /package/{build → dist}/utils/processor/LinkExtractor.d.ts +0 -0
- /package/{build → dist}/utils/processor/LinkExtractor.d.ts.map +0 -0
- /package/{build → dist}/utils/processor/MatterParser.d.ts.map +0 -0
- /package/{build → dist}/utils/processor/types.d.ts +0 -0
- /package/{build → dist}/utils/processor/types.d.ts.map +0 -0
- /package/{build → dist}/utils/semaphore.d.ts +0 -0
- /package/{build → dist}/utils/semaphore.d.ts.map +0 -0
|
@@ -0,0 +1,765 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { debugLogger } from "@sunub/obsidian-mcp-core";
|
|
3
|
+
import { collectContextMemoryPacketSchema, collectContextPayloadSchema, collectContextResponseDataSchema, collectContextTokenV1Schema, } from "../../../../tools/vault/types/collect_context.js";
|
|
4
|
+
import { CONTEXT_MEMORY_SNAPSHOT_NOTE_PATH, CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION, } from "../../../../tools/vault/utils/constants.js";
|
|
5
|
+
import { ACTION_DEFAULT_MAX_OUTPUT_CHARS, finalizePayloadWithCompression, jsonCharLength, normalizeWhitespace, resolveCompressionMode, stripFrontmatterBlock, trimWithEllipsis, } from "../../../../tools/vault/utils/shared.js";
|
|
6
|
+
import { createToolError } from "../../../../utils/createToolError.js";
|
|
7
|
+
import { rerankerClient } from "../../../../utils/RerankerClient.js";
|
|
8
|
+
const COLLECT_CONTEXT_CACHE_MAX_ENTRIES = 200;
|
|
9
|
+
const COLLECT_CONTEXT_MIN_EXCERPT_CHARS = 220;
|
|
10
|
+
const COLLECT_CONTEXT_MIN_SUMMARY_CHARS = 120;
|
|
11
|
+
const COLLECT_CONTEXT_MIN_EVIDENCE_CHARS = 80;
|
|
12
|
+
const collectContextCache = new Map();
|
|
13
|
+
function pickEvidenceSnippets(content, maxSnippets) {
|
|
14
|
+
const lines = stripFrontmatterBlock(content)
|
|
15
|
+
.split("\n")
|
|
16
|
+
.map((line) => line.trim())
|
|
17
|
+
.filter((line) => line.length >= 24);
|
|
18
|
+
const prioritized = [
|
|
19
|
+
...lines.filter((line) => /^([-*]|\d+[.)])\s+/.test(line)),
|
|
20
|
+
...lines.filter((line) => /^#{1,6}\s+/.test(line)),
|
|
21
|
+
...lines.filter((line) => !/^([-*]|\d+[.)])\s+/.test(line) && !/^#{1,6}\s+/.test(line)),
|
|
22
|
+
];
|
|
23
|
+
const unique = [...new Set(prioritized)].map((line) => normalizeWhitespace(line.replace(/^#{1,6}\s+/, "").replace(/^[-*]\s+/, "")));
|
|
24
|
+
return unique
|
|
25
|
+
.slice(0, maxSnippets)
|
|
26
|
+
.map((line) => trimWithEllipsis(line, 220));
|
|
27
|
+
}
|
|
28
|
+
function inferCollectContextRelevance(doc, topic) {
|
|
29
|
+
if (!topic) {
|
|
30
|
+
return "medium";
|
|
31
|
+
}
|
|
32
|
+
const normalizedTopic = topic.toLowerCase();
|
|
33
|
+
if (doc.title.toLowerCase().includes(normalizedTopic) ||
|
|
34
|
+
doc.tags.some((tag) => tag.toLowerCase().includes(normalizedTopic))) {
|
|
35
|
+
return "high";
|
|
36
|
+
}
|
|
37
|
+
if (doc.excerpt.toLowerCase().includes(normalizedTopic)) {
|
|
38
|
+
return "medium";
|
|
39
|
+
}
|
|
40
|
+
return "low";
|
|
41
|
+
}
|
|
42
|
+
function buildCollectContextDocument(doc, maxCharsPerDoc, topic) {
|
|
43
|
+
const filename = doc.filePath.split("/").pop() || doc.filePath;
|
|
44
|
+
const title = doc.frontmatter.title || filename.replace(/\.mdx?$/i, "");
|
|
45
|
+
const tags = Array.isArray(doc.frontmatter.tags)
|
|
46
|
+
? doc.frontmatter.tags.filter((tag) => typeof tag === "string")
|
|
47
|
+
: [];
|
|
48
|
+
const sourceContent = stripFrontmatterBlock(doc.content);
|
|
49
|
+
const excerpt = trimWithEllipsis(normalizeWhitespace(sourceContent), maxCharsPerDoc);
|
|
50
|
+
const evidenceSnippets = pickEvidenceSnippets(sourceContent, 2);
|
|
51
|
+
const summary = evidenceSnippets.join(" ").trim() ||
|
|
52
|
+
trimWithEllipsis(excerpt, 220) ||
|
|
53
|
+
title;
|
|
54
|
+
const sourceContentLength = doc.stats?.contentLength ?? sourceContent.length;
|
|
55
|
+
const stats = {
|
|
56
|
+
contentLength: sourceContentLength,
|
|
57
|
+
wordCount: doc.stats?.wordCount ?? 0,
|
|
58
|
+
hasContent: doc.stats?.hasContent ?? sourceContent.trim().length > 0,
|
|
59
|
+
};
|
|
60
|
+
const docHash = doc.contentHash ??
|
|
61
|
+
createHash("sha256").update(sourceContent, "utf8").digest("hex");
|
|
62
|
+
const relevance = inferCollectContextRelevance({
|
|
63
|
+
title,
|
|
64
|
+
tags,
|
|
65
|
+
excerpt,
|
|
66
|
+
}, topic);
|
|
67
|
+
return {
|
|
68
|
+
filename,
|
|
69
|
+
fullPath: doc.filePath,
|
|
70
|
+
title,
|
|
71
|
+
tags,
|
|
72
|
+
doc_hash: docHash,
|
|
73
|
+
summary,
|
|
74
|
+
excerpt,
|
|
75
|
+
evidence_snippets: evidenceSnippets,
|
|
76
|
+
relevance,
|
|
77
|
+
stats,
|
|
78
|
+
backlinks_count: doc.backlinks?.length ?? 0,
|
|
79
|
+
truncated: sourceContentLength > excerpt.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function buildCollectContextMemoryPacket(topic, documents) {
|
|
83
|
+
const keyFacts = documents
|
|
84
|
+
.flatMap((doc) => doc.evidence_snippets.map((snippet) => `${doc.title}: ${snippet}`))
|
|
85
|
+
.slice(0, 10);
|
|
86
|
+
const experienceBullets = documents
|
|
87
|
+
.map((doc) => `${doc.title}: ${trimWithEllipsis(doc.summary, 180)}`)
|
|
88
|
+
.slice(0, 8);
|
|
89
|
+
const sourceRefs = documents.slice(0, 10).map((doc) => ({
|
|
90
|
+
filePath: doc.fullPath,
|
|
91
|
+
title: doc.title,
|
|
92
|
+
relevance: doc.relevance,
|
|
93
|
+
evidenceSnippets: doc.evidence_snippets.slice(0, 2),
|
|
94
|
+
}));
|
|
95
|
+
const topicSummary = documents.length === 0
|
|
96
|
+
? topic
|
|
97
|
+
? `No evidence was collected for topic "${topic}".`
|
|
98
|
+
: "No documents were collected."
|
|
99
|
+
: trimWithEllipsis(documents
|
|
100
|
+
.slice(0, 3)
|
|
101
|
+
.map((doc) => `${doc.title}: ${doc.summary}`)
|
|
102
|
+
.join(" "), 550);
|
|
103
|
+
const openQuestions = [];
|
|
104
|
+
if (documents.length === 0) {
|
|
105
|
+
openQuestions.push(topic
|
|
106
|
+
? `Should we widen the query beyond "${topic}"?`
|
|
107
|
+
: "Should we provide a narrower topic for better precision?");
|
|
108
|
+
}
|
|
109
|
+
if (documents.some((doc) => doc.truncated)) {
|
|
110
|
+
openQuestions.push("Some excerpts were truncated. Do we need full reads for high-priority notes?");
|
|
111
|
+
}
|
|
112
|
+
if (documents.length > 0 && documents.length < 3) {
|
|
113
|
+
openQuestions.push("Do we need additional sources before using this as final memory context?");
|
|
114
|
+
}
|
|
115
|
+
const truncationRatio = documents.length === 0
|
|
116
|
+
? 1
|
|
117
|
+
: documents.filter((doc) => doc.truncated).length / documents.length;
|
|
118
|
+
const baseConfidence = documents.length === 0
|
|
119
|
+
? 0.25
|
|
120
|
+
: Math.min(0.9, 0.45 + documents.length * 0.08);
|
|
121
|
+
const confidence = Number(Math.max(0.1, Math.min(0.95, baseConfidence - truncationRatio * 0.2)).toFixed(2));
|
|
122
|
+
return collectContextMemoryPacketSchema.parse({
|
|
123
|
+
topicSummary,
|
|
124
|
+
keyFacts,
|
|
125
|
+
experienceBullets,
|
|
126
|
+
sourceRefs,
|
|
127
|
+
openQuestions,
|
|
128
|
+
confidence,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function buildCollectContextPayload(params) {
|
|
132
|
+
return collectContextPayloadSchema.parse({
|
|
133
|
+
action: "collect_context",
|
|
134
|
+
scope: params.scope,
|
|
135
|
+
topic: params.topic,
|
|
136
|
+
matched_total: params.matchedTotal,
|
|
137
|
+
total_in_vault: params.totalInVault,
|
|
138
|
+
documents: params.documents,
|
|
139
|
+
memory_packet: buildCollectContextMemoryPacket(params.topic, params.documents),
|
|
140
|
+
memory_mode: params.memoryMode,
|
|
141
|
+
memory_write: params.memoryWriteRequested
|
|
142
|
+
? {
|
|
143
|
+
requested: true,
|
|
144
|
+
status: "failed",
|
|
145
|
+
reason: "Memory note write requested but no write result was provided.",
|
|
146
|
+
}
|
|
147
|
+
: { requested: false, status: "not_requested" },
|
|
148
|
+
cache: params.cache,
|
|
149
|
+
batch: {
|
|
150
|
+
start_cursor: params.startCursor,
|
|
151
|
+
processed_docs: params.processedDocs,
|
|
152
|
+
consumed_candidates: params.consumedCandidates,
|
|
153
|
+
max_docs: params.maxDocs,
|
|
154
|
+
max_chars_per_doc: params.maxCharsPerDoc,
|
|
155
|
+
has_more: params.hasMore,
|
|
156
|
+
continuation_token: params.continuationToken,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function reduceCollectContextBacklinksForBudget(documents) {
|
|
161
|
+
let changed = false;
|
|
162
|
+
for (const doc of documents) {
|
|
163
|
+
if (doc.backlinks_count <= 0) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
let nextCount = doc.backlinks_count;
|
|
167
|
+
if (doc.backlinks_count > 10) {
|
|
168
|
+
nextCount = 10;
|
|
169
|
+
}
|
|
170
|
+
else if (doc.backlinks_count > 5) {
|
|
171
|
+
nextCount = 5;
|
|
172
|
+
}
|
|
173
|
+
else if (doc.backlinks_count > 3) {
|
|
174
|
+
nextCount = 3;
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
nextCount = 0;
|
|
178
|
+
}
|
|
179
|
+
if (nextCount !== doc.backlinks_count) {
|
|
180
|
+
doc.backlinks_count = nextCount;
|
|
181
|
+
changed = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return changed;
|
|
185
|
+
}
|
|
186
|
+
function reduceCollectContextPerDocCharsForBudget(documents) {
|
|
187
|
+
let changed = false;
|
|
188
|
+
for (const doc of documents) {
|
|
189
|
+
if (doc.excerpt.length > COLLECT_CONTEXT_MIN_EXCERPT_CHARS) {
|
|
190
|
+
const nextLength = Math.max(COLLECT_CONTEXT_MIN_EXCERPT_CHARS, Math.floor(doc.excerpt.length * 0.8));
|
|
191
|
+
const nextExcerpt = trimWithEllipsis(doc.excerpt, nextLength);
|
|
192
|
+
if (nextExcerpt !== doc.excerpt) {
|
|
193
|
+
doc.excerpt = nextExcerpt;
|
|
194
|
+
doc.truncated = true;
|
|
195
|
+
changed = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (doc.summary.length > COLLECT_CONTEXT_MIN_SUMMARY_CHARS) {
|
|
199
|
+
const nextLength = Math.max(COLLECT_CONTEXT_MIN_SUMMARY_CHARS, Math.floor(doc.summary.length * 0.85));
|
|
200
|
+
const nextSummary = trimWithEllipsis(doc.summary, nextLength);
|
|
201
|
+
if (nextSummary !== doc.summary) {
|
|
202
|
+
doc.summary = nextSummary;
|
|
203
|
+
doc.truncated = true;
|
|
204
|
+
changed = true;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const nextEvidence = doc.evidence_snippets.map((snippet) => {
|
|
208
|
+
if (snippet.length <= COLLECT_CONTEXT_MIN_EVIDENCE_CHARS) {
|
|
209
|
+
return snippet;
|
|
210
|
+
}
|
|
211
|
+
const nextLength = Math.max(COLLECT_CONTEXT_MIN_EVIDENCE_CHARS, Math.floor(snippet.length * 0.8));
|
|
212
|
+
return trimWithEllipsis(snippet, nextLength);
|
|
213
|
+
});
|
|
214
|
+
if (JSON.stringify(nextEvidence) !== JSON.stringify(doc.evidence_snippets)) {
|
|
215
|
+
doc.evidence_snippets = nextEvidence;
|
|
216
|
+
doc.truncated = true;
|
|
217
|
+
changed = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return changed;
|
|
221
|
+
}
|
|
222
|
+
function buildCollectContextDocHash(documents) {
|
|
223
|
+
return createHash("sha256")
|
|
224
|
+
.update(JSON.stringify(documents.map((doc) => ({
|
|
225
|
+
fullPath: doc.fullPath,
|
|
226
|
+
doc_hash: doc.doc_hash,
|
|
227
|
+
}))), "utf8")
|
|
228
|
+
.digest("hex");
|
|
229
|
+
}
|
|
230
|
+
function buildCollectContextCacheKey(params) {
|
|
231
|
+
return JSON.stringify({
|
|
232
|
+
scope: params.scope,
|
|
233
|
+
topic: params.topic,
|
|
234
|
+
doc_hash: params.docHash,
|
|
235
|
+
schema_version: CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
236
|
+
mode: params.mode,
|
|
237
|
+
start_cursor: params.startCursor,
|
|
238
|
+
max_docs: params.maxDocs,
|
|
239
|
+
max_chars_per_doc: params.maxCharsPerDoc,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function getCollectContextCache(key) {
|
|
243
|
+
const entry = collectContextCache.get(key);
|
|
244
|
+
if (!entry) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
entry.createdAt = Date.now();
|
|
248
|
+
return structuredClone(entry.payload);
|
|
249
|
+
}
|
|
250
|
+
function setCollectContextCache(key, payload) {
|
|
251
|
+
const cachedPayload = structuredClone(payload);
|
|
252
|
+
collectContextCache.set(key, {
|
|
253
|
+
key,
|
|
254
|
+
payload: cachedPayload,
|
|
255
|
+
createdAt: Date.now(),
|
|
256
|
+
});
|
|
257
|
+
if (collectContextCache.size > COLLECT_CONTEXT_CACHE_MAX_ENTRIES) {
|
|
258
|
+
const oldest = [...collectContextCache.values()].sort((a, b) => a.createdAt - b.createdAt)[0];
|
|
259
|
+
if (oldest) {
|
|
260
|
+
collectContextCache.delete(oldest.key);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return structuredClone(cachedPayload);
|
|
264
|
+
}
|
|
265
|
+
function buildCollectContextSourceHash(payload) {
|
|
266
|
+
const normalized = {
|
|
267
|
+
scope: payload.scope,
|
|
268
|
+
topic: payload.topic,
|
|
269
|
+
matched_total: payload.matched_total,
|
|
270
|
+
documents: payload.documents.map((doc) => ({
|
|
271
|
+
fullPath: doc.fullPath,
|
|
272
|
+
doc_hash: doc.doc_hash,
|
|
273
|
+
title: doc.title,
|
|
274
|
+
tags: doc.tags,
|
|
275
|
+
summary: doc.summary,
|
|
276
|
+
relevance: doc.relevance,
|
|
277
|
+
contentLength: doc.stats.contentLength,
|
|
278
|
+
backlinks_count: doc.backlinks_count,
|
|
279
|
+
truncated: doc.truncated,
|
|
280
|
+
})),
|
|
281
|
+
memory_packet: payload.memory_packet,
|
|
282
|
+
};
|
|
283
|
+
return createHash("sha256")
|
|
284
|
+
.update(JSON.stringify(normalized), "utf8")
|
|
285
|
+
.digest("hex");
|
|
286
|
+
}
|
|
287
|
+
function buildContextMemorySnapshotMarkdown(params) {
|
|
288
|
+
const { payload, generatedAt, sourceHash, notePath } = params;
|
|
289
|
+
const canonical = {
|
|
290
|
+
schema_version: CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
291
|
+
generated_at: generatedAt,
|
|
292
|
+
source_hash: sourceHash,
|
|
293
|
+
note_path: notePath,
|
|
294
|
+
action: payload.action,
|
|
295
|
+
scope: payload.scope,
|
|
296
|
+
topic: payload.topic,
|
|
297
|
+
matched_total: payload.matched_total,
|
|
298
|
+
total_in_vault: payload.total_in_vault,
|
|
299
|
+
documents: payload.documents.map((doc) => ({
|
|
300
|
+
fullPath: doc.fullPath,
|
|
301
|
+
doc_hash: doc.doc_hash,
|
|
302
|
+
title: doc.title,
|
|
303
|
+
tags: doc.tags,
|
|
304
|
+
relevance: doc.relevance,
|
|
305
|
+
summary: doc.summary,
|
|
306
|
+
evidence_snippets: doc.evidence_snippets,
|
|
307
|
+
stats: doc.stats,
|
|
308
|
+
backlinks_count: doc.backlinks_count,
|
|
309
|
+
truncated: doc.truncated,
|
|
310
|
+
})),
|
|
311
|
+
memory_packet: payload.memory_packet,
|
|
312
|
+
};
|
|
313
|
+
const keyFacts = payload.memory_packet.keyFacts
|
|
314
|
+
.slice(0, 8)
|
|
315
|
+
.map((fact) => `- ${fact}`)
|
|
316
|
+
.join("\n");
|
|
317
|
+
const experienceBullets = payload.memory_packet.experienceBullets
|
|
318
|
+
.slice(0, 8)
|
|
319
|
+
.map((item) => `- ${item}`)
|
|
320
|
+
.join("\n");
|
|
321
|
+
const sourceRefs = payload.memory_packet.sourceRefs
|
|
322
|
+
.slice(0, 10)
|
|
323
|
+
.map((ref) => `- [${ref.relevance}] ${ref.title} (${ref.filePath})${ref.evidenceSnippets.length > 0 ? `\n - ${ref.evidenceSnippets.join("\n - ")}` : ""}`)
|
|
324
|
+
.join("\n");
|
|
325
|
+
const openQuestions = payload.memory_packet.openQuestions.length > 0
|
|
326
|
+
? payload.memory_packet.openQuestions.map((q) => `- ${q}`).join("\n")
|
|
327
|
+
: "- None";
|
|
328
|
+
return [
|
|
329
|
+
"# Context Memory Snapshot v1",
|
|
330
|
+
"",
|
|
331
|
+
`- generated_at: ${generatedAt}`,
|
|
332
|
+
`- source_hash: ${sourceHash}`,
|
|
333
|
+
`- schema_version: ${CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION}`,
|
|
334
|
+
`- topic: ${payload.topic ?? "null"}`,
|
|
335
|
+
`- scope: ${payload.scope}`,
|
|
336
|
+
`- matched_total: ${payload.matched_total}`,
|
|
337
|
+
`- total_in_vault: ${payload.total_in_vault}`,
|
|
338
|
+
"",
|
|
339
|
+
"## Topic Summary",
|
|
340
|
+
payload.memory_packet.topicSummary || "(empty)",
|
|
341
|
+
"",
|
|
342
|
+
"## Key Facts",
|
|
343
|
+
keyFacts || "- None",
|
|
344
|
+
"",
|
|
345
|
+
"## Experience Bullets",
|
|
346
|
+
experienceBullets || "- None",
|
|
347
|
+
"",
|
|
348
|
+
"## Source Refs",
|
|
349
|
+
sourceRefs || "- None",
|
|
350
|
+
"",
|
|
351
|
+
"## Open Questions",
|
|
352
|
+
openQuestions,
|
|
353
|
+
"",
|
|
354
|
+
"## Confidence",
|
|
355
|
+
`${payload.memory_packet.confidence}`,
|
|
356
|
+
"",
|
|
357
|
+
"## Canonical JSON",
|
|
358
|
+
"```json",
|
|
359
|
+
JSON.stringify(canonical, null, 2),
|
|
360
|
+
"```",
|
|
361
|
+
"",
|
|
362
|
+
].join("\n");
|
|
363
|
+
}
|
|
364
|
+
async function writeContextMemorySnapshotNote(vaultManager, payload) {
|
|
365
|
+
const generatedAt = new Date().toISOString();
|
|
366
|
+
const sourceHash = buildCollectContextSourceHash(payload);
|
|
367
|
+
const markdown = buildContextMemorySnapshotMarkdown({
|
|
368
|
+
payload,
|
|
369
|
+
generatedAt,
|
|
370
|
+
sourceHash,
|
|
371
|
+
notePath: CONTEXT_MEMORY_SNAPSHOT_NOTE_PATH,
|
|
372
|
+
});
|
|
373
|
+
try {
|
|
374
|
+
await vaultManager.writeRawDocument(CONTEXT_MEMORY_SNAPSHOT_NOTE_PATH, markdown);
|
|
375
|
+
return {
|
|
376
|
+
requested: true,
|
|
377
|
+
status: "written",
|
|
378
|
+
note_path: CONTEXT_MEMORY_SNAPSHOT_NOTE_PATH,
|
|
379
|
+
generated_at: generatedAt,
|
|
380
|
+
source_hash: sourceHash,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return {
|
|
385
|
+
requested: true,
|
|
386
|
+
status: "failed",
|
|
387
|
+
note_path: CONTEXT_MEMORY_SNAPSHOT_NOTE_PATH,
|
|
388
|
+
generated_at: generatedAt,
|
|
389
|
+
source_hash: sourceHash,
|
|
390
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function trimCollectContextPayloadToOutputLimit(payload, maxOutputChars) {
|
|
395
|
+
const next = structuredClone(payload);
|
|
396
|
+
let clamped = false;
|
|
397
|
+
let droppedDocCount = 0;
|
|
398
|
+
if (jsonCharLength(next) > maxOutputChars &&
|
|
399
|
+
reduceCollectContextBacklinksForBudget(next.documents)) {
|
|
400
|
+
clamped = true;
|
|
401
|
+
}
|
|
402
|
+
while (jsonCharLength(next) > maxOutputChars) {
|
|
403
|
+
const reduced = reduceCollectContextPerDocCharsForBudget(next.documents);
|
|
404
|
+
if (!reduced) {
|
|
405
|
+
break;
|
|
406
|
+
}
|
|
407
|
+
clamped = true;
|
|
408
|
+
}
|
|
409
|
+
while (jsonCharLength(next) > maxOutputChars && next.documents.length > 1) {
|
|
410
|
+
next.documents.pop();
|
|
411
|
+
droppedDocCount++;
|
|
412
|
+
clamped = true;
|
|
413
|
+
}
|
|
414
|
+
if (droppedDocCount > 0) {
|
|
415
|
+
next.batch.processed_docs = next.documents.length;
|
|
416
|
+
next.batch.consumed_candidates = Math.max(0, next.batch.consumed_candidates - droppedDocCount);
|
|
417
|
+
const continuationCursor = next.batch.start_cursor + next.batch.consumed_candidates;
|
|
418
|
+
next.batch.has_more = true;
|
|
419
|
+
next.batch.continuation_token = encodeCollectContextContinuationToken({
|
|
420
|
+
v: 1,
|
|
421
|
+
cursor: continuationCursor,
|
|
422
|
+
scope: next.scope,
|
|
423
|
+
topic: next.topic,
|
|
424
|
+
maxDocs: next.batch.max_docs,
|
|
425
|
+
maxCharsPerDoc: next.batch.max_chars_per_doc,
|
|
426
|
+
memoryMode: next.memory_mode,
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
while (jsonCharLength(next) > maxOutputChars &&
|
|
430
|
+
next.memory_packet.keyFacts.length > 3) {
|
|
431
|
+
next.memory_packet.keyFacts.pop();
|
|
432
|
+
clamped = true;
|
|
433
|
+
}
|
|
434
|
+
while (jsonCharLength(next) > maxOutputChars &&
|
|
435
|
+
next.memory_packet.experienceBullets.length > 3) {
|
|
436
|
+
next.memory_packet.experienceBullets.pop();
|
|
437
|
+
clamped = true;
|
|
438
|
+
}
|
|
439
|
+
while (jsonCharLength(next) > maxOutputChars &&
|
|
440
|
+
next.memory_packet.sourceRefs.length > 2) {
|
|
441
|
+
next.memory_packet.sourceRefs.pop();
|
|
442
|
+
clamped = true;
|
|
443
|
+
}
|
|
444
|
+
while (jsonCharLength(next) > maxOutputChars &&
|
|
445
|
+
next.memory_packet.openQuestions.length > 1) {
|
|
446
|
+
next.memory_packet.openQuestions.pop();
|
|
447
|
+
clamped = true;
|
|
448
|
+
}
|
|
449
|
+
if (jsonCharLength(next) > maxOutputChars &&
|
|
450
|
+
next.memory_packet.topicSummary.length > 200) {
|
|
451
|
+
next.memory_packet.topicSummary = trimWithEllipsis(next.memory_packet.topicSummary, 200);
|
|
452
|
+
clamped = true;
|
|
453
|
+
}
|
|
454
|
+
return { payload: next, clamped };
|
|
455
|
+
}
|
|
456
|
+
function encodeCollectContextContinuationToken(token) {
|
|
457
|
+
return Buffer.from(JSON.stringify(token), "utf8").toString("base64url");
|
|
458
|
+
}
|
|
459
|
+
function decodeCollectContextContinuationToken(rawToken) {
|
|
460
|
+
try {
|
|
461
|
+
const parsed = JSON.parse(Buffer.from(rawToken, "base64url").toString("utf8"));
|
|
462
|
+
const validation = collectContextTokenV1Schema.safeParse(parsed);
|
|
463
|
+
if (!validation.success) {
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
v: 1,
|
|
468
|
+
cursor: Math.floor(validation.data.cursor),
|
|
469
|
+
scope: validation.data.scope,
|
|
470
|
+
topic: validation.data.topic,
|
|
471
|
+
maxDocs: Math.floor(validation.data.maxDocs),
|
|
472
|
+
maxCharsPerDoc: Math.floor(validation.data.maxCharsPerDoc),
|
|
473
|
+
memoryMode: validation.data.memoryMode,
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
export async function collectContext(vaultManager, params) {
|
|
481
|
+
await vaultManager.initialize();
|
|
482
|
+
const mode = resolveCompressionMode(params);
|
|
483
|
+
const continuation = params.continuationToken
|
|
484
|
+
? decodeCollectContextContinuationToken(params.continuationToken)
|
|
485
|
+
: null;
|
|
486
|
+
if (params.continuationToken && !continuation) {
|
|
487
|
+
return createToolError("Invalid continuationToken for collect_context action", "Use the continuation_token value returned by a previous collect_context call.");
|
|
488
|
+
}
|
|
489
|
+
const scope = continuation?.scope ?? params.scope ?? "topic";
|
|
490
|
+
const topic = continuation?.topic ?? (params.topic?.trim() ? params.topic.trim() : null);
|
|
491
|
+
const maxDocs = continuation?.maxDocs ?? params.maxDocs ?? 20;
|
|
492
|
+
const maxCharsPerDoc = continuation?.maxCharsPerDoc ?? params.maxCharsPerDoc ?? 1800;
|
|
493
|
+
const memoryMode = continuation?.memoryMode ?? params.memoryMode ?? "response_only";
|
|
494
|
+
const startCursor = continuation?.cursor ?? 0;
|
|
495
|
+
if (scope === "topic" && !topic) {
|
|
496
|
+
return createToolError("topic parameter is required for collect_context when scope='topic'", 'Provide a topic, e.g. { action: "collect_context", topic: "next.js", scope: "topic" }');
|
|
497
|
+
}
|
|
498
|
+
const [allDocuments, matchedCandidates] = await Promise.all([
|
|
499
|
+
vaultManager.getAllDocuments(),
|
|
500
|
+
scope === "all"
|
|
501
|
+
? vaultManager.getAllDocuments()
|
|
502
|
+
: vaultManager.searchDocuments(topic ?? ""),
|
|
503
|
+
]);
|
|
504
|
+
let orderedCandidates = [...matchedCandidates].sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
505
|
+
// Apply Reranking if topic is present and we have candidates
|
|
506
|
+
if (topic && orderedCandidates.length > 0) {
|
|
507
|
+
// Only rerank a reasonable number of top candidates to preserve performance
|
|
508
|
+
const rerankCandidatesLimit = 30;
|
|
509
|
+
const candidatesToRerank = orderedCandidates.slice(0, rerankCandidatesLimit);
|
|
510
|
+
try {
|
|
511
|
+
// Fetch contents for these candidates to perform reranking
|
|
512
|
+
const candidateContents = await Promise.all(candidatesToRerank.map(async (c) => {
|
|
513
|
+
const doc = await vaultManager.getDocumentInfo(c.filePath, {
|
|
514
|
+
maxContentPreview: 1000, // Sufficient for reranking
|
|
515
|
+
});
|
|
516
|
+
return doc?.content || "";
|
|
517
|
+
}));
|
|
518
|
+
const rerankedResults = await rerankerClient.rerank(topic, candidateContents, maxDocs);
|
|
519
|
+
const reranked = rerankedResults.map((result) => candidatesToRerank[result.index]);
|
|
520
|
+
const remaining = orderedCandidates.filter((c) => !reranked.some((r) => r.filePath === c.filePath));
|
|
521
|
+
orderedCandidates = [...reranked, ...remaining];
|
|
522
|
+
}
|
|
523
|
+
catch (error) {
|
|
524
|
+
debugLogger.error("[CollectContext] Reranking failed:", error);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const emptyDocHash = buildCollectContextDocHash([]);
|
|
528
|
+
const emptyCacheKey = buildCollectContextCacheKey({
|
|
529
|
+
scope,
|
|
530
|
+
topic,
|
|
531
|
+
docHash: emptyDocHash,
|
|
532
|
+
mode: memoryMode,
|
|
533
|
+
startCursor,
|
|
534
|
+
maxDocs,
|
|
535
|
+
maxCharsPerDoc,
|
|
536
|
+
});
|
|
537
|
+
if (orderedCandidates.length === 0 ||
|
|
538
|
+
startCursor >= orderedCandidates.length) {
|
|
539
|
+
const cachedEmptyPayload = getCollectContextCache(emptyCacheKey);
|
|
540
|
+
const emptyCacheHit = cachedEmptyPayload !== null;
|
|
541
|
+
let emptyPayload = cachedEmptyPayload ??
|
|
542
|
+
setCollectContextCache(emptyCacheKey, buildCollectContextPayload({
|
|
543
|
+
scope,
|
|
544
|
+
topic,
|
|
545
|
+
matchedTotal: orderedCandidates.length,
|
|
546
|
+
totalInVault: allDocuments.length,
|
|
547
|
+
documents: [],
|
|
548
|
+
memoryMode,
|
|
549
|
+
memoryWriteRequested: false,
|
|
550
|
+
cache: {
|
|
551
|
+
key: emptyCacheKey,
|
|
552
|
+
hit: false,
|
|
553
|
+
schema_version: CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
554
|
+
topic,
|
|
555
|
+
doc_hash: emptyDocHash,
|
|
556
|
+
mode: memoryMode,
|
|
557
|
+
},
|
|
558
|
+
startCursor,
|
|
559
|
+
processedDocs: 0,
|
|
560
|
+
consumedCandidates: 0,
|
|
561
|
+
maxDocs,
|
|
562
|
+
maxCharsPerDoc,
|
|
563
|
+
hasMore: false,
|
|
564
|
+
continuationToken: null,
|
|
565
|
+
}));
|
|
566
|
+
if (emptyCacheHit && emptyPayload.cache) {
|
|
567
|
+
emptyPayload = collectContextPayloadSchema.parse({
|
|
568
|
+
...emptyPayload,
|
|
569
|
+
cache: {
|
|
570
|
+
...emptyPayload.cache,
|
|
571
|
+
hit: true,
|
|
572
|
+
},
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
if (memoryMode !== "response_only") {
|
|
576
|
+
const memoryWrite = await writeContextMemorySnapshotNote(vaultManager, emptyPayload);
|
|
577
|
+
emptyPayload = collectContextPayloadSchema.parse({
|
|
578
|
+
...emptyPayload,
|
|
579
|
+
memory_mode: memoryMode,
|
|
580
|
+
memory_write: memoryWrite,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
const finalizedPayload = collectContextResponseDataSchema.parse(finalizePayloadWithCompression(emptyPayload, {
|
|
584
|
+
mode,
|
|
585
|
+
source_chars: 0,
|
|
586
|
+
max_output_chars: params.maxOutputChars ??
|
|
587
|
+
(mode === "none"
|
|
588
|
+
? null
|
|
589
|
+
: ACTION_DEFAULT_MAX_OUTPUT_CHARS.collect_context[mode]),
|
|
590
|
+
truncated: false,
|
|
591
|
+
expand_hint: "If more context is needed, rerun collect_context with broader scope or topic.",
|
|
592
|
+
}));
|
|
593
|
+
return {
|
|
594
|
+
isError: false,
|
|
595
|
+
content: [
|
|
596
|
+
{ type: "text", text: JSON.stringify(finalizedPayload, null, 2) },
|
|
597
|
+
],
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const maxOutputChars = params.maxOutputChars ??
|
|
601
|
+
(mode === "none"
|
|
602
|
+
? null
|
|
603
|
+
: ACTION_DEFAULT_MAX_OUTPUT_CHARS.collect_context[mode]);
|
|
604
|
+
const documents = [];
|
|
605
|
+
let sourceChars = 0;
|
|
606
|
+
let consumedCandidates = 0;
|
|
607
|
+
let outputCapClamped = false;
|
|
608
|
+
let scannedIndex = startCursor;
|
|
609
|
+
while (scannedIndex < orderedCandidates.length &&
|
|
610
|
+
documents.length < maxDocs) {
|
|
611
|
+
const candidate = orderedCandidates[scannedIndex];
|
|
612
|
+
scannedIndex++;
|
|
613
|
+
consumedCandidates++;
|
|
614
|
+
const enrichedDoc = await vaultManager.getDocumentInfo(candidate.filePath, {
|
|
615
|
+
includeStats: true,
|
|
616
|
+
includeBacklinks: true,
|
|
617
|
+
includeContentHash: true,
|
|
618
|
+
maxContentPreview: maxCharsPerDoc,
|
|
619
|
+
});
|
|
620
|
+
if (!enrichedDoc) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
const nextDoc = buildCollectContextDocument(enrichedDoc, maxCharsPerDoc, topic);
|
|
624
|
+
documents.push(nextDoc);
|
|
625
|
+
sourceChars += nextDoc.stats.contentLength;
|
|
626
|
+
if (typeof maxOutputChars === "number") {
|
|
627
|
+
const buildProvisionalPayload = () => buildCollectContextPayload({
|
|
628
|
+
scope,
|
|
629
|
+
topic,
|
|
630
|
+
matchedTotal: orderedCandidates.length,
|
|
631
|
+
totalInVault: allDocuments.length,
|
|
632
|
+
documents,
|
|
633
|
+
memoryMode,
|
|
634
|
+
memoryWriteRequested: false,
|
|
635
|
+
startCursor,
|
|
636
|
+
processedDocs: documents.length,
|
|
637
|
+
consumedCandidates,
|
|
638
|
+
maxDocs,
|
|
639
|
+
maxCharsPerDoc,
|
|
640
|
+
hasMore: scannedIndex < orderedCandidates.length,
|
|
641
|
+
continuationToken: null,
|
|
642
|
+
});
|
|
643
|
+
let provisionalPayload = buildProvisionalPayload();
|
|
644
|
+
if (jsonCharLength(provisionalPayload) > maxOutputChars) {
|
|
645
|
+
outputCapClamped = true;
|
|
646
|
+
if (reduceCollectContextBacklinksForBudget(documents)) {
|
|
647
|
+
provisionalPayload = buildProvisionalPayload();
|
|
648
|
+
}
|
|
649
|
+
while (jsonCharLength(provisionalPayload) > maxOutputChars) {
|
|
650
|
+
const reduced = reduceCollectContextPerDocCharsForBudget(documents);
|
|
651
|
+
if (!reduced) {
|
|
652
|
+
break;
|
|
653
|
+
}
|
|
654
|
+
provisionalPayload = buildProvisionalPayload();
|
|
655
|
+
}
|
|
656
|
+
if (jsonCharLength(provisionalPayload) > maxOutputChars) {
|
|
657
|
+
if (documents.length > 1) {
|
|
658
|
+
const removedDoc = documents.pop();
|
|
659
|
+
if (removedDoc) {
|
|
660
|
+
sourceChars -= removedDoc.stats.contentLength;
|
|
661
|
+
}
|
|
662
|
+
consumedCandidates--;
|
|
663
|
+
scannedIndex--;
|
|
664
|
+
}
|
|
665
|
+
break;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const nextCursor = startCursor + consumedCandidates;
|
|
671
|
+
const hasMore = nextCursor < orderedCandidates.length;
|
|
672
|
+
const continuationToken = hasMore
|
|
673
|
+
? encodeCollectContextContinuationToken({
|
|
674
|
+
v: 1,
|
|
675
|
+
cursor: nextCursor,
|
|
676
|
+
scope,
|
|
677
|
+
topic,
|
|
678
|
+
maxDocs,
|
|
679
|
+
maxCharsPerDoc,
|
|
680
|
+
memoryMode,
|
|
681
|
+
})
|
|
682
|
+
: null;
|
|
683
|
+
const docHash = buildCollectContextDocHash(documents);
|
|
684
|
+
const cacheKey = buildCollectContextCacheKey({
|
|
685
|
+
scope,
|
|
686
|
+
topic,
|
|
687
|
+
docHash: docHash,
|
|
688
|
+
mode: memoryMode,
|
|
689
|
+
startCursor,
|
|
690
|
+
maxDocs,
|
|
691
|
+
maxCharsPerDoc,
|
|
692
|
+
});
|
|
693
|
+
const cachedPayload = getCollectContextCache(cacheKey);
|
|
694
|
+
const cacheHit = cachedPayload !== null;
|
|
695
|
+
const basePayload = cachedPayload ??
|
|
696
|
+
setCollectContextCache(cacheKey, buildCollectContextPayload({
|
|
697
|
+
scope,
|
|
698
|
+
topic,
|
|
699
|
+
matchedTotal: orderedCandidates.length,
|
|
700
|
+
totalInVault: allDocuments.length,
|
|
701
|
+
documents,
|
|
702
|
+
memoryMode,
|
|
703
|
+
memoryWriteRequested: false,
|
|
704
|
+
cache: {
|
|
705
|
+
key: cacheKey,
|
|
706
|
+
hit: false,
|
|
707
|
+
schema_version: CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
708
|
+
topic,
|
|
709
|
+
doc_hash: docHash,
|
|
710
|
+
mode: memoryMode,
|
|
711
|
+
},
|
|
712
|
+
startCursor,
|
|
713
|
+
processedDocs: documents.length,
|
|
714
|
+
consumedCandidates,
|
|
715
|
+
maxDocs,
|
|
716
|
+
maxCharsPerDoc,
|
|
717
|
+
hasMore,
|
|
718
|
+
continuationToken,
|
|
719
|
+
}));
|
|
720
|
+
let payloadForCompression = cacheHit
|
|
721
|
+
? collectContextPayloadSchema.parse({
|
|
722
|
+
...basePayload,
|
|
723
|
+
cache: basePayload.cache
|
|
724
|
+
? {
|
|
725
|
+
...basePayload.cache,
|
|
726
|
+
hit: true,
|
|
727
|
+
}
|
|
728
|
+
: {
|
|
729
|
+
key: cacheKey,
|
|
730
|
+
hit: true,
|
|
731
|
+
schema_version: CONTEXT_MEMORY_SNAPSHOT_SCHEMA_VERSION,
|
|
732
|
+
topic,
|
|
733
|
+
doc_hash: docHash,
|
|
734
|
+
mode: memoryMode,
|
|
735
|
+
},
|
|
736
|
+
})
|
|
737
|
+
: basePayload;
|
|
738
|
+
if (memoryMode !== "response_only") {
|
|
739
|
+
const memoryWrite = await writeContextMemorySnapshotNote(vaultManager, basePayload);
|
|
740
|
+
payloadForCompression = collectContextPayloadSchema.parse({
|
|
741
|
+
...basePayload,
|
|
742
|
+
memory_mode: memoryMode,
|
|
743
|
+
memory_write: memoryWrite,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
if (typeof maxOutputChars === "number") {
|
|
747
|
+
const trimmed = trimCollectContextPayloadToOutputLimit(payloadForCompression, maxOutputChars);
|
|
748
|
+
payloadForCompression = trimmed.payload;
|
|
749
|
+
outputCapClamped = outputCapClamped || trimmed.clamped;
|
|
750
|
+
}
|
|
751
|
+
const finalizedPayload = collectContextResponseDataSchema.parse(finalizePayloadWithCompression(payloadForCompression, {
|
|
752
|
+
mode,
|
|
753
|
+
source_chars: sourceChars,
|
|
754
|
+
max_output_chars: maxOutputChars,
|
|
755
|
+
truncated: hasMore || documents.some((doc) => doc.truncated) || outputCapClamped,
|
|
756
|
+
expand_hint: "If has_more is true, call collect_context again with continuationToken.",
|
|
757
|
+
}));
|
|
758
|
+
return {
|
|
759
|
+
isError: false,
|
|
760
|
+
content: [
|
|
761
|
+
{ type: "text", text: JSON.stringify(finalizedPayload, null, 2) },
|
|
762
|
+
],
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
//# sourceMappingURL=collect_context.js.map
|