@zooique/memora 2.0.3 → 3.0.1

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 +1037 -0
  2. package/README.en.md +305 -272
  3. package/README.md +303 -260
  4. package/config.example.json +43 -0
  5. package/dist/agent/agent.d.ts +376 -408
  6. package/dist/agent/agent.d.ts.map +1 -1
  7. package/dist/agent/agent.js +1166 -926
  8. package/dist/agent/agent.js.map +1 -1
  9. package/dist/agent/assembler.d.ts +105 -43
  10. package/dist/agent/assembler.d.ts.map +1 -1
  11. package/dist/agent/assembler.js +543 -75
  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 +225 -38
  18. package/dist/agent/builtinToolHandlers.d.ts.map +1 -1
  19. package/dist/agent/builtinToolHandlers.js +748 -132
  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 +137 -20
  30. package/dist/agent/constants.d.ts.map +1 -1
  31. package/dist/agent/constants.js +152 -20
  32. package/dist/agent/constants.js.map +1 -1
  33. package/dist/agent/contextManager.d.ts +119 -77
  34. package/dist/agent/contextManager.d.ts.map +1 -1
  35. package/dist/agent/contextManager.js +192 -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 +716 -295
  50. package/dist/agent/loop.d.ts.map +1 -1
  51. package/dist/agent/loop.js +2178 -664
  52. package/dist/agent/loop.js.map +1 -1
  53. package/dist/agent/managers/archiveCoordinator.d.ts +14 -171
  54. package/dist/agent/managers/archiveCoordinator.d.ts.map +1 -1
  55. package/dist/agent/managers/archiveCoordinator.js +19 -287
  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 +31 -115
  62. package/dist/agent/managers/dedupManager.d.ts.map +1 -1
  63. package/dist/agent/managers/dedupManager.js +48 -136
  64. package/dist/agent/managers/dedupManager.js.map +1 -1
  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 +8 -25
  74. package/dist/agent/managers/llmJudgeHelper.d.ts.map +1 -1
  75. package/dist/agent/managers/llmJudgeHelper.js +7 -24
  76. package/dist/agent/managers/llmJudgeHelper.js.map +1 -1
  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 +85 -207
  84. package/dist/agent/managers/memoryAdvisor.js.map +1 -1
  85. package/dist/agent/managers/memoryGovernance.d.ts +10 -42
  86. package/dist/agent/managers/memoryGovernance.d.ts.map +1 -1
  87. package/dist/agent/managers/memoryGovernance.js +19 -36
  88. package/dist/agent/managers/memoryGovernance.js.map +1 -1
  89. package/dist/agent/managers/memoryInspector.d.ts +59 -268
  90. package/dist/agent/managers/memoryInspector.d.ts.map +1 -1
  91. package/dist/agent/managers/memoryInspector.js +91 -442
  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 -68
  98. package/dist/agent/managers/sessionArchiver.d.ts.map +1 -1
  99. package/dist/agent/managers/sessionArchiver.js +76 -147
  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 +8 -33
  110. package/dist/agent/managers/streamAccumulator.d.ts.map +1 -1
  111. package/dist/agent/managers/streamAccumulator.js +4 -29
  112. package/dist/agent/managers/streamAccumulator.js.map +1 -1
  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 +9 -41
  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 +55 -100
  122. package/dist/agent/managers/workProjection.d.ts.map +1 -1
  123. package/dist/agent/managers/workProjection.js +164 -268
  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 +236 -10
  158. package/dist/agent/toolExecutor.d.ts.map +1 -1
  159. package/dist/agent/toolExecutor.js +932 -21
  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 +510 -169
  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 +27 -98
  198. package/dist/config/loader.d.ts.map +1 -1
  199. package/dist/config/loader.js +110 -223
  200. package/dist/config/loader.js.map +1 -1
  201. package/dist/index.d.ts +67 -77
  202. package/dist/index.d.ts.map +1 -1
  203. package/dist/index.js +90 -69
  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 +22 -32
  210. package/dist/llm/factory.d.ts.map +1 -1
  211. package/dist/llm/factory.js +29 -46
  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 +179 -120
  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 -31
  226. package/dist/logging/logger.d.ts.map +1 -1
  227. package/dist/logging/logger.js +28 -215
  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 +11 -44
  236. package/dist/memory/governance.d.ts.map +1 -1
  237. package/dist/memory/governance.js +11 -64
  238. package/dist/memory/governance.js.map +1 -1
  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 +25 -175
  252. package/dist/memory/inMemoryStorage.d.ts.map +1 -1
  253. package/dist/memory/inMemoryStorage.js +32 -221
  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 +2 -2
  260. package/dist/memory/lockManager.d.ts.map +1 -1
  261. package/dist/memory/lockManager.js +6 -8
  262. package/dist/memory/lockManager.js.map +1 -1
  263. package/dist/memory/projectManager.d.ts +30 -129
  264. package/dist/memory/projectManager.d.ts.map +1 -1
  265. package/dist/memory/projectManager.js +39 -189
  266. package/dist/memory/projectManager.js.map +1 -1
  267. package/dist/memory/projectRegistry.d.ts +14 -62
  268. package/dist/memory/projectRegistry.d.ts.map +1 -1
  269. package/dist/memory/projectRegistry.js +27 -111
  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 +10 -12
  284. package/dist/memory/sourceValidation.d.ts.map +1 -1
  285. package/dist/memory/sourceValidation.js +16 -32
  286. package/dist/memory/sourceValidation.js.map +1 -1
  287. package/dist/memory/storageInterface.d.ts +44 -150
  288. package/dist/memory/storageInterface.d.ts.map +1 -1
  289. package/dist/memory/types.d.ts +65 -164
  290. package/dist/memory/types.d.ts.map +1 -1
  291. package/dist/memory/types.js +81 -83
  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 +136 -19
  342. package/dist/skill/skillManager.d.ts.map +1 -1
  343. package/dist/skill/skillManager.js +312 -61
  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 -16
  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 +40 -72
  366. package/dist/utils/configResourceManager.d.ts.map +1 -1
  367. package/dist/utils/configResourceManager.js +78 -76
  368. package/dist/utils/configResourceManager.js.map +1 -1
  369. package/dist/utils/errors.d.ts +44 -69
  370. package/dist/utils/errors.d.ts.map +1 -1
  371. package/dist/utils/errors.js +54 -71
  372. package/dist/utils/errors.js.map +1 -1
  373. package/dist/utils/eventEmitter.d.ts +104 -103
  374. package/dist/utils/eventEmitter.d.ts.map +1 -1
  375. package/dist/utils/eventEmitter.js +37 -45
  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 +2 -3
  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 +25 -3
  396. package/dist/utils/math.d.ts.map +1 -1
  397. package/dist/utils/math.js +32 -16
  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 -19
  416. package/dist/utils/segmenter.d.ts.map +1 -1
  417. package/dist/utils/segmenter.js +230 -97
  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 +416 -476
  458. package/docs/memora-/346/216/245/345/205/245/346/214/207/345/215/227.md +841 -806
  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 -100
  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 -49
  503. package/dist/agent/guardrail.d.ts.map +0 -1
  504. package/dist/agent/guardrail.js +0 -59
  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 -146
  509. package/dist/agent/managers/autoConfigRefiner.js.map +0 -1
  510. package/dist/agent/managers/configManager.d.ts +0 -209
  511. package/dist/agent/managers/configManager.d.ts.map +0 -1
  512. package/dist/agent/managers/configManager.js +0 -331
  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 -239
  519. package/dist/agent/managers/memoryDecayScheduler.d.ts.map +0 -1
  520. package/dist/agent/managers/memoryDecayScheduler.js +0 -413
  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 -88
  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 -79
  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 -50
  551. package/dist/memory/hybridMerge.d.ts.map +0 -1
  552. package/dist/memory/hybridMerge.js +0 -51
  553. package/dist/memory/hybridMerge.js.map +0 -1
  554. package/dist/memory/inMemoryRelationStore.d.ts +0 -58
  555. package/dist/memory/inMemoryRelationStore.d.ts.map +0 -1
  556. package/dist/memory/inMemoryRelationStore.js +0 -80
  557. package/dist/memory/inMemoryRelationStore.js.map +0 -1
  558. package/dist/memory/loader.d.ts +0 -51
  559. package/dist/memory/loader.d.ts.map +0 -1
  560. package/dist/memory/loader.js +0 -100
  561. package/dist/memory/loader.js.map +0 -1
  562. package/dist/memory/recall.d.ts +0 -82
  563. package/dist/memory/recall.d.ts.map +0 -1
  564. package/dist/memory/recall.js +0 -157
  565. package/dist/memory/recall.js.map +0 -1
  566. package/dist/memory/relationStore.d.ts +0 -95
  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 -71
  571. package/dist/memory/store.d.ts.map +0 -1
  572. package/dist/memory/store.js +0 -199
  573. package/dist/memory/store.js.map +0 -1
  574. package/dist/memory/userProfile.d.ts +0 -180
  575. package/dist/memory/userProfile.d.ts.map +0 -1
  576. package/dist/memory/userProfile.js +0 -381
  577. package/dist/memory/userProfile.js.map +0 -1
  578. package/dist/memory/vectorStore.d.ts +0 -179
  579. package/dist/memory/vectorStore.d.ts.map +0 -1
  580. package/dist/memory/vectorStore.js +0 -284
  581. package/dist/memory/vectorStore.js.map +0 -1
  582. package/dist/persona/personaManager.d.ts +0 -109
  583. package/dist/persona/personaManager.d.ts.map +0 -1
  584. package/dist/persona/personaManager.js +0 -315
  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
@@ -0,0 +1,1082 @@
1
+ /**
2
+ * 角色包管理器:继承 ConfigResourceManager 复用关键词匹配/生命周期;
3
+ * 角色包特有状态(activeRolePack / 粘性匹配 / 自建扫描 / 切换防抖)保留在子类。
4
+ * manifest.json 为唯一核心控制文件,内容文件独立按路径注册装载。
5
+ */
6
+ import { readFile, readdir, access, stat } from 'node:fs/promises';
7
+ import { join, dirname } from 'node:path';
8
+ import { logger } from '../logging/logger.js';
9
+ import { getLogger } from '../utils/loggerHolder.js';
10
+ import { ConfigResourceManager } from '../utils/configResourceManager.js';
11
+ import { resolveSubdir, scanMarkdownDir, resolveSafePath, inferRuntimeFromExt, isFolderFormSkill, } from '../utils/scanner.js';
12
+ import { discoverSkillLayer3, projectDiscoveredLayer3, resolveSkillDir, findLayer3Resource, findLayer3Script, resolveLayer3ResourcePath, resolveLayer3ScriptPath, } from '../skill/skillLayer3.js';
13
+ import { DEFAULT_MAX_CONTENT_LEN, readFileCapped, existsSyncSafe } from '../utils/fileSafe.js';
14
+ import { parseFrontmatter } from '../utils/frontmatter.js';
15
+ import { validateManifest, checkCompanionContentRedline, MAX_HANDOFF_PROMPT_LEN, } from '../role-pack/validator.js';
16
+ import { BUILTIN_FALLBACK_PACK, MAX_TEAM_MEMBERS } from '../role-pack/constants.js';
17
+ import { SkillManager, L1_COMPRESSED_THRESHOLD, L1_LIST_TOOL_THRESHOLD, } from '../skill/skillManager.js';
18
+ import { assembleRolePack } from '../role-pack/strategyResolver.js';
19
+ /** formatVersion 缺省值(未声明时按 1.0.0) */
20
+ const DEFAULT_FORMAT_VERSION = '1.0.0';
21
+ /** 角色包扫描需排除的非包文件(如 README 等允许放在包根) */
22
+ const EXCLUDED_FILES = new Set(['manifest.json']);
23
+ /** 规则文件约定名:manifest 未声明 rules 路径时回退 rules.md(消除路径写错静默丢规则) */
24
+ const DEFAULT_RULES_FILENAME = 'rules.md';
25
+ /** persona 约定文件名:manifest 未声明 persona 路径时回退 persona.md(与 rules.md 对称) */
26
+ const DEFAULT_PERSONA_FILENAME = 'persona.md';
27
+ /**
28
+ * manifest.json 最大长度(字符):JSON 必须完整解析不可截断,超限直接跳过装载。
29
+ */
30
+ const MAX_MANIFEST_LEN = 512 * 1024;
31
+ /** 从 manifest.strategy 解析策略声明(只取四阶段下声明过的键,未声明阶段由 mergeStrategy 补默认值,无 strategy 返回 undefined) */
32
+ function parseStrategyNode(strategyNode) {
33
+ if (typeof strategyNode !== 'object' || strategyNode === null)
34
+ return undefined;
35
+ const stages = {};
36
+ for (const [stage, node] of Object.entries(strategyNode)) {
37
+ if (typeof node !== 'object' || node === null)
38
+ continue;
39
+ stages[stage] = { ...node };
40
+ }
41
+ if (Object.keys(stages).length === 0)
42
+ return undefined;
43
+ return {
44
+ prepare: stages['prepare'],
45
+ act: stages['act'],
46
+ reflect: stages['reflect'],
47
+ global: stages['global'],
48
+ };
49
+ }
50
+ /** 从 persona.md frontmatter 解析 traits.* 数值键值对(clamp 0-1),无 traits 返回 undefined */
51
+ function parseTraits(personaContent) {
52
+ if (!personaContent)
53
+ return undefined;
54
+ const { frontmatter } = parseFrontmatter(personaContent);
55
+ const traits = {};
56
+ for (const [key, value] of Object.entries(frontmatter)) {
57
+ if (!key.startsWith('traits.'))
58
+ continue;
59
+ const traitName = key.slice(7);
60
+ const numVal = Number(value);
61
+ if (!Number.isNaN(numVal)) {
62
+ traits[traitName] = Math.max(0, Math.min(1, numVal)); // clamp 0-1
63
+ }
64
+ }
65
+ return Object.keys(traits).length > 0 ? traits : undefined;
66
+ }
67
+ /** 解析接手衔接提示词(宿主带入对话时预填的话术);非空字符串,否则 undefined。角色包只描述自己(插卡解耦)。长度上限运行时兜底(validator 报错,此处截断)。 */
68
+ function parseHandoffPrompt(raw) {
69
+ if (typeof raw !== 'string' || raw.trim() === '')
70
+ return undefined;
71
+ const trimmed = raw.trim();
72
+ return trimmed.length > MAX_HANDOFF_PROMPT_LEN
73
+ ? trimmed.slice(0, MAX_HANDOFF_PROMPT_LEN)
74
+ : trimmed;
75
+ }
76
+ /**
77
+ * 扫描 skills/ 目录动态注册技能:复用 scanMarkdownDir,frontmatter 的 name/description+正文。
78
+ * manifest.skills 仅作可选白名单过滤(声明 file 路径时),未声明则全量扫描(零配置)。
79
+ */
80
+ async function scanPackSkills(packDir, skillsNode) {
81
+ const scanned = await scanMarkdownDir(join(packDir, 'skills'));
82
+ // 用 entry.filePath 计算相对路径(支持单文件 .md 和文件夹 SKILL.md 两种形式)
83
+ const skills = scanned.map(async (entry) => {
84
+ // 从绝对路径计算相对于 packDir 的路径,统一用正斜杠
85
+ const absPath = entry.filePath;
86
+ let relPath = absPath.slice(packDir.length);
87
+ if (relPath.startsWith('/') || relPath.startsWith('\\'))
88
+ relPath = relPath.slice(1);
89
+ relPath = relPath.replace(/\\/g, '/');
90
+ // L3 隔离纪律(对齐 Claude Code 主流):
91
+ // 仅「文件夹形态」(入口为 SKILL.md)发现 resources/ scripts/;顶层裸 .md 的目录 = 技能池共享根,
92
+ // 同级扫描会误并入其他技能的资源/脚本 → 污染。故裸 .md 为纯 L1/L2,带 L3 必须用文件夹+SKILL.md。
93
+ // 该规则与 skillManager.createEntry **逐字同构**;发现与投影均已收口于 skillLayer3
94
+ // (discoverSkillLayer3 + projectDiscoveredLayer3,SSOT)——两侧各写一遍投影,
95
+ // 改一处即静默漂移。
96
+ const l3 = await discoverSkillLayer3(absPath);
97
+ return {
98
+ file: relPath,
99
+ name: entry.name,
100
+ description: entry.frontmatter['description'] ?? undefined,
101
+ layer3: projectDiscoveredLayer3(l3),
102
+ };
103
+ });
104
+ const resolved = await Promise.all(skills);
105
+ // 区分「未声明 skills」(全量扫描)与「skills: []」(零技能)
106
+ if (!Array.isArray(skillsNode))
107
+ return resolved;
108
+ if (skillsNode.length === 0)
109
+ return [];
110
+ // manifest.skills 声明了 name/description 时覆盖 frontmatter 原值
111
+ // 构建 manifest 配置映射,按 file 路径关联
112
+ const manifestSkillConfigs = new Map();
113
+ for (const item of skillsNode) {
114
+ if (typeof item !== 'object' || item === null)
115
+ continue;
116
+ const config = item;
117
+ const file = config['file'];
118
+ if (typeof file === 'string' && file.trim() !== '') {
119
+ // 路径穿越防护:复用 resolveSafePath(边界前缀 + sep,防兄弟目录前缀绕过),确保结果仍在 packDir 内
120
+ if (!resolveSafePath(packDir, file)) {
121
+ logger.warn({ file, packDir }, 'manifest.skills.file 路径穿越被阻止,已忽略');
122
+ continue;
123
+ }
124
+ manifestSkillConfigs.set(file, config);
125
+ }
126
+ }
127
+ // 白名单过滤 + manifest 元数据覆盖
128
+ const allowedFiles = new Set(manifestSkillConfigs.keys());
129
+ return resolved
130
+ .filter((s) => s.file && allowedFiles.has(s.file))
131
+ .map((entry) => {
132
+ const config = manifestSkillConfigs.get(entry.file);
133
+ if (!config)
134
+ return entry;
135
+ // manifest 声明的 name/description 覆盖 frontmatter 值;未声明则保留原值
136
+ return {
137
+ ...entry,
138
+ name: typeof config['name'] === 'string' && config['name'].trim() !== ''
139
+ ? config['name']
140
+ : entry.name,
141
+ description: typeof config['description'] === 'string' && config['description'].trim() !== ''
142
+ ? config['description']
143
+ : entry.description,
144
+ };
145
+ });
146
+ }
147
+ /** 从 manifest.capabilities 解析能力声明(能力面与技能内容分离,经 capabilityMap 映射为工具白名单) */
148
+ function parseManifestCapabilities(capabilitiesNode) {
149
+ if (!Array.isArray(capabilitiesNode))
150
+ return [];
151
+ const capabilities = [];
152
+ for (const item of capabilitiesNode) {
153
+ if (typeof item !== 'object' || item === null)
154
+ continue;
155
+ const record = item;
156
+ const capability = record['capability'];
157
+ if (typeof capability !== 'string' || capability.trim() === '')
158
+ continue;
159
+ const description = record['description'];
160
+ capabilities.push({
161
+ capability,
162
+ description: typeof description === 'string' ? description : undefined,
163
+ });
164
+ }
165
+ return capabilities;
166
+ }
167
+ /**
168
+ * 解析 rules 内容为规则字符串列表(rule 语义对齐,支持常见 markdown 写法)。
169
+ *
170
+ * 拆规则标准(保守、可预测、向前兼容):
171
+ * - 列表行(- / * / 数字. )→ 逐条规则(保留历史行为);
172
+ * - 连续普通段落文本 → 整段合并为一条规则(段落是一个完整规则语义);
173
+ * - 引用块(> )→ 去 > 前缀后按段落处理(无空行分隔则并入相邻段落);
174
+ * - 排除非规则内容:标题(# )、代码块(``` 围栏)、HTML 注释、空行、分隔线、表格行。
175
+ *
176
+ * 确定性注入、装载时全量,不允许丢规则——无法识别的内容宁可并入最近的段落也不静默丢弃。
177
+ */
178
+ function parseRules(content) {
179
+ if (!content)
180
+ return [];
181
+ const rules = [];
182
+ // 段落累积缓冲:连续普通文本行合并为一条规则(保序)
183
+ let paraBuffer = [];
184
+ // 代码块围栏状态:围栏内的行跳过(不是规则)
185
+ let inCodeBlock = false;
186
+ /** 段落收尾:把累积的段落作为一条规则推入(保证"能识别的都不丢") */
187
+ const flushPara = () => {
188
+ if (paraBuffer.length > 0) {
189
+ // 段落内部多行用空格连接、压缩连续空白,保持单条语义
190
+ const rule = paraBuffer.join(' ').replace(/\s+/g, ' ').trim();
191
+ if (rule)
192
+ rules.push(rule);
193
+ paraBuffer = [];
194
+ }
195
+ };
196
+ const lines = content.split('\n');
197
+ for (const raw of lines) {
198
+ const trimmed = raw.trim();
199
+ // 代码块围栏:切换状态并收尾当前段落(围栏内行不作为规则)
200
+ if (/^```/.test(trimmed)) {
201
+ inCodeBlock = !inCodeBlock;
202
+ flushPara();
203
+ continue;
204
+ }
205
+ if (inCodeBlock)
206
+ continue;
207
+ // 非规则内容:空行 / HTML 注释 / 分隔线 / 表格行 → 段落边界
208
+ if (trimmed === '' ||
209
+ /^<!--/.test(trimmed) ||
210
+ /^---+$/.test(trimmed) ||
211
+ trimmed.startsWith('|')) {
212
+ flushPara();
213
+ continue;
214
+ }
215
+ // 列表行(无序 -/* 或有序 n.)→ 逐条规则
216
+ const listMatch = /^[-*]\s+(.+)$/.exec(trimmed) || /^\d+\.\s+(.+)$/.exec(trimmed);
217
+ if (listMatch) {
218
+ flushPara(); // 列表项前后是独立规则边界
219
+ const rule = listMatch[1]?.trim() ?? '';
220
+ if (rule)
221
+ rules.push(rule);
222
+ continue;
223
+ }
224
+ // 标题(# 至 ######)→ 段落边界(标题本身不是规则,其下内容继续解析)
225
+ if (/^#{1,6}\s+/.test(trimmed)) {
226
+ flushPara();
227
+ continue;
228
+ }
229
+ // 普通段落 / 引用块:累积进段落缓冲(引用块去 > 前缀)
230
+ paraBuffer.push(trimmed.replace(/^>\s?/, ''));
231
+ }
232
+ flushPara();
233
+ return rules;
234
+ }
235
+ /**
236
+ * 安全读取内容文件(persona / rules / 技能正文),失败按缺省返回空串(内容文件可选)。
237
+ *
238
+ * 外部可控内容装载进内存/上下文前统一限长,对齐 toolExecutor 外部内容防护;
239
+ * 长度上限 SSOT 在 utils/fileSafe.DEFAULT_MAX_CONTENT_LEN(与全局技能共用)。
240
+ *
241
+ * @param filePath 文件绝对路径
242
+ * @param maxLen 最大长度(字符),超限截断
243
+ */
244
+ async function readContentSafe(filePath, maxLen = DEFAULT_MAX_CONTENT_LEN) {
245
+ // 读取与截断收口于 fileSafe.readFileCapped(SSOT,与全局技能共用);
246
+ // 本包装只负责角色包语义:失败降级为空串 + 告警(内容文件可选)
247
+ const content = await readFileCapped(filePath, maxLen);
248
+ if (content === null) {
249
+ getLogger().warn({ file: filePath }, '角色包内容文件读取失败,按缺省处理');
250
+ return '';
251
+ }
252
+ return content;
253
+ }
254
+ /**
255
+ * 会议团队上下文块文案(纯函数;`buildTeamContextBlock` 委托本函数)。
256
+ *
257
+ * **how 单源纪律**:状态取值(done/blocked)与标注方法属「工具用法」,唯一真源 =
258
+ * `task_table_update` 工具描述(builtinTools.ts);本函数只承载**会议场景编排**
259
+ * (何时增项 / 何时声明 rolePack / 何时回到组长视角),不复述工具用法——
260
+ * 与 PLAN_NUDGE_PROMPT 同构(when/what 在此,how 指向描述)。守卫见 builtinTools.test。
261
+ */
262
+ export function buildTeamContextBlockText(leader, members) {
263
+ return (`【小组会议角色(组长:${leader};组员:${members.join(' / ')})】` +
264
+ `用户以「小组会议:主题」发起时,系统不自动预置完整流程,但已确定性预置骨架任务表` +
265
+ `(组长开场 → 每位组员各一项发言 → 汇总),你只需按骨架逐项执行;` +
266
+ `如需追加讨论轮次可自行用 task_table_write 增项,并在此项声明 rolePack=对应组员` +
267
+ `(执行到该项时切换为该组员视角作答);汇总项到达后不声明 rolePack(回到组长视角收尾)。` +
268
+ `每完成一个任务项即标记该任务项状态(状态取值与操作方法见 task_table_update 工具描述)。` +
269
+ `禁止用 write_file 创建或修改任务表文件(如 .memora/task-table.md)——任务表只能经 task_table_write 建表。`);
270
+ }
271
+ /**
272
+ * 角色包管理器
273
+ */
274
+ export class RolePackManager extends ConfigResourceManager {
275
+ /** 激活角色包变更回调(首次参数为变前,第二参数为变后;宿主据此同步 UI/记忆标题) */
276
+ onActiveChanged = null;
277
+ /** 切换锁定触发回调(宿主据此展示「角色切换已锁定 2 分钟」提示) */
278
+ onSwitchLocked = null;
279
+ /** 当前激活的角色包名 */
280
+ activePackName = null;
281
+ /** 角色切换时间戳列表(用于时间窗口缓冲) */
282
+ switchTimestamps = [];
283
+ /** 缓冲区开关(30s 内 5 次切换后锁定) */
284
+ switchLocked = false;
285
+ /** 锁定恢复计时器 */
286
+ unlockTimer = null;
287
+ /** 锁定自动恢复时间戳(ms epoch) */
288
+ unlockAt = null;
289
+ /** 时间窗口:30 秒 */
290
+ static SWITCH_WINDOW_MS = 30_000;
291
+ /** 窗口内最大切换次数:5 次 */
292
+ static MAX_SWITCHES_IN_WINDOW = 5;
293
+ /** 锁定后自动恢复时间:2 分钟 */
294
+ static AUTO_UNLOCK_MS = 120_000;
295
+ /** 宿主装配级兜底角色覆盖(§4.6:覆盖值须存在,否则回退内核常量 BUILTIN_FALLBACK_PACK) */
296
+ builtinFallbackRole = null;
297
+ /** 组(宿主装配级):组长角色包 + 组员名单(会议名单容器,非选择对象) */
298
+ rolePackTeams = [];
299
+ /**
300
+ * 本轮表层装配视角(会议机制):任务项级临时覆盖(persona/rules/skills 换、键不换)。
301
+ * 仅在本轮 prepare 生效,用完即回——非会议/越界时置 null 回落 activePack;不改 activePack。
302
+ */
303
+ roundAssemblyRole = null;
304
+ /**
305
+ * @param configDir 配置目录(角色包在 <configDir>/role-packs/ 下)
306
+ */
307
+ constructor(configDir) {
308
+ super(configDir, 'role-packs');
309
+ }
310
+ /**
311
+ * 设置宿主装配级兜底角色覆盖(可选):须指向存在的角色包,覆盖值不存在时回退内核常量。
312
+ * 由装配层在 load() 前调用(AgentOptions.builtinFallbackRole)。
313
+ */
314
+ setBuiltinFallbackRole(role) {
315
+ this.builtinFallbackRole = role;
316
+ }
317
+ /**
318
+ * 设置组数据(宿主装配级,会议名单容器):组长角色包 + 组员名单。
319
+ * 数据归宿主(用户级持久化),内核仅装配时校验(validateTeams);组员仅会议参与,不用于日常。
320
+ */
321
+ setRolePackTeams(teams) {
322
+ this.rolePackTeams = teams;
323
+ }
324
+ /**
325
+ * 获取当前 activePack 作为组长的队伍(供宿主 UI 消费的只读快照)。
326
+ * SSOT 出口:宿主拿 team 数据的唯一公共方法,不绕过内核直接读 globalState。
327
+ * 内部成员名单走 activeTeamMembers 截断(会议消费端约束超限部分不参与),
328
+ * 保证宿主看到的参与名单与内核会议实际消费一致。
329
+ *
330
+ * @returns 队伍快照(组长 + 截断后的组员);activePack 非组长或无队伍时返回 null
331
+ */
332
+ getActiveTeam() {
333
+ if (!this.activePackName)
334
+ return null;
335
+ const team = this.rolePackTeams.find((t) => t.leader === this.activePackName);
336
+ if (!team)
337
+ return null;
338
+ return { leader: team.leader, members: [...this.activeTeamMembers] };
339
+ }
340
+ /**
341
+ * 按组长名取队伍快照(run_team_meeting 等任意组解析入口,SSOT 与 getActiveTeam 同截断口径)。
342
+ *
343
+ * 与 getActiveTeam 的区别:不依赖当前激活角色,显式按组名称解析(组名 = 组长的角色包名)。
344
+ * 组员名单同样经 MAX_TEAM_MEMBERS 截断(队长 1 + 组员 ≤ 4 = 5 人组,超出部分不参与会议)。
345
+ *
346
+ * @param group 组名(= 组长的角色包名)
347
+ * @returns 队伍快照(组长 + 截断后的组员);未找到该组时返回 null
348
+ */
349
+ getTeam(group) {
350
+ if (!group)
351
+ return null;
352
+ const team = this.rolePackTeams.find((t) => t.leader === group);
353
+ if (!team)
354
+ return null;
355
+ return { leader: team.leader, members: team.members.slice(0, MAX_TEAM_MEMBERS) };
356
+ }
357
+ /**
358
+ * 组数据校验(装载后执行):组长身份唯一 / 成员名单非空 / 引用悬空 / 组长组员互斥 → warning(不阻塞装载)。
359
+ * 组员失效 → 会议时缺员跳过(resolveRoundAssemblyRole 内判定);组长失效 → 该组失效(仅影响会议)。
360
+ */
361
+ validateTeams() {
362
+ const leaderCount = new Map();
363
+ for (const team of this.rolePackTeams) {
364
+ if (team.members.length === 0) {
365
+ getLogger().warn({ leader: team.leader }, '组员名单为空,组不成立(仅影响会议,不影响日常)');
366
+ }
367
+ if (!this.items.some((p) => p.meta.name === team.leader)) {
368
+ getLogger().warn({ leader: team.leader }, '组组长引用的角色包不存在(悬空引用,该组失效仅影响会议)');
369
+ }
370
+ if (team.members.includes(team.leader)) {
371
+ getLogger().warn({ leader: team.leader }, '组长与组员身份互斥:组员名单含组长(互斥校验提示,不阻塞装载;会议解析时组长身份恒优先)');
372
+ }
373
+ for (const member of team.members) {
374
+ if (!this.items.some((p) => p.meta.name === member)) {
375
+ getLogger().warn({ leader: team.leader, member }, '组员引用的角色包不存在(悬空引用,会议时缺员跳过)');
376
+ }
377
+ }
378
+ if (team.members.length > MAX_TEAM_MEMBERS) {
379
+ getLogger().warn({ leader: team.leader, count: team.members.length, max: MAX_TEAM_MEMBERS }, '组员数量超上限(队长 1 + 组员 ≤ 4 = 5 人组):超出部分不参与会议(仅影响会议)');
380
+ }
381
+ leaderCount.set(team.leader, (leaderCount.get(team.leader) ?? 0) + 1);
382
+ }
383
+ for (const [leader, count] of leaderCount) {
384
+ if (count > 1) {
385
+ getLogger().warn({ leader, count }, '组长身份不唯一:一个角色包只能是一个组的组长');
386
+ }
387
+ }
388
+ }
389
+ /**
390
+ * 设置本轮表层装配视角(会议机制):任务项级临时覆盖。
391
+ * 由 prepare 期按 active 任务项 rolePack 解析结果设置;非会议/越界 → null(回落 activePack)。
392
+ */
393
+ setRoundAssemblyRole(role) {
394
+ this.roundAssemblyRole = role;
395
+ }
396
+ /** 本轮表层装配视角(会议机制):非会议返回 null(回落 activePack) */
397
+ get roundAssemblyPerspective() {
398
+ return this.roundAssemblyRole;
399
+ }
400
+ /**
401
+ * 会议组员的**唯一消费入口**(SSOT 收口):当前激活角色作为组长时的会议组员名单,超限截断至
402
+ * `MAX_TEAM_MEMBERS` 名。
403
+ *
404
+ * 截断只发生在消费端——`rolePackTeams` 存储保持原样(超限数据不裁切,用户可自行修正),
405
+ * 因此「超限仅影响会议、不影响日常」的语义成立。所有会议消费点(装配角色解析 / 上下文块 /
406
+ * 会议任务项)必须走本 getter,禁止直连 `team.members`,否则「超出部分不参与会议」即成假契约。
407
+ */
408
+ get activeTeamMembers() {
409
+ if (!this.activePackName)
410
+ return [];
411
+ const team = this.rolePackTeams.find((t) => t.leader === this.activePackName);
412
+ return team ? team.members.slice(0, MAX_TEAM_MEMBERS) : [];
413
+ }
414
+ /**
415
+ * 会议机制:解析本轮表层装配角色(范围校验前置,防 LLM 幻觉角色名)。
416
+ * rolePack 必须 ∈ {组长(activePack)} ∪ {组员};越界 → 忽略该覆盖 + warning 返回 null;
417
+ * 组员角色包不存在(缺员)→ 跳过 + warning 返回 null。
418
+ *
419
+ * @param declared 任务项声明的 rolePack(无声明 = 非会议,返回 null)
420
+ * @returns 有效覆盖角色名;无覆盖/越界/缺员返回 null
421
+ */
422
+ resolveRoundAssemblyRole(declared) {
423
+ if (!declared)
424
+ return null;
425
+ // 组长(activePack)恒有效
426
+ if (declared === this.activePackName)
427
+ return declared;
428
+ // 组员:须在「组长 == activePack」的会议名单内(超限部分已被截断,不参与会议),且角色包存在
429
+ const isMember = this.activeTeamMembers.includes(declared);
430
+ if (isMember) {
431
+ if (this.items.some((p) => p.meta.name === declared))
432
+ return declared;
433
+ getLogger().warn({ declared, active: this.activePackName }, '会议任务项声明的组员角色包不存在(缺员),忽略该覆盖');
434
+ return null;
435
+ }
436
+ getLogger().warn({ declared, active: this.activePackName }, '会议任务项声明越界:rolePack 必须 ∈ {组长} ∪ {组员},忽略该覆盖');
437
+ return null;
438
+ }
439
+ /**
440
+ * 技能加载目标角色(会议机制):packName 显式指定优先 → 本轮装配视角(会议)→ 激活角色包。
441
+ * 会议内 skills 加载跟随任务项角色(组员技能正文读得到),键/工具面仍恒为 activePack。
442
+ */
443
+ resolveSkillTarget(packName) {
444
+ return packName ?? this.roundAssemblyRole ?? this.activePackName;
445
+ }
446
+ /**
447
+ * 构建「组长 + 组员名单」上下文块(会议机制实施前提②:组/成员清单暴露给 LLM,防编造角色名)。
448
+ * 仅当 activePack 是某个组的组长且名单非空时产出;非组长返回空串(不注入)。
449
+ *
450
+ * 文案正文在模块级 buildTeamContextBlockText(纯函数,供 how 单源守卫直测;本方法只做前置守卫)。
451
+ * 文案指挥 LLM 用 `task_table_write` 自主建表(确定性骨架预置见 tryBuildMeetingPlan,
452
+ * 二者互补:骨架补首轮确定性,文案约束 LLM 后续走 task_table 单通道):
453
+ * 明确组员发言任务项需声明 `rolePack` 以触发表层装配切换,汇总任务项不声明回到组长视角;
454
+ * 并显式禁止用 write_file 模拟任务表(write_file 写 `.memora/task-table.md`
455
+ * 伪建表会绕过 PlanItem 通道,导致顶部任务板不渲染)。
456
+ */
457
+ buildTeamContextBlock() {
458
+ if (!this.activePackName)
459
+ return '';
460
+ const members = this.activeTeamMembers;
461
+ if (members.length === 0)
462
+ return '';
463
+ return buildTeamContextBlockText(this.activePackName, members);
464
+ }
465
+ /**
466
+ * 会议机制:确定性骨架预置(SSOT 单一入口,「最小受控起点」半反转——
467
+ * 骨架确定性预置、内容与推进交 LLM,与 buildTeamContextBlock 文案互补)。
468
+ *
469
+ * 用户消息含「小组会议」**且** activePack 是某组组长 → 程序化预置占位骨架:
470
+ * 组长开场项(无 rolePack = 默认组长视角)+ 组员各一项(`rolePack=成员`,触发表层装配硬切换)
471
+ * + 汇总项尾(无 rolePack = 组长视角)。不固化发言内容与推进顺序,推进交 LLM;
472
+ * LLM 可经 task_table_write 追加额外任务项(骨架以 append 扩项,不锁死多轮交互)。
473
+ * 不引入会议引擎:任务项执行靠任务表每轮注入(assembler.getTaskTable)驱动 LLM 按项标记 done/blocked,
474
+ * 装配视角逐项切换(getTaskTable → applyActivePlanItemAssembly → applyActivePlanItemAssemblyIfChanged)。
475
+ *
476
+ * 主题取自「小组会议」后文(冒号/逗号/空格分隔均可),为空则任务项仅标「发言/汇总」由 LLM 见用户消息展开。
477
+ * 无 keyword / activePack 非组长 / 组名单空 → 返回 null(不触发,回落普通闭环)。
478
+ *
479
+ * @param input 用户输入
480
+ * @returns 预置骨架任务项(description + 可选 rolePack);不触发返回 null
481
+ */
482
+ tryBuildMeetingPlan(input) {
483
+ if (!this.activePackName)
484
+ return null;
485
+ if (!/小组会议/.test(input))
486
+ return null;
487
+ const members = this.activeTeamMembers;
488
+ if (members.length === 0)
489
+ return null;
490
+ const topic = input.replace(/^\s*小组会议\s*[::,,]?\s*/, '').trim();
491
+ const suffix = topic ? `:${topic}` : '';
492
+ // 组长开场项:无 rolePack(默认组长视角),语义上"主持引入议题"
493
+ const items = [
494
+ { description: `${this.activePackName} 主持开场${suffix}` },
495
+ ];
496
+ // 组员各一项发言,rolePack=组员(触发表层装配切换为该组员视角)
497
+ for (const m of members) {
498
+ items.push({ description: `${m} 发言${suffix}`, rolePack: m });
499
+ }
500
+ // 汇总项尾:无 rolePack(回到组长视角收尾)
501
+ items.push({ description: `汇总各方观点${suffix}` });
502
+ return items;
503
+ }
504
+ // ── 生命周期 ──────────────────────────────────────
505
+ /**
506
+ * 启动加载:扫描含 manifest.json 的角色包文件夹(覆盖基类 loadItems——基类只支持单文件扫描,生命周期其余仍复用基类)。
507
+ * 默认激活路径 = §4.1 单链:activePack(宿主注入)→ 兜底包(builtinFallbackRole ?? BUILTIN_FALLBACK_PACK)。
508
+ */
509
+ async load(activePack) {
510
+ const count = await this.scanRolePacks();
511
+ this.validateTeams();
512
+ if (activePack && this.items.some((p) => p.meta.name === activePack)) {
513
+ this.setActivePackName(activePack);
514
+ }
515
+ else {
516
+ if (activePack) {
517
+ logger.warn({ activePack }, '激活角色包不存在,落兜底包');
518
+ }
519
+ this.activateFallback();
520
+ }
521
+ logger.info({ count, active: this.activePackName }, '角色包加载完成');
522
+ return count;
523
+ }
524
+ /** 重载角色包:重新扫描 + 保持激活态(覆盖基类 reload,基类扫描无法覆盖文件夹包形态);激活包被删 → 落兜底包(§4.1 单链) */
525
+ async reload() {
526
+ const oldActiveName = this.activePackName;
527
+ // 磁盘扫描 + 保留 loadExtraDir 注入的运行时项(retainRuntimeItems 记账合并,同名以磁盘为准)——
528
+ // 与基类 reload 语义一致:重载不抹除无磁盘真理源的运行时注入用户角色包
529
+ const scanned = await this.scanRolePacksForReload();
530
+ this.items = this.retainRuntimeItems(scanned);
531
+ this.validateTeams();
532
+ const count = this.items.length;
533
+ // 保持当前激活角色包(若仍存在);否则落兜底包
534
+ if (oldActiveName) {
535
+ const found = this.items.find((p) => p.meta.name === oldActiveName);
536
+ if (found) {
537
+ this.setActivePackName(found.meta.name);
538
+ }
539
+ else {
540
+ this.activateFallback();
541
+ logger.warn({ oldActive: oldActiveName, newActive: this.activePackName }, '激活角色包已被删除,落兜底包');
542
+ }
543
+ }
544
+ else {
545
+ this.activateFallback();
546
+ }
547
+ logger.info({ count, active: this.activePackName }, '角色包已重载');
548
+ return count;
549
+ }
550
+ /**
551
+ * §4.1 单链兜底:激活兜底包(builtinFallbackRole ?? BUILTIN_FALLBACK_PACK)。
552
+ * 兜底包运行时缺失(用户手动删文件)→ 无 persona 继续运行 + warning(§7 降级优先,不装配失败)。
553
+ */
554
+ activateFallback() {
555
+ const fallback = this.builtinFallbackRole && this.items.some((p) => p.meta.name === this.builtinFallbackRole)
556
+ ? this.builtinFallbackRole
557
+ : BUILTIN_FALLBACK_PACK;
558
+ if (this.items.some((p) => p.meta.name === fallback)) {
559
+ this.setActivePackName(fallback);
560
+ }
561
+ else {
562
+ getLogger().warn({ fallback }, '兜底角色包运行时缺失(构建期应拦截),无 persona 继续运行(降级优先)');
563
+ this.setActivePackName(null);
564
+ }
565
+ }
566
+ /** 扫描角色包目录:每个子目录为一个角色包,须含 manifest.json;manifest.name 优先于文件夹名 */
567
+ async scanRolePacks() {
568
+ const dir = resolveSubdir(this.configDir, this.subdir);
569
+ if (!dir) {
570
+ this.items = [];
571
+ return 0;
572
+ }
573
+ this.items = await this.buildFromDir(dir);
574
+ return this.items.length;
575
+ }
576
+ /**
577
+ * 扫描角色包目录但只返回扫描结果(不改写 items):供 reload 复用 retainRuntimeItems 合并逻辑。
578
+ * 与 scanRolePacks 的差异:scanRolePacks 直接替换 items(load 场景),本方法返回纯扫描数组
579
+ * (reload 场景还需与运行时注入项合并,不能整体覆盖)。
580
+ */
581
+ async scanRolePacksForReload() {
582
+ const dir = resolveSubdir(this.configDir, this.subdir);
583
+ if (!dir)
584
+ return [];
585
+ return this.buildFromDir(dir);
586
+ }
587
+ /** 从角色包目录构建条目列表(仅文件夹形态) */
588
+ async buildFromDir(dir) {
589
+ let entries;
590
+ try {
591
+ await access(dir);
592
+ // 按字典序排序,保证扫描顺序确定性(避免文件系统依赖)
593
+ entries = (await readdir(dir))
594
+ .filter((f) => !f.startsWith('.') && !f.startsWith('_') && !EXCLUDED_FILES.has(f))
595
+ .sort((a, b) => a.localeCompare(b, 'zh-CN'));
596
+ }
597
+ catch {
598
+ getLogger().debug({ dir }, '角色包目录不存在,跳过');
599
+ return [];
600
+ }
601
+ const map = new Map();
602
+ for (const entry of entries) {
603
+ const fullPath = join(dir, entry);
604
+ try {
605
+ const st = await stat(fullPath);
606
+ if (!st.isDirectory())
607
+ continue; // 仅文件夹形态
608
+ const pack = await this.parseManifestPack(fullPath, entry);
609
+ if (pack)
610
+ map.set(pack.name, pack);
611
+ }
612
+ catch (err) {
613
+ getLogger().warn({ entry, err }, '扫描角色包失败');
614
+ }
615
+ }
616
+ return Array.from(map.values());
617
+ }
618
+ /** 解析单个角色包(manifest.json + 独立内容文件),失败返回 null */
619
+ async parseManifestPack(packDir, fallbackName) {
620
+ const manifestPath = join(packDir, 'manifest.json');
621
+ // 读取并解析 manifest.json
622
+ let manifest;
623
+ try {
624
+ const manifestRaw = await readFile(manifestPath, 'utf-8');
625
+ // manifest.json 必须完整解析不可截断,超限直接跳过装载(防超大 JSON 撑爆内存)
626
+ if (manifestRaw.length > MAX_MANIFEST_LEN) {
627
+ getLogger().warn({ manifestPath, size: manifestRaw.length, max: MAX_MANIFEST_LEN }, 'manifest.json 超出大小上限,跳过该角色包');
628
+ return null;
629
+ }
630
+ // 防御:VSCode 等编辑器保存 JSON 时可能加 UTF-8 BOM(U+FEFF),Node.js JSON.parse 不认
631
+ manifest = JSON.parse(manifestRaw.replace(/^\uFEFF/, ''));
632
+ }
633
+ catch (err) {
634
+ getLogger().warn({ manifestPath, err }, 'manifest.json 读取或解析失败,跳过该角色包');
635
+ return null;
636
+ }
637
+ // 接入格式校验器——error 级拒绝装载,与 manifest 解析失败同一失败语义(跳过 + warn),
638
+ // 杜绝「校验了却不拦」的折衷:带 error 的角色包不该进系统。
639
+ // warning 级仍装载,issues 全量透进装配(见文末健康徽章)。
640
+ const validation = validateManifest(manifest);
641
+ if (!validation.valid) {
642
+ const errors = validation.issues.filter((i) => i.severity === 'error');
643
+ getLogger().warn({ file: manifestPath, errors: errors.map((e) => e.message) }, '角色包校验未通过(error 级),跳过装载');
644
+ return null;
645
+ }
646
+ // 解析元数据(manifest 唯一权威)
647
+ const str = (v) => typeof v === 'string' ? v : v === undefined || v === null ? undefined : String(v);
648
+ const meta = {
649
+ name: str(manifest['name']) ?? fallbackName,
650
+ displayName: str(manifest['displayName']),
651
+ description: str(manifest['description']),
652
+ version: str(manifest['version']),
653
+ author: str(manifest['author']),
654
+ formatVersion: str(manifest['formatVersion']) ?? DEFAULT_FORMAT_VERSION,
655
+ interactionType: manifest['interactionType'] === 'companion' ? 'companion' : 'tool_assistant',
656
+ aiIdentityDisclosure: manifest['aiIdentityDisclosure'] === false ? false : true,
657
+ // 未成年人保护为强制项(非可配置):恒为 'required',与 validator「仅支持 required」一致;
658
+ // validator 对显式非法值(非 'required')记 error、阻塞装载;缺省时 validator 放行,
659
+ // 内核在此恒置 'required' 补齐缺省(强制手段是内核恒定取值,缺省不依赖 validator 拦截)。
660
+ minorProtection: 'required',
661
+ // 接手衔接提示词(自洽声明,宿主 prefill;空白视为未声明)
662
+ handoffPrompt: parseHandoffPrompt(manifest['handoffPrompt']),
663
+ };
664
+ // 解析 L2 策略
665
+ const strategy = parseStrategyNode(manifest['strategy']);
666
+ // 内容文件约定俗成固定文件名(消除 manifest 路径声明,避免路径写错静默丢内容)
667
+ // persona 需解析 frontmatter 提取 traits,正文仅取 body 部分
668
+ const rawPersonaContent = await readContentSafe(join(packDir, DEFAULT_PERSONA_FILENAME));
669
+ const { body: personaBody } = parseFrontmatter(rawPersonaContent);
670
+ const personaContent = personaBody;
671
+ const traits = parseTraits(rawPersonaContent);
672
+ const rulesContent = await readContentSafe(join(packDir, DEFAULT_RULES_FILENAME));
673
+ const rules = parseRules(rulesContent);
674
+ // 内嵌技能:目录动态扫描 + frontmatter(manifest.skills 可选过滤;新增技能只写文件)
675
+ const skills = await scanPackSkills(packDir, manifest['skills']);
676
+ // 装载时预缓存技能正文(与全局技能一致,消除 readSkillContent 磁盘 IO)
677
+ const skillsWithContent = await Promise.all(skills.map(async (skill) => {
678
+ if (!skill.file)
679
+ return { ...skill, content: null };
680
+ const skillAbsPath = join(packDir, skill.file);
681
+ try {
682
+ const content = await readContentSafe(skillAbsPath);
683
+ return { ...skill, content };
684
+ }
685
+ catch {
686
+ return { ...skill, content: null };
687
+ }
688
+ }));
689
+ // 能力声明(顶层数组,能力面与技能内容分离)
690
+ const capabilities = parseManifestCapabilities(manifest['capabilities']);
691
+ // companion 内容红线:正文在独立内容文件,由装载方读取后检测
692
+ if (meta.interactionType === 'companion') {
693
+ const redline = checkCompanionContentRedline(personaContent + rulesContent);
694
+ if (redline.length > 0) {
695
+ getLogger().error({ file: manifestPath, errors: redline.map((i) => i.message) }, 'companion 角色包触发内容红线,拒绝装载');
696
+ return null;
697
+ }
698
+ }
699
+ // 内容正文:persona(无 persona 时为空串)
700
+ const content = personaContent;
701
+ // 预计算装配结果并缓存(角色包装载后不可变,缓存安全)
702
+ const pack = {
703
+ // ConfigResource 约束字段
704
+ name: meta.name,
705
+ content,
706
+ filePath: manifestPath,
707
+ // 角色包特有字段
708
+ meta,
709
+ personaContent,
710
+ traits,
711
+ rules,
712
+ skills: skillsWithContent,
713
+ capabilities,
714
+ strategy,
715
+ };
716
+ // 预计算装配结果(mergeStrategy + personaPrompt 构建)
717
+ const cachedAssembly = assembleRolePack(pack);
718
+ // 健康徽章数据源:把 validateManifest 全量 issues(含 warning)透进装配,
719
+ // 宿主据此叠加徽章/问题列表;上方校验块只记 error 日志。
720
+ const assemblyWithIssues = {
721
+ ...cachedAssembly,
722
+ validationIssues: validation.issues,
723
+ };
724
+ return { ...pack, _cachedAssembly: assemblyWithIssues };
725
+ }
726
+ // ── 角色包管理 ────────────────────────────────────
727
+ /** 当前激活的角色包名 */
728
+ get activeName() {
729
+ return this.activePackName;
730
+ }
731
+ /**
732
+ * 注册激活角色包变更监听:每次激活状态实际变化(切换 / 默认激活 / 回退)时回调。
733
+ * @param handler 收到(from, to);to 为 null 表示激活被清空;同名不变不触发
734
+ */
735
+ onRolePackActivated(handler) {
736
+ this.onActiveChanged = handler;
737
+ }
738
+ /**
739
+ * 注册切换锁定触发监听:30s 内超过 5 次切换触发锁定时回调(2 分钟自动恢复)。
740
+ * @param handler 收到(reason, lockedSeconds);reason 为锁定原因描述
741
+ */
742
+ onRolePackSwitchLocked(handler) {
743
+ this.onSwitchLocked = handler;
744
+ }
745
+ /** 返回 parseManifestPack 时预缓存的 Assembly,避免重复 mergeStrategy + personaPrompt 构建;无激活返回 null */
746
+ getActive() {
747
+ if (!this.activePackName)
748
+ return null;
749
+ const pack = this.items.find((p) => p.meta.name === this.activePackName);
750
+ return pack?._cachedAssembly ?? null;
751
+ }
752
+ /** 当前激活角色包的规则列表(设定记忆归角色包后,规则以角色包为准);无激活返回空数组 */
753
+ getActiveRules() {
754
+ if (!this.activePackName)
755
+ return [];
756
+ const pack = this.items.find((p) => p.meta.name === this.activePackName);
757
+ return pack?.rules ?? [];
758
+ }
759
+ /** 按名称获取预缓存的装配结果,不存在返回 null */
760
+ get(name) {
761
+ const pack = this.items.find((p) => p.meta.name === name);
762
+ return pack?._cachedAssembly ?? null;
763
+ }
764
+ /** 激活指定角色包(30s 内超 5 次切换后锁定 2 分钟防抖,避免状态抖动);锁定或不存在返回 false */
765
+ activate(name) {
766
+ // 幂等短路(SSOT):同包重复激活是 no-op——无回调、无副作用,不应消耗切换限流配额。
767
+ // 「激活目标 == 当前激活」这一事实归属操作本身(而非门面),保证原生 API 与门面语义一致。
768
+ if (this.activePackName === name) {
769
+ return true;
770
+ }
771
+ // 缓冲区检查(限流保护,非错误)
772
+ if (this.switchLocked) {
773
+ logger.info({ name }, '角色包切换已锁定(30s 内超过 5 次),保持当前');
774
+ return false;
775
+ }
776
+ const found = this.items.find((p) => p.meta.name === name);
777
+ if (!found) {
778
+ logger.warn({ name }, '角色包不存在,激活失败');
779
+ return false;
780
+ }
781
+ // 记录切换时间戳并判定是否达到锁定阈值
782
+ const now = Date.now();
783
+ this.switchTimestamps.push(now);
784
+ const windowStart = now - RolePackManager.SWITCH_WINDOW_MS;
785
+ this.switchTimestamps = this.switchTimestamps.filter((t) => t > windowStart);
786
+ if (this.switchTimestamps.length >= RolePackManager.MAX_SWITCHES_IN_WINDOW) {
787
+ this.switchLocked = true;
788
+ this.unlockAt = now + RolePackManager.AUTO_UNLOCK_MS;
789
+ const lockedSeconds = Math.round(RolePackManager.AUTO_UNLOCK_MS / 1000);
790
+ const lockReason = `30s 内 ${this.switchTimestamps.length} 次切换,限流锁定 ${lockedSeconds}s`;
791
+ this.unlockTimer = setTimeout(() => {
792
+ this.switchLocked = false;
793
+ this.unlockAt = null;
794
+ this.unlockTimer = null;
795
+ this.switchTimestamps = [];
796
+ logger.info('角色包切换锁已自动恢复');
797
+ }, RolePackManager.AUTO_UNLOCK_MS);
798
+ logger.warn('角色包切换过于频繁,已锁定 2 分钟');
799
+ // 触发锁定回调:宿主据此展示锁定提示
800
+ this.onSwitchLocked?.(lockReason, lockedSeconds);
801
+ }
802
+ this.setActivePackName(name);
803
+ logger.info({ name }, '角色包已激活');
804
+ return true;
805
+ }
806
+ /** 统一激活态赋值:实际变化时触发 onActiveChanged 回调(宿主同步 UI/记忆标题) */
807
+ setActivePackName(next) {
808
+ const prev = this.activePackName;
809
+ this.activePackName = next;
810
+ if (prev !== next) {
811
+ try {
812
+ this.onActiveChanged?.(prev, next);
813
+ }
814
+ catch (err) {
815
+ logger.warn({ err, from: prev, to: next }, '角色包激活回调执行失败');
816
+ }
817
+ }
818
+ }
819
+ /** 角色切换锁定状态;unlockAt 为自动恢复时间戳(ms epoch),未锁定 null */
820
+ getSwitchLockStatus() {
821
+ return { locked: this.switchLocked, unlockAt: this.unlockAt };
822
+ }
823
+ /** 当前激活角色包的 traits,无则 undefined */
824
+ getActiveTraits() {
825
+ if (!this.activePackName)
826
+ return undefined;
827
+ const pack = this.items.find((p) => p.meta.name === this.activePackName);
828
+ return pack?.traits;
829
+ }
830
+ /** 清理资源:清除切换防抖锁计时器,防止关闭后回调触发 */
831
+ close() {
832
+ if (this.unlockTimer) {
833
+ clearTimeout(this.unlockTimer);
834
+ this.unlockTimer = null;
835
+ this.switchLocked = false;
836
+ this.unlockAt = null;
837
+ this.switchTimestamps = [];
838
+ }
839
+ }
840
+ /** 所有角色包元数据摘要列表 */
841
+ listMeta() {
842
+ return this.items.map((p) => p.meta);
843
+ }
844
+ // ── 基类抽象方法实现 ──────────────────────────────
845
+ /**
846
+ * 基类抽象实现:本管理器用自建扫描(parseManifestPack/buildFromDir)覆盖了
847
+ * loadItems/reload,不用基类单文件扫描,恒返回 null 以满足抽象约束。
848
+ */
849
+ async createEntry(_entry) {
850
+ // 角色包不用基类的单文件扫描,返回 null 跳过
851
+ return null;
852
+ }
853
+ // ── 系统提示 ──────────────────────────────────────
854
+ /** 构建 system prompt 的角色包段:personaPrompt + L1 技能清单(渐进披露);name 缺省用当前激活包 */
855
+ buildSystemPrompt(name) {
856
+ const targetName = name ?? this.activePackName;
857
+ if (!targetName)
858
+ return '';
859
+ const pack = this.items.find((p) => p.meta.name === targetName);
860
+ if (!pack)
861
+ return '';
862
+ // 使用预缓存的 Assembly,避免重复构建
863
+ const assembly = pack._cachedAssembly ?? assembleRolePack(pack);
864
+ const skills = assembly.skills.filter((s) => s.name || s.file);
865
+ const skillCount = skills.length;
866
+ // L1 阈值保护(token 经济性):
867
+ // ≤ 30 技能:完整 L1(name + full description + L3 tag)
868
+ // 31-50 技能:压缩 L1(name + 20 字摘要)
869
+ // > 50 技能:切换 list_skills 工具动态查询,不在 system prompt 枚举
870
+ if (skillCount > L1_LIST_TOOL_THRESHOLD) {
871
+ return (assembly.personaPrompt +
872
+ `\n\n【可用技能(${skillCount} 个,数量较多,使用 list_skills 工具查询具体清单)】`);
873
+ }
874
+ const compressed = skillCount > L1_COMPRESSED_THRESHOLD;
875
+ const listed = skills
876
+ .map((s) => {
877
+ const fallbackName = s.file ? this.deriveSkillNameFromFile(s.file) : undefined;
878
+ // SSOT: 使用 SkillManager.formatSkillForPrompt 统一格式化,压缩逻辑由 compress 参数控制
879
+ return SkillManager.formatSkillForPrompt(s, fallbackName, compressed);
880
+ })
881
+ .filter(Boolean);
882
+ const modeNote = compressed
883
+ ? '(技能较多,描述已压缩至 20 字,可用 read_skill 读取完整正文)'
884
+ : '(按需调用 read_skill 读取正文)';
885
+ const skillListBlock = listed.length > 0 ? `\n\n【可用技能(渐进披露 L1)${modeNote}】\n${listed.join('\n')}` : '';
886
+ return assembly.personaPrompt + skillListBlock;
887
+ }
888
+ /**
889
+ * 枚举激活角色包的内嵌技能(渐进披露 L1 清单来源,供宿主技能三源聚合展示)。
890
+ * 技能加载跟随本轮装配视角(会议内 = 任务项角色;非会议 = activePack)。
891
+ * name 缺省时按文件名去扩展名推导;无装配角色返回空数组。SSOT:技能清单只由
892
+ * 角色包自身持有,宿主不重复扫描目录。
893
+ */
894
+ listSkills() {
895
+ const target = this.resolveSkillTarget();
896
+ if (!target)
897
+ return [];
898
+ const pack = this.items.find((p) => p.meta.name === target);
899
+ if (!pack)
900
+ return [];
901
+ return pack.skills
902
+ .map((s) => ({
903
+ name: s.name ?? this.deriveSkillNameFromFile(s.file ?? ''),
904
+ description: s.description,
905
+ }))
906
+ .filter((s) => s.name.length > 0);
907
+ }
908
+ /**
909
+ * 读激活角色包内嵌技能正文(渐进披露 L2,read_skill 数据源)。优先返回 parseManifestPack
910
+ * 时缓存的正文;路径相对角色包目录天然受限,无需额外白名单校验。
911
+ */
912
+ async readSkillContent(skillName, packName) {
913
+ const found = this.findSkillByName(skillName, packName);
914
+ if (!found || !found.skill.file)
915
+ return null;
916
+ // 优先返回 parseManifestPack 时缓存的正文;缓存未就绪(如动态添加的技能)时回退实时读磁盘
917
+ if (found.skill.content !== undefined && found.skill.content !== null) {
918
+ return found.skill.content;
919
+ }
920
+ const skillFilePath = found.skill.file;
921
+ const { pack } = found;
922
+ // 技能文件路径相对角色包目录(manifest.json 所在目录)
923
+ const skillPath = join(dirname(pack.filePath), skillFilePath);
924
+ try {
925
+ return await readContentSafe(skillPath);
926
+ }
927
+ catch (err) {
928
+ getLogger().warn({ pack: pack.meta.name, skill: skillName, skillPath, err }, 'read_skill 读取技能正文失败');
929
+ return null;
930
+ }
931
+ }
932
+ // ── L3 资源/脚本访问 ──────────────────────────────────
933
+ /** 从技能文件路径推导技能名(单文件 write.md → write;文件夹 my-skill/SKILL.md → my-skill) */
934
+ deriveSkillNameFromFile(file) {
935
+ const parts = file.split(/[\\/]/);
936
+ const last = parts.pop() ?? '';
937
+ // 判定「文件夹形态」复用 scanner.isFolderFormSkill(SSOT,内置 Windows 大小写变体兼容)
938
+ if (isFolderFormSkill(last)) {
939
+ return parts.pop() ?? ''; // 文件夹形式
940
+ }
941
+ return last.replace(/\.(md|markdown)$/i, ''); // 单文件形式
942
+ }
943
+ /** 按技能名在装配视角角色包中查找技能条目(跟随本轮装配视角);frontmatter name 精确匹配优先,其次 file 路径推导;未找到返回 null */
944
+ findSkillByName(skillName, packName) {
945
+ const targetName = this.resolveSkillTarget(packName);
946
+ if (!targetName)
947
+ return null;
948
+ const pack = this.items.find((p) => p.meta.name === targetName);
949
+ if (!pack)
950
+ return null;
951
+ const skill = pack.skills.find((s) => {
952
+ if (s.name && s.name === skillName)
953
+ return true;
954
+ if (!s.file)
955
+ return false;
956
+ return this.deriveSkillNameFromFile(s.file) === skillName;
957
+ });
958
+ if (!skill)
959
+ return null;
960
+ return { pack, skill };
961
+ }
962
+ /**
963
+ * 按技能名定位其所在目录(角色包内),供 L3 资源/脚本访问
964
+ *
965
+ * 与 skillLayer3.resolveSkillDir 的分工:本方法负责「技能名 → 技能文件绝对路径」
966
+ * (需 packName 作用域,一个技能名可跨包重名);目录形态解析(文件夹 SKILL.md /
967
+ * 单文件 .md / file 直指目录)委托给后者(SSOT,与全局技能共用同一份规则)。
968
+ */
969
+ locateSkillDir(skillName, packName) {
970
+ const found = this.findSkillByName(skillName, packName);
971
+ if (!found || !found.skill.file)
972
+ return null;
973
+ return resolveSkillDir(join(dirname(found.pack.filePath), found.skill.file));
974
+ }
975
+ /** 读取技能 L3 资源文件内容(渐进披露 L3),不存在或读取失败返回 null */
976
+ async readSkillResource(skillName, resourcePath, packName) {
977
+ // layer3 白名单前置检查:资源必须已由 scanPackSkills 发现并登记(与全局 SkillManager 同标准),
978
+ // 防止未在清单内的路径(含兄弟目录前缀、escape 符)被 resolveSafePath 误放行
979
+ const found = this.findSkillByName(skillName, packName);
980
+ const layer3 = found?.skill.layer3;
981
+ if (!findLayer3Resource(layer3, resourcePath))
982
+ return null;
983
+ const skillDir = this.locateSkillDir(skillName, packName);
984
+ if (!skillDir)
985
+ return null;
986
+ // 路径穿越防护:双层——layer3 白名单 + resolveSafePath 边界前缀(收口于 skillLayer3,SSOT)。
987
+ // 读取基目录按条目来源 subdir 选择(resources/ 或 references/,兼容主流 references/ 目录)
988
+ const resourceFullPath = resolveLayer3ResourcePath(skillDir, layer3, resourcePath);
989
+ if (!resourceFullPath) {
990
+ getLogger().warn({ skill: skillName, resourcePath }, 'read_resource 路径穿越被阻止');
991
+ return null;
992
+ }
993
+ try {
994
+ return await readContentSafe(resourceFullPath);
995
+ }
996
+ catch (err) {
997
+ getLogger().warn({ skill: skillName, resourcePath, err }, 'read_resource 读取技能资源失败');
998
+ return null;
999
+ }
1000
+ }
1001
+ /** 列出技能 L3 资源清单,无资源返回空数组 */
1002
+ listSkillResources(skillName, packName) {
1003
+ const found = this.findSkillByName(skillName, packName);
1004
+ return found?.skill.layer3?.resources ?? [];
1005
+ }
1006
+ /** 获取技能 L3 脚本完整路径,不存在返回 null */
1007
+ getSkillScriptPath(skillName, scriptPath, packName) {
1008
+ // layer3 白名单前置检查:脚本必须已由 scanPackSkills 发现并登记(与 getSkillScriptInfo 同标准),
1009
+ // 防止未在清单内的路径被 resolveSafePath 误放行(对齐 readSkillResource 的双层防护)
1010
+ const found = this.findSkillByName(skillName, packName);
1011
+ const layer3 = found?.skill.layer3;
1012
+ if (!findLayer3Script(layer3, scriptPath))
1013
+ return null;
1014
+ const skillDir = this.locateSkillDir(skillName, packName);
1015
+ if (!skillDir)
1016
+ return null;
1017
+ // 路径穿越防护:双层——layer3 白名单 + resolveSafePath 边界前缀(收口于 skillLayer3,SSOT)
1018
+ const fullPath = resolveLayer3ScriptPath(skillDir, layer3, scriptPath);
1019
+ if (!fullPath)
1020
+ return null;
1021
+ if (existsSyncSafe(fullPath)) {
1022
+ return fullPath;
1023
+ }
1024
+ return null;
1025
+ }
1026
+ /**
1027
+ * 获取技能 L3 脚本元信息(runtime 来源:scanPackSkills 目录扫描按扩展名推断,SSOT 在
1028
+ * utils/scanner;layer3 未命中时回退扩展名推断兜底——防御性分支,正常路径不可达)
1029
+ */
1030
+ getSkillScriptInfo(skillName, scriptPath, packName) {
1031
+ // 优先使用已扫描的 L3 数据(scanPackSkills 已发现 layer3.scripts)
1032
+ const found = this.findSkillByName(skillName, packName);
1033
+ if (found?.skill.layer3?.scripts) {
1034
+ const scriptMeta = found.skill.layer3.scripts.find((s) => s.path === scriptPath);
1035
+ if (scriptMeta) {
1036
+ return { runtime: scriptMeta.runtime };
1037
+ }
1038
+ }
1039
+ // 回退:从扩展名推断 runtime(SSOT 共享出口;未知扩展名返回 undefined → null)
1040
+ const ext = scriptPath.slice(scriptPath.lastIndexOf('.')).toLowerCase();
1041
+ const runtime = inferRuntimeFromExt(ext);
1042
+ if (!runtime)
1043
+ return null;
1044
+ return { runtime };
1045
+ }
1046
+ /**
1047
+ * 加载用户目录的角色包(宿主调用,用于扩展内置角色包池)
1048
+ *
1049
+ * 与 load() 的区别:
1050
+ * - load() 从 configDir/role-packs/ 扫描内置角色包(有磁盘真理源)
1051
+ * - loadExtraDir() 从额外目录扫描用户角色包(运行时注入,reload 时保留)
1052
+ *
1053
+ * 设计哲学:内核保持单一真理源(configDir/role-packs/),宿主负责扩展用户目录。
1054
+ * 用户角色包通过 registerRuntimeItem 注入,reload() 时会保留(同名冲突以磁盘为准)。
1055
+ *
1056
+ * @param dir 用户角色包目录(每个子目录为一个角色包,须含 manifest.json)
1057
+ * @returns 加载的角色包数量
1058
+ */
1059
+ async loadExtraDir(dir) {
1060
+ let count = 0;
1061
+ try {
1062
+ // buildFromDir 接受任意目录路径,不依赖 configDir
1063
+ const packs = await this.buildFromDir(dir);
1064
+ for (const pack of packs) {
1065
+ // 跳过已存在的角色包(内置优先)
1066
+ if (this.items.some((p) => p.meta.name === pack.meta.name)) {
1067
+ logger.info({ name: pack.meta.name }, '用户角色包与内置重名,跳过');
1068
+ continue;
1069
+ }
1070
+ this.registerRuntimeItem(pack);
1071
+ count++;
1072
+ }
1073
+ }
1074
+ catch (err) {
1075
+ logger.warn({ dir, err }, '加载用户角色包失败');
1076
+ }
1077
+ logger.info({ count, dir }, '用户角色包加载完成');
1078
+ return count;
1079
+ }
1080
+ }
1081
+ // statSyncSafe / existsSyncSafe 已收口到 utils/fileSafe(SSOT,与全局技能共用同一实现)
1082
+ //# sourceMappingURL=rolePackManager.js.map