@downcity/plugins 1.0.60 → 1.0.64
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/BuiltinPlugins.d.ts +15 -0
- package/bin/BuiltinPlugins.d.ts.map +1 -1
- package/bin/BuiltinPlugins.js +7 -1
- package/bin/BuiltinPlugins.js.map +1 -1
- package/bin/index.d.ts +6 -0
- package/bin/index.d.ts.map +1 -1
- package/bin/index.js +3 -0
- package/bin/index.js.map +1 -1
- package/bin/memory/Action.d.ts +15 -10
- package/bin/memory/Action.d.ts.map +1 -1
- package/bin/memory/Action.js +233 -16
- package/bin/memory/Action.js.map +1 -1
- package/bin/memory/MemoryPlugin.d.ts +10 -4
- package/bin/memory/MemoryPlugin.d.ts.map +1 -1
- package/bin/memory/MemoryPlugin.js +79 -37
- package/bin/memory/MemoryPlugin.js.map +1 -1
- package/bin/memory/runtime/Search.d.ts +1 -1
- package/bin/memory/runtime/Search.d.ts.map +1 -1
- package/bin/memory/runtime/Search.js +11 -7
- package/bin/memory/runtime/Search.js.map +1 -1
- package/bin/memory/runtime/Store.d.ts +8 -23
- package/bin/memory/runtime/Store.d.ts.map +1 -1
- package/bin/memory/runtime/Store.js +28 -43
- package/bin/memory/runtime/Store.js.map +1 -1
- package/bin/memory/runtime/SystemProvider.d.ts +4 -8
- package/bin/memory/runtime/SystemProvider.d.ts.map +1 -1
- package/bin/memory/runtime/SystemProvider.js +55 -62
- package/bin/memory/runtime/SystemProvider.js.map +1 -1
- package/bin/memory/runtime/Writer.d.ts +48 -10
- package/bin/memory/runtime/Writer.d.ts.map +1 -1
- package/bin/memory/runtime/Writer.js +197 -60
- package/bin/memory/runtime/Writer.js.map +1 -1
- package/bin/memory/types/Memory.d.ts +222 -33
- package/bin/memory/types/Memory.d.ts.map +1 -1
- package/bin/memory/types/Memory.js +4 -3
- package/bin/memory/types/Memory.js.map +1 -1
- package/bin/shell/ShellPlugin.d.ts +2 -1
- package/bin/shell/ShellPlugin.d.ts.map +1 -1
- package/bin/shell/ShellPlugin.js +41 -4
- package/bin/shell/ShellPlugin.js.map +1 -1
- package/bin/shell/ShellRuntimeTypes.d.ts +57 -3
- package/bin/shell/ShellRuntimeTypes.d.ts.map +1 -1
- package/bin/shell/runtime/ShellActionRuntime.d.ts +21 -0
- package/bin/shell/runtime/ShellActionRuntime.d.ts.map +1 -1
- package/bin/shell/runtime/ShellActionRuntime.js +142 -6
- package/bin/shell/runtime/ShellActionRuntime.js.map +1 -1
- package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts +14 -5
- package/bin/shell/runtime/ShellActionRuntimeSupport.d.ts.map +1 -1
- package/bin/shell/runtime/ShellActionRuntimeSupport.js +61 -22
- package/bin/shell/runtime/ShellActionRuntimeSupport.js.map +1 -1
- package/bin/shell/runtime/ShellApprovalRuntime.d.ts +57 -0
- package/bin/shell/runtime/ShellApprovalRuntime.d.ts.map +1 -0
- package/bin/shell/runtime/ShellApprovalRuntime.js +182 -0
- package/bin/shell/runtime/ShellApprovalRuntime.js.map +1 -0
- package/bin/shell/runtime/ShellProcessEvents.js +3 -3
- package/bin/shell/runtime/ShellProcessEvents.js.map +1 -1
- package/bin/shell/types/ShellPluginOptions.d.ts +103 -0
- package/bin/shell/types/ShellPluginOptions.d.ts.map +1 -0
- package/bin/shell/types/ShellPluginOptions.js +10 -0
- package/bin/shell/types/ShellPluginOptions.js.map +1 -0
- package/bin/task/Scheduler.d.ts +8 -0
- package/bin/task/Scheduler.d.ts.map +1 -1
- package/bin/task/Scheduler.js +7 -9
- package/bin/task/Scheduler.js.map +1 -1
- package/bin/task/TaskPlugin.d.ts +18 -1
- package/bin/task/TaskPlugin.d.ts.map +1 -1
- package/bin/task/TaskPlugin.js +23 -1
- package/bin/task/TaskPlugin.js.map +1 -1
- package/bin/task/types/TaskPluginOptions.d.ts +22 -0
- package/bin/task/types/TaskPluginOptions.d.ts.map +1 -0
- package/bin/task/types/TaskPluginOptions.js +9 -0
- package/bin/task/types/TaskPluginOptions.js.map +1 -0
- package/package.json +2 -2
- package/scripts/unrestricted-sandbox-approval.test.mjs +156 -0
- package/src/BuiltinPlugins.ts +27 -1
- package/src/index.ts +35 -0
- package/src/memory/Action.ts +292 -25
- package/src/memory/MemoryPlugin.ts +82 -40
- package/src/memory/runtime/Search.ts +16 -9
- package/src/memory/runtime/Store.ts +52 -49
- package/src/memory/runtime/SystemProvider.ts +55 -69
- package/src/memory/runtime/Writer.ts +262 -81
- package/src/memory/types/Memory.ts +296 -35
- package/src/shell/ShellPlugin.ts +44 -3
- package/src/shell/ShellRuntimeTypes.ts +61 -3
- package/src/shell/runtime/ShellActionRuntime.ts +182 -9
- package/src/shell/runtime/ShellActionRuntimeSupport.ts +112 -21
- package/src/shell/runtime/ShellApprovalRuntime.ts +236 -0
- package/src/shell/runtime/ShellProcessEvents.ts +3 -3
- package/src/shell/types/ShellPluginOptions.ts +122 -0
- package/src/task/Scheduler.ts +15 -9
- package/src/task/TaskPlugin.ts +27 -1
- package/src/task/types/TaskPluginOptions.ts +22 -0
- package/bin/memory/runtime/Flush.d.ts +0 -15
- package/bin/memory/runtime/Flush.d.ts.map +0 -1
- package/bin/memory/runtime/Flush.js +0 -63
- package/bin/memory/runtime/Flush.js.map +0 -1
- package/src/memory/runtime/Flush.ts +0 -83
package/src/memory/Action.ts
CHANGED
|
@@ -1,21 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Memory
|
|
2
|
+
* Memory Plugin action 逻辑。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
5
|
+
* - action 面向 agent 的记忆语义:search/read/remember/digest/revise。
|
|
6
|
+
* - 原始证据先进入 sources,长期知识进入 wiki。
|
|
7
|
+
* - LLM digest/revise 能力由 MemoryPlugin constructor 注入。
|
|
7
8
|
*/
|
|
8
9
|
|
|
10
|
+
import type { UIDataTypes, UIMessagePart, UITools } from "ai";
|
|
11
|
+
import { isTextUIPart } from "ai";
|
|
9
12
|
import type { PluginActionResult } from "@downcity/agent/internal/plugin/types/Plugin.js";
|
|
10
13
|
import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/AgentContext.js";
|
|
11
14
|
import type { JsonValue } from "@downcity/agent/internal/types/common/Json.js";
|
|
12
15
|
import type {
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
MemoryDigestPayload,
|
|
17
|
+
MemoryDigestResponse,
|
|
18
|
+
MemoryPluginOptions,
|
|
19
|
+
MemoryReadPayload,
|
|
20
|
+
MemoryRememberPayload,
|
|
21
|
+
MemoryRememberResponse,
|
|
22
|
+
MemoryRevisePayload,
|
|
23
|
+
MemoryReviseResponse,
|
|
15
24
|
MemorySearchPayload,
|
|
16
|
-
MemoryStorePayload,
|
|
17
25
|
} from "@/memory/types/Memory.js";
|
|
18
|
-
import { flushMemory } from "./runtime/Flush.js";
|
|
19
26
|
import {
|
|
20
27
|
collectMemoryStatus,
|
|
21
28
|
searchMemory,
|
|
@@ -24,7 +31,93 @@ import {
|
|
|
24
31
|
MEMORY_DEFAULTS,
|
|
25
32
|
type MemoryRuntimeState,
|
|
26
33
|
} from "./runtime/Store.js";
|
|
27
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
appendManualSource,
|
|
36
|
+
appendMemoryRevision,
|
|
37
|
+
appendWikiPage,
|
|
38
|
+
readMemory,
|
|
39
|
+
readWikiIndex,
|
|
40
|
+
writeSessionSource,
|
|
41
|
+
writeWikiPage,
|
|
42
|
+
} from "./runtime/Writer.js";
|
|
43
|
+
|
|
44
|
+
type AnyUiMessagePart = UIMessagePart<UIDataTypes, UITools>;
|
|
45
|
+
|
|
46
|
+
function toUiParts(message: { parts?: AnyUiMessagePart[] } | null | undefined): AnyUiMessagePart[] {
|
|
47
|
+
return Array.isArray(message?.parts) ? message.parts : [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function extractReadableLine(message: {
|
|
51
|
+
role?: string;
|
|
52
|
+
parts?: AnyUiMessagePart[];
|
|
53
|
+
}): string {
|
|
54
|
+
const role = String(message.role || "").toLowerCase() === "user" ? "User" : "Assistant";
|
|
55
|
+
const text = toUiParts(message)
|
|
56
|
+
.filter(isTextUIPart)
|
|
57
|
+
.map((part) => String(part.text || "").trim())
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join("\n")
|
|
60
|
+
.trim();
|
|
61
|
+
if (!text) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return `${role}: ${text}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function readDigestPages(result: Awaited<ReturnType<NonNullable<MemoryPluginOptions["digest"]>>>): {
|
|
68
|
+
pages: Array<{ path?: string; title?: string; content: string; tags?: string[] }>;
|
|
69
|
+
summary?: string;
|
|
70
|
+
} {
|
|
71
|
+
if (typeof result === "string") {
|
|
72
|
+
return {
|
|
73
|
+
pages: [{ title: "Memory Digest", content: result, tags: ["memory", "digest"] }],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
pages: result.pages,
|
|
78
|
+
summary: result.summary,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function readReviseResult(
|
|
83
|
+
result: Awaited<ReturnType<NonNullable<MemoryPluginOptions["revise"]>>>,
|
|
84
|
+
fallbackPath: string,
|
|
85
|
+
): { path: string; content: string; summary?: string } {
|
|
86
|
+
if (typeof result === "string") {
|
|
87
|
+
return {
|
|
88
|
+
path: fallbackPath,
|
|
89
|
+
content: result,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
path: result.path || fallbackPath,
|
|
94
|
+
content: result.content,
|
|
95
|
+
summary: result.summary,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function slugify(value: string): string {
|
|
100
|
+
const text = String(value || "")
|
|
101
|
+
.trim()
|
|
102
|
+
.toLowerCase()
|
|
103
|
+
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
|
104
|
+
.replace(/^-+|-+$/g, "")
|
|
105
|
+
.slice(0, 80);
|
|
106
|
+
return text || "inbox";
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function toWikiMemoryPath(value: string): string {
|
|
110
|
+
const clean = String(value || "").replace(/\\/g, "/").replace(/^\/+/, "").trim();
|
|
111
|
+
if (!clean) return ".downcity/memory/wiki/inbox.md";
|
|
112
|
+
if (clean.startsWith(".downcity/memory/wiki/")) {
|
|
113
|
+
return clean.toLowerCase().endsWith(".md") ? clean : `${clean}.md`;
|
|
114
|
+
}
|
|
115
|
+
const withoutPrefix = clean.replace(/^wiki\//, "");
|
|
116
|
+
const withExt = withoutPrefix.toLowerCase().endsWith(".md")
|
|
117
|
+
? withoutPrefix
|
|
118
|
+
: `${withoutPrefix}.md`;
|
|
119
|
+
return `.downcity/memory/wiki/${withExt}`;
|
|
120
|
+
}
|
|
28
121
|
|
|
29
122
|
/**
|
|
30
123
|
* status action。
|
|
@@ -70,14 +163,14 @@ export async function searchMemoryAction(
|
|
|
70
163
|
}
|
|
71
164
|
|
|
72
165
|
/**
|
|
73
|
-
*
|
|
166
|
+
* read action。
|
|
74
167
|
*/
|
|
75
|
-
export async function
|
|
168
|
+
export async function readMemoryAction(
|
|
76
169
|
context: AgentContext,
|
|
77
|
-
payload:
|
|
170
|
+
payload: MemoryReadPayload,
|
|
78
171
|
): Promise<PluginActionResult<JsonValue>> {
|
|
79
172
|
try {
|
|
80
|
-
const data = await
|
|
173
|
+
const data = await readMemory(context, payload);
|
|
81
174
|
return { success: true, data: data as unknown as JsonValue };
|
|
82
175
|
} catch (error) {
|
|
83
176
|
return {
|
|
@@ -88,16 +181,65 @@ export async function getMemoryAction(
|
|
|
88
181
|
}
|
|
89
182
|
|
|
90
183
|
/**
|
|
91
|
-
*
|
|
184
|
+
* remember action。
|
|
92
185
|
*/
|
|
93
|
-
export async function
|
|
186
|
+
export async function rememberMemoryAction(
|
|
94
187
|
context: AgentContext,
|
|
95
|
-
|
|
96
|
-
payload:
|
|
188
|
+
options: MemoryPluginOptions,
|
|
189
|
+
payload: MemoryRememberPayload,
|
|
97
190
|
): Promise<PluginActionResult<JsonValue>> {
|
|
98
191
|
try {
|
|
99
|
-
const
|
|
100
|
-
|
|
192
|
+
const content = String(payload.content || "").trim();
|
|
193
|
+
if (!content) {
|
|
194
|
+
throw new Error("content is required");
|
|
195
|
+
}
|
|
196
|
+
const source = await appendManualSource(context, content, payload.source);
|
|
197
|
+
const targetPath = toWikiMemoryPath(payload.path || slugify(payload.topic || "inbox"));
|
|
198
|
+
|
|
199
|
+
if (options.revise) {
|
|
200
|
+
const current = await readMemory(context, { path: targetPath }).catch(() => ({
|
|
201
|
+
path: targetPath,
|
|
202
|
+
text: "",
|
|
203
|
+
}));
|
|
204
|
+
const revised = readReviseResult(
|
|
205
|
+
await options.revise({
|
|
206
|
+
rootPath: context.rootPath,
|
|
207
|
+
path: targetPath,
|
|
208
|
+
currentContent: current.text,
|
|
209
|
+
instruction: "Integrate this new memory into the wiki page. Deduplicate and keep it concise.",
|
|
210
|
+
evidence: `${content}\n\nSource: ${source.path}`,
|
|
211
|
+
}),
|
|
212
|
+
targetPath,
|
|
213
|
+
);
|
|
214
|
+
const written = await writeWikiPage(context, {
|
|
215
|
+
path: revised.path,
|
|
216
|
+
title: payload.topic,
|
|
217
|
+
content: revised.content,
|
|
218
|
+
tags: ["memory"],
|
|
219
|
+
});
|
|
220
|
+
const response: MemoryRememberResponse = {
|
|
221
|
+
sourcePath: source.path,
|
|
222
|
+
wikiPath: written.path,
|
|
223
|
+
mode: "revised",
|
|
224
|
+
writtenChars: written.writtenChars,
|
|
225
|
+
summary: revised.summary,
|
|
226
|
+
};
|
|
227
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const written = await appendWikiPage(context, {
|
|
231
|
+
path: targetPath,
|
|
232
|
+
title: payload.topic || "Memory Inbox",
|
|
233
|
+
content,
|
|
234
|
+
sourcePath: source.path,
|
|
235
|
+
});
|
|
236
|
+
const response: MemoryRememberResponse = {
|
|
237
|
+
sourcePath: source.path,
|
|
238
|
+
wikiPath: written.path,
|
|
239
|
+
mode: "appended",
|
|
240
|
+
writtenChars: written.writtenChars,
|
|
241
|
+
};
|
|
242
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
101
243
|
} catch (error) {
|
|
102
244
|
return {
|
|
103
245
|
success: false,
|
|
@@ -107,16 +249,135 @@ export async function storeMemoryAction(
|
|
|
107
249
|
}
|
|
108
250
|
|
|
109
251
|
/**
|
|
110
|
-
*
|
|
252
|
+
* digest action。
|
|
111
253
|
*/
|
|
112
|
-
export async function
|
|
254
|
+
export async function digestMemoryAction(
|
|
113
255
|
context: AgentContext,
|
|
114
|
-
|
|
115
|
-
payload:
|
|
116
|
-
): Promise<PluginActionResult
|
|
256
|
+
options: MemoryPluginOptions,
|
|
257
|
+
payload: MemoryDigestPayload,
|
|
258
|
+
): Promise<PluginActionResult<JsonValue>> {
|
|
117
259
|
try {
|
|
118
|
-
const
|
|
119
|
-
|
|
260
|
+
const sessionId = String(payload.sessionId || "").trim();
|
|
261
|
+
if (!sessionId) {
|
|
262
|
+
throw new Error("sessionId is required");
|
|
263
|
+
}
|
|
264
|
+
const maxMessages = Number.isFinite(payload.maxMessages)
|
|
265
|
+
? Math.max(1, Math.floor(payload.maxMessages as number))
|
|
266
|
+
: 30;
|
|
267
|
+
const historyStore = context.session.get(sessionId).getHistoryStore();
|
|
268
|
+
const total = await historyStore.size();
|
|
269
|
+
const start = Math.max(0, total - maxMessages);
|
|
270
|
+
const messages = await historyStore.slice(start, total);
|
|
271
|
+
const lines = messages
|
|
272
|
+
.map((msg) => extractReadableLine(msg))
|
|
273
|
+
.filter((line) => line.length > 0);
|
|
274
|
+
const transcript =
|
|
275
|
+
lines.length > 0
|
|
276
|
+
? lines.join("\n\n")
|
|
277
|
+
: "本次 digest 未找到可写入的用户/助手文本内容。";
|
|
278
|
+
const sourceText = [
|
|
279
|
+
`Window: ${start}-${Math.max(start, total - 1)}`,
|
|
280
|
+
"",
|
|
281
|
+
transcript,
|
|
282
|
+
].join("\n");
|
|
283
|
+
const source = await writeSessionSource(context, sessionId, sourceText);
|
|
284
|
+
|
|
285
|
+
if (options.digest) {
|
|
286
|
+
const wikiIndex = await readWikiIndex(context);
|
|
287
|
+
const digested = readDigestPages(
|
|
288
|
+
await options.digest({
|
|
289
|
+
rootPath: context.rootPath,
|
|
290
|
+
sourceText,
|
|
291
|
+
sourcePath: source.path,
|
|
292
|
+
sessionId,
|
|
293
|
+
wikiIndex,
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
const wikiPaths: string[] = [];
|
|
297
|
+
for (const page of digested.pages) {
|
|
298
|
+
const written = await writeWikiPage(context, page);
|
|
299
|
+
wikiPaths.push(written.path);
|
|
300
|
+
}
|
|
301
|
+
const response: MemoryDigestResponse = {
|
|
302
|
+
sourcePath: source.path,
|
|
303
|
+
wikiPaths,
|
|
304
|
+
messageCount: lines.length,
|
|
305
|
+
mode: "digested",
|
|
306
|
+
summary: digested.summary,
|
|
307
|
+
};
|
|
308
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const written = await appendWikiPage(context, {
|
|
312
|
+
path: "session-digests",
|
|
313
|
+
title: "Session Digests",
|
|
314
|
+
content: sourceText,
|
|
315
|
+
sourcePath: source.path,
|
|
316
|
+
});
|
|
317
|
+
const response: MemoryDigestResponse = {
|
|
318
|
+
sourcePath: source.path,
|
|
319
|
+
wikiPaths: [written.path],
|
|
320
|
+
messageCount: lines.length,
|
|
321
|
+
mode: "archived",
|
|
322
|
+
};
|
|
323
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return {
|
|
326
|
+
success: false,
|
|
327
|
+
error: String(error),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* revise action。
|
|
334
|
+
*/
|
|
335
|
+
export async function reviseMemoryAction(
|
|
336
|
+
context: AgentContext,
|
|
337
|
+
options: MemoryPluginOptions,
|
|
338
|
+
payload: MemoryRevisePayload,
|
|
339
|
+
): Promise<PluginActionResult<JsonValue>> {
|
|
340
|
+
try {
|
|
341
|
+
const targetPath = toWikiMemoryPath(String(payload.path || "").trim());
|
|
342
|
+
if (!targetPath) {
|
|
343
|
+
throw new Error("path is required");
|
|
344
|
+
}
|
|
345
|
+
const instruction = String(payload.instruction || "").trim();
|
|
346
|
+
if (!instruction) {
|
|
347
|
+
throw new Error("instruction is required");
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (options.revise) {
|
|
351
|
+
const current = await readMemory(context, { path: targetPath }).catch(() => ({
|
|
352
|
+
path: targetPath,
|
|
353
|
+
text: "",
|
|
354
|
+
}));
|
|
355
|
+
const revised = readReviseResult(
|
|
356
|
+
await options.revise({
|
|
357
|
+
rootPath: context.rootPath,
|
|
358
|
+
path: targetPath,
|
|
359
|
+
currentContent: current.text,
|
|
360
|
+
instruction,
|
|
361
|
+
evidence: String(payload.evidence || ""),
|
|
362
|
+
}),
|
|
363
|
+
targetPath,
|
|
364
|
+
);
|
|
365
|
+
const written = await writeWikiPage(context, {
|
|
366
|
+
path: revised.path,
|
|
367
|
+
content: revised.content,
|
|
368
|
+
tags: ["memory"],
|
|
369
|
+
});
|
|
370
|
+
const response: MemoryReviseResponse = {
|
|
371
|
+
path: written.path,
|
|
372
|
+
mode: "revised",
|
|
373
|
+
writtenChars: written.writtenChars,
|
|
374
|
+
summary: revised.summary,
|
|
375
|
+
};
|
|
376
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const response = await appendMemoryRevision(context, payload);
|
|
380
|
+
return { success: true, data: response as unknown as JsonValue };
|
|
120
381
|
} catch (error) {
|
|
121
382
|
return {
|
|
122
383
|
success: false,
|
|
@@ -143,5 +404,11 @@ export function toSearchPayload(input: Record<string, unknown>): MemorySearchPay
|
|
|
143
404
|
: typeof input.minScore === "string"
|
|
144
405
|
? Number(input.minScore)
|
|
145
406
|
: MEMORY_DEFAULTS.minScore,
|
|
407
|
+
includeSources:
|
|
408
|
+
typeof input.includeSources === "boolean"
|
|
409
|
+
? input.includeSources
|
|
410
|
+
: typeof input.includeSources === "string"
|
|
411
|
+
? input.includeSources === "true"
|
|
412
|
+
: undefined,
|
|
146
413
|
};
|
|
147
414
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MemoryPlugin:
|
|
2
|
+
* MemoryPlugin:agent 的长期记忆 plugin。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
5
|
+
* - 对外仍然是 MemoryPlugin,内部使用 LLM Wiki 方式组织知识。
|
|
6
|
+
* - constructor 注入 digest/revise 能力,plugin 不绑定具体 LLM 服务。
|
|
7
|
+
* - action 面向 agent 语义,而不是暴露底层文件写入细节。
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { Command } from "commander";
|
|
@@ -13,20 +13,20 @@ import type { AgentContext } from "@downcity/agent/internal/types/runtime/agent/
|
|
|
13
13
|
import type { PluginActions } from "@downcity/agent/internal/plugin/types/Plugin.js";
|
|
14
14
|
import { BasePlugin } from "@downcity/agent/internal/plugin/core/BasePlugin.js";
|
|
15
15
|
import {
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
digestMemoryAction,
|
|
17
|
+
readMemoryAction,
|
|
18
|
+
rememberMemoryAction,
|
|
19
|
+
reviseMemoryAction,
|
|
18
20
|
searchMemoryAction,
|
|
19
21
|
statusMemoryAction,
|
|
20
|
-
storeMemoryAction,
|
|
21
22
|
} from "./Action.js";
|
|
22
23
|
import {
|
|
23
24
|
createMemoryRuntimeState,
|
|
24
|
-
startMemoryRuntime,
|
|
25
|
-
stopMemoryRuntime,
|
|
26
25
|
type MemoryRuntimeState,
|
|
27
26
|
} from "./runtime/Store.js";
|
|
28
27
|
import { buildMemoryPluginSystemText } from "./runtime/SystemProvider.js";
|
|
29
28
|
import { ensureMemoryDirectories } from "./runtime/Writer.js";
|
|
29
|
+
import type { MemoryPluginOptions } from "./types/Memory.js";
|
|
30
30
|
|
|
31
31
|
function parsePositiveInteger(value: string): number {
|
|
32
32
|
const text = String(value || "").trim();
|
|
@@ -74,6 +74,11 @@ function readOptionalNumber(body: JsonObject, key: string): number | undefined {
|
|
|
74
74
|
return typeof value === "number" ? value : undefined;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
function readOptionalBoolean(body: JsonObject, key: string): boolean | undefined {
|
|
78
|
+
const value = body[key];
|
|
79
|
+
return typeof value === "boolean" ? value : undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
77
82
|
/**
|
|
78
83
|
* Memory plugin 类实现。
|
|
79
84
|
*/
|
|
@@ -88,6 +93,13 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
88
93
|
*/
|
|
89
94
|
public runtimeState: MemoryRuntimeState | null = null;
|
|
90
95
|
|
|
96
|
+
/**
|
|
97
|
+
* 创建 MemoryPlugin。
|
|
98
|
+
*/
|
|
99
|
+
constructor(private readonly options: MemoryPluginOptions = {}) {
|
|
100
|
+
super();
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
/**
|
|
92
104
|
* 当前 plugin 的 system 文本提供器。
|
|
93
105
|
*/
|
|
@@ -101,12 +113,9 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
101
113
|
readonly lifecycle = {
|
|
102
114
|
start: async (context: AgentContext): Promise<void> => {
|
|
103
115
|
await ensureMemoryDirectories(context.rootPath);
|
|
104
|
-
|
|
105
|
-
await startMemoryRuntime(context, state);
|
|
116
|
+
this.getOrCreateRuntimeState(context);
|
|
106
117
|
},
|
|
107
118
|
stop: async (): Promise<void> => {
|
|
108
|
-
if (!this.runtimeState) return;
|
|
109
|
-
await stopMemoryRuntime(this.runtimeState);
|
|
110
119
|
this.runtimeState = null;
|
|
111
120
|
},
|
|
112
121
|
};
|
|
@@ -117,7 +126,7 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
117
126
|
readonly actions: PluginActions = {
|
|
118
127
|
status: {
|
|
119
128
|
command: {
|
|
120
|
-
description: "查看 memory 状态(
|
|
129
|
+
description: "查看 memory wiki 状态(wiki/source/working)",
|
|
121
130
|
mapInput() {
|
|
122
131
|
return {};
|
|
123
132
|
},
|
|
@@ -129,12 +138,13 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
129
138
|
},
|
|
130
139
|
search: {
|
|
131
140
|
command: {
|
|
132
|
-
description: "
|
|
141
|
+
description: "检索 memory wiki",
|
|
133
142
|
configure(command: Command) {
|
|
134
143
|
command
|
|
135
144
|
.argument("<query>")
|
|
136
145
|
.option("--max-results <number>", "返回条数上限", parsePositiveInteger)
|
|
137
|
-
.option("--min-score <number>", "最小相关分数", parseNumber)
|
|
146
|
+
.option("--min-score <number>", "最小相关分数", parseNumber)
|
|
147
|
+
.option("--include-sources", "同时检索原始 source 层");
|
|
138
148
|
},
|
|
139
149
|
mapInput({ args, opts }) {
|
|
140
150
|
const payload: JsonObject = {
|
|
@@ -146,6 +156,9 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
146
156
|
if (typeof opts.minScore === "number") {
|
|
147
157
|
payload.minScore = opts.minScore;
|
|
148
158
|
}
|
|
159
|
+
if (opts.includeSources === true) {
|
|
160
|
+
payload.includeSources = true;
|
|
161
|
+
}
|
|
149
162
|
return payload;
|
|
150
163
|
},
|
|
151
164
|
},
|
|
@@ -156,12 +169,13 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
156
169
|
query: readString(body, "query"),
|
|
157
170
|
maxResults: readOptionalNumber(body, "maxResults"),
|
|
158
171
|
minScore: readOptionalNumber(body, "minScore"),
|
|
172
|
+
includeSources: readOptionalBoolean(body, "includeSources"),
|
|
159
173
|
});
|
|
160
174
|
},
|
|
161
175
|
},
|
|
162
|
-
|
|
176
|
+
read: {
|
|
163
177
|
command: {
|
|
164
|
-
description: "
|
|
178
|
+
description: "读取 memory wiki/source 文件片段",
|
|
165
179
|
configure(command: Command) {
|
|
166
180
|
command
|
|
167
181
|
.argument("<memoryPath>", "记忆文件路径(相对项目根目录)")
|
|
@@ -183,52 +197,52 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
183
197
|
},
|
|
184
198
|
execute: async (params) => {
|
|
185
199
|
const body = readBodyObject(params.payload);
|
|
186
|
-
return await
|
|
200
|
+
return await readMemoryAction(params.context, {
|
|
187
201
|
path: readString(body, "path"),
|
|
188
202
|
from: readOptionalNumber(body, "from"),
|
|
189
203
|
lines: readOptionalNumber(body, "lines"),
|
|
190
204
|
});
|
|
191
205
|
},
|
|
192
206
|
},
|
|
193
|
-
|
|
207
|
+
remember: {
|
|
194
208
|
command: {
|
|
195
|
-
description: "
|
|
209
|
+
description: "把事实/偏好/决策记入 memory wiki",
|
|
196
210
|
configure(command: Command) {
|
|
197
211
|
command
|
|
198
|
-
.requiredOption("--content <text>", "
|
|
199
|
-
.option("--
|
|
200
|
-
.option("--
|
|
212
|
+
.requiredOption("--content <text>", "需要记住的内容")
|
|
213
|
+
.option("--topic <topic>", "记忆主题")
|
|
214
|
+
.option("--wiki-path <path>", "目标 wiki page 路径")
|
|
215
|
+
.option("--source <source>", "来源说明");
|
|
201
216
|
},
|
|
202
217
|
mapInput({ opts }) {
|
|
203
218
|
const payload: JsonObject = {
|
|
204
219
|
content: String(opts.content || ""),
|
|
205
220
|
};
|
|
206
|
-
if (typeof opts.
|
|
207
|
-
payload.
|
|
221
|
+
if (typeof opts.topic === "string") {
|
|
222
|
+
payload.topic = String(opts.topic).trim();
|
|
208
223
|
}
|
|
209
|
-
if (typeof opts.
|
|
210
|
-
payload.
|
|
224
|
+
if (typeof opts.wikiPath === "string") {
|
|
225
|
+
payload.path = String(opts.wikiPath).trim();
|
|
226
|
+
}
|
|
227
|
+
if (typeof opts.source === "string") {
|
|
228
|
+
payload.source = String(opts.source).trim();
|
|
211
229
|
}
|
|
212
230
|
return payload;
|
|
213
231
|
},
|
|
214
232
|
},
|
|
215
233
|
execute: async (params) => {
|
|
216
234
|
const body = readBodyObject(params.payload);
|
|
217
|
-
|
|
218
|
-
const state = this.getOrCreateRuntimeState(params.context);
|
|
219
|
-
return await storeMemoryAction(params.context, state, {
|
|
235
|
+
return await rememberMemoryAction(params.context, this.options, {
|
|
220
236
|
content: readString(body, "content"),
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
: undefined,
|
|
225
|
-
sessionId: readOptionalString(body, "sessionId"),
|
|
237
|
+
topic: readOptionalString(body, "topic"),
|
|
238
|
+
path: readOptionalString(body, "path"),
|
|
239
|
+
source: readOptionalString(body, "source"),
|
|
226
240
|
});
|
|
227
241
|
},
|
|
228
242
|
},
|
|
229
|
-
|
|
243
|
+
digest: {
|
|
230
244
|
command: {
|
|
231
|
-
description: "
|
|
245
|
+
description: "把 session 提炼进 memory wiki",
|
|
232
246
|
configure(command: Command) {
|
|
233
247
|
command
|
|
234
248
|
.requiredOption("--session-id <sessionId>", "会话 ID")
|
|
@@ -246,13 +260,41 @@ export class MemoryPlugin extends BasePlugin {
|
|
|
246
260
|
},
|
|
247
261
|
execute: async (params) => {
|
|
248
262
|
const body = readBodyObject(params.payload);
|
|
249
|
-
|
|
250
|
-
return await flushMemoryAction(params.context, state, {
|
|
263
|
+
return await digestMemoryAction(params.context, this.options, {
|
|
251
264
|
sessionId: readString(body, "sessionId"),
|
|
252
265
|
maxMessages: readOptionalNumber(body, "maxMessages"),
|
|
253
266
|
});
|
|
254
267
|
},
|
|
255
268
|
},
|
|
269
|
+
revise: {
|
|
270
|
+
command: {
|
|
271
|
+
description: "基于新证据修订 memory wiki page",
|
|
272
|
+
configure(command: Command) {
|
|
273
|
+
command
|
|
274
|
+
.argument("<memoryPath>", "目标 wiki page 路径")
|
|
275
|
+
.requiredOption("--instruction <text>", "修订指令")
|
|
276
|
+
.option("--evidence <text>", "新证据");
|
|
277
|
+
},
|
|
278
|
+
mapInput({ args, opts }) {
|
|
279
|
+
const payload: JsonObject = {
|
|
280
|
+
path: String(args[0] || ""),
|
|
281
|
+
instruction: String(opts.instruction || ""),
|
|
282
|
+
};
|
|
283
|
+
if (typeof opts.evidence === "string") {
|
|
284
|
+
payload.evidence = String(opts.evidence).trim();
|
|
285
|
+
}
|
|
286
|
+
return payload;
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
execute: async (params) => {
|
|
290
|
+
const body = readBodyObject(params.payload);
|
|
291
|
+
return await reviseMemoryAction(params.context, this.options, {
|
|
292
|
+
path: readString(body, "path"),
|
|
293
|
+
instruction: readString(body, "instruction"),
|
|
294
|
+
evidence: readOptionalString(body, "evidence"),
|
|
295
|
+
});
|
|
296
|
+
},
|
|
297
|
+
},
|
|
256
298
|
};
|
|
257
299
|
|
|
258
300
|
/**
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Memory Search 运行时。
|
|
3
3
|
*
|
|
4
4
|
* 关键点(中文)
|
|
5
|
-
* - 直接扫描 Markdown 文件,不依赖额外索引库。
|
|
5
|
+
* - 直接扫描 LLM Wiki Markdown 文件,不依赖额外索引库。
|
|
6
6
|
* - 统一收敛检索、分块、打分与状态统计逻辑。
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -147,17 +147,20 @@ function chunkMarkdown(content: string): Array<{
|
|
|
147
147
|
return out;
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
async function readMemoryChunks(
|
|
150
|
+
async function readMemoryChunks(
|
|
151
|
+
context: AgentContext,
|
|
152
|
+
options: { includeSources?: boolean },
|
|
153
|
+
): Promise<Array<{
|
|
151
154
|
path: string;
|
|
152
|
-
source: "
|
|
155
|
+
source: "wiki" | "source" | "working";
|
|
153
156
|
startLine: number;
|
|
154
157
|
endLine: number;
|
|
155
158
|
text: string;
|
|
156
159
|
}>> {
|
|
157
|
-
const files = await listMemorySourceFiles(context.rootPath);
|
|
160
|
+
const files = await listMemorySourceFiles(context.rootPath, options);
|
|
158
161
|
const out: Array<{
|
|
159
162
|
path: string;
|
|
160
|
-
source: "
|
|
163
|
+
source: "wiki" | "source" | "working";
|
|
161
164
|
startLine: number;
|
|
162
165
|
endLine: number;
|
|
163
166
|
text: string;
|
|
@@ -185,10 +188,12 @@ export async function collectMemoryStatus(
|
|
|
185
188
|
state: MemoryRuntimeState,
|
|
186
189
|
): Promise<MemoryStatusResponse> {
|
|
187
190
|
void state;
|
|
188
|
-
const files = await listMemorySourceFiles(context.rootPath
|
|
191
|
+
const files = await listMemorySourceFiles(context.rootPath, {
|
|
192
|
+
includeSources: true,
|
|
193
|
+
});
|
|
189
194
|
const sourceCounts: MemorySourceStat[] = [
|
|
190
|
-
{ source: "
|
|
191
|
-
{ source: "
|
|
195
|
+
{ source: "wiki", files: 0, chunks: 0 },
|
|
196
|
+
{ source: "source", files: 0, chunks: 0 },
|
|
192
197
|
{ source: "working", files: 0, chunks: 0 },
|
|
193
198
|
];
|
|
194
199
|
|
|
@@ -257,7 +262,9 @@ export async function searchMemory(
|
|
|
257
262
|
);
|
|
258
263
|
|
|
259
264
|
try {
|
|
260
|
-
const results = (await readMemoryChunks(context
|
|
265
|
+
const results = (await readMemoryChunks(context, {
|
|
266
|
+
includeSources: payload.includeSources,
|
|
267
|
+
}))
|
|
261
268
|
.map((chunk) => {
|
|
262
269
|
const score = buildSnippetScore(chunk.text, tokens);
|
|
263
270
|
const citation =
|