create-principles-disciple 1.91.0 → 1.93.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 (263) hide show
  1. package/README.md +1 -1
  2. package/console/dist/server/config/AuthConfig.js +6 -0
  3. package/console/dist/server/index.js +31 -16
  4. package/console/dist/server/models/ApprovalsGroupedConsoleModel.js +6 -3
  5. package/console/dist/server/models/ConsoleLifecycleDatasource.js +1 -1
  6. package/console/dist/server/routes/activations.js +4 -0
  7. package/console/dist/server/routes/approvals.js +24 -4
  8. package/console/dist/server/routes/feedback-reports.js +7 -5
  9. package/console/dist/server/routes/lifecycle.js +6 -1
  10. package/console/dist/server/routes/principles.js +10 -2
  11. package/console/dist/server/routes/workspaces.js +4 -0
  12. package/console/dist/ui/components/ui/charts.js +4 -1
  13. package/console/dist/ui/components/ui/markdown.js +9 -5
  14. package/console/dist/ui/i18n/en.json +1 -2
  15. package/console/dist/ui/i18n/zh-CN.json +1 -2
  16. package/console/dist/ui/utils/enum-labels.js +4 -2
  17. package/console/dist/web/assets/app.js +8 -8
  18. package/core/dist/index.d.ts +0 -1
  19. package/core/dist/index.d.ts.map +1 -1
  20. package/core/dist/index.js +0 -2
  21. package/core/dist/index.js.map +1 -1
  22. package/core/dist/pain-signal.d.ts +11 -71
  23. package/core/dist/pain-signal.d.ts.map +1 -1
  24. package/core/dist/pain-signal.js +10 -120
  25. package/core/dist/pain-signal.js.map +1 -1
  26. package/core/dist/principle-tree-ledger.d.ts +4 -87
  27. package/core/dist/principle-tree-ledger.d.ts.map +1 -1
  28. package/core/dist/principle-tree-ledger.js +51 -148
  29. package/core/dist/principle-tree-ledger.js.map +1 -1
  30. package/core/dist/runtime-v2/__tests__/activation-types-helpers.test.js +6 -14
  31. package/core/dist/runtime-v2/__tests__/activation-types-helpers.test.js.map +1 -1
  32. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js +180 -70
  33. package/core/dist/runtime-v2/__tests__/architecture-regression.test.js.map +1 -1
  34. package/core/dist/runtime-v2/__tests__/barrel-io-cleanup.test.d.ts +12 -0
  35. package/core/dist/runtime-v2/__tests__/barrel-io-cleanup.test.d.ts.map +1 -0
  36. package/core/dist/runtime-v2/__tests__/barrel-io-cleanup.test.js +46 -0
  37. package/core/dist/runtime-v2/__tests__/barrel-io-cleanup.test.js.map +1 -0
  38. package/core/dist/runtime-v2/__tests__/intake-to-internalization-bridge.test.js +1 -2
  39. package/core/dist/runtime-v2/__tests__/intake-to-internalization-bridge.test.js.map +1 -1
  40. package/core/dist/runtime-v2/__tests__/internalization-orchestrator.test.js +8 -8
  41. package/core/dist/runtime-v2/__tests__/internalization-orchestrator.test.js.map +1 -1
  42. package/core/dist/runtime-v2/__tests__/internalization-peer-runner-contracts.test.js +45 -80
  43. package/core/dist/runtime-v2/__tests__/internalization-peer-runner-contracts.test.js.map +1 -1
  44. package/core/dist/runtime-v2/__tests__/internalization-state-machine.test.js +31 -130
  45. package/core/dist/runtime-v2/__tests__/internalization-state-machine.test.js.map +1 -1
  46. package/core/dist/runtime-v2/__tests__/io-deletion.test.d.ts +12 -0
  47. package/core/dist/runtime-v2/__tests__/io-deletion.test.d.ts.map +1 -0
  48. package/core/dist/runtime-v2/__tests__/io-deletion.test.js +92 -0
  49. package/core/dist/runtime-v2/__tests__/io-deletion.test.js.map +1 -0
  50. package/core/dist/runtime-v2/__tests__/ledger-codec.test.d.ts +2 -0
  51. package/core/dist/runtime-v2/__tests__/ledger-codec.test.d.ts.map +1 -0
  52. package/core/dist/runtime-v2/__tests__/ledger-codec.test.js +429 -0
  53. package/core/dist/runtime-v2/__tests__/ledger-codec.test.js.map +1 -0
  54. package/core/dist/runtime-v2/__tests__/ledger-store.test.d.ts +2 -0
  55. package/core/dist/runtime-v2/__tests__/ledger-store.test.d.ts.map +1 -0
  56. package/core/dist/runtime-v2/__tests__/ledger-store.test.js +65 -0
  57. package/core/dist/runtime-v2/__tests__/ledger-store.test.js.map +1 -0
  58. package/core/dist/runtime-v2/__tests__/pain-signal-observability.test.js +146 -0
  59. package/core/dist/runtime-v2/__tests__/pain-signal-observability.test.js.map +1 -1
  60. package/core/dist/runtime-v2/__tests__/pain-signal-unification.test.d.ts +2 -0
  61. package/core/dist/runtime-v2/__tests__/pain-signal-unification.test.d.ts.map +1 -0
  62. package/core/dist/runtime-v2/__tests__/pain-signal-unification.test.js +150 -0
  63. package/core/dist/runtime-v2/__tests__/pain-signal-unification.test.js.map +1 -0
  64. package/core/dist/runtime-v2/__tests__/pain-to-principle-service.test.js +0 -1
  65. package/core/dist/runtime-v2/__tests__/pain-to-principle-service.test.js.map +1 -1
  66. package/core/dist/runtime-v2/__tests__/pitask-metadata.test.js +4 -4
  67. package/core/dist/runtime-v2/__tests__/pitask-metadata.test.js.map +1 -1
  68. package/core/dist/runtime-v2/__tests__/proven-channel-baseline.test.js +2 -5
  69. package/core/dist/runtime-v2/__tests__/proven-channel-baseline.test.js.map +1 -1
  70. package/core/dist/runtime-v2/__tests__/story-a-demo.test.js +2 -2
  71. package/core/dist/runtime-v2/__tests__/story-a-demo.test.js.map +1 -1
  72. package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js +7 -10
  73. package/core/dist/runtime-v2/activation/__tests__/activation-dispatcher.test.js.map +1 -1
  74. package/core/dist/runtime-v2/activation/__tests__/approval-queue.test.js +1 -3
  75. package/core/dist/runtime-v2/activation/__tests__/approval-queue.test.js.map +1 -1
  76. package/core/dist/runtime-v2/activation/__tests__/approval-store-extended.test.js +4 -4
  77. package/core/dist/runtime-v2/activation/__tests__/sqlite-approval-store.test.js +6 -6
  78. package/core/dist/runtime-v2/activation/__tests__/sqlite-approval-store.test.js.map +1 -1
  79. package/core/dist/runtime-v2/activation/activation-dispatcher.d.ts.map +1 -1
  80. package/core/dist/runtime-v2/activation/activation-dispatcher.js +0 -1
  81. package/core/dist/runtime-v2/activation/activation-dispatcher.js.map +1 -1
  82. package/core/dist/runtime-v2/activation/activation-types.d.ts.map +1 -1
  83. package/core/dist/runtime-v2/activation/activation-types.js +0 -1
  84. package/core/dist/runtime-v2/activation/activation-types.js.map +1 -1
  85. package/core/dist/runtime-v2/adapter/__tests__/test-double-runtime-adapter.test.js +199 -0
  86. package/core/dist/runtime-v2/adapter/__tests__/test-double-runtime-adapter.test.js.map +1 -1
  87. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
  88. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +0 -2
  89. package/core/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
  90. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js +0 -7
  91. package/core/dist/runtime-v2/config/__tests__/pd-config-agent-binding.test.js.map +1 -1
  92. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js +5 -6
  93. package/core/dist/runtime-v2/config/__tests__/pd-config-contract.test.js.map +1 -1
  94. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js +0 -1
  95. package/core/dist/runtime-v2/config/__tests__/pd-config-redaction-deep.test.js.map +1 -1
  96. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js +0 -1
  97. package/core/dist/runtime-v2/config/__tests__/pd-config-validation-edge-cases.test.js.map +1 -1
  98. package/core/dist/runtime-v2/config/pd-config-defaults.d.ts.map +1 -1
  99. package/core/dist/runtime-v2/config/pd-config-defaults.js +0 -1
  100. package/core/dist/runtime-v2/config/pd-config-defaults.js.map +1 -1
  101. package/core/dist/runtime-v2/config/pd-config-types.d.ts +1 -1
  102. package/core/dist/runtime-v2/config/pd-config-types.d.ts.map +1 -1
  103. package/core/dist/runtime-v2/config/pd-config-types.js +0 -1
  104. package/core/dist/runtime-v2/config/pd-config-types.js.map +1 -1
  105. package/core/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.js +156 -0
  106. package/core/dist/runtime-v2/diagnostician/__tests__/diag-rootcause-output.test.js.map +1 -1
  107. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.d.ts.map +1 -1
  108. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js +0 -2
  109. package/core/dist/runtime-v2/feature-flags/feature-flag-contract.js.map +1 -1
  110. package/core/dist/runtime-v2/index.d.ts +2 -9
  111. package/core/dist/runtime-v2/index.d.ts.map +1 -1
  112. package/core/dist/runtime-v2/index.js +5 -6
  113. package/core/dist/runtime-v2/index.js.map +1 -1
  114. package/core/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js +0 -3
  115. package/core/dist/runtime-v2/internalization/__tests__/diag-chain-e2e.test.js.map +1 -1
  116. package/core/dist/runtime-v2/internalization/__tests__/internalization-job-graph.test.js +4 -36
  117. package/core/dist/runtime-v2/internalization/__tests__/internalization-job-graph.test.js.map +1 -1
  118. package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.js +0 -1
  119. package/core/dist/runtime-v2/internalization/__tests__/peer-runner-lineage-injection.test.js.map +1 -1
  120. package/core/dist/runtime-v2/internalization/__tests__/queue-actionability.test.js +0 -15
  121. package/core/dist/runtime-v2/internalization/__tests__/queue-actionability.test.js.map +1 -1
  122. package/core/dist/runtime-v2/internalization/index.d.ts +1 -7
  123. package/core/dist/runtime-v2/internalization/index.d.ts.map +1 -1
  124. package/core/dist/runtime-v2/internalization/index.js +1 -4
  125. package/core/dist/runtime-v2/internalization/index.js.map +1 -1
  126. package/core/dist/runtime-v2/internalization/internalization-job-graph.d.ts +7 -18
  127. package/core/dist/runtime-v2/internalization/internalization-job-graph.d.ts.map +1 -1
  128. package/core/dist/runtime-v2/internalization/internalization-job-graph.js +7 -24
  129. package/core/dist/runtime-v2/internalization/internalization-job-graph.js.map +1 -1
  130. package/core/dist/runtime-v2/internalization/internalization-state-machine.d.ts +3 -2
  131. package/core/dist/runtime-v2/internalization/internalization-state-machine.d.ts.map +1 -1
  132. package/core/dist/runtime-v2/internalization/internalization-state-machine.js +4 -3
  133. package/core/dist/runtime-v2/internalization/internalization-state-machine.js.map +1 -1
  134. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts +6 -6
  135. package/core/dist/runtime-v2/internalization/peer-runner-contracts.d.ts.map +1 -1
  136. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js +0 -3
  137. package/core/dist/runtime-v2/internalization/peer-runner-contracts.js.map +1 -1
  138. package/core/dist/runtime-v2/principle-tree/ledger-codec.d.ts +30 -0
  139. package/core/dist/runtime-v2/principle-tree/ledger-codec.d.ts.map +1 -0
  140. package/core/dist/runtime-v2/principle-tree/ledger-codec.js +154 -0
  141. package/core/dist/runtime-v2/principle-tree/ledger-codec.js.map +1 -0
  142. package/core/dist/runtime-v2/recovery-sweep-service.d.ts.map +1 -1
  143. package/core/dist/runtime-v2/recovery-sweep-service.js +2 -2
  144. package/core/dist/runtime-v2/recovery-sweep-service.js.map +1 -1
  145. package/core/dist/runtime-v2/store/candidate/__tests__/recommendation-kind-resolver.test.js +1 -1
  146. package/core/dist/runtime-v2/store/candidate/__tests__/recommendation-kind-resolver.test.js.map +1 -1
  147. package/core/dist/runtime-v2/types/index.d.ts +2 -0
  148. package/core/dist/runtime-v2/types/index.d.ts.map +1 -1
  149. package/core/dist/runtime-v2/types/index.js +1 -0
  150. package/core/dist/runtime-v2/types/index.js.map +1 -1
  151. package/core/dist/runtime-v2/types/ledger-store.d.ts +101 -0
  152. package/core/dist/runtime-v2/types/ledger-store.d.ts.map +1 -0
  153. package/core/dist/runtime-v2/types/ledger-store.js +15 -0
  154. package/core/dist/runtime-v2/types/ledger-store.js.map +1 -0
  155. package/core/dist/runtime-v2/types/pain-signal.d.ts +5 -3
  156. package/core/dist/runtime-v2/types/pain-signal.d.ts.map +1 -1
  157. package/core/dist/runtime-v2/types/pain-signal.js +23 -3
  158. package/core/dist/runtime-v2/types/pain-signal.js.map +1 -1
  159. package/core/package.json +3 -3
  160. package/dist/installer.d.ts.map +1 -1
  161. package/dist/installer.js +42 -5
  162. package/dist/installer.js.map +1 -1
  163. package/dist/mvp-config.d.ts +1 -1
  164. package/dist/mvp-config.d.ts.map +1 -1
  165. package/dist/mvp-config.js +4 -8
  166. package/dist/mvp-config.js.map +1 -1
  167. package/dist/utils/env.d.ts.map +1 -1
  168. package/dist/utils/env.js +10 -4
  169. package/dist/utils/env.js.map +1 -1
  170. package/package.json +1 -1
  171. package/pd-cli/dist/commands/__tests__/legacy-cleanup.test.js +11 -3
  172. package/pd-cli/dist/commands/__tests__/legacy-cleanup.test.js.map +1 -1
  173. package/pd-cli/dist/commands/build-trajectory-evidence.d.ts.map +1 -1
  174. package/pd-cli/dist/commands/build-trajectory-evidence.js +11 -5
  175. package/pd-cli/dist/commands/build-trajectory-evidence.js.map +1 -1
  176. package/pd-cli/dist/commands/candidate.d.ts.map +1 -1
  177. package/pd-cli/dist/commands/candidate.js +2 -1
  178. package/pd-cli/dist/commands/candidate.js.map +1 -1
  179. package/pd-cli/dist/commands/health.d.ts.map +1 -1
  180. package/pd-cli/dist/commands/health.js +2 -1
  181. package/pd-cli/dist/commands/health.js.map +1 -1
  182. package/pd-cli/dist/commands/runtime-activation.js.map +1 -1
  183. package/pd-cli/dist/commands/runtime-internalization-integrity.d.ts.map +1 -1
  184. package/pd-cli/dist/commands/runtime-internalization-integrity.js +3 -1
  185. package/pd-cli/dist/commands/runtime-internalization-integrity.js.map +1 -1
  186. package/pd-cli/dist/commands/runtime-internalization-run-once.d.ts.map +1 -1
  187. package/pd-cli/dist/commands/runtime-internalization-run-once.js +3 -61
  188. package/pd-cli/dist/commands/runtime-internalization-run-once.js.map +1 -1
  189. package/pd-cli/dist/commands/runtime-uat.d.ts.map +1 -1
  190. package/pd-cli/dist/commands/runtime-uat.js +4 -1
  191. package/pd-cli/dist/commands/runtime-uat.js.map +1 -1
  192. package/pd-cli/dist/legacy/session-history-import.js +7 -5
  193. package/pd-cli/dist/legacy/session-history-import.js.map +1 -1
  194. package/pd-cli/dist/services/__tests__/runtime-adapter-resolver.test.js +13 -2
  195. package/pd-cli/dist/services/__tests__/runtime-adapter-resolver.test.js.map +1 -1
  196. package/pd-cli/dist/services/pain-flood-simulation-runner.d.ts.map +1 -1
  197. package/pd-cli/dist/services/pain-flood-simulation-runner.js +6 -2
  198. package/pd-cli/dist/services/pain-flood-simulation-runner.js.map +1 -1
  199. package/pd-cli/dist/services/runtime-adapter-resolver.d.ts.map +1 -1
  200. package/pd-cli/dist/services/runtime-adapter-resolver.js +6 -4
  201. package/pd-cli/dist/services/runtime-adapter-resolver.js.map +1 -1
  202. package/plugin/dist/commands/capabilities.js +3 -2
  203. package/plugin/dist/commands/disable-impl.js +1 -1
  204. package/plugin/dist/commands/focus.js +10 -2
  205. package/plugin/dist/commands/rollback-impl.js +1 -1
  206. package/plugin/dist/commands/thinking-os.js +5 -1
  207. package/plugin/dist/core/correction-cue-learner.js +2 -0
  208. package/plugin/dist/core/event-log.js +17 -10
  209. package/plugin/dist/core/focus-history.js +1 -1
  210. package/plugin/dist/core/hygiene/tracker.js +3 -3
  211. package/plugin/dist/core/principle-compiler/compiler.js +2 -0
  212. package/plugin/dist/core/replay-engine.js +1 -1
  213. package/plugin/dist/core/rule-host.js +3 -1
  214. package/plugin/dist/core/schema/schema-definitions.js +3 -0
  215. package/plugin/dist/core/thinking-os-parser.js +13 -4
  216. package/plugin/dist/core/trajectory-types.d.ts +5 -0
  217. package/plugin/dist/core/trajectory.d.ts +1 -0
  218. package/plugin/dist/core/trajectory.js +29 -8
  219. package/plugin/dist/hooks/after-tool-call-helpers.d.ts +5 -0
  220. package/plugin/dist/hooks/after-tool-call-helpers.js +64 -0
  221. package/plugin/dist/hooks/llm.d.ts +8 -0
  222. package/plugin/dist/hooks/llm.js +30 -0
  223. package/plugin/dist/hooks/trajectory-collector.d.ts +20 -4
  224. package/plugin/dist/hooks/trajectory-collector.js +95 -156
  225. package/plugin/dist/hooks/trajectory-evidence.js +6 -2
  226. package/plugin/dist/index.js +7 -2
  227. package/plugin/dist/openclaw-sdk.d.ts +5 -1
  228. package/plugin/dist/service/evolution-worker.js +7 -3
  229. package/plugin/dist/service/internalization-auto-consumer-service.js +2 -1
  230. package/plugin/dist/service/runtime-summary-service.js +8 -1
  231. package/plugin/dist/service/workflow-watchdog.js +2 -1
  232. package/plugin/dist/utils/retry.js +7 -2
  233. package/plugin/dist/utils/session-key.js +4 -1
  234. package/core/dist/io.d.ts +0 -35
  235. package/core/dist/io.d.ts.map +0 -1
  236. package/core/dist/io.js +0 -95
  237. package/core/dist/io.js.map +0 -1
  238. package/core/dist/runtime-v2/__tests__/trainer-output-validator.test.d.ts +0 -2
  239. package/core/dist/runtime-v2/__tests__/trainer-output-validator.test.d.ts.map +0 -1
  240. package/core/dist/runtime-v2/__tests__/trainer-output-validator.test.js +0 -257
  241. package/core/dist/runtime-v2/__tests__/trainer-output-validator.test.js.map +0 -1
  242. package/core/dist/runtime-v2/__tests__/trainer-runner-vslice.test.d.ts +0 -2
  243. package/core/dist/runtime-v2/__tests__/trainer-runner-vslice.test.d.ts.map +0 -1
  244. package/core/dist/runtime-v2/__tests__/trainer-runner-vslice.test.js +0 -514
  245. package/core/dist/runtime-v2/__tests__/trainer-runner-vslice.test.js.map +0 -1
  246. package/core/dist/runtime-v2/internalization/__tests__/trainer-prompt-builder.test.d.ts +0 -2
  247. package/core/dist/runtime-v2/internalization/__tests__/trainer-prompt-builder.test.d.ts.map +0 -1
  248. package/core/dist/runtime-v2/internalization/__tests__/trainer-prompt-builder.test.js +0 -141
  249. package/core/dist/runtime-v2/internalization/__tests__/trainer-prompt-builder.test.js.map +0 -1
  250. package/core/dist/runtime-v2/internalization/trainer-output.d.ts +0 -122
  251. package/core/dist/runtime-v2/internalization/trainer-output.d.ts.map +0 -1
  252. package/core/dist/runtime-v2/internalization/trainer-output.js +0 -223
  253. package/core/dist/runtime-v2/internalization/trainer-output.js.map +0 -1
  254. package/core/dist/runtime-v2/internalization/trainer-prompt-builder.d.ts +0 -24
  255. package/core/dist/runtime-v2/internalization/trainer-prompt-builder.d.ts.map +0 -1
  256. package/core/dist/runtime-v2/internalization/trainer-prompt-builder.js +0 -57
  257. package/core/dist/runtime-v2/internalization/trainer-prompt-builder.js.map +0 -1
  258. package/core/dist/runtime-v2/internalization/trainer-runner.d.ts +0 -76
  259. package/core/dist/runtime-v2/internalization/trainer-runner.d.ts.map +0 -1
  260. package/core/dist/runtime-v2/internalization/trainer-runner.js +0 -540
  261. package/core/dist/runtime-v2/internalization/trainer-runner.js.map +0 -1
  262. package/plugin/dist/core/external-training-contract.d.ts +0 -276
  263. package/plugin/dist/core/external-training-contract.js +0 -269
@@ -25,6 +25,68 @@ import { resolveSourceKind, buildToolFailureObservation } from './raw-observatio
25
25
  import { evaluateEvidenceTriage } from './triage-adapter.js';
26
26
  import { evaluateTriggerController } from '@principles/core/runtime-v2';
27
27
  import { buildTrajectoryEvidence } from './trajectory-evidence.js';
28
+ const RESULT_PREVIEW_MAX_LENGTH = 500;
29
+ /**
30
+ * Extract a preview string from tool call result for diagnostic evidence.
31
+ * Pure function — no I/O, no side effects. ERR-001 / ERR-014 compliant.
32
+ */
33
+ export function extractToolResultPreview(result) {
34
+ if (result === null || result === undefined)
35
+ return null;
36
+ try {
37
+ // String result: truncate directly
38
+ if (typeof result === 'string') {
39
+ return result.length > RESULT_PREVIEW_MAX_LENGTH
40
+ ? `${result.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
41
+ : result;
42
+ }
43
+ // Object with content array (e.g., Anthropic content blocks)
44
+ if (typeof result === 'object' && !Array.isArray(result)) {
45
+ const obj = result;
46
+ if (Array.isArray(obj.content)) {
47
+ const textParts = [];
48
+ for (const block of obj.content) {
49
+ if (block && typeof block === 'object' && !Array.isArray(block)) {
50
+ const blockObj = block;
51
+ if (blockObj.type === 'text' && typeof blockObj.text === 'string') {
52
+ textParts.push(blockObj.text);
53
+ }
54
+ }
55
+ }
56
+ if (textParts.length > 0) {
57
+ const joined = textParts.join('\n');
58
+ return joined.length > RESULT_PREVIEW_MAX_LENGTH
59
+ ? `${joined.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
60
+ : joined;
61
+ }
62
+ }
63
+ // Object without content array: JSON.stringify with depth limiter (ERR-014)
64
+ const serialized = JSON.stringify(obj, (_key, value) => {
65
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
66
+ const keys = Object.keys(value);
67
+ if (keys.length > 10) {
68
+ const truncated = {};
69
+ for (const k of keys.slice(0, 10)) {
70
+ truncated[k] = value[k];
71
+ }
72
+ truncated['__truncated__'] = true;
73
+ return truncated;
74
+ }
75
+ }
76
+ return value;
77
+ }, 2);
78
+ if (serialized && serialized !== '{}') {
79
+ return serialized.length > RESULT_PREVIEW_MAX_LENGTH
80
+ ? `${serialized.slice(0, RESULT_PREVIEW_MAX_LENGTH - 3)}...`
81
+ : serialized;
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ catch {
87
+ return '[result_preview_extraction_failed]';
88
+ }
89
+ }
28
90
  // ── Stage 1: Classify ───────────────────────────────────────────────────────
29
91
  /**
30
92
  * Classify the outcome of a tool call event.
@@ -150,6 +212,7 @@ export function handleFrictionTrackingForFailure(sessionId, event, outcome, obse
150
212
  gfiBefore,
151
213
  gfiAfter: updatedState.currentGfi,
152
214
  paramsJson: sanitizeToolParamsForEvidence(event.params, workspaceDir),
215
+ resultPreview: extractToolResultPreview(event.result),
153
216
  });
154
217
  return updatedState;
155
218
  }
@@ -188,6 +251,7 @@ export function handleFrictionTrackingForSuccess(sessionId, event, outcome, obse
188
251
  gfiBefore,
189
252
  gfiAfter: resetState.currentGfi,
190
253
  paramsJson: sanitizeToolParamsForEvidence(event.params, workspaceDir),
254
+ resultPreview: extractToolResultPreview(event.result),
191
255
  });
192
256
  wctx.eventLog.recordToolCall(sessionId, {
193
257
  toolName: event.toolName,
@@ -8,6 +8,14 @@ export interface EmpathySignal {
8
8
  }
9
9
  export declare function extractEmpathySignal(text: string): EmpathySignal;
10
10
  export declare function isEmpathyAuditPayload(text: string): boolean;
11
+ /**
12
+ * Extract enhanced fields from lastAssistant (complete AssistantMessage) in hook payload.
13
+ * Pure function — no I/O, no side effects. ERR-001 compliant.
14
+ */
15
+ export declare function extractAssistantEnhancedFields(lastAssistant: unknown): {
16
+ stopReason: string | null;
17
+ thinkingBlocksCount: number | null;
18
+ };
11
19
  export declare function handleLlmOutput(event: PluginHookLlmOutputEvent, ctx: PluginHookAgentContext & {
12
20
  workspaceDir?: string;
13
21
  }): void;
@@ -124,6 +124,33 @@ export function isEmpathyAuditPayload(text) {
124
124
  return true;
125
125
  return false;
126
126
  }
127
+ /**
128
+ * Extract enhanced fields from lastAssistant (complete AssistantMessage) in hook payload.
129
+ * Pure function — no I/O, no side effects. ERR-001 compliant.
130
+ */
131
+ export function extractAssistantEnhancedFields(lastAssistant) {
132
+ if (!lastAssistant || typeof lastAssistant !== 'object' || Array.isArray(lastAssistant)) {
133
+ return { stopReason: null, thinkingBlocksCount: null };
134
+ }
135
+ const obj = lastAssistant;
136
+ // stopReason: typeof guard
137
+ const stopReason = typeof obj.stopReason === 'string' ? obj.stopReason : null;
138
+ // thinkingBlocksCount: iterate content array, count thinking/redacted_thinking blocks
139
+ let thinkingBlocksCount = null;
140
+ if (Array.isArray(obj.content)) {
141
+ let count = 0;
142
+ for (const block of obj.content) {
143
+ if (block && typeof block === 'object' && !Array.isArray(block)) {
144
+ const blockType = block.type;
145
+ if (blockType === 'thinking' || blockType === 'redacted_thinking') {
146
+ count++;
147
+ }
148
+ }
149
+ }
150
+ thinkingBlocksCount = count;
151
+ }
152
+ return { stopReason, thinkingBlocksCount };
153
+ }
127
154
  export function handleLlmOutput(event, ctx) {
128
155
  if (!ctx.workspaceDir || !ctx.sessionId)
129
156
  return;
@@ -142,6 +169,7 @@ export function handleLlmOutput(event, ctx) {
142
169
  return;
143
170
  const text = event.assistantTexts.join('\n');
144
171
  const signal = extractEmpathySignal(text);
172
+ const enhancedFields = extractAssistantEnhancedFields(event.lastAssistant);
145
173
  const createdAt = new Date().toISOString();
146
174
  let assistantTurnId = null;
147
175
  try {
@@ -154,6 +182,8 @@ export function handleLlmOutput(event, ctx) {
154
182
  sanitizedText: sanitizeAssistantText(text),
155
183
  usageJson: event.usage || {},
156
184
  empathySignalJson: signal,
185
+ stopReason: enhancedFields.stopReason,
186
+ thinkingBlocksCount: enhancedFields.thinkingBlocksCount,
157
187
  createdAt,
158
188
  });
159
189
  }
@@ -1,9 +1,23 @@
1
1
  /**
2
2
  * Trajectory Collector - message write trajectory recording
3
3
  *
4
- * Records message data to memory/trajectories/ JSONL files.
5
- * PRI-347 removed tool_call and llm_output JSONL writers (no consumers).
6
- * PRI-346 will repurpose handleBeforeMessageWrite for SQLite collection.
4
+ * Fallback trajectory collection when llm_output is blocked by missing
5
+ * allowConversationAccess. before_message_write is NOT in
6
+ * CONVERSATION_HOOK_NAMES so OpenClaw always delivers it.
7
+ *
8
+ * SQLite writes are enqueued to an async queue because before_message_write is
9
+ * a SYNCHRONOUS OpenClaw hook — the handler must return immediately. better-sqlite3
10
+ * is synchronous, so calling it directly would block the message-write main path.
11
+ * Trade-off: in degradation mode the last few queued writes may be lost on
12
+ * process exit; this is acceptable because degradation mode is already caused
13
+ * by a config problem (missing allowConversationAccess), and the primary
14
+ * llm_output path is authoritative when authorized.
15
+ *
16
+ * JSONL writing was removed (no consumers — verified zero readers across the
17
+ * codebase; PRI-347 already removed tool_call/llm_output JSONL writers).
18
+ *
19
+ * ERR-002: fallback errors are logged, never silent.
20
+ * ERR-001/005: content is sanitized before persisting.
7
21
  */
8
22
  import type { PluginHookAgentContext, PluginHookBeforeMessageWriteEvent } from '../openclaw-sdk.js';
9
23
  /**
@@ -14,7 +28,9 @@ import type { PluginHookAgentContext, PluginHookBeforeMessageWriteEvent } from '
14
28
  * CONVERSATION_HOOK_NAMES, so it always fires — making it the natural fallback.
15
29
  *
16
30
  * De-duplication: only writes to SQLite when llm_output is blocked (unauthorized).
17
- * When llm_output is working (authorized), this hook degrades to JSONL-only.
31
+ * When llm_output is working (authorized), this hook is a no-op.
32
+ *
33
+ * SQLite writes are async-queued (see file header) to avoid blocking the sync hook.
18
34
  *
19
35
  * ERR-002: structured observability when fallback fires.
20
36
  * ERR-001/005: content is sanitized before persisting.
@@ -1,127 +1,79 @@
1
1
  /**
2
2
  * Trajectory Collector - message write trajectory recording
3
3
  *
4
- * Records message data to memory/trajectories/ JSONL files.
5
- * PRI-347 removed tool_call and llm_output JSONL writers (no consumers).
6
- * PRI-346 will repurpose handleBeforeMessageWrite for SQLite collection.
4
+ * Fallback trajectory collection when llm_output is blocked by missing
5
+ * allowConversationAccess. before_message_write is NOT in
6
+ * CONVERSATION_HOOK_NAMES so OpenClaw always delivers it.
7
+ *
8
+ * SQLite writes are enqueued to an async queue because before_message_write is
9
+ * a SYNCHRONOUS OpenClaw hook — the handler must return immediately. better-sqlite3
10
+ * is synchronous, so calling it directly would block the message-write main path.
11
+ * Trade-off: in degradation mode the last few queued writes may be lost on
12
+ * process exit; this is acceptable because degradation mode is already caused
13
+ * by a config problem (missing allowConversationAccess), and the primary
14
+ * llm_output path is authoritative when authorized.
15
+ *
16
+ * JSONL writing was removed (no consumers — verified zero readers across the
17
+ * codebase; PRI-347 already removed tool_call/llm_output JSONL writers).
18
+ *
19
+ * ERR-002: fallback errors are logged, never silent.
20
+ * ERR-001/005: content is sanitized before persisting.
7
21
  */
8
- import * as fs from 'fs';
9
- import * as path from 'path';
10
22
  import { MAX_STRING_LENGTH } from '../config/defaults/runtime.js';
11
23
  import { WorkspaceContext } from '../core/workspace-context.js';
12
24
  import { SystemLogger } from '../core/system-logger.js';
13
25
  import { sanitizeForEvidence } from './message-sanitize.js';
14
26
  import { checkConversationAccessConfig } from '../core/config-health.js';
15
- const TRAJECTORY_DIR = 'memory/trajectories/';
16
- // 敏感字段匹配正则
17
- const SENSITIVE_KEY_PATTERN = /password|token|authorization|secret|api[_-]?key|credential|cookie|session/i;
18
- /**
19
- * 递归脱敏处理:遍历对象/数组,移除敏感字段值
20
- */
21
- function scrubSensitive(obj, depth = 0) {
22
- // 防止无限递归
23
- if (depth > 10)
24
- return '[MAX_DEPTH]';
25
- // 处理 null/undefined
26
- if (obj == null)
27
- return obj;
28
- // 处理基本类型
29
- if (typeof obj !== 'object') {
30
- if (typeof obj === 'string' && obj.length > MAX_STRING_LENGTH) {
31
- return obj.slice(0, MAX_STRING_LENGTH) + '...[truncated]';
32
- }
33
- return obj;
34
- }
35
- // 处理数组
36
- if (Array.isArray(obj)) {
37
- return obj.map(item => scrubSensitive(item, depth + 1));
38
- }
39
- // 处理对象
40
- const result = {};
41
- for (const [key, value] of Object.entries(obj)) {
42
- if (SENSITIVE_KEY_PATTERN.test(key)) {
43
- result[key] = '[REDACTED]';
44
- }
45
- else {
46
- result[key] = scrubSensitive(value, depth + 1);
47
- }
48
- }
49
- return result;
50
- }
51
27
  /**
52
- * 异步写入队列 - 确保有序、非阻塞写入
28
+ * Async write queue — ensures ordered, non-blocking execution of SQLite writes.
29
+ * before_message_write is a sync hook; SQLite I/O must not block it.
30
+ *
31
+ * Tasks are deferred to the microtask queue via `queueMicrotask` so the sync
32
+ * hook handler returns before any synchronous better-sqlite3 work runs.
53
33
  */
54
34
  class AsyncWriteQueue {
55
35
  queue = [];
56
36
  processing = false;
57
- async enqueue(task) {
37
+ enqueue(task) {
58
38
  this.queue.push(task);
59
39
  if (!this.processing) {
60
- this.processNext();
40
+ this.processing = true;
41
+ queueMicrotask(() => { void this.processNext(); });
61
42
  }
62
43
  }
63
44
  async processNext() {
64
- if (this.queue.length === 0) {
65
- this.processing = false;
66
- return;
67
- }
68
- this.processing = true;
69
- const task = this.queue.shift();
70
- if (!task) {
71
- this.processing = false;
72
- return;
45
+ while (this.queue.length > 0) {
46
+ const task = this.queue.shift();
47
+ if (!task)
48
+ break;
49
+ try {
50
+ await task();
51
+ }
52
+ catch (err) {
53
+ // EP-03/ERR-002: safety net — tasks should catch their own errors with
54
+ // structured logging. This catches bugs where a task's try/catch is
55
+ // missing or re-throws. Never silently swallow (ERR-002).
56
+ console.warn('[PD:trajectory-queue] uncaught task error:', err);
57
+ }
73
58
  }
74
- try {
75
- await task();
76
- }
77
- catch {
78
- // Silently fail - trajectory collection should not block main functionality
79
- }
80
- // 处理下一个任务
81
- this.processNext();
59
+ this.processing = false;
82
60
  }
83
61
  }
84
- // 全局写入队列实例
85
62
  const writeQueue = new AsyncWriteQueue();
86
- // 目录缓存(避免重复检查)
87
- const dirCache = new Map();
88
63
  /**
89
- * 确保轨迹目录存在(异步)
64
+ * Extract text content from a message (string or content-part array).
90
65
  */
91
- async function ensureTrajectoryDirAsync(workspaceDir) {
92
- const dir = path.join(workspaceDir, TRAJECTORY_DIR);
93
- if (dirCache.get(dir)) {
94
- return dir;
95
- }
96
- try {
97
- await fs.promises.mkdir(dir, { recursive: true });
98
- dirCache.set(dir, true);
66
+ function extractContent(content) {
67
+ if (typeof content === 'string') {
68
+ return content;
99
69
  }
100
- catch {
101
- // 目录可能已存在,忽略错误
102
- dirCache.set(dir, true);
70
+ if (Array.isArray(content)) {
71
+ return content
72
+ .filter((part) => part && typeof part === 'object' && part.type === 'text')
73
+ .map((part) => part.text)
74
+ .join('\n');
103
75
  }
104
- return dir;
105
- }
106
- /**
107
- * 获取今日轨迹文件名
108
- */
109
- function getTodayFilename() {
110
- const now = new Date();
111
- const year = now.getUTCFullYear();
112
- const month = String(now.getUTCMonth() + 1).padStart(2, '0');
113
- return `${year}-${month}-${String(now.getUTCDate()).padStart(2, '0')}.jsonl`;
114
- }
115
- /**
116
- * 写入轨迹记录(JSON Lines 格式)- 异步版本
117
- */
118
- function writeTrajectoryRecord(workspaceDir, record) {
119
- const line = JSON.stringify(record) + '\n';
120
- writeQueue.enqueue(async () => {
121
- const dir = await ensureTrajectoryDirAsync(workspaceDir);
122
- const filepath = path.join(dir, getTodayFilename());
123
- await fs.promises.appendFile(filepath, line, 'utf8');
124
- });
76
+ return '';
125
77
  }
126
78
  /**
127
79
  * PRI-346: Message write hook with SQLite fallback trajectory recording.
@@ -131,7 +83,9 @@ function writeTrajectoryRecord(workspaceDir, record) {
131
83
  * CONVERSATION_HOOK_NAMES, so it always fires — making it the natural fallback.
132
84
  *
133
85
  * De-duplication: only writes to SQLite when llm_output is blocked (unauthorized).
134
- * When llm_output is working (authorized), this hook degrades to JSONL-only.
86
+ * When llm_output is working (authorized), this hook is a no-op.
87
+ *
88
+ * SQLite writes are async-queued (see file header) to avoid blocking the sync hook.
135
89
  *
136
90
  * ERR-002: structured observability when fallback fires.
137
91
  * ERR-001/005: content is sanitized before persisting.
@@ -146,73 +100,58 @@ export function handleBeforeMessageWrite(event, ctx) {
146
100
  // Only record user and assistant messages
147
101
  if (msg.role !== 'user' && msg.role !== 'assistant')
148
102
  return;
149
- // Extract text content (consistent with existing implementation)
150
- let content = '';
151
- if (typeof msg.content === 'string') {
152
- content = msg.content;
153
- }
154
- else if (Array.isArray(msg.content)) {
155
- content = msg.content
156
- .filter((part) => part && typeof part === 'object' && part.type === 'text')
157
- .map((part) => part.text)
158
- .join('\n');
159
- }
160
- // Sanitize content preview for JSONL
161
- const sanitizedPreview = scrubSensitive(content.slice(0, 200));
162
- // Existing JSONL write (always, for backward compatibility)
163
- writeTrajectoryRecord(workspaceDir, {
164
- type: 'message',
165
- timestamp: new Date().toISOString(),
166
- sessionId: event.sessionKey || event.sessionId || 'unknown',
167
- role: msg.role,
168
- contentLength: content.length,
169
- contentPreview: typeof sanitizedPreview === 'string' ? sanitizedPreview : '[sanitized]',
170
- agentId: event.agentId || null,
171
- fallback: 'before_message_write',
172
- });
173
103
  // ── SQLite fallback (PRI-346): only when conversation hooks are blocked ──
174
104
  const accessCheck = checkConversationAccessConfig(ctx.pluginConfig);
175
105
  if (accessCheck.authorized) {
176
106
  // llm_output is working — do NOT duplicate write to SQLite (de-dup, case D)
177
107
  return;
178
108
  }
179
- // Conversation hooks blocked — this hook is the fallback trajectory writer
109
+ // Conversation hooks blocked — this hook is the fallback trajectory writer.
110
+ // Enqueue SQLite writes to avoid blocking the sync before_message_write hook.
111
+ const logger = ctx.logger;
112
+ const content = extractContent(msg.content);
113
+ const sessionId = event.sessionKey ?? ctx.sessionId ?? 'unknown';
114
+ const createdAt = new Date().toISOString();
180
115
  if (msg.role === 'assistant') {
181
- try {
182
- const wctx = WorkspaceContext.fromHookContext({ workspaceDir, logger: ctx.logger });
183
- const sanitized = sanitizeForEvidence(content.slice(0, MAX_STRING_LENGTH), workspaceDir);
184
- const sessionId = event.sessionKey ?? ctx.sessionId ?? 'unknown';
185
- wctx.trajectory?.recordAssistantTurn?.({
186
- sessionId,
187
- runId: 'before_message_write_fallback',
188
- provider: 'unknown',
189
- model: 'unknown',
190
- rawText: content,
191
- sanitizedText: sanitized,
192
- usageJson: {},
193
- empathySignalJson: { detected: false, severity: 'mild', confidence: 1 },
194
- createdAt: new Date().toISOString(),
195
- });
196
- }
197
- catch (err) {
198
- ctx.logger?.warn?.(`[PD:before_message_write] SQLite fallback write failed: ${String(err)}`);
199
- }
116
+ writeQueue.enqueue(async () => {
117
+ try {
118
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, logger });
119
+ const sanitized = sanitizeForEvidence(content.slice(0, MAX_STRING_LENGTH), workspaceDir);
120
+ wctx.trajectory?.recordAssistantTurn?.({
121
+ sessionId,
122
+ runId: 'before_message_write_fallback',
123
+ provider: 'unknown',
124
+ model: 'unknown',
125
+ rawText: content,
126
+ sanitizedText: sanitized,
127
+ usageJson: {},
128
+ empathySignalJson: { detected: false, severity: 'mild', confidence: 1 },
129
+ createdAt,
130
+ });
131
+ }
132
+ catch (err) {
133
+ // EP-03/ERR-002: observable degradation, never silent
134
+ logger?.warn?.(`[PD:before_message_write] SQLite fallback write failed: ${String(err)}`);
135
+ }
136
+ });
200
137
  }
201
138
  else if (msg.role === 'user') {
202
- try {
203
- const wctx = WorkspaceContext.fromHookContext({ workspaceDir, logger: ctx.logger });
204
- const sessionId = event.sessionKey ?? ctx.sessionId ?? 'unknown';
205
- wctx.trajectory?.recordUserTurn?.({
206
- sessionId,
207
- turnIndex: 0,
208
- rawText: content.slice(0, MAX_STRING_LENGTH),
209
- correctionDetected: false,
210
- createdAt: new Date().toISOString(),
211
- });
212
- }
213
- catch (err) {
214
- ctx.logger?.warn?.(`[PD:before_message_write] SQLite user turn fallback failed: ${String(err)}`);
215
- }
139
+ writeQueue.enqueue(async () => {
140
+ try {
141
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir, logger });
142
+ wctx.trajectory?.recordUserTurn?.({
143
+ sessionId,
144
+ turnIndex: 0,
145
+ rawText: content.slice(0, MAX_STRING_LENGTH),
146
+ correctionDetected: false,
147
+ createdAt,
148
+ });
149
+ }
150
+ catch (err) {
151
+ // EP-03/ERR-002: observable degradation, never silent
152
+ logger?.warn?.(`[PD:before_message_write] SQLite user turn fallback failed: ${String(err)}`);
153
+ }
154
+ });
216
155
  }
217
156
  // ERR-002: Structured observability — no silent fallback
218
157
  SystemLogger.log(workspaceDir, 'CONVERSATION_HOOK_BLOCKED', JSON.stringify({
@@ -41,9 +41,11 @@ export function buildTrajectoryEvidence(wctx, sessionId) {
41
41
  if (evidence.length >= MAX_EVIDENCE_ENTRIES)
42
42
  break;
43
43
  const sanitizedNote = sanitizeAssistantText((turn.sanitizedText ?? '').slice(0, MAX_EVIDENCE_NOTE_CHARS));
44
+ // Enhanced: append truncation warning when stop_reason=length
45
+ const truncationWarning = turn.stopReason === 'length' ? ' [TRUNCATED: output cut off by length limit]' : '';
44
46
  evidence.push({
45
47
  sourceRef: `agent_turn:${turn.createdAt}`,
46
- note: sanitizedNote,
48
+ note: sanitizedNote + truncationWarning,
47
49
  });
48
50
  }
49
51
  }
@@ -62,7 +64,9 @@ export function buildTrajectoryEvidence(wctx, sessionId) {
62
64
  for (const tc of failedToolCalls) {
63
65
  if (evidence.length >= MAX_EVIDENCE_ENTRIES)
64
66
  break;
65
- const note = `Tool ${tc.toolName} failed: ${tc.errorType ?? 'unknown'} (exitCode: ${tc.exitCode ?? 'N/A'})`;
67
+ // Enhanced: append resultPreview when available
68
+ const previewSuffix = tc.resultPreview ? ` | ${tc.resultPreview.slice(0, 200)}` : '';
69
+ const note = `Tool ${tc.toolName} failed: ${tc.errorType ?? 'unknown'} (exitCode: ${tc.exitCode ?? 'N/A'})${previewSuffix}`;
66
70
  evidence.push({
67
71
  sourceRef: `tool_call_failure:${tc.createdAt}`,
68
72
  note: sanitizeAssistantText(note.slice(0, MAX_EVIDENCE_NOTE_CHARS)),
@@ -266,6 +266,9 @@ const plugin = {
266
266
  }
267
267
  }));
268
268
  // ── Hook: Pain & Trust ──
269
+ // timeoutMs=10s: OpenClaw has no default timeout for after_tool_call; without
270
+ // this a stuck SQLite write (busy_timeout=5000ms) leaks the handler promise.
271
+ // 10s gives 2x headroom over busy_timeout. fail-open means agent is unaffected.
269
272
  api.on('after_tool_call', guardHook('hook:after_tool_call', api.logger, (event, ctx) => {
270
273
  const wsResult = resolveHookWorkspaceDir(ctx, api, 'after_tool_call');
271
274
  if (!wsResult.ok) {
@@ -294,8 +297,10 @@ const plugin = {
294
297
  }, { flushImmediately: true });
295
298
  api.logger.error(`[PD:EmpathyObserver] Error in after_tool_call: ${String(err)}`);
296
299
  }
297
- }));
300
+ }), { timeoutMs: 10_000 });
298
301
  // ── Hook: LLM Analysis ──
302
+ // timeoutMs=10s: OpenClaw has no default timeout for llm_output; without
303
+ // this a stuck SQLite write leaks the handler promise. See after_tool_call.
299
304
  api.on('llm_output', guardHook('hook:llm_output', api.logger, (event, ctx) => {
300
305
  const wsResult = resolveHookWorkspaceDir(ctx, api, 'llm_output');
301
306
  if (!wsResult.ok) {
@@ -325,7 +330,7 @@ const plugin = {
325
330
  });
326
331
  api.logger.error(`[PD] Error in llm_output: ${String(err)}`);
327
332
  }
328
- }));
333
+ }), { timeoutMs: 10_000 });
329
334
  // ── Hook: Lifecycle ──
330
335
  api.on('before_reset', guardHook('hook:before_reset', api.logger, (event, ctx) => {
331
336
  const wsResult = resolveHookWorkspaceDir(ctx, api, 'before_reset');
@@ -151,7 +151,10 @@ export interface OpenClawPluginApi {
151
151
  text: (content: string, code?: number) => void;
152
152
  }) => boolean | Promise<boolean>;
153
153
  }) => void;
154
- on: (event: string, handler: (...args: any[]) => unknown) => void;
154
+ on: (event: string, handler: (...args: any[]) => unknown, opts?: {
155
+ priority?: number;
156
+ timeoutMs?: number;
157
+ }) => void;
155
158
  }
156
159
  export interface PluginHookBeforePromptBuildEvent {
157
160
  agentId?: string;
@@ -231,6 +234,7 @@ export interface PluginHookLlmOutputEvent {
231
234
  usage?: TokenUsage;
232
235
  assistantTexts?: string[];
233
236
  trigger?: string;
237
+ lastAssistant?: unknown;
234
238
  [key: string]: unknown;
235
239
  }
236
240
  export interface PluginHookSubagentSpawningEvent {
@@ -93,7 +93,8 @@ export function purgeStaleFailedTasks(queue, logger) {
93
93
  // Remove purged items from the queue (mutates in place)
94
94
  const purgedIds = new Set(purged.map((t) => t.id));
95
95
  for (let i = queue.length - 1; i >= 0; i--) {
96
- if (purgedIds.has(queue[i].id))
96
+ const task = queue[i];
97
+ if (task && purgedIds.has(task.id))
97
98
  queue.splice(i, 1);
98
99
  }
99
100
  const summary = Object.entries(byReason)
@@ -377,17 +378,20 @@ async function processDetectionQueue(wctx, api, eventLog) {
377
378
  if (wctx.trajectory) {
378
379
  const searchResults = wctx.trajectory.searchPainEvents(text, 5);
379
380
  if (searchResults.length > 0) {
381
+ const topResult = searchResults[0];
382
+ if (!topResult)
383
+ continue;
380
384
  // Found similar pain events - record as L3 semantic hit
381
385
  if (eventLog) {
382
386
  eventLog.recordRuleMatch(undefined, {
383
387
  ruleId: 'l3_semantic',
384
388
  layer: 'L3',
385
- severity: searchResults[0].score,
389
+ severity: topResult.score,
386
390
  textPreview: text.substring(0, 100)
387
391
  });
388
392
  }
389
393
  // Update detection funnel cache with L3 hit result
390
- funnel.updateCache(text, { detected: true, severity: searchResults[0].score });
394
+ funnel.updateCache(text, { detected: true, severity: topResult.score });
391
395
  // Don't track as candidate - this is a confirmed L3 hit
392
396
  if (logger)
393
397
  logger.info(`[PD:EvolutionWorker] L3 semantic hit: found ${searchResults.length} similar pain events for "${text.substring(0, 50)}..."`);
@@ -1,4 +1,5 @@
1
- import { createRuntimeStateHandle, InternalizationOrchestrator, DreamerRunner, DefaultDreamerValidator, PiAiRuntimeAdapter, L2AgentLoopAdapter, buildL2PrincipleReaderFromLedger, loadLedger, OpenClawCliRuntimeAdapter, storeEmitter, resolveRuntimeConfigFromPdConfig, isRuntimeConfigError, computeConsumerDecision, InternalizationQueueReadModel, MVP_CORE_TASK_KINDS, } from '@principles/core/runtime-v2';
1
+ import { createRuntimeStateHandle, InternalizationOrchestrator, DreamerRunner, DefaultDreamerValidator, PiAiRuntimeAdapter, L2AgentLoopAdapter, buildL2PrincipleReaderFromLedger, OpenClawCliRuntimeAdapter, storeEmitter, resolveRuntimeConfigFromPdConfig, isRuntimeConfigError, computeConsumerDecision, InternalizationQueueReadModel, MVP_CORE_TASK_KINDS, } from '@principles/core/runtime-v2';
2
+ import { loadLedger } from '@principles/core/principle-tree-ledger';
2
3
  import { loadPdConfigForPlugin, loadFeatureFlagFromConfig } from '../core/pd-config-loader.js';
3
4
  import { SystemLogger } from '../core/system-logger.js';
4
5
  const INTERNALIZATION_AUTO_CONSUMER_INTERVAL_MS = 120_000;
@@ -290,7 +290,10 @@ export class RuntimeSummaryService {
290
290
  if (sessions.length === 0) {
291
291
  return { session: null, reason: 'none' };
292
292
  }
293
- return { session: sessions[0], reason: 'latest_active' };
293
+ const session = sessions[0];
294
+ if (!session)
295
+ return { session: null, reason: 'none' };
296
+ return { session, reason: 'latest_active' };
294
297
  }
295
298
  static mergeSessionSnapshots(persistedSessions, workspaceDir) {
296
299
  const merged = new Map();
@@ -392,6 +395,8 @@ export class RuntimeSummaryService {
392
395
  if (!m)
393
396
  continue;
394
397
  const fileDate = m[1];
398
+ if (!fileDate)
399
+ continue;
395
400
  if (fileDate > newestDate) {
396
401
  newestDate = fileDate;
397
402
  bestFile = path.join(dir, file);
@@ -463,6 +468,8 @@ export class RuntimeSummaryService {
463
468
  static findLastPainSignal(events, sessionId) {
464
469
  for (let i = events.length - 1; i >= 0; i--) {
465
470
  const entry = events[i];
471
+ if (!entry)
472
+ continue;
466
473
  if (entry.type !== 'pain_signal')
467
474
  continue;
468
475
  if (sessionId && entry.sessionId !== sessionId)
@@ -32,7 +32,8 @@ export async function runWorkflowWatchdog(wctx, api, logger) {
32
32
  const ageMin = Math.round((now - wf.created_at) / 60000);
33
33
  details.push(`stale_active: ${wf.workflow_id} (${wf.workflow_type}, ${ageMin}min old)`);
34
34
  const events = store.getEvents(wf.workflow_id);
35
- const lastEventReason = events.length > 0 ? events[events.length - 1].reason : 'unknown';
35
+ const lastEvent = events[events.length - 1];
36
+ const lastEventReason = lastEvent ? lastEvent.reason : 'unknown';
36
37
  if (isExpectedSubagentError(lastEventReason)) {
37
38
  logger?.debug?.(`[PD:Watchdog] Skipping stale active workflow ${wf.workflow_id}: expected subagent error (${lastEventReason})`);
38
39
  continue;