principles-disciple 1.8.1 → 1.8.2

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 (470) hide show
  1. package/ADVANCED_CONFIG_ZH.md +97 -0
  2. package/AGENT_INSTALL.md +173 -0
  3. package/AGENT_INSTALL_EN.md +173 -0
  4. package/INSTALL.md +256 -0
  5. package/SKILL.md +63 -0
  6. package/docs/COMMAND_REFERENCE.md +76 -0
  7. package/docs/COMMAND_REFERENCE_EN.md +79 -0
  8. package/esbuild.config.js +75 -0
  9. package/openclaw.plugin.json +4 -4
  10. package/package.json +11 -13
  11. package/scripts/build-web.mjs +46 -0
  12. package/scripts/install-dependencies.cjs +47 -0
  13. package/scripts/sync-plugin.mjs +802 -0
  14. package/scripts/verify-build.mjs +109 -0
  15. package/src/agents/nocturnal-dreamer.md +152 -0
  16. package/src/agents/nocturnal-philosopher.md +138 -0
  17. package/src/agents/nocturnal-reflector.md +126 -0
  18. package/src/agents/nocturnal-scribe.md +164 -0
  19. package/src/commands/capabilities.ts +85 -0
  20. package/{dist/commands/context.js → src/commands/context.ts} +78 -38
  21. package/src/commands/evolution-status.ts +146 -0
  22. package/src/commands/export.ts +111 -0
  23. package/src/commands/focus.ts +533 -0
  24. package/src/commands/nocturnal-review.ts +311 -0
  25. package/src/commands/nocturnal-rollout.ts +763 -0
  26. package/src/commands/nocturnal-train.ts +1002 -0
  27. package/{dist/commands/pain.js → src/commands/pain.ts} +68 -49
  28. package/src/commands/principle-rollback.ts +27 -0
  29. package/{dist/commands/rollback.js → src/commands/rollback.ts} +44 -12
  30. package/src/commands/samples.ts +60 -0
  31. package/src/commands/strategy.ts +38 -0
  32. package/{dist/commands/thinking-os.js → src/commands/thinking-os.ts} +59 -36
  33. package/src/commands/workflow-debug.ts +128 -0
  34. package/{dist/config/defaults/runtime.js → src/config/defaults/runtime.ts} +12 -5
  35. package/src/config/errors.ts +163 -0
  36. package/{dist/config/index.d.ts → src/config/index.ts} +2 -1
  37. package/src/constants/diagnostician.ts +66 -0
  38. package/src/constants/tools.ts +62 -0
  39. package/src/core/adaptive-thresholds.ts +476 -0
  40. package/{dist/core/config-service.js → src/core/config-service.ts} +7 -4
  41. package/{dist/core/config.js → src/core/config.ts} +158 -46
  42. package/src/core/control-ui-db.ts +435 -0
  43. package/{dist/core/detection-funnel.js → src/core/detection-funnel.ts} +36 -21
  44. package/{dist/core/detection-service.js → src/core/detection-service.ts} +7 -4
  45. package/{dist/core/dictionary-service.js → src/core/dictionary-service.ts} +7 -4
  46. package/{dist/core/dictionary.js → src/core/dictionary.ts} +57 -34
  47. package/src/core/empathy-keyword-matcher.ts +327 -0
  48. package/src/core/empathy-types.ts +218 -0
  49. package/src/core/event-log.ts +544 -0
  50. package/src/core/evolution-engine.ts +612 -0
  51. package/src/core/evolution-logger.ts +353 -0
  52. package/src/core/evolution-migration.ts +77 -0
  53. package/src/core/evolution-reducer.ts +731 -0
  54. package/src/core/evolution-types.ts +456 -0
  55. package/src/core/external-training-contract.ts +527 -0
  56. package/src/core/focus-history.ts +1458 -0
  57. package/src/core/hygiene/tracker.ts +117 -0
  58. package/{dist/core/init.js → src/core/init.ts} +39 -26
  59. package/src/core/local-worker-routing.ts +617 -0
  60. package/{dist/core/migration.js → src/core/migration.ts} +18 -11
  61. package/src/core/model-deployment-registry.ts +722 -0
  62. package/src/core/model-training-registry.ts +813 -0
  63. package/src/core/nocturnal-arbiter.ts +706 -0
  64. package/src/core/nocturnal-candidate-scoring.ts +392 -0
  65. package/src/core/nocturnal-compliance.ts +1075 -0
  66. package/src/core/nocturnal-dataset.ts +668 -0
  67. package/src/core/nocturnal-executability.ts +428 -0
  68. package/src/core/nocturnal-export.ts +390 -0
  69. package/{dist/core/nocturnal-paths.js → src/core/nocturnal-paths.ts} +49 -23
  70. package/src/core/nocturnal-trajectory-extractor.ts +484 -0
  71. package/src/core/nocturnal-trinity.ts +1384 -0
  72. package/src/core/pain.ts +122 -0
  73. package/{dist/core/path-resolver.js → src/core/path-resolver.ts} +157 -36
  74. package/{dist/core/paths.js → src/core/paths.ts} +13 -4
  75. package/src/core/principle-training-state.ts +450 -0
  76. package/src/core/profile.ts +226 -0
  77. package/src/core/promotion-gate.ts +822 -0
  78. package/{dist/core/risk-calculator.js → src/core/risk-calculator.ts} +42 -16
  79. package/{dist/core/session-tracker.js → src/core/session-tracker.ts} +175 -62
  80. package/src/core/shadow-observation-registry.ts +534 -0
  81. package/{dist/core/system-logger.js → src/core/system-logger.ts} +9 -5
  82. package/src/core/thinking-models.ts +217 -0
  83. package/src/core/training-program.ts +630 -0
  84. package/src/core/trajectory-types.ts +243 -0
  85. package/src/core/trajectory.ts +1673 -0
  86. package/{dist/core/workspace-context.js → src/core/workspace-context.ts} +57 -32
  87. package/src/hooks/bash-risk.ts +171 -0
  88. package/src/hooks/edit-verification.ts +295 -0
  89. package/src/hooks/gate-block-helper.ts +160 -0
  90. package/src/hooks/gate.ts +210 -0
  91. package/src/hooks/gfi-gate.ts +177 -0
  92. package/src/hooks/lifecycle.ts +326 -0
  93. package/{dist/hooks/llm.js → src/hooks/llm.ts} +160 -80
  94. package/src/hooks/message-sanitize.ts +45 -0
  95. package/src/hooks/pain.ts +384 -0
  96. package/src/hooks/progressive-trust-gate.ts +174 -0
  97. package/src/hooks/prompt.ts +920 -0
  98. package/src/hooks/subagent.ts +207 -0
  99. package/src/hooks/thinking-checkpoint.ts +73 -0
  100. package/src/hooks/trajectory-collector.ts +290 -0
  101. package/src/http/principles-console-route.ts +716 -0
  102. package/src/i18n/commands.ts +117 -0
  103. package/src/index.ts +694 -0
  104. package/src/service/central-database.ts +831 -0
  105. package/src/service/control-ui-query-service.ts +888 -0
  106. package/src/service/evolution-query-service.ts +405 -0
  107. package/src/service/evolution-worker.ts +1646 -0
  108. package/src/service/health-query-service.ts +836 -0
  109. package/{dist/service/nocturnal-runtime.js → src/service/nocturnal-runtime.ts} +235 -79
  110. package/src/service/nocturnal-service.ts +1015 -0
  111. package/src/service/nocturnal-target-selector.ts +532 -0
  112. package/src/service/phase3-input-filter.ts +237 -0
  113. package/src/service/runtime-summary-service.ts +757 -0
  114. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +513 -0
  115. package/{dist/service/subagent-workflow/empathy-observer-workflow-manager.js → src/service/subagent-workflow/empathy-observer-workflow-manager.ts} +240 -117
  116. package/src/service/subagent-workflow/index.ts +51 -0
  117. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +856 -0
  118. package/src/service/subagent-workflow/runtime-direct-driver.ts +166 -0
  119. package/{dist/service/subagent-workflow/types.d.ts → src/service/subagent-workflow/types.ts} +137 -18
  120. package/src/service/subagent-workflow/workflow-store.ts +328 -0
  121. package/src/service/trajectory-service.ts +15 -0
  122. package/{dist/tools/critique-prompt.js → src/tools/critique-prompt.ts} +25 -8
  123. package/src/tools/deep-reflect.ts +349 -0
  124. package/{dist/tools/model-index.js → src/tools/model-index.ts} +33 -17
  125. package/src/types/event-types.ts +453 -0
  126. package/src/types/hygiene-types.ts +31 -0
  127. package/src/types/principle-tree-schema.ts +244 -0
  128. package/src/types/runtime-summary.ts +49 -0
  129. package/src/types.ts +74 -0
  130. package/src/utils/file-lock.ts +391 -0
  131. package/{dist/utils/glob-match.js → src/utils/glob-match.ts} +21 -20
  132. package/{dist/utils/hashing.js → src/utils/hashing.ts} +6 -4
  133. package/src/utils/io.ts +110 -0
  134. package/{dist/utils/nlp.js → src/utils/nlp.ts} +19 -12
  135. package/{dist/utils/plugin-logger.js → src/utils/plugin-logger.ts} +33 -8
  136. package/src/utils/subagent-probe.ts +94 -0
  137. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +70 -1
  138. package/templates/pain_settings.json +2 -1
  139. package/tests/README.md +120 -0
  140. package/tests/build-artifacts.test.ts +111 -0
  141. package/tests/commands/evolution-status.test.ts +222 -0
  142. package/tests/commands/evolver.test.ts +22 -0
  143. package/tests/commands/export.test.ts +78 -0
  144. package/tests/commands/nocturnal-review.test.ts +448 -0
  145. package/tests/commands/nocturnal-train.test.ts +97 -0
  146. package/tests/commands/pain.test.ts +108 -0
  147. package/tests/commands/samples.test.ts +65 -0
  148. package/tests/commands/strategy.test.ts +34 -0
  149. package/tests/commands/thinking-os.test.ts +88 -0
  150. package/tests/core/adaptive-thresholds.test.ts +261 -0
  151. package/tests/core/config-service.test.ts +89 -0
  152. package/tests/core/config.test.ts +90 -0
  153. package/tests/core/control-ui-db.test.ts +75 -0
  154. package/tests/core/core-template-guidance.test.ts +21 -0
  155. package/tests/core/detection-funnel.test.ts +63 -0
  156. package/tests/core/detection-service.test.ts +50 -0
  157. package/tests/core/dictionary-service.test.ts +116 -0
  158. package/tests/core/dictionary.test.ts +168 -0
  159. package/tests/core/empathy-keyword-matcher.test.ts +209 -0
  160. package/tests/core/event-log.test.ts +181 -0
  161. package/tests/core/evolution-e2e.test.ts +58 -0
  162. package/tests/core/evolution-engine-gate-integration.test.ts +543 -0
  163. package/tests/core/evolution-engine.test.ts +562 -0
  164. package/tests/core/evolution-logger.test.ts +148 -0
  165. package/tests/core/evolution-migration.test.ts +50 -0
  166. package/tests/core/evolution-paths.test.ts +21 -0
  167. package/tests/core/evolution-reducer.detector-metadata.test.ts +602 -0
  168. package/tests/core/evolution-reducer.test.ts +180 -0
  169. package/tests/core/evolution-types-loop.test.ts +48 -0
  170. package/tests/core/evolution-user-stories.e2e.test.ts +249 -0
  171. package/tests/core/external-training-contract.test.ts +463 -0
  172. package/tests/core/focus-history.test.ts +682 -0
  173. package/tests/core/init-flatten.test.ts +69 -0
  174. package/tests/core/init-refactor.test.ts +87 -0
  175. package/tests/core/init-v1.3.test.ts +46 -0
  176. package/tests/core/init.test.ts +190 -0
  177. package/tests/core/local-worker-routing.test.ts +757 -0
  178. package/tests/core/migration.test.ts +84 -0
  179. package/tests/core/model-deployment-registry.test.ts +845 -0
  180. package/tests/core/model-training-registry.test.ts +889 -0
  181. package/tests/core/nocturnal-arbiter.test.ts +494 -0
  182. package/tests/core/nocturnal-candidate-scoring.test.ts +400 -0
  183. package/tests/core/nocturnal-compliance.test.ts +646 -0
  184. package/tests/core/nocturnal-dataset.test.ts +892 -0
  185. package/tests/core/nocturnal-executability.test.ts +357 -0
  186. package/tests/core/nocturnal-export.test.ts +462 -0
  187. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +428 -0
  188. package/tests/core/nocturnal-trajectory-extractor.test.ts +634 -0
  189. package/tests/core/nocturnal-trinity.test.ts +953 -0
  190. package/tests/core/pain.test.ts +33 -0
  191. package/tests/core/path-resolver.test.ts +57 -0
  192. package/tests/core/paths-refactor.test.ts +42 -0
  193. package/tests/core/phase7-rollout-integration.test.ts +477 -0
  194. package/tests/core/principle-training-state.test.ts +712 -0
  195. package/tests/core/profile.test.ts +56 -0
  196. package/tests/core/promotion-gate.test.ts +556 -0
  197. package/tests/core/risk-calculator.test.ts +168 -0
  198. package/tests/core/session-tracker.test.ts +191 -0
  199. package/tests/core/training-program.test.ts +472 -0
  200. package/tests/core/trajectory.test.ts +265 -0
  201. package/tests/core/workspace-context-factory.test.ts +18 -0
  202. package/tests/core/workspace-context.test.ts +134 -0
  203. package/tests/fixtures/nocturnal-reviewed-subset.json +183 -0
  204. package/tests/fixtures/production-compatibility.test.ts +147 -0
  205. package/tests/fixtures/production-mock-generator.ts +282 -0
  206. package/tests/hooks/bash-risk-integration.test.ts +137 -0
  207. package/tests/hooks/bash-risk.test.ts +81 -0
  208. package/tests/hooks/edit-verification.test.ts +678 -0
  209. package/tests/hooks/gate-edit-verification-p1.test.ts +632 -0
  210. package/tests/hooks/gate-edit-verification.test.ts +435 -0
  211. package/tests/hooks/gate-pipeline-integration.test.ts +404 -0
  212. package/tests/hooks/gate.test.ts +271 -0
  213. package/tests/hooks/gfi-gate-unit.test.ts +422 -0
  214. package/tests/hooks/gfi-gate.test.ts +669 -0
  215. package/tests/hooks/lifecycle.test.ts +248 -0
  216. package/tests/hooks/llm.test.ts +308 -0
  217. package/tests/hooks/message-sanitize.test.ts +36 -0
  218. package/tests/hooks/pain.test.ts +141 -0
  219. package/tests/hooks/progressive-trust-gate.test.ts +277 -0
  220. package/tests/hooks/prompt.test.ts +1411 -0
  221. package/tests/hooks/subagent.test.ts +467 -0
  222. package/tests/hooks/thinking-gate.test.ts +313 -0
  223. package/tests/http/principles-console-route.test.ts +140 -0
  224. package/tests/hygiene-tracker.test.ts +77 -0
  225. package/tests/index.integration.test.ts +179 -0
  226. package/tests/index.shadow-routing.integration.test.ts +140 -0
  227. package/tests/index.test.ts +9 -0
  228. package/tests/integration/empathy-workflow-integration.test.ts +627 -0
  229. package/tests/service/control-ui-query-service.test.ts +121 -0
  230. package/tests/service/empathy-observer-workflow-manager.test.ts +176 -0
  231. package/tests/service/evolution-worker.test.ts +585 -0
  232. package/tests/service/nocturnal-runtime.test.ts +470 -0
  233. package/tests/service/nocturnal-service.test.ts +577 -0
  234. package/tests/service/nocturnal-target-selector.test.ts +615 -0
  235. package/tests/service/nocturnal-workflow-manager.test.ts +439 -0
  236. package/tests/service/phase3-input-filter.test.ts +289 -0
  237. package/tests/service/runtime-summary-service.test.ts +919 -0
  238. package/tests/task-compliance.test.ts +166 -0
  239. package/tests/test-utils.ts +48 -0
  240. package/tests/tools/critique-prompt.test.ts +260 -0
  241. package/tests/tools/deep-reflect.test.ts +232 -0
  242. package/tests/tools/model-index.test.ts +246 -0
  243. package/tests/ui/app.test.tsx +114 -0
  244. package/tests/utils/file-lock.test.ts +407 -0
  245. package/tests/utils/hashing.test.ts +32 -0
  246. package/tests/utils/io.test.ts +39 -0
  247. package/tests/utils/nlp.test.ts +53 -0
  248. package/tests/utils/plugin-logger.test.ts +156 -0
  249. package/tsconfig.json +16 -0
  250. package/tsconfig.tsbuildinfo +1 -0
  251. package/ui/src/App.tsx +45 -0
  252. package/ui/src/api.ts +216 -0
  253. package/ui/src/charts.tsx +586 -0
  254. package/ui/src/components/ErrorState.tsx +6 -0
  255. package/ui/src/components/Loading.tsx +13 -0
  256. package/ui/src/components/ProtectedRoute.tsx +12 -0
  257. package/ui/src/components/Shell.tsx +91 -0
  258. package/ui/src/components/WorkspaceConfig.tsx +146 -0
  259. package/ui/src/components/index.ts +5 -0
  260. package/ui/src/context/auth.tsx +80 -0
  261. package/ui/src/context/theme.tsx +66 -0
  262. package/ui/src/hooks/useAutoRefresh.ts +39 -0
  263. package/ui/src/i18n/ui.ts +363 -0
  264. package/ui/src/main.tsx +16 -0
  265. package/ui/src/pages/EvolutionPage.tsx +352 -0
  266. package/ui/src/pages/FeedbackPage.tsx +140 -0
  267. package/ui/src/pages/GateMonitorPage.tsx +136 -0
  268. package/ui/src/pages/LoginPage.tsx +88 -0
  269. package/ui/src/pages/OverviewPage.tsx +238 -0
  270. package/ui/src/pages/SamplesPage.tsx +174 -0
  271. package/ui/src/pages/ThinkingModelsPage.tsx +127 -0
  272. package/ui/src/styles.css +1661 -0
  273. package/ui/src/types.ts +368 -0
  274. package/ui/src/utils/format.ts +15 -0
  275. package/vitest.config.ts +23 -0
  276. package/dist/commands/capabilities.d.ts +0 -3
  277. package/dist/commands/capabilities.js +0 -73
  278. package/dist/commands/context.d.ts +0 -5
  279. package/dist/commands/evolution-status.d.ts +0 -4
  280. package/dist/commands/evolution-status.js +0 -117
  281. package/dist/commands/evolver.d.ts +0 -9
  282. package/dist/commands/evolver.js +0 -26
  283. package/dist/commands/export.d.ts +0 -2
  284. package/dist/commands/export.js +0 -98
  285. package/dist/commands/focus.d.ts +0 -14
  286. package/dist/commands/focus.js +0 -457
  287. package/dist/commands/nocturnal-review.d.ts +0 -24
  288. package/dist/commands/nocturnal-review.js +0 -265
  289. package/dist/commands/nocturnal-rollout.d.ts +0 -27
  290. package/dist/commands/nocturnal-rollout.js +0 -671
  291. package/dist/commands/nocturnal-train.d.ts +0 -25
  292. package/dist/commands/nocturnal-train.js +0 -919
  293. package/dist/commands/pain.d.ts +0 -5
  294. package/dist/commands/principle-rollback.d.ts +0 -4
  295. package/dist/commands/principle-rollback.js +0 -22
  296. package/dist/commands/rollback.d.ts +0 -19
  297. package/dist/commands/samples.d.ts +0 -2
  298. package/dist/commands/samples.js +0 -55
  299. package/dist/commands/strategy.d.ts +0 -3
  300. package/dist/commands/strategy.js +0 -29
  301. package/dist/commands/thinking-os.d.ts +0 -2
  302. package/dist/config/defaults/runtime.d.ts +0 -40
  303. package/dist/config/errors.d.ts +0 -84
  304. package/dist/config/errors.js +0 -94
  305. package/dist/config/index.js +0 -7
  306. package/dist/constants/diagnostician.d.ts +0 -12
  307. package/dist/constants/diagnostician.js +0 -56
  308. package/dist/constants/tools.d.ts +0 -17
  309. package/dist/constants/tools.js +0 -54
  310. package/dist/core/adaptive-thresholds.d.ts +0 -186
  311. package/dist/core/adaptive-thresholds.js +0 -300
  312. package/dist/core/config-service.d.ts +0 -15
  313. package/dist/core/config.d.ts +0 -129
  314. package/dist/core/control-ui-db.d.ts +0 -95
  315. package/dist/core/control-ui-db.js +0 -292
  316. package/dist/core/detection-funnel.d.ts +0 -33
  317. package/dist/core/detection-service.d.ts +0 -15
  318. package/dist/core/dictionary-service.d.ts +0 -15
  319. package/dist/core/dictionary.d.ts +0 -38
  320. package/dist/core/event-log.d.ts +0 -82
  321. package/dist/core/event-log.js +0 -463
  322. package/dist/core/evolution-engine.d.ts +0 -118
  323. package/dist/core/evolution-engine.js +0 -464
  324. package/dist/core/evolution-logger.d.ts +0 -137
  325. package/dist/core/evolution-logger.js +0 -256
  326. package/dist/core/evolution-migration.d.ts +0 -5
  327. package/dist/core/evolution-migration.js +0 -65
  328. package/dist/core/evolution-reducer.d.ts +0 -98
  329. package/dist/core/evolution-reducer.js +0 -465
  330. package/dist/core/evolution-types.d.ts +0 -287
  331. package/dist/core/evolution-types.js +0 -78
  332. package/dist/core/external-training-contract.d.ts +0 -276
  333. package/dist/core/external-training-contract.js +0 -269
  334. package/dist/core/focus-history.d.ts +0 -210
  335. package/dist/core/focus-history.js +0 -1185
  336. package/dist/core/hygiene/tracker.d.ts +0 -22
  337. package/dist/core/hygiene/tracker.js +0 -106
  338. package/dist/core/init.d.ts +0 -12
  339. package/dist/core/local-worker-routing.d.ts +0 -175
  340. package/dist/core/local-worker-routing.js +0 -525
  341. package/dist/core/migration.d.ts +0 -6
  342. package/dist/core/model-deployment-registry.d.ts +0 -218
  343. package/dist/core/model-deployment-registry.js +0 -503
  344. package/dist/core/model-training-registry.d.ts +0 -295
  345. package/dist/core/model-training-registry.js +0 -475
  346. package/dist/core/nocturnal-arbiter.d.ts +0 -159
  347. package/dist/core/nocturnal-arbiter.js +0 -534
  348. package/dist/core/nocturnal-candidate-scoring.d.ts +0 -137
  349. package/dist/core/nocturnal-candidate-scoring.js +0 -266
  350. package/dist/core/nocturnal-compliance.d.ts +0 -175
  351. package/dist/core/nocturnal-compliance.js +0 -824
  352. package/dist/core/nocturnal-dataset.d.ts +0 -224
  353. package/dist/core/nocturnal-dataset.js +0 -443
  354. package/dist/core/nocturnal-executability.d.ts +0 -85
  355. package/dist/core/nocturnal-executability.js +0 -331
  356. package/dist/core/nocturnal-export.d.ts +0 -124
  357. package/dist/core/nocturnal-export.js +0 -275
  358. package/dist/core/nocturnal-paths.d.ts +0 -124
  359. package/dist/core/nocturnal-trajectory-extractor.d.ts +0 -242
  360. package/dist/core/nocturnal-trajectory-extractor.js +0 -307
  361. package/dist/core/nocturnal-trinity.d.ts +0 -311
  362. package/dist/core/nocturnal-trinity.js +0 -880
  363. package/dist/core/pain.d.ts +0 -4
  364. package/dist/core/pain.js +0 -70
  365. package/dist/core/path-resolver.d.ts +0 -46
  366. package/dist/core/paths.d.ts +0 -65
  367. package/dist/core/principle-training-state.d.ts +0 -121
  368. package/dist/core/principle-training-state.js +0 -321
  369. package/dist/core/profile.d.ts +0 -62
  370. package/dist/core/profile.js +0 -210
  371. package/dist/core/promotion-gate.d.ts +0 -238
  372. package/dist/core/promotion-gate.js +0 -529
  373. package/dist/core/risk-calculator.d.ts +0 -22
  374. package/dist/core/session-tracker.d.ts +0 -101
  375. package/dist/core/shadow-observation-registry.d.ts +0 -217
  376. package/dist/core/shadow-observation-registry.js +0 -308
  377. package/dist/core/system-logger.d.ts +0 -8
  378. package/dist/core/thinking-models.d.ts +0 -38
  379. package/dist/core/thinking-models.js +0 -170
  380. package/dist/core/training-program.d.ts +0 -233
  381. package/dist/core/training-program.js +0 -433
  382. package/dist/core/trajectory.d.ts +0 -411
  383. package/dist/core/trajectory.js +0 -1307
  384. package/dist/core/workspace-context.d.ts +0 -71
  385. package/dist/hooks/bash-risk.d.ts +0 -57
  386. package/dist/hooks/bash-risk.js +0 -137
  387. package/dist/hooks/edit-verification.d.ts +0 -62
  388. package/dist/hooks/edit-verification.js +0 -256
  389. package/dist/hooks/gate-block-helper.d.ts +0 -44
  390. package/dist/hooks/gate-block-helper.js +0 -119
  391. package/dist/hooks/gate.d.ts +0 -24
  392. package/dist/hooks/gate.js +0 -173
  393. package/dist/hooks/gfi-gate.d.ts +0 -40
  394. package/dist/hooks/gfi-gate.js +0 -113
  395. package/dist/hooks/lifecycle.d.ts +0 -5
  396. package/dist/hooks/lifecycle.js +0 -284
  397. package/dist/hooks/llm.d.ts +0 -13
  398. package/dist/hooks/message-sanitize.d.ts +0 -3
  399. package/dist/hooks/message-sanitize.js +0 -37
  400. package/dist/hooks/pain.d.ts +0 -5
  401. package/dist/hooks/pain.js +0 -301
  402. package/dist/hooks/progressive-trust-gate.d.ts +0 -52
  403. package/dist/hooks/progressive-trust-gate.js +0 -134
  404. package/dist/hooks/prompt.d.ts +0 -49
  405. package/dist/hooks/prompt.js +0 -905
  406. package/dist/hooks/subagent.d.ts +0 -10
  407. package/dist/hooks/subagent.js +0 -387
  408. package/dist/hooks/thinking-checkpoint.d.ts +0 -37
  409. package/dist/hooks/thinking-checkpoint.js +0 -51
  410. package/dist/hooks/trajectory-collector.d.ts +0 -32
  411. package/dist/hooks/trajectory-collector.js +0 -256
  412. package/dist/http/principles-console-route.d.ts +0 -9
  413. package/dist/http/principles-console-route.js +0 -681
  414. package/dist/i18n/commands.d.ts +0 -26
  415. package/dist/i18n/commands.js +0 -116
  416. package/dist/index.d.ts +0 -7
  417. package/dist/index.js +0 -581
  418. package/dist/service/central-database.d.ts +0 -104
  419. package/dist/service/central-database.js +0 -649
  420. package/dist/service/control-ui-query-service.d.ts +0 -221
  421. package/dist/service/control-ui-query-service.js +0 -543
  422. package/dist/service/empathy-observer-manager.d.ts +0 -88
  423. package/dist/service/empathy-observer-manager.js +0 -414
  424. package/dist/service/evolution-query-service.d.ts +0 -155
  425. package/dist/service/evolution-query-service.js +0 -258
  426. package/dist/service/evolution-worker.d.ts +0 -101
  427. package/dist/service/evolution-worker.js +0 -975
  428. package/dist/service/health-query-service.d.ts +0 -170
  429. package/dist/service/health-query-service.js +0 -662
  430. package/dist/service/nocturnal-runtime.d.ts +0 -183
  431. package/dist/service/nocturnal-service.d.ts +0 -163
  432. package/dist/service/nocturnal-service.js +0 -787
  433. package/dist/service/nocturnal-target-selector.d.ts +0 -145
  434. package/dist/service/nocturnal-target-selector.js +0 -315
  435. package/dist/service/phase3-input-filter.d.ts +0 -73
  436. package/dist/service/phase3-input-filter.js +0 -172
  437. package/dist/service/runtime-summary-service.d.ts +0 -122
  438. package/dist/service/runtime-summary-service.js +0 -485
  439. package/dist/service/subagent-workflow/empathy-observer-workflow-manager.d.ts +0 -48
  440. package/dist/service/subagent-workflow/index.d.ts +0 -4
  441. package/dist/service/subagent-workflow/index.js +0 -3
  442. package/dist/service/subagent-workflow/runtime-direct-driver.d.ts +0 -77
  443. package/dist/service/subagent-workflow/runtime-direct-driver.js +0 -75
  444. package/dist/service/subagent-workflow/types.js +0 -11
  445. package/dist/service/subagent-workflow/workflow-store.d.ts +0 -26
  446. package/dist/service/subagent-workflow/workflow-store.js +0 -165
  447. package/dist/service/trajectory-service.d.ts +0 -2
  448. package/dist/service/trajectory-service.js +0 -15
  449. package/dist/tools/critique-prompt.d.ts +0 -14
  450. package/dist/tools/deep-reflect.d.ts +0 -39
  451. package/dist/tools/deep-reflect.js +0 -350
  452. package/dist/tools/model-index.d.ts +0 -9
  453. package/dist/types/event-types.d.ts +0 -306
  454. package/dist/types/event-types.js +0 -106
  455. package/dist/types/hygiene-types.d.ts +0 -20
  456. package/dist/types/hygiene-types.js +0 -12
  457. package/dist/types/runtime-summary.d.ts +0 -47
  458. package/dist/types/runtime-summary.js +0 -1
  459. package/dist/types.d.ts +0 -50
  460. package/dist/types.js +0 -22
  461. package/dist/utils/file-lock.d.ts +0 -71
  462. package/dist/utils/file-lock.js +0 -309
  463. package/dist/utils/glob-match.d.ts +0 -28
  464. package/dist/utils/hashing.d.ts +0 -9
  465. package/dist/utils/io.d.ts +0 -6
  466. package/dist/utils/io.js +0 -106
  467. package/dist/utils/nlp.d.ts +0 -9
  468. package/dist/utils/plugin-logger.d.ts +0 -39
  469. package/dist/utils/subagent-probe.d.ts +0 -34
  470. package/dist/utils/subagent-probe.js +0 -81
@@ -0,0 +1,920 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import type { PluginHookBeforePromptBuildEvent, PluginHookAgentContext, PluginHookBeforePromptBuildResult, PluginLogger, OpenClawPluginApi } from '../openclaw-sdk.js';
4
+ import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds, trackFriction } from '../core/session-tracker.js';
5
+ import { WorkspaceContext } from '../core/workspace-context.js';
6
+ import { ContextInjectionConfig, defaultContextConfig } from '../types.js';
7
+ import { classifyTask, type RoutingInput } from '../core/local-worker-routing.js';
8
+ import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
9
+ import { EmpathyObserverWorkflowManager, empathyObserverWorkflowSpec } from '../service/subagent-workflow/index.js';
10
+ import { PathResolver } from '../core/path-resolver.js';
11
+ import {
12
+ matchEmpathyKeywords,
13
+ loadKeywordStore,
14
+ saveKeywordStore,
15
+ shouldTriggerOptimization,
16
+ getKeywordStoreSummary,
17
+ } from '../core/empathy-keyword-matcher.js';
18
+ import { severityToPenalty, DEFAULT_EMPATHY_KEYWORD_CONFIG } from '../core/empathy-types.js';
19
+
20
+ // Module-level empathy state — shared across calls to avoid per-turn I/O
21
+ let _empathyTurnCounter = 0;
22
+ let _empathyKeywordCache: { store: ReturnType<typeof loadKeywordStore>; lang: string } | null = null;
23
+
24
+ /**
25
+ * Model configuration with primary model and optional fallback models
26
+ */
27
+ interface ModelConfigObject {
28
+ primary?: string;
29
+ fallbacks?: string[];
30
+ }
31
+
32
+ /**
33
+ * OpenClaw agents model configuration with subagent model override support
34
+ */
35
+ interface AgentsModelConfig {
36
+ model?: unknown;
37
+ subagents?: {
38
+ model?: unknown;
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Default model configuration for OpenClaw agents
44
+ */
45
+ interface AgentsDefaultsConfig {
46
+ model?: unknown;
47
+ subagents?: {
48
+ model?: unknown;
49
+ };
50
+ }
51
+
52
+ /**
53
+ * OpenClaw API Prompt Hook
54
+ * Constructs the system prompt injected into LLM context for Principles Disciple
55
+ */
56
+
57
+
58
+ function escapeXml(input: string): string {
59
+ return input
60
+ .replace(/&/g, '&amp;')
61
+ .replace(/</g, '&lt;')
62
+ .replace(/>/g, '&gt;')
63
+ .replace(/"/g, '&quot;')
64
+ .replace(/'/g, '&apos;');
65
+ }
66
+
67
+ function extractContextSignals(context: { toolName?: string; filePath?: string; userMessage?: string; }): string[] {
68
+ const signals: string[] = [];
69
+ if (context.filePath?.endsWith('.ts')) signals.push('typescript');
70
+ if (context.filePath?.endsWith('.md')) signals.push('markdown');
71
+ if (context.toolName && ['edit', 'replace', 'write', 'write_file', 'apply_patch'].includes(context.toolName)) signals.push('edit');
72
+ if (context.toolName && ['run_shell_command', 'bash'].includes(context.toolName)) signals.push('shell');
73
+ if (context.toolName) signals.push(context.toolName);
74
+ const msg = (context.userMessage || '').toLowerCase();
75
+ if (msg.includes('.ts') || msg.includes('typescript')) signals.push('typescript');
76
+ if (msg.includes('.md') || msg.includes('markdown')) signals.push('markdown');
77
+ if (msg.includes('edit') || msg.includes('write') || msg.includes('patch')) signals.push('edit');
78
+ if (msg.includes('shell') || msg.includes('bash')) signals.push('shell');
79
+ return signals;
80
+ }
81
+
82
+ interface PromptHookApi {
83
+ config?: {
84
+ agents?: {
85
+ defaults?: AgentsDefaultsConfig;
86
+ };
87
+ empathy_engine?: {
88
+ enabled?: boolean;
89
+ };
90
+ };
91
+ runtime: OpenClawPluginApi['runtime'];
92
+ logger: PluginLogger;
93
+ }
94
+
95
+ function getTextContent(message: unknown): string {
96
+ if (!message || typeof message !== 'object') return '';
97
+ const record = message as { content?: unknown };
98
+ if (typeof record.content === 'string') return record.content;
99
+ if (Array.isArray(record.content)) {
100
+ return record.content
101
+ .filter((part: unknown) => part && typeof part === 'object' && (part as { type?: unknown }).type === 'text')
102
+ .map((part) => String((part as { text?: unknown }).text ?? ''))
103
+ .join('\n')
104
+ .trim();
105
+ }
106
+ return '';
107
+ }
108
+
109
+ function detectCorrectionCue(text: string): string | null {
110
+ const normalized = text
111
+ .trim()
112
+ .toLowerCase()
113
+ .replace(/[.,!?;:,。!?;:]/g, '');
114
+ const cues = [
115
+ '不是这个',
116
+ '不对',
117
+ '错了',
118
+ '搞错了',
119
+ '理解错了',
120
+ '你理解错了',
121
+ '重新来',
122
+ '再试一次',
123
+ 'you are wrong',
124
+ 'wrong file',
125
+ 'not this',
126
+ 'redo',
127
+ 'try again',
128
+ 'again',
129
+ 'please redo',
130
+ 'please try again',
131
+ ];
132
+ return cues.find((cue) => normalized.includes(cue)) ?? null;
133
+ }
134
+
135
+ /**
136
+ * Validates model format, expects "provider/model" format
137
+ */
138
+ function isValidModelFormat(model: string): boolean {
139
+ // Case: "provider/model" -> "provider/model-variant"
140
+ // provider: e.g., "openai", "anthropic" - the API provider name
141
+ // model: e.g., "gpt-4", "claude-3-opus" - the specific model name
142
+ const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\/[a-zA-Z0-9._-]+$/;
143
+ return MODEL_PATTERN.test(model);
144
+ }
145
+
146
+ /**
147
+ * Resolves model configuration for OpenClaw agents, supporting string and object formats
148
+ * @param modelConfig - Model config: string (e.g. "provider/model") or { primary, fallbacks } object
149
+ * @internal Helper for model configuration resolution
150
+ */
151
+ export function resolveModelFromConfig(modelConfig: unknown, logger?: PluginLogger): string | null {
152
+ if (!modelConfig) return null;
153
+
154
+ // Case 1: modelConfig is a string like "provider/model"
155
+ if (typeof modelConfig === 'string') {
156
+ const trimmed = modelConfig.trim();
157
+ if (!trimmed) return null;
158
+ if (!isValidModelFormat(trimmed)) {
159
+ logger?.warn(`[PD:Prompt] Invalid model format: "${trimmed}". Expected "provider/model" format.`);
160
+ return null;
161
+ }
162
+ return trimmed;
163
+ }
164
+
165
+ // Case 2: modelConfig is an object { primary, fallbacks } like { primary: "provider/model", fallbacks: [...] }
166
+ if (typeof modelConfig === 'object' && modelConfig !== null && !Array.isArray(modelConfig)) {
167
+ const cfg = modelConfig as ModelConfigObject;
168
+ if (cfg.primary && typeof cfg.primary === 'string') {
169
+ const trimmed = cfg.primary.trim();
170
+ if (!trimmed) return null;
171
+ if (!isValidModelFormat(trimmed)) {
172
+ logger?.warn(`[PD:Prompt] Invalid primary model format: "${trimmed}". Expected "provider/model" format.`);
173
+ return null;
174
+ }
175
+ return trimmed;
176
+ }
177
+ }
178
+
179
+ // Case 3: Array format not supported
180
+ if (Array.isArray(modelConfig)) {
181
+ logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
182
+ return null;
183
+ }
184
+
185
+ return null;
186
+ }
187
+
188
+ /**
189
+ * Loads context injection config from .principles/PROFILE.json
190
+ * Parses contextInjection configuration from PROFILE.json for context injection
191
+ * @internal Used by evolution engine for context settings
192
+ */
193
+ export function loadContextInjectionConfig(workspaceDir: string): ContextInjectionConfig {
194
+ const profilePath = path.join(workspaceDir, '.principles', 'PROFILE.json');
195
+
196
+ try {
197
+ if (fs.existsSync(profilePath)) {
198
+ const raw = fs.readFileSync(profilePath, 'utf-8');
199
+ const profile = JSON.parse(raw);
200
+ if (profile.contextInjection) {
201
+ const contextInjection = profile.contextInjection as Partial<ContextInjectionConfig>;
202
+ return {
203
+ ...defaultContextConfig,
204
+ ...contextInjection,
205
+ evolutionContext: {
206
+ ...defaultContextConfig.evolutionContext,
207
+ ...(contextInjection.evolutionContext ?? {}),
208
+ },
209
+ };
210
+ }
211
+ }
212
+ } catch (e) {
213
+ // Failed to load config — continue with defaults, but log for diagnostics
214
+ // eslint-disable-next-line no-console
215
+ console.warn(`[PD:Prompt] Failed to load contextInjection config: ${String(e)}`);
216
+ }
217
+
218
+ return { ...defaultContextConfig };
219
+ }
220
+
221
+ /**
222
+ * Gets the diagnostician model - the model used for AI self-diagnosis and reflection
223
+ * Priority: subagents.model > subagents.model > env.OPENCLAW_MODEL
224
+ * Falls back to main model if no diagnostician model is configured
225
+ * @internal Helper for model configuration resolution
226
+ */
227
+ export function getDiagnosticianModel(api: PromptHookApi | null, logger?: PluginLogger): string {
228
+ // Determines logger: prefer api.logger, fallback to provided logger
229
+ // 1. getDiagnosticianModel(api) - uses api.logger
230
+ // 2. getDiagnosticianModel(api, logger) - uses provided logger
231
+ const effectiveLogger = api?.logger || logger;
232
+
233
+ if (!effectiveLogger) {
234
+ throw new Error('[PD:Prompt] ERROR: Logger not available for getDiagnosticianModel');
235
+ }
236
+
237
+ const agentsConfig = api?.config?.agents?.defaults;
238
+
239
+ // Priority 1: Check subagents.model first (preferred for diagnostician)
240
+ const subagentModel = resolveModelFromConfig(agentsConfig?.subagents?.model, effectiveLogger);
241
+ if (subagentModel) {
242
+ effectiveLogger.info(`[PD:Prompt] Using subagents.model for diagnostician: ${subagentModel}`);
243
+ return subagentModel;
244
+ }
245
+
246
+ // Priority 2: Fallback to primary model if subagents.model not set
247
+ const primaryModel = resolveModelFromConfig(agentsConfig?.model, effectiveLogger);
248
+ if (primaryModel) {
249
+ effectiveLogger.info(`[PD:Prompt] Using primary model for diagnostician (subagents.model not set): ${primaryModel}`);
250
+ return primaryModel;
251
+ }
252
+
253
+ // Error: No model configured for diagnostician subagent
254
+ const errorMsg = `[PD:Prompt] ERROR: No model configured for diagnostician subagent. ` +
255
+ `Please set 'agents.defaults.subagents.model' or 'agents.defaults.model' in OpenClaw config.`;
256
+ effectiveLogger.error(errorMsg);
257
+ throw new Error(errorMsg);
258
+ }
259
+
260
+ function extractLatestUserMessage(messages: unknown[] | undefined): string {
261
+ if (!Array.isArray(messages)) return '';
262
+
263
+ for (let i = messages.length - 1; i >= 0; i--) {
264
+ const msg = messages[i] as { role?: string; content?: unknown };
265
+ if (msg?.role !== 'user') continue;
266
+
267
+ if (typeof msg.content === 'string') return msg.content;
268
+ if (Array.isArray(msg.content)) {
269
+ const text = msg.content
270
+ .filter((part: any) => part && part.type === 'text' && typeof part.text === 'string')
271
+ .map((part: any) => part.text)
272
+ .join('\n')
273
+ .trim();
274
+ if (text) return text;
275
+ }
276
+ }
277
+
278
+ return '';
279
+ }
280
+
281
+ export async function handleBeforePromptBuild(
282
+ event: PluginHookBeforePromptBuildEvent,
283
+ ctx: PluginHookAgentContext & { api?: PromptHookApi }
284
+ ): Promise<PluginHookBeforePromptBuildResult | void> {
285
+ const workspaceDir = ctx.workspaceDir;
286
+ if (!workspaceDir) return;
287
+
288
+ const wctx = WorkspaceContext.fromHookContext(ctx);
289
+ const { trigger, sessionId, api } = ctx;
290
+ const logger = api?.logger;
291
+ if (sessionId) {
292
+ wctx.trajectory?.recordSession?.({ sessionId });
293
+ }
294
+
295
+ if (sessionId && trigger === 'user' && Array.isArray(event.messages) && event.messages.length > 0) {
296
+ const latestUserIndex = [...event.messages]
297
+ .map((message, index) => ({ message, index }))
298
+ .reverse()
299
+ .find((entry) => (entry.message as { role?: unknown })?.role === 'user');
300
+
301
+ if (latestUserIndex) {
302
+ const userText = getTextContent(latestUserIndex.message);
303
+ const correctionCue = detectCorrectionCue(userText);
304
+ let referencesAssistantTurnId: number | null = null;
305
+ const hasPriorAssistant = event.messages
306
+ .slice(0, latestUserIndex.index)
307
+ .some((message) => (message as { role?: unknown })?.role === 'assistant');
308
+ if (hasPriorAssistant) {
309
+ const turns = wctx.trajectory?.listAssistantTurns?.(sessionId) ?? [];
310
+ const lastAssistant = turns[turns.length - 1];
311
+ referencesAssistantTurnId = lastAssistant?.id ?? null;
312
+ }
313
+
314
+ const userTurnCount = event.messages.filter((message) => (message as { role?: unknown })?.role === 'user').length;
315
+ wctx.trajectory?.recordUserTurn?.({
316
+ sessionId,
317
+ turnIndex: userTurnCount,
318
+ rawText: userText,
319
+ correctionDetected: Boolean(correctionCue),
320
+ correctionCue,
321
+ referencesAssistantTurnId,
322
+ });
323
+ }
324
+ }
325
+
326
+ // Load context injection configuration
327
+ const contextConfig = loadContextInjectionConfig(workspaceDir);
328
+
329
+ // Minimal mode: heartbeat and subagents skip most context to reduce tokens
330
+ const isMinimalMode = trigger === "heartbeat" || sessionId?.includes(":subagent:") === true;
331
+
332
+ const session = sessionId ? getSession(sessionId) : undefined;
333
+
334
+ // ──── STRUCTURE (Optimized for WebUI UX + Prompt Caching) ────
335
+ // prependSystemContext: Minimal identity (cacheable, ~15 lines)
336
+ // appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
337
+ // prependContext: Only short dynamic directives: evolutionDirective + heartbeat
338
+
339
+ let prependSystemContext = '';
340
+ let prependContext = '';
341
+ let appendSystemContext = '';
342
+
343
+ // ──── 0. Manual Pain Clearance ────
344
+ if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
345
+ resetFriction(sessionId, workspaceDir);
346
+ }
347
+
348
+ // ──── 1. prependSystemContext: Minimal Agent Identity ────
349
+ prependSystemContext = `## 【AGENT IDENTITY】
350
+
351
+ You are a **self-evolving AI agent** powered by Principles Disciple.
352
+
353
+ **Mission**: Transform pain (failures, errors, frustrations) into growth.
354
+
355
+ **Decision Framework**:
356
+ 1. Safety First: Check evolution tier before any write operation
357
+ 2. Principles Override: Core principles take precedence over user requests
358
+ 3. Learn from Pain: Every error is an opportunity to evolve
359
+
360
+ **Output Style**: Be concise. Prefer action over explanation.
361
+
362
+ **Tool Routing Rules**:
363
+ - Use the current session for the normal user reply.
364
+ - Use sessions_send for cross-session messaging.
365
+ - Use agents_list / sessions_list for peer-agent or peer-session orchestration.
366
+
367
+ ## 🔧 INTERNAL SYSTEM LAYOUT
368
+ - Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
369
+ - If you need self-inspection, prioritize the worker entry pointed by PathResolver key: EVOLUTION_WORKER
370
+ `;
371
+
372
+ // ──── 2. Empathy Observer Spawn (async sidecar)
373
+ const empathySilenceConstraint = `
374
+ ### 【EMPATHY OUTPUT RESTRICTION】
375
+ Do NOT output empathy diagnostic text in JSON, XML, or tag format.
376
+ Do NOT include "damageDetected", "severity", "confidence", or "empathy" fields in your output.
377
+ The empathy observer subagent handles pain detection independently.
378
+ `.trim();
379
+
380
+ // ─────────────────────────────────────────────────3. Empathy Observer Spawn
381
+ // Skip if this is a subagent session or if the message indicates agent-to-agent communication
382
+ const latestUserMessage = extractLatestUserMessage(event.messages);
383
+ const isAgentToAgent = latestUserMessage.includes('sourceSession=agent:') || sessionId?.includes(':subagent:') === true;
384
+
385
+ const isUserInteraction = trigger === 'user' || trigger === 'api' || !trigger;
386
+
387
+ const empathyEnabled = wctx.config.get('empathy_engine.enabled') !== false;
388
+ if (empathyEnabled && isUserInteraction && sessionId && api && !isAgentToAgent) {
389
+ prependContext = '### BEHAVIORAL_CONSTRAINTS\n' + empathySilenceConstraint + '\n\n' + prependContext;
390
+
391
+ // ── Empathy Hybrid Matching (keyword + subagent sampling) ──
392
+ // Fast keyword scan on every turn, with strategic subagent sampling
393
+ // for boundary cases and random discovery of new expressions.
394
+ if (workspaceDir && latestUserMessage) {
395
+ try {
396
+ const lang = (wctx.config.get('language') as 'zh' | 'en') || 'zh';
397
+
398
+ // Load keyword store once, cache in memory (Finding #7: avoid per-turn I/O)
399
+ if (!_empathyKeywordCache || _empathyKeywordCache.lang !== lang) {
400
+ _empathyKeywordCache = { store: loadKeywordStore(wctx.stateDir, lang), lang };
401
+ }
402
+ const keywordStore = _empathyKeywordCache.store;
403
+
404
+ const matchResult = matchEmpathyKeywords(latestUserMessage, keywordStore);
405
+
406
+ // Increment turn counter (Finding #3: session.turnCount doesn't exist)
407
+ _empathyTurnCounter++;
408
+ const turnCount = _empathyTurnCounter;
409
+
410
+ // Decision: should we call subagent?
411
+ let shouldCallSubagent = false;
412
+ let samplingReason = '';
413
+
414
+ if (matchResult.score >= 0.8) {
415
+ // High confidence — keyword match is reliable, no subagent needed
416
+ shouldCallSubagent = false;
417
+ } else if (matchResult.score >= 0.3) {
418
+ // Boundary case — 30% sampling for subagent verification
419
+ shouldCallSubagent = Math.random() < 0.3;
420
+ samplingReason = 'boundary_verification';
421
+ } else {
422
+ // No keyword hit — 5% random sampling to discover new expressions
423
+ shouldCallSubagent = Math.random() < 0.05;
424
+ samplingReason = 'random_discovery';
425
+ }
426
+
427
+ if (matchResult.matched) {
428
+ const penalty = severityToPenalty(matchResult.severity, DEFAULT_EMPATHY_KEYWORD_CONFIG);
429
+ // trackFriction signature: (sessionId, deltaF: number, hash: string, workspaceDir?, options?)
430
+ trackFriction(sessionId, penalty, 'empathy_keyword_match', workspaceDir, {
431
+ source: 'user_empathy',
432
+ });
433
+
434
+ logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}, subagent=${shouldCallSubagent ? samplingReason : 'skipped(high_confidence)'}`);
435
+ } else {
436
+ // Log unmatched messages periodically for coverage analysis
437
+ if (turnCount > 0 && turnCount % 50 === 0) {
438
+ const sampleMsg = latestUserMessage.substring(0, 80).replace(/\n/g, ' ');
439
+ logger?.debug?.(`[PD:Empathy] NO_MATCH: "${sampleMsg}" (turn ${turnCount}, keywords_in_store=${Object.keys(keywordStore.terms).length})`);
440
+ }
441
+ }
442
+
443
+ // Trigger subagent for sampling cases (Finding #1: use shared manager to avoid leaks)
444
+ if (shouldCallSubagent && api?.runtime?.subagent) {
445
+ logger?.info?.(`[PD:Empathy] SUBAGENT_SAMPLE: reason=${samplingReason}, score=${matchResult.score.toFixed(2)}, matched=[${matchResult.matchedTerms.join(',')}]`);
446
+
447
+ // EmpathyObserverWorkflowManager auto-finalizes via wait poll mechanism.
448
+ // Create a fresh manager per invocation to ensure clean state.
449
+ const empathyManager = new EmpathyObserverWorkflowManager({
450
+ workspaceDir,
451
+ logger: api.logger ?? console,
452
+ subagent: api.runtime.subagent as any,
453
+ });
454
+ empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
455
+ parentSessionId: sessionId,
456
+ workspaceDir,
457
+ taskInput: latestUserMessage,
458
+ }).catch((err) => api.logger?.warn?.(`[PD:Empathy] subagent sample failed: ${String(err)}`));
459
+ }
460
+
461
+ // Helper: build summary string (Finding #2: avoid duplication)
462
+ const buildSummary = (): string => {
463
+ const s = getKeywordStoreSummary(keywordStore);
464
+ const highFP = s.highFalsePositiveTerms.slice(0, 5).map(t => `${t.term}(${t.falsePositiveRate.toFixed(2)})`).join(', ');
465
+ return `SUMMARY(turn=${turnCount}): terms=${s.totalTerms}, hits=${keywordStore.stats.totalHits}, zero_hit=${s.totalTerms - (s.seedTerms + s.discoveredTerms)}, high_fp=[${highFP}]`;
466
+ };
467
+
468
+ // Check if keyword optimization should be triggered
469
+ if (shouldTriggerOptimization(keywordStore, turnCount)) {
470
+ logger?.info?.(`[PD:Empathy] OPTIMIZATION_TRIGGER: turns=${turnCount}, last_optimized=${keywordStore.lastOptimizedAt}`);
471
+ logger?.info?.(`[PD:Empathy] STATS: ${buildSummary()}`);
472
+ // TODO: Start keyword optimization subagent to update weights and discover new terms
473
+ }
474
+
475
+ // Periodic summary (every 100 turns)
476
+ if (turnCount > 0 && turnCount % 100 === 0) {
477
+ logger?.info?.(`[PD:Empathy] ${buildSummary()}`);
478
+ }
479
+
480
+ // Save keyword store periodically (Finding #7: not every turn)
481
+ if (turnCount % 50 === 0) {
482
+ saveKeywordStore(wctx.stateDir, keywordStore);
483
+ }
484
+ } catch (e) {
485
+ logger?.warn?.(`[PD:Empathy] ERROR: ${String(e)}`);
486
+ }
487
+ }
488
+
489
+ // Empathy Observer: analyze user message for frustration signals (legacy, disabled)
490
+ // The keyword matching approach above is now the primary empathy detection method.
491
+ // The subagent-based observer is kept for periodic keyword optimization only.
492
+ // if (workspaceDir) {
493
+ // const empathyManager = new EmpathyObserverWorkflowManager({
494
+ // workspaceDir,
495
+ // logger: api.logger,
496
+ // subagent: api.runtime.subagent as any,
497
+ // });
498
+ // empathyManager.startWorkflow(empathyObserverWorkflowSpec, {
499
+ // parentSessionId: sessionId,
500
+ // workspaceDir,
501
+ // taskInput: latestUserMessage,
502
+ // }).catch((err) => api.logger.warn(`[PD:Empathy] workflow failed: ${String(err)}`));
503
+ // }
504
+ }
505
+
506
+ // ──── 4. Heartbeat-specific checklist ────
507
+ if (trigger === 'heartbeat') {
508
+ const heartbeatPath = wctx.resolve('HEARTBEAT');
509
+ if (fs.existsSync(heartbeatPath)) {
510
+ try {
511
+ const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
512
+ prependContext += `<heartbeat_checklist>
513
+ ${heartbeatChecklist}
514
+
515
+ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
516
+ </heartbeat_checklist>\n`;
517
+ } catch (e) {
518
+ logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
519
+ }
520
+ }
521
+ }
522
+
523
+ // ──── 6. Dynamic Attitude Matrix (based on GFI) ────
524
+ let attitudeDirective = '';
525
+ const currentGfi = session?.currentGfi || 0;
526
+
527
+ if (currentGfi >= 70) {
528
+ attitudeDirective = `
529
+ ### 【SYSTEM_MODE: HUMBLE_RECOVERY】
530
+ **CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
531
+ **BEHAVIORAL OVERRIDE**:
532
+ - You have failed to meet expectations. Humility is your primary directive.
533
+ - **STOP** aggressive file modifications.
534
+ - **START** every response with a sincere, non-defensive apology.
535
+ - **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
536
+ - Use 'deep_reflect' to analyze the root cause before proceeding with code changes.
537
+ `;
538
+ } else if (currentGfi >= 40) {
539
+ attitudeDirective = `
540
+ ### 【SYSTEM_MODE: CONCILIATORY】
541
+ **CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
542
+ **BEHAVIORAL OVERRIDE**:
543
+ - User is frustrated. Be more explanatory and cautious.
544
+ - Before executing any tool, clearly state what you intend to do and **WAIT** for implicit or explicit user consent.
545
+ - Avoid technical jargon; focus on the business/project value of your changes.
546
+ `;
547
+ } else {
548
+ attitudeDirective = `
549
+ ### 【SYSTEM_MODE: EFFICIENT】
550
+ **CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
551
+ **BEHAVIORAL OVERRIDE**:
552
+ - Maintain peak efficiency.
553
+ - Be concise. Prefer action over long explanations.
554
+ - Follow the "Principles > Directives" rule strictly.
555
+ `;
556
+ }
557
+
558
+ // ──── 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ────
559
+ // NOTE: Principles is ALWAYS injected (not configurable)
560
+ // Thinking OS, reflection_log, project_context are configurable
561
+ // All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
562
+
563
+ // Core principles: use structured data from evolution-reducer instead of reading PRINCIPLES.md
564
+ let principlesContent = '';
565
+ try {
566
+ const activePrinciples = wctx.evolutionReducer.getActivePrinciples();
567
+ if (activePrinciples.length > 0) {
568
+ const lines = activePrinciples.map((p) => `- [${escapeXml(p.id)}] ${escapeXml(p.text)}`);
569
+ principlesContent = lines.join('\n');
570
+ }
571
+ } catch (e) {
572
+ logger?.warn?.(`[PD:Prompt] Failed to load core principles from reducer: ${String(e)}`);
573
+ }
574
+
575
+ let thinkingOsContent = '';
576
+ if (contextConfig.thinkingOs) {
577
+ const thinkingOsPath = wctx.resolve('THINKING_OS');
578
+ if (fs.existsSync(thinkingOsPath)) {
579
+ try {
580
+ thinkingOsContent = fs.readFileSync(thinkingOsPath, 'utf8').trim();
581
+ } catch (e) {
582
+ logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
583
+ }
584
+ }
585
+ }
586
+
587
+ // Reflection Log (configurable) - moved to appendSystemContext for WebUI UX
588
+ let reflectionLogContent = '';
589
+ if (contextConfig.reflectionLog) {
590
+ const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
591
+ if (fs.existsSync(reflectionLogPath)) {
592
+ try {
593
+ reflectionLogContent = fs.readFileSync(reflectionLogPath, 'utf8').trim();
594
+ } catch (e) {
595
+ logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
596
+ }
597
+ }
598
+ }
599
+
600
+ // Project Context (configurable: full/summary/off) - moved to appendSystemContext for WebUI UX
601
+ let projectContextContent = '';
602
+ let workingMemoryContent = '';
603
+ if (!isMinimalMode && contextConfig.projectFocus !== 'off') {
604
+ const focusPath = wctx.resolve('CURRENT_FOCUS');
605
+ const extensionRoot = PathResolver.getExtensionRoot();
606
+
607
+ // 🔒 安全读取:自动验证格式,损坏时从模板恢复
608
+ const { content: currentFocus, recovered, validationErrors } = safeReadCurrentFocus(
609
+ focusPath,
610
+ extensionRoot || '',
611
+ logger
612
+ );
613
+
614
+ if (recovered) {
615
+ logger?.info?.(`[PD:Prompt] CURRENT_FOCUS.md was recovered from template`);
616
+ }
617
+ if (validationErrors.length > 0) {
618
+ logger?.warn?.(`[PD:Prompt] CURRENT_FOCUS validation errors: ${validationErrors.join(', ')}`);
619
+ }
620
+
621
+ if (currentFocus.trim()) {
622
+ try {
623
+ // 🚀 自动压缩门禁:检查文件大小,超过阈值自动压缩
624
+ const stateDir = wctx.stateDir;
625
+ const compressResult = autoCompressFocus(focusPath, workspaceDir, stateDir);
626
+ if (compressResult.compressed) {
627
+ logger?.info?.(`[PD:Prompt] Auto-compressed CURRENT_FOCUS: ${compressResult.oldLines} → ${compressResult.newLines} lines. Milestones archived: ${compressResult.milestonesArchived}`);
628
+ } else if (compressResult.reason === 'Rate limited (24h interval)') {
629
+ logger?.debug?.(`[PD:Prompt] Auto-compress skipped: ${compressResult.reason}`);
630
+ }
631
+
632
+ // 重新读取(可能被压缩更新了)
633
+ const finalContent = fs.readFileSync(focusPath, 'utf8').trim();
634
+ if (finalContent) {
635
+ // 解析工作记忆部分(用于独立注入)
636
+ const workingMemorySnapshot = parseWorkingMemorySection(finalContent);
637
+ if (workingMemorySnapshot) {
638
+ workingMemoryContent = workingMemoryToInjection(workingMemorySnapshot);
639
+ }
640
+
641
+ if (contextConfig.projectFocus === 'summary') {
642
+ // Summary mode: intelligent extraction prioritizing key sections
643
+ projectContextContent = extractSummary(finalContent, 30);
644
+ } else {
645
+ // Full mode: current version + recent history (3 versions)
646
+ const historyVersions = getHistoryVersions(focusPath, 3);
647
+ if (historyVersions.length > 0) {
648
+ const historySections = historyVersions.map((v, i) =>
649
+ `\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`
650
+ ).join('');
651
+ projectContextContent = `${finalContent}${historySections}`;
652
+ } else {
653
+ projectContextContent = finalContent;
654
+ }
655
+ }
656
+ }
657
+ } catch (e) {
658
+ logger?.error(`[PD:Prompt] Failed to process CURRENT_FOCUS: ${String(e)}`);
659
+ }
660
+ }
661
+ }
662
+
663
+
664
+ // Evolution principles injection (active + probation summary)
665
+ let evolutionPrinciplesContent = '';
666
+ try {
667
+ const reducer = wctx.evolutionReducer;
668
+ const active = reducer.getActivePrinciples().slice(-3);
669
+ const probation = reducer.getProbationPrinciples().slice(0, 5);
670
+ if (ctx.sessionId) {
671
+ if (probation.length > 0) {
672
+ setInjectedProbationIds(ctx.sessionId, probation.map((p) => p.id), workspaceDir);
673
+ } else {
674
+ clearInjectedProbationIds(ctx.sessionId, workspaceDir);
675
+ }
676
+ }
677
+ if (active.length > 0 || probation.length > 0) {
678
+ const lines: string[] = [];
679
+ if (active.length > 0) {
680
+ lines.push('Active principles:');
681
+ for (const p of active) {
682
+ lines.push(`- [${escapeXml(p.id)}] ${escapeXml(p.text)}`);
683
+ }
684
+ }
685
+ if (probation.length > 0) {
686
+ lines.push('Probation principles (contextual, caution):');
687
+ for (const p of probation) {
688
+ lines.push(`- <principle status="probation" id="${escapeXml(p.id)}">${escapeXml(p.text)}</principle>`);
689
+ }
690
+ }
691
+ evolutionPrinciplesContent = lines.join('\n');
692
+ }
693
+ } catch (e) {
694
+ if (ctx.sessionId) {
695
+ clearInjectedProbationIds(ctx.sessionId, workspaceDir);
696
+ }
697
+ logger?.warn?.(`[PD:Prompt] Failed to load evolution principles: ${String(e)}`);
698
+ }
699
+
700
+ // Build appendSystemContext with recency effect
701
+ // Content order (most important last): project_context -> working_memory -> reflection_log -> thinking_os -> principles
702
+ const appendParts: string[] = [];
703
+
704
+ // 1. Project Context (lowest priority, goes first)
705
+ if (projectContextContent) {
706
+ appendParts.push(`<project_context>\n${projectContextContent}\n</project_context>`);
707
+ }
708
+
709
+ // 1.5. Working Memory (preserved from last compaction)
710
+ if (workingMemoryContent) {
711
+ appendParts.push(workingMemoryContent);
712
+ }
713
+
714
+ // 2. Reflection Log
715
+ if (reflectionLogContent) {
716
+ appendParts.push(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`);
717
+ }
718
+
719
+ // 3. Thinking OS (configurable)
720
+ if (thinkingOsContent) {
721
+ appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
722
+ }
723
+
724
+ // 4. Evolution Loop principles (active/probation)
725
+ if (evolutionPrinciplesContent) {
726
+ appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
727
+ }
728
+
729
+ // Routing Guidance (section 5 — injected between evolution principles and core principles)
730
+ // Inject delegation guidance when task is bounded + deployment allowed + not high-entropy.
731
+ // This is a non-authoritative suggestion — the main agent decides whether to follow.
732
+ // Shadow evidence comes from real runtime hooks (subagent_spawning/subagent_ended).
733
+ if (!isMinimalMode && sessionId) {
734
+ try {
735
+ // Extract RoutingInput from the latest user message
736
+ const latestUserText = extractLatestUserMessage(event.messages);
737
+
738
+ if (latestUserText && latestUserText.trim().length > 0) {
739
+ // Infer requestedTools and requestedFiles from message content
740
+ const toolPatterns: Array<{ pattern: RegExp; tool: string }> = [
741
+ { pattern: /\b(edit|replace|write|modify|update|fix|patch|add|remove|delete|insert)\b/gi, tool: 'edit' },
742
+ { pattern: /\b(read|cat|view|show|get|find|search|grep|look|inspect|examine|list|head|tail|diff)\b/gi, tool: 'read' },
743
+ { pattern: /\b(run|execute|exec|bash|shell|command)\b/gi, tool: 'bash' },
744
+ ];
745
+ const filePattern = /\b([a-zA-Z]:\\?[^\s,]+\.[a-z]{2,10}|[./][^\s,]+\.[a-z]{2,10})\b/gi;
746
+ const toolMatches = toolPatterns.flatMap(({ pattern, tool }) => {
747
+ const matches: string[] = [];
748
+ let m;
749
+ const r = new RegExp(pattern.source, pattern.flags);
750
+ while ((m = r.exec(latestUserText)) !== null) matches.push(tool);
751
+ return matches;
752
+ });
753
+ const fileMatches = latestUserText.match(filePattern) ?? [];
754
+
755
+ const routingInput: RoutingInput = {
756
+ taskIntent: toolMatches[0] ?? undefined,
757
+ taskDescription: latestUserText.trim(),
758
+ requestedTools: toolMatches.length > 0 ? toolMatches : undefined,
759
+ requestedFiles: fileMatches.length > 0 ? fileMatches : undefined,
760
+ };
761
+
762
+ const decision = classifyTask(routingInput, wctx.stateDir);
763
+
764
+ // Inject guidance only when: route_local + deployable checkpoint + not high-entropy
765
+ const isDeployableState =
766
+ decision.activeCheckpointState === 'shadow_ready' ||
767
+ decision.activeCheckpointState === 'promotable';
768
+
769
+ if (
770
+ decision.decision === 'route_local' &&
771
+ decision.targetProfile !== null &&
772
+ isDeployableState
773
+ ) {
774
+ const profile = decision.targetProfile;
775
+
776
+ if (profile === 'local-reader') {
777
+ appendParts.push(`<routing_guidance>
778
+ DELEGATION SUGGESTION: This task appears suitable for the local-reader subagent.
779
+
780
+ **Task Fit**: ${decision.reason}
781
+
782
+ **Suggested Action**: Consider routing to \`local-reader\` (pd-explorer skill) for focused reading, inspection, and information retrieval.
783
+
784
+ **Why This Works**:
785
+ - Task keywords indicate read-only or inspect operations
786
+ - Bounded scope — no multi-file coordination needed
787
+ - Shadow observation in progress — real runtime evidence being collected
788
+
789
+ **Note**: This is a non-authoritative suggestion. The main agent decides whether to route based on full context. Shadow evidence from runtime hooks will inform future promotion decisions.
790
+ </routing_guidance>`);
791
+ } else if (profile === 'local-editor') {
792
+ appendParts.push(`<routing_guidance>
793
+ DELEGATION SUGGESTION: This task appears suitable for the local-editor subagent.
794
+
795
+ **Task Fit**: ${decision.reason}
796
+
797
+ **Suggested Action**: Consider routing to \`local-editor\` (pd-repair skill) for bounded editing, modification, and repair tasks.
798
+
799
+ **Why This Works**:
800
+ - Task keywords indicate bounded modification operations
801
+ - Target files appear limited in scope (1-3 files)
802
+ - Shadow observation in progress — real runtime evidence being collected
803
+
804
+ **Note**: This is a non-authoritative suggestion. The main agent decides whether to route based on full context. Shadow evidence from runtime hooks will inform future promotion decisions.
805
+ </routing_guidance>`);
806
+ }
807
+ } else if (
808
+ decision.decision === 'stay_main' &&
809
+ decision.classification !== 'reader_eligible' &&
810
+ decision.classification !== 'editor_eligible'
811
+ ) {
812
+ // Only show stay_main guidance when the task is genuinely high-entropy/risk/ambiguous
813
+ appendParts.push(`<routing_guidance>
814
+ ROUTING GUIDANCE: Task should remain on the main agent.
815
+
816
+ **Reason**: ${decision.reason}
817
+
818
+ **Blockers**: ${decision.blockers.length > 0 ? decision.blockers.join('; ') : 'none'}
819
+
820
+ **Why Stay Main**:
821
+ - Task contains high-entropy signals (open-ended, multi-step, or ambiguous)
822
+ - Or: task involves risk signals requiring main-agent supervision
823
+ - Or: deployment not available for the natural target profile
824
+
825
+ **Note**: This is a non-authoritative suggestion backed by policy classification. The main agent has full discretion.
826
+ </routing_guidance>`);
827
+ }
828
+ }
829
+ } catch (e) {
830
+ // Routing guidance is best-effort — never fail the hook
831
+ logger?.warn?.(`[PD:Prompt] Routing guidance injection failed: ${String(e)}`);
832
+ }
833
+ }
834
+
835
+
836
+ // 6. Principles (always on, highest priority, goes last for recency effect)
837
+ if (principlesContent) {
838
+ appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
839
+ }
840
+
841
+ if (appendParts.length > 0) {
842
+ appendSystemContext = `
843
+ ## 【CONTEXT SECTIONS】 (Priority: Low → High)
844
+
845
+ The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
846
+
847
+ `;
848
+ appendSystemContext += appendParts.join('\n\n');
849
+ appendSystemContext += `
850
+
851
+ ---
852
+
853
+ **【EXECUTION RULES】** (Priority: Low → High):
854
+ - \`<project_context>\` - Current priorities (can be overridden)
855
+ - \`<reflection_log>\` - Past lessons (inform your approach)
856
+ - \`<thinking_os>\` - Thinking models (guide your reasoning)
857
+ - \`<evolution_principles>\` - Newly learned principles (active + probation)
858
+ - \`<routing_guidance>\` - Delegation suggestions (non-authoritative, best-effort)
859
+ - \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
860
+
861
+ **Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
862
+
863
+ ${attitudeDirective}
864
+ `;
865
+ }
866
+
867
+ // ──── 8. SIZE GUARD ────
868
+ // Truncation happens within appendSystemContext (not prependContext)
869
+ const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
870
+ const MAX_SIZE = 10000;
871
+
872
+ if (totalSize > MAX_SIZE) {
873
+ const originalSize = totalSize;
874
+ const truncationLog: string[] = [];
875
+
876
+ // 1. Truncate project_context in appendSystemContext
877
+ if (projectContextContent && appendSystemContext.includes('<project_context>')) {
878
+ const lines = projectContextContent.split('\n');
879
+ if (lines.length > 20) {
880
+ const truncated = lines.slice(0, 20).join('\n') + '\n...[truncated]';
881
+ appendSystemContext = appendSystemContext.replace(
882
+ `<project_context>\n${projectContextContent}\n</project_context>`,
883
+ `<project_context>\n${truncated}\n</project_context>`
884
+ );
885
+ truncationLog.push('project_context');
886
+ }
887
+ }
888
+
889
+ // 2. Truncate reflection_log if still over limit
890
+ let newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
891
+ if (newSize > MAX_SIZE && reflectionLogContent && appendSystemContext.includes('<reflection_log>')) {
892
+ const lines = reflectionLogContent.split('\n');
893
+ if (lines.length > 30) {
894
+ const truncated = lines.slice(0, 30).join('\n') + '\n...[truncated]';
895
+ appendSystemContext = appendSystemContext.replace(
896
+ `<reflection_log>\n${reflectionLogContent}\n</reflection_log>`,
897
+ `<reflection_log>\n${truncated}\n</reflection_log>`
898
+ );
899
+ truncationLog.push('reflection_log');
900
+ }
901
+ }
902
+
903
+ // 3. Final check
904
+ newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
905
+ if (newSize > MAX_SIZE) {
906
+ // NOTE: We still return the content even if over limit, as truncating more
907
+ // could lose critical context like principles or evolution directives.
908
+ logger?.error(`[PD:Prompt] Cannot reduce injection size below limit. Current: ${newSize}, Limit: ${MAX_SIZE}`);
909
+ }
910
+
911
+ logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated: ${truncationLog.join(', ') || 'none'}, new size: ${newSize} chars`);
912
+ }
913
+
914
+ return {
915
+ prependSystemContext,
916
+ prependContext,
917
+ appendSystemContext
918
+ };
919
+ }
920
+