aws-runtime-bridge 1.9.33 → 1.9.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter/AcodeSdkAdapter.d.ts +35 -1
- package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/AcodeSdkAdapter.js +417 -40
- package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
- package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
- package/dist/adapter/ClaudeSdkAdapter.js +23 -1
- package/dist/adapter/idle-poll-loop.d.ts +2 -1
- package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
- package/dist/adapter/types.d.ts +65 -3
- package/dist/adapter/types.d.ts.map +1 -1
- package/dist/adapter/types.js +5 -4
- package/dist/index.js +4 -0
- package/dist/routes/acode-todos.d.ts +11 -0
- package/dist/routes/acode-todos.d.ts.map +1 -0
- package/dist/routes/acode-todos.js +75 -0
- package/dist/routes/memory.d.ts +1 -1
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/memory.js +3 -3
- package/dist/routes/pty.d.ts.map +1 -1
- package/dist/routes/pty.js +9 -0
- package/dist/routes/sub-agents.d.ts +14 -0
- package/dist/routes/sub-agents.d.ts.map +1 -0
- package/dist/routes/sub-agents.js +253 -0
- package/dist/routes/terminal.d.ts +9 -1
- package/dist/routes/terminal.d.ts.map +1 -1
- package/dist/routes/terminal.js +207 -22
- package/dist/services/agent-event-store.d.ts +1 -0
- package/dist/services/agent-event-store.d.ts.map +1 -1
- package/dist/services/event-delta-pusher.js +4 -4
- package/dist/services/session-output.d.ts +3 -0
- package/dist/services/session-output.d.ts.map +1 -1
- package/dist/services/session-output.js +38 -2
- package/dist/services/sub-agent-manager-instance.d.ts +4 -0
- package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
- package/dist/services/sub-agent-manager-instance.js +12 -0
- package/dist/services/workspace-files.d.ts +1 -0
- package/dist/services/workspace-files.d.ts.map +1 -1
- package/dist/services/workspace-files.js +57 -1
- package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
- package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
- package/package/acode/dist/ask-user-question-tool.js +233 -0
- package/package/acode/dist/built-in-file-tools.d.ts +2 -0
- package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
- package/package/acode/dist/compaction.d.ts +206 -0
- package/package/acode/dist/compaction.d.ts.map +1 -0
- package/package/acode/dist/compaction.js +664 -0
- package/package/acode/dist/config.d.ts +6 -1
- package/package/acode/dist/config.d.ts.map +1 -1
- package/package/acode/dist/config.js +27 -2
- package/package/acode/dist/index.d.ts +4 -0
- package/package/acode/dist/index.d.ts.map +1 -1
- package/package/acode/dist/index.js +4 -0
- package/package/acode/dist/runtime.d.ts +137 -0
- package/package/acode/dist/runtime.d.ts.map +1 -1
- package/package/acode/dist/runtime.js +974 -172
- package/package/acode/dist/skill-tool.d.ts +30 -0
- package/package/acode/dist/skill-tool.d.ts.map +1 -0
- package/package/acode/dist/skill-tool.js +174 -0
- package/package/acode/dist/sub-agent-manager.d.ts +169 -0
- package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-manager.js +535 -0
- package/package/acode/dist/sub-agent-store.d.ts +33 -0
- package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-store.js +109 -0
- package/package/acode/dist/sub-agent-tools.d.ts +72 -0
- package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-tools.js +429 -0
- package/package/acode/dist/sub-agent-types.d.ts +81 -0
- package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
- package/package/acode/dist/sub-agent-types.js +8 -0
- package/package/acode/dist/todo-store.d.ts +42 -0
- package/package/acode/dist/todo-store.d.ts.map +1 -0
- package/package/acode/dist/todo-store.js +68 -0
- package/package/acode/dist/todo-tool.d.ts +6 -0
- package/package/acode/dist/todo-tool.d.ts.map +1 -0
- package/package/acode/dist/todo-tool.js +138 -0
- package/package/acode/dist/types.d.ts +22 -1
- package/package/acode/dist/types.d.ts.map +1 -1
- package/package/acode/package.json +13 -2
- package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -3
- package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -50
- package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -346
- package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
- package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
- package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/mcp-tools.js +4 -77
- package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -40
- package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.js +62 -149
- package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-store.test.js +175 -164
- package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -9
- package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -55
- package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
- package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
- package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -14
- package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -18
- package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
- package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
- package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
- package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
- package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
- package/package.json +17 -7
- package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
- package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
- package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
- package/dist/adapter/AdapterRegistry.test.js +0 -118
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
- package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/CodexSdkAdapter.test.js +0 -270
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
- package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
- package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
- package/dist/adapter/acode-tool-names.test.d.ts +0 -2
- package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
- package/dist/adapter/acode-tool-names.test.js +0 -28
- package/dist/adapter/adapter.test.d.ts +0 -5
- package/dist/adapter/adapter.test.d.ts.map +0 -1
- package/dist/adapter/adapter.test.js +0 -180
- package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
- package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
- package/dist/adapter/idle-poll-loop.test.js +0 -85
- package/dist/adapter/types.test.d.ts +0 -2
- package/dist/adapter/types.test.d.ts.map +0 -1
- package/dist/adapter/types.test.js +0 -50
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
- package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
- package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
- package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
- package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
- package/dist/config.test.d.ts +0 -2
- package/dist/config.test.d.ts.map +0 -1
- package/dist/config.test.js +0 -107
- package/dist/routes/ai-sources.test.d.ts +0 -2
- package/dist/routes/ai-sources.test.d.ts.map +0 -1
- package/dist/routes/ai-sources.test.js +0 -169
- package/dist/routes/file-browser.test.d.ts +0 -5
- package/dist/routes/file-browser.test.d.ts.map +0 -1
- package/dist/routes/file-browser.test.js +0 -155
- package/dist/routes/git.test.d.ts +0 -2
- package/dist/routes/git.test.d.ts.map +0 -1
- package/dist/routes/git.test.js +0 -330
- package/dist/routes/instance.test.d.ts +0 -5
- package/dist/routes/instance.test.d.ts.map +0 -1
- package/dist/routes/instance.test.js +0 -389
- package/dist/routes/mcp.test.d.ts +0 -5
- package/dist/routes/mcp.test.d.ts.map +0 -1
- package/dist/routes/mcp.test.js +0 -92
- package/dist/routes/memory-route-wiring.test.d.ts +0 -2
- package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
- package/dist/routes/memory-route-wiring.test.js +0 -15
- package/dist/routes/memory.test.d.ts +0 -2
- package/dist/routes/memory.test.d.ts.map +0 -1
- package/dist/routes/memory.test.js +0 -177
- package/dist/routes/properties.test.d.ts +0 -5
- package/dist/routes/properties.test.d.ts.map +0 -1
- package/dist/routes/properties.test.js +0 -72
- package/dist/routes/pty.test.d.ts +0 -2
- package/dist/routes/pty.test.d.ts.map +0 -1
- package/dist/routes/pty.test.js +0 -82
- package/dist/routes/runtime-binding.test.d.ts +0 -2
- package/dist/routes/runtime-binding.test.d.ts.map +0 -1
- package/dist/routes/runtime-binding.test.js +0 -32
- package/dist/routes/sessions.test.d.ts +0 -5
- package/dist/routes/sessions.test.d.ts.map +0 -1
- package/dist/routes/sessions.test.js +0 -86
- package/dist/routes/skills.test.d.ts +0 -5
- package/dist/routes/skills.test.d.ts.map +0 -1
- package/dist/routes/skills.test.js +0 -50
- package/dist/routes/system-metrics.test.d.ts +0 -2
- package/dist/routes/system-metrics.test.d.ts.map +0 -1
- package/dist/routes/system-metrics.test.js +0 -16
- package/dist/routes/terminal.stop-options.test.d.ts +0 -2
- package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
- package/dist/routes/terminal.stop-options.test.js +0 -39
- package/dist/routes/terminal.test.d.ts +0 -2
- package/dist/routes/terminal.test.d.ts.map +0 -1
- package/dist/routes/terminal.test.js +0 -746
- package/dist/routes/yml.test.d.ts +0 -5
- package/dist/routes/yml.test.d.ts.map +0 -1
- package/dist/routes/yml.test.js +0 -58
- package/dist/services/ai-source-apply.test.d.ts +0 -2
- package/dist/services/ai-source-apply.test.d.ts.map +0 -1
- package/dist/services/ai-source-apply.test.js +0 -138
- package/dist/services/auto-register.test.d.ts +0 -2
- package/dist/services/auto-register.test.d.ts.map +0 -1
- package/dist/services/auto-register.test.js +0 -184
- package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
- package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
- package/dist/services/aws-client-agent-mcp.test.js +0 -242
- package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
- package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
- package/dist/services/bridge-package-publish-hooks.test.js +0 -136
- package/dist/services/cc-switch-discovery.test.d.ts +0 -2
- package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
- package/dist/services/cc-switch-discovery.test.js +0 -56
- package/dist/services/cli-commands.test.d.ts +0 -2
- package/dist/services/cli-commands.test.d.ts.map +0 -1
- package/dist/services/cli-commands.test.js +0 -230
- package/dist/services/instance-init-service.test.d.ts +0 -2
- package/dist/services/instance-init-service.test.d.ts.map +0 -1
- package/dist/services/instance-init-service.test.js +0 -140
- package/dist/services/instance-state.test.d.ts +0 -2
- package/dist/services/instance-state.test.d.ts.map +0 -1
- package/dist/services/instance-state.test.js +0 -245
- package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
- package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
- package/dist/services/mcp-launch-binding-queue.test.js +0 -181
- package/dist/services/runtime-binding.test.d.ts +0 -2
- package/dist/services/runtime-binding.test.d.ts.map +0 -1
- package/dist/services/runtime-binding.test.js +0 -78
- package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
- package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
- package/dist/services/runtime-lifecycle-policy.test.js +0 -41
- package/dist/services/session-output.test.d.ts +0 -5
- package/dist/services/session-output.test.d.ts.map +0 -1
- package/dist/services/session-output.test.js +0 -177
- package/dist/services/startup-config-wizard.test.d.ts +0 -2
- package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
- package/dist/services/startup-config-wizard.test.js +0 -184
- package/dist/services/terminal-persistence.test.d.ts +0 -5
- package/dist/services/terminal-persistence.test.d.ts.map +0 -1
- package/dist/services/terminal-persistence.test.js +0 -159
- package/dist/services/tool-installer.test.d.ts +0 -2
- package/dist/services/tool-installer.test.d.ts.map +0 -1
- package/dist/services/tool-installer.test.js +0 -225
- package/dist/services/workspace-files.test.d.ts +0 -2
- package/dist/services/workspace-files.test.d.ts.map +0 -1
- package/dist/services/workspace-files.test.js +0 -648
- package/dist/services/workspace-watch.test.d.ts +0 -2
- package/dist/services/workspace-watch.test.d.ts.map +0 -1
- package/dist/services/workspace-watch.test.js +0 -38
- package/dist/utils/file-utils.test.d.ts +0 -2
- package/dist/utils/file-utils.test.d.ts.map +0 -1
- package/dist/utils/file-utils.test.js +0 -244
- package/dist/utils/logger.test.d.ts +0 -2
- package/dist/utils/logger.test.d.ts.map +0 -1
- package/dist/utils/logger.test.js +0 -93
- package/dist/utils/sdk-package-loader.test.d.ts +0 -2
- package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
- package/dist/utils/sdk-package-loader.test.js +0 -84
- package/dist/utils/validation.test.d.ts +0 -2
- package/dist/utils/validation.test.d.ts.map +0 -1
- package/dist/utils/validation.test.js +0 -88
- package/dist/utils/yaml-utils.test.d.ts +0 -2
- package/dist/utils/yaml-utils.test.d.ts.map +0 -1
- package/dist/utils/yaml-utils.test.js +0 -363
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 提供 MCP Tools 供 AI Agent 调用
|
|
4
4
|
*/
|
|
5
5
|
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
7
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -56,7 +57,7 @@ export class McpServer {
|
|
|
56
57
|
*/
|
|
57
58
|
notifiedFileChangeMemoryIds = new Map();
|
|
58
59
|
/**
|
|
59
|
-
* 本地缓存 listable 记忆(
|
|
60
|
+
* 本地缓存 listable 记忆(recently_memory/agent_memory)的文件 MD5 map。
|
|
60
61
|
* 服务端(Java 后端)不持久化 file_md5_map 字段,listByType 返回的记录该字段为 undefined。
|
|
61
62
|
* 必须本地缓存才能让 checkMemoriesFileChanges 有正确的旧 MD5 做对比。
|
|
62
63
|
*/
|
|
@@ -70,6 +71,17 @@ export class McpServer {
|
|
|
70
71
|
*/
|
|
71
72
|
memoryNotificationNullCache = new Map();
|
|
72
73
|
static MEMORY_NOTIFICATION_NULL_TTL_MS = 60_000;
|
|
74
|
+
/**
|
|
75
|
+
* poll_message 单例控制:防止并发调用导致消息被第一个消费后第二个空等。
|
|
76
|
+
* 当已有 poll 在进行时,后续调用直接等待同一个 Promise。
|
|
77
|
+
*/
|
|
78
|
+
pendingPollMessage = null;
|
|
79
|
+
/**
|
|
80
|
+
* Todo 未完成项提醒去重指纹:记录上次通知时的未完成项内容/状态/优先级快照。
|
|
81
|
+
* 指纹一致则不重复通知;todos 变化时再次通知。
|
|
82
|
+
* 进程重启后自动清空(届时会重新通知一次,可接受)。
|
|
83
|
+
*/
|
|
84
|
+
lastTodoNotificationFingerprint = null;
|
|
73
85
|
// 系统提示词由 runtime adapter(ACode/OpenCode/Codex/Claude)在会话创建时注入,
|
|
74
86
|
// 无需通过 MCP 工具返回。三方 runtime(OpenCode/Claude)将三层提示词合并为一条 system prompt,
|
|
75
87
|
// ACode 则直接注入为前三条 system role 消息。
|
|
@@ -90,6 +102,100 @@ export class McpServer {
|
|
|
90
102
|
this.roleMemoryStore = new MemoryStore(McpServer.buildRoleMemoryStorePath(expectedRoleDir));
|
|
91
103
|
this.initialRoleName = expectedRoleDir;
|
|
92
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* 在 profile 到达后检查是否需要将本地永久记忆同步到 server。
|
|
107
|
+
* 触发条件:当前角色在 server 上还没有任何 memory 类型记录(通常发生在重部署后 server DB 为空时)。
|
|
108
|
+
* 主流程:异步执行,不阻塞 profile 处理链路。
|
|
109
|
+
* 1. 调用 server get_memory_stats 获取当前角色 memory 类型计数
|
|
110
|
+
* 2. 若 count === 0,读取本地永久记忆(角色子目录 + 遗留父目录 fallback)
|
|
111
|
+
* 3. 逐条调用 server create_memory 写入(已存在则静默跳过)
|
|
112
|
+
*/
|
|
113
|
+
maybeSyncPermanentMemoryToServer() {
|
|
114
|
+
this.doMaybeSyncPermanentMemoryToServer().catch((e) => {
|
|
115
|
+
logger.warn("[McpServer] maybeSyncPermanentMemoryToServer 异常:", e);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
async doMaybeSyncPermanentMemoryToServer() {
|
|
119
|
+
logger.info("[McpServer] maybeSyncPermanentMemoryToServer 开始,initialRoleName="
|
|
120
|
+
+ this.initialRoleName);
|
|
121
|
+
// Step 1: 检查 server 当前角色的 memory 计数
|
|
122
|
+
let stats;
|
|
123
|
+
try {
|
|
124
|
+
stats = await this.memoryStore.getStats();
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
logger.warn("[McpServer] 获取 server 记忆统计失败,跳过同步:", e instanceof Error ? e.message : e);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const memoryCount = stats.by_type["project_memory"]?.count ?? 0;
|
|
131
|
+
logger.info(`[McpServer] server memory 计数=${memoryCount},`
|
|
132
|
+
+ `是否需要同步=${memoryCount === 0}`);
|
|
133
|
+
if (memoryCount > 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
// Step 2: 读取本地永久记忆
|
|
137
|
+
// 2a: 先读角色子目录
|
|
138
|
+
let localMemories = [];
|
|
139
|
+
try {
|
|
140
|
+
localMemories = await this.roleMemoryStore.listByType("project_memory");
|
|
141
|
+
logger.info(`[McpServer] 角色子目录读取到 ${localMemories.length} 条永久记忆`);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
logger.warn("[McpServer] 读取角色子目录永久记忆失败:", e instanceof Error ? e.message : e);
|
|
145
|
+
}
|
|
146
|
+
// 2b: 角色子目录为空时,fallback 读遗留父目录(旧版无角色子目录)
|
|
147
|
+
if (localMemories.length === 0) {
|
|
148
|
+
const legacyStorePath = path.dirname(McpServer.buildRoleMemoryStorePath(this.initialRoleName));
|
|
149
|
+
logger.info(`[McpServer] 尝试从遗留目录读取: ${legacyStorePath}`);
|
|
150
|
+
try {
|
|
151
|
+
const legacyStore = new MemoryStore(legacyStorePath);
|
|
152
|
+
const legacyMemories = await legacyStore.listByType("project_memory");
|
|
153
|
+
if (legacyMemories.length > 0) {
|
|
154
|
+
logger.info(`[McpServer] 从遗留目录读取到 ${legacyMemories.length} 条永久记忆`);
|
|
155
|
+
localMemories = legacyMemories;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
logger.warn("[McpServer] 读取遗留目录永久记忆失败:", e instanceof Error ? e.message : e);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (localMemories.length === 0) {
|
|
163
|
+
logger.info("[McpServer] 本地也无永久记忆,跳过同步");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Step 3: 逐条写入 server
|
|
167
|
+
let synced = 0;
|
|
168
|
+
let skipped = 0;
|
|
169
|
+
for (const record of localMemories) {
|
|
170
|
+
try {
|
|
171
|
+
await this.memoryStore.createMemory({
|
|
172
|
+
memory_type: "project_memory",
|
|
173
|
+
uri: record.uri,
|
|
174
|
+
title: record.title,
|
|
175
|
+
content: record.content,
|
|
176
|
+
keywords: record.keywords,
|
|
177
|
+
bound_files: record.bound_files,
|
|
178
|
+
file_md5_map: record.file_md5_map,
|
|
179
|
+
last_check_time: record.last_check_time,
|
|
180
|
+
});
|
|
181
|
+
synced++;
|
|
182
|
+
logger.info(`[McpServer] 同步永久记忆成功: ${record.uri}`);
|
|
183
|
+
}
|
|
184
|
+
catch (e) {
|
|
185
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
186
|
+
if (msg.includes("already exists")
|
|
187
|
+
|| msg.includes("Memory already exists")) {
|
|
188
|
+
skipped++;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
logger.warn(`[McpServer] 同步永久记忆失败 (${record.uri}): ${msg}`);
|
|
192
|
+
skipped++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
logger.info(`[McpServer] 永久记忆同步完成: 成功 ${synced}, 跳过 ${skipped}, 共 ${localMemories.length}`);
|
|
197
|
+
await this.reportCombinedMemoryStats();
|
|
198
|
+
}
|
|
93
199
|
/**
|
|
94
200
|
* 规范化绑定的文件路径。
|
|
95
201
|
* 项目约定以下前缀都表示工作区根路径:
|
|
@@ -281,8 +387,6 @@ export class McpServer {
|
|
|
281
387
|
case "get_profile": {
|
|
282
388
|
return await this.agentClient.getProfile();
|
|
283
389
|
}
|
|
284
|
-
case "get_status":
|
|
285
|
-
return this.agentClient.getState();
|
|
286
390
|
case "hire_colleague":
|
|
287
391
|
return await this.handleHireColleague(args);
|
|
288
392
|
case "hire_colleagues_batch":
|
|
@@ -329,8 +433,6 @@ export class McpServer {
|
|
|
329
433
|
return await this.handleListMemories(args);
|
|
330
434
|
case "get_memory_stats":
|
|
331
435
|
return await this.handleGetMemoryStats(args);
|
|
332
|
-
case "archive_recently_memory":
|
|
333
|
-
return await this.handleArchiveRecentlyMemory(args);
|
|
334
436
|
// Memory 增强功能工具 (v1.6.0)
|
|
335
437
|
case "list_memories_by_file":
|
|
336
438
|
return await this.handleListMemoriesByFile(args);
|
|
@@ -341,23 +443,15 @@ export class McpServer {
|
|
|
341
443
|
return await this.handleMemoryRecurrence(args);
|
|
342
444
|
case "memory_recurrence_complete":
|
|
343
445
|
return await this.handleMemoryRecurrenceComplete();
|
|
344
|
-
// Memory 短期记忆 TODO 完成工具 (v1.14.0)
|
|
345
|
-
case "todo_completed":
|
|
346
|
-
return await this.handleTodoCompleted(args);
|
|
347
|
-
// 上下文压缩工具 (v1.8.0)
|
|
348
|
-
case "get_context_stats":
|
|
349
|
-
return await this.handleGetContextStats(args);
|
|
350
|
-
case "compact_context":
|
|
351
|
-
return await this.handleCompactContext(args);
|
|
352
446
|
default:
|
|
353
447
|
throw new Error(`Unknown tool: ${name}`);
|
|
354
448
|
}
|
|
355
449
|
}
|
|
356
450
|
// ============ 工具处理方法 ============
|
|
357
451
|
static MEMORY_TYPES = [
|
|
358
|
-
"task_list",
|
|
359
452
|
"recently_memory",
|
|
360
|
-
"
|
|
453
|
+
"agent_memory",
|
|
454
|
+
"project_memory",
|
|
361
455
|
"session_context",
|
|
362
456
|
];
|
|
363
457
|
static MEMORY_TOOL_NAMES = new Set([
|
|
@@ -368,15 +462,12 @@ export class McpServer {
|
|
|
368
462
|
"search_memory",
|
|
369
463
|
"list_memories",
|
|
370
464
|
"get_memory_stats",
|
|
371
|
-
"archive_recently_memory",
|
|
372
465
|
// Memory 增强功能工具 (v1.6.0)
|
|
373
466
|
"list_memories_by_file",
|
|
374
467
|
"abandon_memory_update",
|
|
375
468
|
// Memory 近期记忆过期与回忆工具 (v1.11.0)
|
|
376
469
|
"memory_recurrence",
|
|
377
470
|
"memory_recurrence_complete",
|
|
378
|
-
// Memory 短期记忆 TODO 完成工具 (v1.14.0)
|
|
379
|
-
"todo_completed",
|
|
380
471
|
]);
|
|
381
472
|
/**
|
|
382
473
|
* 从 startPath 向上逐级查找 .agentswork 目录。
|
|
@@ -427,7 +518,7 @@ export class McpServer {
|
|
|
427
518
|
}
|
|
428
519
|
/**
|
|
429
520
|
* 解析记忆存储路径。
|
|
430
|
-
* 主流程:
|
|
521
|
+
* 主流程:recently_memory/agent_memory 走 server 数据库;长期 project_memory 默认写入项目 .agentswork/memory/memory/<roleName>/<domain>.json。
|
|
431
522
|
*/
|
|
432
523
|
static resolveMemoryStorePaths() {
|
|
433
524
|
const configuredPath = String(process.env.AWS_MEMORY_STORE_PATH || "").trim();
|
|
@@ -483,16 +574,16 @@ export class McpServer {
|
|
|
483
574
|
}
|
|
484
575
|
/**
|
|
485
576
|
* 按记忆生命周期选择存储作用域。
|
|
486
|
-
* 主流程:长期
|
|
577
|
+
* 主流程:长期 project_memory 走角色级 store;recently_memory/agent_memory 走当前 Agent 实例级 store。
|
|
487
578
|
*/
|
|
488
579
|
/**
|
|
489
580
|
* 按记忆生命周期选择存储作用域。
|
|
490
|
-
* 主流程:永久记忆(
|
|
491
|
-
*
|
|
492
|
-
*
|
|
581
|
+
* 主流程:永久记忆(project_memory)始终走本地 roleMemoryStore(.agentswork),
|
|
582
|
+
* 确保本地文件始终有副本,后续部署恢复时可通过 Local → Server 同步回 server。
|
|
583
|
+
* recently_memory/agent_memory/session_context 始终走实例级 server store。
|
|
493
584
|
*/
|
|
494
585
|
getMemoryStoreForType(memoryType) {
|
|
495
|
-
if (memoryType === "
|
|
586
|
+
if (memoryType === "project_memory") {
|
|
496
587
|
return this.roleMemoryStore;
|
|
497
588
|
}
|
|
498
589
|
return this.memoryStore;
|
|
@@ -508,15 +599,12 @@ export class McpServer {
|
|
|
508
599
|
if (input.memory_type) {
|
|
509
600
|
return await this.getMemoryStoreForType(input.memory_type).readMemory(input);
|
|
510
601
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
if (longTermRecord) {
|
|
518
|
-
return longTermRecord;
|
|
519
|
-
}
|
|
602
|
+
const longTermRecord = await this.roleMemoryStore.readMemory({
|
|
603
|
+
...input,
|
|
604
|
+
memory_type: "project_memory",
|
|
605
|
+
});
|
|
606
|
+
if (longTermRecord) {
|
|
607
|
+
return longTermRecord;
|
|
520
608
|
}
|
|
521
609
|
return await this.memoryStore.readMemory(input);
|
|
522
610
|
}
|
|
@@ -534,14 +622,12 @@ export class McpServer {
|
|
|
534
622
|
memory_type: input.memory_type,
|
|
535
623
|
};
|
|
536
624
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
return { store: this.roleMemoryStore, memory_type: "memory" };
|
|
544
|
-
}
|
|
625
|
+
const longTermRecord = await this.roleMemoryStore.readMemory({
|
|
626
|
+
uri: input.uri,
|
|
627
|
+
memory_type: "project_memory",
|
|
628
|
+
});
|
|
629
|
+
if (longTermRecord) {
|
|
630
|
+
return { store: this.roleMemoryStore, memory_type: "project_memory" };
|
|
545
631
|
}
|
|
546
632
|
return { store: this.memoryStore };
|
|
547
633
|
}
|
|
@@ -558,17 +644,17 @@ export class McpServer {
|
|
|
558
644
|
// permanentCount 取两者的较大值作为保守估计(下界)。
|
|
559
645
|
// 注意: 当 instance 和 role 分别有对方没有的唯一 URI 时,实际并集 > Math.max,
|
|
560
646
|
// 但此处只有 count 无 URI 列表,无法精确计算并集,使用下界避免面板显示虚高。
|
|
561
|
-
const permanentCount = Math.max(instanceStats.by_type.
|
|
562
|
-
// total 同样保守估算:用 server 非
|
|
563
|
-
const serverNonMemoryTotal = instanceStats.total - instanceStats.by_type.
|
|
647
|
+
const permanentCount = Math.max(instanceStats.by_type.project_memory.count, roleStats.by_type.project_memory.count);
|
|
648
|
+
// total 同样保守估算:用 server 非 project_memory count + permanentCount 下界
|
|
649
|
+
const serverNonMemoryTotal = instanceStats.total - instanceStats.by_type.project_memory.count;
|
|
564
650
|
return {
|
|
565
651
|
total: serverNonMemoryTotal + permanentCount,
|
|
566
652
|
listable: instanceStats.listable,
|
|
567
653
|
by_type: {
|
|
568
|
-
task_list: instanceStats.by_type.task_list,
|
|
569
654
|
recently_memory: instanceStats.by_type.recently_memory,
|
|
570
|
-
|
|
571
|
-
|
|
655
|
+
agent_memory: instanceStats.by_type.agent_memory,
|
|
656
|
+
project_memory: {
|
|
657
|
+
...roleStats.by_type.project_memory,
|
|
572
658
|
count: permanentCount,
|
|
573
659
|
},
|
|
574
660
|
session_context: instanceStats.by_type.session_context,
|
|
@@ -591,44 +677,6 @@ export class McpServer {
|
|
|
591
677
|
logger.warn("[McpServer] 上报合并 memory 统计失败,已保留本地记忆写入结果:", error);
|
|
592
678
|
}
|
|
593
679
|
}
|
|
594
|
-
/**
|
|
595
|
-
* 同步本地永久记忆到服务端。
|
|
596
|
-
* 主流程:读取本地 roleMemoryStore 中所有 memory 类型记录 -> 逐条写入服务端 memoryStore -> 已存在的记录静默跳过。
|
|
597
|
-
*/
|
|
598
|
-
async syncLocalPermanentMemoryToServer() {
|
|
599
|
-
if (!this.hasRoleName)
|
|
600
|
-
return;
|
|
601
|
-
try {
|
|
602
|
-
const memories = await this.roleMemoryStore.listByType("memory");
|
|
603
|
-
if (memories.length === 0)
|
|
604
|
-
return;
|
|
605
|
-
let syncedCount = 0;
|
|
606
|
-
for (const memory of memories) {
|
|
607
|
-
try {
|
|
608
|
-
await this.memoryStore.createMemory({
|
|
609
|
-
memory_type: "memory",
|
|
610
|
-
uri: memory.uri,
|
|
611
|
-
title: memory.title,
|
|
612
|
-
content: memory.content,
|
|
613
|
-
keywords: memory.keywords,
|
|
614
|
-
bound_files: memory.bound_files,
|
|
615
|
-
});
|
|
616
|
-
syncedCount++;
|
|
617
|
-
}
|
|
618
|
-
catch (e) {
|
|
619
|
-
// If already exists on server, skip
|
|
620
|
-
const errMsg = e instanceof Error ? e.message : String(e);
|
|
621
|
-
if (!errMsg.includes("already exists")) {
|
|
622
|
-
logger.warn(`[McpServer] Failed to sync permanent memory ${memory.uri}:`, e);
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
logger.info(`[McpServer] Synced ${syncedCount}/${memories.length} permanent memories to server`);
|
|
627
|
-
}
|
|
628
|
-
catch (error) {
|
|
629
|
-
logger.error("[McpServer] Failed to sync permanent memories to server:", error);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
680
|
/**
|
|
633
681
|
* 系统提示词由 runtime adapter 在会话创建时注入,MCP 层面不再负责返回。
|
|
634
682
|
* 详见 startListeningInternal -> profileHandler 中的合并注入逻辑。
|
|
@@ -698,16 +746,16 @@ export class McpServer {
|
|
|
698
746
|
const created = await this.getMemoryStoreForType(input.memory_type).createMemory(input);
|
|
699
747
|
await this.reportCombinedMemoryStats();
|
|
700
748
|
// Dual-write permanent memory to server
|
|
701
|
-
if (input.memory_type === "
|
|
749
|
+
if (input.memory_type === "project_memory") {
|
|
702
750
|
try {
|
|
703
|
-
await this.memoryStore.createMemory(input)
|
|
751
|
+
await this.memoryStore.createMemory(input);
|
|
704
752
|
}
|
|
705
753
|
catch (e) {
|
|
706
754
|
logger.warn("[McpServer] Failed to sync permanent memory to server:", e);
|
|
707
755
|
}
|
|
708
756
|
}
|
|
709
|
-
// v1.13.0: recently_memory 过期判定、
|
|
710
|
-
// 无需 TS 侧本地缓存。服务端默认按类型填充正确状态(recently_memory='normal',
|
|
757
|
+
// v1.13.0: recently_memory 过期判定、agent_memory 过期/回忆状态由服务端 memory_status 字段管理,
|
|
758
|
+
// 无需 TS 侧本地缓存。服务端默认按类型填充正确状态(recently_memory='normal', agent_memory='normal')。
|
|
711
759
|
return this.toMemoryWriteResult(created);
|
|
712
760
|
}
|
|
713
761
|
/**
|
|
@@ -779,27 +827,23 @@ export class McpServer {
|
|
|
779
827
|
});
|
|
780
828
|
await this.reportCombinedMemoryStats();
|
|
781
829
|
// Dual-write permanent memory update to server(同步 MD5 map 避免服务端重复检测)
|
|
782
|
-
if (target.memory_type === "
|
|
830
|
+
if (target.memory_type === "project_memory") {
|
|
783
831
|
try {
|
|
784
|
-
await this.memoryStore
|
|
785
|
-
.updateMemory({
|
|
832
|
+
await this.memoryStore.updateMemory({
|
|
786
833
|
...input,
|
|
787
|
-
memory_type: "
|
|
834
|
+
memory_type: "project_memory",
|
|
788
835
|
file_md5_map: fileMd5Map,
|
|
789
836
|
last_check_time: lastCheckTime,
|
|
790
|
-
})
|
|
791
|
-
.catch(() => { });
|
|
837
|
+
});
|
|
792
838
|
}
|
|
793
839
|
catch (e) {
|
|
794
840
|
logger.warn("[McpServer] Failed to sync permanent memory update to server:", e);
|
|
795
841
|
}
|
|
796
842
|
}
|
|
797
|
-
// v1.13.0:
|
|
798
|
-
// 显式传入的
|
|
799
|
-
//
|
|
800
|
-
//
|
|
801
|
-
// update_memory 内容变更不会重置 created_at,因此"续期"实际对应"修改 created_at" — 当前不提供该能力,
|
|
802
|
-
// 如需续期建议使用 archive_recently_memory 归档或重新 createMemory 替代。
|
|
843
|
+
// v1.13.0: agent_memory 过期与回忆状态由服务端 memory_status 字段管理。
|
|
844
|
+
// 显式传入的 recall_status / memory_status 已在 updateMemory 内部透传到服务端。
|
|
845
|
+
// agent_memory 内容变更会触发 TTL ×2 续期(由 MemoryStore.updateMemory 内部处理),
|
|
846
|
+
// recently_memory 续期建议重新 createMemory 替代。
|
|
803
847
|
return this.toMemoryWriteResult(updated);
|
|
804
848
|
}
|
|
805
849
|
/**
|
|
@@ -819,14 +863,12 @@ export class McpServer {
|
|
|
819
863
|
});
|
|
820
864
|
if (deleted) {
|
|
821
865
|
await this.reportCombinedMemoryStats();
|
|
822
|
-
// v1.13.0: 服务端 deleteMemory
|
|
866
|
+
// v1.13.0: 服务端 deleteMemory 内部已处理相关清理逻辑,
|
|
823
867
|
// TS 侧无需任何本地元数据清理。
|
|
824
868
|
// Dual-write permanent memory delete to server
|
|
825
|
-
if (effectiveType === "
|
|
869
|
+
if (effectiveType === "project_memory") {
|
|
826
870
|
try {
|
|
827
|
-
await this.memoryStore
|
|
828
|
-
.deleteMemory({ uri: input.uri, memory_type: "memory" })
|
|
829
|
-
.catch(() => { });
|
|
871
|
+
await this.memoryStore.deleteMemory({ uri: input.uri, memory_type: "project_memory" });
|
|
830
872
|
}
|
|
831
873
|
catch (e) {
|
|
832
874
|
logger.warn("[McpServer] Failed to sync permanent memory delete to server:", e);
|
|
@@ -872,7 +914,7 @@ export class McpServer {
|
|
|
872
914
|
}
|
|
873
915
|
/**
|
|
874
916
|
* 搜索自己工作区的所有记忆。
|
|
875
|
-
* 具体逻辑:按 memory_type 分流 -> 无类型时合并 roleMemoryStore(永久)与 memoryStore(
|
|
917
|
+
* 具体逻辑:按 memory_type 分流 -> 无类型时合并 roleMemoryStore(永久)与 memoryStore(recently/agent)。
|
|
876
918
|
*/
|
|
877
919
|
async searchOwnMemories(input) {
|
|
878
920
|
if (input.memory_type) {
|
|
@@ -886,7 +928,7 @@ export class McpServer {
|
|
|
886
928
|
return await this.memoryStore.searchMemory(input);
|
|
887
929
|
}
|
|
888
930
|
const [roleResult, instanceResult] = await Promise.all([
|
|
889
|
-
this.roleMemoryStore.searchMemory({ ...input, memory_type: "
|
|
931
|
+
this.roleMemoryStore.searchMemory({ ...input, memory_type: "project_memory" }),
|
|
890
932
|
this.memoryStore.searchMemory(input),
|
|
891
933
|
]);
|
|
892
934
|
const seen = new Set();
|
|
@@ -926,7 +968,7 @@ export class McpServer {
|
|
|
926
968
|
}
|
|
927
969
|
/**
|
|
928
970
|
* 列出短期类型记忆。
|
|
929
|
-
* 主流程:只允许
|
|
971
|
+
* 主流程:只允许 recently_memory/agent_memory/project_memory,由 MemoryStore 阻止 session_context 的全量列出。
|
|
930
972
|
*/
|
|
931
973
|
async handleListMemories(args) {
|
|
932
974
|
return await this.memoryStore.listByType(this.getRequiredString(args, "memory_type"));
|
|
@@ -952,58 +994,39 @@ export class McpServer {
|
|
|
952
994
|
},
|
|
953
995
|
};
|
|
954
996
|
}
|
|
955
|
-
/**
|
|
956
|
-
* 归档近期记忆。
|
|
957
|
-
* 主流程:将 recently_memory 原地转换为长期 memory,并递增版本号。
|
|
958
|
-
*/
|
|
959
|
-
async handleArchiveRecentlyMemory(args) {
|
|
960
|
-
const uri = this.getRequiredString(args, "uri");
|
|
961
|
-
if (this.memoryStoresShareFile) {
|
|
962
|
-
return await this.memoryStore.archiveRecentlyMemory(uri);
|
|
963
|
-
}
|
|
964
|
-
const record = await this.memoryStore.readMemory({
|
|
965
|
-
uri,
|
|
966
|
-
memory_type: "recently_memory",
|
|
967
|
-
});
|
|
968
|
-
if (!record) {
|
|
969
|
-
throw new Error(`Memory not found: ${uri}`);
|
|
970
|
-
}
|
|
971
|
-
const archived = await this.getMemoryStoreForType("memory").createMemory({
|
|
972
|
-
memory_type: "memory",
|
|
973
|
-
uri: record.uri,
|
|
974
|
-
title: record.title,
|
|
975
|
-
content: record.content,
|
|
976
|
-
keywords: record.keywords,
|
|
977
|
-
bound_files: record.bound_files,
|
|
978
|
-
});
|
|
979
|
-
await this.memoryStore.deleteMemory({
|
|
980
|
-
uri: record.uri,
|
|
981
|
-
memory_type: "recently_memory",
|
|
982
|
-
});
|
|
983
|
-
await this.reportCombinedMemoryStats();
|
|
984
|
-
return archived;
|
|
985
|
-
}
|
|
986
997
|
/**
|
|
987
998
|
* 按文件查询绑定了该文件的 Memory
|
|
988
999
|
* Memory 增强功能 (v1.6.0)
|
|
989
1000
|
*/
|
|
990
1001
|
/**
|
|
991
1002
|
* 按文件路径查询 Memory(支持 scope 跨 Agent 搜索)。
|
|
992
|
-
* 主流程:解析scope -> own(默认)仅自己 -> children
|
|
993
|
-
*
|
|
1003
|
+
* 主流程:解析scope -> own(默认)仅自己 -> children搜索子Agent -> all合并。
|
|
1004
|
+
* 子 Agent 搜索同时走两条路径:
|
|
1005
|
+
* 1. server searchChildByFilePath(查 DB)
|
|
1006
|
+
* 2. 本地文件系统扫描 child .agentswork/memory/memory/(查本地永久记忆文件)
|
|
1007
|
+
* 两条路径结果按 id 去重合并,确保无论子 Agent 的永久记忆是否已同步到
|
|
1008
|
+
* 服务端数据库,父 Agent 都能搜索到。
|
|
994
1009
|
*/
|
|
995
1010
|
async handleListMemoriesByFile(args) {
|
|
996
1011
|
const rawPath = this.getRequiredString(args, "file_path");
|
|
997
1012
|
const normalizedPath = this.normalizeBoundFilePath(rawPath);
|
|
998
1013
|
const memoryType = this.getOptionalMemoryType(args);
|
|
999
1014
|
const rawScope = this.getOptionalString(args, "scope");
|
|
1000
|
-
|
|
1001
|
-
|
|
1015
|
+
// 默认 scope='all':合并搜索自身与子Agent绑定文件的记忆
|
|
1016
|
+
const scope = rawScope === "own" || rawScope === "children" ? rawScope : "all";
|
|
1017
|
+
// 搜索子 Agent 的 memory(server + 本地文件系统双路径,并行)
|
|
1018
|
+
const fetchChildMemories = async () => {
|
|
1019
|
+
const [serverResult, localResult] = await Promise.all([
|
|
1020
|
+
this.memoryStore.listByFile(normalizedPath, {
|
|
1021
|
+
memory_type: memoryType,
|
|
1022
|
+
scope: "children",
|
|
1023
|
+
}).catch(() => []), // server 不可用时降级
|
|
1024
|
+
this.searchChildMemoriesByFileLocal(normalizedPath, memoryType),
|
|
1025
|
+
]);
|
|
1026
|
+
return this.mergeIdDedupe(serverResult, localResult);
|
|
1027
|
+
};
|
|
1002
1028
|
if (scope === "children") {
|
|
1003
|
-
const childResult = await
|
|
1004
|
-
memory_type: memoryType,
|
|
1005
|
-
scope: "children",
|
|
1006
|
-
});
|
|
1029
|
+
const childResult = await fetchChildMemories();
|
|
1007
1030
|
return {
|
|
1008
1031
|
file_path: normalizedPath,
|
|
1009
1032
|
count: childResult.length,
|
|
@@ -1019,11 +1042,8 @@ export class McpServer {
|
|
|
1019
1042
|
memories: ownMemories.map(m => this.toMemoryRecordWithMetadata(m)),
|
|
1020
1043
|
};
|
|
1021
1044
|
}
|
|
1022
|
-
// scope='all': 合并自己的 + 子 Agent
|
|
1023
|
-
const childResult = await
|
|
1024
|
-
memory_type: memoryType,
|
|
1025
|
-
scope: "children",
|
|
1026
|
-
});
|
|
1045
|
+
// scope='all': 合并自己的 + 子 Agent 的(server + 本地双路径已由 fetchChildMemories 聚合)
|
|
1046
|
+
const childResult = await fetchChildMemories();
|
|
1027
1047
|
const seen = new Set(ownMemories.map(m => m.id));
|
|
1028
1048
|
const merged = [...ownMemories];
|
|
1029
1049
|
for (const m of childResult) {
|
|
@@ -1039,6 +1059,135 @@ export class McpServer {
|
|
|
1039
1059
|
memories: merged.map(m => this.toMemoryRecordWithMetadata(m)),
|
|
1040
1060
|
};
|
|
1041
1061
|
}
|
|
1062
|
+
/**
|
|
1063
|
+
* 按 id 去重合并两批 MemoryRecord,按 updatedAt 倒序排列。
|
|
1064
|
+
* 首批(left)优先级高,其 id 会标记为 seen,重复的 right 项被丢弃。
|
|
1065
|
+
*/
|
|
1066
|
+
mergeIdDedupe(left, right) {
|
|
1067
|
+
const seen = new Set();
|
|
1068
|
+
for (const m of left) {
|
|
1069
|
+
if (m.id)
|
|
1070
|
+
seen.add(m.id);
|
|
1071
|
+
}
|
|
1072
|
+
const merged = [...left];
|
|
1073
|
+
for (const m of right) {
|
|
1074
|
+
if (m.id && !seen.has(m.id)) {
|
|
1075
|
+
merged.push(m);
|
|
1076
|
+
seen.add(m.id);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
merged.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1080
|
+
return merged;
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* 通过文件系统扫描子 Agent 的 .agentswork 目录,
|
|
1084
|
+
* 读取永久记忆 JSON 文件,按 path_prefix 匹配 bound_files。
|
|
1085
|
+
*
|
|
1086
|
+
* 主流程:
|
|
1087
|
+
* 1. 解析 file_path(如 ./frontend/src/main.ts)的路径层级
|
|
1088
|
+
* 2. 逐级(从长到短)找到子 Agent 的 .agentswork 目录
|
|
1089
|
+
* 3. 读取该目录下所有 role 的 JSON 文件
|
|
1090
|
+
* 4. 对每条记忆,将 bound_files 与 child-relative 路径比较
|
|
1091
|
+
*
|
|
1092
|
+
* 作为 server searchChildByFilePath 的本地补充,
|
|
1093
|
+
* 确保子 Agent 的本地永久记忆(未同步到 server)也能被搜索到。
|
|
1094
|
+
*/
|
|
1095
|
+
async searchChildMemoriesByFileLocal(filePath, memoryType) {
|
|
1096
|
+
const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
|
|
1097
|
+
const normalizedFilePath = this.normalizeBoundFilePath(filePath);
|
|
1098
|
+
// 解析路径 segments
|
|
1099
|
+
// 例如 ./frontend/src/main.ts → relativePath = "frontend/src/main.ts"
|
|
1100
|
+
// segments = ["frontend", "src", "main.ts"]
|
|
1101
|
+
const relativePath = normalizedFilePath.startsWith('./')
|
|
1102
|
+
? normalizedFilePath.substring(2)
|
|
1103
|
+
: normalizedFilePath;
|
|
1104
|
+
const segments = relativePath.split('/');
|
|
1105
|
+
const results = [];
|
|
1106
|
+
const seenById = new Set();
|
|
1107
|
+
const { access, readdir, readFile } = await import('node:fs/promises');
|
|
1108
|
+
// 从最长的路径前缀开始尝试(兼容嵌套子 Agent:深度=2 时 prefix=frontend/src)
|
|
1109
|
+
for (let depth = segments.length - 1; depth >= 1; depth--) {
|
|
1110
|
+
const prefixSegments = segments.slice(0, depth);
|
|
1111
|
+
const childRelativeSegments = segments.slice(depth);
|
|
1112
|
+
const childRelativePath = './' + childRelativeSegments.join('/');
|
|
1113
|
+
// 子 Agent 的候选工作区路径
|
|
1114
|
+
const childWorkspace = path.join(workspacePath, ...prefixSegments);
|
|
1115
|
+
// 跳过父 Agent 自身(避免把 parent 当 child 扫)
|
|
1116
|
+
if (childWorkspace === workspacePath)
|
|
1117
|
+
continue;
|
|
1118
|
+
const childAgentsworkDir = path.join(childWorkspace, '.agentswork');
|
|
1119
|
+
// 检查 .agentswork 是否存在
|
|
1120
|
+
try {
|
|
1121
|
+
await access(childAgentsworkDir);
|
|
1122
|
+
}
|
|
1123
|
+
catch {
|
|
1124
|
+
continue; // 这个前缀不是子 Agent 工作区
|
|
1125
|
+
}
|
|
1126
|
+
// 读取子 Agent 的永久记忆目录 .agentswork/memory/memory/<roleName>/
|
|
1127
|
+
const memoryBaseDir = path.join(childAgentsworkDir, 'memory', 'memory');
|
|
1128
|
+
let roleDirs;
|
|
1129
|
+
try {
|
|
1130
|
+
const entries = await readdir(memoryBaseDir, { withFileTypes: true });
|
|
1131
|
+
roleDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
|
1132
|
+
}
|
|
1133
|
+
catch {
|
|
1134
|
+
continue;
|
|
1135
|
+
}
|
|
1136
|
+
for (const roleDir of roleDirs) {
|
|
1137
|
+
const rolePath = path.join(memoryBaseDir, roleDir);
|
|
1138
|
+
let files;
|
|
1139
|
+
try {
|
|
1140
|
+
files = await readdir(rolePath);
|
|
1141
|
+
}
|
|
1142
|
+
catch {
|
|
1143
|
+
continue;
|
|
1144
|
+
}
|
|
1145
|
+
for (const file of files) {
|
|
1146
|
+
if (!file.endsWith('.json'))
|
|
1147
|
+
continue;
|
|
1148
|
+
const jsonPath = path.join(rolePath, file);
|
|
1149
|
+
let content;
|
|
1150
|
+
try {
|
|
1151
|
+
content = await readFile(jsonPath, 'utf-8');
|
|
1152
|
+
}
|
|
1153
|
+
catch {
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
let parsed;
|
|
1157
|
+
try {
|
|
1158
|
+
parsed = JSON.parse(content);
|
|
1159
|
+
}
|
|
1160
|
+
catch {
|
|
1161
|
+
continue;
|
|
1162
|
+
}
|
|
1163
|
+
if (!parsed.by_uri)
|
|
1164
|
+
continue;
|
|
1165
|
+
// 遍历该 domain 的所有记忆,匹配 bound_files
|
|
1166
|
+
for (const [uri, record] of Object.entries(parsed.by_uri)) {
|
|
1167
|
+
if (!record)
|
|
1168
|
+
continue;
|
|
1169
|
+
const recordId = record.id || uri;
|
|
1170
|
+
if (seenById.has(recordId))
|
|
1171
|
+
continue;
|
|
1172
|
+
if (memoryType && record.memory_type !== memoryType)
|
|
1173
|
+
continue;
|
|
1174
|
+
if (record.bound_files && record.bound_files.length > 0) {
|
|
1175
|
+
const matched = record.bound_files.some(bf => this.normalizeBoundFilePath(bf) === childRelativePath);
|
|
1176
|
+
if (matched) {
|
|
1177
|
+
seenById.add(recordId);
|
|
1178
|
+
results.push({
|
|
1179
|
+
...record,
|
|
1180
|
+
uri: record.uri || uri,
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
|
1189
|
+
return results;
|
|
1190
|
+
}
|
|
1042
1191
|
/**
|
|
1043
1192
|
* 按文件路径搜索本 Agent 的记忆。
|
|
1044
1193
|
* 同时查询临时记忆(server)和永久记忆(local roleMemoryStore)。
|
|
@@ -1049,8 +1198,8 @@ export class McpServer {
|
|
|
1049
1198
|
});
|
|
1050
1199
|
let localResult = [];
|
|
1051
1200
|
// 补充查询本地永久记忆(server 可能未同步);无角色名时跳过(避免 default-role 跨角色污染)
|
|
1052
|
-
if (this.hasRoleName && (!memoryType || memoryType === "
|
|
1053
|
-
localResult = await this.roleMemoryStore.listByFile(filePath, "
|
|
1201
|
+
if (this.hasRoleName && (!memoryType || memoryType === "project_memory")) {
|
|
1202
|
+
localResult = await this.roleMemoryStore.listByFile(filePath, "project_memory");
|
|
1054
1203
|
}
|
|
1055
1204
|
// 去重合并
|
|
1056
1205
|
const seen = new Set(serverResult.map(m => `${m.memory_type}:${m.uri}`));
|
|
@@ -1086,17 +1235,19 @@ export class McpServer {
|
|
|
1086
1235
|
message: "Memory 更新状态已清除",
|
|
1087
1236
|
};
|
|
1088
1237
|
}
|
|
1089
|
-
// ============ Memory 近期记忆过期与回忆工具处理 (v1.11.0 / v1.13.0) ============
|
|
1238
|
+
// ============ Memory 近期记忆过期与回忆工具处理 (v1.11.0 / v1.13.0 / v1.19.0) ============
|
|
1090
1239
|
// v1.13.0: 过期判定与 recalling 状态标记已统一迁移到服务端 memory_status 字段,
|
|
1091
1240
|
// 下方两个工具方法直接调用服务端接口,不再依赖任何本地元数据。
|
|
1241
|
+
// v1.19.0: recently_memory 纳入过期回忆流程,与 agent_memory 行为完全一致。
|
|
1092
1242
|
/**
|
|
1093
1243
|
* 处理 memory_recurrence 工具调用。
|
|
1094
|
-
* 主流程:调用服务端 list_expired_memories
|
|
1244
|
+
* 主流程:调用服务端 list_expired_memories 拉取已过期的 recently_memory / agent_memory ->
|
|
1095
1245
|
* 按过期时间排序(最早优先)-> 取前 limit 条 ->
|
|
1096
1246
|
* 仅标记这 limit 条为「回忆中」状态 -> 返回这些记忆及剩余数量。
|
|
1097
1247
|
*/
|
|
1098
1248
|
async handleMemoryRecurrence(args) {
|
|
1099
1249
|
// v1.13.0: 过期判定与 recalling 状态全部由服务端管理
|
|
1250
|
+
// v1.19.0: recently_memory 同样纳入过期回忆流程
|
|
1100
1251
|
const limit = Math.max(1, args?.limit ?? 10);
|
|
1101
1252
|
const expiredMemories = await this.memoryStore.listExpiredRecentlyMemories();
|
|
1102
1253
|
if (expiredMemories.length === 0) {
|
|
@@ -1145,10 +1296,10 @@ export class McpServer {
|
|
|
1145
1296
|
totalExpired: expiredMemories.length,
|
|
1146
1297
|
message: remaining > 0
|
|
1147
1298
|
? `本次处理 ${batch.length} 条即将遗忘的记忆(最早过期优先),剩余 ${remaining} 条待处理。\n` +
|
|
1148
|
-
`请审查后决定(
|
|
1299
|
+
`请审查后决定(update_memory 续期,TTL ×2 / 不处理),\n` +
|
|
1149
1300
|
`完成后调用 memory_recurrence_complete 清理,再调 memory_recurrence 处理下一批。`
|
|
1150
1301
|
: `已处理全部 ${batch.length} 条即将遗忘的记忆。\n` +
|
|
1151
|
-
`请审查后决定(
|
|
1302
|
+
`请审查后决定(update_memory 续期,TTL ×2 / 不处理),\n` +
|
|
1152
1303
|
`完成后调用 memory_recurrence_complete 清理。`,
|
|
1153
1304
|
};
|
|
1154
1305
|
}
|
|
@@ -1175,104 +1326,6 @@ export class McpServer {
|
|
|
1175
1326
|
: `已清理 ${deletedCount} 条已过期近期记忆。`,
|
|
1176
1327
|
};
|
|
1177
1328
|
}
|
|
1178
|
-
// ============ 短期记忆 TODO 完成工具处理 (v1.14.0) ============
|
|
1179
|
-
/**
|
|
1180
|
-
* 标记 task_list 短期记忆为已完成。
|
|
1181
|
-
* 主流程:调用 memoryStore.todoCompleted(uri) -> 标记完成 -> 检查全部完成 -> 自动清理 -> 返回结果。
|
|
1182
|
-
*/
|
|
1183
|
-
async handleTodoCompleted(args) {
|
|
1184
|
-
const result = await this.memoryStore.todoCompleted(args.uri);
|
|
1185
|
-
await this.reportCombinedMemoryStats();
|
|
1186
|
-
const allCleanedMsg = result.allCleaned
|
|
1187
|
-
? `所有 TODO 均已完成,已自动清理 ${result.cleanedCount} 条已完成短期记忆。`
|
|
1188
|
-
: "已标记为已完成。";
|
|
1189
|
-
return {
|
|
1190
|
-
...result,
|
|
1191
|
-
message: allCleanedMsg,
|
|
1192
|
-
};
|
|
1193
|
-
}
|
|
1194
|
-
// ============ 上下文压缩工具处理 (v1.8.0) ============
|
|
1195
|
-
/**
|
|
1196
|
-
* 获取当前会话的上下文统计。
|
|
1197
|
-
* 主流程:从 profile 获取模型名称 -> 通过模型名查找上下文窗口 -> 调用 ContextManager 获取统计 -> 返回。
|
|
1198
|
-
*/
|
|
1199
|
-
async handleGetContextStats(args) {
|
|
1200
|
-
const profile = await this.agentClient.getProfile().catch(() => null);
|
|
1201
|
-
const sessionId = args.sessionId ?? profile?.runtimeSessionId ?? "default";
|
|
1202
|
-
const modelName = profile?.prompt ? this.guessModelName(profile) : "unknown";
|
|
1203
|
-
const modelInfo = {
|
|
1204
|
-
provider: modelName.split("/")[0] || "unknown",
|
|
1205
|
-
model: modelName,
|
|
1206
|
-
contextLimit: 0, // ContextManager 内部会通过 lookupModelLimits 查找
|
|
1207
|
-
};
|
|
1208
|
-
const stats = await this.contextManager.getStats(sessionId, modelInfo);
|
|
1209
|
-
// 如果 contextLimit 仍为 0,用查找表的默认值
|
|
1210
|
-
if (stats.contextLimit === 0) {
|
|
1211
|
-
const limits = this.contextManager.lookupModelLimits(modelName);
|
|
1212
|
-
return { ...stats, contextLimit: limits.contextLimit, remaining: limits.contextLimit - stats.totalUsed, usagePercent: Math.round((stats.totalUsed / limits.contextLimit) * 100) };
|
|
1213
|
-
}
|
|
1214
|
-
return stats;
|
|
1215
|
-
}
|
|
1216
|
-
/**
|
|
1217
|
-
* 从 profile 猜测当前使用的模型名称。
|
|
1218
|
-
* 主流程:读取 prompt 中的模型信息 -> 回退到 runtimeCliCommand 中的提示 -> 最终回退到环境变量或默认值。
|
|
1219
|
-
*/
|
|
1220
|
-
guessModelName(profile) {
|
|
1221
|
-
// 尝试从 prompt 中提取模型名(prompt 可能包含 @model 标记)
|
|
1222
|
-
const promptMatch = profile.prompt?.match(/@model\s+(\S+)/i);
|
|
1223
|
-
if (promptMatch?.[1])
|
|
1224
|
-
return promptMatch[1];
|
|
1225
|
-
// 回退到 CLI 命令中的线索
|
|
1226
|
-
if (profile.runtimeMode)
|
|
1227
|
-
return profile.runtimeMode;
|
|
1228
|
-
// 默认值
|
|
1229
|
-
return process.env.AWS_DEFAULT_MODEL || "gpt-4o";
|
|
1230
|
-
}
|
|
1231
|
-
/**
|
|
1232
|
-
* 处理上下文压缩请求。
|
|
1233
|
-
* 主流程:解析策略 -> 获取消息历史 -> 执行压缩 -> 保存结果 -> 返回。
|
|
1234
|
-
*/
|
|
1235
|
-
async handleCompactContext(args) {
|
|
1236
|
-
const profile = await this.agentClient.getProfile().catch(() => null);
|
|
1237
|
-
const sessionId = profile?.runtimeSessionId ?? "default";
|
|
1238
|
-
const strategy = args.strategy ?? "auto";
|
|
1239
|
-
const keepLastTurns = args.keepLastTurns ?? 3;
|
|
1240
|
-
// 获取消息历史
|
|
1241
|
-
const messages = await this.fetchRecentMessages();
|
|
1242
|
-
switch (strategy) {
|
|
1243
|
-
case "prune": {
|
|
1244
|
-
const result = this.contextManager.prune(messages, keepLastTurns);
|
|
1245
|
-
await this.contextManager.saveCompactionResult(sessionId, result);
|
|
1246
|
-
return result;
|
|
1247
|
-
}
|
|
1248
|
-
case "summarize": {
|
|
1249
|
-
// summarize 需要 Agent 自己调用 LLM 来完成
|
|
1250
|
-
// 这里先执行 prune 释放部分空间,然后返回需要 Agent 进行 summarize
|
|
1251
|
-
const pruneResult = this.contextManager.prune(messages, keepLastTurns);
|
|
1252
|
-
await this.contextManager.saveCompactionResult(sessionId, pruneResult);
|
|
1253
|
-
return {
|
|
1254
|
-
...pruneResult,
|
|
1255
|
-
strategy: "summarize",
|
|
1256
|
-
summary: await this.contextManager.getCompactionSummary(sessionId) ?? undefined,
|
|
1257
|
-
};
|
|
1258
|
-
}
|
|
1259
|
-
case "auto":
|
|
1260
|
-
default: {
|
|
1261
|
-
// 先尝试 prune
|
|
1262
|
-
const pruneResult = this.contextManager.prune(messages, keepLastTurns);
|
|
1263
|
-
if (pruneResult.freedTokens >= 20_000) {
|
|
1264
|
-
await this.contextManager.saveCompactionResult(sessionId, pruneResult);
|
|
1265
|
-
return pruneResult;
|
|
1266
|
-
}
|
|
1267
|
-
// prune 释放不够,回退到 summarize
|
|
1268
|
-
return {
|
|
1269
|
-
...pruneResult,
|
|
1270
|
-
strategy: "summarize",
|
|
1271
|
-
summary: await this.contextManager.getCompactionSummary(sessionId) ?? undefined,
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
1329
|
/**
|
|
1277
1330
|
* 获取最近的消息历史用于压缩。
|
|
1278
1331
|
* 主流程:获取未读消息 + 历史消息 -> 合并排序 -> 返回。
|
|
@@ -1382,46 +1435,44 @@ export class McpServer {
|
|
|
1382
1435
|
return result;
|
|
1383
1436
|
}
|
|
1384
1437
|
async handlePollMessage() {
|
|
1385
|
-
|
|
1438
|
+
if (this.pendingPollMessage) {
|
|
1439
|
+
logger.info('[McpServer] poll_message 已有进行中的调用,等待其完成');
|
|
1440
|
+
return await this.pendingPollMessage;
|
|
1441
|
+
}
|
|
1442
|
+
this.pendingPollMessage = (async () => {
|
|
1443
|
+
// 入口处检查 Todo 未完成项提醒:仅在每次 poll_message 调用时检查一次,
|
|
1444
|
+
// 不引入后台定时器。状态变化(指纹不同)且有未完成项时立即返回通知;
|
|
1445
|
+
// 否则进入原有阻塞等待真实消息的流程。
|
|
1446
|
+
const todoNotification = await this.buildTodoReminderNotification();
|
|
1447
|
+
if (todoNotification) {
|
|
1448
|
+
return this.buildPollResult({
|
|
1449
|
+
directMessages: [todoNotification],
|
|
1450
|
+
groupMessages: [],
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
return await this.doPollMessage();
|
|
1454
|
+
})();
|
|
1455
|
+
try {
|
|
1456
|
+
return await this.pendingPollMessage;
|
|
1457
|
+
}
|
|
1458
|
+
finally {
|
|
1459
|
+
this.pendingPollMessage = null;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
async doPollMessage() {
|
|
1386
1463
|
await this.hydrateUnreadMessagesFromServer({
|
|
1387
1464
|
blockIfEmpty: false,
|
|
1388
1465
|
includePipelineTasks: false,
|
|
1389
1466
|
});
|
|
1390
|
-
// poll_message 永不超时返回空:while(true) 内部每 30s 轮询一次
|
|
1391
|
-
// WebSocket 消息(主动通知)→ 实时响应
|
|
1392
|
-
// Memory 状态变化(无法主动通知)→ 最长 30s 延迟
|
|
1393
1467
|
while (true) {
|
|
1394
|
-
// 1. 检查 WebSocket 缓冲区(来自 push + 后台补拉)
|
|
1395
1468
|
const buffered = this.messageBuffer.get();
|
|
1396
1469
|
if (buffered.directMessages.length > 0 || buffered.groupMessages.length > 0) {
|
|
1397
1470
|
return await this.finalizeWithMemoryNotifications(buffered);
|
|
1398
1471
|
}
|
|
1399
|
-
// 2. 检查 Memory 通知(缓冲区无消息时直接返回)
|
|
1400
|
-
if (!this.memoryFileCheckInterval) {
|
|
1401
|
-
const fileChange = await this.buildMemoryFileChangeNotification();
|
|
1402
|
-
if (fileChange) {
|
|
1403
|
-
return this.buildPollResult({ directMessages: [fileChange], groupMessages: [] });
|
|
1404
|
-
}
|
|
1405
|
-
}
|
|
1406
|
-
const recurrence = await this.getCachedMemoryNotification('recurrence', () => this.buildMemoryRecurrenceNotification());
|
|
1407
|
-
if (recurrence) {
|
|
1408
|
-
return this.buildPollResult({ directMessages: [recurrence], groupMessages: [] });
|
|
1409
|
-
}
|
|
1410
|
-
const todo = await this.getCachedMemoryNotification('todo', () => this.buildMemoryTodoNotification());
|
|
1411
|
-
if (todo) {
|
|
1412
|
-
return this.buildPollResult({ directMessages: [todo], groupMessages: [] });
|
|
1413
|
-
}
|
|
1414
|
-
const cleanup = await this.getCachedMemoryNotification('cleanup', () => this.buildMemoryCleanupNotification());
|
|
1415
|
-
if (cleanup) {
|
|
1416
|
-
return this.buildPollResult({ directMessages: [cleanup], groupMessages: [] });
|
|
1417
|
-
}
|
|
1418
|
-
// 3. 带超时等 WebSocket 消息(内部轮询周期)
|
|
1419
|
-
// poll() 一旦返回就已消费 buffer,不可 `continue` 回 get()(空)
|
|
1420
1472
|
const polled = await this.messageBuffer.poll(POLL_MESSAGE_INTERNAL_POLL_INTERVAL_MS);
|
|
1421
1473
|
if (polled.directMessages.length > 0 || polled.groupMessages.length > 0) {
|
|
1422
1474
|
return await this.finalizeWithMemoryNotifications(polled);
|
|
1423
1475
|
}
|
|
1424
|
-
// 超时 → 继续循环,重新检查 Memory 和缓冲区
|
|
1425
1476
|
}
|
|
1426
1477
|
}
|
|
1427
1478
|
/**
|
|
@@ -1435,17 +1486,15 @@ export class McpServer {
|
|
|
1435
1486
|
if (await this.prependMemoryNotification(messages, () => this.getCachedMemoryNotification('recurrence', () => this.buildMemoryRecurrenceNotification()))) {
|
|
1436
1487
|
return this.buildPollResult(messages);
|
|
1437
1488
|
}
|
|
1438
|
-
if (await this.prependMemoryNotification(messages, () => this.getCachedMemoryNotification('todo', () => this.buildMemoryTodoNotification()))) {
|
|
1439
|
-
return this.buildPollResult(messages);
|
|
1440
|
-
}
|
|
1441
1489
|
await this.prependMemoryNotification(messages, () => this.getCachedMemoryNotification('cleanup', () => this.buildMemoryCleanupNotification()));
|
|
1442
1490
|
return this.buildPollResult(messages);
|
|
1443
1491
|
}
|
|
1444
|
-
buildPollResult(messages) {
|
|
1492
|
+
buildPollResult(messages, fromBuffer = true) {
|
|
1445
1493
|
return {
|
|
1446
1494
|
directMessages: messages.directMessages,
|
|
1447
1495
|
groupMessages: messages.groupMessages,
|
|
1448
|
-
fromBuffer
|
|
1496
|
+
fromBuffer,
|
|
1497
|
+
systemMessages: [],
|
|
1449
1498
|
};
|
|
1450
1499
|
}
|
|
1451
1500
|
/**
|
|
@@ -1495,8 +1544,8 @@ export class McpServer {
|
|
|
1495
1544
|
const cleanupActions = [
|
|
1496
1545
|
"请调用 get_memory_stats 查看容量详情",
|
|
1497
1546
|
"调用 list_memories 按类型查看超限记忆",
|
|
1498
|
-
"删除不再需要的
|
|
1499
|
-
"
|
|
1547
|
+
"删除不再需要的 recently_memory",
|
|
1548
|
+
"更新 agent_memory 续期(TTL ×2)或删除过期项",
|
|
1500
1549
|
].join(";");
|
|
1501
1550
|
return {
|
|
1502
1551
|
msgId: `memory-cleanup-${Date.now()}`,
|
|
@@ -1516,13 +1565,14 @@ export class McpServer {
|
|
|
1516
1565
|
}
|
|
1517
1566
|
/**
|
|
1518
1567
|
* 构建近期记忆过期回忆通知(有即将遗忘的记忆需要整理)。
|
|
1519
|
-
* Memory 近期记忆过期 (v1.11.0) / v1.13.0 改用服务端 memory_status 字段
|
|
1568
|
+
* Memory 近期记忆过期 (v1.11.0) / v1.13.0 改用服务端 memory_status 字段 / v1.19.0 扩展
|
|
1520
1569
|
*
|
|
1521
1570
|
* 通知策略(基于服务端 memory_status):
|
|
1522
|
-
* 1. 有过期 recently_memory 且 memory_status='recalling'(回忆中但未完成疏理):
|
|
1571
|
+
* 1. 有过期 recently_memory / agent_memory 且 memory_status='recalling'(回忆中但未完成疏理):
|
|
1523
1572
|
* 通知 "存在待疏理的即将遗忘记忆,请通过 memory_recurrence 获取进行整理"
|
|
1524
|
-
* 2. 有过期 recently_memory 且 memory_status='normal'(尚未开始回忆):
|
|
1573
|
+
* 2. 有过期 recently_memory / agent_memory 且 memory_status='normal'(尚未开始回忆):
|
|
1525
1574
|
* 通知 "有即将遗忘记忆得整理,请通过 memory_recurrence 进行回忆"
|
|
1575
|
+
* v1.19.0: recently_memory 纳入过期回忆流程,与 agent_memory 一并处理。
|
|
1526
1576
|
*/
|
|
1527
1577
|
async buildMemoryRecurrenceNotification() {
|
|
1528
1578
|
try {
|
|
@@ -1590,7 +1640,7 @@ export class McpServer {
|
|
|
1590
1640
|
}
|
|
1591
1641
|
// 步骤1: 检查是否有 pending 的 updating memories(角色本地永久记忆),
|
|
1592
1642
|
// 同时检测绑定文件是否再次变更(用户可能在 Agent 处理前又改了文件)
|
|
1593
|
-
const permanentList = await this.roleMemoryStore.listByType('
|
|
1643
|
+
const permanentList = await this.roleMemoryStore.listByType('project_memory');
|
|
1594
1644
|
const updatingMemories = permanentList.filter(m => m.update_status === 'updating' && m.updating_agent_id === agentId);
|
|
1595
1645
|
if (updatingMemories.length > 0) {
|
|
1596
1646
|
// 对 updating 记忆也检测文件是否再次变更
|
|
@@ -1602,7 +1652,7 @@ export class McpServer {
|
|
|
1602
1652
|
if (memory) {
|
|
1603
1653
|
await this.roleMemoryStore.updateMemory({
|
|
1604
1654
|
uri: memory.uri,
|
|
1605
|
-
memory_type: '
|
|
1655
|
+
memory_type: 'project_memory',
|
|
1606
1656
|
update_status: 'updating',
|
|
1607
1657
|
updating_agent_id: agentId,
|
|
1608
1658
|
file_md5_map: memory.file_md5_map,
|
|
@@ -1642,9 +1692,9 @@ export class McpServer {
|
|
|
1642
1692
|
};
|
|
1643
1693
|
}
|
|
1644
1694
|
// 步骤2: 检查短期记忆和近期记忆(服务端存储)
|
|
1645
|
-
const
|
|
1646
|
-
const
|
|
1647
|
-
const listableMemories = [...
|
|
1695
|
+
const recentlyMemoryMemories = await this.memoryStore.listByType('recently_memory');
|
|
1696
|
+
const agentMemoryMemories = await this.memoryStore.listByType('agent_memory');
|
|
1697
|
+
const listableMemories = [...recentlyMemoryMemories, ...agentMemoryMemories];
|
|
1648
1698
|
// 用本地 MD5 缓存补全 listable 记忆(服务端可能不持久化 file_md5_map 字段)
|
|
1649
1699
|
// 确保 checkMemoriesFileChanges 有正确的旧 MD5 做比对而非始终以空字典开始
|
|
1650
1700
|
for (const m of listableMemories) {
|
|
@@ -1703,7 +1753,7 @@ export class McpServer {
|
|
|
1703
1753
|
if (memory) {
|
|
1704
1754
|
await this.roleMemoryStore.updateMemory({
|
|
1705
1755
|
uri: memory.uri,
|
|
1706
|
-
memory_type: '
|
|
1756
|
+
memory_type: 'project_memory',
|
|
1707
1757
|
update_status: 'updating',
|
|
1708
1758
|
updating_agent_id: agentId,
|
|
1709
1759
|
// checkMemoriesFileChanges 已更新 in-memory 对象中的 MD5 map
|
|
@@ -1817,18 +1867,22 @@ export class McpServer {
|
|
|
1817
1867
|
return `${memoryId}:${sorted.map(([k, v]) => `${k}=${v}`).join('|')}`;
|
|
1818
1868
|
}
|
|
1819
1869
|
async handleGetMessage() {
|
|
1870
|
+
// 消息来源标记:
|
|
1871
|
+
// - buffer 在 hydrate 前已有消息 → fromBuffer=true(来自本地 buffer)
|
|
1872
|
+
// - buffer 空、hydrate 从服务端拉到消息 → fromBuffer=false(来自服务端)
|
|
1873
|
+
// - buffer 空、hydrate 也无消息(仅有 Memory 通知注入)→ fromBuffer=true(本地注入)
|
|
1874
|
+
const hadBufferedMessages = this.messageBuffer.size() > 0;
|
|
1820
1875
|
await this.hydrateUnreadMessagesIfNeeded();
|
|
1821
1876
|
const messages = this.messageBuffer.get();
|
|
1877
|
+
const hasRealMessages = messages.directMessages.length > 0 || messages.groupMessages.length > 0;
|
|
1878
|
+
const fromBuffer = hadBufferedMessages || !hasRealMessages;
|
|
1822
1879
|
if (await this.prependMemoryNotification(messages, () => this.buildMemoryRecurrenceNotification())) {
|
|
1823
|
-
return this.buildPollResult(messages);
|
|
1824
|
-
}
|
|
1825
|
-
if (await this.prependMemoryNotification(messages, () => this.buildMemoryTodoNotification())) {
|
|
1826
|
-
return this.buildPollResult(messages);
|
|
1880
|
+
return this.buildPollResult(messages, fromBuffer);
|
|
1827
1881
|
}
|
|
1828
1882
|
if (await this.prependMemoryNotification(messages, () => this.buildMemoryCleanupNotification())) {
|
|
1829
|
-
return this.buildPollResult(messages);
|
|
1883
|
+
return this.buildPollResult(messages, fromBuffer);
|
|
1830
1884
|
}
|
|
1831
|
-
return this.buildPollResult(messages);
|
|
1885
|
+
return this.buildPollResult(messages, fromBuffer);
|
|
1832
1886
|
}
|
|
1833
1887
|
/**
|
|
1834
1888
|
* 在本地缓冲区为空时,从服务端补拉离线期间积累的未读消息。
|
|
@@ -2009,6 +2063,87 @@ export class McpServer {
|
|
|
2009
2063
|
}
|
|
2010
2064
|
return maybeAgentClient.getState().agentId ?? null;
|
|
2011
2065
|
}
|
|
2066
|
+
/**
|
|
2067
|
+
* 将 agentId 安全化为文件名片段:仅保留字母数字下划线短横,其余替换为下划线。
|
|
2068
|
+
* 与 acode 包 TodoStore.filePath 的路径安全化逻辑保持一致。
|
|
2069
|
+
*/
|
|
2070
|
+
sanitizeAgentIdForTodoFile(agentId) {
|
|
2071
|
+
return String(agentId).replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2072
|
+
}
|
|
2073
|
+
/**
|
|
2074
|
+
* 构建 Todo 未完成项提醒通知。
|
|
2075
|
+
*
|
|
2076
|
+
* 触发条件(同时满足):
|
|
2077
|
+
* 1. 当前 agentId 可读取
|
|
2078
|
+
* 2. ~/.acode/todos/{agentId}.json 存在且可解析
|
|
2079
|
+
* 3. 存在 status !== "completed" 的未完成项
|
|
2080
|
+
* 4. 未完成项内容/状态/优先级指纹与上次通知时不同(去重)
|
|
2081
|
+
*
|
|
2082
|
+
* 行为:
|
|
2083
|
+
* - 指纹变化且有未完成项:构造系统通知并返回;指纹更新为本轮快照。
|
|
2084
|
+
* - 指纹未变:返回 null(不重复通知)。
|
|
2085
|
+
* - 全部完成:重置指纹为 null,下次有未完成项时能再次通知。
|
|
2086
|
+
* - 任何异常(文件不存在/JSON 损坏/agentId 缺失):静默返回 null,不阻断 poll_message。
|
|
2087
|
+
*
|
|
2088
|
+
* 通知以 DirectMessage 形式注入,senderId="MCP-System",不落库、不持久化。
|
|
2089
|
+
*/
|
|
2090
|
+
async buildTodoReminderNotification() {
|
|
2091
|
+
try {
|
|
2092
|
+
const agentId = this.getCurrentAgentId();
|
|
2093
|
+
if (!agentId)
|
|
2094
|
+
return null;
|
|
2095
|
+
const todoPath = path.join(os.homedir(), ".acode", "todos", `${this.sanitizeAgentIdForTodoFile(agentId)}.json`);
|
|
2096
|
+
let raw;
|
|
2097
|
+
try {
|
|
2098
|
+
raw = await fs.promises.readFile(todoPath, "utf-8");
|
|
2099
|
+
}
|
|
2100
|
+
catch (error) {
|
|
2101
|
+
// 文件不存在视为无 todo,不通知
|
|
2102
|
+
if (error?.code === "ENOENT")
|
|
2103
|
+
return null;
|
|
2104
|
+
throw error;
|
|
2105
|
+
}
|
|
2106
|
+
const doc = JSON.parse(raw);
|
|
2107
|
+
const todos = Array.isArray(doc.todos)
|
|
2108
|
+
? doc.todos
|
|
2109
|
+
: [];
|
|
2110
|
+
const incomplete = todos.filter((t) => t &&
|
|
2111
|
+
typeof t === "object" &&
|
|
2112
|
+
typeof t.content === "string" &&
|
|
2113
|
+
(t.status === "pending" ||
|
|
2114
|
+
t.status === "in_progress" ||
|
|
2115
|
+
t.status === "cancelled"));
|
|
2116
|
+
if (incomplete.length === 0) {
|
|
2117
|
+
// 全完成(或无未完成项):重置指纹,下次有未完成项时能再次通知
|
|
2118
|
+
this.lastTodoNotificationFingerprint = null;
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
const fingerprint = JSON.stringify(incomplete.map((t) => ({
|
|
2122
|
+
c: t.content,
|
|
2123
|
+
s: t.status,
|
|
2124
|
+
p: t.priority,
|
|
2125
|
+
})));
|
|
2126
|
+
if (fingerprint === this.lastTodoNotificationFingerprint) {
|
|
2127
|
+
// 指纹未变:不重复通知
|
|
2128
|
+
return null;
|
|
2129
|
+
}
|
|
2130
|
+
this.lastTodoNotificationFingerprint = fingerprint;
|
|
2131
|
+
return {
|
|
2132
|
+
msgId: `todo-reminder-${Date.now()}`,
|
|
2133
|
+
senderId: "MCP-System",
|
|
2134
|
+
senderName: "系统",
|
|
2135
|
+
receiverId: agentId,
|
|
2136
|
+
content: `Todo 列表存在 ${incomplete.length} 项未完成,请调用 todo_read 查看并处理。`,
|
|
2137
|
+
requireReply: false,
|
|
2138
|
+
prompt: "这是一条 Todo 未完成项提醒,自动生成,无需持久化。请评估是否需要继续处理未完成的 Todo,可调用 todo_read 获取完整列表。",
|
|
2139
|
+
timestamp: new Date().toISOString(),
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
catch (error) {
|
|
2143
|
+
logger.warn("[McpServer] 构建 Todo 提醒失败:", error);
|
|
2144
|
+
return null;
|
|
2145
|
+
}
|
|
2146
|
+
}
|
|
2012
2147
|
// ============ 生命周期 ============
|
|
2013
2148
|
async start() {
|
|
2014
2149
|
const transport = new StdioServerTransport();
|
|
@@ -2062,37 +2197,6 @@ export class McpServer {
|
|
|
2062
2197
|
}, intervalMs);
|
|
2063
2198
|
logger.info(`[McpServer] 后台文件变更检测已启动,间隔: ${intervalMs}ms(可通过 AWS_MEMORY_FILE_CHECK_INTERVAL_MS 环境变量调整,设为 0 禁用)`);
|
|
2064
2199
|
}
|
|
2065
|
-
/**
|
|
2066
|
-
* 构建短期记忆 TODO 通知。
|
|
2067
|
-
* 主流程:调用服务端 list_by_todo_status({ todo_status: 'pending' }) 获取 pending 待办 -> 注入提醒。
|
|
2068
|
-
* v1.13.0: 由服务端 memory_status 字段管理 todo 状态,TS 侧不再维护本地缓存。
|
|
2069
|
-
*/
|
|
2070
|
-
async buildMemoryTodoNotification() {
|
|
2071
|
-
try {
|
|
2072
|
-
const pendingTodos = await this.memoryStore.listByTodoStatus('pending');
|
|
2073
|
-
if (pendingTodos.length === 0)
|
|
2074
|
-
return null;
|
|
2075
|
-
const agentId = this.getCurrentAgentId();
|
|
2076
|
-
if (!agentId)
|
|
2077
|
-
return null;
|
|
2078
|
-
const pendingCount = pendingTodos.length;
|
|
2079
|
-
const pendingList = pendingTodos.slice(0, 5).map(m => `- ${m.uri}`).join('\n');
|
|
2080
|
-
return {
|
|
2081
|
-
msgId: `memory-todo-${Date.now()}`,
|
|
2082
|
-
senderId: "MCP-System",
|
|
2083
|
-
senderName: "系统",
|
|
2084
|
-
receiverId: agentId,
|
|
2085
|
-
content: `当前有 ${pendingCount} 个 todo 项未完成,请获取短期记忆(todo 列表),且已经完成请使用 todo_completed 标注为已完成状态。\n\n待办项:\n${pendingList}${pendingCount > 5 ? `\n ...及其他 ${pendingCount - 5} 条` : ''}`,
|
|
2086
|
-
requireReply: false,
|
|
2087
|
-
prompt: "这是一条短期记忆 TODO 提醒,请调用 list_memories({ memory_type: 'task_list' }) 查看待办列表,完成后通过 todo_completed({ uri }) 标注为已完成状态。",
|
|
2088
|
-
timestamp: new Date().toISOString(),
|
|
2089
|
-
};
|
|
2090
|
-
}
|
|
2091
|
-
catch (error) {
|
|
2092
|
-
logger.warn("[McpServer] 构建短期记忆 TODO 提醒失败:", error);
|
|
2093
|
-
return null;
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
2200
|
/**
|
|
2097
2201
|
* 停止后台 Memory 文件变更检测定时器。
|
|
2098
2202
|
*/
|
|
@@ -2104,10 +2208,10 @@ export class McpServer {
|
|
|
2104
2208
|
}
|
|
2105
2209
|
}
|
|
2106
2210
|
// ============ 记忆统一状态字段 (v1.13.0) ============
|
|
2107
|
-
// v1.13.0 改用服务端 memory_status 字段管理
|
|
2108
|
-
//
|
|
2109
|
-
// buildMemoryRecurrenceNotification /
|
|
2110
|
-
// 已在文件其他位置实现(直接使用 this.memoryStore.listExpiredRecentlyMemories /
|
|
2211
|
+
// v1.13.0 改用服务端 memory_status 字段管理 agent_memory 过期/回忆状态,
|
|
2212
|
+
// 下方工具直接调用服务端接口,本地不再维护元数据缓存。
|
|
2213
|
+
// buildMemoryRecurrenceNotification / handleMemoryRecurrence / handleMemoryRecurrenceComplete
|
|
2214
|
+
// 已在文件其他位置实现(直接使用 this.memoryStore.listExpiredRecentlyMemories / markExpiredMemoriesRecalling / clearRecallingExpiredMemories)。
|
|
2111
2215
|
async autoRegister() {
|
|
2112
2216
|
const agentConfig = this.agentClient.getAgentConfig();
|
|
2113
2217
|
logger.info(`[McpServer] 角色: "${agentConfig.roleName}"`);
|
|
@@ -2128,11 +2232,10 @@ export class McpServer {
|
|
|
2128
2232
|
statusReporter.reportWaitingInput();
|
|
2129
2233
|
}
|
|
2130
2234
|
await this.reportCombinedMemoryStats();
|
|
2131
|
-
//
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
}
|
|
2235
|
+
// 注册成功后检查是否需要从本地恢复永久记忆到 server
|
|
2236
|
+
// 触发条件:server 当前角色 memory 计数为 0(重部署后 DB 为空)
|
|
2237
|
+
// 不依赖 isExisting / profileHandler,确保所有场景都能触发
|
|
2238
|
+
this.maybeSyncPermanentMemoryToServer();
|
|
2136
2239
|
return;
|
|
2137
2240
|
}
|
|
2138
2241
|
catch (error) {
|
|
@@ -2189,6 +2292,7 @@ export class McpServer {
|
|
|
2189
2292
|
logger.info(`[McpServer] profileHandler 被调用,displayName: ${profile.displayName}`);
|
|
2190
2293
|
this.messageBuffer.setProfileMessage(profile);
|
|
2191
2294
|
this.syncRoleMemoryStore(profile);
|
|
2295
|
+
this.maybeSyncPermanentMemoryToServer();
|
|
2192
2296
|
},
|
|
2193
2297
|
});
|
|
2194
2298
|
return;
|
|
@@ -2200,6 +2304,7 @@ export class McpServer {
|
|
|
2200
2304
|
logger.info(`[McpServer] profileHandler 被调用,displayName: ${profile.displayName}`);
|
|
2201
2305
|
this.messageBuffer.setProfileMessage(profile);
|
|
2202
2306
|
this.syncRoleMemoryStore(profile);
|
|
2307
|
+
this.maybeSyncPermanentMemoryToServer();
|
|
2203
2308
|
},
|
|
2204
2309
|
});
|
|
2205
2310
|
}
|