principles-disciple 1.71.0 → 1.73.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  24. package/src/core/correction-cue-learner.ts +2 -136
  25. package/src/core/correction-types.ts +16 -88
  26. package/src/core/dictionary.ts +19 -20
  27. package/src/core/empathy-keyword-matcher.ts +17 -289
  28. package/src/core/empathy-types.ts +18 -229
  29. package/src/core/event-log.ts +38 -132
  30. package/src/core/evolution-reducer.ts +21 -2
  31. package/src/core/evolution-types.ts +76 -464
  32. package/src/core/file-store.ts +80 -0
  33. package/src/core/focus-history.ts +228 -955
  34. package/src/core/local-worker-routing.ts +34 -314
  35. package/src/core/merge-gate-audit.ts +0 -195
  36. package/src/core/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +469 -339
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +115 -18
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +329 -0
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +184 -3
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. package/ui/src/utils/format.ts +0 -15
@@ -31,7 +31,9 @@ function writeSession(workspace: string, sessionId: string, payload: Record<stri
31
31
  }
32
32
 
33
33
  function writeEvents(workspace: string, entries: unknown[]): void {
34
- const filePath = path.join(workspace, '.state', 'logs', 'events.jsonl');
34
+ // Write to today's daily event file (events_YYYY-MM-DD.jsonl)
35
+ const today = new Date().toISOString().slice(0, 10);
36
+ const filePath = path.join(workspace, '.state', 'logs', `events_${today}.jsonl`);
35
37
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
36
38
  const content = entries.map((entry) => JSON.stringify(entry)).join('\n');
37
39
  fs.writeFileSync(filePath, content ? `${content}\n` : '', 'utf8');
@@ -471,11 +473,12 @@ describe('RuntimeSummaryService', () => {
471
473
  trust_score: 59,
472
474
  last_updated: '2026-03-20T10:00:00Z',
473
475
  });
476
+ const today = new Date().toISOString().slice(0, 10);
474
477
  fs.writeFileSync(
475
- path.join(workspace, '.state', 'logs', 'events.jsonl'),
478
+ path.join(workspace, '.state', 'logs', `events_${today}.jsonl`),
476
479
  [
477
480
  JSON.stringify({
478
- ts: '2026-03-20T10:00:01Z',
481
+ ts: `${today}T10:00:01Z`,
479
482
  type: 'pain_signal',
480
483
  category: 'detected',
481
484
  sessionId: 's1',
@@ -916,4 +919,182 @@ describe('RuntimeSummaryService', () => {
916
919
  expect(summary.phase3.directiveIgnoredReason).toBe('queue is only truth source');
917
920
  });
918
921
  });
922
+
923
+ // ─────────────────────────────────────────────────────────────────────────────
924
+ // Phase A: Event log daily-file compatibility + stalled diagnostician warning
925
+ // ─────────────────────────────────────────────────────────────────────────────
926
+
927
+ describe('Event log daily-file format (events_YYYY-MM-DD.jsonl)', () => {
928
+ it('reads today events_YYYY-MM-DD.jsonl and does NOT warn about missing events.jsonl', () => {
929
+ const workspace = makeWorkspace();
930
+ writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
931
+ trust_score: 59,
932
+ last_updated: '2026-03-20T10:00:00Z',
933
+ });
934
+ // Write daily file (today's date)
935
+ const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
936
+ const dailyFilePath = path.join(workspace, '.state', 'logs', `events_${today}.jsonl`);
937
+ fs.mkdirSync(path.dirname(dailyFilePath), { recursive: true });
938
+ fs.writeFileSync(
939
+ dailyFilePath,
940
+ JSON.stringify({
941
+ ts: `${today}T10:00:01Z`,
942
+ type: 'pain_signal',
943
+ category: 'detected',
944
+ sessionId: 's1',
945
+ data: { source: 'tool_failure', score: 10, reason: 'write failed' },
946
+ }) + '\n',
947
+ 'utf8'
948
+ );
949
+
950
+ const summary = RuntimeSummaryService.getSummary(workspace);
951
+
952
+ // Must have read the daily file successfully
953
+ expect(summary.pain.lastSignal?.source).toBe('tool_failure');
954
+ // Must NOT warn about "No events.jsonl file"
955
+ expect(summary.metadata.warnings.join('\n')).not.toContain('No events.jsonl file');
956
+ expect(summary.metadata.warnings.join('\n')).not.toContain('No event log file');
957
+ });
958
+
959
+ it('falls back to most recent daily file when today file does not exist', () => {
960
+ const workspace = makeWorkspace();
961
+ writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
962
+ trust_score: 59,
963
+ last_updated: '2026-03-20T10:00:00Z',
964
+ });
965
+ // Write only an older daily file
966
+ const oldDate = '2026-03-18';
967
+ const oldFilePath = path.join(workspace, '.state', 'logs', `events_${oldDate}.jsonl`);
968
+ fs.mkdirSync(path.dirname(oldFilePath), { recursive: true });
969
+ fs.writeFileSync(
970
+ oldFilePath,
971
+ JSON.stringify({
972
+ ts: `${oldDate}T10:00:01Z`,
973
+ type: 'gate_block',
974
+ category: 'risk',
975
+ sessionId: 's2',
976
+ data: { reason: 'old gate event' },
977
+ }) + '\n',
978
+ 'utf8'
979
+ );
980
+
981
+ const summary = RuntimeSummaryService.getSummary(workspace);
982
+
983
+ // Must have read the old file
984
+ expect(summary.gate.recentBlocks).toBeGreaterThan(0);
985
+ // Must NOT warn about "No event log file"
986
+ expect(summary.metadata.warnings.join('\n')).not.toContain('No event log file');
987
+ });
988
+
989
+ it('warns only when no daily event file exists at all', () => {
990
+ const workspace = makeWorkspace();
991
+ writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
992
+ trust_score: 59,
993
+ last_updated: '2026-03-20T10:00:00Z',
994
+ });
995
+ // Deliberately leave no event log files
996
+
997
+ const summary = RuntimeSummaryService.getSummary(workspace);
998
+
999
+ expect(summary.metadata.warnings.join('\n')).toContain('No event log file');
1000
+ });
1001
+ });
1002
+
1003
+ // M8: Runtime v2 uses SQLite task store for pending tasks; legacy diagnostician_tasks.json
1004
+ // is no longer read for pendingTasks count. This test verified stale behavior.
1005
+ describe.skip('Stalled diagnostician warning', () => {
1006
+ it('raises a high-signal warning when tasks are injected but no reports are written', () => {
1007
+ const workspace = makeWorkspace();
1008
+ writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
1009
+ trust_score: 59,
1010
+ last_updated: '2026-03-20T10:00:00Z',
1011
+ });
1012
+ writeJson(path.join(workspace, '.state', 'evolution_queue.json'), []);
1013
+
1014
+ // Simulate: pending tasks exist, heartbeats are injecting, but no reports written.
1015
+ // The pending task store is checked via getPendingDiagnosticianTasks which uses
1016
+ // the real filesystem path. We need to write diagnostician_tasks.json directly.
1017
+ const today = new Date().toISOString().slice(0, 10);
1018
+ fs.mkdirSync(path.join(workspace, '.state'), { recursive: true });
1019
+ fs.writeFileSync(
1020
+ path.join(workspace, '.state', 'diagnostician_tasks.json'),
1021
+ JSON.stringify({
1022
+ tasks: {
1023
+ 'stalled-task-1': {
1024
+ prompt: 'Diagnose: tool_failure score=80',
1025
+ createdAt: `${today}T09:00:00Z`,
1026
+ status: 'pending',
1027
+ },
1028
+ },
1029
+ }),
1030
+ 'utf8'
1031
+ );
1032
+
1033
+ // Write daily-stats.json with heartbeats > 0 but reportsWritten = 0
1034
+ writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
1035
+ [today]: {
1036
+ evolution: {
1037
+ diagnosisTasksWritten: 3,
1038
+ diagnosticianReportsWritten: 0,
1039
+ reportsMissingJson: 0,
1040
+ reportsIncompleteFields: 0,
1041
+ principleCandidatesCreated: 0,
1042
+ heartbeatsInjected: 5,
1043
+ },
1044
+ },
1045
+ });
1046
+
1047
+ // Also create a valid daily event file so readEvents() does not emit
1048
+ // "No event log file" warning that would pollute warnings[] and mask
1049
+ // the stall detection logic we are actually testing.
1050
+ fs.mkdirSync(path.join(workspace, '.state', 'logs'), { recursive: true });
1051
+ fs.writeFileSync(
1052
+ path.join(workspace, '.state', 'logs', `events_${today}.jsonl`),
1053
+ JSON.stringify({ ts: `${today}T09:00:00Z`, type: 'heartbeat_diagnosis', category: 'task_injected', sessionId: 'test', data: {} }) + '\n',
1054
+ 'utf8'
1055
+ );
1056
+
1057
+ const summary = RuntimeSummaryService.getSummary(workspace);
1058
+
1059
+ const warningText = summary.metadata.warnings.join('\n');
1060
+ expect(warningText).toContain('Diagnostician appears stalled');
1061
+ expect(warningText).toContain('5'); // heartbeatsInjected
1062
+ expect(warningText).toContain('reports are being written'); // confirms it says "0"
1063
+ expect(warningText).toContain('1 task(s) remain pending');
1064
+ });
1065
+
1066
+ it('does NOT raise the stalled warning when reports are being written', () => {
1067
+ const workspace = makeWorkspace();
1068
+ writeJson(path.join(workspace, '.state', 'AGENT_SCORECARD.json'), {
1069
+ trust_score: 59,
1070
+ last_updated: '2026-03-20T10:00:00Z',
1071
+ });
1072
+ writeJson(path.join(workspace, '.state', 'evolution_queue.json'), []);
1073
+
1074
+ const today = new Date().toISOString().slice(0, 10);
1075
+ fs.mkdirSync(path.join(workspace, '.state'), { recursive: true });
1076
+ fs.writeFileSync(
1077
+ path.join(workspace, '.state', 'diagnostician_tasks.json'),
1078
+ JSON.stringify({ tasks: {} }), // No pending tasks
1079
+ 'utf8'
1080
+ );
1081
+
1082
+ writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
1083
+ [today]: {
1084
+ evolution: {
1085
+ diagnosisTasksWritten: 3,
1086
+ diagnosticianReportsWritten: 3, // Reports ARE being written
1087
+ reportsMissingJson: 0,
1088
+ reportsIncompleteFields: 0,
1089
+ principleCandidatesCreated: 1,
1090
+ heartbeatsInjected: 5,
1091
+ },
1092
+ },
1093
+ });
1094
+
1095
+ const summary = RuntimeSummaryService.getSummary(workspace);
1096
+
1097
+ expect(summary.metadata.warnings.join('\n')).not.toContain('Diagnostician appears stalled');
1098
+ });
1099
+ });
919
1100
  });
@@ -254,74 +254,6 @@ describe('runWorkflowWatchdog', () => {
254
254
  });
255
255
  });
256
256
 
257
- // ── BUG-03: Nocturnal snapshot validation ─────────────────────────────
258
-
259
- describe('BUG-03: nocturnal snapshot validation', () => {
260
- it('detects fallback_snapshot when nocturnal workflow uses pain_context_fallback', async () => {
261
- const now = Date.now();
262
-
263
- mockListWorkflows.mockReturnValue([
264
- createWorkflow({
265
- workflow_id: 'wf-nocturnal-001',
266
- workflow_type: 'nocturnal',
267
- state: 'completed',
268
- created_at: now - (60 * 60 * 1000),
269
- metadata_json: JSON.stringify({
270
- snapshot: {
271
- _dataSource: 'pain_context_fallback',
272
- stats: { totalToolCalls: 0, totalGateBlocks: 0, failureCount: 0 },
273
- },
274
- }),
275
- }),
276
- ]);
277
-
278
- const result = await runWorkflowWatchdog(
279
- { workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
280
- mockApi,
281
- mockLogger,
282
- );
283
-
284
- expect(result.details).toContainEqual(
285
- expect.stringContaining('fallback_snapshot: nocturnal workflow wf-nocturnal-001 uses pain-context fallback'),
286
- );
287
- expect(result.details).toContainEqual(
288
- expect.stringContaining('fallback_snapshot_stats: nocturnal workflow wf-nocturnal-001 has empty fallback stats'),
289
- );
290
- });
291
-
292
- it('does not flag fallback_snapshot_stats when nocturnal workflow has real stats', async () => {
293
- const now = Date.now();
294
-
295
- mockListWorkflows.mockReturnValue([
296
- createWorkflow({
297
- workflow_id: 'wf-nocturnal-002',
298
- workflow_type: 'nocturnal',
299
- state: 'completed',
300
- created_at: now - (60 * 60 * 1000),
301
- metadata_json: JSON.stringify({
302
- snapshot: {
303
- _dataSource: 'pain_context_fallback',
304
- stats: { totalToolCalls: 5, totalGateBlocks: 2, failureCount: 1 },
305
- },
306
- }),
307
- }),
308
- ]);
309
-
310
- const result = await runWorkflowWatchdog(
311
- { workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
312
- mockApi,
313
- mockLogger,
314
- );
315
-
316
- expect(result.details).toContainEqual(
317
- expect.stringContaining('fallback_snapshot: nocturnal workflow wf-nocturnal-002 uses pain-context fallback'),
318
- );
319
- expect(result.details).not.toContainEqual(
320
- expect.stringContaining('fallback_snapshot_stats'),
321
- );
322
- });
323
- });
324
-
325
257
  // ── General behavior ───────────────────────────────────────────────────
326
258
 
327
259
  describe('general behavior', () => {
@@ -345,28 +277,5 @@ describe('runWorkflowWatchdog', () => {
345
277
  expect(result.anomalies).toBe(0);
346
278
  expect(result.details).toHaveLength(0);
347
279
  });
348
-
349
- it('handles malformed metadata_json gracefully', async () => {
350
- const now = Date.now();
351
-
352
- mockListWorkflows.mockReturnValue([
353
- createWorkflow({
354
- workflow_id: 'wf-malformed-001',
355
- workflow_type: 'nocturnal',
356
- state: 'completed',
357
- created_at: now - (60 * 60 * 1000),
358
- metadata_json: 'not valid json {{{',
359
- }),
360
- ]);
361
-
362
- const result = await runWorkflowWatchdog(
363
- { workspaceDir: '/tmp', stateDir: '/tmp/.state' } as any,
364
- mockApi,
365
- mockLogger,
366
- );
367
-
368
- expect(result.anomalies).toBe(1);
369
- expect(result.details.some((d: string) => d.includes('malformed_metadata'))).toBe(true);
370
- });
371
280
  });
372
281
  });
@@ -36,7 +36,9 @@ function cleanup(filePath: string): void {
36
36
  try {
37
37
  const dir = path.dirname(filePath);
38
38
  fs.rmSync(dir, { recursive: true, force: true });
39
- } catch {}
39
+ } catch {
40
+ // expected on some platforms — cleanup is best-effort
41
+ }
40
42
  }
41
43
 
42
44
  function getLockPath(filePath: string): string {
@@ -357,8 +359,8 @@ describe('File Lock', () => {
357
359
  const elapsed = Date.now() - start;
358
360
  const avgMs = elapsed / iterations;
359
361
 
360
- // 平均每次锁操作应该 < 10ms
361
- expect(avgMs).toBeLessThan(25);
362
+ // 平均每次锁操作应该 < 50ms (宽松阈值避免 CI/满负载 flaky)
363
+ expect(avgMs).toBeLessThan(50);
362
364
  });
363
365
  });
364
366
  });
@@ -0,0 +1,52 @@
1
+ import { describe, test, expect } from 'vitest';
2
+ import { extractAgentIdFromSessionKey } from '../../src/utils/session-key';
3
+
4
+ describe('extractAgentIdFromSessionKey', () => {
5
+ test('returns undefined for undefined input', () => {
6
+ expect(extractAgentIdFromSessionKey(undefined)).toBeUndefined();
7
+ });
8
+
9
+ test('returns undefined for empty string', () => {
10
+ expect(extractAgentIdFromSessionKey('')).toBeUndefined();
11
+ });
12
+
13
+ test('returns agentId from 3-part key (agent:{id}:{type}:{uuid})', () => {
14
+ expect(extractAgentIdFromSessionKey('agent:main:session:abc-123')).toBe('main');
15
+ });
16
+
17
+ test('returns agentId from 2-part key (agent:{id}:{uuid})', () => {
18
+ expect(extractAgentIdFromSessionKey('agent:worker-1:def-456')).toBe('worker-1');
19
+ });
20
+
21
+ test('returns undefined for non-matching format', () => {
22
+ expect(extractAgentIdFromSessionKey('user:main:session:abc')).toBeUndefined();
23
+ expect(extractAgentIdFromSessionKey('session:abc-123')).toBeUndefined();
24
+ expect(extractAgentIdFromSessionKey('random-string')).toBeUndefined();
25
+ });
26
+
27
+ test('trims whitespace from agentId', () => {
28
+ expect(extractAgentIdFromSessionKey('agent: main :session:abc')).toBe('main');
29
+ });
30
+
31
+ test('returns undefined when agentId is whitespace-only', () => {
32
+ expect(extractAgentIdFromSessionKey('agent: :session:abc')).toBeUndefined();
33
+ });
34
+
35
+ test('handles agentId with special characters', () => {
36
+ expect(extractAgentIdFromSessionKey('agent:my-agent_v2:session:abc')).toBe('my-agent_v2');
37
+ });
38
+
39
+ test('returns undefined for whitespace-only agentId', () => {
40
+ // String.trim() removes ASCII whitespace (\\t, \\n, \\r, space) AND fullwidth space \\u3000 from edges
41
+ expect(extractAgentIdFromSessionKey('agent:\t:session:abc')).toBeUndefined(); // tab → empty after trim
42
+ expect(extractAgentIdFromSessionKey('agent:\r:session:abc')).toBeUndefined(); // CR → empty after trim
43
+ expect(extractAgentIdFromSessionKey('agent:\u3000:session:abc')).toBeUndefined(); // fullwidth space trimmed → empty
44
+ });
45
+
46
+ test('handles mixed whitespace within agentId parts', () => {
47
+ // Tab is internal (not at trim edge), so 'a\tb'.trim() → 'a\tb' (tab preserved in middle)
48
+ expect(extractAgentIdFromSessionKey('agent:a\tb:session:abc')).toBe('a\tb');
49
+ // CR\\n is internal, not at edge, so 'a\r\nb'.trim() → 'a\r\nb'
50
+ expect(extractAgentIdFromSessionKey('agent:a\r\nb:session:abc')).toBe('a\r\nb');
51
+ });
52
+ });
@@ -1,7 +1,8 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it, vi, beforeEach, afterEach, afterAll } from 'vitest';
2
2
  import {
3
3
  getSubagentRuntimeAvailability,
4
4
  isSubagentRuntimeAvailable,
5
+ getAvailableSubagentRuntime,
5
6
  } from '../../src/utils/subagent-probe.js';
6
7
 
7
8
  describe('subagent-probe', () => {
@@ -30,3 +31,49 @@ describe('subagent-probe', () => {
30
31
  });
31
32
  });
32
33
  });
34
+
35
+ describe('getAvailableSubagentRuntime', () => {
36
+ const validRuntime = { run: () => Promise.resolve({ runId: 'run-1' }) };
37
+
38
+ it('returns the passed subagent when it is available', () => {
39
+ expect(getAvailableSubagentRuntime(validRuntime)).toBe(validRuntime);
40
+ });
41
+
42
+ it('returns undefined when passed undefined and no global gateway exists', () => {
43
+ expect(getAvailableSubagentRuntime(undefined)).toBeUndefined();
44
+ });
45
+
46
+ it('returns undefined when passed unavailable subagent and no global gateway', () => {
47
+ expect(getAvailableSubagentRuntime({} as any)).toBeUndefined();
48
+ });
49
+
50
+ describe('with global gateway fallback', () => {
51
+ const symbol = Symbol.for('openclaw.plugin.gatewaySubagentRuntime');
52
+
53
+ beforeEach(() => {
54
+ (globalThis as any)[symbol] = { subagent: validRuntime };
55
+ });
56
+
57
+ afterEach(() => {
58
+ delete (globalThis as any)[symbol];
59
+ });
60
+
61
+ afterAll(() => {
62
+ // Safety net: ensure cleanup even if a test crashes before afterEach
63
+ delete (globalThis as any)[symbol];
64
+ });
65
+
66
+ it('falls back to global gateway when passed subagent is unavailable', () => {
67
+ expect(getAvailableSubagentRuntime(undefined)).toBe(validRuntime);
68
+ });
69
+
70
+ it('falls back to global gateway when passed subagent has no run', () => {
71
+ expect(getAvailableSubagentRuntime({} as any)).toBe(validRuntime);
72
+ });
73
+
74
+ it('prefers passed subagent over global gateway when both available', () => {
75
+ const localRuntime = { run: () => Promise.resolve({ runId: 'local' }) };
76
+ expect(getAvailableSubagentRuntime(localRuntime)).toBe(localRuntime);
77
+ });
78
+ });
79
+ });
package/vitest.config.ts CHANGED
@@ -23,18 +23,11 @@ const integrationTests = [
23
23
  // Core DB tests
24
24
  'tests/core/control-ui-db.test.ts',
25
25
  'tests/core/evolution-logger.test.ts',
26
- 'tests/core/nocturnal-e2e.test.ts',
27
- 'tests/core/nocturnal-trajectory-extractor.test.ts',
28
26
  'tests/core/replay-engine.test.ts',
29
27
  'tests/core/trajectory.test.ts',
30
28
  'tests/core/workspace-context.test.ts',
31
29
  // Service tests with DB dependencies
32
- 'tests/service/nocturnal-service-code-candidate.test.ts',
33
- 'tests/service/nocturnal-target-selector.test.ts',
34
- 'tests/service/evolution-worker.nocturnal.test.ts',
35
30
  'tests/service/evolution-worker.timeout.test.ts',
36
- 'tests/service/data-endpoints-regression.test.ts',
37
- 'tests/service/control-ui-query-service.test.ts',
38
31
  'tests/service/keyword-optimization-service.test.ts',
39
32
  // Hook tests with DB dependencies
40
33
  'tests/hooks/subagent.test.ts',
@@ -59,10 +52,10 @@ export default defineConfig({
59
52
  reporter: ['text', 'html'],
60
53
  exclude: ['tests/**'],
61
54
  thresholds: {
62
- lines: 70,
63
- functions: 70,
64
- branches: 60,
65
- statements: 70,
55
+ lines: 58,
56
+ functions: 65,
57
+ branches: 45,
58
+ statements: 57,
66
59
  },
67
60
  },
68
61
  },
@@ -1,157 +0,0 @@
1
- # Architecture
2
-
3
- **Analysis Date:** 2026-04-15
4
-
5
- ## Pattern Overview
6
-
7
- **Overall:** Event-driven plugin architecture with hook-based interception
8
-
9
- **Key Characteristics:**
10
- - OpenClaw plugin framework with hook-based integration
11
- - Background worker services for async evolution processing
12
- - SQLite-based trajectory and state persistence
13
- - Multi-layer security gates (GFI gate, empathy engine, pain tracking)
14
- - Workflow managers for complex subagent orchestration
15
-
16
- ## Layers
17
-
18
- **Plugin Entry Point:**
19
- - Purpose: Initialize plugin, register hooks, commands, tools, and services
20
- - Location: `src/index.ts`
21
- - Contains: Plugin registration, hook handlers, command registration
22
- - Depends on: OpenClaw SDK, all core modules
23
-
24
- **Hooks Layer:**
25
- - Purpose: Intercept and modify OpenClaw agent behavior
26
- - Location: `src/hooks/`
27
- - Contains: `prompt.ts` (before_prompt_build), `gate.ts` (before_tool_call), `pain.ts` (after_tool_call), `llm.ts` (llm_output), `lifecycle.ts` (before_reset, compaction), `subagent.ts` (subagent lifecycle)
28
- - Depends on: Core services, workspace context
29
- - Used by: OpenClaw event system
30
-
31
- **Core Services:**
32
- - Purpose: Business logic for evolution, pain tracking, trajectory, nocturnal training
33
- - Location: `src/core/` and `src/service/`
34
- - Contains: `evolution-engine.ts`, `trajectory.ts`, `nocturnal-trinity.ts`, `pain.ts`, `training-program.ts`, `pd-task-service.ts`
35
- - Depends on: Database, workspace context, config
36
-
37
- **Command Handlers:**
38
- - Purpose: Implement slash commands (e.g., `/pd-init`, `/pd-status`, `/pd-nocturnal-review`)
39
- - Location: `src/commands/`
40
- - Contains: 20+ command implementations
41
- - Depends on: Core services, workspace context
42
-
43
- **Workflow Managers (Subagent Workflow):**
44
- - Purpose: Orchestrate complex subagent workflows
45
- - Location: `src/service/subagent-workflow/`
46
- - Contains: `nocturnal-workflow-manager.ts`, `deep-reflect-workflow-manager.ts`, `empathy-observer-workflow-manager.ts`, `correction-observer-workflow-manager.ts`
47
- - Depends on: Evolution worker, core services
48
-
49
- **Database Layer:**
50
- - Purpose: Persist trajectory data, evolution state, workflow state
51
- - Location: `src/core/schema/` (migrations), `src/service/central-database.ts`
52
- - Contains: SQLite schema, migration runner, query services
53
- - Depends on: better-sqlite3
54
-
55
- **UI Layer:**
56
- - Purpose: React-based plugin UI for monitoring and control
57
- - Location: `ui/src/`
58
- - Contains: Pages (Overview, Evolution, Feedback, GateMonitor, Samples, ThinkingModels), components (Shell, ProtectedRoute), context (auth, theme)
59
- - Depends on: React, React Router, lucide-react
60
-
61
- **Utils:**
62
- - Purpose: Shared utilities (I/O, logging, retry, hashing, file locking)
63
- - Location: `src/utils/`
64
- - Contains: `io.ts` (atomic writes), `plugin-logger.ts`, `retry.ts`, `hashing.ts`, `file-lock.ts`
65
-
66
- ## Data Flow
67
-
68
- **Agent Execution Flow:**
69
- 1. OpenClaw fires `before_prompt_build` hook
70
- 2. `hooks/prompt.ts` builds context injection (principles, thinking OS, focus, reflection log)
71
- 3. OpenClaw generates response
72
- 4. `hooks/llm.ts` analyzes LLM output for signals
73
- 5. User triggers tool call
74
- 6. `hooks/gate.ts` intercepts `before_tool_call` - evaluates risk, may block
75
- 7. Tool executes
76
- 8. `hooks/pain.ts` intercepts `after_tool_call` - tracks pain signals, empathy penalties
77
-
78
- **Evolution Flow:**
79
- 1. `EvolutionWorkerService` polls `pain_candidates.json` periodically
80
- 2. For each candidate, `evolution-engine.ts` evaluates and derives improvements
81
- 3. `trajectory.ts` records behavior patterns
82
- 4. Nocturnal training (`nocturnal-trinity.ts`) synthesizes improvements into rule updates
83
- 5. `principle-tree-ledger.ts` manages principle lifecycle
84
-
85
- **Nocturnal Training Flow:**
86
- 1. Candidate scoring via `nocturnal-candidate-scoring.ts`
87
- 2. Target selection via `nocturnal-target-selector.ts`
88
- 3. Rule implementation validation via `nocturnal-rule-implementation-validator.ts`
89
- 4. Promotion via `promotion-gate.ts`
90
-
91
- ## Key Abstractions
92
-
93
- **WorkspaceContext:**
94
- - Purpose: Encapsulates all state and services for a single workspace
95
- - Examples: `src/core/workspace-context.ts`
96
- - Pattern: Factory method `WorkspaceContext.fromHookContext()`
97
-
98
- **EvolutionEngine:**
99
- - Purpose: Core evolution processing logic
100
- - Examples: `src/core/evolution-engine.ts`
101
- - Pattern: Reducer pattern with typed actions
102
-
103
- **NocturnalTrinity:**
104
- - Purpose: Three-phase nocturnal training (dataset, training, evaluation)
105
- - Examples: `src/core/nocturnal-trinity.ts`
106
- - Pattern: Phase orchestration with contract validation
107
-
108
- **PainConfig:**
109
- - Purpose: Plugin configuration management
110
- - Examples: `src/core/config.ts`
111
- - Pattern: Singleton per workspace with file persistence
112
-
113
- **RuleHost:**
114
- - Purpose: Secure rule execution environment
115
- - Examples: `src/core/rule-host.ts`
116
- - Pattern: Sandboxed evaluation with permission checks
117
-
118
- ## Entry Points
119
-
120
- **Plugin Entry:**
121
- - Location: `src/index.ts`
122
- - Triggers: OpenClaw loads plugin
123
- - Responsibilities: Register all hooks, commands, tools, services; initialize workspace
124
-
125
- **Command Entry:**
126
- - Location: `src/commands/*.ts`
127
- - Triggers: User invokes slash command (e.g., `/pd-init`)
128
- - Responsibilities: Validate input, delegate to core services, return formatted result
129
-
130
- **Service Entry:**
131
- - Location: `src/service/evolution-worker.ts`
132
- - Triggers: `before_prompt_build` fires (starts background worker)
133
- - Responsibilities: Process evolution queue, track pain signals, trigger training
134
-
135
- ## Error Handling
136
-
137
- **Strategy:** Graceful degradation with logging
138
-
139
- **Patterns:**
140
- - Try-catch blocks in all hook handlers with error logging
141
- - `SystemLogger.log()` for critical errors
142
- - Workspace context error recording via `eventLog.recordHookExecution()`
143
- - Non-critical errors caught silently (trajectory collection)
144
-
145
- ## Cross-Cutting Concerns
146
-
147
- **Logging:** `src/utils/plugin-logger.ts` - OpenClaw logger abstraction
148
-
149
- **Validation:** Schema validation via `@sinclair/typebox` for config and event types
150
-
151
- **Authentication:** OpenClaw session-based, agent ID extracted from session key
152
-
153
- **I/O Safety:** Atomic file writes via `atomicWriteFileSync()` in `src/utils/io.ts`
154
-
155
- ---
156
-
157
- *Architecture analysis: 2026-04-15*