@zooique/memora 2.0.0 → 3.0.0

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 (588) hide show
  1. package/CHANGELOG.md +1028 -0
  2. package/README.en.md +305 -0
  3. package/README.md +303 -148
  4. package/config.example.json +43 -0
  5. package/dist/agent/agent.d.ts +438 -295
  6. package/dist/agent/agent.d.ts.map +1 -1
  7. package/dist/agent/agent.js +1368 -836
  8. package/dist/agent/agent.js.map +1 -1
  9. package/dist/agent/assembler.d.ts +117 -39
  10. package/dist/agent/assembler.d.ts.map +1 -1
  11. package/dist/agent/assembler.js +574 -71
  12. package/dist/agent/assembler.js.map +1 -1
  13. package/dist/agent/budget.d.ts +131 -0
  14. package/dist/agent/budget.d.ts.map +1 -0
  15. package/dist/agent/budget.js +128 -0
  16. package/dist/agent/budget.js.map +1 -0
  17. package/dist/agent/builtinToolHandlers.d.ts +229 -17
  18. package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
  19. package/dist/agent/builtinToolHandlers.js +758 -70
  20. package/dist/agent/builtinToolHandlers.js.map +1 -1
  21. package/dist/agent/builtinTools.d.ts +198 -7
  22. package/dist/agent/builtinTools.d.ts.map +1 -1
  23. package/dist/agent/builtinTools.js +646 -14
  24. package/dist/agent/builtinTools.js.map +1 -1
  25. package/dist/agent/compaction.d.ts +93 -0
  26. package/dist/agent/compaction.d.ts.map +1 -0
  27. package/dist/agent/compaction.js +192 -0
  28. package/dist/agent/compaction.js.map +1 -0
  29. package/dist/agent/constants.d.ts +139 -18
  30. package/dist/agent/constants.d.ts.map +1 -1
  31. package/dist/agent/constants.js +154 -18
  32. package/dist/agent/constants.js.map +1 -1
  33. package/dist/agent/contextManager.d.ts +126 -76
  34. package/dist/agent/contextManager.d.ts.map +1 -1
  35. package/dist/agent/contextManager.js +197 -135
  36. package/dist/agent/contextManager.js.map +1 -1
  37. package/dist/agent/contextPreparer.d.ts +78 -0
  38. package/dist/agent/contextPreparer.d.ts.map +1 -0
  39. package/dist/agent/contextPreparer.js +128 -0
  40. package/dist/agent/contextPreparer.js.map +1 -0
  41. package/dist/agent/duplicateInterceptor.d.ts +67 -0
  42. package/dist/agent/duplicateInterceptor.d.ts.map +1 -0
  43. package/dist/agent/duplicateInterceptor.js +82 -0
  44. package/dist/agent/duplicateInterceptor.js.map +1 -0
  45. package/dist/agent/guardRail.d.ts +166 -0
  46. package/dist/agent/guardRail.d.ts.map +1 -0
  47. package/dist/agent/guardRail.js +333 -0
  48. package/dist/agent/guardRail.js.map +1 -0
  49. package/dist/agent/loop.d.ts +718 -253
  50. package/dist/agent/loop.d.ts.map +1 -1
  51. package/dist/agent/loop.js +2215 -648
  52. package/dist/agent/loop.js.map +1 -1
  53. package/dist/agent/managers/archiveCoordinator.d.ts +21 -74
  54. package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
  55. package/dist/agent/managers/archiveCoordinator.js +32 -105
  56. package/dist/agent/managers/archiveCoordinator.js.map +1 -1
  57. package/dist/agent/managers/chatLockManager.d.ts +20 -88
  58. package/dist/agent/managers/chatLockManager.d.ts.map +1 -1
  59. package/dist/agent/managers/chatLockManager.js +31 -103
  60. package/dist/agent/managers/chatLockManager.js.map +1 -1
  61. package/dist/agent/managers/dedupManager.d.ts +85 -0
  62. package/dist/agent/managers/dedupManager.d.ts.map +1 -0
  63. package/dist/agent/managers/dedupManager.js +248 -0
  64. package/dist/agent/managers/dedupManager.js.map +1 -0
  65. package/dist/agent/managers/goalConsistencyChecker.d.ts +42 -0
  66. package/dist/agent/managers/goalConsistencyChecker.d.ts.map +1 -0
  67. package/dist/agent/managers/goalConsistencyChecker.js +178 -0
  68. package/dist/agent/managers/goalConsistencyChecker.js.map +1 -0
  69. package/dist/agent/managers/llmCaller.d.ts +124 -0
  70. package/dist/agent/managers/llmCaller.d.ts.map +1 -0
  71. package/dist/agent/managers/llmCaller.js +417 -0
  72. package/dist/agent/managers/llmCaller.js.map +1 -0
  73. package/dist/agent/managers/llmJudgeHelper.d.ts +21 -0
  74. package/dist/agent/managers/llmJudgeHelper.d.ts.map +1 -0
  75. package/dist/agent/managers/llmJudgeHelper.js +32 -0
  76. package/dist/agent/managers/llmJudgeHelper.js.map +1 -0
  77. package/dist/agent/managers/loopMetrics.d.ts +58 -0
  78. package/dist/agent/managers/loopMetrics.d.ts.map +1 -0
  79. package/dist/agent/managers/loopMetrics.js +68 -0
  80. package/dist/agent/managers/loopMetrics.js.map +1 -0
  81. package/dist/agent/managers/memoryAdvisor.d.ts +27 -120
  82. package/dist/agent/managers/memoryAdvisor.d.ts.map +1 -1
  83. package/dist/agent/managers/memoryAdvisor.js +56 -192
  84. package/dist/agent/managers/memoryAdvisor.js.map +1 -1
  85. package/dist/agent/managers/memoryGovernance.d.ts +24 -0
  86. package/dist/agent/managers/memoryGovernance.d.ts.map +1 -0
  87. package/dist/agent/managers/memoryGovernance.js +46 -0
  88. package/dist/agent/managers/memoryGovernance.js.map +1 -0
  89. package/dist/agent/managers/memoryInspector.d.ts +59 -393
  90. package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
  91. package/dist/agent/managers/memoryInspector.js +92 -736
  92. package/dist/agent/managers/memoryInspector.js.map +1 -1
  93. package/dist/agent/managers/roundSummaryGenerator.d.ts +56 -0
  94. package/dist/agent/managers/roundSummaryGenerator.d.ts.map +1 -0
  95. package/dist/agent/managers/roundSummaryGenerator.js +201 -0
  96. package/dist/agent/managers/roundSummaryGenerator.js.map +1 -0
  97. package/dist/agent/managers/sessionArchiver.d.ts +19 -63
  98. package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
  99. package/dist/agent/managers/sessionArchiver.js +83 -157
  100. package/dist/agent/managers/sessionArchiver.js.map +1 -1
  101. package/dist/agent/managers/sessionManager.d.ts +286 -57
  102. package/dist/agent/managers/sessionManager.d.ts.map +1 -1
  103. package/dist/agent/managers/sessionManager.js +846 -85
  104. package/dist/agent/managers/sessionManager.js.map +1 -1
  105. package/dist/agent/managers/sessionNamer.d.ts +35 -0
  106. package/dist/agent/managers/sessionNamer.d.ts.map +1 -0
  107. package/dist/agent/managers/sessionNamer.js +104 -0
  108. package/dist/agent/managers/sessionNamer.js.map +1 -0
  109. package/dist/agent/managers/streamAccumulator.d.ts +20 -0
  110. package/dist/agent/managers/streamAccumulator.d.ts.map +1 -0
  111. package/dist/agent/managers/streamAccumulator.js +15 -0
  112. package/dist/agent/managers/streamAccumulator.js.map +1 -0
  113. package/dist/agent/managers/textPolishManager.d.ts +8 -44
  114. package/dist/agent/managers/textPolishManager.d.ts.map +1 -1
  115. package/dist/agent/managers/textPolishManager.js +12 -47
  116. package/dist/agent/managers/textPolishManager.js.map +1 -1
  117. package/dist/agent/managers/toolCallHelpers.d.ts +105 -0
  118. package/dist/agent/managers/toolCallHelpers.d.ts.map +1 -0
  119. package/dist/agent/managers/toolCallHelpers.js +161 -0
  120. package/dist/agent/managers/toolCallHelpers.js.map +1 -0
  121. package/dist/agent/managers/workProjection.d.ts +57 -87
  122. package/dist/agent/managers/workProjection.d.ts.map +1 -1
  123. package/dist/agent/managers/workProjection.js +166 -246
  124. package/dist/agent/managers/workProjection.js.map +1 -1
  125. package/dist/agent/messageHistory.d.ts +134 -84
  126. package/dist/agent/messageHistory.d.ts.map +1 -1
  127. package/dist/agent/messageHistory.js +419 -133
  128. package/dist/agent/messageHistory.js.map +1 -1
  129. package/dist/agent/needsPlanning.d.ts +28 -0
  130. package/dist/agent/needsPlanning.d.ts.map +1 -0
  131. package/dist/agent/needsPlanning.js +68 -0
  132. package/dist/agent/needsPlanning.js.map +1 -0
  133. package/dist/agent/seed/index.d.ts +11 -0
  134. package/dist/agent/seed/index.d.ts.map +1 -0
  135. package/dist/agent/seed/index.js +10 -0
  136. package/dist/agent/seed/index.js.map +1 -0
  137. package/dist/agent/seed/orchestrator.d.ts +83 -0
  138. package/dist/agent/seed/orchestrator.d.ts.map +1 -0
  139. package/dist/agent/seed/orchestrator.js +260 -0
  140. package/dist/agent/seed/orchestrator.js.map +1 -0
  141. package/dist/agent/seed/prepare.d.ts +49 -0
  142. package/dist/agent/seed/prepare.d.ts.map +1 -0
  143. package/dist/agent/seed/prepare.js +107 -0
  144. package/dist/agent/seed/prepare.js.map +1 -0
  145. package/dist/agent/seed/types.d.ts +115 -0
  146. package/dist/agent/seed/types.d.ts.map +1 -0
  147. package/dist/agent/seed/types.js +15 -0
  148. package/dist/agent/seed/types.js.map +1 -0
  149. package/dist/agent/sessionStateMachine.d.ts +78 -0
  150. package/dist/agent/sessionStateMachine.d.ts.map +1 -0
  151. package/dist/agent/sessionStateMachine.js +182 -0
  152. package/dist/agent/sessionStateMachine.js.map +1 -0
  153. package/dist/agent/taskTableRenderer.d.ts +51 -0
  154. package/dist/agent/taskTableRenderer.d.ts.map +1 -0
  155. package/dist/agent/taskTableRenderer.js +115 -0
  156. package/dist/agent/taskTableRenderer.js.map +1 -0
  157. package/dist/agent/toolExecutor.d.ts +255 -9
  158. package/dist/agent/toolExecutor.d.ts.map +1 -1
  159. package/dist/agent/toolExecutor.js +956 -20
  160. package/dist/agent/toolExecutor.js.map +1 -1
  161. package/dist/agent/toolLedger.d.ts +120 -0
  162. package/dist/agent/toolLedger.d.ts.map +1 -0
  163. package/dist/agent/toolLedger.js +149 -0
  164. package/dist/agent/toolLedger.js.map +1 -0
  165. package/dist/agent/toolResultCache.d.ts +134 -0
  166. package/dist/agent/toolResultCache.d.ts.map +1 -0
  167. package/dist/agent/toolResultCache.js +243 -0
  168. package/dist/agent/toolResultCache.js.map +1 -0
  169. package/dist/agent/toolResultOffload.d.ts +59 -0
  170. package/dist/agent/toolResultOffload.d.ts.map +1 -0
  171. package/dist/agent/toolResultOffload.js +70 -0
  172. package/dist/agent/toolResultOffload.js.map +1 -0
  173. package/dist/agent/toolRunner.d.ts +59 -0
  174. package/dist/agent/toolRunner.d.ts.map +1 -0
  175. package/dist/agent/toolRunner.js +147 -0
  176. package/dist/agent/toolRunner.js.map +1 -0
  177. package/dist/agent/tracer.d.ts +99 -107
  178. package/dist/agent/tracer.d.ts.map +1 -1
  179. package/dist/agent/tracer.js +24 -69
  180. package/dist/agent/tracer.js.map +1 -1
  181. package/dist/agent/types.d.ts +512 -162
  182. package/dist/agent/types.d.ts.map +1 -1
  183. package/dist/agent/types.js +2 -51
  184. package/dist/agent/types.js.map +1 -1
  185. package/dist/code-exec/codeExecutionProvider.d.ts +16 -0
  186. package/dist/code-exec/codeExecutionProvider.d.ts.map +1 -0
  187. package/dist/code-exec/codeExecutionProvider.js +57 -0
  188. package/dist/code-exec/codeExecutionProvider.js.map +1 -0
  189. package/dist/code-exec/types.d.ts +37 -0
  190. package/dist/code-exec/types.d.ts.map +1 -0
  191. package/dist/code-exec/types.js +12 -0
  192. package/dist/code-exec/types.js.map +1 -0
  193. package/dist/config/expandEnvVars.d.ts +22 -0
  194. package/dist/config/expandEnvVars.d.ts.map +1 -0
  195. package/dist/config/expandEnvVars.js +38 -0
  196. package/dist/config/expandEnvVars.js.map +1 -0
  197. package/dist/config/loader.d.ts +59 -242
  198. package/dist/config/loader.d.ts.map +1 -1
  199. package/dist/config/loader.js +237 -194
  200. package/dist/config/loader.js.map +1 -1
  201. package/dist/index.d.ts +69 -74
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +93 -63
  204. package/dist/index.js.map +1 -1
  205. package/dist/llm/abortSignal.d.ts +2 -3
  206. package/dist/llm/abortSignal.d.ts.map +1 -1
  207. package/dist/llm/abortSignal.js +2 -3
  208. package/dist/llm/abortSignal.js.map +1 -1
  209. package/dist/llm/factory.d.ts +29 -25
  210. package/dist/llm/factory.d.ts.map +1 -1
  211. package/dist/llm/factory.js +34 -69
  212. package/dist/llm/factory.js.map +1 -1
  213. package/dist/llm/openaiCompatible.d.ts +25 -40
  214. package/dist/llm/openaiCompatible.d.ts.map +1 -1
  215. package/dist/llm/openaiCompatible.js +181 -129
  216. package/dist/llm/openaiCompatible.js.map +1 -1
  217. package/dist/llm/provider.d.ts +29 -42
  218. package/dist/llm/provider.d.ts.map +1 -1
  219. package/dist/llm/provider.js +11 -10
  220. package/dist/llm/provider.js.map +1 -1
  221. package/dist/llm/types.d.ts +54 -11
  222. package/dist/llm/types.d.ts.map +1 -1
  223. package/dist/llm/types.js +0 -5
  224. package/dist/llm/types.js.map +1 -1
  225. package/dist/logging/logger.d.ts +8 -29
  226. package/dist/logging/logger.d.ts.map +1 -1
  227. package/dist/logging/logger.js +43 -233
  228. package/dist/logging/logger.js.map +1 -1
  229. package/dist/logging/loggerInterface.d.ts +9 -8
  230. package/dist/logging/loggerInterface.d.ts.map +1 -1
  231. package/dist/memory/gcService.d.ts +164 -0
  232. package/dist/memory/gcService.d.ts.map +1 -0
  233. package/dist/memory/gcService.js +215 -0
  234. package/dist/memory/gcService.js.map +1 -0
  235. package/dist/memory/governance.d.ts +17 -0
  236. package/dist/memory/governance.d.ts.map +1 -0
  237. package/dist/memory/governance.js +17 -0
  238. package/dist/memory/governance.js.map +1 -0
  239. package/dist/memory/inMemoryRoundStore.d.ts +124 -0
  240. package/dist/memory/inMemoryRoundStore.d.ts.map +1 -0
  241. package/dist/memory/inMemoryRoundStore.js +216 -0
  242. package/dist/memory/inMemoryRoundStore.js.map +1 -0
  243. package/dist/memory/inMemorySessionStore.d.ts +92 -0
  244. package/dist/memory/inMemorySessionStore.d.ts.map +1 -0
  245. package/dist/memory/inMemorySessionStore.js +219 -0
  246. package/dist/memory/inMemorySessionStore.js.map +1 -0
  247. package/dist/memory/inMemorySessionViewLoader.d.ts +89 -0
  248. package/dist/memory/inMemorySessionViewLoader.d.ts.map +1 -0
  249. package/dist/memory/inMemorySessionViewLoader.js +183 -0
  250. package/dist/memory/inMemorySessionViewLoader.js.map +1 -0
  251. package/dist/memory/inMemoryStorage.d.ts +26 -141
  252. package/dist/memory/inMemoryStorage.d.ts.map +1 -1
  253. package/dist/memory/inMemoryStorage.js +48 -187
  254. package/dist/memory/inMemoryStorage.js.map +1 -1
  255. package/dist/memory/keywordsTouch.d.ts +20 -0
  256. package/dist/memory/keywordsTouch.d.ts.map +1 -0
  257. package/dist/memory/keywordsTouch.js +25 -0
  258. package/dist/memory/keywordsTouch.js.map +1 -0
  259. package/dist/memory/lockManager.d.ts +7 -2
  260. package/dist/memory/lockManager.d.ts.map +1 -1
  261. package/dist/memory/lockManager.js +35 -11
  262. package/dist/memory/lockManager.js.map +1 -1
  263. package/dist/memory/projectManager.d.ts +30 -128
  264. package/dist/memory/projectManager.d.ts.map +1 -1
  265. package/dist/memory/projectManager.js +39 -171
  266. package/dist/memory/projectManager.js.map +1 -1
  267. package/dist/memory/projectRegistry.d.ts +22 -37
  268. package/dist/memory/projectRegistry.d.ts.map +1 -1
  269. package/dist/memory/projectRegistry.js +78 -85
  270. package/dist/memory/projectRegistry.js.map +1 -1
  271. package/dist/memory/roundStore.d.ts +629 -0
  272. package/dist/memory/roundStore.d.ts.map +1 -0
  273. package/dist/memory/roundStore.js +116 -0
  274. package/dist/memory/roundStore.js.map +1 -0
  275. package/dist/memory/sessionStore.d.ts +70 -61
  276. package/dist/memory/sessionStore.d.ts.map +1 -1
  277. package/dist/memory/sessionStore.js +23 -1
  278. package/dist/memory/sessionStore.js.map +1 -1
  279. package/dist/memory/sessionViewLoader.d.ts +141 -0
  280. package/dist/memory/sessionViewLoader.d.ts.map +1 -0
  281. package/dist/memory/sessionViewLoader.js +65 -0
  282. package/dist/memory/sessionViewLoader.js.map +1 -0
  283. package/dist/memory/sourceValidation.d.ts +18 -9
  284. package/dist/memory/sourceValidation.d.ts.map +1 -1
  285. package/dist/memory/sourceValidation.js +35 -29
  286. package/dist/memory/sourceValidation.js.map +1 -1
  287. package/dist/memory/storageInterface.d.ts +45 -126
  288. package/dist/memory/storageInterface.d.ts.map +1 -1
  289. package/dist/memory/types.d.ts +82 -168
  290. package/dist/memory/types.d.ts.map +1 -1
  291. package/dist/memory/types.js +116 -79
  292. package/dist/memory/types.js.map +1 -1
  293. package/dist/project-search/projectSearchProvider.d.ts +29 -0
  294. package/dist/project-search/projectSearchProvider.d.ts.map +1 -0
  295. package/dist/project-search/projectSearchProvider.js +57 -0
  296. package/dist/project-search/projectSearchProvider.js.map +1 -0
  297. package/dist/project-search/terms.d.ts +14 -0
  298. package/dist/project-search/terms.d.ts.map +1 -0
  299. package/dist/project-search/terms.js +68 -0
  300. package/dist/project-search/terms.js.map +1 -0
  301. package/dist/project-search/types.d.ts +139 -0
  302. package/dist/project-search/types.d.ts.map +1 -0
  303. package/dist/project-search/types.js +8 -0
  304. package/dist/project-search/types.js.map +1 -0
  305. package/dist/role-pack/capabilityMap.d.ts +28 -0
  306. package/dist/role-pack/capabilityMap.d.ts.map +1 -0
  307. package/dist/role-pack/capabilityMap.js +56 -0
  308. package/dist/role-pack/capabilityMap.js.map +1 -0
  309. package/dist/role-pack/constants.d.ts +21 -0
  310. package/dist/role-pack/constants.d.ts.map +1 -0
  311. package/dist/role-pack/constants.js +21 -0
  312. package/dist/role-pack/constants.js.map +1 -0
  313. package/dist/role-pack/rolePackManager.d.ts +269 -0
  314. package/dist/role-pack/rolePackManager.d.ts.map +1 -0
  315. package/dist/role-pack/rolePackManager.js +1082 -0
  316. package/dist/role-pack/rolePackManager.js.map +1 -0
  317. package/dist/role-pack/strategyKeys.d.ts +100 -0
  318. package/dist/role-pack/strategyKeys.d.ts.map +1 -0
  319. package/dist/role-pack/strategyKeys.js +184 -0
  320. package/dist/role-pack/strategyKeys.js.map +1 -0
  321. package/dist/role-pack/strategyResolver.d.ts +109 -0
  322. package/dist/role-pack/strategyResolver.d.ts.map +1 -0
  323. package/dist/role-pack/strategyResolver.js +304 -0
  324. package/dist/role-pack/strategyResolver.js.map +1 -0
  325. package/dist/role-pack/types.d.ts +237 -0
  326. package/dist/role-pack/types.d.ts.map +1 -0
  327. package/dist/role-pack/types.js +8 -0
  328. package/dist/role-pack/types.js.map +1 -0
  329. package/dist/role-pack/validator.d.ts +38 -0
  330. package/dist/role-pack/validator.d.ts.map +1 -0
  331. package/dist/role-pack/validator.js +436 -0
  332. package/dist/role-pack/validator.js.map +1 -0
  333. package/dist/security/pathGuard.d.ts +69 -72
  334. package/dist/security/pathGuard.d.ts.map +1 -1
  335. package/dist/security/pathGuard.js +151 -131
  336. package/dist/security/pathGuard.js.map +1 -1
  337. package/dist/skill/skillLayer3.d.ts +91 -0
  338. package/dist/skill/skillLayer3.d.ts.map +1 -0
  339. package/dist/skill/skillLayer3.js +104 -0
  340. package/dist/skill/skillLayer3.js.map +1 -0
  341. package/dist/skill/skillManager.d.ts +127 -56
  342. package/dist/skill/skillManager.d.ts.map +1 -1
  343. package/dist/skill/skillManager.js +318 -163
  344. package/dist/skill/skillManager.js.map +1 -1
  345. package/dist/skill/skillScriptRunner.d.ts +97 -0
  346. package/dist/skill/skillScriptRunner.d.ts.map +1 -0
  347. package/dist/skill/skillScriptRunner.js +271 -0
  348. package/dist/skill/skillScriptRunner.js.map +1 -0
  349. package/dist/skill/types.d.ts +73 -17
  350. package/dist/skill/types.d.ts.map +1 -1
  351. package/dist/skill/types.js +5 -1
  352. package/dist/skill/types.js.map +1 -1
  353. package/dist/utils/array.d.ts +5 -6
  354. package/dist/utils/array.d.ts.map +1 -1
  355. package/dist/utils/array.js +6 -6
  356. package/dist/utils/array.js.map +1 -1
  357. package/dist/utils/atomicWrite.d.ts +8 -0
  358. package/dist/utils/atomicWrite.d.ts.map +1 -0
  359. package/dist/utils/atomicWrite.js +31 -0
  360. package/dist/utils/atomicWrite.js.map +1 -0
  361. package/dist/utils/backgroundTask.d.ts +38 -0
  362. package/dist/utils/backgroundTask.d.ts.map +1 -0
  363. package/dist/utils/backgroundTask.js +117 -0
  364. package/dist/utils/backgroundTask.js.map +1 -0
  365. package/dist/utils/configResourceManager.d.ts +79 -0
  366. package/dist/utils/configResourceManager.d.ts.map +1 -0
  367. package/dist/utils/configResourceManager.js +150 -0
  368. package/dist/utils/configResourceManager.js.map +1 -0
  369. package/dist/utils/errors.d.ts +48 -56
  370. package/dist/utils/errors.d.ts.map +1 -1
  371. package/dist/utils/errors.js +60 -58
  372. package/dist/utils/errors.js.map +1 -1
  373. package/dist/utils/eventEmitter.d.ts +141 -63
  374. package/dist/utils/eventEmitter.d.ts.map +1 -1
  375. package/dist/utils/eventEmitter.js +50 -35
  376. package/dist/utils/eventEmitter.js.map +1 -1
  377. package/dist/utils/fileSafe.d.ts +20 -0
  378. package/dist/utils/fileSafe.d.ts.map +1 -0
  379. package/dist/utils/fileSafe.js +56 -0
  380. package/dist/utils/fileSafe.js.map +1 -0
  381. package/dist/utils/frontmatter.d.ts +2 -3
  382. package/dist/utils/frontmatter.d.ts.map +1 -1
  383. package/dist/utils/frontmatter.js +6 -5
  384. package/dist/utils/frontmatter.js.map +1 -1
  385. package/dist/utils/hash.d.ts +12 -0
  386. package/dist/utils/hash.d.ts.map +1 -0
  387. package/dist/utils/hash.js +27 -0
  388. package/dist/utils/hash.js.map +1 -0
  389. package/dist/utils/http.d.ts +17 -0
  390. package/dist/utils/http.d.ts.map +1 -0
  391. package/dist/utils/http.js +29 -0
  392. package/dist/utils/http.js.map +1 -0
  393. package/dist/utils/json.d.ts +1 -1
  394. package/dist/utils/json.js +1 -1
  395. package/dist/utils/math.d.ts +34 -1
  396. package/dist/utils/math.d.ts.map +1 -1
  397. package/dist/utils/math.js +47 -14
  398. package/dist/utils/math.js.map +1 -1
  399. package/dist/utils/objects.d.ts +2 -3
  400. package/dist/utils/objects.d.ts.map +1 -1
  401. package/dist/utils/objects.js +2 -3
  402. package/dist/utils/objects.js.map +1 -1
  403. package/dist/utils/recallDefaults.d.ts +16 -0
  404. package/dist/utils/recallDefaults.d.ts.map +1 -0
  405. package/dist/utils/recallDefaults.js +16 -0
  406. package/dist/utils/recallDefaults.js.map +1 -0
  407. package/dist/utils/safeTimer.d.ts +8 -36
  408. package/dist/utils/safeTimer.d.ts.map +1 -1
  409. package/dist/utils/safeTimer.js +9 -37
  410. package/dist/utils/safeTimer.js.map +1 -1
  411. package/dist/utils/scanner.d.ts +97 -17
  412. package/dist/utils/scanner.d.ts.map +1 -1
  413. package/dist/utils/scanner.js +269 -47
  414. package/dist/utils/scanner.js.map +1 -1
  415. package/dist/utils/segmenter.d.ts +20 -29
  416. package/dist/utils/segmenter.d.ts.map +1 -1
  417. package/dist/utils/segmenter.js +230 -88
  418. package/dist/utils/segmenter.js.map +1 -1
  419. package/dist/utils/strings.d.ts +15 -32
  420. package/dist/utils/strings.d.ts.map +1 -1
  421. package/dist/utils/strings.js +25 -44
  422. package/dist/utils/strings.js.map +1 -1
  423. package/dist/utils/time.d.ts +37 -0
  424. package/dist/utils/time.d.ts.map +1 -1
  425. package/dist/utils/time.js +56 -0
  426. package/dist/utils/time.js.map +1 -1
  427. package/dist/utils/toError.d.ts +13 -1
  428. package/dist/utils/toError.d.ts.map +1 -1
  429. package/dist/utils/toError.js +18 -1
  430. package/dist/utils/toError.js.map +1 -1
  431. package/dist/web-fetch/fetchWebFetchProvider.d.ts +14 -0
  432. package/dist/web-fetch/fetchWebFetchProvider.d.ts.map +1 -0
  433. package/dist/web-fetch/fetchWebFetchProvider.js +69 -0
  434. package/dist/web-fetch/fetchWebFetchProvider.js.map +1 -0
  435. package/dist/web-fetch/types.d.ts +30 -0
  436. package/dist/web-fetch/types.d.ts.map +1 -0
  437. package/dist/web-fetch/types.js +7 -0
  438. package/dist/web-fetch/types.js.map +1 -0
  439. package/dist/web-fetch/webFetchProvider.d.ts +10 -0
  440. package/dist/web-fetch/webFetchProvider.d.ts.map +1 -0
  441. package/dist/web-fetch/webFetchProvider.js +28 -0
  442. package/dist/web-fetch/webFetchProvider.js.map +1 -0
  443. package/dist/web-search/fetchWebSearchProvider.d.ts +27 -0
  444. package/dist/web-search/fetchWebSearchProvider.d.ts.map +1 -0
  445. package/dist/web-search/fetchWebSearchProvider.js +199 -0
  446. package/dist/web-search/fetchWebSearchProvider.js.map +1 -0
  447. package/dist/web-search/types.d.ts +46 -0
  448. package/dist/web-search/types.d.ts.map +1 -0
  449. package/dist/web-search/types.js +2 -0
  450. package/dist/{persona → web-search}/types.js.map +1 -1
  451. package/dist/web-search/webSearchProvider.d.ts +10 -0
  452. package/dist/web-search/webSearchProvider.d.ts.map +1 -0
  453. package/dist/web-search/webSearchProvider.js +30 -0
  454. package/dist/web-search/webSearchProvider.js.map +1 -0
  455. package/docs/architecture/role-pack-authoring-guide.md +243 -0
  456. package/docs/architecture/role-pack-spec.md +767 -0
  457. package/docs/memora-api-reference.md +1175 -0
  458. package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +843 -0
  459. package/docs/role-pack-/345/274/200/346/224/276/351/224/256/346/214/207/345/215/227.md +148 -0
  460. package/package.json +105 -83
  461. package/role-packs/README.md +41 -0
  462. package/role-packs/memora/345/212/251/346/211/213/manifest.json +12 -0
  463. package/role-packs/memora/345/212/251/346/211/213/persona.md +12 -0
  464. package/role-packs/memora/345/212/251/346/211/213/rules.md +4 -0
  465. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/manifest.json +44 -0
  466. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/persona.md +46 -0
  467. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/rules.md +15 -0
  468. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/character-sheet.md +34 -0
  469. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/core-extractor.md +37 -0
  470. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/craft-review.md +69 -0
  471. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/dialogue-craft.md +45 -0
  472. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/emotion-wave.md +46 -0
  473. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/empathy-craft.md +79 -0
  474. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/foreshadow.md +29 -0
  475. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/hotspot-mapping.md +121 -0
  476. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/idea-lever.md +42 -0
  477. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/logline-craft.md +47 -0
  478. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/longform-guard.md +64 -0
  479. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/need-finder.md +40 -0
  480. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/outline-expand.md +46 -0
  481. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/pacing-hook.md +34 -0
  482. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/polish-method.md +113 -0
  483. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/pov-discipline.md +79 -0
  484. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/SKILL.md +112 -0
  485. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/project-init/scripts/init-novel-project.mjs +441 -0
  486. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/relationship-web.md +47 -0
  487. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/scene-craft.md +46 -0
  488. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-bible.md +60 -0
  489. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/story-structure.md +36 -0
  490. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/style-weaver.md +109 -0
  491. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/three-layer-check.md +27 -0
  492. package/role-packs//345/205/261/351/270/243/345/260/217/350/257/264/345/256/266/skills/world-builder.md +41 -0
  493. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/manifest.json +48 -0
  494. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/persona.md +73 -0
  495. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/rules.md +19 -0
  496. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/api-doc-writer.md +40 -0
  497. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/doc-planner.md +42 -0
  498. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/nutrient-search.md +28 -0
  499. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/seed-finder.md +28 -0
  500. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/ssot-audit.md +34 -0
  501. package/role-packs//347/231/275/350/257/235/346/226/271/346/241/210/350/256/276/350/256/241/345/270/210/skills/style-guide.md +40 -0
  502. package/dist/agent/guardrail.d.ts +0 -47
  503. package/dist/agent/guardrail.d.ts.map +0 -1
  504. package/dist/agent/guardrail.js +0 -51
  505. package/dist/agent/guardrail.js.map +0 -1
  506. package/dist/agent/managers/autoConfigRefiner.d.ts +0 -39
  507. package/dist/agent/managers/autoConfigRefiner.d.ts.map +0 -1
  508. package/dist/agent/managers/autoConfigRefiner.js +0 -150
  509. package/dist/agent/managers/autoConfigRefiner.js.map +0 -1
  510. package/dist/agent/managers/configManager.d.ts +0 -125
  511. package/dist/agent/managers/configManager.d.ts.map +0 -1
  512. package/dist/agent/managers/configManager.js +0 -194
  513. package/dist/agent/managers/configManager.js.map +0 -1
  514. package/dist/agent/managers/insightExtractor.d.ts +0 -139
  515. package/dist/agent/managers/insightExtractor.d.ts.map +0 -1
  516. package/dist/agent/managers/insightExtractor.js +0 -401
  517. package/dist/agent/managers/insightExtractor.js.map +0 -1
  518. package/dist/agent/managers/memoryDecayScheduler.d.ts +0 -198
  519. package/dist/agent/managers/memoryDecayScheduler.d.ts.map +0 -1
  520. package/dist/agent/managers/memoryDecayScheduler.js +0 -349
  521. package/dist/agent/managers/memoryDecayScheduler.js.map +0 -1
  522. package/dist/agent/managers/relationBuilder.d.ts +0 -131
  523. package/dist/agent/managers/relationBuilder.d.ts.map +0 -1
  524. package/dist/agent/managers/relationBuilder.js +0 -192
  525. package/dist/agent/managers/relationBuilder.js.map +0 -1
  526. package/dist/agent/personaMatcher.d.ts +0 -29
  527. package/dist/agent/personaMatcher.d.ts.map +0 -1
  528. package/dist/agent/personaMatcher.js +0 -60
  529. package/dist/agent/personaMatcher.js.map +0 -1
  530. package/dist/agent/userFactExtractor.d.ts +0 -25
  531. package/dist/agent/userFactExtractor.d.ts.map +0 -1
  532. package/dist/agent/userFactExtractor.js +0 -81
  533. package/dist/agent/userFactExtractor.js.map +0 -1
  534. package/dist/eval/evalRunner.d.ts +0 -94
  535. package/dist/eval/evalRunner.d.ts.map +0 -1
  536. package/dist/eval/evalRunner.js +0 -141
  537. package/dist/eval/evalRunner.js.map +0 -1
  538. package/dist/eval/evalTypes.d.ts +0 -148
  539. package/dist/eval/evalTypes.d.ts.map +0 -1
  540. package/dist/eval/evalTypes.js +0 -118
  541. package/dist/eval/evalTypes.js.map +0 -1
  542. package/dist/eval/scenarios.d.ts +0 -33
  543. package/dist/eval/scenarios.d.ts.map +0 -1
  544. package/dist/eval/scenarios.js +0 -109
  545. package/dist/eval/scenarios.js.map +0 -1
  546. package/dist/llm/embedding.d.ts +0 -67
  547. package/dist/llm/embedding.d.ts.map +0 -1
  548. package/dist/llm/embedding.js +0 -184
  549. package/dist/llm/embedding.js.map +0 -1
  550. package/dist/memory/hybridMerge.d.ts +0 -54
  551. package/dist/memory/hybridMerge.d.ts.map +0 -1
  552. package/dist/memory/hybridMerge.js +0 -36
  553. package/dist/memory/hybridMerge.js.map +0 -1
  554. package/dist/memory/inMemoryRelationStore.d.ts +0 -51
  555. package/dist/memory/inMemoryRelationStore.d.ts.map +0 -1
  556. package/dist/memory/inMemoryRelationStore.js +0 -65
  557. package/dist/memory/inMemoryRelationStore.js.map +0 -1
  558. package/dist/memory/loader.d.ts +0 -49
  559. package/dist/memory/loader.d.ts.map +0 -1
  560. package/dist/memory/loader.js +0 -93
  561. package/dist/memory/loader.js.map +0 -1
  562. package/dist/memory/recall.d.ts +0 -66
  563. package/dist/memory/recall.d.ts.map +0 -1
  564. package/dist/memory/recall.js +0 -142
  565. package/dist/memory/recall.js.map +0 -1
  566. package/dist/memory/relationStore.d.ts +0 -78
  567. package/dist/memory/relationStore.d.ts.map +0 -1
  568. package/dist/memory/relationStore.js +0 -2
  569. package/dist/memory/relationStore.js.map +0 -1
  570. package/dist/memory/store.d.ts +0 -50
  571. package/dist/memory/store.d.ts.map +0 -1
  572. package/dist/memory/store.js +0 -161
  573. package/dist/memory/store.js.map +0 -1
  574. package/dist/memory/userProfile.d.ts +0 -156
  575. package/dist/memory/userProfile.d.ts.map +0 -1
  576. package/dist/memory/userProfile.js +0 -320
  577. package/dist/memory/userProfile.js.map +0 -1
  578. package/dist/memory/vectorStore.d.ts +0 -185
  579. package/dist/memory/vectorStore.d.ts.map +0 -1
  580. package/dist/memory/vectorStore.js +0 -257
  581. package/dist/memory/vectorStore.js.map +0 -1
  582. package/dist/persona/personaManager.d.ts +0 -160
  583. package/dist/persona/personaManager.d.ts.map +0 -1
  584. package/dist/persona/personaManager.js +0 -414
  585. package/dist/persona/personaManager.js.map +0 -1
  586. package/dist/persona/types.d.ts +0 -32
  587. package/dist/persona/types.d.ts.map +0 -1
  588. package/dist/persona/types.js +0 -5
@@ -1,9 +1,319 @@
1
- import { toolError, configError, MemoraError, ToolErrorCode, toError } from '../utils/errors.js';
1
+ import { toolError, configError, MemoraError, ToolErrorCode } from '../utils/errors.js';
2
+ import { toError } from '../utils/toError.js';
2
3
  import { logger } from '../logging/logger.js';
3
4
  import { truncate } from '../utils/strings.js';
4
- import { BUILTIN_TOOLS } from '../agent/builtinTools.js';
5
+ import { ALL_BUILTIN_TOOL_DEFS, BUILTIN_TOOLS, WEB_SEARCH_TOOL, WEB_FETCH_TOOL, WEB_FETCH_CONTENT_MAX_LEN, WEB_FETCH_CONTENT_DEFAULT_LEN, RUN_CODE_TOOL, SEARCH_PROJECT_TOOL, } from '../agent/builtinTools.js';
5
6
  import { BuiltinToolHandlers } from '../agent/builtinToolHandlers.js';
6
- export { BUILTIN_TOOLS } from '../agent/builtinTools.js';
7
+ import { safeSearch } from '../web-search/webSearchProvider.js';
8
+ import { safeFetch } from '../web-fetch/webFetchProvider.js';
9
+ import { safeExecuteCode } from '../code-exec/codeExecutionProvider.js';
10
+ import { formatExecutionResult, formatScriptResult, runSkillScript, } from '../skill/skillScriptRunner.js';
11
+ import { resolveSafePath, inferRuntimeFromExt } from '../utils/scanner.js';
12
+ import { safeSearchProjectFiles, safeSearchProjectText, } from '../project-search/projectSearchProvider.js';
13
+ import { buildSearchTerms } from '../project-search/terms.js';
14
+ export { BUILTIN_TOOLS, BUILTIN_TOOL_IDEMPOTENCY } from '../agent/builtinTools.js';
15
+ // ─── web_search 注入防御常量 ─────────────────
16
+ /** web_search 查询串最大长度(防过长/恶意查询滥用) */
17
+ const WEB_SEARCH_QUERY_MAX_LEN = 200;
18
+ /** web_search 单条结果字段最大长度(防长上下文注入) */
19
+ const WEB_SEARCH_RESULT_MAX_LEN = 500;
20
+ // ─── web_fetch / run_code 注入防御常量 ─────────────────
21
+ /** web_fetch 的 url 最大长度(防超长/恶意 URL 滥用) */
22
+ const WEB_FETCH_URL_MAX_LEN = 2000;
23
+ /** run_code 的 code 最大长度(防超长代码滥用) */
24
+ const RUN_CODE_CODE_MAX_LEN = 50_000;
25
+ /** run_code 的 language 最大长度(防超长语言名滥用) */
26
+ const RUN_CODE_LANGUAGE_MAX_LEN = 32;
27
+ /** run_code 单次结果字段最大长度(防长输出撑爆上下文) */
28
+ const RUN_CODE_RESULT_MAX_LEN = 20_000;
29
+ // ─── run_code script_path 模式:脚本扩展名 → 运行时推断 ─────────────────
30
+ // 扩展名 → runtime 映射的单一真理源在 utils/scanner.ts(SCRIPT_RUNTIME_MAP/inferRuntimeFromExt),
31
+ // 此处不复制表;'.ts' 是否可靠支持在 scanner.ts 的映射处唯一声明(node 无法直接解析 TS,
32
+ // 实际可用性取决于宿主执行器是否具备转译能力)。未知扩展名兜底 node(script_path 模式下
33
+ // 宁尝试执行,能否跑由宿主执行器裁决)。
34
+ /**
35
+ * 脚本运行时白名单三档(node/python/shell)
36
+ *
37
+ * run_project_script 的内核子进程执行器只接受三档运行时;
38
+ * 从扩展名推断的语言若落在白名单外一律兜底 'node'(推断即可信来源,不规则值不回传执行器)。
39
+ */
40
+ function normalizeScriptRuntime(language) {
41
+ if (language === 'python')
42
+ return 'python';
43
+ if (language === 'shell')
44
+ return 'shell';
45
+ return 'node';
46
+ }
47
+ // ─── run_skill_script 注入防御常量 ─────────────────
48
+ /** run_skill_script 单次结果最大长度(防脚本刷屏撑爆上下文;对齐 run_code 的 RUN_CODE_RESULT_MAX_LEN) */
49
+ const RUN_SCRIPT_RESULT_MAX_LEN = 20_000;
50
+ // ─── search_project 注入防御常量 ─────────────────
51
+ /** search_project 的 query 最大长度(防超长 glob/关键词滥用) */
52
+ const PROJECT_SEARCH_QUERY_MAX_LEN = 500;
53
+ /** search_project 的 include/exclude glob 最大长度(防超长模式滥用) */
54
+ const PROJECT_SEARCH_GLOB_MAX_LEN = 1000;
55
+ /** search_project 单次返回最大条数(防结果刷屏撑爆上下文;对齐 list_dir 的条目上限;导出供宿主实现方对齐) */
56
+ export const PROJECT_SEARCH_RESULT_MAX_LEN = 100;
57
+ /**
58
+ * search_project 预算下探档位(与对话预算联动)
59
+ *
60
+ * 剩余对话预算(token)越紧,结果条数上限越低——防止搜索结果撑爆上下文。
61
+ * 与 builtinTools 的 maxResults「默认 20 / 最大 100」语义叠加(取更小者):
62
+ * LLM 传入的 maxResults 仍按其意愿生效,但不得超过当前预算档位的 cap。
63
+ * 档位从高到低遍历,首个满足 remaining >= minRemaining 的档位生效。
64
+ */
65
+ const SEARCH_BUDGET_TIERS = [
66
+ { minRemaining: 40_000, cap: 100 }, // 预算充裕:维持内核硬上限
67
+ { minRemaining: 16_000, cap: 30 }, // 中等:降档,结果仍在可精读范围
68
+ { minRemaining: 6_000, cap: 10 }, // 偏紧:只保留最高价值命中
69
+ { minRemaining: 0, cap: 3 }, // 极紧:仅兜底条数
70
+ ];
71
+ /**
72
+ * 按剩余对话预算下探 search_project 结果条数上限
73
+ *
74
+ * @param remainingTokens 剩余对话预算(token);undefined 表示无预算信息(未装配 loop / 未 prepare)
75
+ * @returns 预算档位 cap;无预算信息时返回硬上限(不下探)
76
+ */
77
+ function computeBudgetCappedMaxResults(remainingTokens) {
78
+ if (remainingTokens === undefined)
79
+ return PROJECT_SEARCH_RESULT_MAX_LEN;
80
+ const tier = SEARCH_BUDGET_TIERS.find((t) => remainingTokens >= t.minRemaining);
81
+ return tier?.cap ?? SEARCH_BUDGET_TIERS.at(-1).cap;
82
+ }
83
+ /**
84
+ * search_project(content 模式)结果格式化 —— 诚实化收口
85
+ *
86
+ * 三态分流(对齐 ripgrep 的 exit 0 / 1 / 2):
87
+ * - `failed` → 检索**未完成**,不得表述为"未找到"(检索失败:搜索坏了 ≠ 项目里没有);
88
+ * - `matches.length === 0` → 零命中,但必须交代「这个零为什么可以信」:结果截断 / 只检索了前一部分(部分检索)/ 读取失败 / 已放宽匹配;
89
+ * - 有命中 → 列结果 + 标注放宽 / 截断 / 单文件上限。
90
+ *
91
+ * ⚠️ 放宽用词**只**从 `result.termsUsed` 取(**不**回退到调用方自己下发的 `terms`):两份副本要
92
+ * 保持一致就是双轨镜像;唯一真值 = 宿主回报的"实际用了哪些词"。
93
+ * ⚠️ 文案不重复宿主内部的魔法数(如单文件上限 3):内核从不扫描,把宿主的内部预算写进内核文案
94
+ * 就是跨层常量镜像,故只说"被单文件上限截断"而不说"仅显示前 3 条"。
95
+ */
96
+ function formatProjectTextSearch(result, query) {
97
+ if (result.failed) {
98
+ return (`(项目内容检索未完成(超时或出错),"${query}" 是否存在尚无结论——这不是"没搜到"。` +
99
+ '可重试一次,或换更短/更具体的关键词)');
100
+ }
101
+ const scanned = result.scannedFiles ?? 0;
102
+ const partial = result.partialReadFiles ?? 0;
103
+ const unreadable = result.unreadableSkipped ?? 0;
104
+ const relaxedNote = result.relaxed
105
+ ? `;整串未命中,已按分词放宽为 ${(result.termsUsed ?? []).join('、')} 后仍未命中`
106
+ : '';
107
+ if (result.matches.length === 0) {
108
+ // 零命中的"可信度缺口":有哪些文件/范围根本没被检索过
109
+ const gaps = [];
110
+ if (result.truncated) {
111
+ gaps.push(result.truncatedBy === 'files'
112
+ ? `已扫描 ${scanned} 个文件后达扫描上限,项目仍有未检索的文件`
113
+ : '结果与扫描均已达上限,项目可能仍有未检索的文件');
114
+ }
115
+ if (partial > 0)
116
+ gaps.push(`另有 ${partial} 个文件只检索了前一部分(文件过大,后半段未覆盖)`);
117
+ if (unreadable > 0)
118
+ gaps.push(`另有 ${unreadable} 个文件读取失败,完全未参与检索`);
119
+ if (gaps.length > 0) {
120
+ return (`(未找到包含 "${query}" 的文件${relaxedNote};但${gaps.join(',')}——` +
121
+ '因此这个"未找到"不等于"不存在",可缩小 exclude 范围或换更具体的关键词后重试)');
122
+ }
123
+ return `(未找到包含 "${query}" 的文件${relaxedNote};已检索 ${scanned} 个文件,无截断、无跳过)`;
124
+ }
125
+ const lines = result.matches
126
+ .map((m, i) => `${i + 1}. ${m.path}${m.line ? `:${m.line}` : ''}${m.preview ? ` — ${m.preview}` : ''}`)
127
+ .join('\n');
128
+ const notes = [];
129
+ if (result.relaxed) {
130
+ notes.push(`整串未精确命中,以下为按分词放宽(${(result.termsUsed ?? []).join('、')})后的匹配,可能不是精确命中`);
131
+ }
132
+ if (result.perFileCapped) {
133
+ notes.push('部分文件的命中被单文件上限截断(该文件内可能还有更多命中,可用 read_file 精读)');
134
+ }
135
+ if (result.truncated) {
136
+ notes.push(result.truncatedBy === 'files'
137
+ ? `已扫描 ${scanned} 个文件后达扫描上限,项目可能仍有更多匹配`
138
+ : `结果可能已截断:仅返回前 ${result.matches.length} 条,项目可能仍有更多匹配`);
139
+ }
140
+ if (partial > 0)
141
+ notes.push(`另有 ${partial} 个文件只检索了前一部分(文件过大,后半段未覆盖)`);
142
+ if (unreadable > 0)
143
+ notes.push(`另有 ${unreadable} 个文件读取失败,完全未参与检索`);
144
+ return notes.length > 0 ? `${lines}\n(${notes.join(';')})` : lines;
145
+ }
146
+ /**
147
+ * name 模式检索结果格式化(语义对齐 content 的两轮放宽)
148
+ *
149
+ * name 模式两轮语义:先按 query 原样作 glob 精确匹配;零命中且 query 不含 glob 元字符、
150
+ * 且内核下发了 `terms` 时,宿主按名称子串逐词 OR 放宽。故零命中同样要交代这个零是否可信:
151
+ * 放宽过(可信零)/ 检索失败(无结论)/ 截断过(不可信零)。
152
+ *
153
+ * ⚠️ 放宽用词**只**从 `result.termsUsed` 取(同 content,不回退到内核自己下发的 `terms`):
154
+ * 唯一真值 = 宿主回报的"实际用了哪些词"。
155
+ */
156
+ function formatProjectFileSearch(result, query) {
157
+ if (result.failed) {
158
+ return (`(项目文件检索未完成(超时或出错),"${query}" 是否存在尚无结论——这不是"没搜到"。` +
159
+ '可重试一次,或换更具体的文件名模式)');
160
+ }
161
+ // 放宽用词只取宿主回报,文案与 content 同构("整串"→"整串 glob"的自洽表述)
162
+ const relaxedNote = result.relaxed
163
+ ? `;整串未精确命中,已按名称放宽为 ${(result.termsUsed ?? []).join('、')} 后仍未命中`
164
+ : '';
165
+ if (result.matches.length === 0) {
166
+ // 零命中的"可信度缺口":检索被截断 → 不一定是"不存在"(name 模式截断仅 results 主因)
167
+ if (result.truncated) {
168
+ return (`(未在项目中找到匹配 "${query}" 的文件${relaxedNote};但检索已达上限、项目仍有未检索的文件——` +
169
+ '这个"未找到"不等于"不存在",可换更具体的 glob 重试)');
170
+ }
171
+ // 可信零:原文案不变(未放宽 = 原样 glob 精确匹配的零;放宽过则追加放宽说明)
172
+ return `(未在项目中找到匹配 "${query}" 的文件${relaxedNote})`;
173
+ }
174
+ const fileLines = result.matches.map((m, i) => `${i + 1}. ${m.path}`).join('\n');
175
+ const notes = [];
176
+ if (result.relaxed) {
177
+ notes.push(`整串未精确命中,以下为按名称放宽(${(result.termsUsed ?? []).join('、')})后的匹配,可能不是精确命中`);
178
+ }
179
+ // name 模式由宿主 findFiles 按 maxResults 截断:达上限即提示可能截断(与 content 文案同构)
180
+ if (result.truncated) {
181
+ notes.push(`结果可能已截断:仅返回前 ${result.matches.length} 条,项目可能仍有更多匹配;如需精确定位请换更具体的 glob`);
182
+ }
183
+ return notes.length > 0 ? `${fileLines}\n(${notes.join(';')})` : fileLines;
184
+ }
185
+ // ─── read_skill / read_resource 注入防御常量 ─────────────────
186
+ /** read_skill 技能正文单次返回最大长度(防超长技能正文注入上下文;静态角色包内容走字符上限,与 read_file 的 token 预算分段是不同通道) */
187
+ const SKILL_CONTENT_MAX_LEN = 50_000;
188
+ /** read_resource 资源正文单次返回最大长度(防超长资源注入上下文;与 read_skill 同标准) */
189
+ const RESOURCE_CONTENT_MAX_LEN = 50_000;
190
+ /**
191
+ * 去 ANSI 转义序列与不可打印控制字符(**不限长**)
192
+ *
193
+ * 净化规则的单一真理源。外部不可信内容(web_search / 子进程输出 / 文件正文)注入 LLM 上下文前
194
+ * 须剥掉控制字符,防转义/终端注入。
195
+ *
196
+ * @param text 原始文本
197
+ * @returns 净化后的文本
198
+ */
199
+ export function stripControlChars(text) {
200
+ // 去 ANSI 转义序列(CSI:ESC [ 参数 + 终结符;子进程经 env 继承可能拿到
201
+ // FORCE_COLOR 输出色码,单剥 ESC 会留 `[33m` 残渣——整个序列须剥净)
202
+ const withoutAnsi = text.replace(/\u001B\[[0-9;?]*[a-zA-Z]/g, '');
203
+ // 去控制字符:保留可打印字符(含 \t 制表符),其余控制字符移除
204
+ return withoutAnsi.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '');
205
+ }
206
+ /**
207
+ * 外部工具返回净化:去控制字符 + 长度上限
208
+ *
209
+ * 净化规则本身抽为 `stripControlChars`(**单一真理源**),供需要「净化但不限长」的调用方复用——
210
+ * 例如 `read_file`:它的长度由 **token 预算分段**(`sliceFileByLineBudget`)收口,不走字符上限,
211
+ * 但仍须剥掉 ANSI/控制字符。否则它只能传一个假的上限(如 MAX_SAFE_INTEGER)来迁就本函数签名。
212
+ *
213
+ * @param text 外部原始文本
214
+ * @param maxLen 最大长度
215
+ * @returns 净化后的文本
216
+ */
217
+ export function sanitizeExternalText(text, maxLen) {
218
+ const cleaned = stripControlChars(text);
219
+ return cleaned.length > maxLen ? `${cleaned.slice(0, maxLen)}…` : cleaned;
220
+ }
221
+ /**
222
+ * 解析 task_table_update 的 plan_item_id 为真实任务项 uuid(唯一解析点)。
223
+ *
224
+ * LLM 可见的任务项标识有三种来源,缺一不可达即断链:
225
+ * 1. task_table_write 返回的短 id(uuid 前 8 位,恒 8 hex;见 assembler writePlan 渲染);
226
+ * 2. 任务表 renderer 的行首序号(1-based,渲染形如「3. 描述 [状态]」);
227
+ * 3. 完整 uuid(planItemLog 等展示)。
228
+ *
229
+ * 解析顺序(防歧义):
230
+ * - 完整 uuid 全等命中 → 直用;
231
+ * - 长度恰为 8 的标识 → 按 uuid 前 8 位前缀语义解析(uuid 前 8 位理论可全数字
232
+ * (10/16)^8≈2.3%,8 位数字绝不可能是任务表序号 → 恒按短 id 语义,杜绝错配);
233
+ * - 其余纯数字 → 行首序号(1-based,按 order 匹配——与 renderer「order+1 展示」同键,
234
+ * 不依赖「order==数组下标」弱不变量);
235
+ * - 其余 → 未找到(提示可用格式)。
236
+ *
237
+ * 失败返回带定位提示的错误文案;成功后由调用方以完整 uuid 走 planManager.updatePlanItem
238
+ * (sessionManager.updatePlanItemStatus 保持全等匹配写点,不被污染)。
239
+ */
240
+ function resolvePlanItemId(planItemId, plan) {
241
+ // 1. 完整 uuid 全等(含连字符,长度 36)
242
+ if (plan.some((s) => s.id === planItemId))
243
+ return { ok: true, id: planItemId };
244
+ // 2. 短 id(uuid 前 8 位,恒 8 字符)
245
+ if (planItemId.length === 8) {
246
+ const matches = plan.filter((s) => s.id.startsWith(planItemId));
247
+ if (matches.length === 1)
248
+ return { ok: true, id: matches[0].id };
249
+ if (matches.length > 1) {
250
+ return {
251
+ ok: false,
252
+ error: `[ERR:INVALID_ARG] 任务项短 id "${planItemId}" 不唯一(对应 ${matches.length} 个任务项),请改用任务表行首序号定位`,
253
+ };
254
+ }
255
+ return {
256
+ ok: false,
257
+ error: `[ERR:PLAN_ITEM_NOT_FOUND] 未找到任务项 "${planItemId}":短 id 需为 task_table_write 返回的 8 位标识,或改用任务表行首序号`,
258
+ };
259
+ }
260
+ // 3. 纯数字 → 行首序号(1-based,按 order 匹配)
261
+ if (/^\d+$/.test(planItemId)) {
262
+ const idx = Number(planItemId) - 1;
263
+ const planItem = plan.find((s) => s.order === idx);
264
+ if (planItem)
265
+ return { ok: true, id: planItem.id };
266
+ return {
267
+ ok: false,
268
+ error: `[ERR:INVALID_ARG] 任务项序号 ${planItemId} 超出任务表范围(当前共 ${plan.length} 个任务项,行首序号从 1 开始)`,
269
+ };
270
+ }
271
+ // 4. 未知标识
272
+ return {
273
+ ok: false,
274
+ error: `[ERR:PLAN_ITEM_NOT_FOUND] 未找到任务项 "${planItemId}"(可用任务表行首序号或 task_table_write 返回的短 id 定位)`,
275
+ };
276
+ }
277
+ /**
278
+ * 默认常驻工具集(tool-exposure-model 探索草稿:默认常驻 vs 角色启动)
279
+ *
280
+ * 判据 A(越界判定):副作用不越出「项目 + 会话 + 内核自有」边界 → 常驻;
281
+ * 判据 B(来源可信):执行对象已在仓库/技能目录沉淀 → 常驻。
282
+ * 豁免能力白名单:角色包声明 capabilities 不影响这些工具的暴露面。
283
+ *
284
+ * 特权工具(不在此集,受 toolWhitelist 过滤):web_search / web_fetch(外部网络,
285
+ * 有真实副作用面,判据 A),run_code(LLM 现写任意代码)。
286
+ * task_table_write / task_table_update **直接暴露**:任务表是内核 agent 完成多步任务的
287
+ * **必要基建**(与 compress_context / remember_intel 同属"内核自有上下文维护"判据),
288
+ * 不应由角色包能力声明决定是否可用——否则无 task:plan 能力时,引导语提示用任务表而工具
289
+ * 不可见 = 死胡同,LLM 只能放弃改用 write_file 硬拆。
290
+ */
291
+ export const DEFAULT_EXPOSED_TOOLS = new Set([
292
+ // 项目内读写(判据 A;写删的危险度由 confirmWrites/guest 确认层管)
293
+ 'read_file',
294
+ 'write_file',
295
+ 'delete_file',
296
+ 'list_dir',
297
+ // 内核自有数据 / 人机交互 / 上下文维护(判据 A 基建)
298
+ 'search_memories',
299
+ 'trace_summary',
300
+ 'list_sessions',
301
+ 'compress_context', // ⚠️ loop 专有通道:不在本文件 execute() switch,由 loop 分派(AgentLoop.executeToolCalls 内 compressContext 三元分支,与 remember_intel 同分支)
302
+ 'remember_intel', // 情报区写回(LLM 私有笔记累积,随每轮装配注入)——上下文维护基建;⚠️ loop 专有通道:不在本 execute() switch,由 loop 分派(loop.ts handleRememberIntel)
303
+ // 任务表管理(直接暴露:内核多步任务必要基建,非角色包可选能力)
304
+ 'task_table_write',
305
+ 'task_table_update',
306
+ 'ask_user', // ⚠️ loop 专有通道:不在本 execute() switch,由 loop.handleAskUser 检出挂起(loop.ts willSuspendForAsk → handleAskUser)
307
+ 'register_work',
308
+ // 技能域(判据 A+B:来源可信;既定豁免)
309
+ 'read_skill',
310
+ 'read_resource',
311
+ 'run_skill_script',
312
+ 'list_resources',
313
+ 'list_skills',
314
+ // 项目内既有脚本执行(判据 A+B:仓库已沉淀);search_project 走宿主注入例外,不在此集
315
+ 'run_project_script',
316
+ ]);
7
317
  /**
8
318
  * 工具执行器
9
319
  *
@@ -15,17 +325,169 @@ export class ToolExecutor {
15
325
  customTools = new Map();
16
326
  /** 内置工具处理器(路径安全 + 内置工具实现) */
17
327
  builtinHandlers;
328
+ /**
329
+ * 工具列表变更回调(由 assembleComponents 设置为 loop.refreshToolDefinitions)
330
+ *
331
+ * registerTool 调用后触发,确保 AgentLoop 的 toolDefinitions 快照和 system prompt
332
+ * 同步更新。无 AgentLoop 时为 undefined(如纯 ToolExecutor 单元测试场景)。
333
+ */
334
+ onToolsChanged;
335
+ /**
336
+ * 工具白名单(capabilities → 工具映射产物,即特权声明模型)
337
+ *
338
+ * - `null`(默认):全部暴露——未声明 capabilities 的角色包/无角色包时保持现状;
339
+ * - `string[]`:在 DEFAULT_EXPOSED_TOOLS(默认常驻豁免集)之上,追加白名单内的特权工具;
340
+ * - `[]`:仅暴露常驻豁免集(配合 toolMode=block 即全禁)。
341
+ *
342
+ * 语义(tool-exposure-model 探索草稿:默认常驻 vs 角色启动):
343
+ * 角色包声明 capabilities = 声明超越默认边界的**特权**(web:search / web:fetch / code:execute
344
+ * 等——须在 capabilityMap 映射表内有实际工具映射),而非逐项打开本地能力。
345
+ * ⚠️ `task:plan` **不是**特权:任务表已默认常驻(见上方 DEFAULT_EXPOSED_TOOLS 注释),映射表
346
+ * 不为它保留条目,声明与否行为全同(假特权)。本地只读/项目内/内核基建工具默认常驻,不受白名单过滤——
347
+ * 修正「声明任意能力即误杀常驻工具」的暴露面不对称。
348
+ * 白名单只控制**暴露面**(LLM 可见/可调),不改变 execute 路由。
349
+ */
350
+ toolWhitelist = null;
351
+ /** 安全守卫(构造参数转存,run_code/run_project_script 执行前确认用) */
352
+ security;
353
+ /** 网络搜索提供者(可选,注入时启用 web_search 工具) */
354
+ webSearchProvider;
355
+ /** 网页抓取提供者(可选,注入时启用 web_fetch 工具;与 web_search 构成搜索→抓取闭环) */
356
+ fetchProvider;
357
+ /** 代码执行提供者(可选,注入时启用 run_code 工具) */
358
+ codeExecutionProvider;
359
+ /** 项目搜索提供者(可选,注入时启用 search_project 工具;等价 IDE 全局搜索) */
360
+ projectSearchProvider;
361
+ /** node 可执行文件路径(可选):run_project_script 的 node runtime 分支注入真实 node 路径 */
362
+ scriptNodePath;
363
+ /**
364
+ * 剩余对话预算提供者(可选,预算联动)
365
+ *
366
+ * 由 agent 装配时注入(读取 loop 最近一轮 prepare 的剩余预算),
367
+ * 供 search_project 在预算紧张时下探结果条数上限。
368
+ * undefined / 返回 undefined = 无预算信息,保持原行为(不做下探)。
369
+ * 与 read_skill 等回调同款注入模式(装配时序解耦:loop 创建后才可注入)。
370
+ */
371
+ budgetProvider;
372
+ /** 注入剩余对话预算提供者(装配时由 loop 创建后注入,见 assembler) */
373
+ setBudgetProvider(provider) {
374
+ this.budgetProvider = provider;
375
+ }
376
+ /** 任务表管理回调(由 agent 装配时注入,处理 task_table_write/update) */
377
+ planManager;
378
+ /**
379
+ * read_skill 技能正文读取回调(由 agent 装配时注入,处理 read_skill)
380
+ *
381
+ * 渐进披露 L2 数据源(两级技能统一):先查激活角色包内嵌技能
382
+ * (manifest.skills 的 file 指向),再查全局通用技能池(SkillManager 条目)。
383
+ * 委托装配层注入的回调,避免 ToolExecutor 与 rolePackManager/skillManager 强耦合
384
+ * (装配顺序:rolePackManager 在 toolExec 之后创建,用回调注入解耦时序)。
385
+ * 未注入时 read_skill 返回不可用提示。
386
+ */
387
+ readSkill;
388
+ /**
389
+ * read_resource 资源读取回调(由 agent 装配时注入,处理 read_resource)
390
+ *
391
+ * 渐进披露 L3:读取技能的 resources/ 目录下的参考资料。
392
+ * 委托装配层注入的回调,避免 ToolExecutor 与 rolePackManager/skillManager 强耦合。
393
+ */
394
+ readResource;
395
+ /**
396
+ * run_skill_script 脚本执行回调(由 agent 装配时注入,处理 run_skill_script)
397
+ *
398
+ * 渐进披露 L3:执行技能的 scripts/ 目录下的可执行脚本。
399
+ * 脚本源码不进入 LLM 上下文,仅执行结果返回。
400
+ */
401
+ runSkillScript;
402
+ /**
403
+ * list_resources 资源清单回调(由 agent 装配时注入)
404
+ *
405
+ * 渐进披露 L3:列出技能的 resources/ 目录下所有资源文件。
406
+ */
407
+ listResources;
408
+ /**
409
+ * list_skills 技能清单回调(由 agent 装配时注入)
410
+ *
411
+ * 渐进披露 L1 补充:列出当前可用的所有技能清单。
412
+ */
413
+ listSkills;
414
+ /**
415
+ * register_work 作品索引登记回调(由 agent 装配时注入,处理 register_work)
416
+ *
417
+ * 作品投影:用户主动触发登记一件作品(文档/代码/笔记)
418
+ * 为项目级 JSON 索引,写 <memoraDir>/work-projections.json(纯元数据指针)。
419
+ * 委托装配层注入的回调,避免 ToolExecutor 与 WorkProjectionManager 强耦合
420
+ * (与 read_skill 同款注入模式)。未注入时 register_work 返回不可用提示。
421
+ */
422
+ registerWork;
423
+ /**
424
+ * run_team_meeting 评估/评审型会议回调(由 agent 装配时注入,处理 run_team_meeting)
425
+ *
426
+ * 工具内嵌 LLM 调用的新形态:回调持有 rolePackManager(组解析 + persona 全文)与
427
+ * 前台 provider(单次 chat),在装配层闭包中实现一次调用注入多角色 persona 的会议。
428
+ * 委托装配层注入的回调,避免 ToolExecutor 与 rolePackManager/llm 强耦合(同 readSkill 模式)。
429
+ * 未注入时 run_team_meeting 返回不可用提示。
430
+ */
431
+ runTeamMeeting;
18
432
  constructor(projectPath, security, memoryIndex,
19
- /** 作品投影管理器(可选,读取文件时自动生成投影) */
20
- workProjection) {
433
+ /** 网络搜索提供者(可选,不传则不启用网络搜索能力) */
434
+ webSearchProvider,
435
+ /** 会话存储(可选,trace_summary 溯源原始对话用) */
436
+ sessionStore,
437
+ /** 网页抓取提供者(可选,不传则不启用 web_fetch 工具) */
438
+ fetchProvider,
439
+ /** 代码执行提供者(可选,不传则不启用 run_code 工具) */
440
+ codeExecutionProvider,
441
+ /** 项目搜索提供者(可选,不传则不启用 search_project 工具) */
442
+ projectSearchProvider,
443
+ /** node 可执行文件路径(可选):run_project_script 的 node runtime 分支注入真实 node 路径 */
444
+ scriptNodePath) {
445
+ this.webSearchProvider = webSearchProvider;
446
+ this.fetchProvider = fetchProvider;
447
+ this.codeExecutionProvider = codeExecutionProvider;
448
+ this.projectSearchProvider = projectSearchProvider;
449
+ // 脚本执行 node 路径:存实例属性,run_project_script 分支透传给内核执行器
450
+ this.scriptNodePath = scriptNodePath;
451
+ // 转存 SecurityGuard 引用:执行型工具(run_code/run_project_script)执行前确认用
452
+ this.security = security;
21
453
  // 内置工具实现 + 路径安全委托给 BuiltinToolHandlers
22
454
  // 构造参数仅用于初始化 BuiltinToolHandlers,ToolExecutor 自身不再持有这些引用
23
- this.builtinHandlers = new BuiltinToolHandlers(projectPath, security, memoryIndex, workProjection);
455
+ this.builtinHandlers = new BuiltinToolHandlers(projectPath, security, memoryIndex, sessionStore);
456
+ }
457
+ /**
458
+ * 注入 MemoryInspector,启用 search_memories 的纯关键词检索后端(无向量后端)。
459
+ * assembler 中它依赖 loop/history、后于 toolExec 构造,故用「构造后注入」——装配期经
460
+ * toolExec.setMemoryInspector(...) 接线,未注入时 search_memories 保持基础内存查询行为。
461
+ *
462
+ * @param inspector 记忆搜索器(纯关键词,含 superseded 过滤 + 溯源揭示)
463
+ */
464
+ setMemoryInspector(inspector) {
465
+ this.builtinHandlers.setMemoryInspector(inspector);
466
+ }
467
+ /**
468
+ * 注入 memoryRecalled 事件发射回调(宿主感知「LLM 查询记忆命中 N 条」,§2.4 保留改语义定案)。
469
+ * 转发给 builtinHandlers:search_memories 命中记忆时触发,为全库**唯一** memoryRecalled
470
+ * 发射位(无 warmRecall 等其他发射路径)。
471
+ *
472
+ * @param callback 命中回调(count 命中条数 / query 检索词),缺省注入则工具静默(无宿主 no-op)
473
+ */
474
+ setOnMemoryRecalled(callback) {
475
+ this.builtinHandlers.setOnMemoryRecalled(callback);
476
+ }
477
+ /**
478
+ * 注入召回互斥排除集提供者(memory-tool-recall-design §5.1 工具互斥),
479
+ * 转发给 builtinHandlers:search_memories 用它排除「正文或摘要已在眼前」的轮次 round-summary。
480
+ * assembler 中装配期 wiring(与 setMemoryInspector 同一时机),缺省不注入则工具不过滤。
481
+ *
482
+ * @param provider 返回当前会话「内容已在上下文」的 roundId 精确集合(无参,从 loop 视图派生)
483
+ */
484
+ setExclusionRoundIdsProvider(provider) {
485
+ this.builtinHandlers.setExclusionRoundIdsProvider(provider);
24
486
  }
25
487
  /**
26
488
  * 注册自定义工具
27
489
  *
28
- * 宿主项目调用此方法注册领域专属工具(如 create_chapter、run_tests 等业务专属操作)。
490
+ * 宿主项目调用此方法注册领域专属工具(如 run_tests、query_database、send_email 等业务专属操作)。
29
491
  * 工具名不能与内置工具重复,也不能重复注册。
30
492
  * 注册后工具会出现在 `tools.list` 列表中,
31
493
  * LLM 可通过 tool_call 调用,execute() 会路由到 handler。
@@ -38,12 +500,31 @@ export class ToolExecutor {
38
500
  if (!definition.name || !/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(definition.name)) {
39
501
  throw configError(`工具名无效:"${definition.name}"`, '必须以字母/下划线开头,只含字母/数字/下划线', ['请检查工具名称是否符合命名规范']);
40
502
  }
41
- // 不允许覆盖内置工具
503
+ // 不允许覆盖已暴露的内置工具
42
504
  if (BUILTIN_TOOLS.some((t) => t.name === definition.name)) {
43
505
  throw configError(`不能覆盖内置工具:${definition.name}`, undefined, [
44
506
  '请使用不同的工具名称',
45
507
  ]);
46
508
  }
509
+ // web_search 仅当 webSearchProvider 已注入时由内核管理,不允许覆盖
510
+ // 未注入时宿主可自由注册自己的 web_search 实现
511
+ if (this.webSearchProvider && definition.name === WEB_SEARCH_TOOL.name) {
512
+ throw configError(`不能覆盖内置工具:${definition.name}`, undefined, [
513
+ '请使用不同的工具名称',
514
+ ]);
515
+ }
516
+ // web_fetch 仅当 fetchProvider 已注入时由内核管理,不允许覆盖
517
+ if (this.fetchProvider && definition.name === WEB_FETCH_TOOL.name) {
518
+ throw configError(`不能覆盖内置工具:${definition.name}`, undefined, [
519
+ '请使用不同的工具名称',
520
+ ]);
521
+ }
522
+ // run_code 仅当 codeExecutionProvider 已注入时由内核管理,不允许覆盖
523
+ if (this.codeExecutionProvider && definition.name === RUN_CODE_TOOL.name) {
524
+ throw configError(`不能覆盖内置工具:${definition.name}`, undefined, [
525
+ '请使用不同的工具名称',
526
+ ]);
527
+ }
47
528
  // 不允许重复注册
48
529
  if (this.customTools.has(definition.name)) {
49
530
  throw configError(`工具已注册:${definition.name}`, undefined, [
@@ -52,12 +533,106 @@ export class ToolExecutor {
52
533
  }
53
534
  this.customTools.set(definition.name, { definition, handler });
54
535
  logger.info({ tool: definition.name }, '自定义工具已注册');
536
+ // 触发 AgentLoop 刷新 toolDefinitions 快照 + system prompt
537
+ // 未设置回调时(如单元测试)静默跳过
538
+ this.onToolsChanged?.();
539
+ }
540
+ /**
541
+ * 移除自定义工具
542
+ *
543
+ * 供测试场景使用,可在测试后清理已注册的自定义工具。
544
+ * 仅能移除通过 registerTool 注册的自定义工具,无法移除内置工具。
545
+ *
546
+ * @param name 工具名称
547
+ */
548
+ removeTool(name) {
549
+ if (this.customTools.has(name)) {
550
+ this.customTools.delete(name);
551
+ this.onToolsChanged?.();
552
+ }
553
+ }
554
+ /**
555
+ * 设置工具列表变更回调
556
+ *
557
+ * 由 assembleComponents 在构造 AgentLoop 后调用,将 loop.refreshToolDefinitions
558
+ * 绑定到 registerTool 的副作用链路中。这样宿主调用 registerTool 注册工具后,
559
+ * AgentLoop 的 toolDefinitions 快照和 system prompt 会自动刷新,
560
+ * 无需宿主手动调用 refreshToolDefinitions。
561
+ *
562
+ * @param callback 工具列表变更时的回调(传 undefined 清除回调)
563
+ */
564
+ setOnToolsChanged(callback) {
565
+ this.onToolsChanged = callback;
55
566
  }
56
567
  /**
57
- * 获取所有工具定义(IX-02:统一为 getter 风格,与 persona/skill 一致)
568
+ * 设置工具白名单(换角色 → 工具集切换)
569
+ *
570
+ * 角色包激活/切换时由 Agent 调用(getActiveCapabilities → resolveCapabilityTools),
571
+ * 把中立能力声明映射为 memora 工具白名单。变更后触发 onToolsChanged,
572
+ * AgentLoop 的 toolDefinitions 快照与 system prompt 自动刷新。
573
+ *
574
+ * @param whitelist 白名单工具名数组;null=全部暴露(默认,无能力声明时)
575
+ */
576
+ setToolWhitelist(whitelist) {
577
+ this.toolWhitelist = whitelist;
578
+ this.onToolsChanged?.();
579
+ }
580
+ /** 获取当前工具白名单(null=全部暴露) */
581
+ get currentToolWhitelist() {
582
+ return this.toolWhitelist;
583
+ }
584
+ /**
585
+ * 获取所有工具定义(getter 风格,与 persona/skill 一致)
586
+ *
587
+ * 白名单语义(tool-exposure-model 特权声明模型):
588
+ * - toolWhitelist === null:全部暴露(内置 + web_search 条件 + 自定义工具);
589
+ * - toolWhitelist === string[]:全部暴露命中「默认常驻豁免集 DEFAULT_EXPOSED_TOOLS」的
590
+ * 内置工具,特权工具(web_search / web_fetch / run_code / task_table_*)按名单过滤;
591
+ * - toolWhitelist === []:仅常驻豁免集(角色无任何特权能力时,本地能力仍完整可用)。
592
+ * - 自定义工具始终暴露(宿主注册,属宿主能力面,角色包能力声明不越权过滤宿主工具);
593
+ * - search_project:宿主注入即暴露(方案乙)——本地只读工具,等价 read_file 的只读语义,
594
+ * 不受角色包能力声明与豁免集语义约束(具备与否不决定其可见性)。
58
595
  */
59
596
  get list() {
60
- return [...BUILTIN_TOOLS, ...[...this.customTools.values()].map((e) => e.definition)];
597
+ // 条件性包含外部信息工具:仅当注入了对应 provider 时才暴露给 LLM
598
+ // - web_search:webSearchProvider 注入时
599
+ // - web_fetch:fetchProvider 注入时(与 web_search 构成搜索→抓取闭环)
600
+ // - run_code:codeExecutionProvider 注入时
601
+ // - search_project:projectSearchProvider 注入时(项目内搜索,等价 IDE 全局搜索)
602
+ let baseTools = BUILTIN_TOOLS;
603
+ if (this.webSearchProvider)
604
+ baseTools = [...baseTools, WEB_SEARCH_TOOL];
605
+ if (this.fetchProvider)
606
+ baseTools = [...baseTools, WEB_FETCH_TOOL];
607
+ if (this.codeExecutionProvider)
608
+ baseTools = [...baseTools, RUN_CODE_TOOL];
609
+ // 常驻豁免集无条件暴露(LLM 工具面不因角色包能力声明收窄本地能力)
610
+ const exposed = baseTools.filter((t) => DEFAULT_EXPOSED_TOOLS.has(t.name));
611
+ // 特权工具按白名单过滤(toolWhitelist=null 全暴露;否则只在名单内)
612
+ const privileged = baseTools.filter((t) => !DEFAULT_EXPOSED_TOOLS.has(t.name));
613
+ const whitelisted = this.toolWhitelist
614
+ ? privileged.filter((t) => this.toolWhitelist?.includes(t.name))
615
+ : privileged;
616
+ // search_project 作为宿主注入工具(方案乙):注入即暴露,追加在自定义工具之后,不受白名单过滤
617
+ const projectSearchTool = this.projectSearchProvider ? [SEARCH_PROJECT_TOOL] : [];
618
+ return [
619
+ ...exposed,
620
+ ...whitelisted,
621
+ ...[...this.customTools.values()].map((e) => e.definition),
622
+ ...projectSearchTool,
623
+ ];
624
+ }
625
+ /**
626
+ * 完整内置工具定义(只读闸查询用,单一真理源)
627
+ *
628
+ * 含全部始终内置工具 + 条件工具(web_search / web_fetch / run_code,仅定义层面,
629
+ * 不论 provider 是否注入)。不过白名单过滤、不含自定义工具——只读闸(ToolRunner)需要
630
+ * 「全部内置定义」以查 readonly 标记,而非「当前暴露面」(被白名单过滤的工具 LLM 调不到,
631
+ * 但 readonly 语义应覆盖全部内置写操作)。定义合集真源 = `builtinTools.ALL_BUILTIN_TOOL_DEFS`
632
+ * (写盘派生索引同源消费),此处只转手、不另存清单。
633
+ */
634
+ get builtinDefinitions() {
635
+ return [...ALL_BUILTIN_TOOL_DEFS];
61
636
  }
62
637
  /**
63
638
  * 执行工具调用
@@ -88,26 +663,389 @@ export class ToolExecutor {
88
663
  if (definition) {
89
664
  args = this.validateAndCoerceArgs(name, args, definition);
90
665
  }
91
- const safeArgs = Object.fromEntries(Object.entries(args).map(([k, v]) => [
92
- k,
93
- typeof v === 'string' ? truncate(v, 200) : v,
94
- ]));
666
+ const safeArgs = Object.fromEntries(Object.entries(args).map(([k, v]) => [k, typeof v === 'string' ? truncate(v, 200) : v]));
95
667
  logger.info({ tool: name, args: safeArgs }, '执行工具');
96
668
  // 运行时类型安全:从 args 中提取字符串参数,避免不安全的 as string 断言
97
669
  const strArg = (key, fallback) => {
98
670
  const val = args[key];
99
671
  return typeof val === 'string' ? val : (fallback ?? '');
100
672
  };
101
- // 内置工具调用委托给 BuiltinToolHandlers
673
+ // 内置工具二分落点契约:构造态可独立可用(缺注入降级)→ BuiltinToolHandlers;
674
+ // 可用性本身依赖装配注入(缺注入即 NOT_AVAILABLE)→ 留在本 switch 内联。
675
+ // 新增内置工具先按此判别归属,勿凭参照漂移。
102
676
  switch (name) {
103
677
  case 'read_file':
104
- return this.builtinHandlers.readFile(strArg('path'));
678
+ return this.builtinHandlers.readFile(strArg('path'), strArg('offset') || undefined, strArg('limit') || undefined);
105
679
  case 'write_file':
106
- return this.builtinHandlers.writeFile(strArg('path'), strArg('content'), extensions, strArg('mode', 'overwrite'), strArg('insert_line') || undefined);
680
+ return this.builtinHandlers.writeFile(strArg('path'), strArg('content'), extensions, strArg('mode', 'overwrite'), strArg('insert_line') || undefined, strArg('old_string') || undefined);
107
681
  case 'list_dir':
108
682
  return this.builtinHandlers.listDir(strArg('path', '.'), strArg('recursive', 'false'), strArg('maxDepth', '2'));
109
683
  case 'search_memories':
110
684
  return this.builtinHandlers.searchMemories(strArg('query'), strArg('limit', '10'), strArg('mode', 'match'));
685
+ case 'trace_summary':
686
+ return this.builtinHandlers.traceSummary(strArg('sessionId'), strArg('roundId') || undefined, strArg('limit', '5'));
687
+ case 'list_sessions':
688
+ return this.builtinHandlers.listSessions(strArg('limit', '10'));
689
+ case 'web_search': {
690
+ // web_search 由 ToolExecutor 直接处理,不经过 BuiltinToolHandlers(文件系统导向)
691
+ // 使用注入的 webSearchProvider 执行网络搜索,带超时保护
692
+ if (!this.webSearchProvider) {
693
+ return '[ERR:TOOL:NOT_AVAILABLE] 错误:网络搜索功能未配置,请先注入 IWebSearchProvider';
694
+ }
695
+ const query = strArg('query');
696
+ if (!query) {
697
+ throw toolError('web_search 工具调用缺少 query 参数', 'LLM 未传 query', ['query 不能为空'], undefined, ToolErrorCode.ARGUMENT_ERROR);
698
+ }
699
+ // 参数校验:query 当不可信输入,做长度上限(allow-list 优先)
700
+ if (query.length > WEB_SEARCH_QUERY_MAX_LEN) {
701
+ throw toolError('web_search query 参数过长', `query 超过 ${WEB_SEARCH_QUERY_MAX_LEN} 字符上限`, ['缩短搜索关键词'], undefined, ToolErrorCode.ARGUMENT_ERROR);
702
+ }
703
+ const limit = Math.min(Number.parseInt(strArg('limit', '5'), 10) || 5, 20);
704
+ const results = await safeSearch(this.webSearchProvider, query, { limit });
705
+ if (results.length === 0) {
706
+ return `(未找到与 "${query}" 相关的搜索结果)`;
707
+ }
708
+ // 搜索来源透出:同批次结果来自同一后端,取首条 endpoint 告知用户实际使用的搜索源
709
+ // (降级到 DuckDuckGo 时即透出「搜索来源:DuckDuckGo」),提升 in-flow 信任;宿主渲染工具结果文本即可见。
710
+ const endpoint = results[0]?.endpoint;
711
+ const sourceLine = endpoint ? `(搜索来源:${endpoint})` : '';
712
+ // 返回净化:外部内容去控制字符 + 长度上限,防长上下文注入
713
+ const body = results
714
+ .map((r, i) => {
715
+ const title = sanitizeExternalText(r.title, WEB_SEARCH_RESULT_MAX_LEN);
716
+ const url = sanitizeExternalText(r.url || '(无链接)', WEB_SEARCH_RESULT_MAX_LEN);
717
+ const snippet = sanitizeExternalText(r.snippet.replace(/\n/g, ' '), WEB_SEARCH_RESULT_MAX_LEN);
718
+ return `${i + 1}. ${title}\n URL: ${url}\n ${snippet}`;
719
+ })
720
+ .join('\n\n');
721
+ return sourceLine ? `${sourceLine}\n\n${body}` : body;
722
+ }
723
+ case 'web_fetch': {
724
+ // web_fetch 由 ToolExecutor 直接处理(与 web_search 同侧,均为外部信息获取)
725
+ // 使用注入的 fetchProvider 抓取网页正文,带超时保护;失败由 safeFetch 降级
726
+ if (!this.fetchProvider) {
727
+ return '[ERR:TOOL:NOT_AVAILABLE] 错误:网页抓取功能未配置,请先注入 IFetchProvider';
728
+ }
729
+ const url = strArg('url');
730
+ if (!url) {
731
+ throw toolError('web_fetch 工具调用缺少 url 参数', 'LLM 未传 url', ['url 不能为空'], undefined, ToolErrorCode.ARGUMENT_ERROR);
732
+ }
733
+ // 参数校验:url 当不可信输入,做长度上限 + 协议白名单(仅 http/https,防 file:// 等本地协议)
734
+ if (url.length > WEB_FETCH_URL_MAX_LEN) {
735
+ throw toolError('web_fetch url 参数过长', `url 超过 ${WEB_FETCH_URL_MAX_LEN} 字符上限`, ['缩短 url'], undefined, ToolErrorCode.ARGUMENT_ERROR);
736
+ }
737
+ if (!/^https?:\/\//i.test(url)) {
738
+ throw toolError('web_fetch url 协议不支持', `仅支持 http/https 协议:${url.slice(0, 64)}`, ['使用 http 或 https 开头的完整 URL'], undefined, ToolErrorCode.ARGUMENT_ERROR);
739
+ }
740
+ const maxChars = Math.min(
741
+ // limit 默认值与钳制上限同源(builtinTools WEB_FETCH_TOOL schema 文案,SSOT 收敛)
742
+ Number.parseInt(strArg('limit', String(WEB_FETCH_CONTENT_DEFAULT_LEN)), 10) ||
743
+ WEB_FETCH_CONTENT_DEFAULT_LEN, WEB_FETCH_CONTENT_MAX_LEN);
744
+ const page = await safeFetch(this.fetchProvider, url, { maxChars });
745
+ if (!page.content) {
746
+ return `(未能从 "${url}" 提取到正文内容)`;
747
+ }
748
+ // 返回净化:外部内容去控制字符 + 长度上限,防长上下文注入
749
+ const title = sanitizeExternalText(page.title, 500);
750
+ const content = sanitizeExternalText(page.content, WEB_FETCH_CONTENT_MAX_LEN);
751
+ return `来源:${page.url || url}\n标题:${title || '(无标题)'}\n\n${content}`;
752
+ }
753
+ case 'delete_file':
754
+ // 删除项目文件(临时脚本闭环收尾:write_file 写 → run_code 执行 → delete_file 清理)
755
+ // 路径白名单 + 用户确认(guest/confirmWrites)由 BuiltinToolHandlers.deleteFile 负责
756
+ return this.builtinHandlers.deleteFile(strArg('path'));
757
+ case 'run_code': {
758
+ // run_code 由 ToolExecutor 直接处理(通用计算/数据处理/验证底座)
759
+ // 使用注入的 codeExecutionProvider 执行代码,带超时保护;失败由 safeExecuteCode 降级
760
+ if (!this.codeExecutionProvider) {
761
+ return '[ERR:TOOL:NOT_AVAILABLE] 错误:代码执行功能未配置,请先注入 ICodeExecutionProvider';
762
+ }
763
+ const language = strArg('language');
764
+ const code = strArg('code');
765
+ const scriptPath = strArg('script_path');
766
+ // 两种模式互斥:code(执行代码字符串)与 script_path(执行项目脚本文件)二选一,防语义歧义
767
+ if (scriptPath && code) {
768
+ throw toolError('run_code 参数冲突', 'code 与 script_path 不能同时传入', ['二选一:传 code 执行代码字符串,或传 script_path 执行项目脚本文件'], undefined, ToolErrorCode.ARGUMENT_ERROR);
769
+ }
770
+ // 统一执行入参:脚本源码 / 代码字符串 + 语言 + 工作目录
771
+ let execCode;
772
+ let execLanguage;
773
+ // 临时脚本模式 cwd=项目根(脚本可 require 项目本地依赖、读取项目数据);code 模式不指定由执行器决定
774
+ const execOptions = scriptPath
775
+ ? { cwd: this.builtinHandlers.projectPath }
776
+ : undefined;
777
+ if (scriptPath) {
778
+ // 读取脚本文件原样执行(路径白名单 + 长度上限由 readScriptFile 负责,截断会破坏语法)
779
+ execCode = await this.builtinHandlers.readScriptFile(scriptPath);
780
+ // 扩展名推断 runtime(SSOT:scanner.inferRuntimeFromExt);未知扩展名兜底 node
781
+ const ext = scriptPath.slice(scriptPath.lastIndexOf('.')).toLowerCase();
782
+ execLanguage = language || inferRuntimeFromExt(ext) || 'node';
783
+ }
784
+ else {
785
+ // 传统 code 模式:language + code 均必填
786
+ if (!language) {
787
+ throw toolError('run_code 工具调用缺少 language 参数', 'LLM 未传 language', ['language 不能为空'], undefined, ToolErrorCode.ARGUMENT_ERROR);
788
+ }
789
+ if (!code) {
790
+ throw toolError('run_code 工具调用缺少 code 参数', 'LLM 未传 code', ['code 不能为空'], undefined, ToolErrorCode.ARGUMENT_ERROR);
791
+ }
792
+ execCode = code;
793
+ execLanguage = language;
794
+ }
795
+ // 参数校验:language/code 当不可信输入,做长度上限(防超长滥用)
796
+ // script_path 模式代码长度由 readScriptFile 的 SCRIPT_READ_MAX_LEN 兜底,不重复校验
797
+ if (execLanguage.length > RUN_CODE_LANGUAGE_MAX_LEN) {
798
+ throw toolError('run_code language 参数过长', `language 超过 ${RUN_CODE_LANGUAGE_MAX_LEN} 字符上限`, ['使用简短的语言名(具体可用集合由宿主执行器决定,如 "js"、"node")'], undefined, ToolErrorCode.ARGUMENT_ERROR);
799
+ }
800
+ if (!scriptPath && execCode.length > RUN_CODE_CODE_MAX_LEN) {
801
+ throw toolError('run_code code 参数过长', `code 超过 ${RUN_CODE_CODE_MAX_LEN} 字符上限`, ['精简代码或分步执行'], undefined, ToolErrorCode.ARGUMENT_ERROR);
802
+ }
803
+ // 执行确认:guest 模式或 confirmScripts 时询问(run_code 由宿主沙箱执行,此处为权限层确认;
804
+ // owner+confirmScripts=false 自动批准,走审计。code 无落盘路径,target 用描述型标识)
805
+ const execConfirmed = await this.security.confirmScriptRun(scriptPath ? `run_code:script:${scriptPath}` : 'run_code:inline', 'run_code', scriptPath
806
+ ? `运行脚本 ${scriptPath}(宿主沙箱)`
807
+ : `执行内联代码(${execLanguage},${execCode.length} 字符,宿主沙箱)`);
808
+ if (!execConfirmed) {
809
+ return '[ERR:SCRIPT_DECLINE] 代码执行未获确认(用户拒绝或未注入确认回调,fail-closed)';
810
+ }
811
+ const result = await safeExecuteCode(this.codeExecutionProvider, execCode, execLanguage, execOptions);
812
+ // 结果净化:stdout/stderr 当外部内容去控制字符 + 长度上限,防长上下文注入
813
+ const stdout = sanitizeExternalText(result.stdout, RUN_CODE_RESULT_MAX_LEN);
814
+ const stderr = sanitizeExternalText(result.stderr, RUN_CODE_RESULT_MAX_LEN);
815
+ // 格式化:与 run_skill_script 共用 formatExecutionResult(同一真理源,改格式契约须两链路同步)
816
+ return formatExecutionResult({ stdout, stderr, exitCode: result.exitCode, timedOut: result.timedOut }, { kind: 'CODE', timeoutDetail: '代码执行超时', errorDetail: '代码执行失败' });
817
+ }
818
+ case 'search_project': {
819
+ // search_project 由 ToolExecutor 直接处理(与 web_search/run_code 同侧,均为宿主注入能力)
820
+ // 使用注入的 projectSearchProvider 执行项目内搜索,带超时保护;失败由 safe* 降级
821
+ if (!this.projectSearchProvider) {
822
+ return '[ERR:TOOL:NOT_AVAILABLE] 错误:项目搜索功能未配置,请先注入 IProjectSearchProvider';
823
+ }
824
+ const query = strArg('query');
825
+ const mode = strArg('mode', 'name');
826
+ const exclude = strArg('exclude') || undefined;
827
+ // 预算下探:LLM 传入的 maxResults 仍按其意愿生效,但不得超过预算档位 cap(防结果撑爆上下文)
828
+ const maxResults = Math.min(Number.parseInt(strArg('maxResults', '20'), 10) || 20, PROJECT_SEARCH_RESULT_MAX_LEN, computeBudgetCappedMaxResults(this.budgetProvider?.()));
829
+ // 参数校验:query/exclude 当不可信输入,做长度上限(防超长 glob/关键词滥用)
830
+ if (query.length > PROJECT_SEARCH_QUERY_MAX_LEN) {
831
+ throw toolError('search_project query 参数过长', `query 超过 ${PROJECT_SEARCH_QUERY_MAX_LEN} 字符上限`, ['缩短搜索词'], undefined, ToolErrorCode.ARGUMENT_ERROR);
832
+ }
833
+ if ((exclude?.length ?? 0) > PROJECT_SEARCH_GLOB_MAX_LEN) {
834
+ throw toolError('search_project exclude 参数过长', `exclude 超过 ${PROJECT_SEARCH_GLOB_MAX_LEN} 字符上限`, ['缩短 exclude 模式'], undefined, ToolErrorCode.ARGUMENT_ERROR);
835
+ }
836
+ if (mode !== 'name' && mode !== 'content') {
837
+ return `[ERR:INVALID_ARG] 不支持的搜索模式 "${mode}",仅支持 name/content`;
838
+ }
839
+ if (mode === 'content') {
840
+ if (!query) {
841
+ return '[ERR:INVALID_ARG] content 模式需要 query 内容关键词';
842
+ }
843
+ // 放宽词表(精确优先 + 零命中回退):剔除与整串等价的词——否则宿主会做一轮与
844
+ // 精确轮逐字相同的徒劳扫描,并回报一个名不副实的 relaxed
845
+ const terms = buildSearchTerms(query).filter((t) => t.toLowerCase() !== query.trim().toLowerCase());
846
+ const search = await safeSearchProjectText(this.projectSearchProvider, {
847
+ pattern: query,
848
+ ...(terms.length > 0 ? { terms } : {}),
849
+ exclude,
850
+ maxResults,
851
+ });
852
+ return formatProjectTextSearch(search, query);
853
+ }
854
+ // name 模式:query 为文件名 glob(省略时列出项目全部文件)。
855
+ // 与 content 对齐两轮语义——原样 glob 精确匹配;仅当 query 不含 glob 元字符
856
+ // 时,才有"零命中→按名称子串放宽"的余地(裸词=关键词才需要模糊。含 glob 则精准匹配)。
857
+ const nameQuery = query || '**/*';
858
+ const hasGlobMeta = /[*?{}[\]]/.test(nameQuery);
859
+ // 放宽词表:复用 content 同一生产函数 buildSearchTerms(SSOT,无第二份放宽节奏);
860
+ // 剔除与整串等价的词——否则宿主会做一轮与精确轮逐字相同的徒劳扫描并回报名不副实的 relaxed。
861
+ const nameTerms = !hasGlobMeta
862
+ ? buildSearchTerms(nameQuery).filter((t) => t.toLowerCase() !== nameQuery.toLowerCase())
863
+ : undefined;
864
+ const fileSearch = await safeSearchProjectFiles(this.projectSearchProvider, {
865
+ query: nameQuery,
866
+ ...(nameTerms && nameTerms.length > 0 ? { terms: nameTerms } : {}),
867
+ exclude,
868
+ maxResults,
869
+ });
870
+ return formatProjectFileSearch(fileSearch, nameQuery);
871
+ }
872
+ case 'task_table_write': {
873
+ // 写入任务表(overwrite / append / replace);items 每项可选 rolePack(会议表层装配角色)
874
+ if (!this.planManager) {
875
+ return '[ERR:TOOL:NOT_AVAILABLE] 任务表功能未就绪';
876
+ }
877
+ // mode 为 required(validateAndCoerceArgs 强制校验),不设缺省——设缺省即与 required 契约矛盾
878
+ const writeMode = strArg('mode');
879
+ if (writeMode !== 'overwrite' && writeMode !== 'append' && writeMode !== 'replace') {
880
+ return `[ERR:INVALID_ARG] 不支持的写入模式 "${writeMode}",仅支持 overwrite/append/replace`;
881
+ }
882
+ const itemsRaw = args.items;
883
+ const items = Array.isArray(itemsRaw)
884
+ ? itemsRaw.map((s) => ({
885
+ description: String(s?.description ?? ''),
886
+ // rolePack 可选:仅接受非空字符串;范围校验在 prepare 期(越界忽略 + warning)
887
+ ...(s?.rolePack && typeof s.rolePack === 'string' ? { rolePack: s.rolePack } : {}),
888
+ }))
889
+ : [];
890
+ if (items.length === 0 || items.some((s) => s.description.trim() === '')) {
891
+ return '[ERR:INVALID_ARG] items 参数不能为空,且每项须含非空 description';
892
+ }
893
+ return this.planManager.writePlan(writeMode, items);
894
+ }
895
+ case 'task_table_update': {
896
+ // 更新任务状态
897
+ if (!this.planManager) {
898
+ return '[ERR:TOOL:NOT_AVAILABLE] 任务表功能未就绪';
899
+ }
900
+ const planItemId = strArg('plan_item_id');
901
+ if (!planItemId) {
902
+ return '[ERR:INVALID_ARG] plan_item_id 不能为空';
903
+ }
904
+ // 寻址统一解析(见 resolvePlanItemId):renderer 只向 LLM 展示行首序号(1-based),
905
+ // task_table_write 返回 uuid 前 8 位短 id,planItemLog 展示完整 uuid——三种来源全部归一为真实 uuid 后
906
+ // 再走 updatePlanItem(全等写点),短 id 不会断链。
907
+ const plan = this.planManager.getPlan?.() ?? [];
908
+ const resolved = resolvePlanItemId(planItemId, plan);
909
+ if (!resolved.ok) {
910
+ return resolved.error;
911
+ }
912
+ // status 为 required(validateAndCoerceArgs 强制校验),不设缺省
913
+ const planItemStatus = strArg('status');
914
+ if (planItemStatus !== 'done' && planItemStatus !== 'blocked') {
915
+ return `[ERR:INVALID_ARG] 不支持的状态 "${planItemStatus}",仅支持 done/blocked`;
916
+ }
917
+ return this.planManager.updatePlanItem(resolved.id, planItemStatus);
918
+ }
919
+ case 'read_skill': {
920
+ // 渐进披露 L2:读取激活角色包内嵌技能正文(readSkill 回调由 agent 装配注入)
921
+ // name 为必填参数,已由 validateAndCoerceArgs 校验,此处直接用
922
+ if (!this.readSkill) {
923
+ return '[ERR:TOOL:NOT_AVAILABLE] read_skill 不可用:未装配角色包技能读取回调';
924
+ }
925
+ const content = await this.readSkill(strArg('name'));
926
+ if (content === null) {
927
+ return `[ERR:SKILL_NOT_FOUND] 未找到技能 "${strArg('name')}"(角色包 skills/ 与全局技能池均无此名,或技能正文读取失败)`;
928
+ }
929
+ // 返回净化:技能正文当外部内容去控制字符 + 长度上限(防超长技能正文撑爆上下文)
930
+ return sanitizeExternalText(content, SKILL_CONTENT_MAX_LEN);
931
+ }
932
+ case 'read_resource': {
933
+ // 渐进披露 L3:读取技能的参考资源文件
934
+ if (!this.readResource) {
935
+ return '[ERR:TOOL:NOT_AVAILABLE] read_resource 不可用:未装配 L3 资源读取回调';
936
+ }
937
+ const skillName = strArg('skill_name');
938
+ const resourcePath = strArg('resource_path');
939
+ const resourceContent = await this.readResource(skillName, resourcePath);
940
+ if (resourceContent === null) {
941
+ return `[ERR:RESOURCE_NOT_FOUND] 未找到资源 "${resourcePath}"(技能 "${skillName}" 无此资源,或资源读取失败)`;
942
+ }
943
+ // 返回净化:资源正文当外部内容去控制字符 + 长度上限(防超长资源撑爆上下文)
944
+ return sanitizeExternalText(resourceContent, RESOURCE_CONTENT_MAX_LEN);
945
+ }
946
+ case 'run_skill_script': {
947
+ // 渐进披露 L3:执行技能的可执行脚本(脚本源码不进上下文,仅结果返回)
948
+ if (!this.runSkillScript) {
949
+ return '[ERR:TOOL:NOT_AVAILABLE] run_skill_script 不可用:未装配 L3 脚本执行回调';
950
+ }
951
+ const skillName = strArg('skill_name');
952
+ const scriptPath = strArg('script_path');
953
+ const scriptArgs = Array.isArray(args['args']) ? args['args'] : [];
954
+ // 脚本执行确认(与 run_code/run_project_script 同判据):
955
+ // owner + confirmScripts=false 默认放行(技能脚本来源可信,判据 B,无人值守可跑);
956
+ // guest 恒确认(受限权限下禁止"来源可信豁免"自主执行,缝合三脚本工具语义裂缝);
957
+ // confirmScripts=true 时 owner 亦确认(开关语义 = "脚本执行二次确认",对全部脚本生效)。
958
+ // 无 OS 级沙箱(子进程直跑 + env 继承用户环境),故不能以 Codex workspace-write 的
959
+ // "边界内自动"前提豁免 guest。target 用技能名:脚本路径标识(无落盘绝对路径)。
960
+ const execConfirmed = await this.security.confirmScriptRun(`skill:${skillName}:${scriptPath ?? ''}`, 'run_skill_script', `执行技能 ${skillName} 的脚本 ${scriptPath ?? ''}`);
961
+ if (!execConfirmed) {
962
+ return '[ERR:SCRIPT_DECLINE] 技能脚本执行未获确认(用户拒绝或未注入确认回调,fail-closed)';
963
+ }
964
+ const result = await this.runSkillScript(skillName, scriptPath, scriptArgs);
965
+ if (result === null) {
966
+ return `[ERR:SCRIPT_NOT_FOUND] 未找到脚本 "${scriptPath}"(技能 "${skillName}" 无此脚本,或执行失败)`;
967
+ }
968
+ // 返回净化:脚本输出当外部内容去控制字符 + 长度上限(8-1 对齐 run_code 的防护),防刷屏撑爆上下文
969
+ return sanitizeExternalText(result, RUN_SCRIPT_RESULT_MAX_LEN);
970
+ }
971
+ case 'run_project_script': {
972
+ // 项目内已有脚本执行(默认开放,判据 A+B):内核子进程执行,脚本源码不进上下文
973
+ // 三道防线:路径白名单(执行时二次强制,响应「注册≠强制」教训)→ 确认 → 运行时白名单三档
974
+ const scriptPath = strArg('script_path');
975
+ if (!scriptPath) {
976
+ throw toolError('run_project_script 缺少 script_path 参数', '未传脚本路径', ['传相对项目根目录的脚本路径,如 "scripts/test.py"'], undefined, ToolErrorCode.ARGUMENT_ERROR);
977
+ }
978
+ const scriptArgs = Array.isArray(args['args']) ? args['args'] : [];
979
+ // ① 路径白名单:相对项目根解析,防穿越返回 null → 拒绝(越界不协商)
980
+ const fullPath = resolveSafePath(this.builtinHandlers.projectPath, scriptPath);
981
+ if (!fullPath) {
982
+ return `[ERR:PATH_DENIED] 脚本路径越界(超出项目根):"${scriptPath}"`;
983
+ }
984
+ // ② 执行确认:guest 模式或 confirmScripts 时询问(owner 默认自动批准,走审计)
985
+ const confirmed = await this.security.confirmScriptRun(fullPath, 'run_project_script', `运行项目脚本 ${scriptPath}`);
986
+ if (!confirmed) {
987
+ return '[ERR:SCRIPT_DECLINE] 脚本运行未获确认(用户拒绝或未注入确认回调,fail-closed)';
988
+ }
989
+ // ③ 运行时白名单:扩展名推断并收敛到 node/python/shell 三档(推断即可信,不规则兜底 node)
990
+ const ext = scriptPath.slice(scriptPath.lastIndexOf('.')).toLowerCase();
991
+ const runtime = normalizeScriptRuntime(inferRuntimeFromExt(ext) ?? 'node');
992
+ // timeout_ms(秒,可选)→ 毫秒透传内核执行器(默认 60s,上限 600s,见 skillScriptRunner 常量);
993
+ // 脚本内 API 调用/批处理等长耗时任务由 LLM 按需传参,避免误超时
994
+ const timeoutMs = Number.isFinite(args['timeout_ms'])
995
+ ? Number(args['timeout_ms']) * 1000
996
+ : undefined;
997
+ const result = await runSkillScript(fullPath, runtime, scriptArgs, timeoutMs,
998
+ // cwd=项目根:项目脚本可加载项目本地依赖/相对数据文件
999
+ this.builtinHandlers.projectPath,
1000
+ // node 路径(可选):node runtime 分支走宿主注入的真实 node,避免无独立 node 时 ENOENT
1001
+ this.scriptNodePath);
1002
+ // 返回净化:脚本输出当外部内容去控制字符 + 长度上限(防刷屏撑爆上下文,对齐 run_skill_script)
1003
+ // 超时文案按本次实际超时生成(未传 timeout_ms → 内核默认 60s,见上),不恒报上限 600s
1004
+ return sanitizeExternalText(formatScriptResult(result, timeoutMs), RUN_SCRIPT_RESULT_MAX_LEN);
1005
+ }
1006
+ case 'list_resources': {
1007
+ // 渐进披露 L3:列出技能的资源清单
1008
+ if (!this.listResources) {
1009
+ return '[ERR:TOOL:NOT_AVAILABLE] list_resources 不可用';
1010
+ }
1011
+ const skillName = strArg('skill_name');
1012
+ return await this.listResources(skillName);
1013
+ }
1014
+ case 'list_skills': {
1015
+ // 渐进披露 L1 补充:列出所有技能清单(>50 技能时使用)
1016
+ if (!this.listSkills) {
1017
+ return '[ERR:TOOL:NOT_AVAILABLE] list_skills 不可用';
1018
+ }
1019
+ return await this.listSkills();
1020
+ }
1021
+ case 'register_work': {
1022
+ // 作品投影登记(registerWork 回调由 agent 装配注入;用户主动触发写索引卡片)
1023
+ if (!this.registerWork) {
1024
+ return '[ERR:TOOL:NOT_AVAILABLE] register_work 不可用:未装配作品投影登记回调';
1025
+ }
1026
+ const path = strArg('path');
1027
+ const description = strArg('description');
1028
+ if (!path || !description) {
1029
+ return '[ERR:INVALID_ARG] register_work 需要 path(相对项目根)与 description(一句话说明)参数';
1030
+ }
1031
+ return await this.registerWork(path, description);
1032
+ }
1033
+ case 'run_team_meeting': {
1034
+ // 评估/评审型小组会议(runTeamMeeting 回调由 agent 装配注入;工具内嵌一次 LLM 调用)
1035
+ if (!this.runTeamMeeting) {
1036
+ return '[ERR:TOOL:NOT_AVAILABLE] run_team_meeting 不可用:未装配小组会议回调';
1037
+ }
1038
+ const group = strArg('group');
1039
+ const topic = strArg('topic');
1040
+ // 参数缺失由回调内 runTeamMeetingAssessment 统一抛 ARGUMENT_ERROR,此处前置兜底给不可信入参
1041
+ if (!group) {
1042
+ throw toolError('run_team_meeting 缺少 group 参数', '未传组名(组名 = 组长角色包名)', ['传组名,如 run_team_meeting("设计组", "议题")'], undefined, ToolErrorCode.ARGUMENT_ERROR);
1043
+ }
1044
+ if (!topic) {
1045
+ throw toolError('run_team_meeting 缺少 topic 参数', '未传要评估的议题', ['传一句议题,越具体越好'], undefined, ToolErrorCode.ARGUMENT_ERROR);
1046
+ }
1047
+ return await this.runTeamMeeting(group, topic);
1048
+ }
111
1049
  default: {
112
1050
  // 自定义工具 fallback:查找 customTools Map
113
1051
  const custom = this.customTools.get(name);
@@ -132,9 +1070,7 @@ export class ToolExecutor {
132
1070
  }
133
1071
  }
134
1072
  throw toolError('未知工具', `agent 调用了未注册的工具:${name}`, [
135
- `已注册工具:${this.list
136
- .map((t) => t.name)
137
- .join(', ')}`,
1073
+ `已注册工具:${this.list.map((t) => t.name).join(', ')}`,
138
1074
  '检查 personality.md 是否限制了工具集',
139
1075
  ], undefined, ToolErrorCode.UNKNOWN_TOOL);
140
1076
  }