principles-disciple 1.72.0 → 1.74.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 (319) hide show
  1. package/INSTALL.md +1 -3
  2. package/openclaw.plugin.json +10 -5
  3. package/package.json +17 -19
  4. package/scripts/acceptance-test.mjs +16 -73
  5. package/scripts/sync-plugin.mjs +382 -77
  6. package/src/commands/archive-impl.ts +2 -1
  7. package/src/commands/capabilities.ts +2 -2
  8. package/src/commands/context.ts +2 -2
  9. package/src/commands/disable-impl.ts +2 -1
  10. package/src/commands/evolution-status.ts +16 -16
  11. package/src/commands/export.ts +12 -67
  12. package/src/commands/pain.ts +91 -1
  13. package/src/commands/principle-rollback.ts +2 -1
  14. package/src/commands/promote-impl.ts +7 -43
  15. package/src/commands/rollback-impl.ts +2 -1
  16. package/src/commands/rollback.ts +2 -1
  17. package/src/commands/samples.ts +2 -1
  18. package/src/commands/thinking-os.ts +2 -1
  19. package/src/config/errors.ts +18 -2
  20. package/src/constants/diagnostician.ts +2 -2
  21. package/src/constants/tools.ts +2 -1
  22. package/src/core/__tests__/focus-history.test.ts +210 -0
  23. package/src/core/config.ts +1 -1
  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 +29 -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/migration.ts +0 -1
  37. package/src/core/pain-diagnostic-gate.ts +154 -0
  38. package/src/core/pain-signal.ts +21 -138
  39. package/src/core/pain.ts +15 -88
  40. package/src/core/path-resolver.ts +0 -1
  41. package/src/core/paths.ts +0 -1
  42. package/src/core/pd-task-reconciler.ts +26 -115
  43. package/src/core/pd-task-service.ts +9 -9
  44. package/src/core/pd-task-types.ts +23 -127
  45. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  46. package/src/core/principle-compiler/code-validator.ts +15 -42
  47. package/src/core/principle-compiler/compiler.ts +100 -15
  48. package/src/core/principle-compiler/index.ts +5 -2
  49. package/src/core/principle-compiler/template-generator.ts +4 -104
  50. package/src/core/principle-injection.ts +10 -202
  51. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  52. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  53. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  54. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  55. package/src/core/principle-tree-ledger.ts +8 -6
  56. package/src/core/reflection/reflection-context.ts +14 -109
  57. package/src/core/replay-engine.ts +8 -500
  58. package/src/core/rule-host-helpers.ts +5 -35
  59. package/src/core/rule-host-types.ts +10 -82
  60. package/src/core/rule-host.ts +6 -63
  61. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  62. package/src/core/session-tracker.ts +87 -101
  63. package/src/core/shadow-observation-registry.ts +19 -48
  64. package/src/core/trajectory.ts +3 -1
  65. package/src/core/workflow-funnel-loader.ts +62 -68
  66. package/src/core/workspace-context.ts +46 -0
  67. package/src/core/workspace-dir-service.ts +1 -1
  68. package/src/core/workspace-dir-validation.ts +18 -9
  69. package/src/hooks/AGENTS.md +1 -1
  70. package/src/hooks/gate-block-helper.ts +71 -64
  71. package/src/hooks/gate.ts +183 -31
  72. package/src/hooks/lifecycle.ts +30 -32
  73. package/src/hooks/llm.ts +60 -32
  74. package/src/hooks/pain.ts +297 -103
  75. package/src/hooks/prompt.ts +400 -440
  76. package/src/hooks/subagent.ts +2 -29
  77. package/src/i18n/commands.ts +2 -10
  78. package/src/index.ts +95 -85
  79. package/src/openclaw-sdk.ts +311 -0
  80. package/src/service/central-database.ts +8 -4
  81. package/src/service/evolution-queue-migration.ts +2 -1
  82. package/src/service/evolution-worker.ts +163 -1786
  83. package/src/service/internalization-trigger-adapter.ts +302 -0
  84. package/src/service/keyword-optimization-service.ts +4 -4
  85. package/src/service/monitoring-query-service.ts +1 -215
  86. package/src/service/queue-io.ts +60 -331
  87. package/src/service/runtime-summary-service.ts +59 -16
  88. package/src/service/subagent-workflow/index.ts +0 -41
  89. package/src/service/subagent-workflow/types.ts +9 -120
  90. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  91. package/src/service/workflow-watchdog.ts +0 -43
  92. package/src/types/event-payload.ts +16 -74
  93. package/src/types/event-types.ts +38 -547
  94. package/src/types/hygiene-types.ts +7 -30
  95. package/src/types/principle-tree-schema.ts +20 -222
  96. package/src/types/queue.ts +15 -70
  97. package/src/types/runtime-summary.ts +5 -49
  98. package/src/utils/io.ts +8 -20
  99. package/src/utils/retry.ts +1 -1
  100. package/src/utils/shadow-fingerprint.ts +2 -2
  101. package/src/utils/workspace-resolver.ts +50 -0
  102. package/templates/langs/en/core/AGENTS.md +7 -7
  103. package/templates/langs/en/core/BOOT.md +1 -1
  104. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  105. package/templates/langs/en/principles/THINKING_OS.md +3 -2
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  107. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  108. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  109. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  110. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  111. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  112. package/templates/langs/en/skills/evolve-task/SKILL.md +3 -3
  113. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  114. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  115. package/templates/langs/en/skills/pd-mentor/SKILL.md +2 -3
  116. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  117. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  118. package/templates/langs/zh/core/AGENTS.md +7 -7
  119. package/templates/langs/zh/core/BOOT.md +1 -1
  120. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  121. package/templates/langs/zh/principles/THINKING_OS.md +3 -2
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  127. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  128. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  129. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  130. package/templates/langs/zh/skills/evolve-task/SKILL.md +4 -4
  131. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  132. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  133. package/templates/langs/zh/skills/pd-mentor/SKILL.md +2 -3
  134. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  135. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  136. package/tests/build-artifacts.test.ts +1 -3
  137. package/tests/commands/evolution-status.test.ts +0 -118
  138. package/tests/core/bootstrap-rules.test.ts +1 -1
  139. package/tests/core/config.test.ts +1 -1
  140. package/tests/core/event-log.test.ts +35 -0
  141. package/tests/core/evolution-engine.test.ts +610 -0
  142. package/tests/core/file-store.test.ts +102 -0
  143. package/tests/core/focus-history.test.ts +203 -11
  144. package/tests/core/merge-gate-audit.test.ts +2 -169
  145. package/tests/core/migration.test.ts +7 -7
  146. package/tests/core/model-deployment-registry.test.ts +7 -1
  147. package/tests/core/model-training-registry.test.ts +19 -0
  148. package/tests/core/observability.test.ts +0 -1
  149. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  150. package/tests/core/pain.test.ts +0 -1
  151. package/tests/core/path-resolver.test.ts +1 -1
  152. package/tests/core/paths-refactor.test.ts +0 -22
  153. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  154. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  155. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  156. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  157. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  158. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  159. package/tests/core/reflection-context.test.ts +0 -14
  160. package/tests/core/replay-engine.test.ts +127 -215
  161. package/tests/core/rule-host-helpers.test.ts +2 -2
  162. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  163. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  164. package/tests/core/workspace-context.test.ts +2 -2
  165. package/tests/core/workspace-dir-validation.test.ts +8 -1
  166. package/tests/core-anti-growth.test.ts +191 -0
  167. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  168. package/tests/hooks/confirm-first-removal.test.ts +188 -0
  169. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  170. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  171. package/tests/hooks/gate-no-path-write-tool.test.ts +172 -0
  172. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  173. package/tests/hooks/pain.test.ts +269 -12
  174. package/tests/hooks/prompt-characterization.test.ts +500 -0
  175. package/tests/hooks/prompt-size-guard.test.ts +32 -17
  176. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  177. package/tests/index.test.ts +94 -1
  178. package/tests/integration/auto-entry-gate.test.ts +248 -0
  179. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  180. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  181. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  182. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  183. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  184. package/tests/service/central-database.test.ts +457 -0
  185. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  186. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  187. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  188. package/tests/service/monitoring-query-service.test.ts +1 -47
  189. package/tests/service/queue-io.test.ts +1 -62
  190. package/tests/service/runtime-summary-service.test.ts +3 -1
  191. package/tests/service/workflow-watchdog.test.ts +0 -91
  192. package/tests/utils/file-lock.test.ts +5 -3
  193. package/tests/utils/session-key.test.ts +52 -0
  194. package/tests/utils/subagent-probe.test.ts +48 -1
  195. package/vitest.config.ts +4 -11
  196. package/.planning/codebase/ARCHITECTURE.md +0 -157
  197. package/.planning/codebase/CONCERNS.md +0 -145
  198. package/.planning/codebase/CONVENTIONS.md +0 -148
  199. package/.planning/codebase/INTEGRATIONS.md +0 -81
  200. package/.planning/codebase/STACK.md +0 -87
  201. package/.planning/codebase/STRUCTURE.md +0 -193
  202. package/.planning/codebase/TESTING.md +0 -243
  203. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  204. package/docs/COMMAND_REFERENCE.md +0 -76
  205. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  206. package/scripts/build-web.mjs +0 -46
  207. package/scripts/diagnose-nocturnal.mjs +0 -537
  208. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  209. package/src/commands/nocturnal-review.ts +0 -322
  210. package/src/commands/nocturnal-rollout.ts +0 -790
  211. package/src/commands/nocturnal-train.ts +0 -986
  212. package/src/commands/pd-reflect.ts +0 -88
  213. package/src/core/adaptive-thresholds.ts +0 -478
  214. package/src/core/diagnostician-task-store.ts +0 -192
  215. package/src/core/nocturnal-arbiter.ts +0 -715
  216. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  217. package/src/core/nocturnal-artificer.ts +0 -257
  218. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  219. package/src/core/nocturnal-compliance.ts +0 -1146
  220. package/src/core/nocturnal-dataset.ts +0 -763
  221. package/src/core/nocturnal-executability.ts +0 -428
  222. package/src/core/nocturnal-export.ts +0 -499
  223. package/src/core/nocturnal-paths.ts +0 -240
  224. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  225. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  226. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  227. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  228. package/src/core/nocturnal-trinity-types.ts +0 -218
  229. package/src/core/nocturnal-trinity.ts +0 -2680
  230. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  231. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  232. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  233. package/src/http/principles-console-route.ts +0 -709
  234. package/src/service/central-health-service.ts +0 -49
  235. package/src/service/central-overview-service.ts +0 -138
  236. package/src/service/control-ui-query-service.ts +0 -900
  237. package/src/service/cooldown-strategy.ts +0 -97
  238. package/src/service/evolution-pain-context.ts +0 -79
  239. package/src/service/evolution-query-service.ts +0 -407
  240. package/src/service/health-query-service.ts +0 -1038
  241. package/src/service/nocturnal-config.ts +0 -214
  242. package/src/service/nocturnal-runtime.ts +0 -734
  243. package/src/service/nocturnal-service.ts +0 -1605
  244. package/src/service/nocturnal-target-selector.ts +0 -545
  245. package/src/service/sleep-cycle.ts +0 -157
  246. package/src/service/startup-reconciler.ts +0 -112
  247. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  248. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  249. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  250. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  251. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  252. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  253. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  254. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  255. package/src/tools/write-pain-flag.ts +0 -215
  256. package/templates/langs/en/skills/plan-script/SKILL.md +0 -32
  257. package/templates/langs/zh/skills/plan-script/SKILL.md +0 -32
  258. package/tests/commands/nocturnal-review.test.ts +0 -448
  259. package/tests/commands/nocturnal-train.test.ts +0 -97
  260. package/tests/commands/pd-reflect.test.ts +0 -49
  261. package/tests/core/adaptive-thresholds.test.ts +0 -261
  262. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  263. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  264. package/tests/core/nocturnal-artificer.test.ts +0 -241
  265. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  266. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  267. package/tests/core/nocturnal-compliance.test.ts +0 -646
  268. package/tests/core/nocturnal-dataset.test.ts +0 -892
  269. package/tests/core/nocturnal-e2e.test.ts +0 -234
  270. package/tests/core/nocturnal-executability.test.ts +0 -357
  271. package/tests/core/nocturnal-export.test.ts +0 -517
  272. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  273. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  274. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  275. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  276. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  277. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  278. package/tests/core/pain-auto-repair.test.ts +0 -96
  279. package/tests/core/pain-integration.test.ts +0 -510
  280. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  281. package/tests/http/principles-console-route.test.ts +0 -162
  282. package/tests/integration/chaos-resilience.test.ts +0 -348
  283. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  284. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  285. package/tests/service/control-ui-query-service.test.ts +0 -121
  286. package/tests/service/cooldown-strategy.test.ts +0 -164
  287. package/tests/service/data-endpoints-regression.test.ts +0 -834
  288. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  289. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  290. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  291. package/tests/service/nocturnal-runtime.test.ts +0 -473
  292. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  293. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  294. package/tests/service/startup-reconciler.test.ts +0 -148
  295. package/tests/tools/write-pain-flag.test.ts +0 -358
  296. package/ui/src/App.tsx +0 -45
  297. package/ui/src/api.ts +0 -220
  298. package/ui/src/charts.tsx +0 -955
  299. package/ui/src/components/ErrorState.tsx +0 -6
  300. package/ui/src/components/Loading.tsx +0 -13
  301. package/ui/src/components/ProtectedRoute.tsx +0 -12
  302. package/ui/src/components/Shell.tsx +0 -91
  303. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  304. package/ui/src/components/index.ts +0 -5
  305. package/ui/src/context/auth.tsx +0 -80
  306. package/ui/src/context/theme.tsx +0 -66
  307. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  308. package/ui/src/i18n/ui.ts +0 -473
  309. package/ui/src/main.tsx +0 -16
  310. package/ui/src/pages/EvolutionPage.tsx +0 -333
  311. package/ui/src/pages/FeedbackPage.tsx +0 -138
  312. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  313. package/ui/src/pages/LoginPage.tsx +0 -89
  314. package/ui/src/pages/OverviewPage.tsx +0 -599
  315. package/ui/src/pages/SamplesPage.tsx +0 -174
  316. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  317. package/ui/src/styles.css +0 -2020
  318. package/ui/src/types.ts +0 -384
  319. package/ui/src/utils/format.ts +0 -15
@@ -1,545 +0,0 @@
1
- /**
2
- * Nocturnal Target Selector — Principle + Session Selection for Sleep-Mode Reflection
3
- * ================================================================================
4
- *
5
- * PURPOSE: Select one target principle and one violating session for nocturnal
6
- * reflection processing. This module is a PURE SELECTOR — it does NOT execute
7
- * reflection or write any artifacts.
8
- *
9
- * SELECTION LOGIC:
10
- * 1. Filter principles to evaluable candidates (not manual_only, not cooldown)
11
- * 2. Score candidates by: compliance health, violation trend, sample scarcity
12
- * 3. Select top principle
13
- * 4. Find violating sessions for that principle
14
- * 5. Select best violating session by violation density
15
- *
16
- * SKIP CONDITIONS (return no-op cleanly):
17
- * - no_evaluable_principles: No principles with auto-trainable evaluability
18
- * - all_targets_in_cooldown: All candidates are in cooldown
19
- * - no_violating_sessions: No sessions show violation signals for selected principle
20
- * - workspace_not_idle: Workspace is active, nocturnal run not allowed
21
- * - quota_exhausted: Max runs per window reached
22
- * - insufficient_snapshot_data: Sessions exist but lack tool calls
23
- *
24
- * DESIGN CONSTRAINTS:
25
- * - This is a PURE SELECTOR — no reflection execution
26
- * - No raw text exposure
27
- * - No artifact writing
28
- * - No真实 subagent 调用
29
- * - Deterministic based on inputs (no random selection)
30
- *
31
- * OUTPUT: NocturnalSelectionResult with:
32
- * - decision: 'selected' | 'skip'
33
- * - selectedPrincipleId?: string
34
- * - selectedSessionId?: string
35
- * - skipReason?: SkipReason
36
- * - diagnostics: scoring and filtering details
37
- */
38
-
39
- import type {
40
- NocturnalTrajectoryExtractor,
41
- NocturnalSessionSummary,
42
- } from '../core/nocturnal-trajectory-extractor.js';
43
- import {
44
- listEvaluablePrinciples,
45
- type PrincipleTrainingState,
46
- } from '../core/principle-training-state.js';
47
- import {
48
- checkWorkspaceIdle,
49
- checkCooldown,
50
- type IdleCheckResult,
51
- } from './nocturnal-runtime.js';
52
- import { DEFAULT_IDLE_THRESHOLD_MS } from '../config/defaults/runtime.js';
53
- import { detectViolation } from '../core/nocturnal-compliance.js';
54
-
55
- // ---------------------------------------------------------------------------
56
- // Types
57
- // ---------------------------------------------------------------------------
58
-
59
- export type SkipReason =
60
- | 'no_evaluable_principles' // No principles with evaluability !== manual_only
61
- | 'all_targets_in_cooldown' // All candidates are in per-principle cooldown
62
- | 'no_violating_sessions' // No sessions show violation signals for selected principle
63
- | 'workspace_not_idle' // Workspace is active, nocturnal not allowed
64
- | 'quota_exhausted' // Max runs per quota window reached
65
- | 'insufficient_snapshot_data' // Sessions exist but lack tool calls / events
66
- | 'global_cooldown_active' // Global cooldown is in effect
67
- | 'preflight_blocked'; // Preflight check blocked (idle/cooldown/quota)
68
-
69
- export interface SelectionDiagnostics {
70
- /** Total evaluable principles found */
71
- totalEvaluablePrinciples: number;
72
- /** Principles filtered out by cooldown */
73
- filteredByCooldown: number;
74
- /** Principles that passed all filters */
75
- passedPrinciples: string[];
76
- /** Violating sessions found for selected principle */
77
- violatingSessionCount: number;
78
- /** Violation density of selected session (failures / total tool calls) */
79
- selectedSessionViolationDensity: number | null;
80
- /** Score of selected principle */
81
- selectedPrincipleScore: number | null;
82
- /** Score breakdown */
83
- scoringBreakdown: Record<string, number>;
84
- /** Whether workspace idle check passed */
85
- idleCheckPassed: boolean;
86
- /** Whether cooldown check passed */
87
- cooldownCheckPassed: boolean;
88
- /** Whether quota check passed */
89
- quotaCheckPassed: boolean;
90
- /** Recent pain context used for ranking bias (if available) */
91
- painContext?: {
92
- recentPainCount: number;
93
- recentMaxPainScore: number;
94
- hasRecentPain: boolean;
95
- painSource?: string;
96
- };
97
- }
98
-
99
- export interface NocturnalSelectionResult {
100
- /** Whether a target was selected */
101
- decision: 'selected' | 'skip';
102
- /** Selected principle ID (if decision === 'selected') */
103
- selectedPrincipleId?: string;
104
- /** Selected violating session ID (if decision === 'selected') */
105
- selectedSessionId?: string;
106
- /** Reason for skip (if decision === 'skip') */
107
- skipReason?: SkipReason;
108
- /** Detailed diagnostics for observability */
109
- diagnostics: SelectionDiagnostics;
110
- }
111
-
112
- // ---------------------------------------------------------------------------
113
- // Scoring
114
- // ---------------------------------------------------------------------------
115
-
116
- interface ScoredPrinciple {
117
- principleId: string;
118
- state: PrincipleTrainingState;
119
- score: number;
120
- violationDensity: number; // violations / applicable opportunities
121
- cooldownActive: boolean;
122
- }
123
-
124
- interface ViolationSignal {
125
- sessionId: string;
126
- hasViolation: boolean;
127
- violationSeverity: number; // 0-1
128
- failureCount: number;
129
- painEventCount: number;
130
- gateBlockCount: number;
131
- toolCallCount: number;
132
- }
133
-
134
- /**
135
- * Score a principle for nocturnal targeting.
136
- * Higher score = better candidate for reflection.
137
- *
138
- * Scoring dimensions:
139
- * 1. Compliance health (higher compliance = more worth teaching)
140
- * 2. Violation trend (worsening trend = higher priority)
141
- * 3. Sample scarcity (fewer samples = more valuable)
142
- * 4. Cooldown penalty (in cooldown = lower priority)
143
- * 5. Pain context bias (recent pain = higher priority for related principles)
144
- *
145
- * Score range: 0-100 (normalized)
146
- */
147
- function scorePrinciple(
148
- state: PrincipleTrainingState,
149
- cooldownActive: boolean,
150
- recentPainContext?: {
151
- mostRecent: { score: number; source: string; reason: string; timestamp: string } | null;
152
- recentPainCount: number;
153
- recentMaxPainScore: number;
154
- }
155
- ): number {
156
- let score = 50; // Base score
157
-
158
- // Compliance contribution: 0-25 points
159
- // High compliance = well-understood principle = good teaching target
160
- // But very low compliance = confused principle = also needs work
161
- const complianceContribution = Math.round(state.complianceRate * 25);
162
- score += complianceContribution;
163
-
164
- // Trend contribution: -10 to +10 points
165
- // worsening trend (+1) = higher priority = +10
166
- // improving trend (-1) = lower priority = -10
167
- // stable (0) = neutral = 0
168
- score += state.violationTrend * 10;
169
-
170
- // Sample scarcity contribution: 0-15 points
171
- // Fewer samples generated = more valuable new data
172
- // Max at 0 samples, decreases as samples accumulate
173
- const scarcityPenalty = Math.min(state.generatedSampleCount / 10, 1) * 15;
174
- score += Math.round(15 - scarcityPenalty);
175
-
176
- // Cooldown penalty: -30 points
177
- if (cooldownActive) {
178
- score -= 30;
179
- }
180
-
181
- // Pain context bias: up to +25 points
182
- // Bias toward principles related to recent pain signals
183
- if (recentPainContext && recentPainContext.recentPainCount > 0) {
184
- // Most recent pain score contributes up to 15 points (pain scores are 1-10)
185
- const mostRecentPainScore = recentPainContext.mostRecent?.score ?? 0;
186
- const painScoreContribution = Math.round((mostRecentPainScore / 10) * 15);
187
- score += painScoreContribution;
188
-
189
- // Additional pain count adds up to 5 more points
190
- const additionalPainContribution = Math.min(recentPainContext.recentPainCount - 1, 5) * 1;
191
- score += additionalPainContribution;
192
-
193
- // High pain scores (> 7) get extra 5-point boost
194
- if (recentPainContext.recentMaxPainScore > 7) {
195
- score += 5;
196
- }
197
- }
198
-
199
- return Math.max(0, Math.min(100, score));
200
- }
201
-
202
- /**
203
- * Compute violation signal density for a session.
204
- * Returns 0-1 score (higher = more violation evidence).
205
- */
206
- function computeViolationDensity(session: NocturnalSessionSummary): number {
207
- const total = session.toolCallCount;
208
- if (total === 0) return 0;
209
-
210
- // Violation signals: failures, pain events, gate blocks
211
- const violationSignals =
212
- session.failureCount + session.painEventCount * 0.5 + session.gateBlockCount * 0.3;
213
-
214
- return Math.min(violationSignals / total, 1);
215
- }
216
-
217
- // ---------------------------------------------------------------------------
218
- // Core Selector
219
- // ---------------------------------------------------------------------------
220
-
221
- export interface NocturnalTargetSelectorOptions {
222
- /**
223
- * Minimum violation density threshold for a session to be considered "violating".
224
- * Default: 0.1 (at least 10% of tool calls are violation signals)
225
- */
226
- minViolationDensity?: number;
227
-
228
- /**
229
- * Maximum number of recent sessions to consider for violation matching.
230
- * Default: 50
231
- */
232
- maxSessionCandidates?: number;
233
-
234
- /**
235
- * Idle threshold override (ms).
236
- * Default: from nocturnal-runtime DEFAULT_IDLE_THRESHOLD_MS
237
- */
238
- idleThresholdMs?: number;
239
-
240
- /**
241
- * Override idle check result (for testing).
242
- * If provided, this result is used instead of calling checkWorkspaceIdle.
243
- */
244
- idleCheckOverride?: IdleCheckResult;
245
-
246
- /**
247
- * Recent pain context from evolution worker.
248
- * When provided, principles related to recent pain signals get ranking bias.
249
- * This threads recent pain into sleep_reflection targeting without merging task kinds.
250
- */
251
- recentPainContext?: {
252
- mostRecent: {
253
- score: number;
254
- source: string;
255
- reason: string;
256
- timestamp: string;
257
- } | null;
258
- recentPainCount: number;
259
- recentMaxPainScore: number;
260
- };
261
- }
262
-
263
- /**
264
- * Nocturnal Target Selector.
265
- *
266
- * Selects one target principle and one violating session for nocturnal reflection.
267
- * This is a PURE FUNCTION — no side effects, no artifact writing.
268
- */
269
- export class NocturnalTargetSelector {
270
- private readonly extractor: NocturnalTrajectoryExtractor;
271
- private readonly stateDir: string;
272
- private readonly workspaceDir: string;
273
- private readonly opts: {
274
- minViolationDensity: number;
275
- maxSessionCandidates: number;
276
- idleThresholdMs: number;
277
- };
278
- private readonly idleCheckOverride?: IdleCheckResult;
279
- private readonly recentPainContext?: {
280
- mostRecent: {
281
- score: number;
282
- source: string;
283
- reason: string;
284
- timestamp: string;
285
- } | null;
286
- recentPainCount: number;
287
- recentMaxPainScore: number;
288
- };
289
-
290
-
291
-
292
- constructor(
293
- workspaceDir: string,
294
- stateDir: string,
295
- extractor: NocturnalTrajectoryExtractor,
296
- options: NocturnalTargetSelectorOptions = {}
297
- ) {
298
- this.workspaceDir = workspaceDir;
299
- this.stateDir = stateDir;
300
- this.extractor = extractor;
301
- // Destructure so they are NOT included in opts (stored separately)
302
- const { idleCheckOverride, recentPainContext, ...restOptions } = options;
303
- this.idleCheckOverride = idleCheckOverride;
304
- this.recentPainContext = recentPainContext;
305
- this.opts = {
306
- minViolationDensity: restOptions.minViolationDensity ?? 0.1,
307
- maxSessionCandidates: restOptions.maxSessionCandidates ?? 300,
308
- idleThresholdMs: restOptions.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS,
309
- };
310
- }
311
-
312
- /**
313
- * Select a target principle and violating session.
314
- *
315
- * @returns NocturnalSelectionResult with either selected targets or skip reason
316
- */
317
- select(): NocturnalSelectionResult {
318
- const diagnostics: SelectionDiagnostics = {
319
- totalEvaluablePrinciples: 0,
320
- filteredByCooldown: 0,
321
- passedPrinciples: [],
322
- violatingSessionCount: 0,
323
- selectedSessionViolationDensity: null,
324
- selectedPrincipleScore: null,
325
- scoringBreakdown: {},
326
- idleCheckPassed: false,
327
- cooldownCheckPassed: false,
328
- quotaCheckPassed: false,
329
- painContext: this.recentPainContext ? {
330
- recentPainCount: this.recentPainContext.recentPainCount,
331
- recentMaxPainScore: this.recentPainContext.recentMaxPainScore,
332
- hasRecentPain: this.recentPainContext.recentPainCount > 0,
333
- painSource: this.recentPainContext.mostRecent?.source,
334
- } : undefined,
335
- };
336
-
337
- // Step 1: Idle check — if workspace is not idle, skip
338
- const idleResult = this.idleCheckOverride ?? checkWorkspaceIdle(this.workspaceDir, {
339
- idleThresholdMs: this.opts.idleThresholdMs,
340
- });
341
- diagnostics.idleCheckPassed = idleResult.isIdle;
342
-
343
- if (!idleResult.isIdle) {
344
- return {
345
- decision: 'skip',
346
- skipReason: 'workspace_not_idle',
347
- diagnostics,
348
- };
349
- }
350
-
351
- // Step 2: Cooldown and quota check
352
- // #256: Skip cooldown/quota for manual/test triggers (idleCheckOverride present)
353
- const skipGates = !!this.idleCheckOverride;
354
- const cooldownResult = checkCooldown(this.stateDir);
355
- diagnostics.cooldownCheckPassed = !cooldownResult.globalCooldownActive;
356
- diagnostics.quotaCheckPassed = !cooldownResult.quotaExhausted;
357
-
358
- if (!skipGates && cooldownResult.globalCooldownActive) {
359
- return {
360
- decision: 'skip',
361
- skipReason: 'global_cooldown_active',
362
- diagnostics,
363
- };
364
- }
365
-
366
- if (!skipGates && cooldownResult.quotaExhausted) {
367
- return {
368
- decision: 'skip',
369
- skipReason: 'quota_exhausted',
370
- diagnostics,
371
- };
372
- }
373
-
374
- // Step 3: Get evaluable principles
375
- const evaluablePrinciples = listEvaluablePrinciples(this.stateDir);
376
- diagnostics.totalEvaluablePrinciples = evaluablePrinciples.length;
377
-
378
- if (evaluablePrinciples.length === 0) {
379
- return {
380
- decision: 'skip',
381
- skipReason: 'no_evaluable_principles',
382
- diagnostics,
383
- };
384
- }
385
-
386
- // Step 4: Score and filter candidates
387
- const scoredPrinciples: ScoredPrinciple[] = [];
388
-
389
- for (const state of evaluablePrinciples) {
390
- const cooldownCheck = checkCooldown(this.stateDir, state.principleId);
391
- const cooldownActive = cooldownCheck.principleCooldownActive;
392
- const score = scorePrinciple(state, cooldownActive, this.recentPainContext);
393
-
394
- if (cooldownActive) {
395
- diagnostics.filteredByCooldown++;
396
- }
397
-
398
- scoredPrinciples.push({
399
- principleId: state.principleId,
400
- state,
401
- score,
402
- violationDensity:
403
- state.applicableOpportunityCount > 0
404
- ? state.observedViolationCount / state.applicableOpportunityCount
405
- : 0,
406
- cooldownActive,
407
- });
408
-
409
- diagnostics.scoringBreakdown[state.principleId] = score;
410
- }
411
-
412
- // Filter out principles in cooldown (score already penalized but keep them in diagnostics)
413
- const activeCandidates = scoredPrinciples.filter((p) => !p.cooldownActive);
414
-
415
- if (activeCandidates.length === 0) {
416
- diagnostics.passedPrinciples = [];
417
- return {
418
- decision: 'skip',
419
- skipReason: 'all_targets_in_cooldown',
420
- diagnostics,
421
- };
422
- }
423
-
424
- // Sort by score descending
425
- activeCandidates.sort((a, b) => b.score - a.score);
426
- diagnostics.passedPrinciples = activeCandidates.map((p) => p.principleId);
427
-
428
- // Select the top candidate
429
- const [selected] = activeCandidates;
430
- diagnostics.selectedPrincipleScore = selected.score;
431
-
432
- // Step 5: Find violating sessions for the selected principle
433
- const recentSessions = this.extractor.listRecentNocturnalCandidateSessions({
434
- limit: this.opts.maxSessionCandidates,
435
- minToolCalls: 1,
436
- });
437
-
438
- if (recentSessions.length === 0) {
439
- return {
440
- decision: 'skip',
441
- skipReason: 'insufficient_snapshot_data',
442
- diagnostics,
443
- };
444
- }
445
-
446
- // Compute violation signals for each session
447
- // #256: Lowered from 2 to 1 — most sessions have exactly 1 failure + 1 pain event
448
- // which is a high-quality candidate. Requiring >= 2 excluded the majority of meaningful sessions.
449
- const MIN_VIOLATION_DEPTH = 1;
450
- const richSessions = recentSessions.filter(
451
- s => (s.failureCount ?? 0) + (s.painEventCount ?? 0) + (s.gateBlockCount ?? 0) >= MIN_VIOLATION_DEPTH
452
- );
453
-
454
- const violatingSessions: ViolationSignal[] = richSessions.map((session) => {
455
- const violationDensity = computeViolationDensity(session);
456
- const snapshot = this.extractor.getNocturnalSessionSnapshot(session.sessionId);
457
-
458
- // Use nocturnal-compliance to detect violations for the selected principle
459
- let hasViolation = false;
460
- if (snapshot) {
461
- const violationResult = detectViolation(selected.principleId, {
462
- sessionId: session.sessionId,
463
- toolCalls: snapshot.toolCalls.map((tc) => ({
464
- toolName: tc.toolName,
465
- filePath: tc.filePath ?? undefined,
466
- outcome: tc.outcome,
467
- errorMessage: tc.errorMessage ?? undefined,
468
- })),
469
- painSignals: snapshot.painEvents.map((pe) => ({
470
- source: pe.source,
471
- score: pe.score,
472
- reason: pe.reason ?? undefined,
473
- })),
474
- gateBlocks: snapshot.gateBlocks.map((gb) => ({
475
- toolName: gb.toolName,
476
- reason: gb.reason,
477
- })),
478
- // #268: Use actual correction samples from snapshot instead of empty array
479
- userCorrections: snapshot.userCorrections.map((uc) => ({
480
- correctionCue: uc.correctionCue ?? undefined,
481
- })),
482
- planApprovals: [],
483
- });
484
- hasViolation = violationResult.violated;
485
- }
486
-
487
- return {
488
- sessionId: session.sessionId,
489
- hasViolation,
490
- violationSeverity: violationDensity,
491
- failureCount: session.failureCount,
492
- painEventCount: session.painEventCount,
493
- gateBlockCount: session.gateBlockCount,
494
- toolCallCount: session.toolCallCount,
495
- };
496
- });
497
-
498
- // Filter to sessions with violations above threshold
499
- const violating = violatingSessions.filter(
500
- (s) => s.hasViolation && s.violationSeverity >= this.opts.minViolationDensity
501
- );
502
- diagnostics.violatingSessionCount = violating.length;
503
-
504
- if (violating.length === 0) {
505
- return {
506
- decision: 'skip',
507
- skipReason: 'no_violating_sessions',
508
- diagnostics,
509
- };
510
- }
511
-
512
- // Sort by violation severity descending (most violating first)
513
- violating.sort((a, b) => b.violationSeverity - a.violationSeverity);
514
- const [selectedSession] = violating;
515
- diagnostics.selectedSessionViolationDensity = selectedSession.violationSeverity;
516
-
517
- return {
518
- decision: 'selected',
519
- selectedPrincipleId: selected.principleId,
520
- selectedSessionId: selectedSession.sessionId,
521
- diagnostics,
522
- };
523
- }
524
- }
525
-
526
- // ---------------------------------------------------------------------------
527
- // Convenience function
528
- // ---------------------------------------------------------------------------
529
-
530
- /**
531
- * Select a nocturnal target (principle + session) with default options.
532
- *
533
- * This is a convenience wrapper for the common case.
534
- */
535
-
536
-
537
- export function selectNocturnalTarget(
538
- workspaceDir: string,
539
- stateDir: string,
540
- extractor: NocturnalTrajectoryExtractor,
541
- options: NocturnalTargetSelectorOptions = {}
542
- ): NocturnalSelectionResult {
543
- const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, options);
544
- return selector.select();
545
- }
@@ -1,157 +0,0 @@
1
- /**
2
- * Sleep Cycle Orchestrator — extracted from evolution-worker.ts
3
- *
4
- * Responsibilities:
5
- * - Idle workspace detection via nocturnal-runtime.js
6
- * - Cooldown enforcement via nocturnal-runtime.js
7
- * - Sleep reflection task enqueue orchestration (fire-and-forget)
8
- * - Keyword optimization task enqueue orchestration (fire-and-forget)
9
- * - Cycle heartbeat tracking and periodic trigger reset
10
- *
11
- * Does NOT include (remain in evolution-worker.ts facade):
12
- * - checkPainFlag, processEvolutionQueueWithResult, processDetectionQueue
13
- * - Workflow managers (EmpathyObserver, DeepReflect, Nocturnal)
14
- * - Workflow watchdog (runWorkflowWatchdog)
15
- * - Pain-flag-triggered immediate heartbeat
16
- *
17
- * Dependencies: nocturnal-runtime.js, nocturnal-config.js, queue-io.js
18
- * Zero imports from evolution-worker.ts.
19
- */
20
-
21
- import type { WorkspaceContext } from '../core/workspace-context.js';
22
- import type { OpenClawPluginApi, PluginLogger } from '../openclaw-sdk.js';
23
- import type { EventLog } from '../core/event-log.js';
24
- import { checkWorkspaceIdle, checkCooldown } from './nocturnal-runtime.js';
25
- import { loadNocturnalConfigMerged } from './nocturnal-config.js';
26
- import { enqueueSleepReflectionTask, enqueueKeywordOptimizationTask } from './queue-io.js';
27
-
28
- // ---------------------------------------------------------------------------
29
- // Types
30
- // ---------------------------------------------------------------------------
31
-
32
- export interface WorkerStatusReport {
33
- timestamp: string;
34
- cycle_start_ms: number;
35
- duration_ms: number;
36
- pain_flag: { exists: boolean; score: number | null; source: string | null; enqueued: boolean; skipped_reason: string | null };
37
- queue: { total: number; pending: number; in_progress: number; completed_this_cycle: number; failed_this_cycle: number };
38
- errors: string[];
39
- }
40
-
41
- export interface CycleOptions {
42
- wctx: WorkspaceContext;
43
- logger: PluginLogger | undefined;
44
- eventLog: EventLog;
45
- api: OpenClawPluginApi | undefined;
46
- /** Mutable ref to the heartbeat counter — incremented by runCycle on each call */
47
- heartbeatCounterRef: { value: number };
48
- }
49
-
50
- // ---------------------------------------------------------------------------
51
- // Cycle Orchestrator
52
- // ---------------------------------------------------------------------------
53
-
54
- /**
55
- * Execute one sleep-cycle heartbeat.
56
- *
57
- * Orchestrates:
58
- * 1. Load merged nocturnal config
59
- * 2. Check workspace idle state
60
- * 3. Enqueue keyword_optimization task (independent periodic trigger)
61
- * 4. Enqueue sleep_reflection task (idle-based OR periodic trigger + cooldown gate)
62
- * 5. Cycle result reporting
63
- *
64
- * Does NOT directly call checkPainFlag, processEvolutionQueueWithResult, or
65
- * processDetectionQueue — those remain in the evolution-worker.ts facade.
66
- *
67
- * @param options.wctx — workspace context
68
- * @param options.logger — plugin logger
69
- * @param options.eventLog — event log
70
- * @param options.api — OpenClaw plugin API (optional)
71
- * @param options.heartbeatCounterRef — mutable counter, incremented by runCycle
72
- */
73
- export async function runCycle(options: CycleOptions): Promise<WorkerStatusReport> {
74
- const { wctx, logger, api: _api, heartbeatCounterRef } = options;
75
- const cycleStart = Date.now();
76
- heartbeatCounterRef.value++;
77
-
78
- // ──── DEBUG: Verify subagent availability in heartbeat context ────
79
- const hbSubagent = _api?.runtime?.subagent;
80
- logger?.info?.(`[PD:DEBUG:SubagentCheck:Heartbeat] api_exists=${!!_api}, subagent_exists=${!!hbSubagent}, subagent.run_exists=${!!hbSubagent?.run}, heartbeatCounter=${heartbeatCounterRef.value}`);
81
- if (hbSubagent?.run) {
82
- logger?.info?.('[PD:DEBUG:SubagentCheck:Heartbeat] run entrypoint is callable');
83
- }
84
-
85
- const cycleResult: WorkerStatusReport = {
86
- timestamp: new Date().toISOString(),
87
- cycle_start_ms: cycleStart,
88
- duration_ms: 0,
89
- pain_flag: { exists: false, score: null, source: null, enqueued: false, skipped_reason: null },
90
- queue: { total: 0, pending: 0, in_progress: 0, completed_this_cycle: 0, failed_this_cycle: 0 },
91
- errors: [],
92
- };
93
-
94
- try {
95
- // Load config on each cycle (supports runtime updates) — single file read
96
- const mergedConfig = loadNocturnalConfigMerged(wctx.stateDir);
97
- const { sleepReflection: sleepConfig, keywordOptimization: kwOptConfig } = mergedConfig;
98
-
99
- const idleResult = checkWorkspaceIdle(wctx.workspaceDir, {});
100
- logger?.info?.(`[PD:EvolutionWorker] HEARTBEAT cycle=${new Date().toISOString()} idle=${idleResult.isIdle} idleForMs=${idleResult.idleForMs} userActiveSessions=${idleResult.userActiveSessions} abandonedSessions=${idleResult.abandonedSessionIds.length} lastActivityEpoch=${idleResult.mostRecentActivityAt} triggerMode=${sleepConfig.trigger_mode}`);
101
-
102
- let shouldTrySleepReflection = false;
103
-
104
- // Path 1: Idle-based trigger (default mode)
105
- if (idleResult.isIdle && sleepConfig.trigger_mode === 'idle') {
106
- logger?.info?.(`[PD:EvolutionWorker] Workspace idle (${idleResult.idleForMs}ms since last activity)`);
107
- shouldTrySleepReflection = true;
108
- }
109
-
110
- // keyword_optimization: Independent periodic trigger (CORR-07).
111
- // Fires every kwOptConfig.period_heartbeats regardless of trigger_mode.
112
- // Has its own dedicated config (default 24 heartbeats = 6 hours).
113
- if (kwOptConfig.enabled && heartbeatCounterRef.value > 0 && heartbeatCounterRef.value % kwOptConfig.period_heartbeats === 0) {
114
- logger?.info?.(`[PD:EvolutionWorker] keyword_optimization trigger at heartbeat ${heartbeatCounterRef.value} (trigger_mode=${sleepConfig.trigger_mode})`);
115
- enqueueKeywordOptimizationTask(wctx, logger).catch((err) => {
116
- logger?.error?.(`[PD:EvolutionWorker] Failed to enqueue keyword_optimization task: ${String(err)}`);
117
- });
118
- }
119
-
120
- // Path 2: Periodic trigger for sleep_reflection (fires regardless of idle state)
121
- if (sleepConfig.trigger_mode === 'periodic') {
122
- if (heartbeatCounterRef.value >= sleepConfig.period_heartbeats) {
123
- logger?.info?.(`[PD:EvolutionWorker] Periodic trigger: heartbeatCounter=${heartbeatCounterRef.value} >= period_heartbeats=${sleepConfig.period_heartbeats}`);
124
- shouldTrySleepReflection = true;
125
- heartbeatCounterRef.value = 0; // Reset counter
126
- } else {
127
- logger?.info?.(`[PD:EvolutionWorker] Periodic: ${heartbeatCounterRef.value}/${sleepConfig.period_heartbeats} heartbeats — waiting`);
128
- }
129
- }
130
-
131
- if (shouldTrySleepReflection) {
132
- const cooldown = checkCooldown(wctx.stateDir, undefined, {
133
- globalCooldownMs: sleepConfig.cooldown_ms,
134
- maxRunsPerWindow: sleepConfig.max_runs_per_day,
135
- quotaWindowMs: 24 * 60 * 60 * 1000,
136
- });
137
- logger?.info?.(`[PD:EvolutionWorker] Cooldown check: globalCooldownActive=${cooldown.globalCooldownActive} quotaExhausted=${cooldown.quotaExhausted} runsRemaining=${cooldown.runsRemaining}`);
138
- if (!cooldown.globalCooldownActive && !cooldown.quotaExhausted) {
139
- logger?.info?.('[PD:EvolutionWorker] Attempting to enqueue sleep_reflection task...');
140
- enqueueSleepReflectionTask(wctx, logger).catch((err) => {
141
- logger?.error?.(`[PD:EvolutionWorker] Failed to enqueue sleep_reflection task: ${String(err)}`);
142
- });
143
- } else {
144
- logger?.info?.(`[PD:EvolutionWorker] Skipping sleep_reflection: globalCooldown=${cooldown.globalCooldownActive} quotaExhausted=${cooldown.quotaExhausted}`);
145
- }
146
- }
147
-
148
- cycleResult.duration_ms = Date.now() - cycleStart;
149
- } catch (err) {
150
- const errMsg = `Error in runCycle: ${String(err)}`;
151
- if (logger) logger.error(`[PD:EvolutionWorker] ${errMsg}`);
152
- cycleResult.errors.push(errMsg);
153
- cycleResult.duration_ms = Date.now() - cycleStart;
154
- }
155
-
156
- return cycleResult;
157
- }