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
@@ -1,215 +0,0 @@
1
- import type { OpenClawPluginApi } from '../openclaw-sdk.js';
2
- import { Type } from '@sinclair/typebox';
3
- import { buildPainFlag } from '../core/pain.js';
4
- import { resolveWorkspaceDirFromApi } from '../core/path-resolver.js';
5
- import { TrajectoryRegistry } from '../core/trajectory.js';
6
- import * as fs from 'fs';
7
- import * as path from 'path';
8
- import { atomicWriteFileSync } from '../utils/io.js';
9
-
10
- // Pain flag contract required fields
11
- const PAIN_FLAG_REQUIRED_FIELDS = ['source', 'score', 'time', 'reason'] as const;
12
-
13
- /**
14
- * Atomic file write: write to temp file then rename.
15
- * Prevents corruption if process crashes mid-write.
16
- */
17
- function writePainFlagAtomic(filePath: string, content: string): void {
18
- const dir = path.dirname(filePath);
19
- if (!fs.existsSync(dir)) {
20
- fs.mkdirSync(dir, { recursive: true });
21
- }
22
- atomicWriteFileSync(filePath, content);
23
- }
24
-
25
- /**
26
- * Creates the `write_pain_flag` tool.
27
- *
28
- * This tool allows the agent to record a pain signal when it recognizes
29
- * that it made a mistake, violated a principle, or needs to flag an issue
30
- * for later reflection.
31
- *
32
- * The tool wraps `buildPainFlag` + atomic `writePainFlag` to ensure:
33
- * - Correct KV format serialization (never [object Object] corruption)
34
- * - Atomic writes (temp file + rename, crash-safe)
35
- * - Full contract compliance (source, score, time, reason)
36
- *
37
- * The agent should NEVER write to .pain_flag directly.
38
- */
39
- export function createWritePainFlagTool(api: OpenClawPluginApi) {
40
- return {
41
- name: 'write_pain_flag',
42
- description:
43
- 'Record a pain signal to flag mistakes, principle violations, or issues for later reflection. ' +
44
- 'Use this tool INSTEAD of writing .pain_flag directly. ' +
45
- 'Pain signals are processed by the evolution system on the next heartbeat cycle.',
46
- parameters: Type.Object({
47
- reason: Type.String({
48
- description:
49
- 'Describe specifically what went wrong. ' +
50
- 'Include the error, the violated principle, or the issue. ' +
51
- 'Be concrete: "I edited config.ts without reading it first, breaking the export" ' +
52
- 'is better than "I made a mistake".',
53
- }),
54
- score: Type.Optional(Type.Number({
55
- description:
56
- 'Pain severity score (0-100). Default: 80. ' +
57
- 'Guidelines: 30-50 (minor issue), 50-70 (moderate error), ' +
58
- '70-100 (severe principle violation or data loss risk).',
59
- minimum: 0,
60
- maximum: 100,
61
- })),
62
- source: Type.Optional(Type.String({
63
- description:
64
- 'Source of the pain signal. ' +
65
- 'Values: manual (user flagged), tool_failure (tool error), ' +
66
- 'user_empathy (user frustration), principle_violation (principle broken), ' +
67
- 'human_intervention (user manually intervened). ' +
68
- 'Default: manual.',
69
- })),
70
- session_id: Type.Optional(Type.String({
71
- description:
72
- 'Session ID where the pain occurred. ' +
73
- 'If not provided, the system will use the current session.',
74
- })),
75
- is_risky: Type.Optional(Type.Boolean({
76
- description:
77
- 'Whether this involves a high-risk operation (e.g., writing to sensitive files). ' +
78
- 'Default: false.',
79
- })),
80
- }),
81
-
82
- async execute(
83
- _toolCallId: string,
84
- rawParams: Record<string, unknown>
85
- ): Promise<{ content: { type: string; text: string }[] }> {
86
- const reason = typeof rawParams.reason === 'string' ? rawParams.reason.trim() : '';
87
- const score = typeof rawParams.score === 'number' ? Math.max(0, Math.min(100, Math.round(rawParams.score))) : 80;
88
- const source = typeof rawParams.source === 'string' && rawParams.source.trim() ? rawParams.source.trim() : 'manual';
89
- const sessionId = typeof rawParams.session_id === 'string' ? rawParams.session_id.trim() : '';
90
- const isRisky = rawParams.is_risky === true;
91
-
92
- // ── Validate required fields ──
93
- if (!reason) {
94
- api.logger?.warn?.('[PD:write_pain_flag] Missing required field: reason');
95
- return {
96
- content: [{
97
- type: 'text',
98
- text: '❌ Error: The `reason` parameter is required.\n' +
99
- 'Describe specifically what went wrong. Example:\n' +
100
- '"I edited config.ts without reading it first, breaking the export"',
101
- }],
102
- };
103
- }
104
-
105
- // ── Resolve workspace ──
106
- const workspaceDir = resolveWorkspaceDirFromApi(api);
107
- if (!workspaceDir) {
108
- api.logger?.error?.('[PD:write_pain_flag] Cannot resolve workspace directory');
109
- return {
110
- content: [{
111
- type: 'text',
112
- text: '❌ Error: Cannot determine the workspace directory. ' +
113
- 'Please ensure you are in an active workspace.',
114
- }],
115
- };
116
- }
117
-
118
- try {
119
- // ── Record pain event to trajectory DB first (before flag file) ──
120
- // This ensures we have a real AUTOINCREMENT ID that flows through
121
- // the entire pain→principle→compile chain (painEventId propagation).
122
- let painEventId: number | undefined;
123
- try {
124
- const trajectory = TrajectoryRegistry.get(workspaceDir);
125
- painEventId = trajectory.recordPainEvent({
126
- sessionId: sessionId || 'unknown',
127
- source,
128
- score,
129
- reason,
130
- severity: null,
131
- origin: 'manual',
132
- confidence: null,
133
- text: undefined,
134
- });
135
- } catch (trajErr) {
136
- // If trajectory write fails, log but continue — the pain flag
137
- // itself is still valid and should be processed. The pain event
138
- // ID simply won't be available for the compiler's exact matching.
139
- api.logger?.warn?.(`[PD:write_pain_flag] Failed to record pain event to trajectory: ${String(trajErr)}`);
140
- }
141
-
142
- // ── Build pain flag data (KV format) ──
143
- const painData = buildPainFlag({
144
- source,
145
- score: String(score),
146
- reason,
147
- session_id: sessionId,
148
- is_risky: isRisky,
149
- pain_event_id: painEventId !== undefined ? String(painEventId) : undefined,
150
- });
151
-
152
- // ── Validate contract compliance ──
153
- const missingFields: string[] = [];
154
- for (const field of PAIN_FLAG_REQUIRED_FIELDS) {
155
- if (!painData[field] || painData[field].trim() === '') {
156
- missingFields.push(field);
157
- }
158
- }
159
- if (missingFields.length > 0) {
160
- api.logger?.error?.(`[PD:write_pain_flag] Pain flag missing required fields: ${missingFields.join(', ')}`);
161
- return {
162
- content: [{
163
- type: 'text',
164
- text: `❌ Error: Pain flag is missing required fields: ${missingFields.join(', ')}. ` +
165
- 'This is an internal error — please report it.',
166
- }],
167
- };
168
- }
169
-
170
- // ── Atomic write (temp file + rename) ──
171
- const painFlagPath = path.join(workspaceDir, '.state', '.pain_flag');
172
- const { serializeKvLines } = await import('../utils/io.js');
173
- const content = serializeKvLines(painData);
174
- writePainFlagAtomic(painFlagPath, content);
175
-
176
- // ── Log success ──
177
- api.logger?.info?.(
178
- `[PD:write_pain_flag] Pain signal recorded: source=${source}, score=${score}, ` +
179
- `reason="${reason.slice(0, 80)}"${reason.length > 80 ? '...' : ''}"`
180
- );
181
-
182
- // ── Agent feedback ──
183
- return {
184
- content: [{
185
- type: 'text',
186
- text: `✅ Pain signal recorded successfully.\n\n` +
187
- `- **Reason**: ${reason}\n` +
188
- `- **Score**: ${score}/100\n` +
189
- `- **Source**: ${source}\n` +
190
- `- **Risk**: ${isRisky ? 'Yes' : 'No'}\n` +
191
- `- **Session**: ${sessionId || '(current)'}\n\n` +
192
- `The evolution system will process this signal on the next heartbeat cycle ` +
193
- `(typically within 60 seconds).`,
194
- }],
195
- };
196
- } catch (err) {
197
- // ── Log failure with stack trace ──
198
- const errorMsg = err instanceof Error ? err.message : String(err);
199
- const stack = err instanceof Error ? err.stack?.split('\n').slice(0, 3).join(' → ') : '';
200
- api.logger?.error?.(
201
- `[PD:write_pain_flag] Failed to write pain flag: ${errorMsg}` +
202
- (stack ? `\n Stack: ${stack}` : '')
203
- );
204
-
205
- return {
206
- content: [{
207
- type: 'text',
208
- text: `❌ Failed to record pain signal: ${errorMsg}\n\n` +
209
- 'The error has been logged. Please try again or report this issue.',
210
- }],
211
- };
212
- }
213
- },
214
- };
215
- }
@@ -1,448 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import { handleNocturnalReviewCommand } from '../../src/commands/nocturnal-review.js';
6
- import type { PluginCommandContext } from '../../src/openclaw-sdk.js';
7
- import {
8
- registerSample,
9
- updateReviewStatus,
10
- getDatasetRecord,
11
- } from '../../src/core/nocturnal-dataset.js';
12
- import type { NocturnalArtifact } from '../../src/core/nocturnal-arbiter.js';
13
-
14
- // ---------------------------------------------------------------------------
15
- // Fixtures
16
- // ---------------------------------------------------------------------------
17
-
18
- function makeArtifact(overrides: Partial<NocturnalArtifact> = {}): NocturnalArtifact {
19
- return {
20
- artifactId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
21
- sessionId: 'session-abc123',
22
- principleId: 'T-08',
23
- sourceSnapshotRef: 'snapshot-2026-03-27-001',
24
- badDecision: 'After bash command failed, immediately retried without diagnosing',
25
- betterDecision: 'Check the error message before retrying',
26
- rationale: 'Diagnosing failures prevents repeated failures and respects cost of each attempt',
27
- createdAt: '2026-03-27T12:00:00.000Z',
28
- ...overrides,
29
- };
30
- }
31
-
32
- function makeTmpDir(): string {
33
- return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-review-test-'));
34
- }
35
-
36
- function rmdir(dir: string): void {
37
- try {
38
- if (fs.existsSync(dir)) {
39
- fs.rmSync(dir, { recursive: true, force: true });
40
- }
41
- } catch {
42
- // Ignore
43
- }
44
- }
45
-
46
- function makeCtx(workspaceDir: string, args = ''): PluginCommandContext {
47
- return {
48
- config: { workspaceDir, language: 'en' },
49
- args,
50
- } as unknown as PluginCommandContext;
51
- }
52
-
53
- function setupSample(
54
- workspaceDir: string,
55
- artifactId: string,
56
- family: string | null = 'gpt-4'
57
- ): { fingerprint: string; artifactPath: string } {
58
- const artifact = makeArtifact({ artifactId });
59
- const artifactPath = path.join(
60
- workspaceDir,
61
- '.state',
62
- 'nocturnal',
63
- 'samples',
64
- `${artifactId}.json`
65
- );
66
- fs.mkdirSync(path.dirname(artifactPath), { recursive: true });
67
- fs.writeFileSync(artifactPath, JSON.stringify({ ...artifact, status: 'approved' }), 'utf-8');
68
- const result = registerSample(workspaceDir, artifact, artifactPath, family);
69
- return { fingerprint: result.record.sampleFingerprint, artifactPath };
70
- }
71
-
72
- // ---------------------------------------------------------------------------
73
- // Tests: list
74
- // ---------------------------------------------------------------------------
75
-
76
- describe('NocturnalReviewCommand list', () => {
77
- let tmpDir: string;
78
-
79
- beforeEach(() => {
80
- tmpDir = makeTmpDir();
81
- });
82
-
83
- afterEach(() => {
84
- rmdir(tmpDir);
85
- });
86
-
87
- it('shows pending samples', () => {
88
- const { fingerprint } = setupSample(tmpDir, 'art-pending-list');
89
-
90
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
91
-
92
- expect(result.text).toContain('pending_review');
93
- expect(result.text).toContain(fingerprint.substring(0, 16));
94
- });
95
-
96
- it('shows empty when no pending samples', () => {
97
- const { fingerprint } = setupSample(tmpDir, 'art-approved-list');
98
- updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
99
-
100
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
101
-
102
- expect(result.text).toContain('No pending');
103
- });
104
- });
105
-
106
- // ---------------------------------------------------------------------------
107
- // Tests: approve
108
- // ---------------------------------------------------------------------------
109
-
110
- describe('NocturnalReviewCommand approve', () => {
111
- let tmpDir: string;
112
-
113
- beforeEach(() => {
114
- tmpDir = makeTmpDir();
115
- });
116
-
117
- afterEach(() => {
118
- rmdir(tmpDir);
119
- });
120
-
121
- it('approves a pending sample', () => {
122
- const { fingerprint } = setupSample(tmpDir, 'art-approve-test');
123
-
124
- const result = handleNocturnalReviewCommand(
125
- makeCtx(tmpDir, `approve ${fingerprint} Looks good for training`)
126
- );
127
-
128
- expect(result.text).toContain('approved for training');
129
- const record = getDatasetRecord(tmpDir, fingerprint);
130
- expect(record?.reviewStatus).toBe('approved_for_training');
131
- expect(record?.reviewReason).toBe('Looks good for training');
132
- });
133
-
134
- it('approves with default reason if not provided', () => {
135
- const { fingerprint } = setupSample(tmpDir, 'art-approve-default');
136
-
137
- const result = handleNocturnalReviewCommand(
138
- makeCtx(tmpDir, `approve ${fingerprint}`)
139
- );
140
-
141
- expect(result.text).toContain('approved for training');
142
- const record = getDatasetRecord(tmpDir, fingerprint);
143
- expect(record?.reviewStatus).toBe('approved_for_training');
144
- expect(record?.reviewReason).toBeTruthy();
145
- });
146
-
147
- it('rejects already approved sample', () => {
148
- const { fingerprint } = setupSample(tmpDir, 'art-already-approved');
149
- updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Already approved');
150
-
151
- const result = handleNocturnalReviewCommand(
152
- makeCtx(tmpDir, `approve ${fingerprint}`)
153
- );
154
-
155
- expect(result.text).toContain('already approved');
156
- });
157
-
158
- it('rejects rejected sample without reset', () => {
159
- const { fingerprint } = setupSample(tmpDir, 'art-already-rejected');
160
- updateReviewStatus(tmpDir, fingerprint, 'rejected', 'Rejected');
161
-
162
- const result = handleNocturnalReviewCommand(
163
- makeCtx(tmpDir, `approve ${fingerprint}`)
164
- );
165
-
166
- expect(result.text).toContain('rejected');
167
- });
168
-
169
- it('rejects superseded sample', () => {
170
- const { fingerprint } = setupSample(tmpDir, 'art-superseded');
171
- updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
172
- updateReviewStatus(tmpDir, fingerprint, 'superseded', 'Superseded by better');
173
-
174
- const result = handleNocturnalReviewCommand(
175
- makeCtx(tmpDir, `approve ${fingerprint}`)
176
- );
177
-
178
- expect(result.text).toContain('superseded');
179
- });
180
-
181
- it('returns error for unknown fingerprint', () => {
182
- const result = handleNocturnalReviewCommand(
183
- makeCtx(tmpDir, 'approve unknown-fingerprint')
184
- );
185
-
186
- expect(result.text).toContain('not found');
187
- });
188
-
189
- it('returns error when missing fingerprint', () => {
190
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'approve'));
191
-
192
- expect(result.text).toContain('Usage');
193
- });
194
- });
195
-
196
- // ---------------------------------------------------------------------------
197
- // Tests: reject
198
- // ---------------------------------------------------------------------------
199
-
200
- describe('NocturnalReviewCommand reject', () => {
201
- let tmpDir: string;
202
-
203
- beforeEach(() => {
204
- tmpDir = makeTmpDir();
205
- });
206
-
207
- afterEach(() => {
208
- rmdir(tmpDir);
209
- });
210
-
211
- it('rejects a pending sample', () => {
212
- const { fingerprint } = setupSample(tmpDir, 'art-reject-test');
213
-
214
- const result = handleNocturnalReviewCommand(
215
- makeCtx(tmpDir, `reject ${fingerprint} Not suitable for training`)
216
- );
217
-
218
- expect(result.text).toContain('rejected');
219
- const record = getDatasetRecord(tmpDir, fingerprint);
220
- expect(record?.reviewStatus).toBe('rejected');
221
- expect(record?.reviewReason).toBe('Not suitable for training');
222
- });
223
-
224
- it('rejects with default reason if not provided', () => {
225
- const { fingerprint } = setupSample(tmpDir, 'art-reject-default');
226
-
227
- const result = handleNocturnalReviewCommand(
228
- makeCtx(tmpDir, `reject ${fingerprint}`)
229
- );
230
-
231
- expect(result.text).toContain('rejected');
232
- const record = getDatasetRecord(tmpDir, fingerprint);
233
- expect(record?.reviewStatus).toBe('rejected');
234
- });
235
-
236
- it('rejects already rejected sample', () => {
237
- const { fingerprint } = setupSample(tmpDir, 'art-already-rejected');
238
- updateReviewStatus(tmpDir, fingerprint, 'rejected', 'Already rejected');
239
-
240
- const result = handleNocturnalReviewCommand(
241
- makeCtx(tmpDir, `reject ${fingerprint}`)
242
- );
243
-
244
- expect(result.text).toContain('already rejected');
245
- });
246
-
247
- it('rejects superseded sample', () => {
248
- const { fingerprint } = setupSample(tmpDir, 'art-superseded-reject');
249
- updateReviewStatus(tmpDir, fingerprint, 'approved_for_training', 'Approved');
250
- updateReviewStatus(tmpDir, fingerprint, 'superseded', 'Superseded');
251
-
252
- const result = handleNocturnalReviewCommand(
253
- makeCtx(tmpDir, `reject ${fingerprint}`)
254
- );
255
-
256
- expect(result.text).toContain('superseded');
257
- });
258
-
259
- it('returns error for unknown fingerprint', () => {
260
- const result = handleNocturnalReviewCommand(
261
- makeCtx(tmpDir, 'reject unknown-fingerprint')
262
- );
263
-
264
- expect(result.text).toContain('not found');
265
- });
266
- });
267
-
268
- // ---------------------------------------------------------------------------
269
- // Tests: show
270
- // ---------------------------------------------------------------------------
271
-
272
- describe('NocturnalReviewCommand show', () => {
273
- let tmpDir: string;
274
-
275
- beforeEach(() => {
276
- tmpDir = makeTmpDir();
277
- });
278
-
279
- afterEach(() => {
280
- rmdir(tmpDir);
281
- });
282
-
283
- it('shows sample details', () => {
284
- const { fingerprint } = setupSample(tmpDir, 'art-show-test');
285
-
286
- const result = handleNocturnalReviewCommand(
287
- makeCtx(tmpDir, `show ${fingerprint}`)
288
- );
289
-
290
- expect(result.text).toContain(fingerprint.substring(0, 16));
291
- expect(result.text).toContain('T-08');
292
- expect(result.text).toContain('session-abc123');
293
- });
294
-
295
- it('returns error for unknown fingerprint', () => {
296
- const result = handleNocturnalReviewCommand(
297
- makeCtx(tmpDir, 'show unknown-fingerprint')
298
- );
299
-
300
- expect(result.text).toContain('not found');
301
- });
302
-
303
- it('returns error when missing fingerprint', () => {
304
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'show'));
305
-
306
- expect(result.text).toContain('Usage');
307
- });
308
- });
309
-
310
- // ---------------------------------------------------------------------------
311
- // Tests: set-family
312
- // ---------------------------------------------------------------------------
313
-
314
- describe('NocturnalReviewCommand set-family', () => {
315
- let tmpDir: string;
316
-
317
- beforeEach(() => {
318
- tmpDir = makeTmpDir();
319
- });
320
-
321
- afterEach(() => {
322
- rmdir(tmpDir);
323
- });
324
-
325
- it('sets target model family', () => {
326
- const { fingerprint } = setupSample(tmpDir, 'art-family-test', null);
327
-
328
- const result = handleNocturnalReviewCommand(
329
- makeCtx(tmpDir, `set-family ${fingerprint} claude-3`)
330
- );
331
-
332
- expect(result.text).toContain('claude-3');
333
- const record = getDatasetRecord(tmpDir, fingerprint);
334
- expect(record?.targetModelFamily).toBe('claude-3');
335
- });
336
-
337
- it('updates existing family', () => {
338
- const { fingerprint } = setupSample(tmpDir, 'art-family-update-test', 'gpt-4');
339
-
340
- const result = handleNocturnalReviewCommand(
341
- makeCtx(tmpDir, `set-family ${fingerprint} gpt-4o`)
342
- );
343
-
344
- expect(result.text).toContain('gpt-4o');
345
- const record = getDatasetRecord(tmpDir, fingerprint);
346
- expect(record?.targetModelFamily).toBe('gpt-4o');
347
- });
348
-
349
- it('returns error for unknown fingerprint', () => {
350
- const result = handleNocturnalReviewCommand(
351
- makeCtx(tmpDir, 'set-family unknown-fingerprint gpt-4')
352
- );
353
-
354
- expect(result.text).toContain('not found');
355
- });
356
-
357
- it('returns error when missing args', () => {
358
- const { fingerprint } = setupSample(tmpDir, 'art-family-missing-test');
359
-
360
- const result = handleNocturnalReviewCommand(
361
- makeCtx(tmpDir, `set-family ${fingerprint}`)
362
- );
363
-
364
- expect(result.text).toContain('Usage');
365
- });
366
- });
367
-
368
- // ---------------------------------------------------------------------------
369
- // Tests: stats
370
- // ---------------------------------------------------------------------------
371
-
372
- describe('NocturnalReviewCommand stats', () => {
373
- let tmpDir: string;
374
-
375
- beforeEach(() => {
376
- tmpDir = makeTmpDir();
377
- });
378
-
379
- afterEach(() => {
380
- rmdir(tmpDir);
381
- });
382
-
383
- it('shows dataset statistics', () => {
384
- setupSample(tmpDir, 'art-stats-1', 'gpt-4');
385
- const { fingerprint: fp2 } = setupSample(tmpDir, 'art-stats-2', 'gpt-4');
386
- updateReviewStatus(tmpDir, fp2, 'approved_for_training', 'Approved');
387
-
388
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
389
-
390
- expect(result.text).toContain('Total');
391
- expect(result.text).toContain('2');
392
- });
393
-
394
- it('shows export-ready counts by family', () => {
395
- const { fingerprint: fp1 } = setupSample(tmpDir, 'art-stats-family-1', 'gpt-4');
396
- updateReviewStatus(tmpDir, fp1, 'approved_for_training', 'Approved');
397
-
398
- const result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
399
-
400
- expect(result.text).toContain('gpt-4');
401
- expect(result.text).toContain('1');
402
- });
403
- });
404
-
405
- // ---------------------------------------------------------------------------
406
- // Integration: full review flow
407
- // ---------------------------------------------------------------------------
408
-
409
- describe('NocturnalReviewCommand full review flow', () => {
410
- let tmpDir: string;
411
-
412
- beforeEach(() => {
413
- tmpDir = makeTmpDir();
414
- });
415
-
416
- afterEach(() => {
417
- rmdir(tmpDir);
418
- });
419
-
420
- it('full flow: list → set-family → approve → export-ready', () => {
421
- // 1. Register sample without family
422
- const { fingerprint } = setupSample(tmpDir, 'art-full-flow', null);
423
-
424
- // 2. Verify it's pending
425
- let result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
426
- expect(result.text).toContain(fingerprint.substring(0, 16));
427
-
428
- // 3. Set family
429
- result = handleNocturnalReviewCommand(
430
- makeCtx(tmpDir, `set-family ${fingerprint} gpt-4`)
431
- );
432
- expect(result.text).toContain('gpt-4');
433
-
434
- // 4. Approve
435
- result = handleNocturnalReviewCommand(
436
- makeCtx(tmpDir, `approve ${fingerprint} Good sample`)
437
- );
438
- expect(result.text).toContain('approved for training');
439
-
440
- // 5. Verify stats show approved
441
- result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'stats'));
442
- expect(result.text).toContain('Approved for training');
443
-
444
- // 6. Verify not in pending list anymore
445
- result = handleNocturnalReviewCommand(makeCtx(tmpDir, 'list'));
446
- expect(result.text).not.toContain(fingerprint.substring(0, 16));
447
- });
448
- });