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,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
- }