principles-disciple 1.72.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 +459 -439
  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 +59 -16
  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 +32 -17
  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 +3 -1
  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,580 +0,0 @@
1
- /**
2
- * WorkflowManagerBase - Shared base class for polling-based workflow managers
3
- *
4
- * Extracts common lifecycle, state transitions, and store operations from
5
- * EmpathyObserverWorkflowManager.
6
- *
7
- * Both managers implement the same polling-based workflow lifecycle:
8
- * startWorkflow → driver.run() → store.createWorkflow → scheduleWaitPollWithRetry
9
- * → driver.wait() → finalizeOnce → driver.cleanup()
10
- *
11
- * The base class handles all shared workflow management.
12
- * Subclasses provide only:
13
- * - Their specific constants (WORKFLOW_SESSION_PREFIX, DEFAULT_TIMEOUT_MS, DEFAULT_TTL_MS)
14
- * - Surface-degrade checks (boot session skip, subagent availability)
15
- * - The createWorkflowRecord hook for type-specific metadata
16
- *
17
- * @module subagent-workflow/workflow-manager-base
18
- */
19
- /* global NodeJS */
20
- import type { PluginLogger } from '../../openclaw-sdk.js';
21
- import type {
22
- WorkflowManager,
23
- WorkflowHandle,
24
- SubagentWorkflowSpec,
25
- WorkflowMetadata,
26
- WorkflowDebugSummary,
27
- } from './types.js';
28
- import { RuntimeDirectDriver, type RunParams } from './runtime-direct-driver.js';
29
- import { WorkflowStore } from './workflow-store.js';
30
- import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
31
- import { computeDynamicTimeout, computeRetrySchedule, MAX_TIMEOUT_RETRIES } from './dynamic-timeout.js';
32
-
33
- // ── Constructor Options ────────────────────────────────────────────────────────
34
-
35
- export interface WorkflowManagerBaseOptions {
36
- workspaceDir: string;
37
- logger: PluginLogger;
38
- subagent: RuntimeDirectDriver['subagent'];
39
- /** Pass api.runtime.agent.session to enable heartbeat-safe cleanup (#188) */
40
- agentSession?: RuntimeDirectDriver['agentSession'];
41
- /** Workflow type identifier for logging and dynamic timeout lookups (e.g. 'empathy-observer') */
42
- workflowType: string;
43
- /** Session key prefix (e.g. 'agent:main:subagent:workflow-') */
44
- sessionPrefix: string;
45
- /** Default polling timeout in milliseconds */
46
- defaultTimeoutMs: number;
47
- /** Default TTL for orphan cleanup in milliseconds */
48
- defaultTtlMs: number;
49
- }
50
-
51
- // ── Base Class ─────────────────────────────────────────────────────────────────
52
-
53
- export abstract class WorkflowManagerBase implements WorkflowManager {
54
- // Protected: accessible to subclasses
55
- protected readonly store: WorkflowStore;
56
- protected readonly driver: RuntimeDirectDriver;
57
- protected readonly logger: PluginLogger;
58
- protected readonly workspaceDir: string;
59
- protected readonly workflowType: string;
60
-
61
- // Protected: shared state
62
- protected activeWorkflows = new Map<string, NodeJS.Timeout>();
63
- protected completedWorkflows = new Map<string, number>();
64
- protected workflowSpecs = new Map<string, SubagentWorkflowSpec<unknown>>();
65
-
66
- // Private: subclass-specific constants used by base class methods
67
- private readonly sessionPrefix: string;
68
- private readonly defaultTimeoutMs: number;
69
- private readonly defaultTtlMs: number;
70
-
71
- constructor(opts: WorkflowManagerBaseOptions) {
72
- this.workspaceDir = opts.workspaceDir;
73
- this.logger = opts.logger;
74
- this.store = new WorkflowStore({ workspaceDir: opts.workspaceDir });
75
- this.driver = new RuntimeDirectDriver({
76
- subagent: opts.subagent,
77
- logger: opts.logger,
78
- agentSession: opts.agentSession,
79
- });
80
- this.workflowType = opts.workflowType;
81
- this.sessionPrefix = opts.sessionPrefix;
82
- this.defaultTimeoutMs = opts.defaultTimeoutMs;
83
- this.defaultTtlMs = opts.defaultTtlMs;
84
- }
85
-
86
- // ── startWorkflow ─────────────────────────────────────────────────────────
87
-
88
- /**
89
- * Start a new workflow.
90
- *
91
- * Common flow:
92
- * 1. Validate surface checks (boot session, subagent availability)
93
- * 2. Generate workflowId + childSessionKey
94
- * 3. Build metadata (subclass provides via createWorkflowMetadata hook)
95
- * 4. Call driver.run()
96
- * 5. Create workflow record via createWorkflowRecord hook
97
- * 6. Register spec
98
- * 7. Schedule wait polling
99
- *
100
- * Subclasses override surfaceCheck or startWorkflow to add type-specific
101
- * surface-degrade checks before calling super.startWorkflow().
102
- */
103
- async startWorkflow<TResult>(
104
- spec: SubagentWorkflowSpec<TResult>,
105
- options: {
106
- parentSessionId: string;
107
- workspaceDir?: string;
108
- taskInput: unknown;
109
- metadata?: Record<string, unknown>;
110
- }
111
- ): Promise<WorkflowHandle> {
112
- const workflowId = this.generateWorkflowId();
113
- const childSessionKey = this.buildChildSessionKey(options.parentSessionId);
114
- const now = Date.now();
115
-
116
- // Surface degrade: skip boot sessions
117
- if (options.parentSessionId.startsWith('boot-')) {
118
- this.logger.info(`[PD:${this.workflowType}] Skipping workflow: boot session`);
119
- throw new Error(`${this.constructor.name}: cannot start workflow for boot session`);
120
- }
121
-
122
- // Surface degrade: check subagent runtime availability
123
- if (!isSubagentRuntimeAvailable(this.driver.getSubagent())) {
124
- this.logger.info(`[PD:${this.workflowType}] Skipping workflow: subagent runtime unavailable`);
125
- throw new Error(`${this.constructor.name}: subagent runtime unavailable`);
126
- }
127
-
128
- if (spec.transport !== 'runtime_direct') {
129
- throw new Error(`${this.constructor.name} only supports runtime_direct transport`);
130
- }
131
-
132
- const runParams = this.buildRunParams(spec, options, childSessionKey);
133
- const runResult = await this.driver.run(runParams);
134
-
135
- const metadata = this.createWorkflowMetadata(spec, options, now);
136
-
137
- await this.createWorkflowRecord(workflowId, childSessionKey, spec, options, now, metadata, runResult.runId);
138
-
139
- this.store.recordEvent(workflowId, 'spawned', null, 'active', 'subagent spawned', { runId: runResult.runId });
140
- this.workflowSpecs.set(workflowId, spec as SubagentWorkflowSpec<unknown>);
141
-
142
- this.scheduleWaitPollWithRetry(workflowId, runResult.runId);
143
-
144
- return {
145
- workflowId,
146
- childSessionKey,
147
- runId: runResult.runId,
148
- state: 'active',
149
- };
150
- }
151
-
152
- // ── Protected Hooks (subclasses override) ─────────────────────────────────
153
-
154
- /**
155
- * Create workflow metadata for store.createWorkflow().
156
- * Subclasses override to add type-specific fields.
157
- */
158
-
159
-
160
- protected createWorkflowMetadata<TResult>(
161
- spec: SubagentWorkflowSpec<TResult>,
162
- options: {
163
- parentSessionId: string;
164
- workspaceDir?: string;
165
- taskInput: unknown;
166
- metadata?: Record<string, unknown>;
167
- },
168
- now: number
169
- ): WorkflowMetadata {
170
- return {
171
- parentSessionId: options.parentSessionId,
172
- workspaceDir: options.workspaceDir,
173
- taskInput: options.taskInput,
174
- startedAt: now,
175
- workflowType: spec.workflowType,
176
- ...options.metadata,
177
- };
178
- }
179
-
180
- /**
181
- * Create a workflow record in the store.
182
- * Called after driver.run() succeeds.
183
- * Subclasses override to call store.createWorkflow() with type-specific metadata.
184
- */
185
-
186
-
187
- protected async createWorkflowRecord<TResult>(
188
- workflowId: string,
189
- childSessionKey: string,
190
- spec: SubagentWorkflowSpec<TResult>,
191
- options: {
192
- parentSessionId: string;
193
- workspaceDir?: string;
194
- taskInput: unknown;
195
- metadata?: Record<string, unknown>;
196
- },
197
- now: number,
198
- metadata: WorkflowMetadata,
199
- runId: string
200
- ): Promise<void> {
201
- this.store.createWorkflow({
202
- workflow_id: workflowId,
203
- workflow_type: spec.workflowType,
204
- transport: spec.transport,
205
- parent_session_id: options.parentSessionId,
206
- child_session_key: childSessionKey,
207
- run_id: runId,
208
- state: 'active',
209
- created_at: now,
210
- updated_at: now,
211
- duration_ms: null,
212
- metadata_json: JSON.stringify(metadata),
213
- });
214
- }
215
-
216
- // ── Protected Helpers ────────────────────────────────────────────────────
217
-
218
-
219
-
220
- protected buildRunParams<TResult>(
221
- spec: SubagentWorkflowSpec<TResult>,
222
- options: {
223
- parentSessionId: string;
224
- workspaceDir?: string;
225
- taskInput: unknown;
226
- metadata?: Record<string, unknown>;
227
- },
228
- childSessionKey: string
229
- ): RunParams {
230
- const message = spec.buildPrompt(options.taskInput, {
231
- parentSessionId: options.parentSessionId,
232
- workspaceDir: options.workspaceDir,
233
- taskInput: options.taskInput,
234
- startedAt: Date.now(),
235
- workflowType: spec.workflowType,
236
- ...(options.metadata ?? {}),
237
- });
238
-
239
- return {
240
- sessionKey: childSessionKey,
241
- message,
242
- lane: 'subagent',
243
- deliver: false,
244
- idempotencyKey: options.parentSessionId
245
- ? `${options.parentSessionId}:${Date.now()}`
246
- : `pd:${childSessionKey}:${Date.now()}`,
247
- expectsCompletionMessage: true,
248
- };
249
- }
250
-
251
- /**
252
- * Schedule wait polling with dynamic timeout and automatic retry.
253
- *
254
- * Learns from historical completion times for this workflow type.
255
- * On timeout, retries with exponential backoff (1x → 2x → 4x base timeout).
256
- */
257
- protected scheduleWaitPollWithRetry(
258
- workflowId: string,
259
- runId: string,
260
- attempt = 0,
261
- ): void {
262
- const spec = this.workflowSpecs.get(workflowId);
263
- const staticTimeout = spec?.timeoutMs ?? this.defaultTimeoutMs;
264
-
265
- // Compute dynamic timeout from historical data using this.workflowType
266
- const baseTimeout = computeDynamicTimeout(this.store, this.workflowType, staticTimeout);
267
- const schedule = computeRetrySchedule(baseTimeout, MAX_TIMEOUT_RETRIES);
268
- const timeoutMs = schedule[Math.min(attempt, schedule.length - 1)];
269
- const historyCount = this.store.getCompletionDurations(this.workflowType, 50).length;
270
-
271
- this.logger.info(`[PD:${this.workflowType}] Wait attempt ${attempt + 1}/${schedule.length}: timeout=${timeoutMs}ms (base=${baseTimeout}ms, static=${staticTimeout}ms, samples=${historyCount}) for ${workflowId}`);
272
-
273
- const timeout = setTimeout(async () => {
274
- try {
275
- const result = await this.driver.wait({ runId, timeoutMs });
276
- if (result.status === 'timeout' && attempt < MAX_TIMEOUT_RETRIES) {
277
- this.logger.info(`[PD:${this.workflowType}] Timeout on attempt ${attempt + 1}, retrying for ${workflowId}`);
278
- this.store.recordEvent(workflowId, 'wait_timeout_retry', 'active', 'active', `timeout on attempt ${attempt + 1}, scheduling retry ${attempt + 2}`, { attempt });
279
- this.activeWorkflows.delete(workflowId);
280
- this.scheduleWaitPollWithRetry(workflowId, runId, attempt + 1);
281
- return;
282
- }
283
- if (result.status === 'ok' && attempt > 0) {
284
- this.logger.info(`[PD:${this.workflowType}] Retry succeeded on attempt ${attempt + 1} after timeout for ${workflowId}`);
285
- }
286
- await this.notifyWaitResult(workflowId, result.status, result.error);
287
- } catch (error) {
288
- const errMsg = String(error);
289
- // Don't retry on database errors — the connection was closed
290
- // (typically by lifecycle notification dispose). The subagent
291
- // may have completed successfully.
292
- if (errMsg.includes('not open') || errMsg.includes('database')) {
293
- this.logger.warn(`[PD:${this.workflowType}] Database error during wait poll for ${workflowId}: ${errMsg} — not retrying`);
294
- return;
295
- }
296
- this.logger.error(`[PD:${this.workflowType}] Wait poll failed: ${errMsg}`);
297
- if (attempt < MAX_TIMEOUT_RETRIES) {
298
- this.logger.info(`[PD:${this.workflowType}] Error on attempt ${attempt + 1}, retrying for ${workflowId}`);
299
- this.activeWorkflows.delete(workflowId);
300
- this.scheduleWaitPollWithRetry(workflowId, runId, attempt + 1);
301
- return;
302
- }
303
- await this.notifyWaitResult(workflowId, 'error', errMsg);
304
- }
305
- }, 100);
306
- timeout.unref(); // Don't keep process alive for wait poll
307
-
308
- this.activeWorkflows.set(workflowId, timeout);
309
- }
310
-
311
- // ── WorkflowManager Interface ─────────────────────────────────────────────
312
-
313
- async notifyWaitResult(
314
- workflowId: string,
315
- status: 'ok' | 'error' | 'timeout',
316
- error?: string
317
- ): Promise<void> {
318
-
319
-
320
- let workflow;
321
- try {
322
- workflow = this.store.getWorkflow(workflowId);
323
- /* eslint-disable @typescript-eslint/no-unused-vars -- Reason: Error is handled via early returns based on status, not error value */
324
- } catch (_dbError) {
325
- // Database connection closed (e.g., by lifecycle notification dispose).
326
- // If subagent succeeded, this is a known race condition — the workflow
327
- // will be handled by the original manager's finalizeOnce path.
328
- if (status === 'ok') {
329
- this.logger.info(`[PD:${this.workflowType}] notifyWaitResult: database unavailable for ${workflowId}, status=ok — skipping (original manager will finalize)`);
330
- return;
331
- }
332
- this.logger.warn(`[PD:${this.workflowType}] notifyWaitResult: database unavailable for ${workflowId}, status=${status}`);
333
- return;
334
- }
335
- if (!workflow) {
336
- this.logger.warn(`[PD:${this.workflowType}] notifyWaitResult: workflow not found: ${workflowId}`);
337
- return;
338
- }
339
-
340
- if (workflow.state === 'completed' || workflow.state === 'terminal_error' || workflow.state === 'expired') {
341
- this.logger.info(`[PD:${this.workflowType}] notifyWaitResult: ignoring terminal workflow: ${workflowId}, state=${workflow.state}`);
342
- return;
343
- }
344
-
345
- this.logger.info(`[PD:${this.workflowType}] notifyWaitResult: workflowId=${workflowId}, status=${status}`);
346
-
347
- const previousState = workflow.state;
348
- this.store.updateWorkflowState(workflowId, 'wait_result');
349
- this.store.recordEvent(workflowId, 'wait_result', previousState, 'wait_result', `wait completed: ${status}`, { error });
350
-
351
- const spec = this.workflowSpecs.get(workflowId);
352
- if (!spec) {
353
- // Spec not registered — lifecycle event notification path (subagent.ts).
354
- // Original manager's wait poll will handle finalization.
355
- this.logger.info(`[PD:${this.workflowType}] notifyWaitResult: spec not registered for ${workflowId} — skipping finalization (primary path will handle)`);
356
- return;
357
- }
358
-
359
- const shouldFinalize = spec.shouldFinalizeOnWaitStatus(status);
360
-
361
- if (shouldFinalize) {
362
- await this.finalizeOnce(workflowId);
363
- } else {
364
- this.store.updateWorkflowState(workflowId, 'terminal_error');
365
- this.store.recordEvent(workflowId, 'finalize_skipped', 'wait_result', 'terminal_error', `wait status: ${status}`, { error });
366
- }
367
- }
368
-
369
- async notifyLifecycleEvent(
370
- workflowId: string,
371
- event: 'subagent_spawned' | 'subagent_ended',
372
- data?: { outcome?: 'ok' | 'error' | 'timeout' | 'killed' | 'reset' | 'deleted'; error?: string }
373
- ): Promise<void> {
374
- this.logger.info(`[PD:${this.workflowType}] notifyLifecycleEvent: workflowId=${workflowId}, event=${event}`);
375
-
376
- if (event === 'subagent_ended' && data?.outcome) {
377
- await this.notifyWaitResult(workflowId, data.outcome === 'ok' ? 'ok' : data.outcome === 'error' ? 'error' : 'timeout', data.error);
378
- }
379
- }
380
-
381
- async finalizeOnce(workflowId: string): Promise<void> {
382
- const workflow = this.store.getWorkflow(workflowId);
383
- if (!workflow) {
384
- this.logger.warn(`[PD:${this.workflowType}] finalizeOnce: workflow not found: ${workflowId}`);
385
- return;
386
- }
387
-
388
- const spec = this.workflowSpecs.get(workflowId);
389
- if (!spec) {
390
- throw new Error(`Workflow spec not registered for ${workflowId}`);
391
- }
392
-
393
- if (this.isCompleted(workflowId)) {
394
- this.logger.info(`[PD:${this.workflowType}] finalizeOnce: already completed: ${workflowId}`);
395
- return;
396
- }
397
-
398
- this.logger.info(`[PD:${this.workflowType}] Finalizing workflow: ${workflowId}`);
399
-
400
- this.store.updateWorkflowState(workflowId, 'finalizing');
401
-
402
- try {
403
- const result = await this.driver.getResult({ sessionKey: workflow.child_session_key, limit: 20 });
404
-
405
- const metadata = JSON.parse(workflow.metadata_json) as WorkflowMetadata;
406
- const parsed = await spec.parseResult({
407
- messages: result.messages,
408
- assistantTexts: result.assistantTexts,
409
- metadata,
410
- waitStatus: 'ok',
411
- });
412
-
413
- if (!parsed) {
414
- this.store.updateWorkflowState(workflowId, 'terminal_error');
415
- this.store.recordEvent(workflowId, 'parse_failed', 'finalizing', 'terminal_error', 'spec.parseResult returned null', {});
416
- return;
417
- }
418
-
419
- await spec.persistResult({
420
- result: parsed,
421
- metadata,
422
- workspaceDir: this.workspaceDir,
423
- });
424
- this.store.recordEvent(workflowId, 'persisted', 'finalizing', 'finalizing', 'result persisted', {});
425
-
426
- if (spec.shouldDeleteSessionAfterFinalize && workflow.run_id) {
427
- try {
428
- await this.driver.cleanup({ sessionKey: workflow.child_session_key });
429
- this.store.updateCleanupState(workflowId, 'completed');
430
- } catch (cleanupError) {
431
- this.logger.error(`[PD:${this.workflowType}] cleanup failed after persistence: ${String(cleanupError)}`);
432
- this.store.updateCleanupState(workflowId, 'failed');
433
- this.store.updateWorkflowState(workflowId, 'cleanup_pending');
434
- this.store.recordEvent(workflowId, 'cleanup_failed', 'finalizing', 'cleanup_pending', String(cleanupError), {});
435
- this.markCompleted(workflowId);
436
- return;
437
- }
438
- }
439
-
440
- // Record actual completion duration for adaptive timeout learning
441
- const durationMs = Date.now() - workflow.created_at;
442
- this.store.recordDuration(workflowId, durationMs);
443
- this.logger.info(`[PD:${this.workflowType}] Duration recorded: workflowId=${workflowId}, durationMs=${durationMs}ms (${(durationMs / 1000).toFixed(1)}s), type=${spec.workflowType}`);
444
-
445
- this.store.updateWorkflowState(workflowId, 'completed');
446
- this.store.recordEvent(workflowId, 'finalized', 'finalizing', 'completed', 'success', { durationMs });
447
- this.markCompleted(workflowId);
448
-
449
- } catch (error) {
450
- this.logger.error(`[PD:${this.workflowType}] finalizeOnce failed: ${String(error)}`);
451
- this.store.updateWorkflowState(workflowId, 'terminal_error');
452
- this.store.recordEvent(workflowId, 'finalize_error', 'finalizing', 'terminal_error', String(error), {});
453
- throw error;
454
- }
455
- }
456
-
457
-
458
- async sweepExpiredWorkflows(maxAgeMs?: number): Promise<number> {
459
- const ttl = maxAgeMs ?? this.defaultTtlMs;
460
- const expired = this.store.getExpiredWorkflows(ttl);
461
-
462
- this.logger.info(`[PD:${this.workflowType}] sweepExpiredWorkflows: found ${expired.length} expired`);
463
-
464
- for (const workflow of expired) {
465
- try {
466
- this.logger.info(`[PD:${this.workflowType}] Sweeping expired workflow: ${workflow.workflow_id}`);
467
-
468
- await this.driver.cleanup({ sessionKey: workflow.child_session_key });
469
- this.store.updateCleanupState(workflow.workflow_id, 'completed');
470
- this.store.updateWorkflowState(workflow.workflow_id, 'expired');
471
- this.store.recordEvent(workflow.workflow_id, 'swept', workflow.state, 'expired', 'TTL expired', {});
472
-
473
- } catch (error) {
474
- this.logger.error(`[PD:${this.workflowType}] Sweep cleanup failed for ${workflow.workflow_id}: ${String(error)}`);
475
- this.store.updateCleanupState(workflow.workflow_id, 'failed');
476
- }
477
- }
478
-
479
- // Clean up memory Maps to prevent leaks
480
- const cutoff = Date.now() - 60_000;
481
- for (const [id, timestamp] of this.completedWorkflows) {
482
- if (timestamp < cutoff) {
483
- this.completedWorkflows.delete(id);
484
- }
485
- }
486
- for (const [id, timeout] of this.activeWorkflows) {
487
- const wf = this.store.getWorkflow(id);
488
- if (!wf || wf.state === 'expired' || wf.state === 'completed' || wf.state === 'terminal_error') {
489
- clearTimeout(timeout);
490
- this.activeWorkflows.delete(id);
491
- }
492
- }
493
-
494
- return expired.length;
495
- }
496
-
497
- async getWorkflowDebugSummary(workflowId: string, eventLimit = 10): Promise<WorkflowDebugSummary | null> {
498
- const workflow = this.store.getWorkflow(workflowId);
499
- if (!workflow) return null;
500
-
501
- const metadata = JSON.parse(workflow.metadata_json) as WorkflowMetadata;
502
- const recentEvents = this.store
503
- .getEvents(workflowId)
504
- .slice(-eventLimit)
505
- .map((event) => ({
506
- eventType: event.event_type,
507
- fromState: event.from_state,
508
- toState: event.to_state,
509
- reason: event.reason,
510
- createdAt: event.created_at,
511
- payload: JSON.parse(event.payload_json || '{}') as Record<string, unknown>,
512
- }));
513
-
514
- return {
515
- workflowId: workflow.workflow_id,
516
- workflowType: workflow.workflow_type,
517
- transport: workflow.transport,
518
- parentSessionId: workflow.parent_session_id,
519
- childSessionKey: workflow.child_session_key,
520
- runId: workflow.run_id,
521
- state: workflow.state,
522
- cleanupState: workflow.cleanup_state,
523
- lastObservedAt: workflow.last_observed_at ?? null,
524
- metadata,
525
- recentEvents,
526
- };
527
- }
528
-
529
- // ── Private Helpers ───────────────────────────────────────────────────────
530
-
531
-
532
- protected generateWorkflowId(): string {
533
- // Subclasses override the prefix part via wf_ prefix pattern
534
- return `wf_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
535
- }
536
-
537
- protected buildChildSessionKey(parentSessionId: string): string {
538
- const safeParentSessionId = parentSessionId
539
- .replace(/[^a-zA-Z0-9_-]/g, '_')
540
- .substring(0, 64);
541
- const timestamp = Date.now();
542
- return `${this.sessionPrefix}${safeParentSessionId}-${timestamp}`;
543
- }
544
-
545
- protected isCompleted(workflowId: string): boolean {
546
- const timestamp = this.completedWorkflows.get(workflowId);
547
- if (!timestamp) return false;
548
- return Date.now() - timestamp < 60_000; // 1 minute dedup window
549
- }
550
-
551
- /**
552
- * Get the current state of a workflow by ID.
553
- * Returns null if the workflow doesn't exist.
554
- */
555
- getWorkflowState(workflowId: string): string | null {
556
- const workflow = this.store.getWorkflow(workflowId);
557
- return workflow?.state ?? null;
558
- }
559
-
560
- protected markCompleted(workflowId: string): void {
561
- const timeout = this.activeWorkflows.get(workflowId);
562
- if (timeout) {
563
- clearTimeout(timeout);
564
- this.activeWorkflows.delete(workflowId);
565
- }
566
- this.completedWorkflows.set(workflowId, Date.now());
567
- this.workflowSpecs.delete(workflowId);
568
- }
569
-
570
- dispose(): void {
571
- for (const [workflowId, timeout] of this.activeWorkflows) {
572
- clearTimeout(timeout);
573
- this.logger.info(`[PD:${this.workflowType}] Disposed active workflow: ${workflowId}`);
574
- }
575
- this.activeWorkflows.clear();
576
- this.completedWorkflows.clear();
577
- this.workflowSpecs.clear();
578
- this.store.dispose();
579
- }
580
- }