@downcity/plugins 1.0.253 → 1.0.254
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/bin/memory/Action.d.ts +27 -36
- package/bin/memory/Action.d.ts.map +1 -1
- package/bin/memory/Action.js +71 -311
- package/bin/memory/Action.js.map +1 -1
- package/bin/memory/Index.d.ts +3 -1
- package/bin/memory/Index.d.ts.map +1 -1
- package/bin/memory/Index.js +3 -1
- package/bin/memory/Index.js.map +1 -1
- package/bin/memory/MemoryPlugin.d.ts +20 -37
- package/bin/memory/MemoryPlugin.d.ts.map +1 -1
- package/bin/memory/MemoryPlugin.js +231 -233
- package/bin/memory/MemoryPlugin.js.map +1 -1
- package/bin/memory/adapters/FileMemoryStorageAdapter.d.ts +43 -0
- package/bin/memory/adapters/FileMemoryStorageAdapter.d.ts.map +1 -0
- package/bin/memory/adapters/FileMemoryStorageAdapter.js +163 -0
- package/bin/memory/adapters/FileMemoryStorageAdapter.js.map +1 -0
- package/bin/memory/providers/BuiltinMemoryProvider.d.ts +63 -0
- package/bin/memory/providers/BuiltinMemoryProvider.d.ts.map +1 -0
- package/bin/memory/providers/BuiltinMemoryProvider.js +772 -0
- package/bin/memory/providers/BuiltinMemoryProvider.js.map +1 -0
- package/bin/memory/runtime/SystemProvider.d.ts +7 -12
- package/bin/memory/runtime/SystemProvider.d.ts.map +1 -1
- package/bin/memory/runtime/SystemProvider.js +30 -89
- package/bin/memory/runtime/SystemProvider.js.map +1 -1
- package/bin/memory/types/BuiltinMemoryProvider.d.ts +88 -0
- package/bin/memory/types/BuiltinMemoryProvider.d.ts.map +1 -0
- package/bin/memory/types/BuiltinMemoryProvider.js +9 -0
- package/bin/memory/types/BuiltinMemoryProvider.js.map +1 -0
- package/bin/memory/types/Memory.d.ts +249 -408
- package/bin/memory/types/Memory.d.ts.map +1 -1
- package/bin/memory/types/Memory.js +4 -4
- package/bin/memory/types/MemoryStorage.d.ts +35 -0
- package/bin/memory/types/MemoryStorage.d.ts.map +1 -0
- package/bin/memory/types/MemoryStorage.js +10 -0
- package/bin/memory/types/MemoryStorage.js.map +1 -0
- package/bin/memory.d.ts +6 -2
- package/bin/memory.d.ts.map +1 -1
- package/bin/memory.js +3 -1
- package/bin/memory.js.map +1 -1
- package/package.json +3 -2
- package/scripts/memory-plugin.test.mjs +227 -0
- package/scripts/plugin-subpaths.test.mjs +8 -0
- package/src/memory/Action.ts +106 -377
- package/src/memory/Index.ts +6 -1
- package/src/memory/MemoryPlugin.ts +254 -246
- package/src/memory/adapters/FileMemoryStorageAdapter.ts +197 -0
- package/src/memory/providers/BuiltinMemoryProvider.ts +905 -0
- package/src/memory/runtime/SystemProvider.ts +32 -95
- package/src/memory/types/BuiltinMemoryProvider.ts +121 -0
- package/src/memory/types/Memory.ts +322 -456
- package/src/memory/types/MemoryStorage.ts +44 -0
- package/src/memory.ts +48 -24
- package/bin/memory/runtime/Search.d.ts +0 -19
- package/bin/memory/runtime/Search.d.ts.map +0 -1
- package/bin/memory/runtime/Search.js +0 -262
- package/bin/memory/runtime/Search.js.map +0 -1
- package/bin/memory/runtime/Store.d.ts +0 -42
- package/bin/memory/runtime/Store.d.ts.map +0 -1
- package/bin/memory/runtime/Store.js +0 -94
- package/bin/memory/runtime/Store.js.map +0 -1
- package/bin/memory/runtime/Writer.d.ts +0 -61
- package/bin/memory/runtime/Writer.d.ts.map +0 -1
- package/bin/memory/runtime/Writer.js +0 -274
- package/bin/memory/runtime/Writer.js.map +0 -1
- package/src/memory/runtime/Search.ts +0 -327
- package/src/memory/runtime/Store.ts +0 -158
- package/src/memory/runtime/Writer.ts +0 -351
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory Writer(LLM Wiki 文件读写与路径安全)。
|
|
3
|
-
*
|
|
4
|
-
* 关键点(中文)
|
|
5
|
-
* - `sources/` 保存原始证据,`wiki/` 保存整理后的长期记忆。
|
|
6
|
-
* - 所有外部传入路径都必须限制在 Memory Plugin 自己的 `.downcity/memory` 目录内。
|
|
7
|
-
* - 无 LLM 注入时使用 append fallback,保证 MemoryPlugin 仍然可用。
|
|
8
|
-
*/
|
|
9
|
-
import fs from "node:fs/promises";
|
|
10
|
-
import path from "node:path";
|
|
11
|
-
function nowIso() {
|
|
12
|
-
return new Date().toISOString();
|
|
13
|
-
}
|
|
14
|
-
function dateStamp(now = new Date()) {
|
|
15
|
-
return now.toISOString().slice(0, 10);
|
|
16
|
-
}
|
|
17
|
-
function toRelPath(rootPath, absPath) {
|
|
18
|
-
return path.relative(rootPath, absPath).replace(/\\/g, "/");
|
|
19
|
-
}
|
|
20
|
-
function isWithin(parentPath, childPath) {
|
|
21
|
-
const parent = path.resolve(parentPath);
|
|
22
|
-
const child = path.resolve(childPath);
|
|
23
|
-
if (parent === child)
|
|
24
|
-
return true;
|
|
25
|
-
return child.startsWith(`${parent}${path.sep}`);
|
|
26
|
-
}
|
|
27
|
-
function slugify(value) {
|
|
28
|
-
const text = String(value || "")
|
|
29
|
-
.trim()
|
|
30
|
-
.toLowerCase()
|
|
31
|
-
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
32
|
-
.replace(/^-+|-+$/g, "")
|
|
33
|
-
.slice(0, 80);
|
|
34
|
-
return text || "inbox";
|
|
35
|
-
}
|
|
36
|
-
function normalizeMarkdownPath(value) {
|
|
37
|
-
const clean = String(value || "")
|
|
38
|
-
.replace(/\\/g, "/")
|
|
39
|
-
.replace(/^\/+/, "")
|
|
40
|
-
.trim();
|
|
41
|
-
if (!clean)
|
|
42
|
-
return "";
|
|
43
|
-
return clean.toLowerCase().endsWith(".md") ? clean : `${clean}.md`;
|
|
44
|
-
}
|
|
45
|
-
function resolveWikiPath(context, requestedPath, title) {
|
|
46
|
-
const memoryRoot = path.join(context.workspace_path, ".downcity", "memory");
|
|
47
|
-
const wikiRoot = path.join(memoryRoot, "wiki");
|
|
48
|
-
const normalized = normalizeMarkdownPath(requestedPath || slugify(title || "inbox"));
|
|
49
|
-
const withoutPrefix = normalized
|
|
50
|
-
.replace(/^\.downcity\/memory\/wiki\//, "")
|
|
51
|
-
.replace(/^wiki\//, "");
|
|
52
|
-
const absPath = path.resolve(wikiRoot, withoutPrefix);
|
|
53
|
-
if (!isWithin(wikiRoot, absPath)) {
|
|
54
|
-
throw new Error("wiki path is not allowed");
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
absPath,
|
|
58
|
-
relPath: toRelPath(context.workspace_path, absPath),
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
function resolveAllowedReadPath(context, relPath) {
|
|
62
|
-
const normalized = relPath.replace(/\\/g, "/").replace(/^\/+/, "").trim();
|
|
63
|
-
if (!normalized) {
|
|
64
|
-
throw new Error("path is required");
|
|
65
|
-
}
|
|
66
|
-
const absPath = path.resolve(context.workspace_path, normalized);
|
|
67
|
-
const memoryRoot = path.resolve(path.join(context.workspace_path, ".downcity", "memory"));
|
|
68
|
-
const isMemoryPath = isWithin(memoryRoot, absPath);
|
|
69
|
-
if (!isMemoryPath) {
|
|
70
|
-
throw new Error("path is not allowed");
|
|
71
|
-
}
|
|
72
|
-
if (!normalized.toLowerCase().endsWith(".md")) {
|
|
73
|
-
throw new Error("path must be markdown");
|
|
74
|
-
}
|
|
75
|
-
return absPath;
|
|
76
|
-
}
|
|
77
|
-
function buildSourceEntry(content, source) {
|
|
78
|
-
const clean = String(content || "").trim();
|
|
79
|
-
const sourceText = source ? `Source: ${source}\n\n` : "";
|
|
80
|
-
return `## ${nowIso()}\n\n${sourceText}${clean}\n`;
|
|
81
|
-
}
|
|
82
|
-
function buildFallbackWikiEntry(payload) {
|
|
83
|
-
const lines = [
|
|
84
|
-
`## ${nowIso()}`,
|
|
85
|
-
"",
|
|
86
|
-
...(payload.instruction ? [`Instruction: ${payload.instruction}`, ""] : []),
|
|
87
|
-
payload.content.trim(),
|
|
88
|
-
"",
|
|
89
|
-
...(payload.sourcePath ? [`Source: ${payload.sourcePath}`, ""] : []),
|
|
90
|
-
];
|
|
91
|
-
return lines.join("\n");
|
|
92
|
-
}
|
|
93
|
-
function ensureFrontmatter(draft) {
|
|
94
|
-
const content = String(draft.content || "").trim();
|
|
95
|
-
if (content.startsWith("---")) {
|
|
96
|
-
return `${content}\n`;
|
|
97
|
-
}
|
|
98
|
-
const title = String(draft.title || "Memory Page").trim();
|
|
99
|
-
const tags = draft.tags && draft.tags.length > 0
|
|
100
|
-
? draft.tags.map((tag) => String(tag).trim()).filter(Boolean)
|
|
101
|
-
: ["memory"];
|
|
102
|
-
return [
|
|
103
|
-
"---",
|
|
104
|
-
`title: ${JSON.stringify(title)}`,
|
|
105
|
-
`date: ${dateStamp()}`,
|
|
106
|
-
`tags: [${tags.map((tag) => JSON.stringify(tag)).join(", ")}]`,
|
|
107
|
-
"---",
|
|
108
|
-
"",
|
|
109
|
-
content,
|
|
110
|
-
"",
|
|
111
|
-
].join("\n");
|
|
112
|
-
}
|
|
113
|
-
async function readTextIfExists(absPath) {
|
|
114
|
-
try {
|
|
115
|
-
return String(await fs.readFile(absPath, "utf-8"));
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return "";
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* 初始化 memory wiki 目录结构(幂等)。
|
|
123
|
-
*/
|
|
124
|
-
export async function ensureMemoryDirectories(rootPath) {
|
|
125
|
-
const memoryRoot = path.join(rootPath, ".downcity", "memory");
|
|
126
|
-
await fs.mkdir(path.join(memoryRoot, "wiki"), { recursive: true });
|
|
127
|
-
await fs.mkdir(path.join(memoryRoot, "sources", "manual"), { recursive: true });
|
|
128
|
-
await fs.mkdir(path.join(memoryRoot, "sources", "sessions"), { recursive: true });
|
|
129
|
-
const indexPath = path.join(memoryRoot, "wiki", "index.md");
|
|
130
|
-
const exists = await fs
|
|
131
|
-
.access(indexPath)
|
|
132
|
-
.then(() => true)
|
|
133
|
-
.catch(() => false);
|
|
134
|
-
if (!exists) {
|
|
135
|
-
await fs.writeFile(indexPath, [
|
|
136
|
-
"---",
|
|
137
|
-
'title: "Memory Index"',
|
|
138
|
-
`date: ${dateStamp()}`,
|
|
139
|
-
'tags: ["memory", "index"]',
|
|
140
|
-
"---",
|
|
141
|
-
"",
|
|
142
|
-
"This is the root index for the agent-maintained LLM Wiki memory.",
|
|
143
|
-
"",
|
|
144
|
-
].join("\n"), "utf-8");
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* 读取指定记忆文件(支持行区间)。
|
|
149
|
-
*/
|
|
150
|
-
export async function readMemory(context, payload) {
|
|
151
|
-
const requestedPath = String(payload.path || "").trim();
|
|
152
|
-
if (!requestedPath) {
|
|
153
|
-
throw new Error("path is required");
|
|
154
|
-
}
|
|
155
|
-
const absPath = resolveAllowedReadPath(context, requestedPath);
|
|
156
|
-
const relPath = toRelPath(context.workspace_path, absPath);
|
|
157
|
-
const exists = await fs
|
|
158
|
-
.access(absPath)
|
|
159
|
-
.then(() => true)
|
|
160
|
-
.catch(() => false);
|
|
161
|
-
if (!exists) {
|
|
162
|
-
return { path: relPath, text: "", missing: true };
|
|
163
|
-
}
|
|
164
|
-
const content = String(await fs.readFile(absPath, "utf-8"));
|
|
165
|
-
const from = payload.from ? Math.max(1, Math.floor(payload.from)) : undefined;
|
|
166
|
-
const lines = payload.lines ? Math.max(1, Math.floor(payload.lines)) : undefined;
|
|
167
|
-
if (!from && !lines) {
|
|
168
|
-
return { path: relPath, text: content };
|
|
169
|
-
}
|
|
170
|
-
const rows = content.split("\n");
|
|
171
|
-
const start = from ?? 1;
|
|
172
|
-
const size = lines ?? rows.length;
|
|
173
|
-
const slice = rows.slice(start - 1, start - 1 + size).join("\n");
|
|
174
|
-
return { path: relPath, text: slice };
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* 读取 wiki index 内容。
|
|
178
|
-
*/
|
|
179
|
-
export async function readWikiIndex(context) {
|
|
180
|
-
const indexPath = path.join(context.workspace_path, ".downcity", "memory", "wiki", "index.md");
|
|
181
|
-
return await readTextIfExists(indexPath);
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* 归档手动 source。
|
|
185
|
-
*/
|
|
186
|
-
export async function appendManualSource(context, content, source) {
|
|
187
|
-
const absPath = path.join(context.workspace_path, ".downcity", "memory", "sources", "manual", `${dateStamp()}.md`);
|
|
188
|
-
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
189
|
-
const entry = buildSourceEntry(content, source);
|
|
190
|
-
await fs.appendFile(absPath, `\n${entry}`, "utf-8");
|
|
191
|
-
return {
|
|
192
|
-
path: toRelPath(context.workspace_path, absPath),
|
|
193
|
-
writtenChars: entry.length,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* 写入 session source。
|
|
198
|
-
*/
|
|
199
|
-
export async function writeSessionSource(context, session_id, content) {
|
|
200
|
-
const safeSessionId = slugify(session_id);
|
|
201
|
-
const absPath = path.join(context.workspace_path, ".downcity", "memory", "sources", "sessions", `${safeSessionId}.md`);
|
|
202
|
-
const text = [
|
|
203
|
-
"---",
|
|
204
|
-
`title: ${JSON.stringify(`Session ${session_id}`)}`,
|
|
205
|
-
`date: ${dateStamp()}`,
|
|
206
|
-
'tags: ["memory-source", "session"]',
|
|
207
|
-
"---",
|
|
208
|
-
"",
|
|
209
|
-
`# Session ${session_id}`,
|
|
210
|
-
"",
|
|
211
|
-
content.trim(),
|
|
212
|
-
"",
|
|
213
|
-
].join("\n");
|
|
214
|
-
await fs.mkdir(path.dirname(absPath), { recursive: true });
|
|
215
|
-
await fs.writeFile(absPath, text, "utf-8");
|
|
216
|
-
return {
|
|
217
|
-
path: toRelPath(context.workspace_path, absPath),
|
|
218
|
-
writtenChars: text.length,
|
|
219
|
-
};
|
|
220
|
-
}
|
|
221
|
-
/**
|
|
222
|
-
* 写入完整 wiki page。
|
|
223
|
-
*/
|
|
224
|
-
export async function writeWikiPage(context, draft) {
|
|
225
|
-
const resolved = resolveWikiPath(context, draft.path, draft.title);
|
|
226
|
-
const content = ensureFrontmatter(draft);
|
|
227
|
-
await fs.mkdir(path.dirname(resolved.absPath), { recursive: true });
|
|
228
|
-
await fs.writeFile(resolved.absPath, content, "utf-8");
|
|
229
|
-
return {
|
|
230
|
-
path: resolved.relPath,
|
|
231
|
-
writtenChars: content.length,
|
|
232
|
-
};
|
|
233
|
-
}
|
|
234
|
-
/**
|
|
235
|
-
* 追加写入 wiki page。
|
|
236
|
-
*/
|
|
237
|
-
export async function appendWikiPage(context, payload) {
|
|
238
|
-
const resolved = resolveWikiPath(context, payload.path, payload.title);
|
|
239
|
-
await fs.mkdir(path.dirname(resolved.absPath), { recursive: true });
|
|
240
|
-
const exists = await fs
|
|
241
|
-
.access(resolved.absPath)
|
|
242
|
-
.then(() => true)
|
|
243
|
-
.catch(() => false);
|
|
244
|
-
if (!exists) {
|
|
245
|
-
await writeWikiPage(context, {
|
|
246
|
-
path: resolved.relPath,
|
|
247
|
-
title: payload.title || "Memory Inbox",
|
|
248
|
-
content: "This page is maintained by MemoryPlugin fallback writes.",
|
|
249
|
-
tags: ["memory"],
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
const entry = buildFallbackWikiEntry(payload);
|
|
253
|
-
await fs.appendFile(resolved.absPath, `\n${entry}`, "utf-8");
|
|
254
|
-
return {
|
|
255
|
-
path: resolved.relPath,
|
|
256
|
-
writtenChars: entry.length,
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
/**
|
|
260
|
-
* 使用 fallback 方式修订 wiki page。
|
|
261
|
-
*/
|
|
262
|
-
export async function appendMemoryRevision(context, payload) {
|
|
263
|
-
const written = await appendWikiPage(context, {
|
|
264
|
-
path: payload.path,
|
|
265
|
-
content: String(payload.evidence || "").trim() || "(no evidence)",
|
|
266
|
-
instruction: payload.instruction,
|
|
267
|
-
});
|
|
268
|
-
return {
|
|
269
|
-
path: written.path,
|
|
270
|
-
mode: "appended",
|
|
271
|
-
writtenChars: written.writtenChars,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
//# sourceMappingURL=Writer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Writer.js","sourceRoot":"","sources":["../../../src/memory/runtime/Writer.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Memory Search 运行时。
|
|
3
|
-
*
|
|
4
|
-
* 关键点(中文)
|
|
5
|
-
* - 直接扫描 LLM Wiki Markdown 文件,不依赖额外索引库。
|
|
6
|
-
* - 统一收敛检索、分块、打分与状态统计逻辑。
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import fs from "node:fs/promises";
|
|
10
|
-
import type { PluginContext } from "@downcity/agent";
|
|
11
|
-
import type {
|
|
12
|
-
MemorySearchPayload,
|
|
13
|
-
MemorySearchResponse,
|
|
14
|
-
MemorySearchResultItem,
|
|
15
|
-
MemorySourceStat,
|
|
16
|
-
MemoryStatusResponse,
|
|
17
|
-
} from "@/memory/types/Memory.js";
|
|
18
|
-
import {
|
|
19
|
-
listMemorySourceFiles,
|
|
20
|
-
MEMORY_DEFAULTS,
|
|
21
|
-
type MemoryRuntimeState,
|
|
22
|
-
} from "./Store.js";
|
|
23
|
-
|
|
24
|
-
const SNIPPET_MAX_CHARS = 700;
|
|
25
|
-
const CHUNK_MAX_CHARS = 1600;
|
|
26
|
-
const CHUNK_OVERLAP_CHARS = 240;
|
|
27
|
-
|
|
28
|
-
function clampNumber(value: number, min: number, max: number): number {
|
|
29
|
-
if (!Number.isFinite(value)) return min;
|
|
30
|
-
return Math.max(min, Math.min(max, value));
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function normalizeText(input: string): string {
|
|
34
|
-
return String(input || "").replace(/\r\n/g, "\n");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function truncateText(value: string, maxChars: number): string {
|
|
38
|
-
if (value.length <= maxChars) {
|
|
39
|
-
return value;
|
|
40
|
-
}
|
|
41
|
-
return value.slice(0, maxChars);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function tokenizeQuery(raw: string): string[] {
|
|
45
|
-
return String(raw || "")
|
|
46
|
-
.toLowerCase()
|
|
47
|
-
.replace(/[^\p{L}\p{N}_-]+/gu, " ")
|
|
48
|
-
.split(/\s+/)
|
|
49
|
-
.map((item) => item.trim())
|
|
50
|
-
.filter(Boolean)
|
|
51
|
-
.slice(0, 12);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function countTokenOccurrences(text: string, token: string): number {
|
|
55
|
-
if (!token) return 0;
|
|
56
|
-
let hits = 0;
|
|
57
|
-
let start = 0;
|
|
58
|
-
while (start < text.length) {
|
|
59
|
-
const index = text.indexOf(token, start);
|
|
60
|
-
if (index < 0) break;
|
|
61
|
-
hits += 1;
|
|
62
|
-
start = index + token.length;
|
|
63
|
-
}
|
|
64
|
-
return hits;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function buildSnippetScore(text: string, tokens: string[]): number {
|
|
68
|
-
if (tokens.length === 0) return 0;
|
|
69
|
-
const normalized = normalizeText(text).toLowerCase();
|
|
70
|
-
let matchedTokens = 0;
|
|
71
|
-
let totalHits = 0;
|
|
72
|
-
for (const token of tokens) {
|
|
73
|
-
const hits = countTokenOccurrences(normalized, token);
|
|
74
|
-
if (hits > 0) {
|
|
75
|
-
matchedTokens += 1;
|
|
76
|
-
totalHits += Math.min(hits, 4);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (matchedTokens === 0) {
|
|
80
|
-
return 0;
|
|
81
|
-
}
|
|
82
|
-
const coverageScore = matchedTokens / tokens.length;
|
|
83
|
-
const densityScore = Math.min(totalHits, tokens.length * 3) / (tokens.length * 3);
|
|
84
|
-
return Number((coverageScore * 0.75 + densityScore * 0.25).toFixed(4));
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function chunkMarkdown(content: string): Array<{
|
|
88
|
-
startLine: number;
|
|
89
|
-
endLine: number;
|
|
90
|
-
text: string;
|
|
91
|
-
}> {
|
|
92
|
-
const lines = normalizeText(content).split("\n");
|
|
93
|
-
if (lines.length === 0) {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
const out: Array<{
|
|
97
|
-
startLine: number;
|
|
98
|
-
endLine: number;
|
|
99
|
-
text: string;
|
|
100
|
-
}> = [];
|
|
101
|
-
let bucket: Array<{ line: string; lineNo: number }> = [];
|
|
102
|
-
let chars = 0;
|
|
103
|
-
|
|
104
|
-
const flush = () => {
|
|
105
|
-
if (bucket.length === 0) return;
|
|
106
|
-
const startLine = bucket[0]?.lineNo ?? 1;
|
|
107
|
-
const endLine = bucket[bucket.length - 1]?.lineNo ?? startLine;
|
|
108
|
-
const text = bucket.map((item) => item.line).join("\n").trim();
|
|
109
|
-
if (!text) return;
|
|
110
|
-
out.push({
|
|
111
|
-
startLine,
|
|
112
|
-
endLine,
|
|
113
|
-
text,
|
|
114
|
-
});
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const carryOverlap = () => {
|
|
118
|
-
if (bucket.length === 0 || CHUNK_OVERLAP_CHARS <= 0) {
|
|
119
|
-
bucket = [];
|
|
120
|
-
chars = 0;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
let acc = 0;
|
|
124
|
-
const next: Array<{ line: string; lineNo: number }> = [];
|
|
125
|
-
for (let i = bucket.length - 1; i >= 0; i -= 1) {
|
|
126
|
-
const row = bucket[i];
|
|
127
|
-
if (!row) continue;
|
|
128
|
-
acc += row.line.length + 1;
|
|
129
|
-
next.unshift(row);
|
|
130
|
-
if (acc >= CHUNK_OVERLAP_CHARS) break;
|
|
131
|
-
}
|
|
132
|
-
bucket = next;
|
|
133
|
-
chars = bucket.reduce((sum, item) => sum + item.line.length + 1, 0);
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
137
|
-
const line = lines[i] ?? "";
|
|
138
|
-
const rowSize = line.length + 1;
|
|
139
|
-
if (bucket.length > 0 && chars + rowSize > CHUNK_MAX_CHARS) {
|
|
140
|
-
flush();
|
|
141
|
-
carryOverlap();
|
|
142
|
-
}
|
|
143
|
-
bucket.push({ line, lineNo: i + 1 });
|
|
144
|
-
chars += rowSize;
|
|
145
|
-
}
|
|
146
|
-
flush();
|
|
147
|
-
return out;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
async function readMemoryChunks(
|
|
151
|
-
context: PluginContext,
|
|
152
|
-
options: { includeSources?: boolean },
|
|
153
|
-
): Promise<Array<{
|
|
154
|
-
path: string;
|
|
155
|
-
source: "wiki" | "source" | "working";
|
|
156
|
-
startLine: number;
|
|
157
|
-
endLine: number;
|
|
158
|
-
text: string;
|
|
159
|
-
}>> {
|
|
160
|
-
const files = await listMemorySourceFiles(context.workspace_path, options);
|
|
161
|
-
const out: Array<{
|
|
162
|
-
path: string;
|
|
163
|
-
source: "wiki" | "source" | "working";
|
|
164
|
-
startLine: number;
|
|
165
|
-
endLine: number;
|
|
166
|
-
text: string;
|
|
167
|
-
}> = [];
|
|
168
|
-
for (const file of files) {
|
|
169
|
-
const content = String(await fs.readFile(file.absPath, "utf-8"));
|
|
170
|
-
for (const chunk of chunkMarkdown(content)) {
|
|
171
|
-
out.push({
|
|
172
|
-
path: file.relPath,
|
|
173
|
-
source: file.source,
|
|
174
|
-
startLine: chunk.startLine,
|
|
175
|
-
endLine: chunk.endLine,
|
|
176
|
-
text: chunk.text,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return out;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* 收集当前 memory Markdown 状态。
|
|
185
|
-
*/
|
|
186
|
-
export async function collectMemoryStatus(
|
|
187
|
-
context: PluginContext,
|
|
188
|
-
state: MemoryRuntimeState,
|
|
189
|
-
): Promise<MemoryStatusResponse> {
|
|
190
|
-
void state;
|
|
191
|
-
const files = await listMemorySourceFiles(context.workspace_path, {
|
|
192
|
-
includeSources: true,
|
|
193
|
-
});
|
|
194
|
-
const sourceCounts: MemorySourceStat[] = [
|
|
195
|
-
{ source: "wiki", files: 0, chunks: 0 },
|
|
196
|
-
{ source: "source", files: 0, chunks: 0 },
|
|
197
|
-
{ source: "working", files: 0, chunks: 0 },
|
|
198
|
-
];
|
|
199
|
-
|
|
200
|
-
let totalChunks = 0;
|
|
201
|
-
for (const file of files) {
|
|
202
|
-
const bucket = sourceCounts.find((item) => item.source === file.source);
|
|
203
|
-
if (bucket) {
|
|
204
|
-
bucket.files += 1;
|
|
205
|
-
}
|
|
206
|
-
const content = String(await fs.readFile(file.absPath, "utf-8"));
|
|
207
|
-
const chunks = chunkMarkdown(content).length;
|
|
208
|
-
totalChunks += chunks;
|
|
209
|
-
if (bucket) {
|
|
210
|
-
bucket.chunks += chunks;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return {
|
|
215
|
-
backend: "builtin",
|
|
216
|
-
mode: "scan",
|
|
217
|
-
files: files.length,
|
|
218
|
-
chunks: totalChunks,
|
|
219
|
-
sourceCounts,
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* 执行检索。
|
|
225
|
-
*/
|
|
226
|
-
export async function searchMemory(
|
|
227
|
-
context: PluginContext,
|
|
228
|
-
state: MemoryRuntimeState,
|
|
229
|
-
payload: MemorySearchPayload,
|
|
230
|
-
): Promise<MemorySearchResponse> {
|
|
231
|
-
void state;
|
|
232
|
-
|
|
233
|
-
const query = String(payload.query || "").trim();
|
|
234
|
-
if (!query) {
|
|
235
|
-
return {
|
|
236
|
-
backend: "builtin",
|
|
237
|
-
mode: "scan",
|
|
238
|
-
results: [],
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const tokens = tokenizeQuery(query);
|
|
243
|
-
if (tokens.length === 0) {
|
|
244
|
-
return {
|
|
245
|
-
backend: "builtin",
|
|
246
|
-
mode: "scan",
|
|
247
|
-
results: [],
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const maxResults = Math.floor(
|
|
252
|
-
clampNumber(
|
|
253
|
-
Number(payload.maxResults ?? MEMORY_DEFAULTS.maxResults),
|
|
254
|
-
1,
|
|
255
|
-
20,
|
|
256
|
-
),
|
|
257
|
-
);
|
|
258
|
-
const minScore = clampNumber(
|
|
259
|
-
Number(payload.minScore ?? MEMORY_DEFAULTS.minScore),
|
|
260
|
-
0,
|
|
261
|
-
1,
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
try {
|
|
265
|
-
const results = (await readMemoryChunks(context, {
|
|
266
|
-
includeSources: payload.includeSources,
|
|
267
|
-
}))
|
|
268
|
-
.map((chunk) => {
|
|
269
|
-
const score = buildSnippetScore(chunk.text, tokens);
|
|
270
|
-
const citation =
|
|
271
|
-
chunk.startLine === chunk.endLine
|
|
272
|
-
? `${chunk.path}#L${chunk.startLine}`
|
|
273
|
-
: `${chunk.path}#L${chunk.startLine}-L${chunk.endLine}`;
|
|
274
|
-
return {
|
|
275
|
-
path: chunk.path,
|
|
276
|
-
source: chunk.source,
|
|
277
|
-
startLine: chunk.startLine,
|
|
278
|
-
endLine: chunk.endLine,
|
|
279
|
-
score,
|
|
280
|
-
snippet: truncateText(chunk.text, SNIPPET_MAX_CHARS),
|
|
281
|
-
citation,
|
|
282
|
-
} satisfies MemorySearchResultItem;
|
|
283
|
-
})
|
|
284
|
-
.filter((item) => item.score >= minScore)
|
|
285
|
-
.sort((left, right) => {
|
|
286
|
-
if (right.score !== left.score) {
|
|
287
|
-
return right.score - left.score;
|
|
288
|
-
}
|
|
289
|
-
if (left.path !== right.path) {
|
|
290
|
-
return left.path.localeCompare(right.path);
|
|
291
|
-
}
|
|
292
|
-
return left.startLine - right.startLine;
|
|
293
|
-
})
|
|
294
|
-
.slice(0, maxResults * 3);
|
|
295
|
-
|
|
296
|
-
let remain = Math.max(0, MEMORY_DEFAULTS.maxInjectedChars);
|
|
297
|
-
const clamped: MemorySearchResultItem[] = [];
|
|
298
|
-
for (const item of results) {
|
|
299
|
-
if (remain <= 0 || clamped.length >= maxResults) break;
|
|
300
|
-
if (item.snippet.length <= remain) {
|
|
301
|
-
clamped.push(item);
|
|
302
|
-
remain -= item.snippet.length;
|
|
303
|
-
continue;
|
|
304
|
-
}
|
|
305
|
-
clamped.push({
|
|
306
|
-
...item,
|
|
307
|
-
snippet: item.snippet.slice(0, remain),
|
|
308
|
-
});
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return {
|
|
313
|
-
backend: "builtin",
|
|
314
|
-
mode: "scan",
|
|
315
|
-
results: clamped,
|
|
316
|
-
};
|
|
317
|
-
} catch (error) {
|
|
318
|
-
const message = String(error);
|
|
319
|
-
return {
|
|
320
|
-
backend: "builtin",
|
|
321
|
-
mode: "scan",
|
|
322
|
-
results: [],
|
|
323
|
-
error: message,
|
|
324
|
-
action: "Check the Markdown memory files under `.downcity/memory/`.",
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
}
|