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
@@ -30,11 +30,10 @@
30
30
  * - Observations are retained until cleanup removes expired entries
31
31
  */
32
32
 
33
- import * as fs from 'fs';
34
33
  import * as path from 'path';
35
34
  import * as crypto from 'crypto';
36
35
  import { withLock } from '../utils/file-lock.js';
37
- import { atomicWriteFileSync } from '../utils/io.js';
36
+ import { JsonFileStore } from './file-store.js';
38
37
 
39
38
  // ---------------------------------------------------------------------------
40
39
  // Constants
@@ -170,67 +169,44 @@ export interface ShadowRegistry {
170
169
  }
171
170
 
172
171
  // ---------------------------------------------------------------------------
173
- // Registry Path
172
+ // Registry Path & Store
174
173
  // ---------------------------------------------------------------------------
175
174
 
175
+ let _store: Map<string, JsonFileStore<ShadowRegistry>> = new Map();
176
+
176
177
  function getRegistryPath(stateDir: string): string {
177
178
  return path.join(stateDir, SHADOW_REGISTRY_FILE);
178
179
  }
179
180
 
180
- /**
181
- * Ensure the registry directory exists.
182
- */
183
- function ensureRegistryDir(stateDir: string): void {
184
- const registryPath = getRegistryPath(stateDir);
185
- const dir = path.dirname(registryPath);
186
- if (!fs.existsSync(dir)) {
187
- fs.mkdirSync(dir, { recursive: true });
181
+ function getStore(stateDir: string): JsonFileStore<ShadowRegistry> {
182
+ let store = _store.get(stateDir);
183
+ if (!store) {
184
+ const filePath = path.join(stateDir, SHADOW_REGISTRY_FILE);
185
+ store = new JsonFileStore<ShadowRegistry>(filePath, () => ({ observations: [], version: 1 }));
186
+ _store.set(stateDir, store);
188
187
  }
188
+ return store;
189
189
  }
190
190
 
191
191
  // ---------------------------------------------------------------------------
192
192
  // File Operations
193
193
  // ---------------------------------------------------------------------------
194
194
 
195
- /**
196
- * Read the registry from disk. Returns empty registry if missing.
197
- */
198
- function readRegistry(stateDir: string): ShadowRegistry {
199
- const registryPath = getRegistryPath(stateDir);
200
- if (!fs.existsSync(registryPath)) {
201
- return { observations: [], version: 1 };
202
- }
203
- try {
204
- const content = fs.readFileSync(registryPath, 'utf-8');
205
- return JSON.parse(content) as ShadowRegistry;
206
- } catch (err) {
207
- console.warn(`[shadow-observation-registry] Registry corrupted at ${registryPath}, recovering with empty state: ${String(err)}`);
208
- return { observations: [], version: 1 };
209
- }
210
- }
211
-
212
- /**
213
- * Write the registry to disk atomically.
214
- */
215
- function writeRegistry(stateDir: string, registry: ShadowRegistry): void {
216
- ensureRegistryDir(stateDir);
217
- const registryPath = getRegistryPath(stateDir);
218
- atomicWriteFileSync(registryPath, JSON.stringify(registry, null, 2));
219
- }
220
-
221
195
  /**
222
196
  * Execute a read-modify-write under an exclusive file lock.
223
197
  */
224
-
225
198
  function withShadowRegistryLock<T>(
226
199
  stateDir: string,
227
200
  fn: (_registry: ShadowRegistry) => T
228
201
  ): T {
229
-
230
- const registryPath = getRegistryPath(stateDir);
231
- return withLock(registryPath, () => {
232
- const registry = readRegistry(stateDir);
233
- return fn(registry);
202
+ const store = getStore(stateDir);
203
+ const filePath = getRegistryPath(stateDir);
204
+ // Re-create lock behavior using the store's mutate under a simple fs-based lock
205
+ return withLock(filePath, () => {
206
+ const registry = store.load();
207
+ const result = fn(registry);
208
+ store.save(registry);
209
+ return result;
234
210
  });
235
211
  }
236
212
 
@@ -276,7 +252,6 @@ export function recordShadowRouting(
276
252
 
277
253
  return withShadowRegistryLock(stateDir, (registry) => {
278
254
  registry.observations.push(observation);
279
- writeRegistry(stateDir, registry);
280
255
  return observation;
281
256
  });
282
257
  }
@@ -326,7 +301,6 @@ export function completeShadowObservation(
326
301
  extra: {},
327
302
  };
328
303
 
329
- writeRegistry(stateDir, registry);
330
304
  return observation;
331
305
  });
332
306
  }
@@ -369,7 +343,6 @@ export function completeShadowObservationByTask(
369
343
  extra: {},
370
344
  };
371
345
 
372
- writeRegistry(stateDir, registry);
373
346
  return observation;
374
347
  });
375
348
  }
@@ -506,7 +479,6 @@ export function markObservationsUsedInGate(
506
479
  obs.usedInGate = true;
507
480
  }
508
481
  }
509
- writeRegistry(stateDir, registry);
510
482
  });
511
483
  }
512
484
 
@@ -530,7 +502,6 @@ export function cleanupExpiredObservations(
530
502
  (o) => o.routedAt >= cutoff
531
503
  );
532
504
  removed = before - registry.observations.length;
533
- writeRegistry(stateDir, registry);
534
505
  });
535
506
 
536
507
  return removed;
@@ -861,12 +861,13 @@ export class TrajectoryDatabase {
861
861
  listUserTurnsForSession(sessionId: string): {
862
862
  id: number;
863
863
  turnIndex: number;
864
+ rawExcerpt: string;
864
865
  correctionDetected: boolean;
865
866
  correctionCue: string | null;
866
867
  createdAt: string;
867
868
  }[] {
868
869
  const rows = this.db.prepare(`
869
- SELECT id, turn_index, correction_detected, correction_cue, created_at
870
+ SELECT id, turn_index, raw_excerpt, correction_detected, correction_cue, created_at
870
871
  FROM user_turns
871
872
  WHERE session_id = ?
872
873
  ORDER BY turn_index ASC
@@ -875,6 +876,7 @@ export class TrajectoryDatabase {
875
876
  return rows.map((row) => ({
876
877
  id: Number(row.id),
877
878
  turnIndex: Number(row.turn_index),
879
+ rawExcerpt: String(row.raw_excerpt ?? ''),
878
880
  correctionDetected: Boolean(row.correction_detected),
879
881
  correctionCue: row.correction_cue ? String(row.correction_cue) : null,
880
882
  createdAt: String(row.created_at),
@@ -18,16 +18,34 @@ import yaml from 'js-yaml';
18
18
 
19
19
  /**
20
20
  * A single stage in a workflow funnel.
21
+ * Policy fields are optional — existing stages without policy fields remain valid.
21
22
  */
22
23
  export interface WorkflowStage {
23
- /** Stage name within the funnel (e.g., 'dreamer_completed') */
24
24
  name: string;
25
- /** Event type string (e.g., 'nocturnal_dreamer_completed') */
26
25
  eventType: string;
27
- /** Event category (e.g., 'completed', 'created', 'blocked') */
28
26
  eventCategory: string;
29
- /** Dot-path to stats field (e.g., 'evolution.nocturnalDreamerCompleted') */
30
27
  statsField: string;
28
+ timeoutMs?: number;
29
+ successCriteria?: string;
30
+ legacyDisabled?: boolean;
31
+ observability?: {
32
+ enabled?: boolean;
33
+ emitEvents?: string[];
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Funnel-level policy that applies to all stages unless overridden per-stage.
39
+ */
40
+ export interface FunnelPolicy {
41
+ timeoutMs?: number;
42
+ stageOrder?: 'strict' | 'relaxed';
43
+ legacyDisabled?: boolean;
44
+ observability?: {
45
+ enabled?: boolean;
46
+ emitEvents?: string[];
47
+ logLevel?: 'debug' | 'info' | 'warn' | 'error';
48
+ };
31
49
  }
32
50
 
33
51
  /**
@@ -36,6 +54,7 @@ export interface WorkflowStage {
36
54
  export interface WorkflowFunnel {
37
55
  workflowId: string;
38
56
  stages: WorkflowStage[];
57
+ policy?: FunnelPolicy;
39
58
  }
40
59
 
41
60
  /**
@@ -50,56 +69,31 @@ export interface WorkflowFunnelConfig {
50
69
  // WorkflowFunnelLoader
51
70
  // ─────────────────────────────────────────────────────────────────────────────
52
71
 
53
- /**
54
- * Loads and watches workflows.yaml, building an in-memory WORKFLOW_FUNNELS table.
55
- *
56
- * Failure semantics (per Codex review):
57
- * - Missing file: clears in-memory funnels, uses empty Map
58
- * - Malformed YAML: preserves last known-good config, logs warning
59
- * - Schema-invalid YAML: same as malformed YAML
60
- *
61
- * Usage:
62
- * const loader = new WorkflowFunnelLoader(stateDir);
63
- * const funnels = loader.getAllFunnels(); // Map<string, WorkflowStage[]>
64
- * loader.watch(); // Enable hot reload
65
- */
66
72
  export class WorkflowFunnelLoader {
67
- /** In-memory WORKFLOW_FUNNELS table: workflowId -> stages */
68
73
  private readonly funnels = new Map<string, WorkflowStage[]>();
69
-
74
+ private readonly fullFunnels = new Map<string, WorkflowFunnel>();
70
75
  private readonly configPath: string;
71
-
72
- /** fs.watch() handle for cleanup */
73
76
  private watchHandle?: fs.FSWatcher;
74
-
75
- /** YAML parse warnings from last load() call */
76
77
  private readonly warnings: string[] = [];
77
78
 
78
79
  constructor(stateDir: string) {
79
- // D-02: workflows.yaml in .state/ directory
80
80
  this.configPath = path.join(stateDir, 'workflows.yaml');
81
81
  this.load();
82
82
  }
83
83
 
84
- /**
85
- * Load (or reload) workflows.yaml from disk.
86
- * On parse/validation failure, preserves the last known-good config.
87
- * On missing file, clears to empty.
88
- */
89
84
  load(): void {
90
- this.warnings.length = 0; // reset warnings on each load
85
+ this.warnings.length = 0;
91
86
  if (!fs.existsSync(this.configPath)) {
92
87
  this.warnings.push('workflows.yaml file not found.');
93
88
  this.funnels.clear();
89
+ this.fullFunnels.clear();
94
90
  return;
95
91
  }
96
92
 
97
93
  try {
98
94
  const content = fs.readFileSync(this.configPath, 'utf-8');
99
- // Use safe load — no arbitrary code execution
100
95
  const config = yaml.load(content, { schema: yaml.DEFAULT_SCHEMA }) as WorkflowFunnelConfig;
101
96
 
102
- // Validate top-level structure
103
97
  if (!config || typeof config.version !== 'string' || !Array.isArray(config.funnels)) {
104
98
  const msg = 'workflows.yaml validation failed: missing version or funnels array. Preserving last valid config.';
105
99
  console.warn(`[WorkflowFunnelLoader] ${msg}`);
@@ -107,11 +101,12 @@ export class WorkflowFunnelLoader {
107
101
  return;
108
102
  }
109
103
 
110
- // Rebuild funnels map
111
104
  const newFunnels = new Map<string, WorkflowStage[]>();
105
+ const newFullFunnels = new Map<string, WorkflowFunnel>();
112
106
  for (const funnel of config.funnels) {
113
107
  if (funnel?.workflowId && typeof funnel.workflowId === 'string' && Array.isArray(funnel.stages)) {
114
108
  newFunnels.set(funnel.workflowId, funnel.stages);
109
+ newFullFunnels.set(funnel.workflowId, funnel);
115
110
  } else {
116
111
  const msg = 'Skipping invalid funnel entry: missing workflowId or stages.';
117
112
  console.warn(`[WorkflowFunnelLoader] ${msg}`);
@@ -119,44 +114,28 @@ export class WorkflowFunnelLoader {
119
114
  }
120
115
  }
121
116
 
122
- // Atomic replace: only commit if entire parse/validation succeeded
123
117
  this.funnels.clear();
124
- for (const [k, v] of newFunnels) {
125
- this.funnels.set(k, v);
126
- }
118
+ this.fullFunnels.clear();
119
+ for (const [k, v] of newFunnels) { this.funnels.set(k, v); }
120
+ for (const [k, v] of newFullFunnels) { this.fullFunnels.set(k, v); }
127
121
  } catch (err) {
128
- // Best-effort: preserve last known-good config on parse error
129
122
  const msg = `Failed to parse workflows.yaml: ${String(err)}. Preserving last valid config.`;
130
123
  console.warn(`[WorkflowFunnelLoader] ${msg}`);
131
124
  this.warnings.push(msg);
132
125
  }
133
126
  }
134
127
 
135
- /**
136
- * Start watching workflows.yaml for changes.
137
- * Calls load() automatically when the file changes.
138
- * No-op if the config file does not exist.
139
- */
140
128
  watch(): void {
141
- // WATCHER-01: re-entry guard — prevent FSWatcher leak on double-watch
142
129
  if (this.watchHandle) return;
143
- // Guard: fs.watch fails with ENOENT if the path does not exist
144
130
  if (!fs.existsSync(this.configPath)) return;
145
- // Debounce: only re-read after file write settles (100ms)
146
131
  let debounceTimer: ReturnType<typeof setTimeout> | undefined;
147
132
  this.watchHandle = fs.watch(this.configPath, (eventType) => {
148
- // PLAT-01: handle both 'change' and 'rename' events for Windows compatibility
149
133
  if (eventType !== 'change' && eventType !== 'rename') return;
150
134
  if (debounceTimer) clearTimeout(debounceTimer);
151
- debounceTimer = setTimeout(() => {
152
- this.load();
153
- }, 100);
135
+ debounceTimer = setTimeout(() => this.load(), 100);
154
136
  });
155
137
  }
156
138
 
157
- /**
158
- * Stop watching and clean up the FSWatcher.
159
- */
160
139
  dispose(): void {
161
140
  if (this.watchHandle) {
162
141
  this.watchHandle.close();
@@ -164,38 +143,53 @@ export class WorkflowFunnelLoader {
164
143
  }
165
144
  }
166
145
 
167
- /**
168
- * Get all stages for a workflow.
169
- */
170
146
  getStages(workflowId: string): WorkflowStage[] {
171
147
  return this.funnels.get(workflowId) ?? [];
172
148
  }
173
149
 
174
- /**
175
- * Get the full WORKFLOW_FUNNELS table.
176
- * Returns a deep clone — consumer mutations do not affect internal state.
177
- */
150
+ getFunnel(workflowId: string): WorkflowFunnel | undefined {
151
+ const funnel = this.fullFunnels.get(workflowId);
152
+ if (!funnel) return undefined;
153
+ return this.cloneFunnel(funnel);
154
+ }
155
+
178
156
  getAllFunnels(): Map<string, WorkflowStage[]> {
179
157
  const result = new Map<string, WorkflowStage[]>();
180
158
  for (const [k, v] of this.funnels) {
181
- // WATCHER-03: deep-clone arrays and stage objects
182
159
  result.set(k, v.map(stage => ({ ...stage })));
183
160
  }
184
161
  return result;
185
162
  }
186
163
 
187
- /**
188
- * Returns warnings from the last load() call.
189
- * Callers can inspect these and propagate them to metadata.warnings.
190
- */
164
+ getAllFunnelsWithPolicy(): Map<string, WorkflowFunnel> {
165
+ const result = new Map<string, WorkflowFunnel>();
166
+ for (const [k, v] of this.fullFunnels) {
167
+ result.set(k, this.cloneFunnel(v));
168
+ }
169
+ return result;
170
+ }
171
+
191
172
  getWarnings(): string[] {
192
173
  return [...this.warnings];
193
174
  }
194
175
 
195
- /**
196
- * Get the config file path (for testing/debugging).
197
- */
198
176
  getConfigPath(): string {
199
177
  return this.configPath;
200
178
  }
179
+
180
+ private cloneFunnel(funnel: WorkflowFunnel): WorkflowFunnel {
181
+ return {
182
+ ...funnel,
183
+ stages: funnel.stages.map(stage => ({ ...stage })),
184
+ policy: funnel.policy ? {
185
+ ...funnel.policy,
186
+ observability: funnel.policy.observability ? {
187
+ ...funnel.policy.observability,
188
+ emitEvents: funnel.policy.observability.emitEvents
189
+ ? [...funnel.policy.observability.emitEvents]
190
+ : undefined,
191
+ } : undefined,
192
+ } : undefined,
193
+ };
194
+ }
201
195
  }
@@ -1,6 +1,7 @@
1
1
  import type { PD_FILES } from './paths.js';
2
2
  import { resolvePdPath } from './paths.js';
3
3
  import { PathResolver } from './path-resolver.js';
4
+ import { validateWorkspaceDir } from './workspace-dir-validation.js';
4
5
  import { ConfigService } from './config-service.js';
5
6
  import type { PainConfig } from './config.js';
6
7
  import type { EventLog } from './event-log.js';
@@ -193,6 +194,14 @@ export class WorkspaceContext {
193
194
  }
194
195
  }
195
196
 
197
+ const validationIssue = validateWorkspaceDir(workspaceDir);
198
+ if (validationIssue !== null) {
199
+ logWarn(
200
+ `[PD:WorkspaceContext] LEGACY_PATH_RESOLVER_FALLBACK: ${validationIssue}. ` +
201
+ 'This is a legacy discovery path; explicit workspaceDir should be provided in the hook context.',
202
+ );
203
+ }
204
+
196
205
  const existing = this.instances.get(workspaceDir);
197
206
  if (existing) return existing;
198
207
 
@@ -210,6 +219,43 @@ export class WorkspaceContext {
210
219
  return instance;
211
220
  }
212
221
 
222
+ /**
223
+ * Creates a WorkspaceContext requiring explicit workspaceDir.
224
+ * For Runtime V2 entrypoints where implicit PathResolver fallback is unacceptable.
225
+ * @throws Error if workspaceDir is not provided in the context.
226
+ */
227
+ static fromHookContextExplicit(ctx: { workspaceDir?: string; logger?: { error?: (...args: unknown[]) => void; warn?: (...args: unknown[]) => void; info?: (...args: unknown[]) => void } }): WorkspaceContext {
228
+ const { logger } = ctx;
229
+ let { workspaceDir } = ctx;
230
+ if (!workspaceDir || !workspaceDir.trim()) {
231
+ const error = {
232
+ ok: false as const,
233
+ reason: 'workspace_dir_missing',
234
+ message: 'workspaceDir is required for Runtime V2 entrypoints. Provide it explicitly in the hook context.',
235
+ nextAction: 'Ensure the OpenClaw hook context includes workspaceDir, or use PD_WORKSPACE_DIR env var.',
236
+ };
237
+ logger?.error?.(`[PD:WorkspaceContext] ${error.message}`);
238
+ throw new Error(`[PD:WorkspaceContext] ${error.reason}: ${error.message}`);
239
+ }
240
+ const normalized = this.pathResolver.normalizeWorkspacePath(workspaceDir);
241
+ if (normalized !== workspaceDir) {
242
+ logger?.info?.(`[PD:WorkspaceContext] Normalized workspaceDir before validation: ${workspaceDir} -> ${normalized}`);
243
+ workspaceDir = normalized;
244
+ }
245
+ const validationIssue = validateWorkspaceDir(workspaceDir);
246
+ if (validationIssue !== null) {
247
+ const error = {
248
+ ok: false as const,
249
+ reason: 'workspace_dir_invalid',
250
+ message: `workspaceDir validation failed for Runtime V2 entrypoint: ${validationIssue}`,
251
+ nextAction: 'Provide a valid workspaceDir that is not the home directory, root, or empty.',
252
+ };
253
+ logger?.error?.(`[PD:WorkspaceContext] ${error.message}`);
254
+ throw new Error(`[PD:WorkspaceContext] ${error.reason}: ${error.message}`);
255
+ }
256
+ return this.fromHookContext({ ...ctx, workspaceDir });
257
+ }
258
+
213
259
  /**
214
260
  * Resolves a PD file path within the workspace.
215
261
  */
@@ -19,7 +19,7 @@ function tryResolveFromAgent(
19
19
  attempts: string[],
20
20
  ): string | undefined {
21
21
  try {
22
- const resolved = api.runtime.agent.resolveAgentWorkspaceDir(api.config, agentId);
22
+ const resolved = api.runtime?.agent?.resolveAgentWorkspaceDir?.(api.config, agentId);
23
23
  const issue = validateWorkspaceDir(resolved);
24
24
  if (!issue) {
25
25
  return resolved;
@@ -1,4 +1,4 @@
1
- /**
1
+ /**
2
2
  * WorkspaceDir Validation Utilities
3
3
  *
4
4
  * This module only validates candidate workspace directories and delegates
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  import * as os from 'os';
9
+ import * as path from 'path';
9
10
 
10
11
  export interface WorkspaceResolutionContext {
11
12
  workspaceDir?: string;
@@ -17,27 +18,35 @@ export function validateWorkspaceDir(dir: string | undefined): string | null {
17
18
  return 'workspaceDir is undefined/null';
18
19
  }
19
20
 
21
+ if (/^[A-Za-z]:\\?$/.test(dir)) {
22
+ return `workspaceDir is a drive root: "${dir}"`;
23
+ }
24
+
25
+ const resolved = path.resolve(dir);
20
26
  const homeDir = os.homedir();
21
27
 
22
- if (dir === homeDir) {
28
+ if (resolved === homeDir) {
23
29
  return `workspaceDir equals home directory (${homeDir}), likely missing context field`;
24
30
  }
25
31
 
26
- if (dir === '/' || dir === '') {
27
- return `workspaceDir is root or empty: "${dir}"`;
32
+ if (resolved === '/' || resolved === '') {
33
+ return `workspaceDir is root or empty: "${resolved}"`;
34
+ }
35
+
36
+ if (/^[A-Za-z]:\\?$/.test(resolved)) {
37
+ return `workspaceDir is a drive root: "${resolved}"`;
28
38
  }
29
39
 
30
40
  const escapedHome = homeDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
31
41
  const badPatterns = [
32
- { pattern: new RegExp(`^${escapedHome}$`), desc: 'is home directory itself' },
33
- { pattern: new RegExp(`^${escapedHome}/$`), desc: 'is home directory with trailing slash' },
42
+ { pattern: new RegExp(`^${escapedHome}[\\\\/]?$`), desc: 'is home directory' },
34
43
  ];
35
44
 
36
45
  for (const { pattern, desc } of badPatterns) {
37
- if (pattern.test(dir)) {
38
- return `workspaceDir ${desc}: "${dir}"`;
46
+ if (pattern.test(resolved)) {
47
+ return `workspaceDir ${desc}: "${resolved}"`;
39
48
  }
40
49
  }
41
50
 
42
51
  return null;
43
- }
52
+ }
@@ -8,7 +8,7 @@
8
8
  |------------|------|--------------|
9
9
  | `before_prompt_build` | `prompt.ts` | Multi-layer context injection: identity, trust, evolution, principles, thinking OS |
10
10
  | `before_tool_call` | `gate.ts` | Security gate: trust stage checks, risk path blocking, bash security (Cyrillic de-obfuscation, command tokenization) |
11
- | `after_tool_call` | `pain.ts` | Pain detection: failure → pain score → `.pain_flag` evolution queue |
11
+ | `after_tool_call` | `pain.ts` | Pain detection: failure → pain score → Runtime V2 `PainSignalBridge` |
12
12
  | `before_compaction` | `lifecycle.ts` | Checkpoints state before context loss |
13
13
  | `after_compaction` | `lifecycle.ts` | State recovery |
14
14
  | `before_reset` / `session_*` | `lifecycle.ts` | Session lifecycle management |