principles-disciple 1.11.0 → 1.13.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 (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -1,5 +1,5 @@
1
1
  import { ControlUiDatabase } from '../core/control-ui-db.js';
2
- import { getThinkingModel, listThinkingModels } from '../core/thinking-models.js';
2
+ import { getThinkingModel, listThinkingModels, getThinkingModelDefinitions } from '../core/thinking-models.js';
3
3
  import { WorkspaceContext } from '../core/workspace-context.js';
4
4
 
5
5
  /** Time window (in minutes) for querying principle events related to a sample */
@@ -22,33 +22,35 @@ export interface OverviewResponse {
22
22
  gateBlocks: number;
23
23
  taskOutcomes: number;
24
24
  };
25
- dailyTrend: Array<{
25
+ dailyTrend: {
26
26
  day: string;
27
27
  toolCalls: number;
28
28
  failures: number;
29
29
  userCorrections: number;
30
30
  thinkingTurns: number;
31
- }>;
32
- topRegressions: Array<{
31
+ }[];
32
+ topRegressions: {
33
33
  toolName: string;
34
34
  errorType: string;
35
35
  occurrences: number;
36
- }>;
36
+ }[];
37
37
  sampleQueue: {
38
38
  counters: Record<string, number>;
39
- preview: Array<{
39
+ preview: {
40
40
  sampleId: string;
41
41
  sessionId: string;
42
42
  qualityScore: number;
43
43
  reviewStatus: string;
44
44
  createdAt: string;
45
- }>;
45
+ }[];
46
46
  };
47
47
  thinkingSummary: {
48
48
  activeModels: number;
49
49
  dormantModels: number;
50
50
  effectiveModels: number;
51
51
  coverageRate: number;
52
+ modelBreakdown?: { modelId: string; hits: number }[];
53
+ modelDefinitions?: { modelId: string; name: string; description: string }[];
52
54
  };
53
55
  }
54
56
 
@@ -64,7 +66,7 @@ export interface SampleListFilters {
64
66
 
65
67
  export interface SamplesResponse {
66
68
  counters: Record<string, number>;
67
- items: Array<{
69
+ items: {
68
70
  sampleId: string;
69
71
  sessionId: string;
70
72
  reviewStatus: string;
@@ -74,7 +76,7 @@ export interface SamplesResponse {
74
76
  createdAt: string;
75
77
  updatedAt: string;
76
78
  diffExcerpt: string;
77
- }>;
79
+ }[];
78
80
  pagination: {
79
81
  page: number;
80
82
  pageSize: number;
@@ -102,13 +104,13 @@ export interface SampleDetailResponse {
102
104
  correctionCue: string | null;
103
105
  createdAt: string;
104
106
  };
105
- recoveryToolSpan: Array<{ id: number; toolName: string }>;
106
- relatedPrinciples: Array<{
107
+ recoveryToolSpan: { id: number; toolName: string }[];
108
+ relatedPrinciples: {
107
109
  principleId: string | null;
108
110
  eventType: string;
109
111
  createdAt: string;
110
- }>;
111
- relatedThinkingHits: Array<{
112
+ }[];
113
+ relatedThinkingHits: {
112
114
  id: number;
113
115
  modelId: string;
114
116
  modelName: string;
@@ -116,12 +118,12 @@ export interface SampleDetailResponse {
116
118
  scenarios: string[];
117
119
  createdAt: string;
118
120
  triggerExcerpt: string;
119
- }>;
120
- reviewHistory: Array<{
121
+ }[];
122
+ reviewHistory: {
121
123
  reviewStatus: string;
122
124
  note: string | null;
123
125
  createdAt: string;
124
- }>;
126
+ }[];
125
127
  }
126
128
 
127
129
  export interface ThinkingModelSummary {
@@ -148,24 +150,24 @@ export interface ThinkingOverviewResponse {
148
150
  coverageRate: number;
149
151
  };
150
152
  topModels: ThinkingModelSummary[];
151
- dormantModels: Array<{
153
+ dormantModels: {
152
154
  modelId: string;
153
155
  name: string;
154
156
  description: string;
155
- }>;
157
+ }[];
156
158
  effectiveModels: ThinkingModelSummary[];
157
- scenarioMatrix: Array<{
159
+ scenarioMatrix: {
158
160
  modelId: string;
159
161
  modelName: string;
160
162
  scenario: string;
161
163
  hits: number;
162
- }>;
163
- coverageTrend: Array<{
164
+ }[];
165
+ coverageTrend: {
164
166
  day: string;
165
167
  assistantTurns: number;
166
168
  thinkingTurns: number;
167
169
  coverageRate: number;
168
- }>;
170
+ }[];
169
171
  }
170
172
 
171
173
  export interface ThinkingModelDetailResponse {
@@ -177,14 +179,14 @@ export interface ThinkingModelDetailResponse {
177
179
  coverageRate: number;
178
180
  recommendation: 'reinforce' | 'rework' | 'archive';
179
181
  };
180
- usageTrend: Array<{
182
+ usageTrend: {
181
183
  day: string;
182
184
  hits: number;
183
- }>;
184
- scenarioDistribution: Array<{
185
+ }[];
186
+ scenarioDistribution: {
185
187
  scenario: string;
186
188
  hits: number;
187
- }>;
189
+ }[];
188
190
  outcomeStats: {
189
191
  events: number;
190
192
  successRate: number;
@@ -193,16 +195,16 @@ export interface ThinkingModelDetailResponse {
193
195
  correctionRate: number;
194
196
  correctionSampleRate: number;
195
197
  };
196
- recentEvents: Array<{
198
+ recentEvents: {
197
199
  id: number;
198
200
  createdAt: string;
199
201
  matchedPattern: string;
200
202
  scenarios: string[];
201
203
  triggerExcerpt: string;
202
- toolContext: Array<{ toolName: string; outcome: string; errorType?: string | null }>;
203
- painContext: Array<{ source: string; score: number }>;
204
- principleContext: Array<{ principleId: string | null; eventType: string }>;
205
- }>;
204
+ toolContext: { toolName: string; outcome: string; errorType?: string | null }[];
205
+ painContext: { source: string; score: number }[];
206
+ principleContext: { principleId: string | null; eventType: string }[];
207
+ }[];
206
208
  }
207
209
 
208
210
  function parseJson<T>(raw: string | null | undefined, fallback: T): T {
@@ -253,7 +255,7 @@ export class ControlUiQueryService {
253
255
  this.uiDb.dispose();
254
256
  }
255
257
 
256
- getOverview(days: number = 30): OverviewResponse {
258
+ getOverview(days = 30): OverviewResponse {
257
259
  const stats = this.trajectory.getDataStats();
258
260
  const regressionRows = this.uiDb.all<{
259
261
  tool_name: string;
@@ -313,6 +315,12 @@ export class ControlUiQueryService {
313
315
  painRate: roundRate(Number(row.pain_windows), Number(row.events)),
314
316
  correctionRate: roundRate(Number(row.correction_windows), Number(row.events)),
315
317
  }) === 'reinforce').length;
318
+ const modelBreakdown = this.uiDb.all<{ modelId: string; hits: number }>(`
319
+ SELECT model_id as modelId, COUNT(*) as hits
320
+ FROM thinking_model_events
321
+ GROUP BY model_id
322
+ ORDER BY hits DESC
323
+ `).map(row => ({ modelId: row.modelId, hits: Number(row.hits) }));
316
324
  const dailyTrend = this.uiDb.all<{
317
325
  day: string;
318
326
  tool_calls: number;
@@ -385,6 +393,8 @@ export class ControlUiQueryService {
385
393
  dormantModels: Math.max(0, listThinkingModels().length - activeModels),
386
394
  effectiveModels: effectiveCount,
387
395
  coverageRate: roundRate(coverageRow.thinking_turns, coverageRow.assistant_turns),
396
+ modelBreakdown,
397
+ modelDefinitions: getThinkingModelDefinitions(),
388
398
  },
389
399
  };
390
400
  }
@@ -621,7 +631,7 @@ export class ControlUiQueryService {
621
631
  correctionCue: row.user_correction_cue,
622
632
  createdAt: row.user_created_at,
623
633
  },
624
- recoveryToolSpan: parseJson<Array<{ id: number; toolName: string }>>(row.recovery_tool_span_json, []),
634
+ recoveryToolSpan: parseJson<{ id: number; toolName: string }[]>(row.recovery_tool_span_json, []),
625
635
  relatedPrinciples: [
626
636
  ...seededPrincipleIds,
627
637
  ...relatedPrinciples.map((item) => ({
@@ -822,9 +832,9 @@ export class ControlUiQueryService {
822
832
  matchedPattern: row.matched_pattern,
823
833
  scenarios: parseJson<string[]>(row.scenario_json, []),
824
834
  triggerExcerpt: row.trigger_excerpt,
825
- toolContext: parseJson<Array<{ toolName: string; outcome: string; errorType?: string | null }>>(row.tool_context_json, []),
826
- painContext: parseJson<Array<{ source: string; score: number }>>(row.pain_context_json, []),
827
- principleContext: parseJson<Array<{ principleId: string | null; eventType: string }>>(row.principle_context_json, []),
835
+ toolContext: parseJson<{ toolName: string; outcome: string; errorType?: string | null }[]>(row.tool_context_json, []),
836
+ painContext: parseJson<{ source: string; score: number }[]>(row.pain_context_json, []),
837
+ principleContext: parseJson<{ principleId: string | null; eventType: string }[]>(row.principle_context_json, []),
828
838
  })),
829
839
  };
830
840
  }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * EventLog Auditor — Search and verify events across all .state directories
3
+ *
4
+ * This tool addresses a common debugging issue where hook events may be
5
+ * written to the wrong .state directory due to workspaceDir resolution bugs.
6
+ *
7
+ * Usage:
8
+ * const report = await auditEventLogs(openclawDir, ['after_tool_call', 'before_tool_call']);
9
+ * console.log(report.summary);
10
+ */
11
+
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import * as os from 'os';
15
+
16
+ interface EventLogEntry {
17
+ ts: string;
18
+ date: string;
19
+ type: string;
20
+ category: string;
21
+ sessionId?: string;
22
+ data: Record<string, unknown>;
23
+ }
24
+
25
+ interface LocationReport {
26
+ path: string;
27
+ lastModified: Date | null;
28
+ totalEntries: number;
29
+ hookCounts: Record<string, number>;
30
+ recentEntries: EventLogEntry[];
31
+ }
32
+
33
+ interface AuditReport {
34
+ searchedPaths: string[];
35
+ locations: LocationReport[];
36
+ primaryPath: string | null;
37
+ misplacedEvents: { path: string; entries: EventLogEntry[] }[];
38
+ }
39
+
40
+ /**
41
+ * Find all events.jsonl files under a directory tree.
42
+ */
43
+ function findEventLogs(baseDir: string, maxDepth = 4): string[] {
44
+ const results: string[] = [];
45
+
46
+ function scan(dir: string, depth: number): void {
47
+ if (depth > maxDepth) return;
48
+ try {
49
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ if (entry.name === 'events.jsonl') {
52
+ results.push(path.join(dir, entry.name));
53
+ } else if (entry.isDirectory() && !entry.name.startsWith('.')) {
54
+ scan(path.join(dir, entry.name), depth + 1);
55
+ }
56
+ }
57
+ } catch {
58
+ // Permission denied or directory doesn't exist
59
+ }
60
+ }
61
+
62
+ scan(baseDir, 0);
63
+ return results;
64
+ }
65
+
66
+ /**
67
+ * Find events.jsonl in well-known locations.
68
+ */
69
+ function findKnownEventLogPaths(): string[] {
70
+ const homeDir = os.homedir();
71
+ const candidates: string[] = [];
72
+
73
+ // Common patterns
74
+ const patterns = [
75
+ path.join(homeDir, '.state', 'logs', 'events.jsonl'),
76
+ path.join(homeDir, '.openclaw', '.state', 'logs', 'events.jsonl'),
77
+ path.join(homeDir, '.openclaw', 'workspace-main', '.state', 'logs', 'events.jsonl'),
78
+ path.join(homeDir, '.openclaw', 'workspace-builder', '.state', 'logs', 'events.jsonl'),
79
+ path.join(homeDir, '.openclaw', 'workspace-pm', '.state', 'logs', 'events.jsonl'),
80
+ path.join(homeDir, '.openclaw', 'workspace-hr', '.state', 'logs', 'events.jsonl'),
81
+ path.join(homeDir, '.openclaw', 'workspace-repair', '.state', 'logs', 'events.jsonl'),
82
+ path.join(homeDir, '.openclaw', 'workspace-research', '.state', 'logs', 'events.jsonl'),
83
+ path.join(homeDir, '.openclaw', 'workspace-scout', '.state', 'logs', 'events.jsonl'),
84
+ ];
85
+
86
+ for (const p of patterns) {
87
+ if (fs.existsSync(p)) {
88
+ candidates.push(p);
89
+ }
90
+ }
91
+
92
+ return candidates;
93
+ }
94
+
95
+ /**
96
+ * Read the last N entries from an events.jsonl file.
97
+ */
98
+ function readRecentEntries(filePath: string, count = 50): EventLogEntry[] {
99
+ try {
100
+ const content = fs.readFileSync(filePath, 'utf-8');
101
+ const lines = content.trim().split('\n').filter(Boolean);
102
+ const recent = lines.slice(-count);
103
+ return recent.map(line => JSON.parse(line));
104
+ } catch {
105
+ return [];
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Count all hooks in the entire file (for summary).
111
+ */
112
+ function countAllHooks(filePath: string): Record<string, number> {
113
+ const counts: Record<string, number> = {};
114
+ try {
115
+ const content = fs.readFileSync(filePath, 'utf-8');
116
+ const lines = content.trim().split('\n').filter(Boolean);
117
+ for (const line of lines) {
118
+ try {
119
+ const entry = JSON.parse(line) as EventLogEntry;
120
+ if (entry.type === 'hook_execution' && entry.data?.hook) {
121
+ const hook = entry.data.hook as string;
122
+ counts[hook] = (counts[hook] || 0) + 1;
123
+ }
124
+ } catch {
125
+ // Skip malformed lines
126
+ }
127
+ }
128
+ } catch {
129
+ // File doesn't exist or can't be read
130
+ }
131
+ return counts;
132
+ }
133
+
134
+ /**
135
+ * Audit all events.jsonl files.
136
+ *
137
+ * @param openclawDir - Base OpenClaw directory (e.g., ~/.openclaw)
138
+ * @param expectedToolHooks - Hook names that should appear in the primary workspace
139
+ */
140
+ export async function auditEventLogs(
141
+ openclawDir: string,
142
+ expectedToolHooks: string[] = ['before_tool_call', 'after_tool_call'],
143
+ ): Promise<AuditReport> {
144
+ const homeDir = os.homedir();
145
+
146
+ // Find all event logs
147
+ const knownPaths = findKnownEventLogPaths();
148
+ const scannedPaths = findEventLogs(homeDir, 4);
149
+ const allPaths = [...new Set([...knownPaths, ...scannedPaths])];
150
+
151
+ const locations: LocationReport[] = [];
152
+ let primaryPath: string | null = null;
153
+
154
+ for (const filePath of allPaths) {
155
+ try {
156
+ const stat = fs.statSync(filePath);
157
+ const allCounts = countAllHooks(filePath);
158
+ const recent = readRecentEntries(filePath, 30);
159
+
160
+ locations.push({
161
+ path: filePath,
162
+ lastModified: stat.mtime,
163
+ totalEntries: Object.values(allCounts).reduce((a, b) => a + b, 0),
164
+ hookCounts: allCounts,
165
+ recentEntries: recent,
166
+ });
167
+
168
+ // Determine primary path (workspace-main or most recent)
169
+ if (filePath.includes('workspace-main') || filePath.includes('workspace-main')) {
170
+ primaryPath = filePath;
171
+ }
172
+ } catch {
173
+ // Skip unreadable files
174
+ }
175
+ }
176
+
177
+ // If no primary found, use most recent
178
+ if (!primaryPath && locations.length > 0) {
179
+ locations.sort((a, b) => {
180
+ if (!a.lastModified) return 1;
181
+ if (!b.lastModified) return -1;
182
+ return b.lastModified.getTime() - a.lastModified.getTime();
183
+ });
184
+ primaryPath = locations[0].path;
185
+ }
186
+
187
+ // Detect misplaced tool hook events
188
+ const misplacedEvents: { path: string; entries: EventLogEntry[] }[] = [];
189
+ for (const loc of locations) {
190
+ if (loc.path === primaryPath) continue;
191
+
192
+ const toolHookEntries = loc.recentEntries.filter(e =>
193
+ e.type === 'hook_execution' && expectedToolHooks.includes(e.data?.hook as string)
194
+ );
195
+
196
+ if (toolHookEntries.length > 0) {
197
+ misplacedEvents.push({ path: loc.path, entries: toolHookEntries });
198
+ }
199
+ }
200
+
201
+ return {
202
+ searchedPaths: allPaths,
203
+ locations,
204
+ primaryPath,
205
+ misplacedEvents,
206
+ };
207
+ }
208
+
209
+ /**
210
+ * Format audit report for display.
211
+ */
212
+ export function formatAuditReport(report: AuditReport): string {
213
+ const lines: string[] = [];
214
+
215
+ lines.push('=== Event Log Audit Report ===\n');
216
+
217
+ lines.push(`Searched ${report.searchedPaths.length} paths:\n`);
218
+ for (const p of report.searchedPaths) {
219
+ lines.push(` ${p}`);
220
+ }
221
+ lines.push('');
222
+
223
+ lines.push(`Primary: ${report.primaryPath ?? 'NOT FOUND'}\n`);
224
+
225
+ for (const loc of report.locations) {
226
+ const isPrimary = loc.path === report.primaryPath;
227
+ lines.push(`─── ${isPrimary ? '[PRIMARY]' : '[OTHER] '}${loc.path}`);
228
+ lines.push(` Last modified: ${loc.lastModified?.toISOString() ?? 'never'}`);
229
+ lines.push(` Hook counts:`);
230
+
231
+ const hooks = Object.entries(loc.hookCounts).sort((a, b) => b[1] - a[1]);
232
+ for (const [hook, count] of hooks) {
233
+ lines.push(` ${hook}: ${count}`);
234
+ }
235
+
236
+ if (hooks.length === 0) {
237
+ lines.push(` (no hooks recorded)`);
238
+ }
239
+ lines.push('');
240
+ }
241
+
242
+ if (report.misplacedEvents.length > 0) {
243
+ lines.push('⚠️ MISPLACED tool hook events detected:');
244
+ for (const me of report.misplacedEvents) {
245
+ lines.push(`\n ${me.path}:`);
246
+ for (const entry of me.entries.slice(0, 5)) {
247
+ lines.push(` ${entry.ts} - ${entry.data.hook}`);
248
+ }
249
+ if (me.entries.length > 5) {
250
+ lines.push(` ... and ${me.entries.length - 5} more`);
251
+ }
252
+ }
253
+ lines.push('');
254
+ lines.push('This means tool hooks are writing events to the wrong .state directory.');
255
+ lines.push('Check workspaceDir resolution in the hook handler.');
256
+ } else {
257
+ lines.push('✅ No misplaced tool hook events detected.');
258
+ }
259
+
260
+ return lines.join('\n');
261
+ }
@@ -8,7 +8,7 @@
8
8
  * - getStats() - 获取统计数据
9
9
  */
10
10
 
11
- import type { TrajectoryDatabase, EvolutionTaskRecord, EvolutionEventRecord } from '../core/trajectory.js';
11
+ import type { TrajectoryDatabase, EvolutionTaskRecord } from '../core/trajectory.js';
12
12
  import { STAGE_LABELS, STAGE_COLORS } from '../core/evolution-logger.js';
13
13
 
14
14
  export interface TaskListFilters {
@@ -27,7 +27,7 @@ export interface EventFilters {
27
27
  }
28
28
 
29
29
  export interface TasksResponse {
30
- items: Array<{
30
+ items: {
31
31
  taskId: string;
32
32
  traceId: string;
33
33
  source: string;
@@ -41,7 +41,7 @@ export interface TasksResponse {
41
41
  resolution: string | null;
42
42
  eventCount: number;
43
43
  createdAt: string;
44
- }>;
44
+ }[];
45
45
  pagination: {
46
46
  page: number;
47
47
  pageSize: number;
@@ -51,7 +51,7 @@ export interface TasksResponse {
51
51
  }
52
52
 
53
53
  export interface EventsResponse {
54
- items: Array<{
54
+ items: {
55
55
  id: number;
56
56
  traceId: string;
57
57
  taskId: string | null;
@@ -63,7 +63,7 @@ export interface EventsResponse {
63
63
  summary: string | null;
64
64
  metadata: Record<string, unknown>;
65
65
  createdAt: string;
66
- }>;
66
+ }[];
67
67
  pagination: {
68
68
  limit: number;
69
69
  offset: number;
@@ -88,7 +88,7 @@ export interface TraceDetailResponse {
88
88
  createdAt: string;
89
89
  updatedAt: string;
90
90
  };
91
- events: Array<{
91
+ events: {
92
92
  id: number;
93
93
  traceId: string;
94
94
  taskId: string | null;
@@ -100,15 +100,15 @@ export interface TraceDetailResponse {
100
100
  summary: string | null;
101
101
  metadata: Record<string, unknown>;
102
102
  createdAt: string;
103
- }>;
104
- timeline: Array<{
103
+ }[];
104
+ timeline: {
105
105
  stage: string;
106
106
  stageLabel: string;
107
107
  stageColor: string;
108
108
  timestamp: string;
109
109
  message: string;
110
110
  summary: string | null;
111
- }>;
111
+ }[];
112
112
  }
113
113
 
114
114
  export interface EvolutionStatsResponse {
@@ -117,16 +117,16 @@ export interface EvolutionStatsResponse {
117
117
  inProgress: number;
118
118
  completed: number;
119
119
  failed: number;
120
- recentActivity: Array<{
120
+ recentActivity: {
121
121
  day: string;
122
122
  created: number;
123
123
  completed: number;
124
- }>;
125
- stageDistribution: Array<{
124
+ }[];
125
+ stageDistribution: {
126
126
  stage: string;
127
127
  stageLabel: string;
128
128
  count: number;
129
- }>;
129
+ }[];
130
130
  }
131
131
 
132
132
  /**
@@ -154,6 +154,7 @@ export class EvolutionQueryService {
154
154
  * 释放资源
155
155
  * 注意:不关闭 trajectory,因为它是单例由 TrajectoryRegistry 管理
156
156
  */
157
+ /* eslint-disable @typescript-eslint/class-methods-use-this -- Reason: Delegates to TrajectoryRegistry lifecycle management */
157
158
  dispose(): void {
158
159
  // EvolutionQueryService 不拥有 trajectory,所以不关闭它
159
160
  // trajectory 是由 TrajectoryRegistry 管理的单例
@@ -324,7 +325,7 @@ export class EvolutionQueryService {
324
325
  /**
325
326
  * 获取统计数据
326
327
  */
327
- getStats(days: number = 30): EvolutionStatsResponse {
328
+ getStats(days = 30): EvolutionStatsResponse {
328
329
  // 获取基础统计
329
330
  const stats = this.trajectory.getEvolutionStats();
330
331
 
@@ -340,21 +341,21 @@ export class EvolutionQueryService {
340
341
  const activityByDay = new Map<string, { created: number; completed: number }>();
341
342
  for (let i = 0; i < days; i++) {
342
343
  const day = new Date(now.getTime() - i * 24 * 60 * 60 * 1000);
343
- const dayStr = day.toISOString().split('T')[0];
344
+ const [dayStr] = day.toISOString().split('T');
344
345
  activityByDay.set(dayStr, { created: 0, completed: 0 });
345
346
  }
346
347
 
347
348
  for (const task of recentTasks) {
348
- const createdDay = task.createdAt.split('T')[0];
349
+ const [createdDay] = task.createdAt.split('T');
349
350
  if (activityByDay.has(createdDay)) {
350
- const entry = activityByDay.get(createdDay)!;
351
- entry.created++;
351
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Reason: has() check guarantees entry exists
352
+ activityByDay.get(createdDay)!.created++;
352
353
  }
353
354
  if (task.completedAt) {
354
- const completedDay = task.completedAt.split('T')[0];
355
+ const [completedDay] = task.completedAt.split('T');
355
356
  if (activityByDay.has(completedDay)) {
356
- const entry = activityByDay.get(completedDay)!;
357
- entry.completed++;
357
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Reason: has() check guarantees entry exists
358
+ activityByDay.get(completedDay)!.completed++;
358
359
  }
359
360
  }
360
361
  }
@@ -394,7 +395,7 @@ const serviceCache = new Map<string, EvolutionQueryService>();
394
395
  */
395
396
  export function getEvolutionQueryService(trajectory: TrajectoryDatabase): EvolutionQueryService {
396
397
  // 使用 trajectory 的 dbPath 作为缓存键
397
- const cacheKey = (trajectory as any).dbPath || 'default';
398
+ const cacheKey = (trajectory as unknown as { dbPath?: string }).dbPath || 'default';
398
399
  const cached = serviceCache.get(cacheKey);
399
400
  if (cached) {
400
401
  return cached;