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
@@ -10,14 +10,15 @@
10
10
  * had their own block persistence implementations.
11
11
  */
12
12
 
13
- import { trackBlock } from '../core/session-tracker.js';
13
+ import { getSession, trackBlock } from '../core/session-tracker.js';
14
14
  import type { WorkspaceContext } from '../core/workspace-context.js';
15
15
  import type { PluginHookBeforeToolCallResult } from '../openclaw-sdk.js';
16
+ import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
17
+ import { emitPainDetectedEvent } from './pain.js';
16
18
  import {
17
19
  TRAJECTORY_GATE_BLOCK_RETRY_DELAY_MS,
18
20
  TRAJECTORY_GATE_BLOCK_MAX_RETRIES
19
21
  } from '../config/index.js';
20
- import { buildPainFlag, writePainFlag } from '../core/pain.js';
21
22
 
22
23
  /**
23
24
  * Block context containing all information needed for block persistence
@@ -99,82 +100,88 @@ export function recordGateBlockAndReturn(
99
100
  scheduleTrajectoryGateBlockRetry(wctx, trajectoryPayload, 1, logWarn, logError);
100
101
  }
101
102
 
102
- // 5. Emit pain signal for gate block (#256)
103
- // Gate blocks are a strong frustration signal the agent tried to do something
104
- // and was blocked by a principle gate. This should feed into the nocturnal pipeline.
103
+ // 5. Record gate block pain context. Runtime V2 diagnosis is gated by GFI
104
+ // so one mild block does not start a long diagnostician run.
105
105
  if (sessionId) {
106
- const GATE_BLOCK_PAIN_SCORE = 30; // Moderate not a failure but a blocked intent
107
- try {
108
- const trajectoryPainId = wctx.trajectory?.recordPainEvent?.({
109
- sessionId,
110
- source: 'gate_blocked',
111
- score: GATE_BLOCK_PAIN_SCORE,
112
- reason: `Gate blocked ${toolName} on ${filePath}: ${reason}`,
113
- severity: 'mild',
114
- origin: 'system_infer',
115
- });
106
+ const GATE_BLOCK_PAIN_SCORE = 45; // Must be >= pain_trigger (40) so single gate block can trigger diagnosis (PRI-274)
107
+ // Record to trajectory (fire-and-forget, no .pain_flag file needed)
108
+ wctx.trajectory?.recordPainEvent?.({
109
+ sessionId,
110
+ source: 'gate_blocked',
111
+ score: GATE_BLOCK_PAIN_SCORE,
112
+ reason: `Gate blocked ${toolName} on ${filePath}: ${reason}`,
113
+ severity: 'mild',
114
+ origin: 'system_infer',
115
+ });
116
116
 
117
- // Update .pain_flag if score is significant
118
- wctx.eventLog.recordPainSignal(sessionId, {
119
- source: 'gate_blocked',
120
- score: GATE_BLOCK_PAIN_SCORE,
121
- reason,
122
- });
117
+ const session = getSession(sessionId);
118
+ const gate = evaluatePainDiagnosticGate({
119
+ source: 'gate_blocked',
120
+ score: GATE_BLOCK_PAIN_SCORE,
121
+ currentGfi: session?.currentGfi ?? 0,
122
+ consecutiveErrors: session?.consecutiveErrors ?? 0,
123
+ sessionId,
124
+ errorHash: `${toolName}:${filePath}:${reason}`,
125
+ thresholds: {
126
+ painTrigger: wctx.config.get('thresholds.pain_trigger') || 40,
127
+ highSeverity: wctx.config.get('severity_thresholds.high') || 70,
128
+ },
129
+ });
123
130
 
124
- // Write to pain flag file (merge with existing if present)
125
- try {
126
-
127
- const workspaceDir = wctx.workspaceDir;
128
- const currentFlag = wctx.eventLog.findLatestPainSignal(sessionId);
129
- const currentScore = currentFlag?.score ?? 0;
130
- if (currentScore < GATE_BLOCK_PAIN_SCORE) {
131
- const flag = buildPainFlag({
132
- source: 'gate_blocked',
133
- score: String(GATE_BLOCK_PAIN_SCORE),
134
- reason: `Gate blocked: ${reason}`,
135
- session_id: sessionId,
136
- agent_id: 'main',
137
- is_risky: false,
138
- pain_event_id: trajectoryPainId !== undefined && trajectoryPainId >= 0 ? String(trajectoryPainId) : undefined,
139
- });
140
- writePainFlag(workspaceDir, flag);
141
- }
142
- } catch (flagErr) {
143
- logWarn(`[PD_GATE] Failed to update pain flag for gate block: ${String(flagErr)}`);
144
- }
145
- } catch (painErr) {
146
- logWarn(`[PD_GATE] Failed to record gate block pain signal: ${String(painErr)}`);
131
+ if (gate.shouldDiagnose) {
132
+ void emitPainDetectedEvent(wctx, {
133
+ ts: new Date().toISOString(),
134
+ type: 'pain_detected',
135
+ data: {
136
+ painId: `gate_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
137
+ painType: 'user_frustration',
138
+ source: 'gate_blocked',
139
+ reason: `Gate blocked ${toolName} on ${filePath}: ${reason}`,
140
+ score: GATE_BLOCK_PAIN_SCORE,
141
+ sessionId,
142
+ agentId: 'main',
143
+ },
144
+ }).catch((emitErr) => {
145
+ logWarn(`[PD_GATE] Failed to emit gate block pain event: ${String(emitErr)}`);
146
+ });
147
+ } else {
148
+ logger.info?.(`[PD_GATE] Gate block recorded without Runtime V2 diagnosis: ${gate.detail}`);
147
149
  }
148
150
  }
149
151
 
150
- // 6. Return consistent block result with operator guidance
152
+ // 6. Return consistent block result with contextual operator guidance
153
+ const blockMessage = buildContextualBlockMessage({ filePath, reason });
154
+
151
155
  return {
152
156
  block: true,
153
- blockReason: `[Principles Disciple] Security Gate Blocked this action.
157
+ blockReason: blockMessage,
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Build contextual block message based on block source.
163
+ * - rule-host: principle-based guidance
164
+ * - default/gate: generic security gate message
165
+ */
166
+ function buildContextualBlockMessage({
167
+ filePath,
168
+ reason,
169
+ }: {
170
+ filePath: string;
171
+ reason: string;
172
+ }): string {
173
+ // rule-host or generic gate blocks
174
+ return `[Principles Disciple] Security Gate Blocked this action.
154
175
  File: ${filePath}
155
176
  Reason: ${reason}
156
177
 
157
178
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
158
179
  📋 How to unblock this operation:
159
180
 
160
- 1. Use the plan-script skill to create a PLAN.md:
161
- Invoke: skill:plan-script
162
-
163
- 2. Fill in the plan with:
164
- - Target Files: ${filePath}
165
- - Steps: What you want to do (be specific)
166
- - Metrics: How to verify success
167
- - Active Mental Models: Select 2 relevant models from .principles/THINKING_OS.md
168
- - Rollback: How to restore if it fails
169
-
170
- 3. After completing the plan, set STATUS: READY in PLAN.md
171
-
172
- 4. Retry the operation
173
-
174
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
175
- This is a mandatory security gate. The operation was blocked because the modification exceeds the allowed threshold for your current evolution tier.
176
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
177
- };
181
+ This action was blocked by a Rule Host principle.
182
+ If the blocked path is correct and safe, explain the reasoning to the owner
183
+ and ask for explicit confirmation to proceed.
184
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;
178
185
  }
179
186
 
180
187
  /**
package/src/hooks/gate.ts CHANGED
@@ -9,13 +9,12 @@
9
9
  * 2. Rule Host: Dynamic principle-based evaluation (sole gate)
10
10
  */
11
11
 
12
- import * as fs from 'fs';
13
- import * as path from 'path';
14
- import { normalizePath, planStatus } from '../utils/io.js';
12
+ import { normalizePath } from '../utils/io.js';
15
13
  import { WorkspaceContext } from '../core/workspace-context.js';
16
14
  import { recordGateBlockAndReturn } from './gate-block-helper.js';
17
15
  import { RuleHost } from '../core/rule-host.js';
18
- import type { RuleHostInput } from '../core/rule-host-types.js';
16
+ import type { RuleHostInput } from '@principles/core/runtime-v2';
17
+ import { validateCorrectionProposal, validateProposedPathBounds } from '@principles/core/runtime-v2';
19
18
  import type { PluginHookBeforeToolCallEvent, PluginHookToolContext, PluginHookBeforeToolCallResult, PluginLogger } from '../openclaw-sdk.js';
20
19
  import { AGENT_TOOLS, BASH_TOOLS_SET, WRITE_TOOLS } from '../constants/tools.js';
21
20
  import { getSession, hasRecentThinking } from '../core/session-tracker.js';
@@ -41,11 +40,11 @@ export function handleBeforeToolCall(
41
40
  const wctx = WorkspaceContext.fromHookContext(ctx);
42
41
 
43
42
  // 2. Resolve the target file path
44
- let filePath = event.params.file_path || event.params.path || event.params.file || event.params.target;
43
+ let filePath = event.params?.file_path || event.params?.path || event.params?.file || event.params?.target;
45
44
 
46
45
  // Heuristic for bash mutation detection
47
46
  if (isBash && !filePath) {
48
- const command = String(event.params.command || event.params.args || '');
47
+ const command = String(event.params?.command || event.params?.args || '');
49
48
  const mutationMatch = /(?:>|>>|sed\s+-i|rm|mv|mkdir|touch|cp)\s+(?:-[a-zA-Z]+\s+)*([^\s;&|<>]+)/.exec(command);
50
49
 
51
50
  if (mutationMatch) {
@@ -56,6 +55,12 @@ export function handleBeforeToolCall(
56
55
  }
57
56
  }
58
57
 
58
+ // Write tools without a file path must still go through RuleHost evaluation.
59
+ // Use a synthetic path so RuleHost can evaluate and potentially block.
60
+ if (!filePath && isWriteTool) {
61
+ filePath = `<tool:${event.toolName}>`;
62
+ }
63
+
59
64
  if (typeof filePath !== 'string') return;
60
65
 
61
66
  const relPath = normalizePath(filePath, ctx.workspaceDir);
@@ -67,12 +72,16 @@ export function handleBeforeToolCall(
67
72
  action: {
68
73
  toolName: event.toolName,
69
74
  normalizedPath: relPath,
70
- paramsSummary: _extractParamsSummary(event.params),
75
+ paramsSummary: _extractParamsSummary(event.params ?? {}),
71
76
  },
72
77
  workspace: {
73
78
  isRiskPath: false, // Rule Host determines risk dynamically
74
- planStatus: _getPlanStatus(ctx.workspaceDir),
75
- hasPlanFile: _hasPlanFile(ctx.workspaceDir),
79
+ // DEPRECATED (PRI-286): planStatus/hasPlanFile are legacy compatibility fields.
80
+ // Live PD no longer reads or manages PLAN.md state. These fields must not be
81
+ // used for new MVP behavior. Future "plan-first" enforcement must come from
82
+ // owner-approved RuleHost/code_tool_hook activation, not built-in state.
83
+ planStatus: 'NONE' as const,
84
+ hasPlanFile: false,
76
85
  },
77
86
  session: {
78
87
  sessionId: ctx.sessionId,
@@ -83,7 +92,7 @@ export function handleBeforeToolCall(
83
92
  epTier: _getEpTier(wctx.workspaceDir),
84
93
  },
85
94
  derived: {
86
- estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params }),
95
+ estimatedLineChanges: estimateLineChanges({ toolName: event.toolName, params: event.params ?? {} }),
87
96
  bashRisk: _getBashRisk(event),
88
97
  },
89
98
  };
@@ -153,8 +162,170 @@ export function handleBeforeToolCall(
153
162
  logger?.warn?.(`[PD_GATE] Failed to record rule_enforced/rulehost_requireApproval: ${String(evErr)}`);
154
163
  }
155
164
  }
165
+
166
+ if (hostResult?.decision === 'auto_correct' && hostResult.correctionProposal) {
167
+ const proposal = hostResult.correctionProposal;
168
+ let validation: { valid: boolean; errors: string[] };
169
+ try {
170
+ validation = validateCorrectionProposal(proposal);
171
+ } catch (validationError: unknown) {
172
+ validation = { valid: false, errors: [`Validator threw: ${String(validationError)}`] };
173
+ }
174
+
175
+ try {
176
+ const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
177
+ const correctedFields = Array.isArray(proposal.correctedFields)
178
+ ? proposal.correctedFields.map((f: unknown) => typeof f === 'object' && f !== null ? String((f as { field?: string }).field) : String(f))
179
+ : [];
180
+ eventLog.recordRuleHostAutoCorrectProposed({
181
+ toolName: event.toolName,
182
+ filePath: relPath,
183
+ ruleId: String(proposal.ruleId ?? 'unknown'),
184
+ principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
185
+ confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
186
+ reason: hostResult.reason,
187
+ applicationMode: proposal.applicationMode === 'live' ? 'live' : 'shadow',
188
+ correctedFields,
189
+ validationValid: validation.valid,
190
+ });
191
+ } catch (evErr) {
192
+ logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_proposed: ${String(evErr)}`);
193
+ }
194
+
195
+ if (proposal.applicationMode === 'live' && validation.valid) {
196
+ if (!event.params) {
197
+ return;
198
+ }
199
+ const originalParams = { ...event.params };
200
+ const nextParams: Record<string, unknown> = {};
201
+ const appliedFields: Array<{ field: string; original: unknown; applied: unknown }> = [];
202
+
203
+ try {
204
+ if (!Array.isArray(proposal.correctedFields)) {
205
+ throw new Error('proposal.correctedFields is not an array');
206
+ }
207
+
208
+ if (!proposal.proposedParams || typeof proposal.proposedParams !== 'object' || Array.isArray(proposal.proposedParams)) {
209
+ throw new Error('proposal.proposedParams must be an object');
210
+ }
211
+
212
+ const trustedWorkspaceDir = ctx.workspaceDir;
213
+ if (typeof trustedWorkspaceDir === 'string' && trustedWorkspaceDir.trim().length > 0) {
214
+ const pathBoundsResult = validateProposedPathBounds(proposal.proposedParams, trustedWorkspaceDir);
215
+ if (!pathBoundsResult.valid) {
216
+ throw new Error(`Path boundary violation: ${pathBoundsResult.reason}`);
217
+ }
218
+ } else {
219
+ const hasPathField = Object.keys(proposal.proposedParams).some(k => typeof proposal.proposedParams[k] === 'string' && (k === 'file_path' || k === 'path' || k === 'filePath'));
220
+ if (hasPathField) {
221
+ throw new Error('Cannot apply live auto-correction with path fields: no trusted workspace directory available');
222
+ }
223
+ }
224
+
225
+ for (const cf of proposal.correctedFields) {
226
+ if (typeof cf !== 'object' || cf === null || typeof cf.field !== 'string') {
227
+ throw new Error('correctedFields entry must be an object with a string field');
228
+ }
229
+ const field = cf.field;
230
+ if (!Object.hasOwn(event.params, field)) {
231
+ throw new Error(`Field '${field}' not found in event.params`);
232
+ }
233
+ if (!Object.hasOwn(proposal.proposedParams, field)) {
234
+ throw new Error(`Field '${field}' not found in proposal.proposedParams`);
235
+ }
236
+ }
237
+
238
+ for (const cf of proposal.correctedFields) {
239
+ if (typeof cf === 'object' && cf !== null && typeof cf.field === 'string') {
240
+ const field = cf.field;
241
+ const originalValue = event.params[field];
242
+ const appliedValue = proposal.proposedParams[field];
243
+ nextParams[field] = appliedValue;
244
+ appliedFields.push({
245
+ field,
246
+ original: originalValue,
247
+ applied: appliedValue,
248
+ });
249
+ }
250
+ }
251
+
252
+ Object.assign(event.params, nextParams);
253
+
254
+ try {
255
+ const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
256
+ eventLog.recordRuleHostAutoCorrectApplied({
257
+ toolName: event.toolName,
258
+ filePath: relPath,
259
+ ruleId: String(proposal.ruleId ?? 'unknown'),
260
+ principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
261
+ confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
262
+ reason: hostResult.reason || proposal.correctedFields?.[0]?.reason || 'auto-correct applied',
263
+ correctedFields: appliedFields,
264
+ });
265
+ } catch (evErr) {
266
+ logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_applied: ${String(evErr)}`);
267
+ }
268
+
269
+ if (proposal.notifyAgent === true && appliedFields.length > 0) {
270
+ const messages = appliedFields.map(f =>
271
+ `[PD Auto-Correct] Rule ${proposal.ruleId}: ${proposal.correctedFields?.[0]?.reason || 'correction applied'}. Parameter '${f.field}' was adjusted from ${JSON.stringify(f.original)} to ${JSON.stringify(f.applied)}.`
272
+ );
273
+ return {
274
+ toolArgs: event.toolArgs,
275
+ skipToolCall: false,
276
+ _pdAutoCorrectWarning: messages.join('\n'),
277
+ };
278
+ }
279
+ } catch (applyError: unknown) {
280
+ if (event.params) {
281
+ Object.assign(event.params, originalParams);
282
+ }
283
+ const errorMsg = String(applyError);
284
+ const isPathViolation = errorMsg.includes('Path boundary violation') || errorMsg.includes('no trusted workspace directory');
285
+ if (isPathViolation) {
286
+ logger?.warn?.(`[PD_GATE] Live auto-correction rejected — path out of bounds: ${errorMsg}`);
287
+ try {
288
+ const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
289
+ eventLog.recordRuleHostAutoCorrectProposed({
290
+ toolName: event.toolName,
291
+ filePath: relPath,
292
+ ruleId: String(proposal.ruleId ?? 'unknown'),
293
+ principleId: proposal.principleId != null ? String(proposal.principleId) : undefined,
294
+ confidence: typeof proposal.confidence === 'number' ? proposal.confidence : 0,
295
+ reason: `Path boundary rejected: ${errorMsg}`,
296
+ applicationMode: 'shadow',
297
+ correctedFields: Array.isArray(proposal.correctedFields)
298
+ ? proposal.correctedFields.map((f: unknown) => typeof f === 'object' && f !== null ? String((f as { field?: string }).field) : String(f))
299
+ : [],
300
+ validationValid: false,
301
+ });
302
+ } catch (evErr) {
303
+ logger?.warn?.(`[PD_GATE] Failed to record path rejection telemetry: ${String(evErr)}`);
304
+ }
305
+ } else {
306
+ logger?.warn?.(`[PD_GATE] Failed to apply auto-correction, using original params: ${errorMsg}`);
307
+ }
308
+ }
309
+ }
310
+ } else if (hostResult?.decision === 'auto_correct') {
311
+ // auto_correct without correctionProposal — emit telemetry for observability
312
+ try {
313
+ const eventLog = EventLogService.get(wctx.stateDir, logger as PluginLogger | undefined);
314
+ eventLog.recordRuleHostAutoCorrectProposed({
315
+ toolName: event.toolName,
316
+ filePath: relPath,
317
+ ruleId: hostResult.ruleId ?? 'unknown',
318
+ confidence: 0,
319
+ reason: hostResult.reason ?? 'auto_correct without correctionProposal',
320
+ applicationMode: 'shadow',
321
+ correctedFields: [],
322
+ validationValid: false,
323
+ });
324
+ } catch (evErr) {
325
+ logger?.warn?.(`[PD_GATE] Failed to record rulehost_auto_correct_proposed (no proposal): ${String(evErr)}`);
326
+ }
327
+ }
156
328
  } catch (hostError: unknown) {
157
- // D-08: Conservative degradation — log and allow on Rule Host failure
158
329
  logger.warn?.(`[PD_GATE:RULE_HOST] Host evaluation failed, allowing conservatively: ${String(hostError)}`);
159
330
  }
160
331
 
@@ -177,25 +348,6 @@ function _extractParamsSummary(params: Record<string, unknown>): Record<string,
177
348
  return summary;
178
349
  }
179
350
 
180
- function _getPlanStatus(workspaceDir: string): 'NONE' | 'DRAFT' | 'READY' | 'UNKNOWN' {
181
- try {
182
- const status = planStatus(workspaceDir);
183
- if (status === 'READY') return 'READY';
184
- if (status === 'DRAFT') return 'DRAFT';
185
- if (status === '') return 'NONE';
186
- return 'UNKNOWN';
187
- } catch {
188
- return 'UNKNOWN';
189
- }
190
- }
191
-
192
- function _hasPlanFile(workspaceDir: string): boolean {
193
- try {
194
- return fs.existsSync(path.join(workspaceDir, 'PLAN.md'));
195
- } catch {
196
- return false;
197
- }
198
- }
199
351
 
200
352
  function _getCurrentGfi(sessionId?: string): number {
201
353
  if (!sessionId) return 0;
@@ -227,7 +379,7 @@ function _getEpTier(workspaceDir: string): number {
227
379
  function _getBashRisk(event: PluginHookBeforeToolCallEvent): 'safe' | 'normal' | 'dangerous' | 'unknown' {
228
380
  if (!BASH_TOOLS_SET.has(event.toolName)) return 'unknown';
229
381
  try {
230
- const command = String(event.params.command || event.params.args || '');
382
+ const command = String(event.params?.command || event.params?.args || '');
231
383
  const isDangerous = /\brm\s+-rf\b|\bchmod\b|\bchown\b|>\s*\/dev\//.test(command);
232
384
  if (isDangerous) return 'dangerous';
233
385
  const isMutation = /(?:>|>>|sed|rm|mv|mkdir|touch|cp|npm|yarn|pnpm|pip|cargo)/.test(command);
@@ -1,7 +1,6 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import * as readline from 'readline';
4
- import { recordAndWritePainFlag } from '../core/pain.js';
5
4
  import { atomicWriteFileSync } from '../utils/io.js';
6
5
  import { WorkspaceContext } from '../core/workspace-context.js';
7
6
  import { PD_DIRS } from '../core/paths.js';
@@ -10,6 +9,7 @@ import {
10
9
  mergeWorkingMemory,
11
10
  } from '../core/focus-history.js';
12
11
  import type { PluginHookBeforeResetEvent, PluginHookBeforeCompactionEvent, PluginHookAfterCompactionEvent, PluginHookAgentContext } from '../openclaw-sdk.js';
12
+ import { emitPainDetectedEvent } from './pain.js';
13
13
 
14
14
  export async function handleBeforeReset(
15
15
  event: PluginHookBeforeResetEvent,
@@ -66,7 +66,7 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
66
66
  return;
67
67
  }
68
68
 
69
- if (ctx.logger) ctx.logger.info(`[Pain Extractor] Scanning session transcript for pain signals: ${sessionFile}`);
69
+ ctx.logger?.info?.(`[Pain Extractor] Scanning session transcript for pain signals: ${sessionFile}`);
70
70
 
71
71
  const fileStream = fs.createReadStream(sessionFile);
72
72
  const rl = readline.createInterface({
@@ -97,13 +97,13 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
97
97
 
98
98
  if (msg.openclawAbort?.aborted) {
99
99
  const runIdSafe = msg.openclawAbort?.runId || 'unknown';
100
- if (ctx.logger) ctx.logger.info(`[Pain Extractor] Detected hard-abort snapshot (runId: ${runIdSafe})`);
100
+ ctx.logger?.info?.(`[Pain Extractor] Detected hard-abort snapshot (runId: ${runIdSafe})`);
101
101
  painPoints.push(`[FATAL INTERCEPT] 动作被沙箱防御机制强制击落。大模型被击落前的思考流 (未遂动机): ${text.substring(0, 250)}...`);
102
102
  continue;
103
103
  }
104
104
 
105
105
  if (msg.__openclaw?.truncated && msg.__openclaw?.reason === 'oversized') {
106
- if (ctx.logger) ctx.logger.info(`[Pain Extractor] Detected oversized data truncation placeholder`);
106
+ ctx.logger?.info?.(`[Pain Extractor] Detected oversized data truncation placeholder`);
107
107
  painPoints.push(`[COGNITIVE OVERLOAD] 大模型尝试读取极大体积的输入,已被底层守护程序抹除/折叠防爆。请反思是否读取了不当的文件或日志: ${text.substring(0, 150)}...`);
108
108
  continue;
109
109
  }
@@ -154,21 +154,19 @@ export async function extractPainFromSessionFile(sessionFile: string, ctx: Plugi
154
154
 
155
155
  const hasFatal = painPoints.some(p => p.includes('[FATAL INTERCEPT]'));
156
156
  if (hasFatal) {
157
- recordAndWritePainFlag(wctx, {
158
- sessionId: ctx.sessionId || 'unknown',
159
- source: 'intercept_extraction',
160
- score: 100,
161
- reason: 'Hard intercept detected in session history compaction.',
162
- severity: 'severe',
163
- origin: 'system_infer',
164
- }, {
165
- source: 'intercept_extraction',
166
- score: '100',
167
- reason: 'Hard intercept detected in session history compaction.',
168
- is_risky: true,
169
- trigger_text_preview: painPoints.find(p => p.includes('[FATAL INTERCEPT]'))?.substring(0, 150) || 'Fatal intercept',
170
- session_id: ctx.sessionId || '',
171
- agent_id: ctx.agentId || '',
157
+ // Emit via the Runtime v2 pain chain — no .pain_flag file written
158
+ emitPainDetectedEvent(wctx, {
159
+ ts: new Date().toISOString(),
160
+ type: 'pain_detected',
161
+ data: {
162
+ painId: `intercept_${Date.now()}`,
163
+ painType: 'tool_failure' as const,
164
+ source: 'intercept_extraction',
165
+ reason: 'Hard intercept detected in session history compaction.',
166
+ score: 100,
167
+ sessionId: ctx.sessionId || 'unknown',
168
+ agentId: ctx.agentId,
169
+ },
172
170
  });
173
171
  }
174
172
  } catch (err) {
@@ -200,13 +198,14 @@ export async function handleBeforeCompaction(
200
198
  }
201
199
 
202
200
  // 提取工作记忆(从 sessionFile)
203
- if (event.sessionFile) {
204
- await extractPainFromSessionFile(event.sessionFile, ctx);
201
+ const sessionFile = (event as { sessionFile?: string }).sessionFile;
202
+ if (sessionFile) {
203
+ await extractPainFromSessionFile(sessionFile, ctx);
205
204
 
206
205
  // 新增:提取并保存工作记忆
207
-
208
-
209
- await extractAndSaveWorkingMemory(event.sessionFile, ctx, wctx);
206
+
207
+
208
+ await extractAndSaveWorkingMemory(sessionFile, ctx, wctx);
210
209
  }
211
210
  }
212
211
 
@@ -296,20 +295,18 @@ async function extractAndSaveWorkingMemory(
296
295
  // 写入文件
297
296
  atomicWriteFileSync(focusPath, updatedContent);
298
297
 
299
- if (ctx.logger) {
300
- ctx.logger.info(`[WorkingMemory] Preserved ${snapshot.artifacts.length} artifacts, ` +
301
- `${snapshot.activeProblems.length} problems, ` +
302
- `${snapshot.nextActions.length} next actions to CURRENT_FOCUS.md`);
303
- }
298
+ ctx.logger?.info?.(`[WorkingMemory] Preserved ${snapshot.artifacts.length} artifacts, ` +
299
+ `${snapshot.activeProblems.length} problems, ` +
300
+ `${snapshot.nextActions.length} next actions to CURRENT_FOCUS.md`);
304
301
  } catch (err) {
305
302
  ctx.logger?.error?.(`[PD:Lifecycle] Failed to save working memory: ${String(err)}`);
306
-
303
+
307
304
  // 尝试恢复备份
308
305
  const backupPath = `${focusPath}.wm-backup`;
309
306
  if (fs.existsSync(backupPath)) {
310
307
  try {
311
308
  fs.copyFileSync(backupPath, focusPath);
312
- if (ctx.logger) ctx.logger.warn(`[WorkingMemory] Restored from backup after failure`);
309
+ ctx.logger?.warn?.(`[WorkingMemory] Restored from backup after failure`);
313
310
  } catch {
314
311
  // 忽略恢复错误
315
312
  }
@@ -325,8 +322,9 @@ export async function handleAfterCompaction(
325
322
 
326
323
  const [dateStrPost] = new Date().toISOString().split('T');
327
324
  const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStrPost}.md`);
325
+ const messageCount = (event as { messageCount?: number }).messageCount ?? 0;
328
326
  const log =
329
- `- Post-Compaction Complete. Reduced active context to ${event.messageCount} messages.\n`;
327
+ `- Post-Compaction Complete. Reduced active context to ${messageCount} messages.\n`;
330
328
 
331
329
  try {
332
330
  fs.appendFileSync(checkpointPath, log, 'utf8');