aws-runtime-bridge 1.9.79 → 1.9.80

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 (596) hide show
  1. package/dist/adapter/AcodeSdkAdapter.d.ts +0 -1
  2. package/dist/adapter/AcodeSdkAdapter.js +11 -1986
  3. package/dist/adapter/AdapterRegistry.d.ts +0 -1
  4. package/dist/adapter/AdapterRegistry.js +2 -100
  5. package/dist/adapter/ClaudeSdkAdapter.d.ts +0 -1
  6. package/dist/adapter/ClaudeSdkAdapter.js +4 -1300
  7. package/dist/adapter/CodexSdkAdapter.d.ts +0 -1
  8. package/dist/adapter/CodexSdkAdapter.js +4 -836
  9. package/dist/adapter/OpencodeSdkAdapter.d.ts +0 -1
  10. package/dist/adapter/OpencodeSdkAdapter.js +4 -879
  11. package/dist/adapter/SdkProviderSpi.d.ts +0 -1
  12. package/dist/adapter/SdkProviderSpi.js +2 -55
  13. package/dist/adapter/acode-tool-names.d.ts +0 -1
  14. package/dist/adapter/acode-tool-names.js +2 -37
  15. package/dist/adapter/idle-poll-loop.d.ts +0 -1
  16. package/dist/adapter/idle-poll-loop.js +2 -59
  17. package/dist/adapter/index.d.ts +0 -1
  18. package/dist/adapter/index.js +2 -11
  19. package/dist/adapter/instance-ai-config-resolver.d.ts +0 -1
  20. package/dist/adapter/instance-ai-config-resolver.js +2 -46
  21. package/dist/adapter/types.d.ts +0 -1
  22. package/dist/adapter/types.js +2 -443
  23. package/dist/adapters/cc-switch/common.d.ts +0 -1
  24. package/dist/adapters/cc-switch/common.js +2 -61
  25. package/dist/adapters/cc-switch/index.d.ts +0 -1
  26. package/dist/adapters/cc-switch/index.js +2 -57
  27. package/dist/adapters/cc-switch/mcp-acode.d.ts +0 -1
  28. package/dist/adapters/cc-switch/mcp-acode.js +2 -71
  29. package/dist/adapters/cc-switch/mcp-claude.d.ts +0 -1
  30. package/dist/adapters/cc-switch/mcp-claude.js +2 -89
  31. package/dist/adapters/cc-switch/mcp-claudecode.d.ts +0 -1
  32. package/dist/adapters/cc-switch/mcp-claudecode.js +2 -77
  33. package/dist/adapters/cc-switch/mcp-codex.d.ts +0 -1
  34. package/dist/adapters/cc-switch/mcp-codex.js +11 -249
  35. package/dist/adapters/cc-switch/mcp-opencode.d.ts +0 -1
  36. package/dist/adapters/cc-switch/mcp-opencode.js +2 -97
  37. package/dist/adapters/cc-switch/mcp-placeholder.d.ts +0 -1
  38. package/dist/adapters/cc-switch/mcp-placeholder.js +2 -19
  39. package/dist/adapters/cc-switch/skill-acode.d.ts +0 -1
  40. package/dist/adapters/cc-switch/skill-acode.js +2 -12
  41. package/dist/adapters/cc-switch/skill-claude.d.ts +0 -1
  42. package/dist/adapters/cc-switch/skill-claude.js +2 -11
  43. package/dist/adapters/cc-switch/skill-claudecode.d.ts +0 -1
  44. package/dist/adapters/cc-switch/skill-claudecode.js +2 -11
  45. package/dist/adapters/cc-switch/skill-codex.d.ts +0 -1
  46. package/dist/adapters/cc-switch/skill-codex.js +2 -6
  47. package/dist/adapters/cc-switch/skill-directory.d.ts +0 -1
  48. package/dist/adapters/cc-switch/skill-directory.js +3 -137
  49. package/dist/adapters/cc-switch/skill-opencode.d.ts +0 -1
  50. package/dist/adapters/cc-switch/skill-opencode.js +2 -11
  51. package/dist/adapters/cc-switch/skill-placeholder.d.ts +0 -1
  52. package/dist/adapters/cc-switch/skill-placeholder.js +2 -19
  53. package/dist/config.d.ts +0 -1
  54. package/dist/config.js +2 -290
  55. package/dist/index.d.ts +0 -1
  56. package/dist/index.js +2 -573
  57. package/dist/middleware/auth.d.ts +0 -1
  58. package/dist/middleware/auth.js +2 -98
  59. package/dist/routes/acode-todos.d.ts +0 -1
  60. package/dist/routes/acode-todos.js +2 -75
  61. package/dist/routes/ai-sources.d.ts +0 -1
  62. package/dist/routes/ai-sources.js +2 -132
  63. package/dist/routes/dashboard.d.ts +0 -1
  64. package/dist/routes/dashboard.js +182 -649
  65. package/dist/routes/events.d.ts +0 -1
  66. package/dist/routes/events.js +2 -22
  67. package/dist/routes/file-browser.d.ts +0 -1
  68. package/dist/routes/file-browser.js +2 -781
  69. package/dist/routes/file-diff-rollback.d.ts +0 -1
  70. package/dist/routes/file-diff-rollback.js +2 -301
  71. package/dist/routes/file-snapshot.d.ts +0 -1
  72. package/dist/routes/file-snapshot.js +2 -156
  73. package/dist/routes/git.d.ts +0 -1
  74. package/dist/routes/git.js +28 -1900
  75. package/dist/routes/instance.d.ts +0 -1
  76. package/dist/routes/instance.js +3 -748
  77. package/dist/routes/mcp.d.ts +0 -1
  78. package/dist/routes/mcp.js +2 -196
  79. package/dist/routes/processes.d.ts +0 -1
  80. package/dist/routes/processes.js +2 -252
  81. package/dist/routes/properties.d.ts +0 -1
  82. package/dist/routes/properties.js +2 -72
  83. package/dist/routes/pty.d.ts +0 -1
  84. package/dist/routes/pty.js +6 -560
  85. package/dist/routes/runtime-binding.d.ts +0 -1
  86. package/dist/routes/runtime-binding.js +2 -263
  87. package/dist/routes/sessions.d.ts +0 -1
  88. package/dist/routes/sessions.js +2 -758
  89. package/dist/routes/skills.d.ts +0 -1
  90. package/dist/routes/skills.js +2 -187
  91. package/dist/routes/sub-agents.d.ts +0 -1
  92. package/dist/routes/sub-agents.js +20 -279
  93. package/dist/routes/system-metrics.d.ts +0 -1
  94. package/dist/routes/system-metrics.js +2 -20
  95. package/dist/routes/task-context.d.ts +0 -1
  96. package/dist/routes/task-context.js +2 -78
  97. package/dist/routes/terminal.d.ts +0 -1
  98. package/dist/routes/terminal.js +54 -2882
  99. package/dist/routes/yml.d.ts +0 -1
  100. package/dist/routes/yml.js +2 -223
  101. package/dist/services/acode-package.d.ts +0 -1
  102. package/dist/services/acode-package.js +2 -111
  103. package/dist/services/agent-event-store.d.ts +0 -1
  104. package/dist/services/agent-event-store.js +2 -81
  105. package/dist/services/agent-process-manager.d.ts +0 -1
  106. package/dist/services/agent-process-manager.js +2 -796
  107. package/dist/services/ai-source-apply.d.ts +0 -1
  108. package/dist/services/ai-source-apply.js +2 -83
  109. package/dist/services/auto-register.d.ts +0 -1
  110. package/dist/services/auto-register.js +2 -1053
  111. package/dist/services/aws-client-agent-mcp.d.ts +0 -1
  112. package/dist/services/aws-client-agent-mcp.js +3 -337
  113. package/dist/services/bridge-package-publish-hooks.d.ts +0 -1
  114. package/dist/services/bridge-package-publish-hooks.js +2 -115
  115. package/dist/services/cc-switch-sdk.d.ts +0 -1
  116. package/dist/services/cc-switch-sdk.js +2 -135
  117. package/dist/services/cli-commands.d.ts +6 -1
  118. package/dist/services/cli-commands.js +6 -645
  119. package/dist/services/event-delta-pusher.d.ts +0 -1
  120. package/dist/services/event-delta-pusher.js +2 -95
  121. package/dist/services/fileSnapshotStore.d.ts +0 -1
  122. package/dist/services/fileSnapshotStore.js +2 -500
  123. package/dist/services/instance-ai-config-service.d.ts +0 -1
  124. package/dist/services/instance-ai-config-service.js +2 -174
  125. package/dist/services/instance-init-service.d.ts +0 -1
  126. package/dist/services/instance-init-service.js +2 -193
  127. package/dist/services/instance-service.d.ts +0 -1
  128. package/dist/services/instance-service.js +2 -237
  129. package/dist/services/instance-state.d.ts +0 -1
  130. package/dist/services/instance-state.js +2 -94
  131. package/dist/services/legacy-path-migration.d.ts +0 -1
  132. package/dist/services/legacy-path-migration.js +2 -66
  133. package/dist/services/lifecycle-state.d.ts +0 -1
  134. package/dist/services/lifecycle-state.js +3 -110
  135. package/dist/services/mcp-launch-binding-queue.d.ts +0 -1
  136. package/dist/services/mcp-launch-binding-queue.js +2 -175
  137. package/dist/services/orphan-monitor.d.ts +0 -1
  138. package/dist/services/orphan-monitor.js +2 -382
  139. package/dist/services/panel-auth.d.ts +0 -1
  140. package/dist/services/panel-auth.js +2 -168
  141. package/dist/services/process-detector.d.ts +0 -1
  142. package/dist/services/process-detector.js +18 -1019
  143. package/dist/services/process-registry.d.ts +0 -1
  144. package/dist/services/process-registry.js +2 -375
  145. package/dist/services/ring-buffer.d.ts +0 -1
  146. package/dist/services/ring-buffer.js +2 -103
  147. package/dist/services/runtime-binding.d.ts +0 -1
  148. package/dist/services/runtime-binding.js +6 -363
  149. package/dist/services/runtime-lifecycle-policy.d.ts +0 -1
  150. package/dist/services/runtime-lifecycle-policy.js +2 -35
  151. package/dist/services/session-output.d.ts +0 -1
  152. package/dist/services/session-output.js +5 -539
  153. package/dist/services/startup-config-wizard.d.ts +0 -1
  154. package/dist/services/startup-config-wizard.js +14 -258
  155. package/dist/services/sub-agent-manager-instance.d.ts +0 -1
  156. package/dist/services/sub-agent-manager-instance.js +2 -12
  157. package/dist/services/system-metrics.d.ts +0 -1
  158. package/dist/services/system-metrics.js +2 -252
  159. package/dist/services/terminal-persistence.d.ts +0 -1
  160. package/dist/services/terminal-persistence.js +2 -176
  161. package/dist/services/tool-installer.d.ts +0 -1
  162. package/dist/services/tool-installer.js +5 -751
  163. package/dist/services/tunnel-client.d.ts +0 -1
  164. package/dist/services/tunnel-client.js +2 -402
  165. package/dist/services/windows-service-manager.d.ts +3 -3
  166. package/dist/services/windows-service-manager.js +2 -59
  167. package/dist/services/workspace-files.d.ts +0 -1
  168. package/dist/services/workspace-files.js +2 -1275
  169. package/dist/services/workspace-watch.d.ts +0 -1
  170. package/dist/services/workspace-watch.js +2 -123
  171. package/dist/services/workspaceTaskChangeWatcher.d.ts +0 -1
  172. package/dist/services/workspaceTaskChangeWatcher.js +2 -483
  173. package/dist/types.d.ts +0 -1
  174. package/dist/types.js +1 -1
  175. package/dist/utils/file-utils.d.ts +0 -1
  176. package/dist/utils/file-utils.js +3 -208
  177. package/dist/utils/instance-ai-config-debug-logger.d.ts +0 -1
  178. package/dist/utils/instance-ai-config-debug-logger.js +4 -63
  179. package/dist/utils/logger.d.ts +0 -1
  180. package/dist/utils/logger.js +2 -154
  181. package/dist/utils/mcp-utils.d.ts +0 -1
  182. package/dist/utils/mcp-utils.js +2 -187
  183. package/dist/utils/path-utils.d.ts +0 -1
  184. package/dist/utils/path-utils.js +2 -44
  185. package/dist/utils/sdk-package-loader.d.ts +0 -1
  186. package/dist/utils/sdk-package-loader.js +2 -78
  187. package/dist/utils/validation.d.ts +0 -1
  188. package/dist/utils/validation.js +2 -53
  189. package/dist/utils/yaml-utils.d.ts +0 -1
  190. package/dist/utils/yaml-utils.js +2 -312
  191. package/node_modules/@cc-switch/sdk/dist/constants.d.ts +0 -1
  192. package/node_modules/@cc-switch/sdk/dist/constants.js +2 -63
  193. package/node_modules/@cc-switch/sdk/dist/errors.d.ts +0 -1
  194. package/node_modules/@cc-switch/sdk/dist/errors.js +2 -8
  195. package/node_modules/@cc-switch/sdk/dist/index.d.ts +0 -1
  196. package/node_modules/@cc-switch/sdk/dist/index.js +2 -9
  197. package/node_modules/@cc-switch/sdk/dist/schemas.d.ts +0 -1
  198. package/node_modules/@cc-switch/sdk/dist/schemas.js +2 -38
  199. package/node_modules/@cc-switch/sdk/dist/sdk.d.ts +0 -1
  200. package/node_modules/@cc-switch/sdk/dist/sdk.js +2 -470
  201. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts +0 -1
  202. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.js +4 -307
  203. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts +0 -1
  204. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.js +2 -37
  205. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts +0 -1
  206. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.js +2 -9
  207. package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts +0 -1
  208. package/node_modules/@cc-switch/sdk/dist/services/skill-model.js +2 -41
  209. package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts +0 -1
  210. package/node_modules/@cc-switch/sdk/dist/services/skill-service.js +2 -9
  211. package/node_modules/@cc-switch/sdk/dist/state.d.ts +0 -1
  212. package/node_modules/@cc-switch/sdk/dist/state.js +2 -19
  213. package/node_modules/@cc-switch/sdk/dist/types.d.ts +0 -1
  214. package/node_modules/@cc-switch/sdk/dist/types.js +1 -1
  215. package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts +0 -1
  216. package/node_modules/@cc-switch/sdk/dist/utils/fs.js +3 -114
  217. package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts +0 -1
  218. package/node_modules/@cc-switch/sdk/dist/utils/id.js +2 -12
  219. package/node_modules/@cc-switch/sdk/package.json +1 -1
  220. package/package/acode/dist/ask-user-question-tool.d.ts +0 -1
  221. package/package/acode/dist/ask-user-question-tool.js +4 -233
  222. package/package/acode/dist/built-in-file-tools.d.ts +0 -1
  223. package/package/acode/dist/built-in-file-tools.js +4 -966
  224. package/package/acode/dist/built-in-symbols-tool.d.ts +0 -1
  225. package/package/acode/dist/built-in-symbols-tool.js +2 -117
  226. package/package/acode/dist/builtins/commandArtifacts.d.ts +0 -1
  227. package/package/acode/dist/builtins/commandArtifacts.js +9 -97
  228. package/package/acode/dist/builtins/commandOutputEvents.d.ts +0 -1
  229. package/package/acode/dist/builtins/commandOutputEvents.js +2 -56
  230. package/package/acode/dist/builtins/shellCommandPreparation.d.ts +0 -1
  231. package/package/acode/dist/builtins/shellCommandPreparation.js +5 -44
  232. package/package/acode/dist/builtins/shellCommandResult.d.ts +0 -1
  233. package/package/acode/dist/builtins/shellCommandResult.js +2 -71
  234. package/package/acode/dist/builtins/shellCommandRunner.d.ts +0 -1
  235. package/package/acode/dist/builtins/shellCommandRunner.js +2 -115
  236. package/package/acode/dist/builtins/shellOutputDecoder.d.ts +0 -1
  237. package/package/acode/dist/builtins/shellOutputDecoder.js +2 -94
  238. package/package/acode/dist/cli.d.ts +0 -1
  239. package/package/acode/dist/cli.js +4 -38
  240. package/package/acode/dist/commands/commandLoader.d.ts +0 -1
  241. package/package/acode/dist/commands/commandLoader.js +2 -33
  242. package/package/acode/dist/commands/commandParser.d.ts +0 -1
  243. package/package/acode/dist/commands/commandParser.js +3 -77
  244. package/package/acode/dist/commands/commandRegistry.d.ts +0 -1
  245. package/package/acode/dist/commands/commandRegistry.js +2 -30
  246. package/package/acode/dist/compaction.d.ts +0 -1
  247. package/package/acode/dist/compaction.js +7 -919
  248. package/package/acode/dist/config.d.ts +0 -1
  249. package/package/acode/dist/config.js +5 -105
  250. package/package/acode/dist/fs-utils.d.ts +0 -1
  251. package/package/acode/dist/fs-utils.js +2 -79
  252. package/package/acode/dist/hooks/hookBus.d.ts +0 -1
  253. package/package/acode/dist/hooks/hookBus.js +2 -40
  254. package/package/acode/dist/index.d.ts +0 -1
  255. package/package/acode/dist/index.js +2 -19
  256. package/package/acode/dist/mcpToolNames.d.ts +0 -1
  257. package/package/acode/dist/mcpToolNames.js +2 -17
  258. package/package/acode/dist/parsers/ast-cache.d.ts +0 -1
  259. package/package/acode/dist/parsers/ast-cache.js +2 -76
  260. package/package/acode/dist/parsers/language-detector.d.ts +0 -1
  261. package/package/acode/dist/parsers/language-detector.js +2 -44
  262. package/package/acode/dist/parsers/language-queries.d.ts +0 -1
  263. package/package/acode/dist/parsers/language-queries.js +12 -602
  264. package/package/acode/dist/parsers/symbol-extractor.d.ts +0 -1
  265. package/package/acode/dist/parsers/symbol-extractor.js +3 -164
  266. package/package/acode/dist/parsers/tree-sitter-loader.d.ts +0 -1
  267. package/package/acode/dist/parsers/tree-sitter-loader.js +2 -64
  268. package/package/acode/dist/parsers/types.d.ts +0 -1
  269. package/package/acode/dist/parsers/types.js +1 -7
  270. package/package/acode/dist/runtime.d.ts +0 -1
  271. package/package/acode/dist/runtime.js +86 -3771
  272. package/package/acode/dist/skill-tool.d.ts +0 -1
  273. package/package/acode/dist/skill-tool.js +5 -174
  274. package/package/acode/dist/sub-agent-manager.d.ts +0 -1
  275. package/package/acode/dist/sub-agent-manager.js +2 -1159
  276. package/package/acode/dist/sub-agent-store.d.ts +0 -1
  277. package/package/acode/dist/sub-agent-store.js +4 -236
  278. package/package/acode/dist/sub-agent-tools.d.ts +0 -1
  279. package/package/acode/dist/sub-agent-tools.js +15 -369
  280. package/package/acode/dist/sub-agent-types.d.ts +0 -1
  281. package/package/acode/dist/sub-agent-types.js +1 -8
  282. package/package/acode/dist/todo-store.d.ts +0 -1
  283. package/package/acode/dist/todo-store.js +2 -73
  284. package/package/acode/dist/todo-tool.d.ts +0 -1
  285. package/package/acode/dist/todo-tool.js +5 -121
  286. package/package/acode/dist/types.d.ts +0 -1
  287. package/package/acode/dist/types.js +1 -1
  288. package/package/acode/package.json +2 -2
  289. package/package/aws-client-agent-mcp/dist/agent-client.d.ts +0 -1
  290. package/package/aws-client-agent-mcp/dist/agent-client.js +2 -890
  291. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts +0 -1
  292. package/package/aws-client-agent-mcp/dist/agent-client.test.js +0 -1
  293. package/package/aws-client-agent-mcp/dist/config.d.ts +0 -1
  294. package/package/aws-client-agent-mcp/dist/config.js +2 -243
  295. package/package/aws-client-agent-mcp/dist/config.test.d.ts +0 -1
  296. package/package/aws-client-agent-mcp/dist/config.test.js +0 -1
  297. package/package/aws-client-agent-mcp/dist/constants.d.ts +0 -1
  298. package/package/aws-client-agent-mcp/dist/constants.js +2 -24
  299. package/package/aws-client-agent-mcp/dist/context-manager.d.ts +0 -1
  300. package/package/aws-client-agent-mcp/dist/context-manager.js +2 -477
  301. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts +0 -1
  302. package/package/aws-client-agent-mcp/dist/context-manager.test.js +0 -1
  303. package/package/aws-client-agent-mcp/dist/http-client.d.ts +0 -1
  304. package/package/aws-client-agent-mcp/dist/http-client.js +2 -446
  305. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts +0 -1
  306. package/package/aws-client-agent-mcp/dist/http-client.test.js +0 -1
  307. package/package/aws-client-agent-mcp/dist/index.d.ts +0 -1
  308. package/package/aws-client-agent-mcp/dist/index.js +2 -34
  309. package/package/aws-client-agent-mcp/dist/logger.d.ts +0 -1
  310. package/package/aws-client-agent-mcp/dist/logger.js +3 -178
  311. package/package/aws-client-agent-mcp/dist/logger.test.d.ts +0 -1
  312. package/package/aws-client-agent-mcp/dist/logger.test.js +0 -1
  313. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts +0 -1
  314. package/package/aws-client-agent-mcp/dist/mcp-server.js +38 -2709
  315. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts +0 -1
  316. package/package/aws-client-agent-mcp/dist/mcp-server.test.js +0 -1
  317. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts +0 -1
  318. package/package/aws-client-agent-mcp/dist/mcp-tools.js +22 -562
  319. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts +0 -1
  320. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js +0 -1
  321. package/package/aws-client-agent-mcp/dist/memory-store.d.ts +0 -1
  322. package/package/aws-client-agent-mcp/dist/memory-store.js +4 -826
  323. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts +0 -1
  324. package/package/aws-client-agent-mcp/dist/memory-store.test.js +0 -1
  325. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts +0 -1
  326. package/package/aws-client-agent-mcp/dist/memory-tools.js +31 -294
  327. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts +0 -1
  328. package/package/aws-client-agent-mcp/dist/message-buffer.js +2 -288
  329. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts +0 -1
  330. package/package/aws-client-agent-mcp/dist/message-buffer.test.js +0 -1
  331. package/package/aws-client-agent-mcp/dist/messageContent.d.ts +0 -1
  332. package/package/aws-client-agent-mcp/dist/messageContent.js +2 -125
  333. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts +0 -1
  334. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js +2 -103
  335. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts +0 -1
  336. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js +0 -1
  337. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts +0 -1
  338. package/package/aws-client-agent-mcp/dist/server-memory-store.js +2 -96
  339. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts +0 -1
  340. package/package/aws-client-agent-mcp/dist/status-reporter.js +3 -327
  341. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts +0 -1
  342. package/package/aws-client-agent-mcp/dist/status-reporter.test.js +0 -1
  343. package/package/aws-client-agent-mcp/dist/types.d.ts +0 -1
  344. package/package/aws-client-agent-mcp/dist/types.js +2 -9
  345. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts +0 -1
  346. package/package/aws-client-agent-mcp/dist/websocket-client.js +2 -677
  347. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts +0 -1
  348. package/package/aws-client-agent-mcp/dist/websocket-client.test.js +0 -1
  349. package/package/aws-client-agent-mcp/package.json +1 -1
  350. package/package/cc-switch-sdk/dist/constants.d.ts +0 -1
  351. package/package/cc-switch-sdk/dist/constants.js +2 -63
  352. package/package/cc-switch-sdk/dist/errors.d.ts +0 -1
  353. package/package/cc-switch-sdk/dist/errors.js +2 -8
  354. package/package/cc-switch-sdk/dist/index.d.ts +0 -1
  355. package/package/cc-switch-sdk/dist/index.js +2 -9
  356. package/package/cc-switch-sdk/dist/schemas.d.ts +0 -1
  357. package/package/cc-switch-sdk/dist/schemas.js +2 -38
  358. package/package/cc-switch-sdk/dist/sdk.d.ts +0 -1
  359. package/package/cc-switch-sdk/dist/sdk.js +2 -470
  360. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts +0 -1
  361. package/package/cc-switch-sdk/dist/services/ai-config-service.js +4 -307
  362. package/package/cc-switch-sdk/dist/services/mcp-model.d.ts +0 -1
  363. package/package/cc-switch-sdk/dist/services/mcp-model.js +2 -37
  364. package/package/cc-switch-sdk/dist/services/mcp-service.d.ts +0 -1
  365. package/package/cc-switch-sdk/dist/services/mcp-service.js +2 -9
  366. package/package/cc-switch-sdk/dist/services/skill-model.d.ts +0 -1
  367. package/package/cc-switch-sdk/dist/services/skill-model.js +2 -41
  368. package/package/cc-switch-sdk/dist/services/skill-service.d.ts +0 -1
  369. package/package/cc-switch-sdk/dist/services/skill-service.js +2 -9
  370. package/package/cc-switch-sdk/dist/state.d.ts +0 -1
  371. package/package/cc-switch-sdk/dist/state.js +2 -19
  372. package/package/cc-switch-sdk/dist/types.d.ts +0 -1
  373. package/package/cc-switch-sdk/dist/types.js +1 -1
  374. package/package/cc-switch-sdk/dist/utils/fs.d.ts +0 -1
  375. package/package/cc-switch-sdk/dist/utils/fs.js +3 -114
  376. package/package/cc-switch-sdk/dist/utils/id.d.ts +0 -1
  377. package/package/cc-switch-sdk/dist/utils/id.js +2 -12
  378. package/package/cc-switch-sdk/package.json +1 -1
  379. package/package.json +4 -2
  380. package/dist/adapter/AcodeSdkAdapter.d.ts.map +0 -1
  381. package/dist/adapter/AdapterRegistry.d.ts.map +0 -1
  382. package/dist/adapter/ClaudeSdkAdapter.d.ts.map +0 -1
  383. package/dist/adapter/CodexSdkAdapter.d.ts.map +0 -1
  384. package/dist/adapter/OpencodeSdkAdapter.d.ts.map +0 -1
  385. package/dist/adapter/SdkProviderSpi.d.ts.map +0 -1
  386. package/dist/adapter/acode-tool-names.d.ts.map +0 -1
  387. package/dist/adapter/idle-poll-loop.d.ts.map +0 -1
  388. package/dist/adapter/index.d.ts.map +0 -1
  389. package/dist/adapter/instance-ai-config-resolver.d.ts.map +0 -1
  390. package/dist/adapter/types.d.ts.map +0 -1
  391. package/dist/adapters/cc-switch/common.d.ts.map +0 -1
  392. package/dist/adapters/cc-switch/index.d.ts.map +0 -1
  393. package/dist/adapters/cc-switch/mcp-acode.d.ts.map +0 -1
  394. package/dist/adapters/cc-switch/mcp-claude.d.ts.map +0 -1
  395. package/dist/adapters/cc-switch/mcp-claudecode.d.ts.map +0 -1
  396. package/dist/adapters/cc-switch/mcp-codex.d.ts.map +0 -1
  397. package/dist/adapters/cc-switch/mcp-opencode.d.ts.map +0 -1
  398. package/dist/adapters/cc-switch/mcp-placeholder.d.ts.map +0 -1
  399. package/dist/adapters/cc-switch/skill-acode.d.ts.map +0 -1
  400. package/dist/adapters/cc-switch/skill-claude.d.ts.map +0 -1
  401. package/dist/adapters/cc-switch/skill-claudecode.d.ts.map +0 -1
  402. package/dist/adapters/cc-switch/skill-codex.d.ts.map +0 -1
  403. package/dist/adapters/cc-switch/skill-directory.d.ts.map +0 -1
  404. package/dist/adapters/cc-switch/skill-opencode.d.ts.map +0 -1
  405. package/dist/adapters/cc-switch/skill-placeholder.d.ts.map +0 -1
  406. package/dist/config.d.ts.map +0 -1
  407. package/dist/index.d.ts.map +0 -1
  408. package/dist/middleware/auth.d.ts.map +0 -1
  409. package/dist/routes/acode-todos.d.ts.map +0 -1
  410. package/dist/routes/ai-sources.d.ts.map +0 -1
  411. package/dist/routes/dashboard.d.ts.map +0 -1
  412. package/dist/routes/events.d.ts.map +0 -1
  413. package/dist/routes/file-browser.d.ts.map +0 -1
  414. package/dist/routes/file-diff-rollback.d.ts.map +0 -1
  415. package/dist/routes/file-snapshot.d.ts.map +0 -1
  416. package/dist/routes/git.d.ts.map +0 -1
  417. package/dist/routes/instance.d.ts.map +0 -1
  418. package/dist/routes/mcp.d.ts.map +0 -1
  419. package/dist/routes/processes.d.ts.map +0 -1
  420. package/dist/routes/properties.d.ts.map +0 -1
  421. package/dist/routes/pty.d.ts.map +0 -1
  422. package/dist/routes/runtime-binding.d.ts.map +0 -1
  423. package/dist/routes/sessions.d.ts.map +0 -1
  424. package/dist/routes/skills.d.ts.map +0 -1
  425. package/dist/routes/sub-agents.d.ts.map +0 -1
  426. package/dist/routes/system-metrics.d.ts.map +0 -1
  427. package/dist/routes/task-context.d.ts.map +0 -1
  428. package/dist/routes/terminal.d.ts.map +0 -1
  429. package/dist/routes/yml.d.ts.map +0 -1
  430. package/dist/services/acode-package.d.ts.map +0 -1
  431. package/dist/services/agent-event-store.d.ts.map +0 -1
  432. package/dist/services/agent-process-manager.d.ts.map +0 -1
  433. package/dist/services/ai-source-apply.d.ts.map +0 -1
  434. package/dist/services/auto-register.d.ts.map +0 -1
  435. package/dist/services/aws-client-agent-mcp.d.ts.map +0 -1
  436. package/dist/services/bridge-package-publish-hooks.d.ts.map +0 -1
  437. package/dist/services/cc-switch-sdk.d.ts.map +0 -1
  438. package/dist/services/cli-commands.d.ts.map +0 -1
  439. package/dist/services/event-delta-pusher.d.ts.map +0 -1
  440. package/dist/services/fileSnapshotStore.d.ts.map +0 -1
  441. package/dist/services/instance-ai-config-service.d.ts.map +0 -1
  442. package/dist/services/instance-init-service.d.ts.map +0 -1
  443. package/dist/services/instance-service.d.ts.map +0 -1
  444. package/dist/services/instance-state.d.ts.map +0 -1
  445. package/dist/services/legacy-path-migration.d.ts.map +0 -1
  446. package/dist/services/lifecycle-state.d.ts.map +0 -1
  447. package/dist/services/mcp-launch-binding-queue.d.ts.map +0 -1
  448. package/dist/services/orphan-monitor.d.ts.map +0 -1
  449. package/dist/services/panel-auth.d.ts.map +0 -1
  450. package/dist/services/process-detector.d.ts.map +0 -1
  451. package/dist/services/process-registry.d.ts.map +0 -1
  452. package/dist/services/ring-buffer.d.ts.map +0 -1
  453. package/dist/services/runtime-binding.d.ts.map +0 -1
  454. package/dist/services/runtime-lifecycle-policy.d.ts.map +0 -1
  455. package/dist/services/session-output.d.ts.map +0 -1
  456. package/dist/services/startup-config-wizard.d.ts.map +0 -1
  457. package/dist/services/sub-agent-manager-instance.d.ts.map +0 -1
  458. package/dist/services/system-metrics.d.ts.map +0 -1
  459. package/dist/services/terminal-persistence.d.ts.map +0 -1
  460. package/dist/services/tool-installer.d.ts.map +0 -1
  461. package/dist/services/tunnel-client.d.ts.map +0 -1
  462. package/dist/services/windows-service-manager.d.ts.map +0 -1
  463. package/dist/services/workspace-files.d.ts.map +0 -1
  464. package/dist/services/workspace-watch.d.ts.map +0 -1
  465. package/dist/services/workspaceTaskChangeWatcher.d.ts.map +0 -1
  466. package/dist/types.d.ts.map +0 -1
  467. package/dist/utils/file-utils.d.ts.map +0 -1
  468. package/dist/utils/instance-ai-config-debug-logger.d.ts.map +0 -1
  469. package/dist/utils/logger.d.ts.map +0 -1
  470. package/dist/utils/mcp-utils.d.ts.map +0 -1
  471. package/dist/utils/path-utils.d.ts.map +0 -1
  472. package/dist/utils/sdk-package-loader.d.ts.map +0 -1
  473. package/dist/utils/validation.d.ts.map +0 -1
  474. package/dist/utils/yaml-utils.d.ts.map +0 -1
  475. package/node_modules/@cc-switch/sdk/dist/constants.d.ts.map +0 -1
  476. package/node_modules/@cc-switch/sdk/dist/errors.d.ts.map +0 -1
  477. package/node_modules/@cc-switch/sdk/dist/index.d.ts.map +0 -1
  478. package/node_modules/@cc-switch/sdk/dist/schemas.d.ts.map +0 -1
  479. package/node_modules/@cc-switch/sdk/dist/sdk.d.ts.map +0 -1
  480. package/node_modules/@cc-switch/sdk/dist/services/ai-config-service.d.ts.map +0 -1
  481. package/node_modules/@cc-switch/sdk/dist/services/mcp-model.d.ts.map +0 -1
  482. package/node_modules/@cc-switch/sdk/dist/services/mcp-service.d.ts.map +0 -1
  483. package/node_modules/@cc-switch/sdk/dist/services/skill-model.d.ts.map +0 -1
  484. package/node_modules/@cc-switch/sdk/dist/services/skill-service.d.ts.map +0 -1
  485. package/node_modules/@cc-switch/sdk/dist/state.d.ts.map +0 -1
  486. package/node_modules/@cc-switch/sdk/dist/types.d.ts.map +0 -1
  487. package/node_modules/@cc-switch/sdk/dist/utils/fs.d.ts.map +0 -1
  488. package/node_modules/@cc-switch/sdk/dist/utils/id.d.ts.map +0 -1
  489. package/package/acode/dist/ask-user-question-tool.d.ts.map +0 -1
  490. package/package/acode/dist/built-in-file-tools.d.ts.map +0 -1
  491. package/package/acode/dist/built-in-symbols-tool.d.ts.map +0 -1
  492. package/package/acode/dist/builtins/commandArtifacts.d.ts.map +0 -1
  493. package/package/acode/dist/builtins/commandOutputEvents.d.ts.map +0 -1
  494. package/package/acode/dist/builtins/shellCommandPreparation.d.ts.map +0 -1
  495. package/package/acode/dist/builtins/shellCommandResult.d.ts.map +0 -1
  496. package/package/acode/dist/builtins/shellCommandRunner.d.ts.map +0 -1
  497. package/package/acode/dist/builtins/shellOutputDecoder.d.ts.map +0 -1
  498. package/package/acode/dist/cli.d.ts.map +0 -1
  499. package/package/acode/dist/commands/commandLoader.d.ts.map +0 -1
  500. package/package/acode/dist/commands/commandParser.d.ts.map +0 -1
  501. package/package/acode/dist/commands/commandRegistry.d.ts.map +0 -1
  502. package/package/acode/dist/compaction.d.ts.map +0 -1
  503. package/package/acode/dist/config.d.ts.map +0 -1
  504. package/package/acode/dist/fs-utils.d.ts.map +0 -1
  505. package/package/acode/dist/hooks/hookBus.d.ts.map +0 -1
  506. package/package/acode/dist/index.d.ts.map +0 -1
  507. package/package/acode/dist/mcpToolNames.d.ts.map +0 -1
  508. package/package/acode/dist/parsers/ast-cache.d.ts.map +0 -1
  509. package/package/acode/dist/parsers/language-detector.d.ts.map +0 -1
  510. package/package/acode/dist/parsers/language-queries.d.ts.map +0 -1
  511. package/package/acode/dist/parsers/symbol-extractor.d.ts.map +0 -1
  512. package/package/acode/dist/parsers/tree-sitter-loader.d.ts.map +0 -1
  513. package/package/acode/dist/parsers/types.d.ts.map +0 -1
  514. package/package/acode/dist/runtime.d.ts.map +0 -1
  515. package/package/acode/dist/skill-tool.d.ts.map +0 -1
  516. package/package/acode/dist/sub-agent-manager.d.ts.map +0 -1
  517. package/package/acode/dist/sub-agent-store.d.ts.map +0 -1
  518. package/package/acode/dist/sub-agent-tools.d.ts.map +0 -1
  519. package/package/acode/dist/sub-agent-types.d.ts.map +0 -1
  520. package/package/acode/dist/todo-store.d.ts.map +0 -1
  521. package/package/acode/dist/todo-tool.d.ts.map +0 -1
  522. package/package/acode/dist/types.d.ts.map +0 -1
  523. package/package/aws-client-agent-mcp/dist/agent-client.d.ts.map +0 -1
  524. package/package/aws-client-agent-mcp/dist/agent-client.js.map +0 -1
  525. package/package/aws-client-agent-mcp/dist/agent-client.test.d.ts.map +0 -1
  526. package/package/aws-client-agent-mcp/dist/agent-client.test.js.map +0 -1
  527. package/package/aws-client-agent-mcp/dist/config.d.ts.map +0 -1
  528. package/package/aws-client-agent-mcp/dist/config.js.map +0 -1
  529. package/package/aws-client-agent-mcp/dist/config.test.d.ts.map +0 -1
  530. package/package/aws-client-agent-mcp/dist/config.test.js.map +0 -1
  531. package/package/aws-client-agent-mcp/dist/constants.d.ts.map +0 -1
  532. package/package/aws-client-agent-mcp/dist/constants.js.map +0 -1
  533. package/package/aws-client-agent-mcp/dist/context-manager.d.ts.map +0 -1
  534. package/package/aws-client-agent-mcp/dist/context-manager.js.map +0 -1
  535. package/package/aws-client-agent-mcp/dist/context-manager.test.d.ts.map +0 -1
  536. package/package/aws-client-agent-mcp/dist/context-manager.test.js.map +0 -1
  537. package/package/aws-client-agent-mcp/dist/http-client.d.ts.map +0 -1
  538. package/package/aws-client-agent-mcp/dist/http-client.js.map +0 -1
  539. package/package/aws-client-agent-mcp/dist/http-client.test.d.ts.map +0 -1
  540. package/package/aws-client-agent-mcp/dist/http-client.test.js.map +0 -1
  541. package/package/aws-client-agent-mcp/dist/index.d.ts.map +0 -1
  542. package/package/aws-client-agent-mcp/dist/index.js.map +0 -1
  543. package/package/aws-client-agent-mcp/dist/logger.d.ts.map +0 -1
  544. package/package/aws-client-agent-mcp/dist/logger.js.map +0 -1
  545. package/package/aws-client-agent-mcp/dist/logger.test.d.ts.map +0 -1
  546. package/package/aws-client-agent-mcp/dist/logger.test.js.map +0 -1
  547. package/package/aws-client-agent-mcp/dist/mcp-server.d.ts.map +0 -1
  548. package/package/aws-client-agent-mcp/dist/mcp-server.js.map +0 -1
  549. package/package/aws-client-agent-mcp/dist/mcp-server.test.d.ts.map +0 -1
  550. package/package/aws-client-agent-mcp/dist/mcp-server.test.js.map +0 -1
  551. package/package/aws-client-agent-mcp/dist/mcp-tools.d.ts.map +0 -1
  552. package/package/aws-client-agent-mcp/dist/mcp-tools.js.map +0 -1
  553. package/package/aws-client-agent-mcp/dist/mcp-tools.test.d.ts.map +0 -1
  554. package/package/aws-client-agent-mcp/dist/mcp-tools.test.js.map +0 -1
  555. package/package/aws-client-agent-mcp/dist/memory-store.d.ts.map +0 -1
  556. package/package/aws-client-agent-mcp/dist/memory-store.js.map +0 -1
  557. package/package/aws-client-agent-mcp/dist/memory-store.test.d.ts.map +0 -1
  558. package/package/aws-client-agent-mcp/dist/memory-store.test.js.map +0 -1
  559. package/package/aws-client-agent-mcp/dist/memory-tools.d.ts.map +0 -1
  560. package/package/aws-client-agent-mcp/dist/memory-tools.js.map +0 -1
  561. package/package/aws-client-agent-mcp/dist/message-buffer.d.ts.map +0 -1
  562. package/package/aws-client-agent-mcp/dist/message-buffer.js.map +0 -1
  563. package/package/aws-client-agent-mcp/dist/message-buffer.test.d.ts.map +0 -1
  564. package/package/aws-client-agent-mcp/dist/message-buffer.test.js.map +0 -1
  565. package/package/aws-client-agent-mcp/dist/messageContent.d.ts.map +0 -1
  566. package/package/aws-client-agent-mcp/dist/messageContent.js.map +0 -1
  567. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.d.ts.map +0 -1
  568. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.js.map +0 -1
  569. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.d.ts.map +0 -1
  570. package/package/aws-client-agent-mcp/dist/runtime-launch-binding.test.js.map +0 -1
  571. package/package/aws-client-agent-mcp/dist/server-memory-store.d.ts.map +0 -1
  572. package/package/aws-client-agent-mcp/dist/server-memory-store.js.map +0 -1
  573. package/package/aws-client-agent-mcp/dist/status-reporter.d.ts.map +0 -1
  574. package/package/aws-client-agent-mcp/dist/status-reporter.js.map +0 -1
  575. package/package/aws-client-agent-mcp/dist/status-reporter.test.d.ts.map +0 -1
  576. package/package/aws-client-agent-mcp/dist/status-reporter.test.js.map +0 -1
  577. package/package/aws-client-agent-mcp/dist/types.d.ts.map +0 -1
  578. package/package/aws-client-agent-mcp/dist/types.js.map +0 -1
  579. package/package/aws-client-agent-mcp/dist/websocket-client.d.ts.map +0 -1
  580. package/package/aws-client-agent-mcp/dist/websocket-client.js.map +0 -1
  581. package/package/aws-client-agent-mcp/dist/websocket-client.test.d.ts.map +0 -1
  582. package/package/aws-client-agent-mcp/dist/websocket-client.test.js.map +0 -1
  583. package/package/cc-switch-sdk/dist/constants.d.ts.map +0 -1
  584. package/package/cc-switch-sdk/dist/errors.d.ts.map +0 -1
  585. package/package/cc-switch-sdk/dist/index.d.ts.map +0 -1
  586. package/package/cc-switch-sdk/dist/schemas.d.ts.map +0 -1
  587. package/package/cc-switch-sdk/dist/sdk.d.ts.map +0 -1
  588. package/package/cc-switch-sdk/dist/services/ai-config-service.d.ts.map +0 -1
  589. package/package/cc-switch-sdk/dist/services/mcp-model.d.ts.map +0 -1
  590. package/package/cc-switch-sdk/dist/services/mcp-service.d.ts.map +0 -1
  591. package/package/cc-switch-sdk/dist/services/skill-model.d.ts.map +0 -1
  592. package/package/cc-switch-sdk/dist/services/skill-service.d.ts.map +0 -1
  593. package/package/cc-switch-sdk/dist/state.d.ts.map +0 -1
  594. package/package/cc-switch-sdk/dist/types.d.ts.map +0 -1
  595. package/package/cc-switch-sdk/dist/utils/fs.d.ts.map +0 -1
  596. package/package/cc-switch-sdk/dist/utils/id.d.ts.map +0 -1
@@ -1,2709 +1,38 @@
1
- /**
2
- * MCP Server 实现
3
- * 提供 MCP Tools 供 AI Agent 调用
4
- */
5
- import fs from "node:fs";
6
- import os from "node:os";
7
- import path from "node:path";
8
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
9
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
10
- import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
11
- import { AgentClient } from "./agent-client.js";
12
- import { MCP_SERVER_NAME, MCP_SERVER_VERSION, REGISTER_MAX_RETRIES, REGISTER_RETRY_INTERVAL_MS, DEGRADED_MODE_ENABLED, DEGRADED_MODE_RETRY_INTERVAL_MS, DEGRADED_MODE_MAX_RETRIES, } from "./constants.js";
13
- import { logger } from "./logger.js";
14
- import { MCP_TOOLS, MEMORY_TOOL_NAMES } from "./mcp-tools.js";
15
- import { MessageBuffer } from "./message-buffer.js";
16
- import { ContextManager } from "./context-manager.js";
17
- import { MemoryCapacityError, MemoryStore, } from "./memory-store.js";
18
- import { ServerMemoryStore } from "./server-memory-store.js";
19
- const DEFAULT_MCP_HTTP_REQUEST_TIMEOUT_MS = 120_000;
20
- /**
21
- * poll_message 内部轮询间隔:每 30 秒在永久阻塞的 poll_message 中醒来一次,
22
- * 检查 WebSocket 缓冲区、Memory 通知(文件变更/TODO/容量等)。
23
- * 能主动通知的消息(WebSocket)实时响应,无法主动通知的状态变化最长 30s 延迟。
24
- * poll_message 永不超时返回空,只在有消息可返回时才退出。
25
- */
26
- const POLL_MESSAGE_INTERNAL_POLL_INTERVAL_MS = 30_000;
27
- const PATH_UNSAFE_CHARACTERS = new Set([
28
- "<",
29
- ">",
30
- ":",
31
- '"',
32
- "/",
33
- "\\",
34
- "|",
35
- "?",
36
- "*",
37
- ]);
38
- export class McpServer {
39
- server;
40
- agentClient;
41
- messageBuffer;
42
- memoryStore;
43
- roleMemoryStore;
44
- sessionContextMemoryStore;
45
- initialRoleName = "";
46
- /** 是否有真实的角色名(非 env 空值 fallback "default-role") */
47
- hasRoleName = false;
48
- /** 上下文管理器 (v1.8.0) */
49
- contextManager;
50
- /** 子 Agent 模式:仅暴露 Memory 工具,拒绝非 Memory 工具调用 */
51
- isSubAgent;
52
- /**
53
- * 租约续期定时器 (v1.21.0)。
54
- * 每 60 秒续期一次当前 agent 持有的所有 updating memory 的租约。
55
- */
56
- leaseRenewalTimer = null;
57
- activeLeaseUris = new Set();
58
- static LEASE_DURATION_MINUTES = 10;
59
- static LEASE_RENEW_INTERVAL_MS = 60_000;
60
- /** profile 是否已同步(防止 activelySyncProfile 与 profileHandler 双重同步) */
61
- profileSynced = false;
62
- /**
63
- * poll_message 单例控制:防止并发调用导致消息被第一个消费后第二个空等。
64
- * 当已有 poll 在进行时,后续调用直接等待同一个 Promise。
65
- */
66
- pendingPollMessage = null;
67
- // 系统提示词由 runtime adapter(ACode/OpenCode/Codex/Claude)在会话创建时注入,
68
- // 无需通过 MCP 工具返回。三方 runtime(OpenCode/Claude)将三层提示词合并为一条 system prompt,
69
- // ACode 则直接注入为前三条 system role 消息。
70
- /**
71
- * 收到 Profile 后同步角色存储路径。
72
- * 当服务端返回的 roleName 与进程初始化时不一致时,重建 roleMemoryStore 指向正确的目录。
73
- */
74
- syncRoleMemoryStore(profile) {
75
- logger.info(`[McpServer] syncRoleMemoryStore 被调用: displayName=${profile.displayName}, roleName="${profile.roleName ?? '(空)'}", id=${profile.id}`);
76
- if (!profile.roleName) {
77
- logger.warn(`[McpServer] syncRoleMemoryStore: profile.roleName 为空,hasRoleName 保持 false。profile=${JSON.stringify({ id: profile.id, displayName: profile.displayName, roleName: profile.roleName, instanceName: profile.instanceName })}`);
78
- return;
79
- }
80
- const expectedRoleDir = McpServer.sanitizeRoleDirectoryName(profile.roleName);
81
- this.hasRoleName = true; // Profile 确认了角色名
82
- if (expectedRoleDir === this.initialRoleName) {
83
- return;
84
- }
85
- logger.info(`[McpServer] 角色目录与初始值不一致,重建 roleMemoryStore: "${this.initialRoleName}" -> "${expectedRoleDir}"`);
86
- this.roleMemoryStore = new MemoryStore(McpServer.buildRoleMemoryStorePath(expectedRoleDir));
87
- this.initialRoleName = expectedRoleDir;
88
- }
89
- /**
90
- * 在 profile 到达后检查是否需要将本地永久记忆同步到 server。
91
- * 触发条件:当前角色在 server 上还没有任何 memory 类型记录(通常发生在重部署后 server DB 为空时)。
92
- * 主流程:异步执行,不阻塞 profile 处理链路。
93
- * 1. 调用 server get_memory_stats 获取当前角色 memory 类型计数
94
- * 2. 若 count === 0,读取本地永久记忆(角色子目录 + 遗留父目录 fallback)
95
- * 3. 逐条调用 server create_memory 写入(已存在则静默跳过)
96
- */
97
- maybeSyncPermanentMemoryToServer() {
98
- this.doMaybeSyncPermanentMemoryToServer().catch((e) => {
99
- logger.warn('[McpServer] maybeSyncPermanentMemoryToServer 异常: %s, stack=%s', e instanceof Error ? e.message : String(e), e instanceof Error ? e.stack : '(no stack)');
100
- });
101
- }
102
- async doMaybeSyncPermanentMemoryToServer() {
103
- logger.info("[McpServer] maybeSyncPermanentMemoryToServer 开始,initialRoleName="
104
- + this.initialRoleName);
105
- // Step 1: 检查 server 当前角色的 memory 计数
106
- let stats;
107
- try {
108
- stats = await this.memoryStore.getStats();
109
- }
110
- catch (e) {
111
- logger.warn("[McpServer] 获取 server 记忆统计失败,跳过同步:", e instanceof Error ? e.message : e);
112
- return;
113
- }
114
- const memoryCount = stats.by_type["project_memory"]?.count ?? 0;
115
- logger.info(`[McpServer] server memory 计数=${memoryCount},`
116
- + `是否需要同步=${memoryCount === 0}`);
117
- if (memoryCount > 0) {
118
- return;
119
- }
120
- // Step 2: 读取本地永久记忆
121
- // 2a: 先读角色子目录
122
- let localMemories = [];
123
- try {
124
- localMemories = await this.roleMemoryStore.listByType("project_memory");
125
- logger.info(`[McpServer] 角色子目录读取到 ${localMemories.length} 条永久记忆`);
126
- }
127
- catch (e) {
128
- logger.warn("[McpServer] 读取角色子目录永久记忆失败:", e instanceof Error ? e.message : e);
129
- }
130
- // 2b: 角色子目录为空时,fallback 读遗留父目录(旧版无角色子目录)
131
- if (localMemories.length === 0) {
132
- const legacyStorePath = path.dirname(McpServer.buildRoleMemoryStorePath(this.initialRoleName));
133
- logger.info(`[McpServer] 尝试从遗留目录读取: ${legacyStorePath}`);
134
- try {
135
- const legacyStore = new MemoryStore(legacyStorePath);
136
- const legacyMemories = await legacyStore.listByType("project_memory");
137
- if (legacyMemories.length > 0) {
138
- logger.info(`[McpServer] 从遗留目录读取到 ${legacyMemories.length} 条永久记忆`);
139
- localMemories = legacyMemories;
140
- }
141
- }
142
- catch (e) {
143
- logger.warn("[McpServer] 读取遗留目录永久记忆失败:", e instanceof Error ? e.message : e);
144
- }
145
- }
146
- if (localMemories.length === 0) {
147
- logger.info("[McpServer] 本地也无永久记忆,跳过同步");
148
- return;
149
- }
150
- // Step 3: 逐条写入 server
151
- let synced = 0;
152
- let skipped = 0;
153
- for (const record of localMemories) {
154
- try {
155
- await this.memoryStore.createMemory({
156
- memory_type: "project_memory",
157
- uri: record.uri,
158
- title: record.title,
159
- content: record.content,
160
- keywords: record.keywords,
161
- bound_files: record.bound_files,
162
- file_md5_map: record.file_md5_map,
163
- last_check_time: record.last_check_time,
164
- });
165
- synced++;
166
- logger.info(`[McpServer] 同步永久记忆成功: ${record.uri}`);
167
- }
168
- catch (e) {
169
- const msg = e instanceof Error ? e.message : String(e);
170
- if (msg.includes("already exists")
171
- || msg.includes("Memory already exists")) {
172
- skipped++;
173
- }
174
- else {
175
- logger.warn(`[McpServer] 同步永久记忆失败 (${record.uri}): ${msg}`);
176
- skipped++;
177
- }
178
- }
179
- }
180
- logger.info(`[McpServer] 永久记忆同步完成: 成功 ${synced}, 跳过 ${skipped}, 共 ${localMemories.length}`);
181
- await this.reportMemoryStatsToServer();
182
- }
183
- /**
184
- * 规范化绑定的文件路径。
185
- * 项目约定以下前缀都表示工作区根路径:
186
- * ./xxx → ./xxx (已是相对路径)
187
- * /xxx → ./xxx (/ 视为工作区根路径)
188
- * @/xxx → ./xxx (@/ 视为工作区根路径别名)
189
- * xxx → ./xxx (裸路径视为当前工作区相对)
190
- */
191
- normalizeBoundFilePath(filePath) {
192
- const trimmed = String(filePath || "").trim().replace(/\\/g, "/");
193
- if (!trimmed)
194
- return trimmed;
195
- // 去掉 @/ 前缀(@ 视为工作区根路径)
196
- const noAt = trimmed.startsWith("@/") ? trimmed.substring(1) : trimmed;
197
- if (noAt.startsWith("./"))
198
- return noAt;
199
- // /xxx 和 xxx 都统一成 ./xxx(与 Java normalizeBoundFilePath 保持一致,处理多前导斜杠)
200
- return `./${noAt.replace(/^\/+/, "")}`;
201
- }
202
- /**
203
- * 规范化绑定的文件路径数组。
204
- */
205
- normalizeBoundFiles(files) {
206
- if (!files || !Array.isArray(files))
207
- return files;
208
- return files.map(f => this.normalizeBoundFilePath(String(f)));
209
- }
210
- /**
211
- * 验证绑定文件是否存在。
212
- * 主流程:规范化后的路径 -> 相对于工作区根路径解析 -> 逐文件检查是否存在 -> 汇总缺失列表并抛出错误。
213
- */
214
- async validateBoundFilesExist(boundFiles) {
215
- if (!boundFiles || boundFiles.length === 0)
216
- return;
217
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
218
- const { access } = await import('node:fs/promises');
219
- const missingFiles = [];
220
- for (const filePath of boundFiles) {
221
- try {
222
- const resolvedPath = path.resolve(workspacePath, filePath);
223
- await access(resolvedPath);
224
- }
225
- catch {
226
- missingFiles.push(filePath);
227
- }
228
- }
229
- if (missingFiles.length > 0) {
230
- throw new Error(`绑定的文件不存在,请确认文件路径正确后再重新提交:\n${missingFiles.map(f => ` - ${f}`).join('\n')}`);
231
- }
232
- }
233
- /**
234
- * 记忆写入成功后的 MCP 响应 DTO。
235
- * 主流程:保留定位与版本元数据,刻意不包含 content,避免 create/update 回显大段提交内容。
236
- */
237
- toMemoryWriteResult(record) {
238
- return {
239
- id: record.id,
240
- uri: record.uri,
241
- domain: record.domain,
242
- path: record.path,
243
- title: record.title,
244
- memory_type: record.memory_type,
245
- createdAt: record.createdAt,
246
- updatedAt: record.updatedAt,
247
- version: record.version,
248
- keywords: record.keywords,
249
- bound_files: record.bound_files,
250
- };
251
- }
252
- /**
253
- * 返回完整的 Memory 记录(包含元数据)。
254
- * Memory 增强功能 (v1.6.0)
255
- */
256
- toMemoryRecordWithMetadata(record) {
257
- return {
258
- id: record.id,
259
- uri: record.uri,
260
- domain: record.domain,
261
- path: record.path,
262
- title: record.title,
263
- content: record.content,
264
- memory_type: record.memory_type,
265
- createdAt: record.createdAt,
266
- updatedAt: record.updatedAt,
267
- version: record.version,
268
- // Memory 增强字段
269
- keywords: record.keywords,
270
- bound_files: record.bound_files,
271
- file_md5_map: record.file_md5_map,
272
- memory_status: record.memory_status,
273
- updating_agent_id: record.updating_agent_id,
274
- last_check_time: record.last_check_time,
275
- };
276
- }
277
- constructor() {
278
- this.agentClient = new AgentClient();
279
- this.messageBuffer = new MessageBuffer();
280
- this.initialRoleName = McpServer.resolveRoleMemoryDirectoryName();
281
- this.hasRoleName = !!process.env.AWS_ROLE_NAME; // 只有明确设置了 env 才认为有角色名
282
- const memoryStorePaths = McpServer.resolveMemoryStorePaths();
283
- this.memoryStore = new ServerMemoryStore(this.agentClient);
284
- this.roleMemoryStore = new MemoryStore(memoryStorePaths.rolePath);
285
- this.sessionContextMemoryStore = new MemoryStore(path.join(path.dirname(memoryStorePaths.rolePath), "..", "session_context"));
286
- this.isSubAgent = process.env.AWS_SUB_AGENT === "true";
287
- this.contextManager = new ContextManager(this.sessionContextMemoryStore);
288
- this.server = new Server({
289
- name: MCP_SERVER_NAME,
290
- version: MCP_SERVER_VERSION,
291
- }, {
292
- capabilities: {
293
- tools: {},
294
- },
295
- });
296
- this.setupHandlers();
297
- }
298
- setupHandlers() {
299
- this.server.setRequestHandler(ListToolsRequestSchema, async () => {
300
- const tools = this.isSubAgent
301
- ? MCP_TOOLS.filter((t) => MEMORY_TOOL_NAMES.has(t.name))
302
- : MCP_TOOLS;
303
- return { tools };
304
- });
305
- this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
306
- const { name, arguments: args } = request.params;
307
- try {
308
- const result = await this.handleToolCall(name, args);
309
- return {
310
- content: [
311
- {
312
- type: "text",
313
- text: JSON.stringify(result, null, 2),
314
- },
315
- ],
316
- };
317
- }
318
- catch (error) {
319
- const errorMessage = error instanceof Error ? error.message : String(error);
320
- const errorPayload = error instanceof MemoryCapacityError
321
- ? {
322
- error: errorMessage,
323
- ...error.details,
324
- }
325
- : { error: errorMessage };
326
- // 服务端记录完整异常,方便排查 LOST 等异常状态
327
- logger.error('[McpServer] 工具调用异常: tool=%s, error=%s, stack=%s', name, errorMessage, error instanceof Error ? error.stack : '(no stack)');
328
- return {
329
- content: [
330
- {
331
- type: "text",
332
- text: JSON.stringify(errorPayload, null, 2),
333
- },
334
- ],
335
- isError: true,
336
- };
337
- }
338
- });
339
- }
340
- /**
341
- * 处理工具调用
342
- */
343
- async handleToolCall(name, args) {
344
- // 子 agent 共享父 agent MCP 连接时注入的标记:
345
- // 剥离后跳过状态上报,避免子 agent 事件出现在父 agent 时间线上
346
- const isSubAgentCall = args?._aws_sub_agent === true;
347
- // 委托等待循环中后续 poll_message 注入的标记:
348
- // 跳过 reportToolUse/reportToolResult,只保留 reportThinking,
349
- // 避免"调用MCP↔思考中"状态反复跳动
350
- const isSilentPoll = args?._aws_silent_poll === true;
351
- const cleanArgs = { ...args };
352
- delete cleanArgs._aws_sub_agent;
353
- delete cleanArgs._aws_silent_poll;
354
- // 报告工具调用状态
355
- // - 子 agent 调用:完全跳过(不污染父 agent 时间线)
356
- // - 静默 poll:跳过 reportToolUse(不报"调用MCP"),但仍保留 statusReporter 供 reportThinking
357
- const statusReporter = isSubAgentCall
358
- ? null
359
- : this.agentClient.getStatusReporter();
360
- if (statusReporter && !isSilentPoll) {
361
- statusReporter.reportToolUse(name, this.sanitizeToolUseArgs(name, cleanArgs));
362
- }
363
- try {
364
- const result = await this.executeToolCall(name, cleanArgs);
365
- // 工具调用完成后,先上报返回数据供时间线日志查看,再切换为思考状态
366
- // (Agent 正在处理工具结果、决定下一步动作,而非真正的空闲等待)
367
- if (statusReporter && !isSilentPoll) {
368
- const reportableResult = this.stripAutoDelegateForReporting(result);
369
- if (reportableResult !== null) {
370
- // 正常结果:上报 toolResult + thinking(工具调用完成,回到思考状态)
371
- statusReporter.reportToolResult(reportableResult);
372
- statusReporter.reportThinking();
373
- }
374
- // 委托消息(reportableResult === null):跳过 reportToolResult 和 reportThinking,
375
- // 保持 tool_using 状态。主 agent 仍在 poll_message 循环中等待真正的消息,
376
- // 委托给子 agent 处理不应影响主 agent 的状态显示。
377
- }
378
- return result;
379
- }
380
- catch (error) {
381
- // 工具调用失败,报告错误状态
382
- if (statusReporter && !isSilentPoll) {
383
- statusReporter.reportToolError(error);
384
- }
385
- throw error;
386
- }
387
- }
388
- /**
389
- * 执行具体的工具调用
390
- */
391
- async executeToolCall(name, args) {
392
- if (this.isSubAgent && !MEMORY_TOOL_NAMES.has(name)) {
393
- throw new Error(`Sub-agent is not allowed to call tool: ${name}`);
394
- }
395
- switch (name) {
396
- case "get_colleague":
397
- return await this.agentClient.discoverColleagues();
398
- case "get_group_rooms":
399
- return await this.agentClient.discoverGroupRooms();
400
- case "get_profile": {
401
- return await this.agentClient.getProfile();
402
- }
403
- case "hire_colleague":
404
- return await this.handleHireColleague(args);
405
- case "hire_colleagues_batch":
406
- return await this.handleHireColleaguesBatch(args);
407
- case "send_group":
408
- return await this.handleSendGroup(args);
409
- case "send_private":
410
- return await this.handleSendPrivate(args);
411
- case "poll_message":
412
- return await this.handlePollMessage();
413
- case "get_message":
414
- return this.handleGetMessage();
415
- case "get_dm_history":
416
- return await this.handleGetDmHistory(args);
417
- case "get_group_history":
418
- return await this.handleGetGroupHistory(args);
419
- case "my_task":
420
- return await this.agentClient.getMyTasks();
421
- case "create_task_instance_comment":
422
- return await this.handleCreateTaskInstanceComment(args);
423
- case "submit_task_result":
424
- return await this.handleSubmitTaskResult(args);
425
- case "start_task_rollback":
426
- return await this.handleStartTaskRollback(args);
427
- case "finish_task_rollback":
428
- return await this.handleFinishTaskRollback(args);
429
- case "reject_task":
430
- return await this.handleRejectTask(args);
431
- case "terminate_task":
432
- return await this.handleTerminateTask(args);
433
- case "create_memory":
434
- return await this.handleCreateMemory(args);
435
- case "read_memory":
436
- return await this.handleReadMemory(args);
437
- case "update_memory":
438
- return await this.handleUpdateMemory(args);
439
- case "delete_memory":
440
- return await this.handleDeleteMemory(args);
441
- case "search_memory":
442
- return await this.handleSearchMemory(args);
443
- case "list_memories":
444
- return await this.handleListMemories(args);
445
- case "get_memory_stats":
446
- return await this.handleGetMemoryStats(args);
447
- // Memory 增强功能工具 (v1.6.0)
448
- case "list_memories_by_file":
449
- return await this.handleListMemoriesByFile(args);
450
- case "abandon_memory_update":
451
- return await this.handleAbandonMemoryUpdate(args);
452
- // Memory 近期记忆过期与回忆工具 (v1.11.0)
453
- case "memory_recurrence":
454
- return await this.handleMemoryRecurrence(args);
455
- case "memory_recurrence_complete":
456
- return await this.handleMemoryRecurrenceComplete();
457
- default:
458
- throw new Error(`Unknown tool: ${name}`);
459
- }
460
- }
461
- // ============ 工具处理方法 ============
462
- static MEMORY_TYPES = [
463
- "recently_memory",
464
- "agent_memory",
465
- "project_memory",
466
- "session_context",
467
- ];
468
- static MEMORY_TOOL_NAMES = new Set([
469
- "create_memory",
470
- "read_memory",
471
- "update_memory",
472
- "delete_memory",
473
- "search_memory",
474
- "list_memories",
475
- "get_memory_stats",
476
- // Memory 增强功能工具 (v1.6.0)
477
- "list_memories_by_file",
478
- "abandon_memory_update",
479
- // Memory 近期记忆过期与回忆工具 (v1.11.0)
480
- "memory_recurrence",
481
- "memory_recurrence_complete",
482
- ]);
483
- /**
484
- * 从 startPath 向上逐级查找 .agentswork 目录。
485
- * 主流程:从 startPath 开始检查是否存在 .agentswork -> 递归父目录直到根目录 -> 如果找到返回完整路径,否则在 startPath 创建。
486
- */
487
- static findAgentsworkDirSync(startPath) {
488
- let current = path.resolve(startPath);
489
- while (true) {
490
- if (fs.existsSync(path.join(current, ".agentswork"))) {
491
- return path.join(current, ".agentswork");
492
- }
493
- const parent = path.dirname(current);
494
- if (parent === current)
495
- break; // 到达根目录
496
- current = parent;
497
- }
498
- return path.join(path.resolve(startPath), ".agentswork");
499
- }
500
- /**
501
- * 清理角色名中的非法路径字符,返回安全的目录名。
502
- */
503
- static sanitizeRoleDirectoryName(roleName) {
504
- const safeRoleName = String(roleName || "")
505
- .trim()
506
- .split("")
507
- .map((character) => PATH_UNSAFE_CHARACTERS.has(character) || character.charCodeAt(0) < 32
508
- ? "_"
509
- : character)
510
- .join("")
511
- .replace(/^\.+|\.+$/g, "")
512
- .trim();
513
- return safeRoleName || "default-role";
514
- }
515
- /**
516
- * 解析角色级记忆目录名。
517
- * 主流程:优先使用 AWS_ROLE_NAME,清理路径分隔符等非法字符,避免不同角色的长期 memory 混写。
518
- */
519
- static resolveRoleMemoryDirectoryName() {
520
- return McpServer.sanitizeRoleDirectoryName(process.env.AWS_ROLE_NAME || "");
521
- }
522
- /**
523
- * 根据 roleName 构建永久记忆存储路径。
524
- */
525
- static buildRoleMemoryStorePath(roleName) {
526
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
527
- const agentsworkDir = McpServer.findAgentsworkDirSync(workspacePath);
528
- return path.join(agentsworkDir, "memory", "memory", McpServer.sanitizeRoleDirectoryName(roleName));
529
- }
530
- /**
531
- * 解析记忆存储路径。
532
- * 主流程:recently_memory/agent_memory 走 server 数据库;长期 project_memory 默认写入项目 .agentswork/memory/memory/<roleName>/<domain>.json。
533
- */
534
- static resolveMemoryStorePaths() {
535
- const configuredPath = String(process.env.AWS_MEMORY_STORE_PATH || "").trim();
536
- if (configuredPath) {
537
- return { instancePath: configuredPath, rolePath: configuredPath };
538
- }
539
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
540
- const agentsworkDir = McpServer.findAgentsworkDirSync(workspacePath);
541
- const instancePath = String(process.env.AWS_INSTANCE_MEMORY_STORE_PATH || "").trim() ||
542
- path.join(agentsworkDir, "memory", "server-managed");
543
- const rolePath = String(process.env.AWS_ROLE_MEMORY_STORE_PATH || "").trim() ||
544
- path.join(agentsworkDir, "memory", "memory", McpServer.resolveRoleMemoryDirectoryName());
545
- return { instancePath, rolePath };
546
- }
547
- getRequiredString(args, key) {
548
- const value = args[key];
549
- if (typeof value !== "string" || value.trim().length === 0) {
550
- throw new Error(`${key} is required`);
551
- }
552
- return value;
553
- }
554
- getOptionalString(args, key) {
555
- const value = args[key];
556
- return typeof value === "string" ? value : undefined;
557
- }
558
- getOptionalNumber(args, key) {
559
- const value = args[key];
560
- return typeof value === "number" ? value : undefined;
561
- }
562
- getOptionalStringArray(args, key) {
563
- const value = args[key];
564
- return Array.isArray(value) ? value.map((v) => String(v)) : undefined;
565
- }
566
- getOptionalMemoryType(args) {
567
- const value = args.memory_type;
568
- if (value === undefined) {
569
- return undefined;
570
- }
571
- return this.parseMemoryType(value);
572
- }
573
- parseMemoryType(value) {
574
- if (typeof value !== "string") {
575
- throw new Error(`Invalid memory_type: ${String(value)}`);
576
- }
577
- const normalized = value.trim();
578
- if (!this.isMemoryType(normalized)) {
579
- throw new Error(`Invalid memory_type: ${value}`);
580
- }
581
- return normalized;
582
- }
583
- isMemoryType(value) {
584
- return McpServer.MEMORY_TYPES.some((memoryType) => memoryType === value);
585
- }
586
- /**
587
- * 所有类型的记忆统一走服务端(Server 是唯一权威源)。
588
- * 本地 roleMemoryStore 仅用于 project_memory 的备份写入与启动恢复。
589
- */
590
- getMemoryStoreForType(_memoryType) {
591
- return this.memoryStore;
592
- }
593
- /**
594
- * 读取记忆:直接走服务端(Server 是唯一权威源)。
595
- */
596
- async readMemoryFromScopedStores(input) {
597
- if (input.memory_type) {
598
- return await this.memoryStore.readMemory(input);
599
- }
600
- const longTerm = await this.memoryStore.readMemory({
601
- ...input,
602
- memory_type: "project_memory",
603
- }).catch(() => null);
604
- if (longTerm) {
605
- return longTerm;
606
- }
607
- return await this.memoryStore.readMemory(input);
608
- }
609
- /**
610
- * 将 project_memory 异步备份到本地文件(纯灾备,不作为读取源)。
611
- */
612
- async backupProjectMemoryToLocal(input) {
613
- try {
614
- const record = await this.memoryStore.readMemory({
615
- uri: input.uri,
616
- memory_type: "project_memory",
617
- });
618
- if (record) {
619
- await this.roleMemoryStore.deleteMemory({ uri: input.uri, memory_type: "project_memory" }).catch(() => { });
620
- await this.roleMemoryStore.createMemory(record);
621
- }
622
- }
623
- catch (e) {
624
- logger.warn("[McpServer] backupProjectMemoryToLocal 失败: uri=%s", input.uri, e);
625
- }
626
- }
627
- /**
628
- * 删除 project_memory 的本地备份文件。
629
- */
630
- async removeProjectMemoryLocalBackup(uri) {
631
- try {
632
- await this.roleMemoryStore.deleteMemory({ uri, memory_type: "project_memory" });
633
- }
634
- catch (e) {
635
- logger.warn("[McpServer] removeProjectMemoryLocalBackup 失败: uri=%s", uri, e);
636
- }
637
- }
638
- /**
639
- * 定位可更新/删除的存储:统一走服务端。
640
- */
641
- async findMemoryStoreForMutation(input) {
642
- if (input.memory_type) {
643
- return { store: this.memoryStore, memory_type: input.memory_type };
644
- }
645
- const serverLongTerm = await this.memoryStore.readMemory({
646
- uri: input.uri,
647
- memory_type: "project_memory",
648
- }).catch(() => null);
649
- if (serverLongTerm) {
650
- return { store: this.memoryStore, memory_type: "project_memory" };
651
- }
652
- return { store: this.memoryStore };
653
- }
654
- async reportMemoryStatsToServer() {
655
- const stats = await this.memoryStore.getStats();
656
- try {
657
- await this.agentClient.reportMemoryStats(stats);
658
- }
659
- catch (error) {
660
- logger.warn("[McpServer] 上报 memory 统计失败:", error);
661
- }
662
- }
663
- /**
664
- * 系统提示词由 runtime adapter 在会话创建时注入,MCP 层面不再负责返回。
665
- * 详见 startListeningInternal -> profileHandler 中的合并注入逻辑。
666
- *
667
- * @deprecated 启动记忆不再通过 MCP 的 systemMessages 返回。
668
- * 三方 runtime(OpenCode/Claude)在 adapter 层将三层提示词合并为一条 system prompt;
669
- * ACode 则直接注入为前三条 system role 消息。
670
- */
671
- /**
672
- * 生成安全的工具状态上报参数。
673
- * 主流程:memory 工具只上报元数据,避免 content/query/old_string/new_string/append 离开本地记忆边界。
674
- */
675
- sanitizeToolUseArgs(name, args) {
676
- if (!McpServer.MEMORY_TOOL_NAMES.has(name)) {
677
- return args;
678
- }
679
- const sanitized = {};
680
- const uri = this.getOptionalString(args, "uri");
681
- const memoryType = this.getOptionalString(args, "memory_type");
682
- const limit = this.getOptionalNumber(args, "limit");
683
- if (uri) {
684
- sanitized.uri = uri;
685
- }
686
- if (memoryType) {
687
- sanitized.memory_type = memoryType;
688
- }
689
- if (typeof limit === "number") {
690
- sanitized.limit = limit;
691
- }
692
- if (name === "search_memory") {
693
- sanitized.queryProvided =
694
- typeof args.query === "string" && args.query.length > 0;
695
- if (args.keyword_mode !== undefined) {
696
- sanitized.keyword_mode = args.keyword_mode;
697
- }
698
- }
699
- return sanitized;
700
- }
701
- /**
702
- * 过滤工具返回结果中的 _acodeAutoDelegate 消息,避免自动委托通知
703
- * (memory 文件变更/过期/容量整理)作为工具返回数据泄露到时间线。
704
- *
705
- * 背景:ACode 运行时在 processMemoryAutoDelegates 中拦截这些消息并 spawn
706
- * 子 agent 处理,主 agent 不会感知。但 MCP server 的 statusReporter 在
707
- * 拦截之前就已上报原始结果,Java 后端 AgentEventService 会将含 actionResult
708
- * 的状态事件持久化并通过 AGENT_EVENTS_DELTA 推送到 dashboard 时间线,
709
- * 导致本应被隐藏的委托通知暴露给用户。
710
- *
711
- * 返回 null 表示结果全部为委托消息,应跳过 reportToolResult
712
- * (由 ACode 运行时在拦截后上报最终结果)。
713
- */
714
- stripAutoDelegateForReporting(result) {
715
- if (!result || typeof result !== "object") {
716
- return result;
717
- }
718
- const pollResult = result;
719
- const directMessages = Array.isArray(pollResult.directMessages)
720
- ? pollResult.directMessages
721
- : [];
722
- const groupMessages = Array.isArray(pollResult.groupMessages)
723
- ? pollResult.groupMessages
724
- : [];
725
- const isDelegate = (m) => !!m &&
726
- typeof m === "object" &&
727
- "_acodeAutoDelegate" in m;
728
- const hasDelegate = directMessages.some(isDelegate);
729
- if (!hasDelegate) {
730
- return result;
731
- }
732
- const remainingDm = directMessages.filter((m) => !isDelegate(m));
733
- if (remainingDm.length === 0 && groupMessages.length === 0) {
734
- return null;
735
- }
736
- return { ...pollResult, directMessages: remainingDm };
737
- }
738
- /**
739
- * 创建本地记忆。
740
- * 主流程:校验必填字段 -> 交给 MemoryStore 规范化 URI 和类型 -> 返回不含 content 的写入结果。
741
- */
742
- async handleCreateMemory(args) {
743
- const boundFiles = this.normalizeBoundFiles(this.getOptionalStringArray(args, "bound_files"));
744
- // 验证绑定文件是否存在,避免创建无有效文件绑定的记忆
745
- await this.validateBoundFilesExist(boundFiles);
746
- // 创建时即计算绑定文件的 MD5 基线,避免首次文件变更检测产生假阳性
747
- let fileMd5Map;
748
- let lastCheckTime;
749
- if (boundFiles && boundFiles.length > 0) {
750
- fileMd5Map = {};
751
- for (const f of boundFiles) {
752
- const md5 = await this.calculateFileMd5(f);
753
- if (md5)
754
- fileMd5Map[f] = md5;
755
- }
756
- lastCheckTime = new Date().toISOString();
757
- }
758
- const input = {
759
- memory_type: this.parseMemoryType(this.getRequiredString(args, "memory_type")),
760
- uri: this.getRequiredString(args, "uri"),
761
- title: this.getOptionalString(args, "title"),
762
- content: this.getRequiredString(args, "content"),
763
- keywords: this.getOptionalStringArray(args, "keywords"),
764
- bound_files: boundFiles,
765
- file_md5_map: fileMd5Map,
766
- last_check_time: lastCheckTime,
767
- };
768
- const created = await this.memoryStore.createMemory(input);
769
- if (input.memory_type === "project_memory") {
770
- this.backupProjectMemoryToLocal(input).catch((e) => {
771
- logger.warn("[McpServer] 备份 project_memory 到本地失败:", e);
772
- });
773
- }
774
- await this.reportMemoryStatsToServer();
775
- // v1.13.0: recently_memory 过期判定、agent_memory 过期/回忆状态由服务端 memory_status 字段管理,
776
- // 无需 TS 侧本地缓存。服务端默认按类型填充正确状态(recently_memory='normal', agent_memory='normal')。
777
- return this.toMemoryWriteResult(created);
778
- }
779
- /**
780
- * 读取本地记忆。
781
- * 主流程:按 uri 读取;如果传入 memory_type,则由 MemoryStore 执行类型一致性校验。
782
- */
783
- async handleReadMemory(args) {
784
- const input = {
785
- uri: this.getRequiredString(args, "uri"),
786
- memory_type: this.getOptionalMemoryType(args),
787
- };
788
- return await this.readMemoryFromScopedStores(input);
789
- }
790
- /**
791
- * 更新本地记忆。
792
- * 主流程:精确替换/追加/仅状态更新 -> MemoryStore 执行变更和版本递增 -> 返回不含 content 的写入结果。
793
- * 支持三种写法:1) 精确替换(old_string + new_string);2) 追加(append);3) 无内容更新(仅传 uri,
794
- * 用于过期续期、刷新绑定文件 MD5、释放 updating 状态等场景)。
795
- */
796
- async handleUpdateMemory(args) {
797
- const hasReplace = typeof args.old_string === "string" ||
798
- typeof args.new_string === "string";
799
- const hasCompleteReplace = typeof args.old_string === "string" &&
800
- typeof args.new_string === "string";
801
- const append = this.getOptionalString(args, "append");
802
- if (hasReplace && !hasCompleteReplace) {
803
- throw new Error("old_string and new_string must be provided together");
804
- }
805
- // 允许无内容更新:当未提供 old_string/new_string 且未提供有效 append 时,
806
- // 视为"仅状态更新"——用于过期续期、刷新绑定文件 MD5、释放 updating 状态等场景。
807
- // 此时内容不变,但会重算绑定文件 MD5 并续期(expirable 类型)。
808
- const input = {
809
- uri: this.getRequiredString(args, "uri"),
810
- memory_type: this.getOptionalMemoryType(args),
811
- title: this.getOptionalString(args, "title"),
812
- old_string: this.getOptionalString(args, "old_string"),
813
- new_string: this.getOptionalString(args, "new_string"),
814
- append,
815
- keywords: this.getOptionalStringArray(args, "keywords"),
816
- bound_files: this.normalizeBoundFiles(this.getOptionalStringArray(args, "bound_files")),
817
- };
818
- const target = await this.findMemoryStoreForMutation(input);
819
- // 读取当前记忆以获取 bound_files,用于 MD5 重算
820
- const existingForMd5 = await target.store.readMemory({
821
- uri: input.uri,
822
- memory_type: target.memory_type ?? input.memory_type,
823
- });
824
- // 校验绑定文件存在性:
825
- // - 如果提供了新的 bound_files,校验新列表中的文件是否都存在
826
- // - 如果未提供 bound_files,校验当前已绑定的文件是否都存在
827
- if (input.bound_files !== undefined) {
828
- await this.validateBoundFilesExist(input.bound_files);
829
- }
830
- else if (existingForMd5?.bound_files && existingForMd5.bound_files.length > 0) {
831
- await this.validateBoundFilesExist(existingForMd5.bound_files);
832
- }
833
- const effectiveBoundFiles = input.bound_files ?? existingForMd5?.bound_files;
834
- // 重新计算绑定文件的 MD5 map,确保更新后 MD5 与文件实际内容一致
835
- let fileMd5Map;
836
- let lastCheckTime;
837
- if (effectiveBoundFiles && effectiveBoundFiles.length > 0) {
838
- fileMd5Map = {};
839
- for (const f of effectiveBoundFiles) {
840
- const md5 = await this.calculateFileMd5(f);
841
- if (md5)
842
- fileMd5Map[f] = md5;
843
- }
844
- lastCheckTime = new Date().toISOString();
845
- }
846
- const updated = await target.store.updateMemory({
847
- ...input,
848
- memory_type: target.memory_type ?? input.memory_type,
849
- file_md5_map: fileMd5Map,
850
- last_check_time: lastCheckTime,
851
- });
852
- if (target.memory_type === "project_memory") {
853
- this.backupProjectMemoryToLocal({
854
- ...input,
855
- memory_type: "project_memory",
856
- file_md5_map: fileMd5Map,
857
- last_check_time: lastCheckTime,
858
- }).catch((e) => {
859
- logger.warn("[McpServer] 备份 project_memory 更新到本地失败:", e);
860
- });
861
- }
862
- await this.reportMemoryStatsToServer();
863
- // v1.13.0: agent_memory 过期与回忆状态由服务端 memory_status 字段管理。
864
- // v1.21.0: 如果是 project_memory 且之前是 updating 状态,释放 Server claim
865
- if (target.memory_type === 'project_memory' && existingForMd5?.memory_status === 'updating') {
866
- try {
867
- await this.memoryStore.releaseMemoryClaim(input.uri);
868
- this.activeLeaseUris.delete(input.uri);
869
- if (this.activeLeaseUris.size === 0) {
870
- this.stopLeaseRenewal();
871
- }
872
- }
873
- catch (e) {
874
- logger.warn('[McpServer] 释放 memory claim 失败: uri=%s, error=%s', input.uri, e instanceof Error ? e.message : String(e));
875
- }
876
- }
877
- return this.toMemoryWriteResult(updated);
878
- }
879
- /**
880
- * 删除本地记忆。
881
- * 主流程:按 uri 删除;如果传入 memory_type,则先校验分类,最后返回 deleted 布尔值。
882
- */
883
- async handleDeleteMemory(args) {
884
- const singleUri = typeof args.uri === "string" ? args.uri : undefined;
885
- const memoriesArray = Array.isArray(args.memories)
886
- ? args.memories
887
- : undefined;
888
- // uri 与 memories 互斥校验
889
- if (singleUri && memoriesArray && memoriesArray.length > 0) {
890
- throw new Error("delete_memory 不支持同时传入 uri 和 memories,请二选一");
891
- }
892
- if (!singleUri && (!memoriesArray || memoriesArray.length === 0)) {
893
- throw new Error("delete_memory 需要 uri 或 memories 参数");
894
- }
895
- // 单条路径:保持原返回结构 { deleted: boolean },向后兼容
896
- if (singleUri) {
897
- const memory_type = this.getOptionalMemoryType(args);
898
- const deleted = await this.deleteMemorySingle(singleUri, memory_type);
899
- return { deleted };
900
- }
901
- // 批量路径:逐条删除,每条独立 memory_type
902
- let deletedCount = 0;
903
- const notFound = [];
904
- const failed = [];
905
- for (let i = 0; i < memoriesArray.length; i++) {
906
- const item = memoriesArray[i];
907
- if (typeof item !== "object" || item === null) {
908
- failed.push({ uri: String(item), reason: `memories[${i}] 不是对象` });
909
- continue;
910
- }
911
- const obj = item;
912
- const uri = typeof obj.uri === "string" ? obj.uri : "";
913
- if (!uri) {
914
- failed.push({ uri: "", reason: `memories[${i}].uri 缺失` });
915
- continue;
916
- }
917
- const memory_type = typeof obj.memory_type === "string"
918
- ? this.parseMemoryType(obj.memory_type)
919
- : undefined;
920
- try {
921
- const deleted = await this.deleteMemorySingle(uri, memory_type, /* skipStats */ true);
922
- if (deleted) {
923
- deletedCount++;
924
- }
925
- else {
926
- notFound.push(uri);
927
- }
928
- }
929
- catch (e) {
930
- failed.push({ uri, reason: e instanceof Error ? e.message : String(e) });
931
- }
932
- }
933
- if (deletedCount > 0) {
934
- await this.reportMemoryStatsToServer();
935
- }
936
- return {
937
- deleted: deletedCount,
938
- not_found: notFound,
939
- failed,
940
- };
941
- }
942
- /**
943
- * 删除单条记忆的内部辅助方法。
944
- * - skipStats=true 时跳过 reportMemoryStatsToServer 调用,由批量调用方统一上报一次。
945
- * - 返回 true/false 表示是否实际删除(与 store.deleteMemory 一致)。
946
- */
947
- async deleteMemorySingle(uri, memory_type, skipStats = false) {
948
- const input = { uri, memory_type };
949
- const target = await this.findMemoryStoreForMutation(input);
950
- const effectiveType = target.memory_type ?? input.memory_type;
951
- const deleted = await target.store.deleteMemory({
952
- ...input,
953
- memory_type: effectiveType,
954
- });
955
- if (deleted) {
956
- if (effectiveType === "project_memory") {
957
- this.removeProjectMemoryLocalBackup(uri).catch((e) => {
958
- logger.warn("[McpServer] 删除 project_memory 本地备份失败:", e);
959
- });
960
- }
961
- if (!skipStats) {
962
- await this.reportMemoryStatsToServer();
963
- }
964
- }
965
- return deleted;
966
- }
967
- /**
968
- * 搜索本地记忆与子 Agent 记忆(父子 Agent 可见性)。
969
- * 主流程:解析 scope -> own(默认)仅搜自己 -> children仅搜子Agent的file-scoped记忆 -> all合并。
970
- * 子 Agent 判定规则:同项目且工作区路径为当前路径子目录。
971
- */
972
- async handleSearchMemory(args) {
973
- const rawScope = this.getOptionalString(args, "scope");
974
- const scope = rawScope === "children" || rawScope === "all" ? rawScope : "own";
975
- const rawKeywordMode = this.getOptionalString(args, "keyword_mode");
976
- const keywordMode = rawKeywordMode === "all" ? "all" : rawKeywordMode === "any" ? "any" : undefined;
977
- const input = {
978
- query: this.getRequiredString(args, "query"),
979
- limit: this.getOptionalNumber(args, "limit"),
980
- memory_type: this.getOptionalMemoryType(args),
981
- keywords: this.getOptionalStringArray(args, "keywords"),
982
- keyword_mode: keywordMode,
983
- scope,
984
- };
985
- // scope='children': 仅搜索子 Agent 的 file-scoped 记忆(通过 server 代理)
986
- if (scope === "children") {
987
- return await this.memoryStore.searchMemory(input);
988
- }
989
- // 搜索自己的记忆(scope='own' 默认行为)
990
- const ownResult = await this.searchOwnMemories(input);
991
- // scope='own': 仅返回自己的结果
992
- if (scope === "own") {
993
- return ownResult;
994
- }
995
- // scope='all': 合并自己的记忆与子 Agent 的 file-scoped 记忆
996
- const childResult = await this.memoryStore.searchMemory({
997
- ...input,
998
- scope: "children",
999
- });
1000
- return this.mergeSearchResults(ownResult, childResult, input.limit);
1001
- }
1002
- /**
1003
- * 搜索自己工作区的所有记忆。
1004
- * 具体逻辑:按 memory_type 分流 -> 无类型时合并 roleMemoryStore(永久)与 memoryStore(recently/agent)。
1005
- */
1006
- async searchOwnMemories(input) {
1007
- return await this.memoryStore.searchMemory(input);
1008
- }
1009
- /**
1010
- * 合并两组搜索结果,按 uri 去重、更新时间排序。
1011
- * 具体逻辑:own 结果优先 -> 补充 child 结果中 uri 不重复的 -> 排序 -> 截断到 limit。
1012
- */
1013
- mergeSearchResults(own, child, limit) {
1014
- const seen = new Set();
1015
- const maxResults = typeof limit === "number" && limit > 0 ? limit : 10;
1016
- const merged = [...own.results, ...child.results]
1017
- .filter((record) => {
1018
- const key = record.uri;
1019
- if (seen.has(key)) {
1020
- return false;
1021
- }
1022
- seen.add(key);
1023
- return true;
1024
- })
1025
- .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
1026
- .slice(0, maxResults);
1027
- return { query: own.query || child.query, results: merged };
1028
- }
1029
- /**
1030
- * 列出短期类型记忆。
1031
- * 主流程:只允许 recently_memory/agent_memory,拒绝 project_memory(永久记忆仅通过 search 检索)。
1032
- * MemoryStore.listByType 内部仍允许 project_memory 供文件变更检测使用,但 MCP 层面不暴露给 Agent。
1033
- */
1034
- async handleListMemories(args) {
1035
- const memoryType = this.getRequiredString(args, "memory_type");
1036
- if (memoryType === "project_memory" || memoryType === "session_context") {
1037
- throw new Error(`Invalid memory_type: ${memoryType}`);
1038
- }
1039
- return await this.memoryStore.listByType(memoryType);
1040
- }
1041
- /**
1042
- * 获取本地记忆容量统计。
1043
- * 主流程:读取 MemoryStore 统计结果,供 Agent 在创建前判断 listable/type 容量。
1044
- */
1045
- async handleGetMemoryStats(args) {
1046
- const stats = await this.memoryStore.getStats();
1047
- const memoryType = this.getOptionalMemoryType(args);
1048
- if (!memoryType) {
1049
- return stats;
1050
- }
1051
- return {
1052
- memory_type: memoryType,
1053
- total: stats.by_type[memoryType].count,
1054
- by_type: {
1055
- [memoryType]: stats.by_type[memoryType],
1056
- },
1057
- };
1058
- }
1059
- /**
1060
- * 按文件查询绑定了该文件的 Memory
1061
- * Memory 增强功能 (v1.6.0)
1062
- */
1063
- /**
1064
- * 按文件路径查询 Memory(支持 scope 跨 Agent 搜索)。
1065
- * 主流程:解析scope -> own(默认)仅自己 -> children搜索子Agent -> all合并。
1066
- * 子 Agent 搜索同时走两条路径:
1067
- * 1. server searchChildByFilePath(查 DB)
1068
- * 2. 本地文件系统扫描 child .agentswork/memory/memory/(查本地永久记忆文件)
1069
- * 两条路径结果按 id 去重合并,确保无论子 Agent 的永久记忆是否已同步到
1070
- * 服务端数据库,父 Agent 都能搜索到。
1071
- */
1072
- async handleListMemoriesByFile(args) {
1073
- const rawPath = this.getRequiredString(args, "file_path");
1074
- const normalizedPath = this.normalizeBoundFilePath(rawPath);
1075
- const memoryType = this.getOptionalMemoryType(args);
1076
- const rawScope = this.getOptionalString(args, "scope");
1077
- // 默认 scope='all':合并搜索自身与子Agent绑定文件的记忆
1078
- const scope = rawScope === "own" || rawScope === "children" ? rawScope : "all";
1079
- const fetchChildMemories = async () => {
1080
- return await this.memoryStore.listByFile(normalizedPath, {
1081
- memory_type: memoryType,
1082
- scope: "children",
1083
- }).catch(() => []);
1084
- };
1085
- if (scope === "children") {
1086
- const childResult = await fetchChildMemories();
1087
- return {
1088
- file_path: normalizedPath,
1089
- count: childResult.length,
1090
- memories: childResult.map(m => this.toMemoryRecordWithMetadata(m)),
1091
- };
1092
- }
1093
- // 搜索自己的记忆
1094
- const ownMemories = await this.collectOwnMemoriesByFile(normalizedPath, memoryType);
1095
- if (scope === "own") {
1096
- return {
1097
- file_path: normalizedPath,
1098
- count: ownMemories.length,
1099
- memories: ownMemories.map(m => this.toMemoryRecordWithMetadata(m)),
1100
- };
1101
- }
1102
- // scope='all': 合并自己的 + 子 Agent 的(server + 本地双路径已由 fetchChildMemories 聚合)
1103
- const childResult = await fetchChildMemories();
1104
- const seen = new Set(ownMemories.map(m => m.id));
1105
- const merged = [...ownMemories];
1106
- for (const m of childResult) {
1107
- if (!seen.has(m.id)) {
1108
- merged.push(m);
1109
- seen.add(m.id);
1110
- }
1111
- }
1112
- merged.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1113
- return {
1114
- file_path: normalizedPath,
1115
- count: merged.length,
1116
- memories: merged.map(m => this.toMemoryRecordWithMetadata(m)),
1117
- };
1118
- }
1119
- /**
1120
- * 按 id 去重合并两批 MemoryRecord,按 updatedAt 倒序排列。
1121
- * 首批(left)优先级高,其 id 会标记为 seen,重复的 right 项被丢弃。
1122
- */
1123
- mergeIdDedupe(left, right) {
1124
- const seen = new Set();
1125
- for (const m of left) {
1126
- if (m.id)
1127
- seen.add(m.id);
1128
- }
1129
- const merged = [...left];
1130
- for (const m of right) {
1131
- if (m.id && !seen.has(m.id)) {
1132
- merged.push(m);
1133
- seen.add(m.id);
1134
- }
1135
- }
1136
- merged.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1137
- return merged;
1138
- }
1139
- /**
1140
- * 通过文件系统扫描子 Agent 的 .agentswork 目录,
1141
- * 读取永久记忆 JSON 文件,按 path_prefix 匹配 bound_files。
1142
- *
1143
- * 主流程:
1144
- * 1. 解析 file_path(如 ./frontend/src/main.ts)的路径层级
1145
- * 2. 逐级(从长到短)找到子 Agent 的 .agentswork 目录
1146
- * 3. 读取该目录下所有 role 的 JSON 文件
1147
- * 4. 对每条记忆,将 bound_files 与 child-relative 路径比较
1148
- *
1149
- * 作为 server searchChildByFilePath 的本地补充,
1150
- * 确保子 Agent 的本地永久记忆(未同步到 server)也能被搜索到。
1151
- */
1152
- async searchChildMemoriesByFileLocal(filePath, memoryType) {
1153
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
1154
- const normalizedFilePath = this.normalizeBoundFilePath(filePath);
1155
- // 解析路径 segments
1156
- // 例如 ./frontend/src/main.ts → relativePath = "frontend/src/main.ts"
1157
- // segments = ["frontend", "src", "main.ts"]
1158
- const relativePath = normalizedFilePath.startsWith('./')
1159
- ? normalizedFilePath.substring(2)
1160
- : normalizedFilePath;
1161
- const segments = relativePath.split('/');
1162
- const results = [];
1163
- const seenById = new Set();
1164
- const { access, readdir, readFile } = await import('node:fs/promises');
1165
- // 从最长的路径前缀开始尝试(兼容嵌套子 Agent:深度=2 时 prefix=frontend/src)
1166
- for (let depth = segments.length - 1; depth >= 1; depth--) {
1167
- const prefixSegments = segments.slice(0, depth);
1168
- const childRelativeSegments = segments.slice(depth);
1169
- const childRelativePath = './' + childRelativeSegments.join('/');
1170
- // 子 Agent 的候选工作区路径
1171
- const childWorkspace = path.join(workspacePath, ...prefixSegments);
1172
- // 跳过父 Agent 自身(避免把 parent 当 child 扫)
1173
- if (childWorkspace === workspacePath)
1174
- continue;
1175
- const childAgentsworkDir = path.join(childWorkspace, '.agentswork');
1176
- // 检查 .agentswork 是否存在
1177
- try {
1178
- await access(childAgentsworkDir);
1179
- }
1180
- catch {
1181
- continue; // 这个前缀不是子 Agent 工作区
1182
- }
1183
- // 读取子 Agent 的永久记忆目录 .agentswork/memory/memory/<roleName>/
1184
- const memoryBaseDir = path.join(childAgentsworkDir, 'memory', 'memory');
1185
- let roleDirs;
1186
- try {
1187
- const entries = await readdir(memoryBaseDir, { withFileTypes: true });
1188
- roleDirs = entries.filter(e => e.isDirectory()).map(e => e.name);
1189
- }
1190
- catch {
1191
- continue;
1192
- }
1193
- for (const roleDir of roleDirs) {
1194
- const rolePath = path.join(memoryBaseDir, roleDir);
1195
- let files;
1196
- try {
1197
- files = await readdir(rolePath);
1198
- }
1199
- catch {
1200
- continue;
1201
- }
1202
- for (const file of files) {
1203
- if (!file.endsWith('.json'))
1204
- continue;
1205
- const jsonPath = path.join(rolePath, file);
1206
- let content;
1207
- try {
1208
- content = await readFile(jsonPath, 'utf-8');
1209
- }
1210
- catch {
1211
- continue;
1212
- }
1213
- let parsed;
1214
- try {
1215
- parsed = JSON.parse(content);
1216
- }
1217
- catch {
1218
- continue;
1219
- }
1220
- if (!parsed.by_uri)
1221
- continue;
1222
- // 遍历该 domain 的所有记忆,匹配 bound_files
1223
- for (const [uri, record] of Object.entries(parsed.by_uri)) {
1224
- if (!record)
1225
- continue;
1226
- const recordId = record.id || uri;
1227
- if (seenById.has(recordId))
1228
- continue;
1229
- if (memoryType && record.memory_type !== memoryType)
1230
- continue;
1231
- if (record.bound_files && record.bound_files.length > 0) {
1232
- const matched = record.bound_files.some(bf => this.normalizeBoundFilePath(bf) === childRelativePath);
1233
- if (matched) {
1234
- seenById.add(recordId);
1235
- results.push({
1236
- ...record,
1237
- uri: record.uri || uri,
1238
- });
1239
- }
1240
- }
1241
- }
1242
- }
1243
- }
1244
- }
1245
- results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1246
- return results;
1247
- }
1248
- /**
1249
- * 按文件路径搜索本 Agent 的记忆。
1250
- * 同时查询临时记忆(server)和永久记忆(local roleMemoryStore)。
1251
- */
1252
- async collectOwnMemoriesByFile(filePath, memoryType) {
1253
- return await this.memoryStore.listByFile(filePath, {
1254
- memory_type: memoryType,
1255
- });
1256
- }
1257
- /**
1258
- * 放弃 Memory 更新状态 (v1.21.0 改为走 release API)
1259
- * Memory 增强功能 (v1.6.0)
1260
- */
1261
- async handleAbandonMemoryUpdate(args) {
1262
- const uri = this.getRequiredString(args, "uri");
1263
- const memoryType = this.getOptionalMemoryType(args);
1264
- // 关闭更新前先读取记忆,校验已绑定文件的存在性。
1265
- // 若存在已删除的文件,放弃更新将失败——Agent 需先通过 update_memory 修正 bound_files 列表
1266
- // (移除不存在的文件)后,由 update_memory 自动释放 claim。
1267
- const record = await this.memoryStore.readMemory({ uri, memory_type: memoryType });
1268
- if (record?.bound_files && record.bound_files.length > 0) {
1269
- await this.validateBoundFilesExist(record.bound_files);
1270
- }
1271
- // 调 Server release API 释放 claim
1272
- await this.memoryStore.releaseMemoryClaim(uri);
1273
- if (record?.memory_type === 'project_memory') {
1274
- this.backupProjectMemoryToLocal({ uri, memory_type: 'project_memory' }).catch((e) => {
1275
- logger.info(`[McpServer] abandon_memory_update: 同步本地备份失败: ${uri}`, e);
1276
- });
1277
- }
1278
- this.activeLeaseUris.delete(uri);
1279
- if (this.activeLeaseUris.size === 0) {
1280
- this.stopLeaseRenewal();
1281
- }
1282
- return {
1283
- success: true,
1284
- uri,
1285
- message: "Memory 更新状态已清除",
1286
- };
1287
- }
1288
- // ============ Memory 近期记忆过期与回忆工具处理 (v1.11.0 / v1.13.0 / v1.19.0) ============
1289
- // v1.13.0: 过期判定与 recalling 状态标记已统一迁移到服务端 memory_status 字段,
1290
- // 下方两个工具方法直接调用服务端接口,不再依赖任何本地元数据。
1291
- // v1.19.0: recently_memory 纳入过期回忆流程,与 agent_memory 行为完全一致。
1292
- /**
1293
- * 处理 memory_recurrence 工具调用。
1294
- * 主流程:调用服务端 list_expired_memories 拉取已过期的 recently_memory / agent_memory ->
1295
- * 按过期时间排序(最早优先)-> 取前 limit 条 ->
1296
- * 仅标记这 limit 条为「回忆中」状态 -> 返回这些记忆及剩余数量。
1297
- */
1298
- async handleMemoryRecurrence(args) {
1299
- // v1.13.0: 过期判定与 recalling 状态全部由服务端管理
1300
- // v1.19.0: recently_memory 同样纳入过期回忆流程
1301
- const limit = Math.max(1, args?.limit ?? 10);
1302
- const expiredMemories = await this.memoryStore.listExpiredRecentlyMemories();
1303
- if (expiredMemories.length === 0) {
1304
- return {
1305
- memories: [],
1306
- count: 0,
1307
- remaining: 0,
1308
- totalExpired: 0,
1309
- message: "没有待回忆的即将遗忘记忆。",
1310
- };
1311
- }
1312
- // 按过期时间排序(最早过期优先)并取前 limit 条
1313
- // 使用 Date.getTime() 确保无论日期格式(UTC/本地时间)都能正确排序
1314
- const sorted = [...expiredMemories].sort((a, b) => {
1315
- const aTime = new Date(a.expiresAt || a.createdAt || 0).getTime();
1316
- const bTime = new Date(b.expiresAt || b.createdAt || 0).getTime();
1317
- return aTime - bTime;
1318
- });
1319
- const batch = sorted.slice(0, limit);
1320
- // 仅标记这一批为 recalling 状态(服务端)
1321
- const batchUris = batch.map(m => m.uri);
1322
- try {
1323
- await this.memoryStore.markExpiredMemoriesRecalling(batchUris);
1324
- }
1325
- catch (e) {
1326
- logger.warn('[McpServer] 标记过期记忆为 recalling 失败: uriCount=%s, error=%s', batchUris.length, e instanceof Error ? e.message : String(e));
1327
- }
1328
- const remaining = Math.max(0, expiredMemories.length - batch.length);
1329
- // 返回过期记忆的摘要(不含完整 content 以减少上下文占用)
1330
- const memorySummaries = batch.map(m => ({
1331
- uri: m.uri,
1332
- title: m.title,
1333
- domain: m.domain,
1334
- path: m.path,
1335
- memory_status: 'recalling',
1336
- contentPreview: typeof m.content === 'string' && m.content.length > 200 ? m.content.substring(0, 200) + '...' : m.content,
1337
- createdAt: m.createdAt,
1338
- updatedAt: m.updatedAt,
1339
- }));
1340
- return {
1341
- memories: memorySummaries,
1342
- count: batch.length,
1343
- limit: limit,
1344
- remaining: remaining,
1345
- totalExpired: expiredMemories.length,
1346
- message: remaining > 0
1347
- ? `本次处理 ${batch.length} 条即将遗忘的记忆(最早过期优先),剩余 ${remaining} 条待处理。\n` +
1348
- `请审查后决定(update_memory 续期,TTL ×2 / 不处理),\n` +
1349
- `完成后调用 memory_recurrence_complete 清理,再调 memory_recurrence 处理下一批。`
1350
- : `已处理全部 ${batch.length} 条即将遗忘的记忆。\n` +
1351
- `请审查后决定(update_memory 续期,TTL ×2 / 不处理),\n` +
1352
- `完成后调用 memory_recurrence_complete 清理。`,
1353
- };
1354
- }
1355
- /**
1356
- * 处理 memory_recurrence_complete 工具调用。
1357
- * 主流程:调用服务端 clear_recalling_expired_memories 删除所有「recalling + 已过期」记录。
1358
- */
1359
- async handleMemoryRecurrenceComplete() {
1360
- // v1.13.0: 删除操作由服务端完成
1361
- let result = { deleted: 0, uris: [] };
1362
- try {
1363
- result = await this.memoryStore.clearRecallingExpiredMemories();
1364
- }
1365
- catch (e) {
1366
- logger.warn('[McpServer] 清理回忆中记忆失败: %s', e instanceof Error ? e.message : String(e));
1367
- }
1368
- await this.reportMemoryStatsToServer();
1369
- const deletedCount = result.deleted ?? 0;
1370
- return {
1371
- deleted: deletedCount,
1372
- uris: result.uris ?? [],
1373
- message: deletedCount === 0
1374
- ? "没有待疏理的回忆中记忆。"
1375
- : `已清理 ${deletedCount} 条已过期近期记忆。`,
1376
- };
1377
- }
1378
- /**
1379
- * 获取最近的消息历史用于压缩。
1380
- * 主流程:获取未读消息 + 历史消息 -> 合并排序 -> 返回。
1381
- */
1382
- async fetchRecentMessages() {
1383
- const [dmHistory, groupHistory] = await Promise.all([
1384
- this.agentClient.getDmHistory(undefined, 200).catch(() => ({ messages: [], count: 0 })),
1385
- this.agentClient.getGroupHistory(200).catch(() => ({ messages: [], count: 0 })),
1386
- ]);
1387
- const messages = [
1388
- ...dmHistory.messages,
1389
- ...groupHistory.messages,
1390
- ];
1391
- messages.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
1392
- return messages;
1393
- }
1394
- async handleSendGroup(args) {
1395
- if (!args.content || typeof args.content !== "string") {
1396
- throw new Error("content is required");
1397
- }
1398
- // 记录 token 消耗 (v1.8.0)
1399
- this.recordMessageTokenUsage(args.content).catch((err) => logger.warn("[McpServer] Failed to record token usage", err));
1400
- return await this.agentClient.sendGroupMessage(args.content, args.projectName, args.roomId);
1401
- }
1402
- /**
1403
- * 记录消息发送产生的 token 消耗。
1404
- * 主流程:估算内容 token -> 获取会话 ID -> 记录到 ContextManager。
1405
- */
1406
- async recordMessageTokenUsage(content) {
1407
- try {
1408
- const profile = await this.agentClient.getProfile().catch(() => null);
1409
- if (!profile?.runtimeSessionId)
1410
- return;
1411
- const estimatedTokens = this.contextManager.estimateTokens(content);
1412
- await this.contextManager.recordUsage(profile.runtimeSessionId, { input: 0, output: estimatedTokens });
1413
- }
1414
- catch {
1415
- // 非关键功能,静默失败
1416
- }
1417
- }
1418
- async handleHireColleague(args) {
1419
- if (!args.workspacePath || typeof args.workspacePath !== "string") {
1420
- throw new Error("workspacePath is required");
1421
- }
1422
- return await this.agentClient.hireColleague(args);
1423
- }
1424
- async handleHireColleaguesBatch(args) {
1425
- if (!Array.isArray(args.items) || args.items.length === 0) {
1426
- throw new Error("items is required and must contain at least one hire item");
1427
- }
1428
- args.items.forEach((item, index) => {
1429
- if (!item.workspacePath || typeof item.workspacePath !== "string") {
1430
- throw new Error(`items[${index}].workspacePath is required`);
1431
- }
1432
- });
1433
- return await this.agentClient.hireColleaguesBatch(args);
1434
- }
1435
- async handleSendPrivate(args) {
1436
- if (!args.targetId || typeof args.targetId !== "string") {
1437
- throw new Error("targetId is required");
1438
- }
1439
- if (!args.content || typeof args.content !== "string") {
1440
- throw new Error("content is required");
1441
- }
1442
- // 记录 token 消耗 (v1.8.0)
1443
- this.recordMessageTokenUsage(args.content).catch((err) => logger.warn("[McpServer] Failed to record token usage", err));
1444
- return await this.agentClient.sendDirectMessage(args.targetId, args.content, args.waitReply, args.replyToCallId, args.timeoutMs);
1445
- }
1446
- async handleCreateTaskInstanceComment(args) {
1447
- const taskInstanceId = this.getRequiredString(args, "taskInstanceId");
1448
- const content = this.getRequiredString(args, "content");
1449
- return await this.agentClient.createTaskInstanceComment({
1450
- taskInstanceId,
1451
- content,
1452
- });
1453
- }
1454
- /**
1455
- * 构建内存通知并前置到消息列表。
1456
- * @returns 若生成了通知并已前置,返回 true;否则返回 false。
1457
- */
1458
- async prependMemoryNotification(messages, buildFn) {
1459
- const notification = await buildFn();
1460
- if (notification) {
1461
- messages.directMessages.unshift(notification);
1462
- return true;
1463
- }
1464
- return false;
1465
- }
1466
- async handlePollMessage() {
1467
- logger.info('[McpServer] handlePollMessage 被调用');
1468
- if (this.pendingPollMessage) {
1469
- logger.info('[McpServer] poll_message 已有进行中的调用,等待其完成');
1470
- return await this.pendingPollMessage;
1471
- }
1472
- this.pendingPollMessage = (async () => {
1473
- return await this.doPollMessage();
1474
- })();
1475
- try {
1476
- return await this.pendingPollMessage;
1477
- }
1478
- finally {
1479
- this.pendingPollMessage = null;
1480
- }
1481
- }
1482
- /**
1483
- * poll_message 主循环逻辑。
1484
- *
1485
- * 紧急/非紧急分类策略:
1486
- * - 紧急类:未读消息(私信/群聊)、task、todo
1487
- * - 非紧急类:memory过期回忆、memory文件变更、memory容量整理
1488
- * - 规则:有紧急类时返回所有命中的紧急项(不搭配非紧急类);
1489
- * 无紧急类时返回所有命中的非紧急项。
1490
- *
1491
- * 返回组合(只可能是以下两种之一):
1492
- * 1、所有命中的紧急项(未读消息 + todo + task 中命中的全部)
1493
- * 2、所有命中的非紧急项(memory过期 + 文件变更 + 容量整理 中命中的全部)
1494
- *
1495
- * 注:subAgent 终态事件由 ACode 运行时层(runtime.ts callPollMessageWithSubAgentRace)
1496
- * 在 MCP 调用前后注入,MCP server 内部不直接检测子 agent 状态。
1497
- * 注:Todo 在入口和 while 循环中均检查,确保阻塞期间创建的 Todo 也能被感知。
1498
- */
1499
- async doPollMessage() {
1500
- logger.info('[McpServer] doPollMessage 被调用');
1501
- // 紧急类检查:hydrate 未读消息 + task
1502
- await this.hydrateUnreadMessagesFromServer({
1503
- blockIfEmpty: false,
1504
- includePipelineTasks: true,
1505
- });
1506
- // 紧急类检查:Todo(只检查一次)
1507
- const todoNotif = await this.buildTodoReminderNotification();
1508
- // 紧急类:有未读消息/task 时,加上 todo(如果有),返回所有紧急项
1509
- const urgentBuffered = this.messageBuffer.get();
1510
- if (urgentBuffered.directMessages.length > 0 || urgentBuffered.groupMessages.length > 0) {
1511
- if (todoNotif) {
1512
- urgentBuffered.directMessages.unshift(todoNotif);
1513
- }
1514
- return this.buildPollResult(urgentBuffered);
1515
- }
1516
- // 紧急类:无未读消息/task 但有 todo,直接返回 todo
1517
- if (todoNotif) {
1518
- return this.buildPollResult({ directMessages: [todoNotif], groupMessages: [] });
1519
- }
1520
- // 非紧急类:无紧急类时,检查所有非紧急项(memory过期 + 文件变更 + 容量整理)
1521
- const nonUrgentMessages = await this.checkAllLocalNonUrgentNotifications();
1522
- if (nonUrgentMessages.length > 0) {
1523
- return this.buildPollResult({ directMessages: nonUrgentMessages, groupMessages: [] });
1524
- }
1525
- // 无紧急类且无非紧急类:进入 while 循环等待新消息
1526
- let pollTagAlreadyMcp = false; // 标记当前状态 tag 已确认为 poll_message,后续不再检查
1527
- while (true) {
1528
- // ★ 修复:poll_message 阻塞期间,如果有 thinking 事件覆盖了状态 tag,
1529
- // 在 while 循环首次迭代中检查并恢复 poll_message 状态,确保前端状态 tag
1530
- // 与真实运行状态一致。已确认后 flag 置位,后续循环不再重复检查和恢复。
1531
- if (!pollTagAlreadyMcp) {
1532
- const statusReporter = this.agentClient.getStatusReporter();
1533
- if (statusReporter) {
1534
- const currentStatus = statusReporter.getCurrentStatus();
1535
- const isPollMessageTag = currentStatus.status === 'tool_using' &&
1536
- currentStatus.actionDetail != null &&
1537
- currentStatus.actionDetail.includes('poll_message');
1538
- if (isPollMessageTag) {
1539
- pollTagAlreadyMcp = true;
1540
- }
1541
- else {
1542
- // 状态 tag 被覆盖了,恢复为 poll_message(只恢复一次)
1543
- statusReporter.reportToolUse('poll_message', {});
1544
- pollTagAlreadyMcp = true;
1545
- }
1546
- }
1547
- }
1548
- const polled = await this.messageBuffer.poll(POLL_MESSAGE_INTERNAL_POLL_INTERVAL_MS);
1549
- // 紧急类:poll 到消息(私信/群聊),立即返回
1550
- if (polled.directMessages.length > 0 || polled.groupMessages.length > 0) {
1551
- return this.buildPollResult(polled);
1552
- }
1553
- // poll 超时无消息时,重新检查紧急类(task + todo 可能在此期间到达/创建)
1554
- // 与入口处逻辑一致:先查 todo,再合并消息/task,紧急项一起返回
1555
- await this.hydrateUnreadMessagesFromServer({
1556
- blockIfEmpty: false,
1557
- includePipelineTasks: true,
1558
- });
1559
- const todoNotifLoop = await this.buildTodoReminderNotification();
1560
- const taskBuffered = this.messageBuffer.get();
1561
- if (taskBuffered.directMessages.length > 0 || taskBuffered.groupMessages.length > 0) {
1562
- if (todoNotifLoop) {
1563
- taskBuffered.directMessages.unshift(todoNotifLoop);
1564
- }
1565
- return this.buildPollResult(taskBuffered);
1566
- }
1567
- // 紧急类:无消息/task 但有 todo,返回 todo
1568
- if (todoNotifLoop) {
1569
- return this.buildPollResult({ directMessages: [todoNotifLoop], groupMessages: [] });
1570
- }
1571
- // 非紧急类:无紧急类时,检查非紧急项(文件变更仅在入口处检测一次,循环内跳过)
1572
- const nonUrgent = await this.checkAllLocalNonUrgentNotifications(false);
1573
- if (nonUrgent.length > 0) {
1574
- return this.buildPollResult({ directMessages: nonUrgent, groupMessages: [] });
1575
- }
1576
- }
1577
- }
1578
- /**
1579
- * 检查本地非紧急通知(memory过期回忆、文件变更、容量整理),返回所有命中的通知。
1580
- * Task 不在此方法中检查,因为 Task 通过服务端 hydrate 以消息形式返回。
1581
- * @param includeFileChange 是否包含文件变更检测,默认 true;循环轮询时传 false 以跳过。
1582
- */
1583
- async checkAllLocalNonUrgentNotifications(includeFileChange = true) {
1584
- const messages = [];
1585
- const recurrenceNotif = await this.buildMemoryRecurrenceNotification();
1586
- if (recurrenceNotif)
1587
- messages.push(recurrenceNotif);
1588
- if (includeFileChange) {
1589
- const fileChangeNotif = await this.buildMemoryFileChangeNotification();
1590
- if (fileChangeNotif)
1591
- messages.push(fileChangeNotif);
1592
- }
1593
- const cleanupNotif = await this.buildMemoryCleanupNotification();
1594
- if (cleanupNotif)
1595
- messages.push(cleanupNotif);
1596
- return messages;
1597
- }
1598
- buildPollResult(messages, fromBuffer = true) {
1599
- return {
1600
- directMessages: messages.directMessages,
1601
- groupMessages: messages.groupMessages,
1602
- fromBuffer,
1603
- systemMessages: [],
1604
- };
1605
- }
1606
- /**
1607
- * 解析正整数环境变量。
1608
- * 主流程:读取环境变量 -> 转成整数 -> 非法值使用默认值。
1609
- */
1610
- parsePositiveIntegerEnv(name, defaultValue) {
1611
- const raw = process.env[name];
1612
- if (!raw || !raw.trim()) {
1613
- return defaultValue;
1614
- }
1615
- const parsed = Number.parseInt(raw, 10);
1616
- if (!Number.isFinite(parsed) || parsed <= 0) {
1617
- logger.warn(`[McpServer] ${name} 无效,使用默认值 ${defaultValue}ms`);
1618
- return defaultValue;
1619
- }
1620
- return parsed;
1621
- }
1622
- sleep(delayMs) {
1623
- if (delayMs <= 0) {
1624
- return Promise.resolve();
1625
- }
1626
- return new Promise((resolve) => setTimeout(resolve, delayMs));
1627
- }
1628
- /**
1629
- * 构建本地记忆整理提醒。
1630
- * 主流程:读取容量统计 -> 找出超过软限制的 listable 类型 -> 注入一条非持久化系统私信提醒 Agent 整理。
1631
- */
1632
- async buildMemoryCleanupNotification() {
1633
- try {
1634
- const stats = await this.memoryStore.getStats();
1635
- const exceededTypes = Object.entries(stats.by_type).filter(([, typeStats]) => typeStats.listable && typeStats.exceeded);
1636
- const exceededDomains = Object.entries(stats.by_type).flatMap(([memoryType, typeStats]) => typeStats.listable
1637
- ? Object.entries(typeStats.by_domain)
1638
- .filter(([, domainStats]) => domainStats.exceeded)
1639
- .map(([domain, domainStats]) => ({
1640
- memoryType,
1641
- domain,
1642
- domainStats,
1643
- }))
1644
- : []);
1645
- if (exceededTypes.length === 0 && exceededDomains.length === 0) {
1646
- return null;
1647
- }
1648
- const typeDetails = exceededTypes.map(([memoryType, typeStats]) => `${memoryType} 当前 ${typeStats.domainCount} 个 URI domain 类型,建议上限 ${typeStats.limit} 个,硬上限 ${typeStats.hardLimit} 个`);
1649
- const domainDetails = exceededDomains.map(({ memoryType, domain, domainStats }) => `${memoryType}/${domain} 类型下当前 ${domainStats.count} 条 memory,建议上限 ${domainStats.limit} 条,硬上限 ${domainStats.hardLimit} 条`);
1650
- const cleanupActions = [
1651
- "请调用 get_memory_stats 查看容量详情",
1652
- "调用 list_memories 按类型查看超限记忆",
1653
- "删除不再需要的 recently_memory",
1654
- "更新 agent_memory 续期(TTL ×2)或删除过期项",
1655
- ].join(";");
1656
- return {
1657
- msgId: `memory-cleanup-${Date.now()}`,
1658
- senderId: "MCP-System",
1659
- senderName: "系统",
1660
- receiverId: this.getCurrentAgentId() || undefined,
1661
- content: `本地 memory 已超过建议容量,需要整理。${[...typeDetails, ...domainDetails].join(";")}。${cleanupActions}。`,
1662
- requireReply: false,
1663
- prompt: "这是一条本地 memory 容量整理提醒,自动生成,无需持久化。请优先整理已超过建议上限的短期/近期 memory 类型,或具体类型下的 memory 条目。",
1664
- timestamp: new Date().toISOString(),
1665
- _acodeAutoDelegate: {
1666
- type: 'memory_cleanup',
1667
- uris: exceededDomains.map(d => `${d.memoryType}/${d.domain}`),
1668
- },
1669
- };
1670
- }
1671
- catch (error) {
1672
- logger.warn('[McpServer] 构建本地 memory 整理提醒失败: %s', error instanceof Error ? error.message : String(error));
1673
- return null;
1674
- }
1675
- }
1676
- /**
1677
- * 构建近期记忆过期回忆通知(有即将遗忘的记忆需要整理)。
1678
- * Memory 近期记忆过期 (v1.11.0) / v1.13.0 改用服务端 memory_status 字段 / v1.19.0 扩展
1679
- *
1680
- * 通知策略(基于服务端 memory_status):
1681
- * 1. 有过期 recently_memory / agent_memory 且 memory_status='recalling'(回忆中但未完成疏理):
1682
- * 通知 "存在待疏理的即将遗忘记忆,请通过 memory_recurrence 获取进行整理"
1683
- * 2. 有过期 recently_memory / agent_memory 且 memory_status='normal'(尚未开始回忆):
1684
- * 通知 "有即将遗忘记忆得整理,请通过 memory_recurrence 进行回忆"
1685
- * v1.19.0: recently_memory 纳入过期回忆流程,与 agent_memory 一并处理。
1686
- */
1687
- async buildMemoryRecurrenceNotification() {
1688
- try {
1689
- // v1.13.0: 改为调用服务端 list_expired_memories,返回的记录带 memory_status
1690
- const expiredMemories = await this.memoryStore.listExpiredRecentlyMemories();
1691
- if (expiredMemories.length === 0)
1692
- return null;
1693
- const agentId = this.getCurrentAgentId();
1694
- if (!agentId)
1695
- return null;
1696
- const recallingExpired = expiredMemories.filter(m => m.memory_status === 'recalling');
1697
- const normalExpired = expiredMemories.filter(m => !recallingExpired.includes(m));
1698
- // 场景1: 有 recalling 状态的过期记忆(Agent 未完成疏理)
1699
- if (recallingExpired.length > 0) {
1700
- const memoryList = recallingExpired.slice(0, 5).map(m => m.uri).join('\n');
1701
- return {
1702
- msgId: `memory-recurrence-pending-${Date.now()}`,
1703
- senderId: "MCP-System",
1704
- senderName: "系统",
1705
- receiverId: agentId,
1706
- content: `存在待疏理的即将遗忘记忆(共 ${recallingExpired.length} 条),请通过 memory_recurrence 获取进行整理。\n\n待疏理记忆:\n${memoryList}${recallingExpired.length > 5 ? `\n ...及其他 ${recallingExpired.length - 5} 条` : ''}`,
1707
- requireReply: false,
1708
- prompt: "memory_recurrence 取回的记忆仍在回忆中,请通过 memory_recurrence_complete 完成疏理,或继续归档/续期。",
1709
- timestamp: new Date().toISOString(),
1710
- _acodeAutoDelegate: {
1711
- type: 'memory_expiration',
1712
- uris: recallingExpired.map(m => m.uri),
1713
- },
1714
- };
1715
- }
1716
- // 场景2: 有待回忆的过期记忆
1717
- if (normalExpired.length > 0) {
1718
- const memoryList = normalExpired.slice(0, 5).map(m => m.uri).join('\n');
1719
- return {
1720
- msgId: `memory-recurrence-${Date.now()}`,
1721
- senderId: "MCP-System",
1722
- senderName: "系统",
1723
- receiverId: agentId,
1724
- content: `有 ${normalExpired.length} 条即将遗忘的记忆得整理,请使用 memory_recurrence(limit=10) 分批处理(最早过期优先,每次 10 条)。\n\n即将遗忘的记忆:\n${memoryList}${normalExpired.length > 5 ? `\n ...及其他 ${normalExpired.length - 5} 条` : ''}`,
1725
- requireReply: false,
1726
- prompt: `这是一条近期记忆过期提醒,共有 ${normalExpired.length} 条记忆即将遗忘,请调用 memory_recurrence 分批次获取并决定处理方式。`,
1727
- timestamp: new Date().toISOString(),
1728
- _acodeAutoDelegate: {
1729
- type: 'memory_expiration',
1730
- uris: normalExpired.map(m => m.uri),
1731
- },
1732
- };
1733
- }
1734
- return null;
1735
- }
1736
- catch (error) {
1737
- logger.warn('[McpServer] 构建近期记忆过期回忆通知失败: %s', error instanceof Error ? error.message : String(error));
1738
- return null;
1739
- }
1740
- }
1741
- /**
1742
- * 构建 Memory 文件变更检测通知。
1743
- * Memory 增强功能 (v1.6.0)
1744
- *
1745
- * 检测流程(所有数据均从服务端读取,不降级到本地):
1746
- * 1. 检查本 Agent 标记为 updating 但未处理的 Memory
1747
- * 2. 检查短期记忆、近期记忆绑定的文件是否变更
1748
- * 3. 检查永久记忆的文件变更,先走 Server 原子 claim,再标记本地
1749
- */
1750
- async buildMemoryFileChangeNotification() {
1751
- logger.info(`[McpServer] buildMemoryFileChangeNotification 被调用: hasRoleName=${this.hasRoleName}, agentId=${this.getCurrentAgentId() ?? '(空)'}`);
1752
- // 无角色名时不检测本地永久记忆文件变更(避免 default-role 跨角色污染)
1753
- if (!this.hasRoleName) {
1754
- logger.info('[McpServer] 文件变更检测跳过: hasRoleName=false');
1755
- return null;
1756
- }
1757
- try {
1758
- const agentId = this.getCurrentAgentId();
1759
- if (!agentId) {
1760
- logger.info('[McpServer] 文件变更检测跳过: agentId 为空');
1761
- return null;
1762
- }
1763
- // ★ 所有检查均从服务端读取(memory_status/claim/租约/file_md5_map 均以服务端 DB 为权威源)。
1764
- // 原Step 0 syncClaimStatusFromServer 读本地 roleMemoryStore 清理本地过时 claim,
1765
- // 服务端权威后本地 updating 状态不再被任何决策路径读取,该同步已无意义,移除。
1766
- // 服务端不可用时直接 catch 返回 null(下方 catch),不降级到本地。
1767
- // 步骤1: 检查是否有 pending 的 updating memories(服务端角色级永久记忆),
1768
- // 同时检测绑定文件是否再次变更(用户可能在 Agent 处理前又改了文件)
1769
- // 数据源切换:从服务端角色级 scope 读取(同角色所有实例共享),
1770
- // 不再用本地 roleMemoryStore(仅本实例可见,兄弟实例无法检测到彼此创建的 memory)。
1771
- const permanentList = await this.memoryStore.listByType('project_memory');
1772
- const updatingMemories = permanentList.filter(m => m.memory_status === 'updating' && m.updating_agent_id === agentId);
1773
- if (updatingMemories.length > 0) {
1774
- // 当前 agent 持有未完成的 updating memory,确保租约续期定时器在运行,
1775
- // 避免 agent 在更新过程中租约过期被其他实例接管。
1776
- this.startLeaseRenewal();
1777
- // 对 updating 记忆也检测文件是否再次变更
1778
- const updatingChanges = await this.checkMemoriesFileChanges(updatingMemories);
1779
- if (updatingChanges.length > 0) {
1780
- // 有新的文件变更,更新 MD5 map 持久化,发出新变更通知
1781
- for (const change of updatingChanges) {
1782
- const memory = updatingMemories.find(m => m.id === change.memoryId);
1783
- if (memory) {
1784
- // MD5 回写到服务端角色级 scope(显式传 memory_status:updating 防止 auto-release)
1785
- // bound_files 同步回写:checkMemoriesFileChanges 可能已修剪掉消失的文件路径(消除悬空引用)
1786
- await this.memoryStore.updateMemory({
1787
- uri: memory.uri,
1788
- memory_type: 'project_memory',
1789
- memory_status: 'updating',
1790
- updating_agent_id: agentId,
1791
- file_md5_map: memory.file_md5_map,
1792
- last_check_time: memory.last_check_time,
1793
- bound_files: memory.bound_files,
1794
- });
1795
- }
1796
- }
1797
- const selectedCount = Math.min(updatingChanges.length, 5);
1798
- const changeDetails = updatingChanges
1799
- .slice(0, 5)
1800
- .map(change => `- ${change.memoryUri} (文件: ${change.changedFiles.join(', ')})`)
1801
- .join('\n');
1802
- return {
1803
- msgId: `memory-file-change-${Date.now()}`,
1804
- senderId: "MCP-System",
1805
- senderName: "系统",
1806
- receiverId: agentId,
1807
- content: `检测到以下 ${selectedCount} 个处于更新中的 Memory 绑定的文件再次变更,已更新 MD5 记录:\n\n${changeDetails}\n\n请调用 read_memory 查看最新内容并调用 update_memory 更新。`,
1808
- requireReply: false,
1809
- prompt: "这是一条文件变更提醒,您之前标记为更新中的记忆绑定的文件又有新变更,请处理。",
1810
- timestamp: new Date().toISOString(),
1811
- _acodeAutoDelegate: {
1812
- type: 'memory_file_change',
1813
- uris: updatingChanges.slice(0, 5).map(c => c.memoryUri),
1814
- fileChanges: updatingChanges.slice(0, 5).map(c => {
1815
- const memory = updatingMemories.find(m => m.id === c.memoryId);
1816
- return {
1817
- uri: c.memoryUri,
1818
- boundFiles: memory?.bound_files ?? [],
1819
- fileStatuses: c.fileStatuses,
1820
- changedFiles: c.changedFiles,
1821
- };
1822
- }),
1823
- },
1824
- };
1825
- }
1826
- const pendingToNotify = updatingMemories;
1827
- const pendingStatuses = new Map();
1828
- for (const m of pendingToNotify.slice(0, 5)) {
1829
- pendingStatuses.set(m.uri, await this.computeBoundFileStatuses(m));
1830
- }
1831
- const memoriesWithRealChanges = pendingToNotify.filter(m => {
1832
- const statuses = pendingStatuses.get(m.uri);
1833
- if (!statuses || statuses.length === 0)
1834
- return false;
1835
- return statuses.some(s => s.status === 'content_changed' || s.status === 'not_found');
1836
- });
1837
- const staleUpdatingMemories = pendingToNotify.filter(m => !memoriesWithRealChanges.includes(m));
1838
- if (staleUpdatingMemories.length > 0) {
1839
- logger.info(`[McpServer] 文件变更检测 Step1: ${staleUpdatingMemories.length} 个 updating memory 绑定文件无真实变更,自动释放: ${staleUpdatingMemories.map(m => m.uri).join(', ')}`);
1840
- for (const m of staleUpdatingMemories) {
1841
- try {
1842
- await this.memoryStore.releaseMemoryClaim(m.uri);
1843
- this.activeLeaseUris.delete(m.uri);
1844
- await this.memoryStore.updateMemory({
1845
- uri: m.uri,
1846
- memory_type: 'project_memory',
1847
- });
1848
- }
1849
- catch (e) {
1850
- logger.info(`[McpServer] 自动释放 stale updating memory 失败: ${m.uri}`, e);
1851
- }
1852
- }
1853
- if (this.activeLeaseUris.size === 0) {
1854
- this.stopLeaseRenewal();
1855
- }
1856
- }
1857
- if (memoriesWithRealChanges.length === 0) {
1858
- logger.info(`[McpServer] 文件变更检测 Step1: 所有 updating memory 绑定文件均无真实变更,已自动释放,跳过委托`);
1859
- return null;
1860
- }
1861
- const memoryList = memoriesWithRealChanges
1862
- .slice(0, 5)
1863
- .map(m => `- ${m.uri} (文件: ${m.bound_files?.join(', ') || '未知'})`)
1864
- .join('\n');
1865
- logger.info(`[McpServer] 文件变更检测 Step1: ${memoriesWithRealChanges.length}/${updatingMemories.length} 个 updating memory 有真实文件变更,触发委托`);
1866
- return {
1867
- msgId: `memory-update-pending-${Date.now()}`,
1868
- senderId: "MCP-System",
1869
- senderName: "系统",
1870
- receiverId: agentId,
1871
- content: `您有以下 Memory 正处于更新中状态但尚未完成更新:\n\n${memoryList}\n\n请继续完成更新工作。`,
1872
- requireReply: false,
1873
- prompt: "这是一条更新提醒,您之前已标记这些记忆为更新中,请继续处理。",
1874
- timestamp: new Date().toISOString(),
1875
- _acodeAutoDelegate: {
1876
- type: 'memory_file_change',
1877
- uris: memoriesWithRealChanges.slice(0, 5).map(m => m.uri),
1878
- fileChanges: memoriesWithRealChanges.slice(0, 5).map(m => ({
1879
- uri: m.uri,
1880
- boundFiles: m.bound_files ?? [],
1881
- fileStatuses: pendingStatuses.get(m.uri) ?? [],
1882
- })),
1883
- },
1884
- };
1885
- }
1886
- // 步骤2: 检查短期记忆和近期记忆(服务端存储)
1887
- const recentlyMemoryMemories = await this.memoryStore.listByType('recently_memory');
1888
- const agentMemoryMemories = await this.memoryStore.listByType('agent_memory');
1889
- const listableMemories = [...recentlyMemoryMemories, ...agentMemoryMemories];
1890
- // 诊断日志:列出 listable memory 的绑定文件和 MD5 基线情况
1891
- const listableWithFiles = listableMemories.filter(m => m.bound_files && m.bound_files.length > 0);
1892
- logger.info(`[McpServer] 文件变更检测 Step2: recently=${recentlyMemoryMemories.length}, agent=${agentMemoryMemories.length}, ` +
1893
- `有绑定文件=${listableWithFiles.length}/${listableMemories.length}。` +
1894
- `详情: ${listableMemories.map(m => `${m.uri}(bound=${m.bound_files?.length ?? 0},md5=${Object.keys(m.file_md5_map ?? {}).length})`).join('; ') || '无 memory'}`);
1895
- // 捕获检测前的 md5 map 快照,用于检测 checkMemoriesFileChanges 是否修复了键缺失
1896
- // (路径规范化不一致导致 bound_files 与 file_md5_map 键不匹配时的静默修正)
1897
- const listableMd5Snapshots = new Map();
1898
- for (const m of listableMemories) {
1899
- if (m.bound_files && m.bound_files.length > 0) {
1900
- listableMd5Snapshots.set(m.id, JSON.stringify(m.file_md5_map ?? {}));
1901
- }
1902
- }
1903
- const listableChanges = await this.checkMemoriesFileChanges(listableMemories);
1904
- const newListableChanges = listableChanges;
1905
- logger.info(`[McpServer] 文件变更检测 Step2: 检测到变更=${listableChanges.length}` +
1906
- (listableChanges.length > 0 ? ` 变更项: ${listableChanges.map(c => `${c.memoryUri}(${c.changedFiles.join(',')})`).join('; ')}` : ''));
1907
- // 回写被修复的 md5 map(键缺失修正)到服务端,避免后续轮次因键不匹配反复误判。
1908
- // 仅回写未发生真实变更的 memory(发生变更的已在下方 claim/通知流程中处理)。
1909
- const repairedListableMemories = listableMemories.filter(m => {
1910
- if (!m.bound_files || m.bound_files.length === 0)
1911
- return false;
1912
- if (listableChanges.some(c => c.memoryId === m.id))
1913
- return false;
1914
- const before = listableMd5Snapshots.get(m.id);
1915
- const after = JSON.stringify(m.file_md5_map ?? {});
1916
- return before !== after;
1917
- });
1918
- if (repairedListableMemories.length > 0) {
1919
- logger.info(`[McpServer] 文件变更检测 Step2: 回写 ${repairedListableMemories.length} 个 listable memory 的修正 MD5 map`);
1920
- for (const m of repairedListableMemories) {
1921
- try {
1922
- await this.memoryStore.updateMemory({
1923
- uri: m.uri,
1924
- memory_type: m.memory_type,
1925
- file_md5_map: m.file_md5_map,
1926
- last_check_time: m.last_check_time,
1927
- });
1928
- }
1929
- catch (e) {
1930
- logger.info(`[McpServer] 回写 listable MD5 map 失败: ${m.uri}`, e);
1931
- }
1932
- }
1933
- }
1934
- if (newListableChanges.length > 0) {
1935
- const changeDetails = newListableChanges
1936
- .map(change => `- ${change.memoryUri} (文件: ${change.changedFiles.join(', ')})`)
1937
- .join('\n');
1938
- return {
1939
- msgId: `memory-file-change-${Date.now()}`,
1940
- senderId: "MCP-System",
1941
- senderName: "系统",
1942
- receiverId: agentId,
1943
- content: `检测到以下 Memory 绑定的文件已变更,请及时更新:\n\n${changeDetails}\n\n请调用 read_memory 查看内容并调用 update_memory 更新。`,
1944
- requireReply: false,
1945
- prompt: "这是一条文件变更提醒,自动生成,无需持久化。",
1946
- timestamp: new Date().toISOString(),
1947
- _acodeAutoDelegate: {
1948
- type: 'memory_file_change',
1949
- uris: newListableChanges.map(c => c.memoryUri),
1950
- fileChanges: newListableChanges.map(c => {
1951
- const memory = listableMemories.find(m => m.id === c.memoryId);
1952
- return {
1953
- uri: c.memoryUri,
1954
- boundFiles: memory?.bound_files ?? [],
1955
- fileStatuses: c.fileStatuses,
1956
- changedFiles: c.changedFiles,
1957
- };
1958
- }),
1959
- },
1960
- };
1961
- }
1962
- // 步骤3: 检查永久记忆(角色本地存储)— 优先走 Server 原子 claim,失败时降级到本地直接标记
1963
- const permanentMemoriesWithFiles = permanentList.filter(m => m.bound_files && m.bound_files.length > 0
1964
- && (m.memory_status === 'normal' || !m.memory_status)
1965
- && !this.isClaimCached(m.uri));
1966
- const permanentTotalWithFiles = permanentList.filter(m => m.bound_files && m.bound_files.length > 0);
1967
- logger.info(`[McpServer] 文件变更检测 Step3: project_memory 总数=${permanentList.length}, ` +
1968
- `有绑定文件=${permanentTotalWithFiles.length}, 待检测(过滤updating/claimed后)=${permanentMemoriesWithFiles.length}。` +
1969
- `被过滤原因: ${permanentTotalWithFiles.length - permanentMemoriesWithFiles.length} 项(memory_status=updating 或 isClaimCached)`);
1970
- // 捕获检测前的 md5 map 快照,用于检测 checkMemoriesFileChanges 是否修复了无基线/键缺失
1971
- const permanentMd5Snapshots = new Map();
1972
- for (const m of permanentMemoriesWithFiles) {
1973
- permanentMd5Snapshots.set(m.id, JSON.stringify(m.file_md5_map ?? {}));
1974
- }
1975
- const permanentChanges = await this.checkMemoriesFileChanges(permanentMemoriesWithFiles);
1976
- // 回写被修复的 md5 map(无基线建基线或键缺失修正)到服务端。
1977
- // 通过快照对比精确识别被修正的 memory,避免每轮回写所有未变更记录。
1978
- const baselineUpdatedMemories = permanentMemoriesWithFiles.filter(m => {
1979
- if (permanentChanges.some(c => c.memoryId === m.id))
1980
- return false;
1981
- const before = permanentMd5Snapshots.get(m.id);
1982
- const after = JSON.stringify(m.file_md5_map ?? {});
1983
- return before !== after
1984
- && m.file_md5_map && Object.keys(m.file_md5_map).length > 0;
1985
- });
1986
- if (baselineUpdatedMemories.length > 0) {
1987
- logger.info(`[McpServer] 文件变更检测 Step3: 回写 ${baselineUpdatedMemories.length} 个 memory 的修正 MD5 map 到服务端(无基线/键缺失修复)`);
1988
- for (const m of baselineUpdatedMemories) {
1989
- try {
1990
- await this.memoryStore.updateMemory({
1991
- uri: m.uri,
1992
- memory_type: 'project_memory',
1993
- file_md5_map: m.file_md5_map,
1994
- last_check_time: m.last_check_time,
1995
- });
1996
- }
1997
- catch (e) {
1998
- logger.info(`[McpServer] 回写基线 MD5 失败: ${m.uri}`, e);
1999
- }
2000
- }
2001
- }
2002
- logger.info(`[McpServer] 文件变更检测 Step3: 检测到变更=${permanentChanges.length}` +
2003
- (permanentChanges.length > 0 ? ` 变更项: ${permanentChanges.map(c => `${c.memoryUri}(${c.changedFiles.join(',')})`).join('; ')}` : ''));
2004
- if (permanentChanges.length > 0) {
2005
- // 最多选取 5 个
2006
- const selectedChanges = permanentChanges.slice(0, 5);
2007
- const urisToClaim = selectedChanges.map(c => c.memoryUri);
2008
- let claimedViaServer = false;
2009
- let claimedUris = new Set();
2010
- try {
2011
- // Path A: 尝试走 Server 原子 claim
2012
- const claimResult = await this.memoryStore.claimMemoryUpdate(urisToClaim, McpServer.LEASE_DURATION_MINUTES, 'project_memory');
2013
- claimedUris = new Set(claimResult.claimed_uris ?? []);
2014
- claimedViaServer = true;
2015
- }
2016
- catch (e) {
2017
- // Path B: Server API 不可用 → 降级到本地直接标记
2018
- logger.info("[McpServer] Server claim API 不可用,降级到本地直接标记:", e);
2019
- claimedUris = new Set(urisToClaim);
2020
- }
2021
- if (claimedUris.size > 0) {
2022
- const claimedChanges = selectedChanges.filter(c => claimedUris.has(c.memoryUri));
2023
- // 标记本地 roleMemoryStore
2024
- for (const change of claimedChanges) {
2025
- const memory = permanentList.find(m => m.id === change.memoryId);
2026
- if (memory) {
2027
- // claim 状态由 batchClaimUpdate 在服务端原子写入;此处仅回写新 MD5 基线到服务端角色级 scope,
2028
- // 显式传 memory_status:updating/updating_agent_id 防止 Java updateMemory auto-release 误释放 claim。
2029
- // bound_files 同步回写:checkMemoriesFileChanges 可能已修剪掉消失的文件路径(消除悬空引用)
2030
- await this.memoryStore.updateMemory({
2031
- uri: memory.uri,
2032
- memory_type: 'project_memory',
2033
- memory_status: 'updating',
2034
- updating_agent_id: agentId,
2035
- file_md5_map: memory.file_md5_map,
2036
- last_check_time: memory.last_check_time,
2037
- bound_files: memory.bound_files,
2038
- });
2039
- this.activeLeaseUris.add(memory.uri);
2040
- }
2041
- }
2042
- // 启动租约续期(仅 Server claim 成功时需要,降级模式不续期)
2043
- if (claimedViaServer && claimedUris.size > 0) {
2044
- this.startLeaseRenewal();
2045
- }
2046
- const changeDetails = claimedChanges
2047
- .map(change => `- ${change.memoryUri} (文件: ${change.changedFiles.join(', ')})`)
2048
- .join('\n');
2049
- return {
2050
- msgId: `memory-file-change-${Date.now()}`,
2051
- senderId: "MCP-System",
2052
- senderName: "系统",
2053
- receiverId: agentId,
2054
- content: `检测到以下 ${claimedChanges.length} 个永久记忆绑定的文件已变更,已为您标记为更新中:\n\n${changeDetails}\n\n请调用 read_memory 查看内容并调用 update_memory 更新。更新完成后状态将自动恢复为正常。`,
2055
- requireReply: false,
2056
- prompt: "这是一条文件变更提醒,已自动标记更新状态,请处理。",
2057
- timestamp: new Date().toISOString(),
2058
- _acodeAutoDelegate: {
2059
- type: 'memory_file_change',
2060
- uris: claimedChanges.map(c => c.memoryUri),
2061
- fileChanges: claimedChanges.map(c => {
2062
- const memory = permanentList.find(m => m.id === c.memoryId);
2063
- return {
2064
- uri: c.memoryUri,
2065
- boundFiles: memory?.bound_files ?? [],
2066
- fileStatuses: c.fileStatuses,
2067
- changedFiles: c.changedFiles,
2068
- };
2069
- }),
2070
- },
2071
- };
2072
- }
2073
- }
2074
- logger.info('[McpServer] 文件变更检测完成: 无变更通知产生');
2075
- return null;
2076
- }
2077
- catch (error) {
2078
- logger.warn('[McpServer] 构建 Memory 文件变更检测通知失败: %s', error instanceof Error ? error.message : String(error));
2079
- return null;
2080
- }
2081
- }
2082
- isClaimCached(uri) {
2083
- return this.activeLeaseUris.has(uri);
2084
- }
2085
- /**
2086
- * 计算某条记忆全部绑定文件的逐文件状态(纯计算,不修改任何状态)。
2087
- * - no_change: 文件存在且 MD5 与基线一致(或无基线时保守视为未变)
2088
- * - content_changed: 文件存在但 MD5 与基线不一致
2089
- * - not_found: 文件读取不到(被删除/移动)
2090
- * 注意:无基线(新增绑定文件尚未建基线)的文件保守返回 no_change,避免误报为变更。
2091
- */
2092
- async computeBoundFileStatuses(memory) {
2093
- const result = [];
2094
- if (!memory.bound_files || memory.bound_files.length === 0)
2095
- return result;
2096
- const oldMd5Map = memory.file_md5_map || {};
2097
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
2098
- for (const filePath of memory.bound_files) {
2099
- try {
2100
- const currentMd5 = await this.calculateFileMd5(filePath);
2101
- if (currentMd5 === null) {
2102
- const resolvedPath = path.resolve(workspacePath, filePath);
2103
- const fileTrulyMissing = await this.verifyFileNotExist(resolvedPath);
2104
- if (fileTrulyMissing && oldMd5Map[filePath]) {
2105
- result.push({ path: filePath, status: 'not_found', serverMd5: oldMd5Map[filePath] });
2106
- }
2107
- else {
2108
- result.push({ path: filePath, status: 'no_change' });
2109
- }
2110
- continue;
2111
- }
2112
- const oldMd5 = oldMd5Map[filePath];
2113
- if (!oldMd5) {
2114
- result.push({ path: filePath, status: 'no_change' });
2115
- }
2116
- else if (oldMd5 !== currentMd5) {
2117
- result.push({ path: filePath, status: 'content_changed', localMd5: currentMd5, serverMd5: oldMd5 });
2118
- }
2119
- else {
2120
- result.push({ path: filePath, status: 'no_change' });
2121
- }
2122
- }
2123
- catch {
2124
- result.push({ path: filePath, status: 'no_change' });
2125
- }
2126
- }
2127
- return result;
2128
- }
2129
- /**
2130
- * 检查一组 Memory 的文件变更
2131
- * Memory 增强功能 (v1.6.0)
2132
- */
2133
- async checkMemoriesFileChanges(memories) {
2134
- const changes = [];
2135
- for (const memory of memories) {
2136
- if (!memory.bound_files || memory.bound_files.length === 0) {
2137
- continue;
2138
- }
2139
- try {
2140
- const changedFiles = [];
2141
- const statuses = [];
2142
- const newMd5Map = {};
2143
- const oldMd5Map = memory.file_md5_map || {};
2144
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
2145
- const hasNoBaseline = Object.keys(oldMd5Map).length === 0;
2146
- // 标记本轮是否修复了单文件基线(键缺失/空值),用于回写修正后的 map 但不计入变更
2147
- let baselineRepaired = false;
2148
- for (const filePath of memory.bound_files) {
2149
- try {
2150
- const currentMd5 = await this.calculateFileMd5(filePath);
2151
- if (currentMd5 === null) {
2152
- const resolvedPath = path.resolve(workspacePath, filePath);
2153
- logger.info(`[McpServer] MD5 计算返回 null: ${filePath} -> ${resolvedPath} (workspace=${workspacePath})`);
2154
- const oldMd5ForMissing = oldMd5Map[filePath];
2155
- if (oldMd5ForMissing) {
2156
- const fileTrulyMissing = await this.verifyFileNotExist(resolvedPath);
2157
- if (fileTrulyMissing) {
2158
- statuses.push({ path: filePath, status: 'not_found', serverMd5: oldMd5ForMissing });
2159
- changedFiles.push(`${filePath}(文件找不到 Server: ${oldMd5ForMissing})`);
2160
- if (memory.bound_files) {
2161
- memory.bound_files = memory.bound_files.filter(f => f !== filePath);
2162
- }
2163
- }
2164
- else {
2165
- logger.info(`[McpServer] 文件存在但 MD5 计算失败(瞬态错误),跳过: ${filePath}`);
2166
- statuses.push({ path: filePath, status: 'no_change' });
2167
- }
2168
- }
2169
- else {
2170
- statuses.push({ path: filePath, status: 'no_change' });
2171
- }
2172
- continue;
2173
- }
2174
- newMd5Map[filePath] = currentMd5;
2175
- const oldMd5 = oldMd5Map[filePath];
2176
- if (!oldMd5) {
2177
- statuses.push({ path: filePath, status: 'no_change' });
2178
- baselineRepaired = true;
2179
- logger.info(`[McpServer] 建立单文件基线: ${filePath} new=${currentMd5} (workspace=${workspacePath}, memory=${memory.uri})`);
2180
- }
2181
- else if (oldMd5 !== currentMd5) {
2182
- logger.info(`[McpServer] 文件变更: ${filePath} old=${oldMd5} new=${currentMd5} (workspace=${workspacePath})`);
2183
- statuses.push({ path: filePath, status: 'content_changed', localMd5: currentMd5, serverMd5: oldMd5 });
2184
- changedFiles.push(`${filePath}(内容变更 本地: ${currentMd5} Server: ${oldMd5})`);
2185
- }
2186
- else {
2187
- statuses.push({ path: filePath, status: 'no_change' });
2188
- }
2189
- }
2190
- catch (error) {
2191
- logger.info(`[McpServer] 计算文件 MD5 失败: ${filePath}`, error);
2192
- statuses.push({ path: filePath, status: 'no_change' });
2193
- }
2194
- }
2195
- // 无基线时:更新 MD5 map 到 memory 对象(供调用方回写),但不计入变更
2196
- if (hasNoBaseline && Object.keys(newMd5Map).length > 0) {
2197
- memory.file_md5_map = newMd5Map;
2198
- memory.last_check_time = new Date().toISOString();
2199
- continue; // 不 push 到 changes
2200
- }
2201
- // 键缺失修复:map 非空但部分文件缺少基线(路径规范化不一致或新增文件)。
2202
- // 回写修正后的 map 供调用方持久化,但不计入变更,避免误报。
2203
- if (baselineRepaired && Object.keys(newMd5Map).length > 0) {
2204
- memory.file_md5_map = newMd5Map;
2205
- memory.last_check_time = new Date().toISOString();
2206
- }
2207
- if (changedFiles.length > 0) {
2208
- changes.push({
2209
- memoryId: memory.id,
2210
- memoryUri: memory.uri,
2211
- fileStatuses: statuses,
2212
- changedFiles,
2213
- });
2214
- // ⚠️ 不要用 newMd5Map 覆盖 memory.file_md5_map!
2215
- // 检测阶段只负责"发现变更并通知 agent",MD5 基线必须保留为旧值,
2216
- // 让调用方把旧基线回写到 DB。否则 agent 收到变更通知后,
2217
- // 重算文件 MD5 与 DB 中已被覆盖的新基线一致,会误判"文件实际未变"。
2218
- // 新基线只能由 agent 主动调用 update_memory 处理完变更后重建。
2219
- // 仅更新检测时间戳,记录本次检查发生过。
2220
- memory.last_check_time = new Date().toISOString();
2221
- }
2222
- }
2223
- catch (error) {
2224
- logger.warn(`[McpServer] 检查 Memory ${memory.id} 文件变更失败:`, error);
2225
- }
2226
- }
2227
- return changes;
2228
- }
2229
- /**
2230
- * 计算文件 MD5
2231
- * Memory 增强功能 (v1.6.0)
2232
- */
2233
- async calculateFileMd5(filePath) {
2234
- try {
2235
- const { createReadStream } = await import('node:fs');
2236
- const { createHash } = await import('node:crypto');
2237
- // 绑定文件以工作区相对路径存储(./xxx),需相对 AWS_WORKSPACE_PATH 解析,
2238
- // 与 validateBoundFilesExist 保持一致,避免 cwd ≠ 工作区时读不到文件。
2239
- const workspacePath = process.env.AWS_WORKSPACE_PATH || process.cwd();
2240
- const resolvedPath = path.resolve(workspacePath, filePath);
2241
- // L-5: 防止路径逃逸(如 ../../../etc/passwd)
2242
- const normalizedWorkspace = path.resolve(workspacePath);
2243
- const normalizedResolved = path.resolve(resolvedPath);
2244
- if (!normalizedResolved.startsWith(normalizedWorkspace + path.sep) && normalizedResolved !== normalizedWorkspace) {
2245
- logger.warn(`[McpServer] calculateFileMd5: 路径逃逸被拒绝: ${filePath}`);
2246
- return null;
2247
- }
2248
- return new Promise((resolve) => {
2249
- const hash = createHash('md5');
2250
- const stream = createReadStream(resolvedPath);
2251
- stream.on('data', (chunk) => hash.update(chunk));
2252
- stream.on('end', () => resolve(hash.digest('hex')));
2253
- stream.on('error', () => resolve(null));
2254
- });
2255
- }
2256
- catch (error) {
2257
- logger.info(`[McpServer] 计算文件 MD5 失败: ${filePath}`, error);
2258
- return null;
2259
- }
2260
- }
2261
- async verifyFileNotExist(resolvedPath) {
2262
- try {
2263
- await fs.promises.access(resolvedPath, fs.constants.F_OK);
2264
- return false;
2265
- }
2266
- catch {
2267
- return true;
2268
- }
2269
- }
2270
- async handleGetMessage() {
2271
- // 消息来源标记:
2272
- // - buffer 在 hydrate 前已有消息 → fromBuffer=true(来自本地 buffer)
2273
- // - buffer 空、hydrate 从服务端拉到消息 → fromBuffer=false(来自服务端)
2274
- // - buffer 空、hydrate 也无消息(仅有 Memory 通知注入)→ fromBuffer=true(本地注入)
2275
- const hadBufferedMessages = this.messageBuffer.size() > 0;
2276
- await this.hydrateUnreadMessagesFromServer();
2277
- const messages = this.messageBuffer.get();
2278
- const hasRealMessages = messages.directMessages.length > 0 || messages.groupMessages.length > 0;
2279
- const fromBuffer = hadBufferedMessages || !hasRealMessages;
2280
- if (await this.prependMemoryNotification(messages, () => this.buildMemoryRecurrenceNotification())) {
2281
- return this.buildPollResult(messages, fromBuffer);
2282
- }
2283
- if (await this.prependMemoryNotification(messages, () => this.buildMemoryCleanupNotification())) {
2284
- return this.buildPollResult(messages, fromBuffer);
2285
- }
2286
- return this.buildPollResult(messages, fromBuffer);
2287
- }
2288
- /**
2289
- * 从服务端补拉未读消息并写入本地缓冲。
2290
- * 主流程:按调用方指定的阻塞/流程线任务参数请求服务端 -> 过滤本地不可投递消息 -> 追加到当前消息缓存。
2291
- */
2292
- async hydrateUnreadMessagesFromServer(options = {}) {
2293
- try {
2294
- const unreadMessages = await this.agentClient.fetchUnreadMessages({
2295
- blockIfEmpty: options.blockIfEmpty ?? false,
2296
- blockTimeoutMs: options.blockTimeoutMs,
2297
- includePipelineTasks: options.includePipelineTasks,
2298
- });
2299
- if (unreadMessages.directMessages.length === 0 &&
2300
- unreadMessages.groupMessages.length === 0) {
2301
- return true;
2302
- }
2303
- const filteredDirectMessages = unreadMessages.directMessages.filter((message) => this.shouldBufferIncomingMessage(message));
2304
- const filteredGroupMessages = unreadMessages.groupMessages.filter((message) => this.shouldBufferIncomingMessage(message));
2305
- filteredDirectMessages.forEach((message) => {
2306
- this.messageBuffer.push(message);
2307
- });
2308
- filteredGroupMessages.forEach((message) => {
2309
- this.messageBuffer.push(message);
2310
- });
2311
- logger.info(`[McpServer] 已补拉未读消息,私聊: ${filteredDirectMessages.length},群聊: ${filteredGroupMessages.length}`);
2312
- return true;
2313
- }
2314
- catch (error) {
2315
- logger.error('[McpServer] 补拉未读消息失败: %s, stack=%s', error instanceof Error ? error.message : String(error), error instanceof Error ? error.stack : '(no stack)');
2316
- return false;
2317
- }
2318
- }
2319
- async handleGetDmHistory(args) {
2320
- return await this.agentClient.getDmHistory(args.targetUserId, args.limit, args.since);
2321
- }
2322
- async handleGetGroupHistory(args) {
2323
- return await this.agentClient.getGroupHistory(args.limit, args.roomId, args.since);
2324
- }
2325
- async handleSubmitTaskResult(args) {
2326
- if (!args.nodeId || typeof args.nodeId !== "string") {
2327
- throw new Error("nodeId is required");
2328
- }
2329
- if (!args.outputPayload || typeof args.outputPayload !== "string") {
2330
- throw new Error("outputPayload is required");
2331
- }
2332
- const changeLog = Array.isArray(args.changeLog)
2333
- ? args.changeLog
2334
- : undefined;
2335
- return await this.agentClient.submitTaskResult(args.nodeId, args.outputPayload, changeLog);
2336
- }
2337
- async handleStartTaskRollback(args) {
2338
- const input = {
2339
- rollbackTaskId: this.getRequiredString(args, "rollbackTaskId"),
2340
- };
2341
- return await this.agentClient.startTaskRollback(input);
2342
- }
2343
- async handleFinishTaskRollback(args) {
2344
- const input = {
2345
- rollbackTaskId: this.getRequiredString(args, "rollbackTaskId"),
2346
- resultMessage: this.getOptionalString(args, "resultMessage"),
2347
- };
2348
- return await this.agentClient.finishTaskRollback(input);
2349
- }
2350
- async handleRejectTask(args) {
2351
- if (!args.nodeId || typeof args.nodeId !== "string") {
2352
- throw new Error("nodeId is required");
2353
- }
2354
- if (!args.reason || typeof args.reason !== "string") {
2355
- throw new Error("reason is required");
2356
- }
2357
- return await this.agentClient.rejectTask(args.nodeId, args.reason);
2358
- }
2359
- async handleTerminateTask(args) {
2360
- if (!args.taskInstanceId || typeof args.taskInstanceId !== "string") {
2361
- throw new Error("taskInstanceId is required");
2362
- }
2363
- if (!args.reason || typeof args.reason !== "string") {
2364
- throw new Error("reason is required");
2365
- }
2366
- return await this.agentClient.terminateTask(args.taskInstanceId, args.reason);
2367
- }
2368
- /**
2369
- * 处理接收到的消息(WebSocket 推送)
2370
- */
2371
- async handleIncomingMessage(message) {
2372
- if (!this.shouldBufferIncomingMessage(message)) {
2373
- // pipeline_task_assigned 不进 buffer,但需唤醒 poll 走 hydrate 重查任务,
2374
- // 否则 poll_message 会被 messageBuffer.poll(30s) 阻塞,task 流转最长延迟 30s。
2375
- if (!this.isGroupMessage(message) && this.isPipelineTaskAssignedNotification(message)) {
2376
- logger.info("[McpServer] 收到 pipeline_task_assigned 唤醒信号,立即唤醒 poll 走 hydrate 重查");
2377
- this.messageBuffer.wakeUpWaiters();
2378
- }
2379
- return undefined;
2380
- }
2381
- logger.info(`[McpServer] 收到消息: ${message.content?.substring(0, 50) || "(无内容)"}`);
2382
- this.messageBuffer.push(message);
2383
- return undefined;
2384
- }
2385
- /**
2386
- * 判断消息是否应该进入本地缓冲区。
2387
- * 主流程:过滤结构异常消息 -> 过滤当前 Agent 自发消息 -> 过滤持久化流程线唤醒私信。
2388
- */
2389
- shouldBufferIncomingMessage(message) {
2390
- if (!message || typeof message !== "object") {
2391
- logger.warn("[McpServer] 忽略结构异常的消息,避免缓冲区污染");
2392
- return false;
2393
- }
2394
- if (this.isSelfSentMessage(message)) {
2395
- logger.info("[McpServer] 忽略当前 Agent 自己发送的消息,避免 poll_message/get_message 回环");
2396
- return false;
2397
- }
2398
- if (!this.isGroupMessage(message)) {
2399
- if (this.isPipelineTaskAssignedNotification(message)) {
2400
- logger.info("[McpServer] 忽略持久化 pipeline_task_assigned 私信,仅作为 poll_message 唤醒信号处理");
2401
- return false;
2402
- }
2403
- return true;
2404
- }
2405
- return true;
2406
- }
2407
- /**
2408
- * 判断是否为群消息。
2409
- * 通过 msgId 类型进行收窄,便于后续安全执行群消息专属过滤逻辑。
2410
- */
2411
- isGroupMessage(message) {
2412
- return !!message && typeof message === "object" && typeof message.msgId === "number";
2413
- }
2414
- /**
2415
- * 判断是否为持久化流程线任务待领取私信。
2416
- * 主流程:同时检查 content 与 prompt,只要包含 pipeline_task_assigned 就不进入本地缓冲,避免未抢占成功 Agent 收到系统任务提醒。
2417
- */
2418
- isPipelineTaskAssignedNotification(message) {
2419
- const content = typeof message.content === "string" ? message.content : "";
2420
- const prompt = typeof message.prompt === "string" ? message.prompt : "";
2421
- return (content.includes("pipeline_task_assigned") ||
2422
- prompt.includes("pipeline_task_assigned"));
2423
- }
2424
- /**
2425
- * 判断消息是否由当前 Agent 自己发送。
2426
- * 主流程:读取当前 Agent 状态 -> 比较 senderId -> 返回过滤结果。
2427
- */
2428
- isSelfSentMessage(message) {
2429
- if (!message || typeof message !== "object") {
2430
- return false;
2431
- }
2432
- const currentAgentId = this.getCurrentAgentId();
2433
- return (!!currentAgentId &&
2434
- typeof message.senderId === "string" &&
2435
- message.senderId === currentAgentId);
2436
- }
2437
- /**
2438
- * 安全读取当前 Agent ID。
2439
- * 测试替身或异常场景下如果 agentClient 未提供 getState,则返回 null,避免影响消息主流程。
2440
- */
2441
- getCurrentAgentId() {
2442
- const maybeAgentClient = this.agentClient;
2443
- if (typeof maybeAgentClient.getState !== "function") {
2444
- return null;
2445
- }
2446
- return maybeAgentClient.getState().agentId ?? null;
2447
- }
2448
- /**
2449
- * 将 agentId 安全化为文件名片段:仅保留字母数字下划线短横,其余替换为下划线。
2450
- * 与 acode 包 TodoStore.filePath 的路径安全化逻辑保持一致。
2451
- */
2452
- sanitizeAgentIdForTodoFile(agentId) {
2453
- return String(agentId).replace(/[^a-zA-Z0-9_-]/g, "_");
2454
- }
2455
- /**
2456
- * 构建 Todo 未完成项提醒通知。
2457
- *
2458
- * 触发条件(全部满足):
2459
- * 1. 当前 agentId 可读取
2460
- * 2. ~/.acode/todos/{agentId}.json 存在且可解析
2461
- * 3. 存在 status !== "completed" 的未完成项
2462
- *
2463
- * 行为:
2464
- * - 有未完成项:构造轻量系统通知并返回(不持久化)。
2465
- * - 全部完成 / 无文件 / 异常:静默返回 null,不阻断 poll_message。
2466
- *
2467
- * 通知以 DirectMessage 形式注入,senderId="MCP-System",不落库、不持久化。
2468
- * 提示内容简洁,仅提醒有未完成待办,Agent 如需查看详情应调用 todo_read。
2469
- */
2470
- async buildTodoReminderNotification() {
2471
- try {
2472
- const agentId = this.getCurrentAgentId();
2473
- if (!agentId)
2474
- return null;
2475
- const todoPath = path.join(os.homedir(), ".acode", "todos", `${this.sanitizeAgentIdForTodoFile(agentId)}.json`);
2476
- let raw;
2477
- try {
2478
- raw = await fs.promises.readFile(todoPath, "utf-8");
2479
- }
2480
- catch (error) {
2481
- // 文件不存在视为无 todo,不通知
2482
- if (error?.code === "ENOENT")
2483
- return null;
2484
- throw error;
2485
- }
2486
- const doc = JSON.parse(raw);
2487
- const todos = Array.isArray(doc.todos)
2488
- ? doc.todos
2489
- : [];
2490
- const incomplete = todos.filter((t) => t &&
2491
- typeof t === "object" &&
2492
- typeof t.content === "string" &&
2493
- (t.status === "pending" ||
2494
- t.status === "in_progress" ||
2495
- t.status === "cancelled"));
2496
- if (incomplete.length === 0) {
2497
- return null;
2498
- }
2499
- return {
2500
- msgId: `todo-reminder-${Date.now()}`,
2501
- senderId: "MCP-System",
2502
- senderName: "系统",
2503
- receiverId: agentId,
2504
- content: `Todo 列表存在 ${incomplete.length} 项未完成,请调用 todo_read 查看并处理。`,
2505
- requireReply: false,
2506
- prompt: "这是一条 Todo 未完成项提醒,自动生成,无需持久化。请评估是否需要继续处理未完成的 Todo,可调用 todo_read 获取完整列表。",
2507
- timestamp: new Date().toISOString(),
2508
- };
2509
- }
2510
- catch (error) {
2511
- logger.warn('[McpServer] 构建 Todo 提醒失败: %s', error instanceof Error ? error.message : String(error));
2512
- return null;
2513
- }
2514
- }
2515
- // ============ 生命周期 ============
2516
- async start() {
2517
- const transport = new StdioServerTransport();
2518
- await this.server.connect(transport);
2519
- await this.agentClient.initialize();
2520
- logger.info("[McpServer] MCP Server 已启动,等待连接...");
2521
- logger.info(`[McpServer] 可用工具: ${MCP_TOOLS.map((t) => t.name).join(", ")}`);
2522
- logger.info(`[McpServer] 工作目录: ${process.env.AWS_WORKSPACE_PATH || process.cwd()}`);
2523
- // 检查降级模式配置
2524
- if (DEGRADED_MODE_ENABLED) {
2525
- logger.info(`[McpServer] 降级模式已启用,注册失败时将以离线模式继续运行`);
2526
- }
2527
- // v1.13.0: 不再加载本地元数据缓存,过期/TODO/回忆状态统一由服务端 memory_status 字段管理
2528
- // 后台注册:不阻塞 MCP 工具暴露,Agent 可立即接收工具调用
2529
- this.autoRegisterInBackground();
2530
- logger.info("[McpServer] MCP Server 已就绪,工具已可用");
2531
- }
2532
- /**
2533
- * 启动租约续期定时器 (v1.21.0)。
2534
- * 每 60 秒续期一次当前 agent 持有的所有 updating memory 的租约,
2535
- * 防止租约过期导致其他实例接管。
2536
- */
2537
- startLeaseRenewal() {
2538
- if (this.leaseRenewalTimer)
2539
- return;
2540
- this.leaseRenewalTimer = setInterval(async () => {
2541
- try {
2542
- const agentId = this.getCurrentAgentId();
2543
- if (!agentId)
2544
- return;
2545
- const result = await this.memoryStore.renewMemoryLease(McpServer.LEASE_DURATION_MINUTES);
2546
- // 续期返回 0 说明租约已丢失(被过期清理或被其他 agent 接管),停止续期
2547
- if (result.renewed === 0) {
2548
- logger.warn("[McpServer] 租约已丢失(renewed=0),停止续期定时器");
2549
- this.stopLeaseRenewal();
2550
- }
2551
- }
2552
- catch (e) {
2553
- logger.warn('[McpServer] 续期租约失败: %s', e instanceof Error ? e.message : String(e));
2554
- }
2555
- }, McpServer.LEASE_RENEW_INTERVAL_MS);
2556
- logger.info("[McpServer] 租约续期定时器已启动,间隔: 60s");
2557
- }
2558
- /**
2559
- * 停止租约续期定时器 (v1.21.0)。
2560
- */
2561
- stopLeaseRenewal() {
2562
- if (this.leaseRenewalTimer) {
2563
- clearInterval(this.leaseRenewalTimer);
2564
- this.leaseRenewalTimer = null;
2565
- logger.info("[McpServer] 租约续期定时器已停止");
2566
- }
2567
- }
2568
- /**
2569
- * 停止 McpServer,清理后台定时器。
2570
- * 主流程:停止租约续期 -> 释放所有 claim -> 停止 Agent 客户端消息循环。
2571
- */
2572
- stop() {
2573
- this.stopLeaseRenewal();
2574
- // 释放所有持有的 claim
2575
- const agentId = this.getCurrentAgentId();
2576
- if (agentId) {
2577
- this.memoryStore.releaseAllMemoryClaims().catch(e => logger.warn('[McpServer] 释放 claim 失败: %s', e instanceof Error ? e.message : String(e)));
2578
- }
2579
- this.agentClient.stop();
2580
- logger.info("[McpServer] MCP Server 已停止");
2581
- }
2582
- // ============ 记忆统一状态字段 (v1.13.0) ============
2583
- // v1.13.0 改用服务端 memory_status 字段管理 agent_memory 过期/回忆状态,
2584
- // 下方工具直接调用服务端接口,本地不再维护元数据缓存。
2585
- // buildMemoryRecurrenceNotification / handleMemoryRecurrence / handleMemoryRecurrenceComplete
2586
- // 已在文件其他位置实现(直接使用 this.memoryStore.listExpiredRecentlyMemories / markExpiredMemoriesRecalling / clearRecallingExpiredMemories)。
2587
- async autoRegister() {
2588
- const agentConfig = this.agentClient.getAgentConfig();
2589
- logger.info(`[McpServer] 角色: "${agentConfig.roleName}"`);
2590
- logger.info(`[McpServer] AgentID: "${agentConfig.agentId || "(自动分配)"}"`);
2591
- logger.info("[McpServer] 正在请求调度中心注册...");
2592
- let retryCount = 0;
2593
- while (true) {
2594
- try {
2595
- const result = await this.agentClient.register();
2596
- logger.info(`[McpServer] 注册成功: ${result.displayName} (${result.agentId})`);
2597
- logger.info(`[McpServer] 状态: ${result.status}`);
2598
- // 注册成功后自动启动消息监听
2599
- this.startListeningInternal();
2600
- logger.info("[McpServer] 消息监听已自动启动");
2601
- // 报告 Agent 已启动并等待输入
2602
- const statusReporter = this.agentClient.getStatusReporter();
2603
- if (statusReporter) {
2604
- statusReporter.reportWaitingInput();
2605
- }
2606
- await this.reportMemoryStatsToServer();
2607
- // 注册成功后检查是否需要从本地恢复永久记忆到 server
2608
- // 触发条件:server 当前角色 memory 计数为 0(重部署后 DB 为空)
2609
- // 不依赖 isExisting / profileHandler,确保所有场景都能触发
2610
- this.maybeSyncPermanentMemoryToServer();
2611
- return;
2612
- }
2613
- catch (error) {
2614
- const errorMessage = error instanceof Error ? error.message : String(error);
2615
- retryCount++;
2616
- // 检查是否达到最大重试次数
2617
- if (REGISTER_MAX_RETRIES > 0 && retryCount >= REGISTER_MAX_RETRIES) {
2618
- // 如果启用了降级模式,进入降级模式而不是抛出异常
2619
- if (DEGRADED_MODE_ENABLED) {
2620
- logger.warn(`[McpServer] 注册超过最大重试次数 (${REGISTER_MAX_RETRIES}次),进入降级模式`);
2621
- this.agentClient.enterDegradedMode();
2622
- // 启动消息监听(降级模式下仍可接收本地消息)
2623
- this.startListeningInternal();
2624
- logger.info("[McpServer] 降级模式:消息监听已启动");
2625
- // 启动后台重试注册
2626
- this.agentClient.retryRegistrationInBackground(DEGRADED_MODE_RETRY_INTERVAL_MS, DEGRADED_MODE_MAX_RETRIES);
2627
- // 报告 Agent 已启动(降级模式)
2628
- const statusReporter = this.agentClient.getStatusReporter();
2629
- if (statusReporter) {
2630
- statusReporter.reportWaitingInput();
2631
- }
2632
- await this.reportMemoryStatsToServer();
2633
- return;
2634
- }
2635
- // 未启用降级模式,抛出异常
2636
- throw new Error(`注册超过最大重试次数 (${REGISTER_MAX_RETRIES}次),放弃启动: ${errorMessage}`);
2637
- }
2638
- logger.warn(`[McpServer] 注册失败,等待重试... (${retryCount}/${REGISTER_MAX_RETRIES}): ${errorMessage}`);
2639
- await this.delay(REGISTER_RETRY_INTERVAL_MS);
2640
- }
2641
- }
2642
- }
2643
- /**
2644
- * 在后台启动注册流程,不阻塞 MCP 工具暴露。
2645
- * 主流程:autoRegister 包含重试循环和降级模式逻辑,后台执行避免拖慢 MCP Server 就绪时间。
2646
- */
2647
- autoRegisterInBackground() {
2648
- this.autoRegister().catch((error) => {
2649
- logger.error('[McpServer] 后台注册异常: %s, stack=%s', error instanceof Error ? error.message : String(error), error instanceof Error ? error.stack : '(no stack)');
2650
- });
2651
- }
2652
- /**
2653
- * 内部方法:启动消息监听(不返回 MCP 响应)
2654
- */
2655
- startListeningInternal() {
2656
- logger.info("[McpServer] startListeningInternal 被调用");
2657
- const profileHandler = (profile) => {
2658
- // 收到 Profile 信息后,存储到消息缓冲区
2659
- logger.info(`[McpServer] profileHandler 被调用,displayName: ${profile.displayName}`);
2660
- this.messageBuffer.setProfileMessage(profile);
2661
- this.profileSynced = true;
2662
- this.syncRoleMemoryStore(profile);
2663
- this.maybeSyncPermanentMemoryToServer();
2664
- };
2665
- // 降级模式下跳过 WebSocket 连接
2666
- if (this.agentClient.isInDegradedMode()) {
2667
- logger.info("[McpServer] 降级模式:跳过 WebSocket 连接,仅启动本地消息监听");
2668
- this.agentClient.start(this.handleIncomingMessage.bind(this), {
2669
- pollMode: false,
2670
- profileHandler,
2671
- });
2672
- this.activelySyncProfile();
2673
- return;
2674
- }
2675
- this.agentClient.start(this.handleIncomingMessage.bind(this), {
2676
- pollMode: false,
2677
- profileHandler,
2678
- });
2679
- // 注册时 getProfileByAgentId 会触发 Java markProfileFetched,导致 WebSocket 连接后
2680
- // Java 不再推送 PROFILE_INFO 消息(AgentWebSocketHandler 检查 hasProfileFetched 跳过)。
2681
- // 因此 profileHandler 可能永远不会被触发,需要主动获取 profile 同步 hasRoleName。
2682
- this.activelySyncProfile();
2683
- }
2684
- /**
2685
- * 主动获取 profile 并同步角色存储路径。
2686
- * 当 Java 侧因 hasProfileFetched=true 跳过 PROFILE_INFO 推送时,
2687
- * profileHandler 不会被调用,hasRoleName 永远为 false,导致文件变更检测被跳过。
2688
- * 此方法在 startListeningInternal 后主动拉取 profile,确保 hasRoleName 被正确设置。
2689
- */
2690
- activelySyncProfile() {
2691
- if (this.profileSynced)
2692
- return;
2693
- this.agentClient.getProfile().then((profile) => {
2694
- if (this.profileSynced)
2695
- return;
2696
- this.profileSynced = true;
2697
- logger.info(`[McpServer] 主动获取 profile 成功: displayName=${profile.displayName}, roleName="${profile.roleName ?? '(空)'}"`);
2698
- this.messageBuffer.setProfileMessage(profile);
2699
- this.syncRoleMemoryStore(profile);
2700
- this.maybeSyncPermanentMemoryToServer();
2701
- }).catch((e) => {
2702
- logger.warn('[McpServer] 主动获取 profile 失败: %s', e instanceof Error ? e.message : String(e));
2703
- });
2704
- }
2705
- delay(ms) {
2706
- return new Promise((resolve) => setTimeout(resolve, ms));
2707
- }
2708
- }
2709
- //# sourceMappingURL=mcp-server.js.map
1
+ import $ from"node:fs";import U from"node:os";import g from"node:path";import{Server as B}from"@modelcontextprotocol/sdk/server/index.js";import{StdioServerTransport as x}from"@modelcontextprotocol/sdk/server/stdio.js";import{CallToolRequestSchema as q,ListToolsRequestSchema as W}from"@modelcontextprotocol/sdk/types.js";import{AgentClient as H}from"./agent-client.js";import{MCP_SERVER_NAME as z,MCP_SERVER_VERSION as G,REGISTER_MAX_RETRIES as C,REGISTER_RETRY_INTERVAL_MS as Y,DEGRADED_MODE_ENABLED as j,DEGRADED_MODE_RETRY_INTERVAL_MS as K,DEGRADED_MODE_MAX_RETRIES as J}from"./constants.js";import{logger as a}from"./logger.js";import{MCP_TOOLS as O,MEMORY_TOOL_NAMES as F}from"./mcp-tools.js";import{MessageBuffer as V}from"./message-buffer.js";import{ContextManager as X}from"./context-manager.js";import{MemoryCapacityError as Q,MemoryStore as N}from"./memory-store.js";import{ServerMemoryStore as Z}from"./server-memory-store.js";const fe=12e4,ee=3e4,te=new Set(["<",">",":",'"',"/","\\","|","?","*"]);class v{server;agentClient;messageBuffer;memoryStore;roleMemoryStore;sessionContextMemoryStore;initialRoleName="";hasRoleName=!1;contextManager;isSubAgent;leaseRenewalTimer=null;activeLeaseUris=new Set;static LEASE_DURATION_MINUTES=10;static LEASE_RENEW_INTERVAL_MS=6e4;profileSynced=!1;pendingPollMessage=null;syncRoleMemoryStore(e){if(a.info(`[McpServer] syncRoleMemoryStore \u88AB\u8C03\u7528: displayName=${e.displayName}, roleName="${e.roleName??"(\u7A7A)"}", id=${e.id}`),!e.roleName){a.warn(`[McpServer] syncRoleMemoryStore: profile.roleName \u4E3A\u7A7A\uFF0ChasRoleName \u4FDD\u6301 false\u3002profile=${JSON.stringify({id:e.id,displayName:e.displayName,roleName:e.roleName,instanceName:e.instanceName})}`);return}const t=v.sanitizeRoleDirectoryName(e.roleName);this.hasRoleName=!0,t!==this.initialRoleName&&(a.info(`[McpServer] \u89D2\u8272\u76EE\u5F55\u4E0E\u521D\u59CB\u503C\u4E0D\u4E00\u81F4\uFF0C\u91CD\u5EFA roleMemoryStore: "${this.initialRoleName}" -> "${t}"`),this.roleMemoryStore=new N(v.buildRoleMemoryStorePath(t)),this.initialRoleName=t)}maybeSyncPermanentMemoryToServer(){this.doMaybeSyncPermanentMemoryToServer().catch(e=>{a.warn("[McpServer] maybeSyncPermanentMemoryToServer \u5F02\u5E38: %s, stack=%s",e instanceof Error?e.message:String(e),e instanceof Error?e.stack:"(no stack)")})}async doMaybeSyncPermanentMemoryToServer(){a.info("[McpServer] maybeSyncPermanentMemoryToServer \u5F00\u59CB\uFF0CinitialRoleName="+this.initialRoleName);let e;try{e=await this.memoryStore.getStats()}catch(s){a.warn("[McpServer] \u83B7\u53D6 server \u8BB0\u5FC6\u7EDF\u8BA1\u5931\u8D25\uFF0C\u8DF3\u8FC7\u540C\u6B65:",s instanceof Error?s.message:s);return}const t=e.by_type.project_memory?.count??0;if(a.info(`[McpServer] server memory \u8BA1\u6570=${t}\uFF0C\u662F\u5426\u9700\u8981\u540C\u6B65=${t===0}`),t>0)return;let r=[];try{r=await this.roleMemoryStore.listByType("project_memory"),a.info(`[McpServer] \u89D2\u8272\u5B50\u76EE\u5F55\u8BFB\u53D6\u5230 ${r.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`)}catch(s){a.warn("[McpServer] \u8BFB\u53D6\u89D2\u8272\u5B50\u76EE\u5F55\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25:",s instanceof Error?s.message:s)}if(r.length===0){const s=g.dirname(v.buildRoleMemoryStorePath(this.initialRoleName));a.info(`[McpServer] \u5C1D\u8BD5\u4ECE\u9057\u7559\u76EE\u5F55\u8BFB\u53D6: ${s}`);try{const l=await new N(s).listByType("project_memory");l.length>0&&(a.info(`[McpServer] \u4ECE\u9057\u7559\u76EE\u5F55\u8BFB\u53D6\u5230 ${l.length} \u6761\u6C38\u4E45\u8BB0\u5FC6`),r=l)}catch(o){a.warn("[McpServer] \u8BFB\u53D6\u9057\u7559\u76EE\u5F55\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25:",o instanceof Error?o.message:o)}}if(r.length===0){a.info("[McpServer] \u672C\u5730\u4E5F\u65E0\u6C38\u4E45\u8BB0\u5FC6\uFF0C\u8DF3\u8FC7\u540C\u6B65");return}let n=0,i=0;for(const s of r)try{await this.memoryStore.createMemory({memory_type:"project_memory",uri:s.uri,title:s.title,content:s.content,keywords:s.keywords,bound_files:s.bound_files,file_md5_map:s.file_md5_map,last_check_time:s.last_check_time}),n++,a.info(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u6210\u529F: ${s.uri}`)}catch(o){const l=o instanceof Error?o.message:String(o);l.includes("already exists")||l.includes("Memory already exists")||a.warn(`[McpServer] \u540C\u6B65\u6C38\u4E45\u8BB0\u5FC6\u5931\u8D25 (${s.uri}): ${l}`),i++}a.info(`[McpServer] \u6C38\u4E45\u8BB0\u5FC6\u540C\u6B65\u5B8C\u6210: \u6210\u529F ${n}, \u8DF3\u8FC7 ${i}, \u5171 ${r.length}`),await this.reportMemoryStatsToServer()}normalizeBoundFilePath(e){const t=String(e||"").trim().replace(/\\/g,"/");if(!t)return t;const r=t.startsWith("@/")?t.substring(1):t;return r.startsWith("./")?r:`./${r.replace(/^\/+/,"")}`}normalizeBoundFiles(e){return!e||!Array.isArray(e)?e:e.map(t=>this.normalizeBoundFilePath(String(t)))}async validateBoundFilesExist(e){if(!e||e.length===0)return;const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),{access:r}=await import("node:fs/promises"),n=[];for(const i of e)try{const s=g.resolve(t,i);await r(s)}catch{n.push(i)}if(n.length>0)throw new Error(`\u7ED1\u5B9A\u7684\u6587\u4EF6\u4E0D\u5B58\u5728\uFF0C\u8BF7\u786E\u8BA4\u6587\u4EF6\u8DEF\u5F84\u6B63\u786E\u540E\u518D\u91CD\u65B0\u63D0\u4EA4:
2
+ ${n.map(i=>` - ${i}`).join(`
3
+ `)}`)}toMemoryWriteResult(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files}}toMemoryRecordWithMetadata(e){return{id:e.id,uri:e.uri,domain:e.domain,path:e.path,title:e.title,content:e.content,memory_type:e.memory_type,createdAt:e.createdAt,updatedAt:e.updatedAt,version:e.version,keywords:e.keywords,bound_files:e.bound_files,file_md5_map:e.file_md5_map,memory_status:e.memory_status,updating_agent_id:e.updating_agent_id,last_check_time:e.last_check_time}}constructor(){this.agentClient=new H,this.messageBuffer=new V,this.initialRoleName=v.resolveRoleMemoryDirectoryName(),this.hasRoleName=!!process.env.AWS_ROLE_NAME;const e=v.resolveMemoryStorePaths();this.memoryStore=new Z(this.agentClient),this.roleMemoryStore=new N(e.rolePath),this.sessionContextMemoryStore=new N(g.join(g.dirname(e.rolePath),"..","session_context")),this.isSubAgent=process.env.AWS_SUB_AGENT==="true",this.contextManager=new X(this.sessionContextMemoryStore),this.server=new B({name:z,version:G},{capabilities:{tools:{}}}),this.setupHandlers()}setupHandlers(){this.server.setRequestHandler(W,async()=>({tools:this.isSubAgent?O.filter(t=>F.has(t.name)):O})),this.server.setRequestHandler(q,async e=>{const{name:t,arguments:r}=e.params;try{const n=await this.handleToolCall(t,r);return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){const i=n instanceof Error?n.message:String(n),s=n instanceof Q?{error:i,...n.details}:{error:i};return a.error("[McpServer] \u5DE5\u5177\u8C03\u7528\u5F02\u5E38: tool=%s, error=%s, stack=%s",t,i,n instanceof Error?n.stack:"(no stack)"),{content:[{type:"text",text:JSON.stringify(s,null,2)}],isError:!0}}})}async handleToolCall(e,t){const r=t?._aws_sub_agent===!0,n=t?._aws_silent_poll===!0,i={...t};delete i._aws_sub_agent,delete i._aws_silent_poll;const s=r?null:this.agentClient.getStatusReporter();s&&!n&&s.reportToolUse(e,this.sanitizeToolUseArgs(e,i));try{const o=await this.executeToolCall(e,i);if(s&&!n){const l=this.stripAutoDelegateForReporting(o);l!==null&&(s.reportToolResult(l),s.reportThinking())}return o}catch(o){throw s&&!n&&s.reportToolError(o),o}}async executeToolCall(e,t){if(this.isSubAgent&&!F.has(e))throw new Error(`Sub-agent is not allowed to call tool: ${e}`);switch(e){case"get_colleague":return await this.agentClient.discoverColleagues();case"get_group_rooms":return await this.agentClient.discoverGroupRooms();case"get_profile":return await this.agentClient.getProfile();case"hire_colleague":return await this.handleHireColleague(t);case"hire_colleagues_batch":return await this.handleHireColleaguesBatch(t);case"send_group":return await this.handleSendGroup(t);case"send_private":return await this.handleSendPrivate(t);case"poll_message":return await this.handlePollMessage();case"get_message":return this.handleGetMessage();case"get_dm_history":return await this.handleGetDmHistory(t);case"get_group_history":return await this.handleGetGroupHistory(t);case"my_task":return await this.agentClient.getMyTasks();case"create_task_instance_comment":return await this.handleCreateTaskInstanceComment(t);case"submit_task_result":return await this.handleSubmitTaskResult(t);case"start_task_rollback":return await this.handleStartTaskRollback(t);case"finish_task_rollback":return await this.handleFinishTaskRollback(t);case"reject_task":return await this.handleRejectTask(t);case"terminate_task":return await this.handleTerminateTask(t);case"create_memory":return await this.handleCreateMemory(t);case"read_memory":return await this.handleReadMemory(t);case"update_memory":return await this.handleUpdateMemory(t);case"delete_memory":return await this.handleDeleteMemory(t);case"search_memory":return await this.handleSearchMemory(t);case"list_memories":return await this.handleListMemories(t);case"get_memory_stats":return await this.handleGetMemoryStats(t);case"list_memories_by_file":return await this.handleListMemoriesByFile(t);case"abandon_memory_update":return await this.handleAbandonMemoryUpdate(t);case"memory_recurrence":return await this.handleMemoryRecurrence(t);case"memory_recurrence_complete":return await this.handleMemoryRecurrenceComplete();default:throw new Error(`Unknown tool: ${e}`)}}static MEMORY_TYPES=["recently_memory","agent_memory","project_memory","session_context"];static MEMORY_TOOL_NAMES=new Set(["create_memory","read_memory","update_memory","delete_memory","search_memory","list_memories","get_memory_stats","list_memories_by_file","abandon_memory_update","memory_recurrence","memory_recurrence_complete"]);static findAgentsworkDirSync(e){let t=g.resolve(e);for(;;){if($.existsSync(g.join(t,".agentswork")))return g.join(t,".agentswork");const r=g.dirname(t);if(r===t)break;t=r}return g.join(g.resolve(e),".agentswork")}static sanitizeRoleDirectoryName(e){return String(e||"").trim().split("").map(r=>te.has(r)||r.charCodeAt(0)<32?"_":r).join("").replace(/^\.+|\.+$/g,"").trim()||"default-role"}static resolveRoleMemoryDirectoryName(){return v.sanitizeRoleDirectoryName(process.env.AWS_ROLE_NAME||"")}static buildRoleMemoryStorePath(e){const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t);return g.join(r,"memory","memory",v.sanitizeRoleDirectoryName(e))}static resolveMemoryStorePaths(){const e=String(process.env.AWS_MEMORY_STORE_PATH||"").trim();if(e)return{instancePath:e,rolePath:e};const t=process.env.AWS_WORKSPACE_PATH||process.cwd(),r=v.findAgentsworkDirSync(t),n=String(process.env.AWS_INSTANCE_MEMORY_STORE_PATH||"").trim()||g.join(r,"memory","server-managed"),i=String(process.env.AWS_ROLE_MEMORY_STORE_PATH||"").trim()||g.join(r,"memory","memory",v.resolveRoleMemoryDirectoryName());return{instancePath:n,rolePath:i}}getRequiredString(e,t){const r=e[t];if(typeof r!="string"||r.trim().length===0)throw new Error(`${t} is required`);return r}getOptionalString(e,t){const r=e[t];return typeof r=="string"?r:void 0}getOptionalNumber(e,t){const r=e[t];return typeof r=="number"?r:void 0}getOptionalStringArray(e,t){const r=e[t];return Array.isArray(r)?r.map(n=>String(n)):void 0}getOptionalMemoryType(e){const t=e.memory_type;if(t!==void 0)return this.parseMemoryType(t)}parseMemoryType(e){if(typeof e!="string")throw new Error(`Invalid memory_type: ${String(e)}`);const t=e.trim();if(!this.isMemoryType(t))throw new Error(`Invalid memory_type: ${e}`);return t}isMemoryType(e){return v.MEMORY_TYPES.some(t=>t===e)}getMemoryStoreForType(e){return this.memoryStore}async readMemoryFromScopedStores(e){if(e.memory_type)return await this.memoryStore.readMemory(e);const t=await this.memoryStore.readMemory({...e,memory_type:"project_memory"}).catch(()=>null);return t||await this.memoryStore.readMemory(e)}async backupProjectMemoryToLocal(e){try{const t=await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"});t&&(await this.roleMemoryStore.deleteMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>{}),await this.roleMemoryStore.createMemory(t))}catch(t){a.warn("[McpServer] backupProjectMemoryToLocal \u5931\u8D25: uri=%s",e.uri,t)}}async removeProjectMemoryLocalBackup(e){try{await this.roleMemoryStore.deleteMemory({uri:e,memory_type:"project_memory"})}catch(t){a.warn("[McpServer] removeProjectMemoryLocalBackup \u5931\u8D25: uri=%s",e,t)}}async findMemoryStoreForMutation(e){return e.memory_type?{store:this.memoryStore,memory_type:e.memory_type}:await this.memoryStore.readMemory({uri:e.uri,memory_type:"project_memory"}).catch(()=>null)?{store:this.memoryStore,memory_type:"project_memory"}:{store:this.memoryStore}}async reportMemoryStatsToServer(){const e=await this.memoryStore.getStats();try{await this.agentClient.reportMemoryStats(e)}catch(t){a.warn("[McpServer] \u4E0A\u62A5 memory \u7EDF\u8BA1\u5931\u8D25:",t)}}sanitizeToolUseArgs(e,t){if(!v.MEMORY_TOOL_NAMES.has(e))return t;const r={},n=this.getOptionalString(t,"uri"),i=this.getOptionalString(t,"memory_type"),s=this.getOptionalNumber(t,"limit");return n&&(r.uri=n),i&&(r.memory_type=i),typeof s=="number"&&(r.limit=s),e==="search_memory"&&(r.queryProvided=typeof t.query=="string"&&t.query.length>0,t.keyword_mode!==void 0&&(r.keyword_mode=t.keyword_mode)),r}stripAutoDelegateForReporting(e){if(!e||typeof e!="object")return e;const t=e,r=Array.isArray(t.directMessages)?t.directMessages:[],n=Array.isArray(t.groupMessages)?t.groupMessages:[],i=l=>!!l&&typeof l=="object"&&"_acodeAutoDelegate"in l;if(!r.some(i))return e;const o=r.filter(l=>!i(l));return o.length===0&&n.length===0?null:{...t,directMessages:o}}async handleCreateMemory(e){const t=this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"));await this.validateBoundFilesExist(t);let r,n;if(t&&t.length>0){r={};for(const o of t){const l=await this.calculateFileMd5(o);l&&(r[o]=l)}n=new Date().toISOString()}const i={memory_type:this.parseMemoryType(this.getRequiredString(e,"memory_type")),uri:this.getRequiredString(e,"uri"),title:this.getOptionalString(e,"title"),content:this.getRequiredString(e,"content"),keywords:this.getOptionalStringArray(e,"keywords"),bound_files:t,file_md5_map:r,last_check_time:n},s=await this.memoryStore.createMemory(i);return i.memory_type==="project_memory"&&this.backupProjectMemoryToLocal(i).catch(o=>{a.warn("[McpServer] \u5907\u4EFD project_memory \u5230\u672C\u5730\u5931\u8D25:",o)}),await this.reportMemoryStatsToServer(),this.toMemoryWriteResult(s)}async handleReadMemory(e){const t={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e)};return await this.readMemoryFromScopedStores(t)}async handleUpdateMemory(e){const t=typeof e.old_string=="string"||typeof e.new_string=="string",r=typeof e.old_string=="string"&&typeof e.new_string=="string",n=this.getOptionalString(e,"append");if(t&&!r)throw new Error("old_string and new_string must be provided together");const i={uri:this.getRequiredString(e,"uri"),memory_type:this.getOptionalMemoryType(e),title:this.getOptionalString(e,"title"),old_string:this.getOptionalString(e,"old_string"),new_string:this.getOptionalString(e,"new_string"),append:n,keywords:this.getOptionalStringArray(e,"keywords"),bound_files:this.normalizeBoundFiles(this.getOptionalStringArray(e,"bound_files"))},s=await this.findMemoryStoreForMutation(i),o=await s.store.readMemory({uri:i.uri,memory_type:s.memory_type??i.memory_type});i.bound_files!==void 0?await this.validateBoundFilesExist(i.bound_files):o?.bound_files&&o.bound_files.length>0&&await this.validateBoundFilesExist(o.bound_files);const l=i.bound_files??o?.bound_files;let m,y;if(l&&l.length>0){m={};for(const u of l){const S=await this.calculateFileMd5(u);S&&(m[u]=S)}y=new Date().toISOString()}const h=await s.store.updateMemory({...i,memory_type:s.memory_type??i.memory_type,file_md5_map:m,last_check_time:y});if(s.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({...i,memory_type:"project_memory",file_md5_map:m,last_check_time:y}).catch(u=>{a.warn("[McpServer] \u5907\u4EFD project_memory \u66F4\u65B0\u5230\u672C\u5730\u5931\u8D25:",u)}),await this.reportMemoryStatsToServer(),s.memory_type==="project_memory"&&o?.memory_status==="updating")try{await this.memoryStore.releaseMemoryClaim(i.uri),this.activeLeaseUris.delete(i.uri),this.activeLeaseUris.size===0&&this.stopLeaseRenewal()}catch(u){a.warn("[McpServer] \u91CA\u653E memory claim \u5931\u8D25: uri=%s, error=%s",i.uri,u instanceof Error?u.message:String(u))}return this.toMemoryWriteResult(h)}async handleDeleteMemory(e){const t=typeof e.uri=="string"?e.uri:void 0,r=Array.isArray(e.memories)?e.memories:void 0;if(t&&r&&r.length>0)throw new Error("delete_memory \u4E0D\u652F\u6301\u540C\u65F6\u4F20\u5165 uri \u548C memories\uFF0C\u8BF7\u4E8C\u9009\u4E00");if(!t&&(!r||r.length===0))throw new Error("delete_memory \u9700\u8981 uri \u6216 memories \u53C2\u6570");if(t){const o=this.getOptionalMemoryType(e);return{deleted:await this.deleteMemorySingle(t,o)}}let n=0;const i=[],s=[];for(let o=0;o<r.length;o++){const l=r[o];if(typeof l!="object"||l===null){s.push({uri:String(l),reason:`memories[${o}] \u4E0D\u662F\u5BF9\u8C61`});continue}const m=l,y=typeof m.uri=="string"?m.uri:"";if(!y){s.push({uri:"",reason:`memories[${o}].uri \u7F3A\u5931`});continue}const h=typeof m.memory_type=="string"?this.parseMemoryType(m.memory_type):void 0;try{await this.deleteMemorySingle(y,h,!0)?n++:i.push(y)}catch(u){s.push({uri:y,reason:u instanceof Error?u.message:String(u)})}}return n>0&&await this.reportMemoryStatsToServer(),{deleted:n,not_found:i,failed:s}}async deleteMemorySingle(e,t,r=!1){const n={uri:e,memory_type:t},i=await this.findMemoryStoreForMutation(n),s=i.memory_type??n.memory_type,o=await i.store.deleteMemory({...n,memory_type:s});return o&&(s==="project_memory"&&this.removeProjectMemoryLocalBackup(e).catch(l=>{a.warn("[McpServer] \u5220\u9664 project_memory \u672C\u5730\u5907\u4EFD\u5931\u8D25:",l)}),r||await this.reportMemoryStatsToServer()),o}async handleSearchMemory(e){const t=this.getOptionalString(e,"scope"),r=t==="children"||t==="all"?t:"own",n=this.getOptionalString(e,"keyword_mode"),i=n==="all"?"all":n==="any"?"any":void 0,s={query:this.getRequiredString(e,"query"),limit:this.getOptionalNumber(e,"limit"),memory_type:this.getOptionalMemoryType(e),keywords:this.getOptionalStringArray(e,"keywords"),keyword_mode:i,scope:r};if(r==="children")return await this.memoryStore.searchMemory(s);const o=await this.searchOwnMemories(s);if(r==="own")return o;const l=await this.memoryStore.searchMemory({...s,scope:"children"});return this.mergeSearchResults(o,l,s.limit)}async searchOwnMemories(e){return await this.memoryStore.searchMemory(e)}mergeSearchResults(e,t,r){const n=new Set,i=typeof r=="number"&&r>0?r:10,s=[...e.results,...t.results].filter(o=>{const l=o.uri;return n.has(l)?!1:(n.add(l),!0)}).sort((o,l)=>l.updatedAt.localeCompare(o.updatedAt)).slice(0,i);return{query:e.query||t.query,results:s}}async handleListMemories(e){const t=this.getRequiredString(e,"memory_type");if(t==="project_memory"||t==="session_context")throw new Error(`Invalid memory_type: ${t}`);return await this.memoryStore.listByType(t)}async handleGetMemoryStats(e){const t=await this.memoryStore.getStats(),r=this.getOptionalMemoryType(e);return r?{memory_type:r,total:t.by_type[r].count,by_type:{[r]:t.by_type[r]}}:t}async handleListMemoriesByFile(e){const t=this.getRequiredString(e,"file_path"),r=this.normalizeBoundFilePath(t),n=this.getOptionalMemoryType(e),i=this.getOptionalString(e,"scope"),s=i==="own"||i==="children"?i:"all",o=async()=>await this.memoryStore.listByFile(r,{memory_type:n,scope:"children"}).catch(()=>[]);if(s==="children"){const u=await o();return{file_path:r,count:u.length,memories:u.map(S=>this.toMemoryRecordWithMetadata(S))}}const l=await this.collectOwnMemoriesByFile(r,n);if(s==="own")return{file_path:r,count:l.length,memories:l.map(u=>this.toMemoryRecordWithMetadata(u))};const m=await o(),y=new Set(l.map(u=>u.id)),h=[...l];for(const u of m)y.has(u.id)||(h.push(u),y.add(u.id));return h.sort((u,S)=>S.updatedAt.localeCompare(u.updatedAt)),{file_path:r,count:h.length,memories:h.map(u=>this.toMemoryRecordWithMetadata(u))}}mergeIdDedupe(e,t){const r=new Set;for(const i of e)i.id&&r.add(i.id);const n=[...e];for(const i of t)i.id&&!r.has(i.id)&&(n.push(i),r.add(i.id));return n.sort((i,s)=>s.updatedAt.localeCompare(i.updatedAt)),n}async searchChildMemoriesByFileLocal(e,t){const r=process.env.AWS_WORKSPACE_PATH||process.cwd(),n=this.normalizeBoundFilePath(e),s=(n.startsWith("./")?n.substring(2):n).split("/"),o=[],l=new Set,{access:m,readdir:y,readFile:h}=await import("node:fs/promises");for(let u=s.length-1;u>=1;u--){const S=s.slice(0,u),T="./"+s.slice(u).join("/"),A=g.join(r,...S);if(A===r)continue;const c=g.join(A,".agentswork");try{await m(c)}catch{continue}const p=g.join(c,"memory","memory");let R;try{R=(await y(p,{withFileTypes:!0})).filter(f=>f.isDirectory()).map(f=>f.name)}catch{continue}for(const _ of R){const f=g.join(p,_);let I;try{I=await y(f)}catch{continue}for(const d of I){if(!d.endsWith(".json"))continue;const w=g.join(f,d);let M;try{M=await h(w,"utf-8")}catch{continue}let b;try{b=JSON.parse(M)}catch{continue}if(b.by_uri)for(const[P,k]of Object.entries(b.by_uri)){if(!k)continue;const D=k.id||P;l.has(D)||t&&k.memory_type!==t||k.bound_files&&k.bound_files.length>0&&k.bound_files.some(L=>this.normalizeBoundFilePath(L)===T)&&(l.add(D),o.push({...k,uri:k.uri||P}))}}}}return o.sort((u,S)=>S.updatedAt.localeCompare(u.updatedAt)),o}async collectOwnMemoriesByFile(e,t){return await this.memoryStore.listByFile(e,{memory_type:t})}async handleAbandonMemoryUpdate(e){const t=this.getRequiredString(e,"uri"),r=this.getOptionalMemoryType(e),n=await this.memoryStore.readMemory({uri:t,memory_type:r});return n?.bound_files&&n.bound_files.length>0&&await this.validateBoundFilesExist(n.bound_files),await this.memoryStore.releaseMemoryClaim(t),n?.memory_type==="project_memory"&&this.backupProjectMemoryToLocal({uri:t,memory_type:"project_memory"}).catch(i=>{a.info(`[McpServer] abandon_memory_update: \u540C\u6B65\u672C\u5730\u5907\u4EFD\u5931\u8D25: ${t}`,i)}),this.activeLeaseUris.delete(t),this.activeLeaseUris.size===0&&this.stopLeaseRenewal(),{success:!0,uri:t,message:"Memory \u66F4\u65B0\u72B6\u6001\u5DF2\u6E05\u9664"}}async handleMemoryRecurrence(e){const t=Math.max(1,e?.limit??10),r=await this.memoryStore.listExpiredRecentlyMemories();if(r.length===0)return{memories:[],count:0,remaining:0,totalExpired:0,message:"\u6CA1\u6709\u5F85\u56DE\u5FC6\u7684\u5373\u5C06\u9057\u5FD8\u8BB0\u5FC6\u3002"};const i=[...r].sort((m,y)=>{const h=new Date(m.expiresAt||m.createdAt||0).getTime(),u=new Date(y.expiresAt||y.createdAt||0).getTime();return h-u}).slice(0,t),s=i.map(m=>m.uri);try{await this.memoryStore.markExpiredMemoriesRecalling(s)}catch(m){a.warn("[McpServer] \u6807\u8BB0\u8FC7\u671F\u8BB0\u5FC6\u4E3A recalling \u5931\u8D25: uriCount=%s, error=%s",s.length,m instanceof Error?m.message:String(m))}const o=Math.max(0,r.length-i.length);return{memories:i.map(m=>({uri:m.uri,title:m.title,domain:m.domain,path:m.path,memory_status:"recalling",contentPreview:typeof m.content=="string"&&m.content.length>200?m.content.substring(0,200)+"...":m.content,createdAt:m.createdAt,updatedAt:m.updatedAt})),count:i.length,limit:t,remaining:o,totalExpired:r.length,message:o>0?`\u672C\u6B21\u5904\u7406 ${i.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\uFF08\u6700\u65E9\u8FC7\u671F\u4F18\u5148\uFF09\uFF0C\u5269\u4F59 ${o} \u6761\u5F85\u5904\u7406\u3002
4
+ \u8BF7\u5BA1\u67E5\u540E\u51B3\u5B9A\uFF08update_memory \u7EED\u671F\uFF0CTTL \xD72 / \u4E0D\u5904\u7406\uFF09\uFF0C
5
+ \u5B8C\u6210\u540E\u8C03\u7528 memory_recurrence_complete \u6E05\u7406\uFF0C\u518D\u8C03 memory_recurrence \u5904\u7406\u4E0B\u4E00\u6279\u3002`:`\u5DF2\u5904\u7406\u5168\u90E8 ${i.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\u3002
6
+ \u8BF7\u5BA1\u67E5\u540E\u51B3\u5B9A\uFF08update_memory \u7EED\u671F\uFF0CTTL \xD72 / \u4E0D\u5904\u7406\uFF09\uFF0C
7
+ \u5B8C\u6210\u540E\u8C03\u7528 memory_recurrence_complete \u6E05\u7406\u3002`}}async handleMemoryRecurrenceComplete(){let e={deleted:0,uris:[]};try{e=await this.memoryStore.clearRecallingExpiredMemories()}catch(r){a.warn("[McpServer] \u6E05\u7406\u56DE\u5FC6\u4E2D\u8BB0\u5FC6\u5931\u8D25: %s",r instanceof Error?r.message:String(r))}await this.reportMemoryStatsToServer();const t=e.deleted??0;return{deleted:t,uris:e.uris??[],message:t===0?"\u6CA1\u6709\u5F85\u758F\u7406\u7684\u56DE\u5FC6\u4E2D\u8BB0\u5FC6\u3002":`\u5DF2\u6E05\u7406 ${t} \u6761\u5DF2\u8FC7\u671F\u8FD1\u671F\u8BB0\u5FC6\u3002`}}async fetchRecentMessages(){const[e,t]=await Promise.all([this.agentClient.getDmHistory(void 0,200).catch(()=>({messages:[],count:0})),this.agentClient.getGroupHistory(200).catch(()=>({messages:[],count:0}))]),r=[...e.messages,...t.messages];return r.sort((n,i)=>new Date(n.timestamp).getTime()-new Date(i.timestamp).getTime()),r}async handleSendGroup(e){if(!e.content||typeof e.content!="string")throw new Error("content is required");return this.recordMessageTokenUsage(e.content).catch(t=>a.warn("[McpServer] Failed to record token usage",t)),await this.agentClient.sendGroupMessage(e.content,e.projectName,e.roomId)}async recordMessageTokenUsage(e){try{const t=await this.agentClient.getProfile().catch(()=>null);if(!t?.runtimeSessionId)return;const r=this.contextManager.estimateTokens(e);await this.contextManager.recordUsage(t.runtimeSessionId,{input:0,output:r})}catch{}}async handleHireColleague(e){if(!e.workspacePath||typeof e.workspacePath!="string")throw new Error("workspacePath is required");return await this.agentClient.hireColleague(e)}async handleHireColleaguesBatch(e){if(!Array.isArray(e.items)||e.items.length===0)throw new Error("items is required and must contain at least one hire item");return e.items.forEach((t,r)=>{if(!t.workspacePath||typeof t.workspacePath!="string")throw new Error(`items[${r}].workspacePath is required`)}),await this.agentClient.hireColleaguesBatch(e)}async handleSendPrivate(e){if(!e.targetId||typeof e.targetId!="string")throw new Error("targetId is required");if(!e.content||typeof e.content!="string")throw new Error("content is required");return this.recordMessageTokenUsage(e.content).catch(t=>a.warn("[McpServer] Failed to record token usage",t)),await this.agentClient.sendDirectMessage(e.targetId,e.content,e.waitReply,e.replyToCallId,e.timeoutMs)}async handleCreateTaskInstanceComment(e){const t=this.getRequiredString(e,"taskInstanceId"),r=this.getRequiredString(e,"content");return await this.agentClient.createTaskInstanceComment({taskInstanceId:t,content:r})}async prependMemoryNotification(e,t){const r=await t();return r?(e.directMessages.unshift(r),!0):!1}async handlePollMessage(){if(a.info("[McpServer] handlePollMessage \u88AB\u8C03\u7528"),this.pendingPollMessage)return a.info("[McpServer] poll_message \u5DF2\u6709\u8FDB\u884C\u4E2D\u7684\u8C03\u7528\uFF0C\u7B49\u5F85\u5176\u5B8C\u6210"),await this.pendingPollMessage;this.pendingPollMessage=(async()=>await this.doPollMessage())();try{return await this.pendingPollMessage}finally{this.pendingPollMessage=null}}async doPollMessage(){a.info("[McpServer] doPollMessage \u88AB\u8C03\u7528"),await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0});const e=await this.buildTodoReminderNotification(),t=this.messageBuffer.get();if(t.directMessages.length>0||t.groupMessages.length>0)return e&&t.directMessages.unshift(e),this.buildPollResult(t);if(e)return this.buildPollResult({directMessages:[e],groupMessages:[]});const r=await this.checkAllLocalNonUrgentNotifications();if(r.length>0)return this.buildPollResult({directMessages:r,groupMessages:[]});let n=!1;for(;;){if(!n){const m=this.agentClient.getStatusReporter();if(m){const y=m.getCurrentStatus();y.status==="tool_using"&&y.actionDetail!=null&&y.actionDetail.includes("poll_message")||m.reportToolUse("poll_message",{}),n=!0}}const i=await this.messageBuffer.poll(ee);if(i.directMessages.length>0||i.groupMessages.length>0)return this.buildPollResult(i);await this.hydrateUnreadMessagesFromServer({blockIfEmpty:!1,includePipelineTasks:!0});const s=await this.buildTodoReminderNotification(),o=this.messageBuffer.get();if(o.directMessages.length>0||o.groupMessages.length>0)return s&&o.directMessages.unshift(s),this.buildPollResult(o);if(s)return this.buildPollResult({directMessages:[s],groupMessages:[]});const l=await this.checkAllLocalNonUrgentNotifications(!1);if(l.length>0)return this.buildPollResult({directMessages:l,groupMessages:[]})}}async checkAllLocalNonUrgentNotifications(e=!0){const t=[],r=await this.buildMemoryRecurrenceNotification();if(r&&t.push(r),e){const i=await this.buildMemoryFileChangeNotification();i&&t.push(i)}const n=await this.buildMemoryCleanupNotification();return n&&t.push(n),t}buildPollResult(e,t=!0){return{directMessages:e.directMessages,groupMessages:e.groupMessages,fromBuffer:t,systemMessages:[]}}parsePositiveIntegerEnv(e,t){const r=process.env[e];if(!r||!r.trim())return t;const n=Number.parseInt(r,10);return!Number.isFinite(n)||n<=0?(a.warn(`[McpServer] ${e} \u65E0\u6548\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u503C ${t}ms`),t):n}sleep(e){return e<=0?Promise.resolve():new Promise(t=>setTimeout(t,e))}async buildMemoryCleanupNotification(){try{const e=await this.memoryStore.getStats(),t=Object.entries(e.by_type).filter(([,o])=>o.listable&&o.exceeded),r=Object.entries(e.by_type).flatMap(([o,l])=>l.listable?Object.entries(l.by_domain).filter(([,m])=>m.exceeded).map(([m,y])=>({memoryType:o,domain:m,domainStats:y})):[]);if(t.length===0&&r.length===0)return null;const n=t.map(([o,l])=>`${o} \u5F53\u524D ${l.domainCount} \u4E2A URI domain \u7C7B\u578B\uFF0C\u5EFA\u8BAE\u4E0A\u9650 ${l.limit} \u4E2A\uFF0C\u786C\u4E0A\u9650 ${l.hardLimit} \u4E2A`),i=r.map(({memoryType:o,domain:l,domainStats:m})=>`${o}/${l} \u7C7B\u578B\u4E0B\u5F53\u524D ${m.count} \u6761 memory\uFF0C\u5EFA\u8BAE\u4E0A\u9650 ${m.limit} \u6761\uFF0C\u786C\u4E0A\u9650 ${m.hardLimit} \u6761`),s=["\u8BF7\u8C03\u7528 get_memory_stats \u67E5\u770B\u5BB9\u91CF\u8BE6\u60C5","\u8C03\u7528 list_memories \u6309\u7C7B\u578B\u67E5\u770B\u8D85\u9650\u8BB0\u5FC6","\u5220\u9664\u4E0D\u518D\u9700\u8981\u7684 recently_memory","\u66F4\u65B0 agent_memory \u7EED\u671F\uFF08TTL \xD72\uFF09\u6216\u5220\u9664\u8FC7\u671F\u9879"].join("\uFF1B");return{msgId:`memory-cleanup-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:this.getCurrentAgentId()||void 0,content:`\u672C\u5730 memory \u5DF2\u8D85\u8FC7\u5EFA\u8BAE\u5BB9\u91CF\uFF0C\u9700\u8981\u6574\u7406\u3002${[...n,...i].join("\uFF1B")}\u3002${s}\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u672C\u5730 memory \u5BB9\u91CF\u6574\u7406\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u4F18\u5148\u6574\u7406\u5DF2\u8D85\u8FC7\u5EFA\u8BAE\u4E0A\u9650\u7684\u77ED\u671F/\u8FD1\u671F memory \u7C7B\u578B\uFF0C\u6216\u5177\u4F53\u7C7B\u578B\u4E0B\u7684 memory \u6761\u76EE\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_cleanup",uris:r.map(o=>`${o.memoryType}/${o.domain}`)}}}catch(e){return a.warn("[McpServer] \u6784\u5EFA\u672C\u5730 memory \u6574\u7406\u63D0\u9192\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async buildMemoryRecurrenceNotification(){try{const e=await this.memoryStore.listExpiredRecentlyMemories();if(e.length===0)return null;const t=this.getCurrentAgentId();if(!t)return null;const r=e.filter(i=>i.memory_status==="recalling"),n=e.filter(i=>!r.includes(i));if(r.length>0){const i=r.slice(0,5).map(s=>s.uri).join(`
8
+ `);return{msgId:`memory-recurrence-pending-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:t,content:`\u5B58\u5728\u5F85\u758F\u7406\u7684\u5373\u5C06\u9057\u5FD8\u8BB0\u5FC6\uFF08\u5171 ${r.length} \u6761\uFF09\uFF0C\u8BF7\u901A\u8FC7 memory_recurrence \u83B7\u53D6\u8FDB\u884C\u6574\u7406\u3002
9
+
10
+ \u5F85\u758F\u7406\u8BB0\u5FC6\uFF1A
11
+ ${i}${r.length>5?`
12
+ ...\u53CA\u5176\u4ED6 ${r.length-5} \u6761`:""}`,requireReply:!1,prompt:"memory_recurrence \u53D6\u56DE\u7684\u8BB0\u5FC6\u4ECD\u5728\u56DE\u5FC6\u4E2D\uFF0C\u8BF7\u901A\u8FC7 memory_recurrence_complete \u5B8C\u6210\u758F\u7406\uFF0C\u6216\u7EE7\u7EED\u5F52\u6863/\u7EED\u671F\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:r.map(s=>s.uri)}}}if(n.length>0){const i=n.slice(0,5).map(s=>s.uri).join(`
13
+ `);return{msgId:`memory-recurrence-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:t,content:`\u6709 ${n.length} \u6761\u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\u5F97\u6574\u7406\uFF0C\u8BF7\u4F7F\u7528 memory_recurrence(limit=10) \u5206\u6279\u5904\u7406\uFF08\u6700\u65E9\u8FC7\u671F\u4F18\u5148\uFF0C\u6BCF\u6B21 10 \u6761\uFF09\u3002
14
+
15
+ \u5373\u5C06\u9057\u5FD8\u7684\u8BB0\u5FC6\uFF1A
16
+ ${i}${n.length>5?`
17
+ ...\u53CA\u5176\u4ED6 ${n.length-5} \u6761`:""}`,requireReply:!1,prompt:`\u8FD9\u662F\u4E00\u6761\u8FD1\u671F\u8BB0\u5FC6\u8FC7\u671F\u63D0\u9192\uFF0C\u5171\u6709 ${n.length} \u6761\u8BB0\u5FC6\u5373\u5C06\u9057\u5FD8\uFF0C\u8BF7\u8C03\u7528 memory_recurrence \u5206\u6279\u6B21\u83B7\u53D6\u5E76\u51B3\u5B9A\u5904\u7406\u65B9\u5F0F\u3002`,timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_expiration",uris:n.map(s=>s.uri)}}}return null}catch(e){return a.warn("[McpServer] \u6784\u5EFA\u8FD1\u671F\u8BB0\u5FC6\u8FC7\u671F\u56DE\u5FC6\u901A\u77E5\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async buildMemoryFileChangeNotification(){if(a.info(`[McpServer] buildMemoryFileChangeNotification \u88AB\u8C03\u7528: hasRoleName=${this.hasRoleName}, agentId=${this.getCurrentAgentId()??"(\u7A7A)"}`),!this.hasRoleName)return a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u8DF3\u8FC7: hasRoleName=false"),null;try{const e=this.getCurrentAgentId();if(!e)return a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u8DF3\u8FC7: agentId \u4E3A\u7A7A"),null;const t=await this.memoryStore.listByType("project_memory"),r=t.filter(c=>c.memory_status==="updating"&&c.updating_agent_id===e);if(r.length>0){this.startLeaseRenewal();const c=await this.checkMemoriesFileChanges(r);if(c.length>0){for(const M of c){const b=r.find(P=>P.id===M.memoryId);b&&await this.memoryStore.updateMemory({uri:b.uri,memory_type:"project_memory",memory_status:"updating",updating_agent_id:e,file_md5_map:b.file_md5_map,last_check_time:b.last_check_time,bound_files:b.bound_files})}const d=Math.min(c.length,5),w=c.slice(0,5).map(M=>`- ${M.memoryUri} (\u6587\u4EF6: ${M.changedFiles.join(", ")})`).join(`
18
+ `);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B ${d} \u4E2A\u5904\u4E8E\u66F4\u65B0\u4E2D\u7684 Memory \u7ED1\u5B9A\u7684\u6587\u4EF6\u518D\u6B21\u53D8\u66F4\uFF0C\u5DF2\u66F4\u65B0 MD5 \u8BB0\u5F55\uFF1A
19
+
20
+ ${w}
21
+
22
+ \u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u6700\u65B0\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u6807\u8BB0\u4E3A\u66F4\u65B0\u4E2D\u7684\u8BB0\u5FC6\u7ED1\u5B9A\u7684\u6587\u4EF6\u53C8\u6709\u65B0\u53D8\u66F4\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:c.slice(0,5).map(M=>M.memoryUri),fileChanges:c.slice(0,5).map(M=>{const b=r.find(P=>P.id===M.memoryId);return{uri:M.memoryUri,boundFiles:b?.bound_files??[],fileStatuses:M.fileStatuses,changedFiles:M.changedFiles}})}}}const p=r,R=new Map;for(const d of p.slice(0,5))R.set(d.uri,await this.computeBoundFileStatuses(d));const _=p.filter(d=>{const w=R.get(d.uri);return!w||w.length===0?!1:w.some(M=>M.status==="content_changed"||M.status==="not_found")}),f=p.filter(d=>!_.includes(d));if(f.length>0){a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: ${f.length} \u4E2A updating memory \u7ED1\u5B9A\u6587\u4EF6\u65E0\u771F\u5B9E\u53D8\u66F4\uFF0C\u81EA\u52A8\u91CA\u653E: ${f.map(d=>d.uri).join(", ")}`);for(const d of f)try{await this.memoryStore.releaseMemoryClaim(d.uri),this.activeLeaseUris.delete(d.uri),await this.memoryStore.updateMemory({uri:d.uri,memory_type:"project_memory"})}catch(w){a.info(`[McpServer] \u81EA\u52A8\u91CA\u653E stale updating memory \u5931\u8D25: ${d.uri}`,w)}this.activeLeaseUris.size===0&&this.stopLeaseRenewal()}if(_.length===0)return a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: \u6240\u6709 updating memory \u7ED1\u5B9A\u6587\u4EF6\u5747\u65E0\u771F\u5B9E\u53D8\u66F4\uFF0C\u5DF2\u81EA\u52A8\u91CA\u653E\uFF0C\u8DF3\u8FC7\u59D4\u6258"),null;const I=_.slice(0,5).map(d=>`- ${d.uri} (\u6587\u4EF6: ${d.bound_files?.join(", ")||"\u672A\u77E5"})`).join(`
23
+ `);return a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step1: ${_.length}/${r.length} \u4E2A updating memory \u6709\u771F\u5B9E\u6587\u4EF6\u53D8\u66F4\uFF0C\u89E6\u53D1\u59D4\u6258`),{msgId:`memory-update-pending-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u60A8\u6709\u4EE5\u4E0B Memory \u6B63\u5904\u4E8E\u66F4\u65B0\u4E2D\u72B6\u6001\u4F46\u5C1A\u672A\u5B8C\u6210\u66F4\u65B0\uFF1A
24
+
25
+ ${I}
26
+
27
+ \u8BF7\u7EE7\u7EED\u5B8C\u6210\u66F4\u65B0\u5DE5\u4F5C\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u66F4\u65B0\u63D0\u9192\uFF0C\u60A8\u4E4B\u524D\u5DF2\u6807\u8BB0\u8FD9\u4E9B\u8BB0\u5FC6\u4E3A\u66F4\u65B0\u4E2D\uFF0C\u8BF7\u7EE7\u7EED\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:_.slice(0,5).map(d=>d.uri),fileChanges:_.slice(0,5).map(d=>({uri:d.uri,boundFiles:d.bound_files??[],fileStatuses:R.get(d.uri)??[]}))}}}const n=await this.memoryStore.listByType("recently_memory"),i=await this.memoryStore.listByType("agent_memory"),s=[...n,...i],o=s.filter(c=>c.bound_files&&c.bound_files.length>0);a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: recently=${n.length}, agent=${i.length}, \u6709\u7ED1\u5B9A\u6587\u4EF6=${o.length}/${s.length}\u3002\u8BE6\u60C5: ${s.map(c=>`${c.uri}(bound=${c.bound_files?.length??0},md5=${Object.keys(c.file_md5_map??{}).length})`).join("; ")||"\u65E0 memory"}`);const l=new Map;for(const c of s)c.bound_files&&c.bound_files.length>0&&l.set(c.id,JSON.stringify(c.file_md5_map??{}));const m=await this.checkMemoriesFileChanges(s),y=m;a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: \u68C0\u6D4B\u5230\u53D8\u66F4=${m.length}`+(m.length>0?` \u53D8\u66F4\u9879: ${m.map(c=>`${c.memoryUri}(${c.changedFiles.join(",")})`).join("; ")}`:""));const h=s.filter(c=>{if(!c.bound_files||c.bound_files.length===0||m.some(_=>_.memoryId===c.id))return!1;const p=l.get(c.id),R=JSON.stringify(c.file_md5_map??{});return p!==R});if(h.length>0){a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step2: \u56DE\u5199 ${h.length} \u4E2A listable memory \u7684\u4FEE\u6B63 MD5 map`);for(const c of h)try{await this.memoryStore.updateMemory({uri:c.uri,memory_type:c.memory_type,file_md5_map:c.file_md5_map,last_check_time:c.last_check_time})}catch(p){a.info(`[McpServer] \u56DE\u5199 listable MD5 map \u5931\u8D25: ${c.uri}`,p)}}if(y.length>0){const c=y.map(p=>`- ${p.memoryUri} (\u6587\u4EF6: ${p.changedFiles.join(", ")})`).join(`
28
+ `);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B Memory \u7ED1\u5B9A\u7684\u6587\u4EF6\u5DF2\u53D8\u66F4\uFF0C\u8BF7\u53CA\u65F6\u66F4\u65B0\uFF1A
29
+
30
+ ${c}
31
+
32
+ \u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:y.map(p=>p.memoryUri),fileChanges:y.map(p=>{const R=s.find(_=>_.id===p.memoryId);return{uri:p.memoryUri,boundFiles:R?.bound_files??[],fileStatuses:p.fileStatuses,changedFiles:p.changedFiles}})}}}const u=t.filter(c=>c.bound_files&&c.bound_files.length>0&&(c.memory_status==="normal"||!c.memory_status)&&!this.isClaimCached(c.uri)),S=t.filter(c=>c.bound_files&&c.bound_files.length>0);a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: project_memory \u603B\u6570=${t.length}, \u6709\u7ED1\u5B9A\u6587\u4EF6=${S.length}, \u5F85\u68C0\u6D4B(\u8FC7\u6EE4updating/claimed\u540E)=${u.length}\u3002\u88AB\u8FC7\u6EE4\u539F\u56E0: ${S.length-u.length} \u9879(memory_status=updating \u6216 isClaimCached)`);const E=new Map;for(const c of u)E.set(c.id,JSON.stringify(c.file_md5_map??{}));const T=await this.checkMemoriesFileChanges(u),A=u.filter(c=>{if(T.some(_=>_.memoryId===c.id))return!1;const p=E.get(c.id),R=JSON.stringify(c.file_md5_map??{});return p!==R&&c.file_md5_map&&Object.keys(c.file_md5_map).length>0});if(A.length>0){a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: \u56DE\u5199 ${A.length} \u4E2A memory \u7684\u4FEE\u6B63 MD5 map \u5230\u670D\u52A1\u7AEF\uFF08\u65E0\u57FA\u7EBF/\u952E\u7F3A\u5931\u4FEE\u590D\uFF09`);for(const c of A)try{await this.memoryStore.updateMemory({uri:c.uri,memory_type:"project_memory",file_md5_map:c.file_md5_map,last_check_time:c.last_check_time})}catch(p){a.info(`[McpServer] \u56DE\u5199\u57FA\u7EBF MD5 \u5931\u8D25: ${c.uri}`,p)}}if(a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B Step3: \u68C0\u6D4B\u5230\u53D8\u66F4=${T.length}`+(T.length>0?` \u53D8\u66F4\u9879: ${T.map(c=>`${c.memoryUri}(${c.changedFiles.join(",")})`).join("; ")}`:"")),T.length>0){const c=T.slice(0,5),p=c.map(f=>f.memoryUri);let R=!1,_=new Set;try{const f=await this.memoryStore.claimMemoryUpdate(p,v.LEASE_DURATION_MINUTES,"project_memory");_=new Set(f.claimed_uris??[]),R=!0}catch(f){a.info("[McpServer] Server claim API \u4E0D\u53EF\u7528\uFF0C\u964D\u7EA7\u5230\u672C\u5730\u76F4\u63A5\u6807\u8BB0:",f),_=new Set(p)}if(_.size>0){const f=c.filter(d=>_.has(d.memoryUri));for(const d of f){const w=t.find(M=>M.id===d.memoryId);w&&(await this.memoryStore.updateMemory({uri:w.uri,memory_type:"project_memory",memory_status:"updating",updating_agent_id:e,file_md5_map:w.file_md5_map,last_check_time:w.last_check_time,bound_files:w.bound_files}),this.activeLeaseUris.add(w.uri))}R&&_.size>0&&this.startLeaseRenewal();const I=f.map(d=>`- ${d.memoryUri} (\u6587\u4EF6: ${d.changedFiles.join(", ")})`).join(`
33
+ `);return{msgId:`memory-file-change-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`\u68C0\u6D4B\u5230\u4EE5\u4E0B ${f.length} \u4E2A\u6C38\u4E45\u8BB0\u5FC6\u7ED1\u5B9A\u7684\u6587\u4EF6\u5DF2\u53D8\u66F4\uFF0C\u5DF2\u4E3A\u60A8\u6807\u8BB0\u4E3A\u66F4\u65B0\u4E2D\uFF1A
34
+
35
+ ${I}
36
+
37
+ \u8BF7\u8C03\u7528 read_memory \u67E5\u770B\u5185\u5BB9\u5E76\u8C03\u7528 update_memory \u66F4\u65B0\u3002\u66F4\u65B0\u5B8C\u6210\u540E\u72B6\u6001\u5C06\u81EA\u52A8\u6062\u590D\u4E3A\u6B63\u5E38\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761\u6587\u4EF6\u53D8\u66F4\u63D0\u9192\uFF0C\u5DF2\u81EA\u52A8\u6807\u8BB0\u66F4\u65B0\u72B6\u6001\uFF0C\u8BF7\u5904\u7406\u3002",timestamp:new Date().toISOString(),_acodeAutoDelegate:{type:"memory_file_change",uris:f.map(d=>d.memoryUri),fileChanges:f.map(d=>{const w=t.find(M=>M.id===d.memoryId);return{uri:d.memoryUri,boundFiles:w?.bound_files??[],fileStatuses:d.fileStatuses,changedFiles:d.changedFiles}})}}}}return a.info("[McpServer] \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u5B8C\u6210: \u65E0\u53D8\u66F4\u901A\u77E5\u4EA7\u751F"),null}catch(e){return a.warn("[McpServer] \u6784\u5EFA Memory \u6587\u4EF6\u53D8\u66F4\u68C0\u6D4B\u901A\u77E5\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}isClaimCached(e){return this.activeLeaseUris.has(e)}async computeBoundFileStatuses(e){const t=[];if(!e.bound_files||e.bound_files.length===0)return t;const r=e.file_md5_map||{},n=process.env.AWS_WORKSPACE_PATH||process.cwd();for(const i of e.bound_files)try{const s=await this.calculateFileMd5(i);if(s===null){const l=g.resolve(n,i);await this.verifyFileNotExist(l)&&r[i]?t.push({path:i,status:"not_found",serverMd5:r[i]}):t.push({path:i,status:"no_change"});continue}const o=r[i];o?o!==s?t.push({path:i,status:"content_changed",localMd5:s,serverMd5:o}):t.push({path:i,status:"no_change"}):t.push({path:i,status:"no_change"})}catch{t.push({path:i,status:"no_change"})}return t}async checkMemoriesFileChanges(e){const t=[];for(const r of e)if(!(!r.bound_files||r.bound_files.length===0))try{const n=[],i=[],s={},o=r.file_md5_map||{},l=process.env.AWS_WORKSPACE_PATH||process.cwd(),m=Object.keys(o).length===0;let y=!1;for(const h of r.bound_files)try{const u=await this.calculateFileMd5(h);if(u===null){const E=g.resolve(l,h);a.info(`[McpServer] MD5 \u8BA1\u7B97\u8FD4\u56DE null: ${h} -> ${E} (workspace=${l})`);const T=o[h];T?await this.verifyFileNotExist(E)?(i.push({path:h,status:"not_found",serverMd5:T}),n.push(`${h}\uFF08\u6587\u4EF6\u627E\u4E0D\u5230 Server: ${T}\uFF09`),r.bound_files&&(r.bound_files=r.bound_files.filter(c=>c!==h))):(a.info(`[McpServer] \u6587\u4EF6\u5B58\u5728\u4F46 MD5 \u8BA1\u7B97\u5931\u8D25\uFF08\u77AC\u6001\u9519\u8BEF\uFF09\uFF0C\u8DF3\u8FC7: ${h}`),i.push({path:h,status:"no_change"})):i.push({path:h,status:"no_change"});continue}s[h]=u;const S=o[h];S?S!==u?(a.info(`[McpServer] \u6587\u4EF6\u53D8\u66F4: ${h} old=${S} new=${u} (workspace=${l})`),i.push({path:h,status:"content_changed",localMd5:u,serverMd5:S}),n.push(`${h}\uFF08\u5185\u5BB9\u53D8\u66F4 \u672C\u5730: ${u} Server: ${S}\uFF09`)):i.push({path:h,status:"no_change"}):(i.push({path:h,status:"no_change"}),y=!0,a.info(`[McpServer] \u5EFA\u7ACB\u5355\u6587\u4EF6\u57FA\u7EBF: ${h} new=${u} (workspace=${l}, memory=${r.uri})`))}catch(u){a.info(`[McpServer] \u8BA1\u7B97\u6587\u4EF6 MD5 \u5931\u8D25: ${h}`,u),i.push({path:h,status:"no_change"})}if(m&&Object.keys(s).length>0){r.file_md5_map=s,r.last_check_time=new Date().toISOString();continue}y&&Object.keys(s).length>0&&(r.file_md5_map=s,r.last_check_time=new Date().toISOString()),n.length>0&&(t.push({memoryId:r.id,memoryUri:r.uri,fileStatuses:i,changedFiles:n}),r.last_check_time=new Date().toISOString())}catch(n){a.warn(`[McpServer] \u68C0\u67E5 Memory ${r.id} \u6587\u4EF6\u53D8\u66F4\u5931\u8D25:`,n)}return t}async calculateFileMd5(e){try{const{createReadStream:t}=await import("node:fs"),{createHash:r}=await import("node:crypto"),n=process.env.AWS_WORKSPACE_PATH||process.cwd(),i=g.resolve(n,e),s=g.resolve(n),o=g.resolve(i);return!o.startsWith(s+g.sep)&&o!==s?(a.warn(`[McpServer] calculateFileMd5: \u8DEF\u5F84\u9003\u9038\u88AB\u62D2\u7EDD: ${e}`),null):new Promise(l=>{const m=r("md5"),y=t(i);y.on("data",h=>m.update(h)),y.on("end",()=>l(m.digest("hex"))),y.on("error",()=>l(null))})}catch(t){return a.info(`[McpServer] \u8BA1\u7B97\u6587\u4EF6 MD5 \u5931\u8D25: ${e}`,t),null}}async verifyFileNotExist(e){try{return await $.promises.access(e,$.constants.F_OK),!1}catch{return!0}}async handleGetMessage(){const e=this.messageBuffer.size()>0;await this.hydrateUnreadMessagesFromServer();const t=this.messageBuffer.get(),r=t.directMessages.length>0||t.groupMessages.length>0,n=e||!r;return await this.prependMemoryNotification(t,()=>this.buildMemoryRecurrenceNotification())?this.buildPollResult(t,n):await this.prependMemoryNotification(t,()=>this.buildMemoryCleanupNotification())?this.buildPollResult(t,n):this.buildPollResult(t,n)}async hydrateUnreadMessagesFromServer(e={}){try{const t=await this.agentClient.fetchUnreadMessages({blockIfEmpty:e.blockIfEmpty??!1,blockTimeoutMs:e.blockTimeoutMs,includePipelineTasks:e.includePipelineTasks});if(t.directMessages.length===0&&t.groupMessages.length===0)return!0;const r=t.directMessages.filter(i=>this.shouldBufferIncomingMessage(i)),n=t.groupMessages.filter(i=>this.shouldBufferIncomingMessage(i));return r.forEach(i=>{this.messageBuffer.push(i)}),n.forEach(i=>{this.messageBuffer.push(i)}),a.info(`[McpServer] \u5DF2\u8865\u62C9\u672A\u8BFB\u6D88\u606F\uFF0C\u79C1\u804A: ${r.length}\uFF0C\u7FA4\u804A: ${n.length}`),!0}catch(t){return a.error("[McpServer] \u8865\u62C9\u672A\u8BFB\u6D88\u606F\u5931\u8D25: %s, stack=%s",t instanceof Error?t.message:String(t),t instanceof Error?t.stack:"(no stack)"),!1}}async handleGetDmHistory(e){return await this.agentClient.getDmHistory(e.targetUserId,e.limit,e.since)}async handleGetGroupHistory(e){return await this.agentClient.getGroupHistory(e.limit,e.roomId,e.since)}async handleSubmitTaskResult(e){if(!e.nodeId||typeof e.nodeId!="string")throw new Error("nodeId is required");if(!e.outputPayload||typeof e.outputPayload!="string")throw new Error("outputPayload is required");const t=Array.isArray(e.changeLog)?e.changeLog:void 0;return await this.agentClient.submitTaskResult(e.nodeId,e.outputPayload,t)}async handleStartTaskRollback(e){const t={rollbackTaskId:this.getRequiredString(e,"rollbackTaskId")};return await this.agentClient.startTaskRollback(t)}async handleFinishTaskRollback(e){const t={rollbackTaskId:this.getRequiredString(e,"rollbackTaskId"),resultMessage:this.getOptionalString(e,"resultMessage")};return await this.agentClient.finishTaskRollback(t)}async handleRejectTask(e){if(!e.nodeId||typeof e.nodeId!="string")throw new Error("nodeId is required");if(!e.reason||typeof e.reason!="string")throw new Error("reason is required");return await this.agentClient.rejectTask(e.nodeId,e.reason)}async handleTerminateTask(e){if(!e.taskInstanceId||typeof e.taskInstanceId!="string")throw new Error("taskInstanceId is required");if(!e.reason||typeof e.reason!="string")throw new Error("reason is required");return await this.agentClient.terminateTask(e.taskInstanceId,e.reason)}async handleIncomingMessage(e){if(!this.shouldBufferIncomingMessage(e)){!this.isGroupMessage(e)&&this.isPipelineTaskAssignedNotification(e)&&(a.info("[McpServer] \u6536\u5230 pipeline_task_assigned \u5524\u9192\u4FE1\u53F7\uFF0C\u7ACB\u5373\u5524\u9192 poll \u8D70 hydrate \u91CD\u67E5"),this.messageBuffer.wakeUpWaiters());return}a.info(`[McpServer] \u6536\u5230\u6D88\u606F: ${e.content?.substring(0,50)||"(\u65E0\u5185\u5BB9)"}`),this.messageBuffer.push(e)}shouldBufferIncomingMessage(e){return!e||typeof e!="object"?(a.warn("[McpServer] \u5FFD\u7565\u7ED3\u6784\u5F02\u5E38\u7684\u6D88\u606F\uFF0C\u907F\u514D\u7F13\u51B2\u533A\u6C61\u67D3"),!1):this.isSelfSentMessage(e)?(a.info("[McpServer] \u5FFD\u7565\u5F53\u524D Agent \u81EA\u5DF1\u53D1\u9001\u7684\u6D88\u606F\uFF0C\u907F\u514D poll_message/get_message \u56DE\u73AF"),!1):this.isGroupMessage(e)?!0:this.isPipelineTaskAssignedNotification(e)?(a.info("[McpServer] \u5FFD\u7565\u6301\u4E45\u5316 pipeline_task_assigned \u79C1\u4FE1\uFF0C\u4EC5\u4F5C\u4E3A poll_message \u5524\u9192\u4FE1\u53F7\u5904\u7406"),!1):!0}isGroupMessage(e){return!!e&&typeof e=="object"&&typeof e.msgId=="number"}isPipelineTaskAssignedNotification(e){const t=typeof e.content=="string"?e.content:"",r=typeof e.prompt=="string"?e.prompt:"";return t.includes("pipeline_task_assigned")||r.includes("pipeline_task_assigned")}isSelfSentMessage(e){if(!e||typeof e!="object")return!1;const t=this.getCurrentAgentId();return!!t&&typeof e.senderId=="string"&&e.senderId===t}getCurrentAgentId(){const e=this.agentClient;return typeof e.getState!="function"?null:e.getState().agentId??null}sanitizeAgentIdForTodoFile(e){return String(e).replace(/[^a-zA-Z0-9_-]/g,"_")}async buildTodoReminderNotification(){try{const e=this.getCurrentAgentId();if(!e)return null;const t=g.join(U.homedir(),".acode","todos",`${this.sanitizeAgentIdForTodoFile(e)}.json`);let r;try{r=await $.promises.readFile(t,"utf-8")}catch(o){if(o?.code==="ENOENT")return null;throw o}const n=JSON.parse(r),s=(Array.isArray(n.todos)?n.todos:[]).filter(o=>o&&typeof o=="object"&&typeof o.content=="string"&&(o.status==="pending"||o.status==="in_progress"||o.status==="cancelled"));return s.length===0?null:{msgId:`todo-reminder-${Date.now()}`,senderId:"MCP-System",senderName:"\u7CFB\u7EDF",receiverId:e,content:`Todo \u5217\u8868\u5B58\u5728 ${s.length} \u9879\u672A\u5B8C\u6210\uFF0C\u8BF7\u8C03\u7528 todo_read \u67E5\u770B\u5E76\u5904\u7406\u3002`,requireReply:!1,prompt:"\u8FD9\u662F\u4E00\u6761 Todo \u672A\u5B8C\u6210\u9879\u63D0\u9192\uFF0C\u81EA\u52A8\u751F\u6210\uFF0C\u65E0\u9700\u6301\u4E45\u5316\u3002\u8BF7\u8BC4\u4F30\u662F\u5426\u9700\u8981\u7EE7\u7EED\u5904\u7406\u672A\u5B8C\u6210\u7684 Todo\uFF0C\u53EF\u8C03\u7528 todo_read \u83B7\u53D6\u5B8C\u6574\u5217\u8868\u3002",timestamp:new Date().toISOString()}}catch(e){return a.warn("[McpServer] \u6784\u5EFA Todo \u63D0\u9192\u5931\u8D25: %s",e instanceof Error?e.message:String(e)),null}}async start(){const e=new x;await this.server.connect(e),await this.agentClient.initialize(),a.info("[McpServer] MCP Server \u5DF2\u542F\u52A8\uFF0C\u7B49\u5F85\u8FDE\u63A5..."),a.info(`[McpServer] \u53EF\u7528\u5DE5\u5177: ${O.map(t=>t.name).join(", ")}`),a.info(`[McpServer] \u5DE5\u4F5C\u76EE\u5F55: ${process.env.AWS_WORKSPACE_PATH||process.cwd()}`),j&&a.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\u5DF2\u542F\u7528\uFF0C\u6CE8\u518C\u5931\u8D25\u65F6\u5C06\u4EE5\u79BB\u7EBF\u6A21\u5F0F\u7EE7\u7EED\u8FD0\u884C"),this.autoRegisterInBackground(),a.info("[McpServer] MCP Server \u5DF2\u5C31\u7EEA\uFF0C\u5DE5\u5177\u5DF2\u53EF\u7528")}startLeaseRenewal(){this.leaseRenewalTimer||(this.leaseRenewalTimer=setInterval(async()=>{try{if(!this.getCurrentAgentId())return;(await this.memoryStore.renewMemoryLease(v.LEASE_DURATION_MINUTES)).renewed===0&&(a.warn("[McpServer] \u79DF\u7EA6\u5DF2\u4E22\u5931\uFF08renewed=0\uFF09\uFF0C\u505C\u6B62\u7EED\u671F\u5B9A\u65F6\u5668"),this.stopLeaseRenewal())}catch(e){a.warn("[McpServer] \u7EED\u671F\u79DF\u7EA6\u5931\u8D25: %s",e instanceof Error?e.message:String(e))}},v.LEASE_RENEW_INTERVAL_MS),a.info("[McpServer] \u79DF\u7EA6\u7EED\u671F\u5B9A\u65F6\u5668\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: 60s"))}stopLeaseRenewal(){this.leaseRenewalTimer&&(clearInterval(this.leaseRenewalTimer),this.leaseRenewalTimer=null,a.info("[McpServer] \u79DF\u7EA6\u7EED\u671F\u5B9A\u65F6\u5668\u5DF2\u505C\u6B62"))}stop(){this.stopLeaseRenewal(),this.getCurrentAgentId()&&this.memoryStore.releaseAllMemoryClaims().catch(t=>a.warn("[McpServer] \u91CA\u653E claim \u5931\u8D25: %s",t instanceof Error?t.message:String(t))),this.agentClient.stop(),a.info("[McpServer] MCP Server \u5DF2\u505C\u6B62")}async autoRegister(){const e=this.agentClient.getAgentConfig();a.info(`[McpServer] \u89D2\u8272: "${e.roleName}"`),a.info(`[McpServer] AgentID: "${e.agentId||"(\u81EA\u52A8\u5206\u914D)"}"`),a.info("[McpServer] \u6B63\u5728\u8BF7\u6C42\u8C03\u5EA6\u4E2D\u5FC3\u6CE8\u518C...");let t=0;for(;;)try{const r=await this.agentClient.register();a.info(`[McpServer] \u6CE8\u518C\u6210\u529F: ${r.displayName} (${r.agentId})`),a.info(`[McpServer] \u72B6\u6001: ${r.status}`),this.startListeningInternal(),a.info("[McpServer] \u6D88\u606F\u76D1\u542C\u5DF2\u81EA\u52A8\u542F\u52A8");const n=this.agentClient.getStatusReporter();n&&n.reportWaitingInput(),await this.reportMemoryStatsToServer(),this.maybeSyncPermanentMemoryToServer();return}catch(r){const n=r instanceof Error?r.message:String(r);if(t++,C>0&&t>=C){if(j){a.warn(`[McpServer] \u6CE8\u518C\u8D85\u8FC7\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${C}\u6B21)\uFF0C\u8FDB\u5165\u964D\u7EA7\u6A21\u5F0F`),this.agentClient.enterDegradedMode(),this.startListeningInternal(),a.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u6D88\u606F\u76D1\u542C\u5DF2\u542F\u52A8"),this.agentClient.retryRegistrationInBackground(K,J);const i=this.agentClient.getStatusReporter();i&&i.reportWaitingInput(),await this.reportMemoryStatsToServer();return}throw new Error(`\u6CE8\u518C\u8D85\u8FC7\u6700\u5927\u91CD\u8BD5\u6B21\u6570 (${C}\u6B21)\uFF0C\u653E\u5F03\u542F\u52A8: ${n}`)}a.warn(`[McpServer] \u6CE8\u518C\u5931\u8D25\uFF0C\u7B49\u5F85\u91CD\u8BD5... (${t}/${C}): ${n}`),await this.delay(Y)}}autoRegisterInBackground(){this.autoRegister().catch(e=>{a.error("[McpServer] \u540E\u53F0\u6CE8\u518C\u5F02\u5E38: %s, stack=%s",e instanceof Error?e.message:String(e),e instanceof Error?e.stack:"(no stack)")})}startListeningInternal(){a.info("[McpServer] startListeningInternal \u88AB\u8C03\u7528");const e=t=>{a.info(`[McpServer] profileHandler \u88AB\u8C03\u7528\uFF0CdisplayName: ${t.displayName}`),this.messageBuffer.setProfileMessage(t),this.profileSynced=!0,this.syncRoleMemoryStore(t),this.maybeSyncPermanentMemoryToServer()};if(this.agentClient.isInDegradedMode()){a.info("[McpServer] \u964D\u7EA7\u6A21\u5F0F\uFF1A\u8DF3\u8FC7 WebSocket \u8FDE\u63A5\uFF0C\u4EC5\u542F\u52A8\u672C\u5730\u6D88\u606F\u76D1\u542C"),this.agentClient.start(this.handleIncomingMessage.bind(this),{pollMode:!1,profileHandler:e}),this.activelySyncProfile();return}this.agentClient.start(this.handleIncomingMessage.bind(this),{pollMode:!1,profileHandler:e}),this.activelySyncProfile()}activelySyncProfile(){this.profileSynced||this.agentClient.getProfile().then(e=>{this.profileSynced||(this.profileSynced=!0,a.info(`[McpServer] \u4E3B\u52A8\u83B7\u53D6 profile \u6210\u529F: displayName=${e.displayName}, roleName="${e.roleName??"(\u7A7A)"}"`),this.messageBuffer.setProfileMessage(e),this.syncRoleMemoryStore(e),this.maybeSyncPermanentMemoryToServer())}).catch(e=>{a.warn("[McpServer] \u4E3B\u52A8\u83B7\u53D6 profile \u5931\u8D25: %s",e instanceof Error?e.message:String(e))})}delay(e){return new Promise(t=>setTimeout(t,e))}}export{v as McpServer};
38
+