principles-disciple 1.71.0 → 1.73.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 (309) hide show
  1. package/openclaw.plugin.json +10 -5
  2. package/package.json +17 -19
  3. package/scripts/acceptance-test.mjs +16 -73
  4. package/scripts/sync-plugin.mjs +382 -77
  5. package/src/commands/archive-impl.ts +2 -1
  6. package/src/commands/capabilities.ts +2 -2
  7. package/src/commands/context.ts +2 -2
  8. package/src/commands/disable-impl.ts +2 -1
  9. package/src/commands/evolution-status.ts +16 -16
  10. package/src/commands/export.ts +12 -67
  11. package/src/commands/pain.ts +91 -1
  12. package/src/commands/principle-rollback.ts +2 -1
  13. package/src/commands/promote-impl.ts +7 -43
  14. package/src/commands/rollback-impl.ts +2 -1
  15. package/src/commands/rollback.ts +2 -1
  16. package/src/commands/samples.ts +2 -1
  17. package/src/commands/thinking-os.ts +2 -1
  18. package/src/config/errors.ts +18 -2
  19. package/src/constants/diagnostician.ts +2 -2
  20. package/src/constants/tools.ts +2 -1
  21. package/src/core/__tests__/focus-history.test.ts +210 -0
  22. package/src/core/config.ts +1 -1
  23. package/src/core/confirm-first-gate.ts +255 -0
  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 +38 -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/pain-diagnostic-gate.ts +154 -0
  37. package/src/core/pain-signal.ts +21 -138
  38. package/src/core/pain.ts +15 -88
  39. package/src/core/pd-task-reconciler.ts +26 -115
  40. package/src/core/pd-task-service.ts +9 -9
  41. package/src/core/pd-task-types.ts +23 -127
  42. package/src/core/principle-compiler/__tests__/compiler-replay-gate.test.ts +174 -0
  43. package/src/core/principle-compiler/code-validator.ts +15 -42
  44. package/src/core/principle-compiler/compiler.ts +100 -15
  45. package/src/core/principle-compiler/index.ts +5 -2
  46. package/src/core/principle-compiler/template-generator.ts +4 -104
  47. package/src/core/principle-injection.ts +10 -202
  48. package/src/core/principle-internalization/filesystem-lifecycle-datasource.ts +42 -0
  49. package/src/core/principle-internalization/lifecycle-read-model.ts +39 -242
  50. package/src/core/principle-internalization/principle-lifecycle-service.ts +12 -10
  51. package/src/core/principle-tree-ledger-adapter.ts +145 -0
  52. package/src/core/principle-tree-ledger.ts +8 -6
  53. package/src/core/reflection/reflection-context.ts +14 -109
  54. package/src/core/replay-engine.ts +8 -500
  55. package/src/core/rule-host-helpers.ts +5 -35
  56. package/src/core/rule-host-types.ts +10 -82
  57. package/src/core/rule-host.ts +6 -63
  58. package/src/core/runtime-v2-prompt-activation-reader.ts +231 -0
  59. package/src/core/session-tracker.ts +87 -101
  60. package/src/core/shadow-observation-registry.ts +19 -48
  61. package/src/core/trajectory.ts +3 -1
  62. package/src/core/workflow-funnel-loader.ts +62 -68
  63. package/src/core/workspace-context.ts +46 -0
  64. package/src/core/workspace-dir-service.ts +1 -1
  65. package/src/core/workspace-dir-validation.ts +18 -9
  66. package/src/hooks/AGENTS.md +1 -1
  67. package/src/hooks/gate-block-helper.ts +46 -44
  68. package/src/hooks/gate.ts +207 -7
  69. package/src/hooks/lifecycle.ts +30 -32
  70. package/src/hooks/llm.ts +60 -32
  71. package/src/hooks/pain.ts +297 -103
  72. package/src/hooks/prompt.ts +469 -339
  73. package/src/hooks/subagent.ts +2 -29
  74. package/src/i18n/commands.ts +2 -10
  75. package/src/index.ts +95 -85
  76. package/src/openclaw-sdk.ts +311 -0
  77. package/src/service/central-database.ts +8 -4
  78. package/src/service/evolution-queue-migration.ts +2 -1
  79. package/src/service/evolution-worker.ts +163 -1786
  80. package/src/service/internalization-trigger-adapter.ts +302 -0
  81. package/src/service/keyword-optimization-service.ts +4 -4
  82. package/src/service/monitoring-query-service.ts +1 -215
  83. package/src/service/queue-io.ts +60 -331
  84. package/src/service/runtime-summary-service.ts +115 -18
  85. package/src/service/subagent-workflow/index.ts +0 -41
  86. package/src/service/subagent-workflow/types.ts +9 -120
  87. package/src/service/subagent-workflow/workflow-store.ts +2 -119
  88. package/src/service/workflow-watchdog.ts +0 -43
  89. package/src/types/event-payload.ts +16 -74
  90. package/src/types/event-types.ts +39 -547
  91. package/src/types/hygiene-types.ts +7 -30
  92. package/src/types/principle-tree-schema.ts +20 -222
  93. package/src/types/queue.ts +15 -70
  94. package/src/types/runtime-summary.ts +5 -49
  95. package/src/utils/io.ts +10 -0
  96. package/src/utils/retry.ts +1 -1
  97. package/src/utils/shadow-fingerprint.ts +2 -2
  98. package/src/utils/workspace-resolver.ts +50 -0
  99. package/templates/langs/en/core/AGENTS.md +2 -2
  100. package/templates/langs/en/core/BOOT.md +1 -1
  101. package/templates/langs/en/core/HEARTBEAT.md +2 -2
  102. package/templates/langs/en/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  103. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  104. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  105. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  106. package/templates/langs/en/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  107. package/templates/langs/en/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  108. package/templates/langs/en/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  109. package/templates/langs/en/skills/evolve-task/SKILL.md +1 -1
  110. package/templates/langs/en/skills/pd-cli-operator/SKILL.md +67 -0
  111. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +1 -1
  112. package/templates/langs/en/skills/pd-mentor/SKILL.md +1 -1
  113. package/templates/langs/en/skills/pd-pain-signal/SKILL.md +17 -39
  114. package/templates/langs/en/skills/pd-runtime-v2/SKILL.md +61 -0
  115. package/templates/langs/zh/core/AGENTS.md +2 -2
  116. package/templates/langs/zh/core/BOOT.md +1 -1
  117. package/templates/langs/zh/core/HEARTBEAT.md +2 -2
  118. package/templates/langs/zh/skills/ai-sprint-orchestration/references/agent-registry.json +1 -72
  119. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/bugfix-complex-template.json +6 -6
  120. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/feature-complex-template.json +6 -6
  121. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/nocturnal-trinity-quality-enhancement.json +8 -8
  122. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal-verify.json +2 -12
  123. package/templates/langs/zh/skills/ai-sprint-orchestration/references/specs/workflow-validation-minimal.json +2 -12
  124. package/templates/langs/zh/skills/ai-sprint-orchestration/runtime/.gitignore +2 -2
  125. package/templates/langs/zh/skills/ai-sprint-orchestration/scripts/run.mjs +51 -15
  126. package/templates/langs/zh/skills/ai-sprint-orchestration/test/run.test.mjs +21 -5
  127. package/templates/langs/zh/skills/evolve-task/SKILL.md +2 -2
  128. package/templates/langs/zh/skills/pd-cli-operator/SKILL.md +67 -0
  129. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +1 -1
  130. package/templates/langs/zh/skills/pd-mentor/SKILL.md +1 -1
  131. package/templates/langs/zh/skills/pd-pain-signal/SKILL.md +17 -38
  132. package/templates/langs/zh/skills/pd-runtime-v2/SKILL.md +61 -0
  133. package/tests/build-artifacts.test.ts +1 -3
  134. package/tests/commands/evolution-status.test.ts +0 -118
  135. package/tests/core/bootstrap-rules.test.ts +1 -1
  136. package/tests/core/config.test.ts +1 -1
  137. package/tests/core/event-log.test.ts +35 -0
  138. package/tests/core/evolution-engine.test.ts +610 -0
  139. package/tests/core/file-store.test.ts +102 -0
  140. package/tests/core/focus-history.test.ts +203 -11
  141. package/tests/core/merge-gate-audit.test.ts +2 -169
  142. package/tests/core/model-deployment-registry.test.ts +7 -1
  143. package/tests/core/model-training-registry.test.ts +19 -0
  144. package/tests/core/observability.test.ts +0 -1
  145. package/tests/core/pain-diagnostic-gate.test.ts +498 -0
  146. package/tests/core/pain.test.ts +0 -1
  147. package/tests/core/principle-internalization/deprecated-readiness.test.ts +2 -2
  148. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +2 -2
  149. package/tests/core/principle-internalization/{internalization-routing-policy.test.ts → lifecycle-routing-policy.test.ts} +6 -6
  150. package/tests/core/principle-internalization/lineage-source-retired.test.ts +56 -0
  151. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +1 -23
  152. package/tests/core/principle-tree-ledger-adapter.test.ts +253 -0
  153. package/tests/core/reflection-context.test.ts +0 -14
  154. package/tests/core/replay-engine.test.ts +127 -215
  155. package/tests/core/rule-host-helpers.test.ts +2 -2
  156. package/tests/core/rule-implementation-runtime.test.ts +0 -27
  157. package/tests/core/workflow-funnel-loader.test.ts +162 -0
  158. package/tests/core/workspace-dir-validation.test.ts +8 -1
  159. package/tests/core-anti-growth.test.ts +192 -0
  160. package/tests/hook-workspace-nextaction-contract.test.ts +42 -0
  161. package/tests/hooks/confirm-first-gate.test.ts +333 -0
  162. package/tests/hooks/gate-auto-correct-shadow.test.ts +310 -0
  163. package/tests/hooks/gate-auto-correct.test.ts +665 -0
  164. package/tests/hooks/gate-rule-host-pipeline.test.ts +2 -1
  165. package/tests/hooks/pain.test.ts +269 -12
  166. package/tests/hooks/prompt-characterization.test.ts +500 -0
  167. package/tests/hooks/prompt-size-guard.test.ts +329 -0
  168. package/tests/hooks/runtime-v2-prompt-activation.test.ts +869 -0
  169. package/tests/index.test.ts +94 -1
  170. package/tests/integration/auto-entry-gate.test.ts +248 -0
  171. package/tests/integration/internalization-trigger-guard.test.ts +69 -0
  172. package/tests/integration/m8-legacy-paths.test.ts +63 -0
  173. package/tests/integration/runtime-v2-pain-guard.test.ts +125 -0
  174. package/tests/plugin-config-resolution-cutover.test.ts +359 -0
  175. package/tests/runtime-v2-discovery-guard.test.ts +154 -0
  176. package/tests/service/central-database.test.ts +457 -0
  177. package/tests/service/evolution-worker.correction-observer.test.ts +173 -0
  178. package/tests/service/evolution-worker.timeout.test.ts +11 -129
  179. package/tests/service/internalization-trigger-adapter.test.ts +251 -0
  180. package/tests/service/monitoring-query-service.test.ts +1 -47
  181. package/tests/service/queue-io.test.ts +1 -62
  182. package/tests/service/runtime-summary-service.test.ts +184 -3
  183. package/tests/service/workflow-watchdog.test.ts +0 -91
  184. package/tests/utils/file-lock.test.ts +5 -3
  185. package/tests/utils/session-key.test.ts +52 -0
  186. package/tests/utils/subagent-probe.test.ts +48 -1
  187. package/vitest.config.ts +4 -11
  188. package/.planning/codebase/ARCHITECTURE.md +0 -157
  189. package/.planning/codebase/CONCERNS.md +0 -145
  190. package/.planning/codebase/CONVENTIONS.md +0 -148
  191. package/.planning/codebase/INTEGRATIONS.md +0 -81
  192. package/.planning/codebase/STACK.md +0 -87
  193. package/.planning/codebase/STRUCTURE.md +0 -193
  194. package/.planning/codebase/TESTING.md +0 -243
  195. package/.planning/phases/01-basic-visualization/01-GAP-CLOSURE-VERIFICATION.md +0 -113
  196. package/docs/COMMAND_REFERENCE.md +0 -76
  197. package/docs/COMMAND_REFERENCE_EN.md +0 -79
  198. package/scripts/build-web.mjs +0 -46
  199. package/scripts/diagnose-nocturnal.mjs +0 -537
  200. package/scripts/seed-nocturnal-scenarios.mjs +0 -384
  201. package/src/commands/nocturnal-review.ts +0 -322
  202. package/src/commands/nocturnal-rollout.ts +0 -790
  203. package/src/commands/nocturnal-train.ts +0 -986
  204. package/src/commands/pd-reflect.ts +0 -88
  205. package/src/core/adaptive-thresholds.ts +0 -478
  206. package/src/core/diagnostician-task-store.ts +0 -192
  207. package/src/core/nocturnal-arbiter.ts +0 -715
  208. package/src/core/nocturnal-artifact-lineage.ts +0 -116
  209. package/src/core/nocturnal-artificer.ts +0 -257
  210. package/src/core/nocturnal-candidate-scoring.ts +0 -530
  211. package/src/core/nocturnal-compliance.ts +0 -1146
  212. package/src/core/nocturnal-dataset.ts +0 -763
  213. package/src/core/nocturnal-executability.ts +0 -428
  214. package/src/core/nocturnal-export.ts +0 -499
  215. package/src/core/nocturnal-paths.ts +0 -240
  216. package/src/core/nocturnal-reasoning-deriver.ts +0 -343
  217. package/src/core/nocturnal-rule-implementation-validator.ts +0 -246
  218. package/src/core/nocturnal-snapshot-contract.ts +0 -99
  219. package/src/core/nocturnal-trajectory-extractor.ts +0 -512
  220. package/src/core/nocturnal-trinity-types.ts +0 -218
  221. package/src/core/nocturnal-trinity.ts +0 -2680
  222. package/src/core/principle-internalization/deprecated-readiness.ts +0 -93
  223. package/src/core/principle-internalization/internalization-routing-policy.ts +0 -208
  224. package/src/core/principle-internalization/lifecycle-metrics.ts +0 -152
  225. package/src/http/principles-console-route.ts +0 -709
  226. package/src/service/central-health-service.ts +0 -49
  227. package/src/service/central-overview-service.ts +0 -138
  228. package/src/service/control-ui-query-service.ts +0 -900
  229. package/src/service/cooldown-strategy.ts +0 -97
  230. package/src/service/evolution-pain-context.ts +0 -79
  231. package/src/service/evolution-query-service.ts +0 -407
  232. package/src/service/health-query-service.ts +0 -1038
  233. package/src/service/nocturnal-config.ts +0 -214
  234. package/src/service/nocturnal-runtime.ts +0 -734
  235. package/src/service/nocturnal-service.ts +0 -1605
  236. package/src/service/nocturnal-target-selector.ts +0 -545
  237. package/src/service/sleep-cycle.ts +0 -157
  238. package/src/service/startup-reconciler.ts +0 -112
  239. package/src/service/subagent-workflow/correction-observer-types.ts +0 -82
  240. package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +0 -250
  241. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +0 -1
  242. package/src/service/subagent-workflow/dynamic-timeout.ts +0 -30
  243. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +0 -268
  244. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +0 -795
  245. package/src/service/subagent-workflow/runtime-direct-driver.ts +0 -268
  246. package/src/service/subagent-workflow/workflow-manager-base.ts +0 -580
  247. package/src/tools/write-pain-flag.ts +0 -215
  248. package/tests/commands/nocturnal-review.test.ts +0 -448
  249. package/tests/commands/nocturnal-train.test.ts +0 -97
  250. package/tests/commands/pd-reflect.test.ts +0 -49
  251. package/tests/core/adaptive-thresholds.test.ts +0 -261
  252. package/tests/core/nocturnal-arbiter.test.ts +0 -559
  253. package/tests/core/nocturnal-artifact-lineage.test.ts +0 -53
  254. package/tests/core/nocturnal-artificer.test.ts +0 -241
  255. package/tests/core/nocturnal-candidate-scoring.test.ts +0 -532
  256. package/tests/core/nocturnal-compliance-p-principles.test.ts +0 -133
  257. package/tests/core/nocturnal-compliance.test.ts +0 -646
  258. package/tests/core/nocturnal-dataset.test.ts +0 -892
  259. package/tests/core/nocturnal-e2e.test.ts +0 -234
  260. package/tests/core/nocturnal-executability.test.ts +0 -357
  261. package/tests/core/nocturnal-export.test.ts +0 -517
  262. package/tests/core/nocturnal-reasoning-deriver.test.ts +0 -372
  263. package/tests/core/nocturnal-reviewed-subset-comparison.test.ts +0 -428
  264. package/tests/core/nocturnal-rule-implementation-validator.test.ts +0 -127
  265. package/tests/core/nocturnal-snapshot-contract.test.ts +0 -121
  266. package/tests/core/nocturnal-trajectory-extractor.test.ts +0 -634
  267. package/tests/core/nocturnal-trinity.test.ts +0 -2053
  268. package/tests/core/pain-auto-repair.test.ts +0 -96
  269. package/tests/core/pain-integration.test.ts +0 -510
  270. package/tests/fixtures/nocturnal-reviewed-subset.json +0 -183
  271. package/tests/http/principles-console-route.test.ts +0 -162
  272. package/tests/integration/chaos-resilience.test.ts +0 -348
  273. package/tests/integration/empathy-workflow-integration.test.ts +0 -626
  274. package/tests/integration/pain-diagnostician-loop.e2e.test.ts +0 -380
  275. package/tests/service/control-ui-query-service.test.ts +0 -121
  276. package/tests/service/cooldown-strategy.test.ts +0 -164
  277. package/tests/service/data-endpoints-regression.test.ts +0 -834
  278. package/tests/service/empathy-observer-workflow-manager.test.ts +0 -175
  279. package/tests/service/evolution-worker.nocturnal.test.ts +0 -601
  280. package/tests/service/nocturnal-runtime-hardening.test.ts +0 -118
  281. package/tests/service/nocturnal-runtime.test.ts +0 -473
  282. package/tests/service/nocturnal-service-code-candidate.test.ts +0 -330
  283. package/tests/service/nocturnal-target-selector.test.ts +0 -615
  284. package/tests/service/startup-reconciler.test.ts +0 -148
  285. package/tests/tools/write-pain-flag.test.ts +0 -358
  286. package/ui/src/App.tsx +0 -45
  287. package/ui/src/api.ts +0 -220
  288. package/ui/src/charts.tsx +0 -955
  289. package/ui/src/components/ErrorState.tsx +0 -6
  290. package/ui/src/components/Loading.tsx +0 -13
  291. package/ui/src/components/ProtectedRoute.tsx +0 -12
  292. package/ui/src/components/Shell.tsx +0 -91
  293. package/ui/src/components/WorkspaceConfig.tsx +0 -178
  294. package/ui/src/components/index.ts +0 -5
  295. package/ui/src/context/auth.tsx +0 -80
  296. package/ui/src/context/theme.tsx +0 -66
  297. package/ui/src/hooks/useAutoRefresh.ts +0 -39
  298. package/ui/src/i18n/ui.ts +0 -473
  299. package/ui/src/main.tsx +0 -16
  300. package/ui/src/pages/EvolutionPage.tsx +0 -333
  301. package/ui/src/pages/FeedbackPage.tsx +0 -138
  302. package/ui/src/pages/GateMonitorPage.tsx +0 -136
  303. package/ui/src/pages/LoginPage.tsx +0 -89
  304. package/ui/src/pages/OverviewPage.tsx +0 -599
  305. package/ui/src/pages/SamplesPage.tsx +0 -174
  306. package/ui/src/pages/ThinkingModelsPage.tsx +0 -702
  307. package/ui/src/styles.css +0 -2020
  308. package/ui/src/types.ts +0 -384
  309. 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
- });