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.
Files changed (276) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +35 -1
  2. package/dist/adapter/AcodeSdkAdapter.d.ts.map +1 -1
  3. package/dist/adapter/AcodeSdkAdapter.js +417 -40
  4. package/dist/adapter/ClaudeSdkAdapter.d.ts +6 -0
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +1 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +23 -1
  7. package/dist/adapter/idle-poll-loop.d.ts +2 -1
  8. package/dist/adapter/idle-poll-loop.d.ts.map +1 -1
  9. package/dist/adapter/types.d.ts +65 -3
  10. package/dist/adapter/types.d.ts.map +1 -1
  11. package/dist/adapter/types.js +5 -4
  12. package/dist/index.js +4 -0
  13. package/dist/routes/acode-todos.d.ts +11 -0
  14. package/dist/routes/acode-todos.d.ts.map +1 -0
  15. package/dist/routes/acode-todos.js +75 -0
  16. package/dist/routes/memory.d.ts +1 -1
  17. package/dist/routes/memory.d.ts.map +1 -1
  18. package/dist/routes/memory.js +3 -3
  19. package/dist/routes/pty.d.ts.map +1 -1
  20. package/dist/routes/pty.js +9 -0
  21. package/dist/routes/sub-agents.d.ts +14 -0
  22. package/dist/routes/sub-agents.d.ts.map +1 -0
  23. package/dist/routes/sub-agents.js +253 -0
  24. package/dist/routes/terminal.d.ts +9 -1
  25. package/dist/routes/terminal.d.ts.map +1 -1
  26. package/dist/routes/terminal.js +207 -22
  27. package/dist/services/agent-event-store.d.ts +1 -0
  28. package/dist/services/agent-event-store.d.ts.map +1 -1
  29. package/dist/services/event-delta-pusher.js +4 -4
  30. package/dist/services/session-output.d.ts +3 -0
  31. package/dist/services/session-output.d.ts.map +1 -1
  32. package/dist/services/session-output.js +38 -2
  33. package/dist/services/sub-agent-manager-instance.d.ts +4 -0
  34. package/dist/services/sub-agent-manager-instance.d.ts.map +1 -0
  35. package/dist/services/sub-agent-manager-instance.js +12 -0
  36. package/dist/services/workspace-files.d.ts +1 -0
  37. package/dist/services/workspace-files.d.ts.map +1 -1
  38. package/dist/services/workspace-files.js +57 -1
  39. package/package/acode/dist/ask-user-question-tool.d.ts +47 -0
  40. package/package/acode/dist/ask-user-question-tool.d.ts.map +1 -0
  41. package/package/acode/dist/ask-user-question-tool.js +233 -0
  42. package/package/acode/dist/built-in-file-tools.d.ts +2 -0
  43. package/package/acode/dist/built-in-file-tools.d.ts.map +1 -1
  44. package/package/acode/dist/compaction.d.ts +206 -0
  45. package/package/acode/dist/compaction.d.ts.map +1 -0
  46. package/package/acode/dist/compaction.js +664 -0
  47. package/package/acode/dist/config.d.ts +6 -1
  48. package/package/acode/dist/config.d.ts.map +1 -1
  49. package/package/acode/dist/config.js +27 -2
  50. package/package/acode/dist/index.d.ts +4 -0
  51. package/package/acode/dist/index.d.ts.map +1 -1
  52. package/package/acode/dist/index.js +4 -0
  53. package/package/acode/dist/runtime.d.ts +137 -0
  54. package/package/acode/dist/runtime.d.ts.map +1 -1
  55. package/package/acode/dist/runtime.js +974 -172
  56. package/package/acode/dist/skill-tool.d.ts +30 -0
  57. package/package/acode/dist/skill-tool.d.ts.map +1 -0
  58. package/package/acode/dist/skill-tool.js +174 -0
  59. package/package/acode/dist/sub-agent-manager.d.ts +169 -0
  60. package/package/acode/dist/sub-agent-manager.d.ts.map +1 -0
  61. package/package/acode/dist/sub-agent-manager.js +535 -0
  62. package/package/acode/dist/sub-agent-store.d.ts +33 -0
  63. package/package/acode/dist/sub-agent-store.d.ts.map +1 -0
  64. package/package/acode/dist/sub-agent-store.js +109 -0
  65. package/package/acode/dist/sub-agent-tools.d.ts +72 -0
  66. package/package/acode/dist/sub-agent-tools.d.ts.map +1 -0
  67. package/package/acode/dist/sub-agent-tools.js +429 -0
  68. package/package/acode/dist/sub-agent-types.d.ts +81 -0
  69. package/package/acode/dist/sub-agent-types.d.ts.map +1 -0
  70. package/package/acode/dist/sub-agent-types.js +8 -0
  71. package/package/acode/dist/todo-store.d.ts +42 -0
  72. package/package/acode/dist/todo-store.d.ts.map +1 -0
  73. package/package/acode/dist/todo-store.js +68 -0
  74. package/package/acode/dist/todo-tool.d.ts +6 -0
  75. package/package/acode/dist/todo-tool.d.ts.map +1 -0
  76. package/package/acode/dist/todo-tool.js +138 -0
  77. package/package/acode/dist/types.d.ts +22 -1
  78. package/package/acode/dist/types.d.ts.map +1 -1
  79. package/package/acode/package.json +13 -2
  80. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -3
  81. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +1 -1
  82. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +83 -50
  83. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +1 -1
  84. package/package/aws-client-agent-mcp/dist/mcp-server.js +451 -346
  85. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +1 -1
  86. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +259 -241
  87. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +1 -1
  88. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +1 -19
  89. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +1 -1
  90. package/package/aws-client-agent-mcp/dist/mcp-tools.js +4 -77
  91. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +1 -1
  92. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +17 -40
  93. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +1 -1
  94. package/package/aws-client-agent-mcp/dist/memory-store.js +62 -149
  95. package/package/aws-client-agent-mcp/dist/memory-store.js.map +1 -1
  96. package/package/aws-client-agent-mcp/dist/memory-store.test.js +175 -164
  97. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +1 -1
  98. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +3 -9
  99. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +1 -1
  100. package/package/aws-client-agent-mcp/dist/memory-tools.js +28 -55
  101. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +1 -1
  102. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +27 -9
  103. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +1 -1
  104. package/package/aws-client-agent-mcp/dist/message-buffer.js +100 -23
  105. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +1 -1
  106. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +1 -14
  107. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +1 -1
  108. package/package/aws-client-agent-mcp/dist/server-memory-store.js +7 -18
  109. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +1 -1
  110. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +1 -1
  111. package/package/aws-client-agent-mcp/dist/status-reporter.js +8 -5
  112. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +1 -1
  113. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +93 -0
  114. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +1 -1
  115. package/package/aws-client-agent-mcp/dist/types.d.ts +3 -25
  116. package/package/aws-client-agent-mcp/dist/types.d.ts.map +1 -1
  117. package/package.json +17 -7
  118. package/dist/adapter/AcodeSdkAdapter.test.d.ts +0 -2
  119. package/dist/adapter/AcodeSdkAdapter.test.d.ts.map +0 -1
  120. package/dist/adapter/AcodeSdkAdapter.test.js +0 -2000
  121. package/dist/adapter/AdapterRegistry.test.d.ts +0 -5
  122. package/dist/adapter/AdapterRegistry.test.d.ts.map +0 -1
  123. package/dist/adapter/AdapterRegistry.test.js +0 -118
  124. package/dist/adapter/ClaudeSdkAdapter.test.d.ts +0 -2
  125. package/dist/adapter/ClaudeSdkAdapter.test.d.ts.map +0 -1
  126. package/dist/adapter/ClaudeSdkAdapter.test.js +0 -311
  127. package/dist/adapter/CodexSdkAdapter.test.d.ts +0 -2
  128. package/dist/adapter/CodexSdkAdapter.test.d.ts.map +0 -1
  129. package/dist/adapter/CodexSdkAdapter.test.js +0 -270
  130. package/dist/adapter/OpencodeSdkAdapter.test.d.ts +0 -2
  131. package/dist/adapter/OpencodeSdkAdapter.test.d.ts.map +0 -1
  132. package/dist/adapter/OpencodeSdkAdapter.test.js +0 -158
  133. package/dist/adapter/acode-tool-names.test.d.ts +0 -2
  134. package/dist/adapter/acode-tool-names.test.d.ts.map +0 -1
  135. package/dist/adapter/acode-tool-names.test.js +0 -28
  136. package/dist/adapter/adapter.test.d.ts +0 -5
  137. package/dist/adapter/adapter.test.d.ts.map +0 -1
  138. package/dist/adapter/adapter.test.js +0 -180
  139. package/dist/adapter/idle-poll-loop.test.d.ts +0 -2
  140. package/dist/adapter/idle-poll-loop.test.d.ts.map +0 -1
  141. package/dist/adapter/idle-poll-loop.test.js +0 -85
  142. package/dist/adapter/types.test.d.ts +0 -2
  143. package/dist/adapter/types.test.d.ts.map +0 -1
  144. package/dist/adapter/types.test.js +0 -50
  145. package/dist/adapters/cc-switch/mcp-acode.test.d.ts +0 -2
  146. package/dist/adapters/cc-switch/mcp-acode.test.d.ts.map +0 -1
  147. package/dist/adapters/cc-switch/mcp-acode.test.js +0 -80
  148. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts +0 -2
  149. package/dist/adapters/cc-switch/mcp-claudecode.test.d.ts.map +0 -1
  150. package/dist/adapters/cc-switch/mcp-claudecode.test.js +0 -75
  151. package/dist/adapters/cc-switch/mcp-codex.test.d.ts +0 -2
  152. package/dist/adapters/cc-switch/mcp-codex.test.d.ts.map +0 -1
  153. package/dist/adapters/cc-switch/mcp-codex.test.js +0 -126
  154. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts +0 -2
  155. package/dist/adapters/cc-switch/mcp-opencode.test.d.ts.map +0 -1
  156. package/dist/adapters/cc-switch/mcp-opencode.test.js +0 -62
  157. package/dist/adapters/cc-switch/skill-directory.test.d.ts +0 -2
  158. package/dist/adapters/cc-switch/skill-directory.test.d.ts.map +0 -1
  159. package/dist/adapters/cc-switch/skill-directory.test.js +0 -74
  160. package/dist/config.test.d.ts +0 -2
  161. package/dist/config.test.d.ts.map +0 -1
  162. package/dist/config.test.js +0 -107
  163. package/dist/routes/ai-sources.test.d.ts +0 -2
  164. package/dist/routes/ai-sources.test.d.ts.map +0 -1
  165. package/dist/routes/ai-sources.test.js +0 -169
  166. package/dist/routes/file-browser.test.d.ts +0 -5
  167. package/dist/routes/file-browser.test.d.ts.map +0 -1
  168. package/dist/routes/file-browser.test.js +0 -155
  169. package/dist/routes/git.test.d.ts +0 -2
  170. package/dist/routes/git.test.d.ts.map +0 -1
  171. package/dist/routes/git.test.js +0 -330
  172. package/dist/routes/instance.test.d.ts +0 -5
  173. package/dist/routes/instance.test.d.ts.map +0 -1
  174. package/dist/routes/instance.test.js +0 -389
  175. package/dist/routes/mcp.test.d.ts +0 -5
  176. package/dist/routes/mcp.test.d.ts.map +0 -1
  177. package/dist/routes/mcp.test.js +0 -92
  178. package/dist/routes/memory-route-wiring.test.d.ts +0 -2
  179. package/dist/routes/memory-route-wiring.test.d.ts.map +0 -1
  180. package/dist/routes/memory-route-wiring.test.js +0 -15
  181. package/dist/routes/memory.test.d.ts +0 -2
  182. package/dist/routes/memory.test.d.ts.map +0 -1
  183. package/dist/routes/memory.test.js +0 -177
  184. package/dist/routes/properties.test.d.ts +0 -5
  185. package/dist/routes/properties.test.d.ts.map +0 -1
  186. package/dist/routes/properties.test.js +0 -72
  187. package/dist/routes/pty.test.d.ts +0 -2
  188. package/dist/routes/pty.test.d.ts.map +0 -1
  189. package/dist/routes/pty.test.js +0 -82
  190. package/dist/routes/runtime-binding.test.d.ts +0 -2
  191. package/dist/routes/runtime-binding.test.d.ts.map +0 -1
  192. package/dist/routes/runtime-binding.test.js +0 -32
  193. package/dist/routes/sessions.test.d.ts +0 -5
  194. package/dist/routes/sessions.test.d.ts.map +0 -1
  195. package/dist/routes/sessions.test.js +0 -86
  196. package/dist/routes/skills.test.d.ts +0 -5
  197. package/dist/routes/skills.test.d.ts.map +0 -1
  198. package/dist/routes/skills.test.js +0 -50
  199. package/dist/routes/system-metrics.test.d.ts +0 -2
  200. package/dist/routes/system-metrics.test.d.ts.map +0 -1
  201. package/dist/routes/system-metrics.test.js +0 -16
  202. package/dist/routes/terminal.stop-options.test.d.ts +0 -2
  203. package/dist/routes/terminal.stop-options.test.d.ts.map +0 -1
  204. package/dist/routes/terminal.stop-options.test.js +0 -39
  205. package/dist/routes/terminal.test.d.ts +0 -2
  206. package/dist/routes/terminal.test.d.ts.map +0 -1
  207. package/dist/routes/terminal.test.js +0 -746
  208. package/dist/routes/yml.test.d.ts +0 -5
  209. package/dist/routes/yml.test.d.ts.map +0 -1
  210. package/dist/routes/yml.test.js +0 -58
  211. package/dist/services/ai-source-apply.test.d.ts +0 -2
  212. package/dist/services/ai-source-apply.test.d.ts.map +0 -1
  213. package/dist/services/ai-source-apply.test.js +0 -138
  214. package/dist/services/auto-register.test.d.ts +0 -2
  215. package/dist/services/auto-register.test.d.ts.map +0 -1
  216. package/dist/services/auto-register.test.js +0 -184
  217. package/dist/services/aws-client-agent-mcp.test.d.ts +0 -2
  218. package/dist/services/aws-client-agent-mcp.test.d.ts.map +0 -1
  219. package/dist/services/aws-client-agent-mcp.test.js +0 -242
  220. package/dist/services/bridge-package-publish-hooks.test.d.ts +0 -2
  221. package/dist/services/bridge-package-publish-hooks.test.d.ts.map +0 -1
  222. package/dist/services/bridge-package-publish-hooks.test.js +0 -136
  223. package/dist/services/cc-switch-discovery.test.d.ts +0 -2
  224. package/dist/services/cc-switch-discovery.test.d.ts.map +0 -1
  225. package/dist/services/cc-switch-discovery.test.js +0 -56
  226. package/dist/services/cli-commands.test.d.ts +0 -2
  227. package/dist/services/cli-commands.test.d.ts.map +0 -1
  228. package/dist/services/cli-commands.test.js +0 -230
  229. package/dist/services/instance-init-service.test.d.ts +0 -2
  230. package/dist/services/instance-init-service.test.d.ts.map +0 -1
  231. package/dist/services/instance-init-service.test.js +0 -140
  232. package/dist/services/instance-state.test.d.ts +0 -2
  233. package/dist/services/instance-state.test.d.ts.map +0 -1
  234. package/dist/services/instance-state.test.js +0 -245
  235. package/dist/services/mcp-launch-binding-queue.test.d.ts +0 -2
  236. package/dist/services/mcp-launch-binding-queue.test.d.ts.map +0 -1
  237. package/dist/services/mcp-launch-binding-queue.test.js +0 -181
  238. package/dist/services/runtime-binding.test.d.ts +0 -2
  239. package/dist/services/runtime-binding.test.d.ts.map +0 -1
  240. package/dist/services/runtime-binding.test.js +0 -78
  241. package/dist/services/runtime-lifecycle-policy.test.d.ts +0 -2
  242. package/dist/services/runtime-lifecycle-policy.test.d.ts.map +0 -1
  243. package/dist/services/runtime-lifecycle-policy.test.js +0 -41
  244. package/dist/services/session-output.test.d.ts +0 -5
  245. package/dist/services/session-output.test.d.ts.map +0 -1
  246. package/dist/services/session-output.test.js +0 -177
  247. package/dist/services/startup-config-wizard.test.d.ts +0 -2
  248. package/dist/services/startup-config-wizard.test.d.ts.map +0 -1
  249. package/dist/services/startup-config-wizard.test.js +0 -184
  250. package/dist/services/terminal-persistence.test.d.ts +0 -5
  251. package/dist/services/terminal-persistence.test.d.ts.map +0 -1
  252. package/dist/services/terminal-persistence.test.js +0 -159
  253. package/dist/services/tool-installer.test.d.ts +0 -2
  254. package/dist/services/tool-installer.test.d.ts.map +0 -1
  255. package/dist/services/tool-installer.test.js +0 -225
  256. package/dist/services/workspace-files.test.d.ts +0 -2
  257. package/dist/services/workspace-files.test.d.ts.map +0 -1
  258. package/dist/services/workspace-files.test.js +0 -648
  259. package/dist/services/workspace-watch.test.d.ts +0 -2
  260. package/dist/services/workspace-watch.test.d.ts.map +0 -1
  261. package/dist/services/workspace-watch.test.js +0 -38
  262. package/dist/utils/file-utils.test.d.ts +0 -2
  263. package/dist/utils/file-utils.test.d.ts.map +0 -1
  264. package/dist/utils/file-utils.test.js +0 -244
  265. package/dist/utils/logger.test.d.ts +0 -2
  266. package/dist/utils/logger.test.d.ts.map +0 -1
  267. package/dist/utils/logger.test.js +0 -93
  268. package/dist/utils/sdk-package-loader.test.d.ts +0 -2
  269. package/dist/utils/sdk-package-loader.test.d.ts.map +0 -1
  270. package/dist/utils/sdk-package-loader.test.js +0 -84
  271. package/dist/utils/validation.test.d.ts +0 -2
  272. package/dist/utils/validation.test.d.ts.map +0 -1
  273. package/dist/utils/validation.test.js +0 -88
  274. package/dist/utils/yaml-utils.test.d.ts +0 -2
  275. package/dist/utils/yaml-utils.test.d.ts.map +0 -1
  276. 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 记忆(task_list/recently_memory)的文件 MD5 map。
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
- "memory",
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
- * 主流程:task_list/recently_memory 走 server 数据库;长期 memory 默认写入项目 .agentswork/memory/memory/<roleName>/<domain>.json。
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
- * 主流程:长期 memory 走角色级 store;task_list/recently_memory 走当前 Agent 实例级 store。
577
+ * 主流程:长期 project_memory 走角色级 store;recently_memory/agent_memory 走当前 Agent 实例级 store。
487
578
  */
488
579
  /**
489
580
  * 按记忆生命周期选择存储作用域。
490
- * 主流程:永久记忆(memory)仅在 hasRoleName 为 true 时走角色级 store,
491
- * 否则回退到实例级 server store(避免空角色名下所有实例共享 default-role 目录导致跨角色污染)。
492
- * task_list/recently_memory/session_context 始终走实例级 store。
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 === "memory" && this.hasRoleName) {
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
- // hasRoleName false 时不查 roleMemoryStore,避免空角色名共享 default-role 目录
512
- if (this.hasRoleName) {
513
- const longTermRecord = await this.roleMemoryStore.readMemory({
514
- ...input,
515
- memory_type: "memory",
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
- if (this.hasRoleName) {
538
- const longTermRecord = await this.roleMemoryStore.readMemory({
539
- uri: input.uri,
540
- memory_type: "memory",
541
- });
542
- if (longTermRecord) {
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.memory.count, roleStats.by_type.memory.count);
562
- // total 同样保守估算:用 server 非 memory count + permanentCount 下界
563
- const serverNonMemoryTotal = instanceStats.total - instanceStats.by_type.memory.count;
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
- memory: {
571
- ...roleStats.by_type.memory,
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 === "memory") {
749
+ if (input.memory_type === "project_memory") {
702
750
  try {
703
- await this.memoryStore.createMemory(input).catch(() => { });
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 过期判定、task_list todo 状态由服务端 memory_status 字段管理,
710
- // 无需 TS 侧本地缓存。服务端默认按类型填充正确状态(recently_memory='normal', task_list='pending')。
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 === "memory") {
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: "memory",
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: task_list TODO 状态、recently_memory 过期与回忆状态均由服务端 memory_status 字段管理。
798
- // 显式传入的 todo_status / recall_status / memory_status 已在 updateMemory 内部透传到服务端。
799
- // 服务端会自动处理"所有 TODO 已完成时清理全部已完成项"的逻辑(见 AgentMemoryService.updateMemory)。
800
- // 内容变更对 recently_memory 的"续期"语义现在由过期判定基于 created_at 决定,
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 内部已处理"task_list 删除后清空 completed 残留"逻辑,
866
+ // v1.13.0: 服务端 deleteMemory 内部已处理相关清理逻辑,
823
867
  // TS 侧无需任何本地元数据清理。
824
868
  // Dual-write permanent memory delete to server
825
- if (effectiveType === "memory") {
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(task_list/recently)。
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: "memory" }),
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
- * 主流程:只允许 task_list/recently_memory,由 MemoryStore 阻止长期 memory 的全量列出。
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通过server按path_prefix匹配子Agent -> all合并。
993
- * 对于永久记忆(type=memory)同时查询本地roleMemoryStore作为补充。
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
- const scope = rawScope === "children" || rawScope === "all" ? rawScope : "own";
1001
- // scope='children': 仅通过 server 搜索子 Agent(带 path_prefix 自动匹配)
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 this.memoryStore.listByFile(normalizedPath, {
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 this.memoryStore.listByFile(normalizedPath, {
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 === "memory")) {
1053
- localResult = await this.roleMemoryStore.listByFile(filePath, "memory");
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 拉取已过期 recently_memory ->
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
- `请审查后决定(archive_recently_memory 归档 / update_memory 续期),\n` +
1299
+ `请审查后决定(update_memory 续期,TTL ×2 / 不处理),\n` +
1149
1300
  `完成后调用 memory_recurrence_complete 清理,再调 memory_recurrence 处理下一批。`
1150
1301
  : `已处理全部 ${batch.length} 条即将遗忘的记忆。\n` +
1151
- `请审查后决定(archive_recently_memory 归档 / update_memory 续期),\n` +
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: true,
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
- "删除不再需要的 task_list",
1499
- "删除或 archive_recently_memory 归档 recently_memory 为长期 memory",
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('memory');
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: 'memory',
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 recentlyMemories = await this.memoryStore.listByType('recently_memory');
1646
- const taskListMemories = await this.memoryStore.listByType('task_list');
1647
- const listableMemories = [...recentlyMemories, ...taskListMemories];
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: 'memory',
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 字段管理 task_list todo 状态与 recently_memory 过期/回忆状态,
2108
- // 下方 4 个工具直接调用服务端接口,本地不再维护元数据缓存。
2109
- // buildMemoryRecurrenceNotification / buildMemoryTodoNotification / handleMemoryRecurrence / handleMemoryRecurrenceComplete
2110
- // 已在文件其他位置实现(直接使用 this.memoryStore.listExpiredRecentlyMemories / listByTodoStatus / markExpiredMemoriesRecalling / clearRecallingExpiredMemories)。
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
- if (result.isExisting === false) {
2133
- logger.info("[McpServer] 新角色创建,同步本地永久记忆到服务端...");
2134
- await this.syncLocalPermanentMemoryToServer();
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
  }