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
@@ -1,626 +0,0 @@
1
- /**
2
- * Integration tests for Empathy Observer Workflow
3
- *
4
- * These tests verify the complete chain from hook entry to workflow state/events.
5
- * Unlike unit tests, these use real file system and SQLite for WorkflowStore.
6
- *
7
- * Coverage:
8
- * 1. EmpathyObserverWorkflowManager startWorkflow triggers workflow helper
9
- * 2. WorkflowStore records spawn/wait/finalize events
10
- * 3. Lifecycle cleanup (sweepExpiredWorkflows) works when wired
11
- */
12
-
13
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
14
- import * as fs from 'node:fs';
15
- import * as os from 'node:os';
16
- import * as path from 'node:path';
17
- import { EmpathyObserverWorkflowManager, empathyObserverWorkflowSpec } from '../../src/service/subagent-workflow/empathy-observer-workflow-manager.js';
18
- import { WorkflowStore } from '../../src/service/subagent-workflow/workflow-store.js';
19
- import { handleBeforePromptBuild } from '../../src/hooks/prompt.js';
20
- import { WorkspaceContext } from '../../src/core/workspace-context.js';
21
-
22
- /**
23
- * Helper to create a mock async function for workflow-manager tests.
24
- */
25
- function mockAsyncFn<T extends (...args: any[]) => Promise<any>>(impl: (...args: any[]) => any) {
26
- const fn = vi.fn(impl) as unknown as T;
27
- Object.defineProperty(fn, 'constructor', {
28
- value: function AsyncFunction() {},
29
- writable: true,
30
- configurable: true,
31
- });
32
- return fn;
33
- }
34
-
35
- describe('Empathy Workflow Integration (PR2)', () => {
36
- let tempDir: string;
37
- let stateDir: string;
38
- let store: WorkflowStore;
39
-
40
- const logger = {
41
- info: vi.fn(),
42
- warn: vi.fn(),
43
- error: vi.fn(),
44
- debug: vi.fn(),
45
- };
46
-
47
- beforeEach(() => {
48
- tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-empathy-integration-'));
49
- stateDir = path.join(tempDir, '.state');
50
- fs.mkdirSync(stateDir, { recursive: true });
51
- });
52
-
53
- afterEach(() => {
54
- store?.dispose();
55
- fs.rmSync(tempDir, { recursive: true, force: true });
56
- vi.clearAllMocks();
57
- });
58
-
59
- // ---------------------------------------------------------------------------
60
- // Test 1: WorkflowStore records spawn/wait/finalize events
61
- // ---------------------------------------------------------------------------
62
- describe('WorkflowStore event chain', () => {
63
- it('records spawned event when workflow starts', async () => {
64
- store = new WorkflowStore({ workspaceDir: tempDir });
65
-
66
- const subagent = {
67
- run: mockAsyncFn(async () => ({ runId: 'run-001' })),
68
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
69
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
70
- deleteSession: mockAsyncFn(async () => {}),
71
- };
72
-
73
- const manager = new EmpathyObserverWorkflowManager({
74
- workspaceDir: tempDir,
75
- logger,
76
- subagent,
77
- });
78
-
79
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
80
- parentSessionId: 'session-001',
81
- taskInput: 'user message',
82
- });
83
-
84
- // Verify workflow was created in store
85
- const workflow = store.getWorkflow(handle.workflowId);
86
- expect(workflow).not.toBeNull();
87
- expect(workflow?.state).toBe('active');
88
- expect(workflow?.parent_session_id).toBe('session-001');
89
-
90
- // Verify spawned event was recorded
91
- const events = store.getEvents(handle.workflowId);
92
- const spawnedEvent = events.find(e => e.event_type === 'spawned');
93
- expect(spawnedEvent).toBeDefined();
94
- expect(spawnedEvent?.to_state).toBe('active');
95
-
96
- manager.dispose();
97
- });
98
-
99
- it('records wait_result and finalized events on successful completion', async () => {
100
- store = new WorkflowStore({ workspaceDir: tempDir });
101
-
102
- const subagent = {
103
- run: mockAsyncFn(async () => ({ runId: 'run-002' })),
104
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
105
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
106
- deleteSession: mockAsyncFn(async () => {}),
107
- };
108
-
109
- const manager = new EmpathyObserverWorkflowManager({
110
- workspaceDir: tempDir,
111
- logger,
112
- subagent,
113
- });
114
-
115
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
116
- parentSessionId: 'session-002',
117
- taskInput: 'user message',
118
- });
119
-
120
- // Clear the scheduled poll timeout to manually trigger notifyWaitResult
121
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
122
- if (timeout) {
123
- clearTimeout(timeout);
124
- (manager as any).activeWorkflows.delete(handle.workflowId);
125
- }
126
-
127
- await manager.notifyWaitResult(handle.workflowId, 'ok');
128
-
129
- // Verify final state
130
- const workflow = store.getWorkflow(handle.workflowId);
131
- expect(workflow?.state).toBe('completed');
132
-
133
- // Verify event chain: spawned -> wait_result -> finalized
134
- const events = store.getEvents(handle.workflowId);
135
- const eventTypes = events.map(e => e.event_type);
136
-
137
- expect(eventTypes).toContain('spawned');
138
- expect(eventTypes).toContain('wait_result');
139
- expect(eventTypes).toContain('finalized');
140
-
141
- manager.dispose();
142
- });
143
- });
144
-
145
- // ---------------------------------------------------------------------------
146
- // Test 2: sweepExpiredWorkflows works when wired
147
- // ---------------------------------------------------------------------------
148
- describe('Lifecycle cleanup (sweepExpiredWorkflows)', () => {
149
- it('sweeps workflows older than TTL', async () => {
150
- store = new WorkflowStore({ workspaceDir: tempDir });
151
-
152
- const subagent = {
153
- run: mockAsyncFn(async () => ({ runId: 'run-003' })),
154
- waitForRun: mockAsyncFn(async () => ({ status: 'timeout' as const })),
155
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: [] })),
156
- deleteSession: mockAsyncFn(async () => {}),
157
- };
158
-
159
- const manager = new EmpathyObserverWorkflowManager({
160
- workspaceDir: tempDir,
161
- logger,
162
- subagent,
163
- });
164
-
165
- // Create a workflow
166
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
167
- parentSessionId: 'session-003',
168
- taskInput: 'user message',
169
- });
170
-
171
- // Clear the scheduled poll timeout
172
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
173
- if (timeout) {
174
- clearTimeout(timeout);
175
- (manager as any).activeWorkflows.delete(handle.workflowId);
176
- }
177
-
178
- // Manually set last_observed_at to simulate old workflow
179
- const oldTime = Date.now() - 10 * 60 * 1000; // 10 minutes ago
180
- const db = (store as any).db;
181
- db.prepare('UPDATE subagent_workflows SET last_observed_at = ? WHERE workflow_id = ?').run(oldTime, handle.workflowId);
182
-
183
- // Run sweep with 5 minute TTL
184
- const sweptCount = await manager.sweepExpiredWorkflows(5 * 60 * 1000);
185
-
186
- expect(sweptCount).toBe(1);
187
-
188
- // Verify workflow is now expired
189
- const workflow = store.getWorkflow(handle.workflowId);
190
- expect(workflow?.state).toBe('expired');
191
-
192
- // Verify swept event was recorded
193
- const events = store.getEvents(handle.workflowId);
194
- const sweptEvent = events.find(e => e.event_type === 'swept');
195
- expect(sweptEvent).toBeDefined();
196
-
197
- manager.dispose();
198
- });
199
- });
200
-
201
- // ---------------------------------------------------------------------------
202
- // Test 3: Debug summary is accessible
203
- // ---------------------------------------------------------------------------
204
- describe('Observability (getWorkflowDebugSummary)', () => {
205
- it('provides debug summary with recent events', async () => {
206
- store = new WorkflowStore({ workspaceDir: tempDir });
207
-
208
- const subagent = {
209
- run: mockAsyncFn(async () => ({ runId: 'run-004' })),
210
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
211
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
212
- deleteSession: mockAsyncFn(async () => {}),
213
- };
214
-
215
- const manager = new EmpathyObserverWorkflowManager({
216
- workspaceDir: tempDir,
217
- logger,
218
- subagent,
219
- });
220
-
221
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
222
- parentSessionId: 'session-004',
223
- taskInput: 'user message',
224
- });
225
-
226
- const summary = await manager.getWorkflowDebugSummary(handle.workflowId);
227
-
228
- expect(summary).not.toBeNull();
229
- expect(summary?.workflowId).toBe(handle.workflowId);
230
- expect(summary?.workflowType).toBe('empathy-observer');
231
- expect(summary?.transport).toBe('runtime_direct');
232
- expect(summary?.state).toBe('active');
233
- expect(summary?.parentSessionId).toBe('session-004');
234
- expect(summary?.recentEvents.length).toBeGreaterThan(0);
235
- expect(summary?.recentEvents[0].eventType).toBe('spawned');
236
-
237
- manager.dispose();
238
- });
239
- });
240
-
241
- // ---------------------------------------------------------------------------
242
- // Test 4: Workflow isolation
243
- // ---------------------------------------------------------------------------
244
- describe('Workflow isolation', () => {
245
- it('each workflow is recorded independently in WorkflowStore', async () => {
246
- store = new WorkflowStore({ workspaceDir: tempDir });
247
-
248
- const subagent = {
249
- run: mockAsyncFn(async () => ({ runId: 'run-005' })),
250
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
251
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
252
- deleteSession: mockAsyncFn(async () => {}),
253
- };
254
-
255
- const manager = new EmpathyObserverWorkflowManager({
256
- workspaceDir: tempDir,
257
- logger,
258
- subagent,
259
- });
260
-
261
- // Start workflow
262
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
263
- parentSessionId: 'session-005',
264
- taskInput: 'user message',
265
- });
266
-
267
- // Verify workflow is recorded independently
268
- const workflow = store.getWorkflow(handle.workflowId);
269
- expect(workflow).not.toBeNull();
270
- expect(workflow?.workflow_type).toBe('empathy-observer');
271
-
272
- manager.dispose();
273
- });
274
- });
275
-
276
- // ---------------------------------------------------------------------------
277
- // Test 5: State transition validation
278
- // ---------------------------------------------------------------------------
279
- describe('Workflow state transitions', () => {
280
- it('transitions: active -> wait_result -> finalizing -> completed', async () => {
281
- store = new WorkflowStore({ workspaceDir: tempDir });
282
-
283
- const subagent = {
284
- run: mockAsyncFn(async () => ({ runId: 'run-006' })),
285
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
286
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
287
- deleteSession: mockAsyncFn(async () => {}),
288
- };
289
-
290
- const manager = new EmpathyObserverWorkflowManager({
291
- workspaceDir: tempDir,
292
- logger,
293
- subagent,
294
- });
295
-
296
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
297
- parentSessionId: 'session-006',
298
- taskInput: 'user message',
299
- });
300
-
301
- // Verify initial state
302
- expect(store.getWorkflow(handle.workflowId)?.state).toBe('active');
303
-
304
- // Clear timeout and trigger wait result
305
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
306
- if (timeout) {
307
- clearTimeout(timeout);
308
- (manager as any).activeWorkflows.delete(handle.workflowId);
309
- }
310
-
311
- await manager.notifyWaitResult(handle.workflowId, 'ok');
312
-
313
- // Verify final state
314
- expect(store.getWorkflow(handle.workflowId)?.state).toBe('completed');
315
-
316
- // Verify state transition events
317
- const events = store.getEvents(handle.workflowId);
318
- const stateChanges = events.filter(e => e.event_type === 'state_change' || e.event_type === 'wait_result' || e.event_type === 'finalized');
319
-
320
- // Should have spawn, wait_result, finalized at minimum
321
- expect(stateChanges.length).toBeGreaterThanOrEqual(2);
322
-
323
- manager.dispose();
324
- });
325
-
326
- it('transitions to terminal_error on timeout', async () => {
327
- store = new WorkflowStore({ workspaceDir: tempDir });
328
-
329
- const subagent = {
330
- run: mockAsyncFn(async () => ({ runId: 'run-007' })),
331
- waitForRun: mockAsyncFn(async () => ({ status: 'timeout' as const })),
332
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: [] })),
333
- deleteSession: mockAsyncFn(async () => {}),
334
- };
335
-
336
- const manager = new EmpathyObserverWorkflowManager({
337
- workspaceDir: tempDir,
338
- logger,
339
- subagent,
340
- });
341
-
342
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
343
- parentSessionId: 'session-007',
344
- taskInput: 'user message',
345
- });
346
-
347
- // Clear timeout and trigger error result
348
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
349
- if (timeout) {
350
- clearTimeout(timeout);
351
- (manager as any).activeWorkflows.delete(handle.workflowId);
352
- }
353
-
354
- await manager.notifyWaitResult(handle.workflowId, 'timeout', 'wait timed out');
355
-
356
- // Verify terminal state
357
- expect(store.getWorkflow(handle.workflowId)?.state).toBe('terminal_error');
358
-
359
- manager.dispose();
360
- });
361
- });
362
-
363
- // ---------------------------------------------------------------------------
364
- // Test 6: PR2.1 Task 1 - subagent_ended -> notifyLifecycleEvent wiring
365
- // ---------------------------------------------------------------------------
366
- describe('PR2.1: subagent_ended lifecycle event wiring', () => {
367
- it('triggers notifyWaitResult when subagent_ended event received', async () => {
368
- store = new WorkflowStore({ workspaceDir: tempDir });
369
-
370
- const subagent = {
371
- run: mockAsyncFn(async () => ({ runId: 'run-pr21-001' })),
372
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
373
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":false}'] })),
374
- deleteSession: mockAsyncFn(async () => {}),
375
- };
376
-
377
- const manager = new EmpathyObserverWorkflowManager({
378
- workspaceDir: tempDir,
379
- logger,
380
- subagent,
381
- });
382
-
383
- // Start a helper workflow
384
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
385
- parentSessionId: 'session-pr21-001',
386
- taskInput: 'user message',
387
- });
388
-
389
- // Get the child session key
390
- const workflow = store.getWorkflow(handle.workflowId);
391
- expect(workflow).not.toBeNull();
392
- const childSessionKey = workflow!.child_session_key;
393
-
394
- // Verify child session key has expected prefix
395
- expect(childSessionKey).toMatch(/^agent:main:subagent:workflow-/);
396
-
397
- // Clear the scheduled poll timeout to prevent auto-wait
398
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
399
- if (timeout) {
400
- clearTimeout(timeout);
401
- (manager as any).activeWorkflows.delete(handle.workflowId);
402
- }
403
-
404
- // Verify initial state is active
405
- expect(store.getWorkflow(handle.workflowId)?.state).toBe('active');
406
-
407
- // Simulate subagent_ended event by calling notifyLifecycleEvent
408
- // This triggers notifyWaitResult internally
409
- await manager.notifyLifecycleEvent(handle.workflowId, 'subagent_ended', {
410
- outcome: 'ok',
411
- });
412
-
413
- // Verify workflow completed through lifecycle event path
414
- const finalWorkflow = store.getWorkflow(handle.workflowId);
415
- expect(finalWorkflow?.state).toBe('completed');
416
-
417
- // Verify event chain includes finalized
418
- const events = store.getEvents(handle.workflowId);
419
- const finalizedEvent = events.find(e => e.event_type === 'finalized');
420
- expect(finalizedEvent).toBeDefined();
421
-
422
- manager.dispose();
423
- });
424
-
425
- it('handles error outcome via lifecycle event', async () => {
426
- store = new WorkflowStore({ workspaceDir: tempDir });
427
-
428
- const subagent = {
429
- run: mockAsyncFn(async () => ({ runId: 'run-pr21-002' })),
430
- waitForRun: mockAsyncFn(async () => ({ status: 'error' as const, error: 'test error' })),
431
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: [] })),
432
- deleteSession: mockAsyncFn(async () => {}),
433
- };
434
-
435
- const manager = new EmpathyObserverWorkflowManager({
436
- workspaceDir: tempDir,
437
- logger,
438
- subagent,
439
- });
440
-
441
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
442
- parentSessionId: 'session-pr21-002',
443
- taskInput: 'test',
444
- });
445
-
446
- // Clear timeout
447
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
448
- if (timeout) {
449
- clearTimeout(timeout);
450
- (manager as any).activeWorkflows.delete(handle.workflowId);
451
- }
452
-
453
- // Notify error outcome
454
- await manager.notifyLifecycleEvent(handle.workflowId, 'subagent_ended', {
455
- outcome: 'error',
456
- error: 'subagent failed',
457
- });
458
-
459
- // Verify terminal state
460
- const workflow = store.getWorkflow(handle.workflowId);
461
- expect(workflow?.state).toBe('terminal_error');
462
-
463
- manager.dispose();
464
- });
465
- });
466
-
467
- // ---------------------------------------------------------------------------
468
- // Test 7: PR2.1 Task 2 - sweepExpiredWorkflows integration
469
- // ---------------------------------------------------------------------------
470
- describe('PR2.1: sweepExpiredWorkflows integration', () => {
471
- it('WorkflowStore.getExpiredWorkflows returns workflows past TTL', async () => {
472
- store = new WorkflowStore({ workspaceDir: tempDir });
473
-
474
- const subagent = {
475
- run: mockAsyncFn(async () => ({ runId: 'run-sweep-001' })),
476
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
477
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{}'] })),
478
- deleteSession: mockAsyncFn(async () => {}),
479
- };
480
-
481
- const manager = new EmpathyObserverWorkflowManager({
482
- workspaceDir: tempDir,
483
- logger,
484
- subagent,
485
- });
486
-
487
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
488
- parentSessionId: 'session-sweep-001',
489
- taskInput: 'test',
490
- });
491
-
492
- // Clear timeout
493
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
494
- if (timeout) {
495
- clearTimeout(timeout);
496
- (manager as any).activeWorkflows.delete(handle.workflowId);
497
- }
498
-
499
- // Manually set last_observed_at to simulate expired workflow
500
- const db = (store as any).db;
501
- const oldTime = Date.now() - 10 * 60 * 1000; // 10 minutes ago
502
- db.prepare('UPDATE subagent_workflows SET last_observed_at = ? WHERE workflow_id = ?').run(oldTime, handle.workflowId);
503
-
504
- // Verify getExpiredWorkflows finds it
505
- const expired = store.getExpiredWorkflows(5 * 60 * 1000);
506
- expect(expired.length).toBe(1);
507
- expect(expired[0].workflow_id).toBe(handle.workflowId);
508
-
509
- manager.dispose();
510
- });
511
-
512
- it('sweepExpiredWorkflows marks workflows as expired', async () => {
513
- store = new WorkflowStore({ workspaceDir: tempDir });
514
-
515
- const subagent = {
516
- run: mockAsyncFn(async () => ({ runId: 'run-sweep-002' })),
517
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
518
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{}'] })),
519
- deleteSession: mockAsyncFn(async () => {}),
520
- };
521
-
522
- const manager = new EmpathyObserverWorkflowManager({
523
- workspaceDir: tempDir,
524
- logger,
525
- subagent,
526
- });
527
-
528
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
529
- parentSessionId: 'session-sweep-002',
530
- taskInput: 'test',
531
- });
532
-
533
- // Clear timeout
534
- const timeout = (manager as any).activeWorkflows.get(handle.workflowId);
535
- if (timeout) {
536
- clearTimeout(timeout);
537
- (manager as any).activeWorkflows.delete(handle.workflowId);
538
- }
539
-
540
- // Manually expire
541
- const db = (store as any).db;
542
- const oldTime = Date.now() - 10 * 60 * 1000;
543
- db.prepare('UPDATE subagent_workflows SET last_observed_at = ? WHERE workflow_id = ?').run(oldTime, handle.workflowId);
544
-
545
- // Sweep
546
- const count = await manager.sweepExpiredWorkflows(5 * 60 * 1000);
547
- expect(count).toBe(1);
548
-
549
- // Verify state
550
- const workflow = store.getWorkflow(handle.workflowId);
551
- expect(workflow?.state).toBe('expired');
552
-
553
- // Verify swept event
554
- const events = store.getEvents(handle.workflowId);
555
- const sweptEvent = events.find(e => e.event_type === 'swept');
556
- expect(sweptEvent).toBeDefined();
557
-
558
- manager.dispose();
559
- });
560
- });
561
-
562
- // ---------------------------------------------------------------------------
563
- // Test 8: PR2.1 Task 3 - getWorkflowDebugSummary slash command
564
- // ---------------------------------------------------------------------------
565
- describe('PR2.1: getWorkflowDebugSummary accessibility', () => {
566
- it('provides complete debug summary with all fields', async () => {
567
- store = new WorkflowStore({ workspaceDir: tempDir });
568
-
569
- const subagent = {
570
- run: mockAsyncFn(async () => ({ runId: 'run-debug-001' })),
571
- waitForRun: mockAsyncFn(async () => ({ status: 'ok' as const })),
572
- getSessionMessages: mockAsyncFn(async () => ({ messages: [], assistantTexts: ['{"damageDetected":true,"severity":"moderate"}'] })),
573
- deleteSession: mockAsyncFn(async () => {}),
574
- };
575
-
576
- const manager = new EmpathyObserverWorkflowManager({
577
- workspaceDir: tempDir,
578
- logger,
579
- subagent,
580
- });
581
-
582
- const handle = await manager.startWorkflow(empathyObserverWorkflowSpec, {
583
- parentSessionId: 'session-debug-001',
584
- workspaceDir: tempDir,
585
- taskInput: 'This is a test user message for debugging',
586
- metadata: { customField: 'customValue' },
587
- });
588
-
589
- const summary = await manager.getWorkflowDebugSummary(handle.workflowId);
590
-
591
- expect(summary).not.toBeNull();
592
- expect(summary?.workflowId).toBe(handle.workflowId);
593
- expect(summary?.workflowType).toBe('empathy-observer');
594
- expect(summary?.transport).toBe('runtime_direct');
595
- expect(summary?.state).toBe('active');
596
- expect(summary?.cleanupState).toBe('none');
597
- expect(summary?.parentSessionId).toBe('session-debug-001');
598
- expect(summary?.childSessionKey).toContain('workflow-');
599
- expect(summary?.runId).toBe('run-debug-001');
600
- expect(summary?.metadata.taskInput).toBe('This is a test user message for debugging');
601
- expect(summary?.metadata.customField).toBe('customValue');
602
- expect(summary?.recentEvents.length).toBeGreaterThan(0);
603
-
604
- // Verify first event is spawned
605
- expect(summary?.recentEvents[0].eventType).toBe('spawned');
606
-
607
- manager.dispose();
608
- });
609
-
610
- it('returns null for non-existent workflow', async () => {
611
- store = new WorkflowStore({ workspaceDir: tempDir });
612
-
613
- const manager = new EmpathyObserverWorkflowManager({
614
- workspaceDir: tempDir,
615
- logger,
616
- subagent: {} as any,
617
- });
618
-
619
- const summary = await manager.getWorkflowDebugSummary('non-existent-id');
620
- expect(summary).toBeNull();
621
-
622
- manager.dispose();
623
- });
624
- });
625
-
626
- });