@desplega.ai/agent-swarm 1.20.0 → 1.51.2

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 (561) hide show
  1. package/README.md +271 -169
  2. package/openapi.json +5015 -0
  3. package/package.json +40 -7
  4. package/plugin/commands/close-issue.md +7 -3
  5. package/plugin/commands/create-pr.md +18 -12
  6. package/plugin/commands/implement-issue.md +7 -3
  7. package/plugin/commands/respond-github.md +8 -4
  8. package/plugin/commands/review-pr.md +44 -10
  9. package/plugin/commands/start-leader.md +1 -3
  10. package/plugin/commands/start-worker.md +1 -3
  11. package/plugin/commands/work-on-task.md +22 -3
  12. package/plugin/pi-skills/close-issue/SKILL.md +90 -0
  13. package/plugin/pi-skills/create-pr/SKILL.md +99 -0
  14. package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
  15. package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
  16. package/plugin/pi-skills/respond-github/SKILL.md +98 -0
  17. package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
  18. package/plugin/pi-skills/review-pr/SKILL.md +261 -0
  19. package/plugin/pi-skills/start-leader/SKILL.md +121 -0
  20. package/plugin/pi-skills/start-worker/SKILL.md +60 -0
  21. package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
  22. package/plugin/pi-skills/todos/SKILL.md +66 -0
  23. package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
  24. package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
  25. package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
  26. package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
  27. package/plugin/skills/artifacts/examples/static-report.sh +17 -0
  28. package/plugin/skills/artifacts/skill.md +71 -0
  29. package/src/agentmail/app.ts +65 -0
  30. package/src/agentmail/handlers.ts +262 -0
  31. package/src/agentmail/index.ts +9 -0
  32. package/src/agentmail/templates.ts +111 -0
  33. package/src/agentmail/types.ts +51 -0
  34. package/src/artifact-sdk/browser-sdk.ts +30 -0
  35. package/src/artifact-sdk/index.ts +2 -0
  36. package/src/artifact-sdk/localtunnel.d.ts +20 -0
  37. package/src/artifact-sdk/port.ts +12 -0
  38. package/src/artifact-sdk/server.ts +156 -0
  39. package/src/artifact-sdk/tunnel.ts +19 -0
  40. package/src/be/chunking.ts +193 -0
  41. package/src/be/db-queries/oauth.ts +90 -0
  42. package/src/be/db-queries/tracker.ts +182 -0
  43. package/src/be/db.ts +3327 -784
  44. package/src/be/embedding.ts +80 -0
  45. package/src/be/migrations/001_initial.sql +409 -0
  46. package/src/be/migrations/002_one_time_schedules.sql +59 -0
  47. package/src/be/migrations/003_workflows.sql +51 -0
  48. package/src/be/migrations/004_workflow_source.sql +81 -0
  49. package/src/be/migrations/005_epic_next_steps.sql +2 -0
  50. package/src/be/migrations/006_vcs_provider.sql +94 -0
  51. package/src/be/migrations/007_task_dir.sql +2 -0
  52. package/src/be/migrations/008_workflow_redesign.sql +85 -0
  53. package/src/be/migrations/009_tracker_integration.sql +144 -0
  54. package/src/be/migrations/010_step_diagnostics.sql +1 -0
  55. package/src/be/migrations/011_step_next_port.sql +1 -0
  56. package/src/be/migrations/012_trigger_schema.sql +1 -0
  57. package/src/be/migrations/013_task_output_schema.sql +2 -0
  58. package/src/be/migrations/014_prompt_templates.sql +33 -0
  59. package/src/be/migrations/015_workflow_workspace.sql +3 -0
  60. package/src/be/migrations/016_active_session_runner_session.sql +4 -0
  61. package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
  62. package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
  63. package/src/be/migrations/runner.ts +188 -0
  64. package/src/be/seed.ts +62 -0
  65. package/src/cli.tsx +231 -299
  66. package/src/commands/artifact.ts +241 -0
  67. package/src/commands/onboard/compose-generator.ts +169 -0
  68. package/src/commands/onboard/env-generator.ts +79 -0
  69. package/src/commands/onboard/manifest.ts +37 -0
  70. package/src/commands/onboard/presets.ts +85 -0
  71. package/src/commands/onboard/service-names.ts +47 -0
  72. package/src/commands/onboard/steps/core-credentials.tsx +111 -0
  73. package/src/commands/onboard/steps/custom-templates.tsx +168 -0
  74. package/src/commands/onboard/steps/generate.tsx +154 -0
  75. package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
  76. package/src/commands/onboard/steps/harness.tsx +21 -0
  77. package/src/commands/onboard/steps/health-check.tsx +171 -0
  78. package/src/commands/onboard/steps/integration-github.tsx +105 -0
  79. package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
  80. package/src/commands/onboard/steps/integration-menu.tsx +58 -0
  81. package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
  82. package/src/commands/onboard/steps/integration-slack.tsx +165 -0
  83. package/src/commands/onboard/steps/post-connect.tsx +145 -0
  84. package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
  85. package/src/commands/onboard/steps/post-task.tsx +103 -0
  86. package/src/commands/onboard/steps/prereq-check.tsx +178 -0
  87. package/src/commands/onboard/steps/review.tsx +82 -0
  88. package/src/commands/onboard/steps/start.tsx +97 -0
  89. package/src/commands/onboard/templates.ts +34 -0
  90. package/src/commands/onboard/types.ts +259 -0
  91. package/src/commands/onboard.tsx +425 -0
  92. package/src/commands/runner.ts +1540 -630
  93. package/src/commands/setup.tsx +23 -38
  94. package/src/commands/shared/client-config.ts +41 -0
  95. package/src/commands/templates.ts +172 -0
  96. package/src/github/app.ts +8 -0
  97. package/src/github/handlers.ts +384 -151
  98. package/src/github/index.ts +1 -0
  99. package/src/github/mentions-aliases.test.ts +73 -0
  100. package/src/github/mentions.test.ts +3 -3
  101. package/src/github/mentions.ts +32 -6
  102. package/src/github/templates.ts +398 -0
  103. package/src/github/types.ts +1 -0
  104. package/src/gitlab/auth.ts +63 -0
  105. package/src/gitlab/handlers.ts +368 -0
  106. package/src/gitlab/index.ts +19 -0
  107. package/src/gitlab/reactions.ts +104 -0
  108. package/src/gitlab/templates.ts +140 -0
  109. package/src/gitlab/types.ts +130 -0
  110. package/src/heartbeat/heartbeat.ts +434 -0
  111. package/src/heartbeat/index.ts +1 -0
  112. package/src/heartbeat/templates.ts +30 -0
  113. package/src/hooks/hook.ts +555 -4
  114. package/src/hooks/tool-loop-detection.test.ts +158 -0
  115. package/src/hooks/tool-loop-detection.ts +167 -0
  116. package/src/http/active-sessions.ts +199 -0
  117. package/src/http/agents.ts +328 -0
  118. package/src/http/config.ts +191 -0
  119. package/src/http/core.ts +309 -0
  120. package/src/http/db-query.ts +91 -0
  121. package/src/http/ecosystem.ts +63 -0
  122. package/src/http/epics.ts +460 -0
  123. package/src/http/index.ts +216 -0
  124. package/src/http/mcp.ts +77 -0
  125. package/src/http/memory.ts +168 -0
  126. package/src/http/openapi.ts +109 -0
  127. package/src/http/poll.ts +299 -0
  128. package/src/http/prompt-templates.ts +412 -0
  129. package/src/http/repos.ts +195 -0
  130. package/src/http/route-def.ts +123 -0
  131. package/src/http/schedules.ts +426 -0
  132. package/src/http/session-data.ts +241 -0
  133. package/src/http/stats.ts +174 -0
  134. package/src/http/tasks.ts +468 -0
  135. package/src/http/trackers/index.ts +10 -0
  136. package/src/http/trackers/linear.ts +187 -0
  137. package/src/http/types.ts +12 -0
  138. package/src/http/utils.ts +87 -0
  139. package/src/http/webhooks.ts +432 -0
  140. package/src/http/workflows.ts +530 -0
  141. package/src/http.ts +1 -1890
  142. package/src/linear/README.md +65 -0
  143. package/src/linear/app.ts +48 -0
  144. package/src/linear/client.ts +18 -0
  145. package/src/linear/index.ts +1 -0
  146. package/src/linear/oauth.ts +35 -0
  147. package/src/linear/outbound.ts +212 -0
  148. package/src/linear/sync.ts +567 -0
  149. package/src/linear/templates.ts +47 -0
  150. package/src/linear/types.ts +7 -0
  151. package/src/linear/webhook.ts +104 -0
  152. package/src/oauth/README.md +66 -0
  153. package/src/oauth/index.ts +6 -0
  154. package/src/oauth/wrapper.ts +204 -0
  155. package/src/prompts/base-prompt.ts +150 -265
  156. package/src/prompts/defaults.ts +196 -0
  157. package/src/prompts/registry.ts +57 -0
  158. package/src/prompts/resolver.ts +296 -0
  159. package/src/prompts/session-templates.ts +604 -0
  160. package/src/providers/claude-adapter.ts +442 -0
  161. package/src/providers/index.ts +24 -0
  162. package/src/providers/pi-mono-adapter.ts +442 -0
  163. package/src/providers/pi-mono-extension.ts +624 -0
  164. package/src/providers/pi-mono-mcp-client.ts +124 -0
  165. package/src/providers/types.ts +75 -0
  166. package/src/scheduler/scheduler.test.ts +2 -0
  167. package/src/scheduler/scheduler.ts +231 -40
  168. package/src/server.ts +97 -6
  169. package/src/slack/HEURISTICS.md +105 -0
  170. package/src/slack/actions.ts +133 -0
  171. package/src/slack/app.ts +7 -0
  172. package/src/slack/assistant.ts +118 -0
  173. package/src/slack/blocks.ts +233 -0
  174. package/src/slack/channel-activity.ts +177 -0
  175. package/src/slack/commands.ts +31 -17
  176. package/src/slack/files.ts +1 -1
  177. package/src/slack/handlers.test.ts +114 -1
  178. package/src/slack/handlers.ts +230 -55
  179. package/src/slack/responses.ts +120 -67
  180. package/src/slack/router.ts +17 -99
  181. package/src/slack/templates.ts +55 -0
  182. package/src/slack/thread-buffer.ts +213 -0
  183. package/src/slack/watcher.ts +119 -4
  184. package/src/tests/agent-activity.test.ts +247 -0
  185. package/src/tests/agentmail-filters.test.ts +97 -0
  186. package/src/tests/artifact-sdk.test.ts +800 -0
  187. package/src/tests/base-prompt.test.ts +264 -0
  188. package/src/tests/build-pi-skills.test.ts +127 -0
  189. package/src/tests/channel-activity.test.ts +363 -0
  190. package/src/tests/claude-adapter.test.ts +126 -0
  191. package/src/tests/context-versioning.test.ts +425 -0
  192. package/src/tests/db-queries-oauth.test.ts +197 -0
  193. package/src/tests/db-queries-tracker.test.ts +230 -0
  194. package/src/tests/epics.test.ts +3 -3
  195. package/src/tests/error-tracker.test.ts +368 -0
  196. package/src/tests/fetch-resolved-env.test.ts +167 -0
  197. package/src/tests/generate-default-claude-md.test.ts +9 -1
  198. package/src/tests/generate-identity-templates.test.ts +124 -0
  199. package/src/tests/gitlab-auth.test.ts +109 -0
  200. package/src/tests/gitlab-handlers.test.ts +691 -0
  201. package/src/tests/gitlab-vcs-db.test.ts +177 -0
  202. package/src/tests/heartbeat.test.ts +364 -0
  203. package/src/tests/http-api-integration.test.ts +1698 -0
  204. package/src/tests/linear-outbound-sync.test.ts +200 -0
  205. package/src/tests/linear-webhook.test.ts +406 -0
  206. package/src/tests/match-route.test.ts +187 -0
  207. package/src/tests/memory.test.ts +737 -0
  208. package/src/tests/migration-runner-regressions.test.ts +86 -0
  209. package/src/tests/model-control.test.ts +338 -0
  210. package/src/tests/oauth-wrapper.test.ts +147 -0
  211. package/src/tests/onboard-compose.test.ts +138 -0
  212. package/src/tests/onboard-env.test.ts +174 -0
  213. package/src/tests/onboard-manifest.test.ts +137 -0
  214. package/src/tests/pi-mono-adapter.test.ts +234 -0
  215. package/src/tests/pool-session-logs.test.ts +199 -0
  216. package/src/tests/progress-dedup.test.ts +98 -0
  217. package/src/tests/prompt-template-github.test.ts +682 -0
  218. package/src/tests/prompt-template-remaining.test.ts +504 -0
  219. package/src/tests/prompt-template-resolver.test.ts +621 -0
  220. package/src/tests/prompt-template-session.test.ts +363 -0
  221. package/src/tests/prompt-templates-db.test.ts +616 -0
  222. package/src/tests/provider-adapter.test.ts +122 -0
  223. package/src/tests/provider-command-format.test.ts +98 -0
  224. package/src/tests/reload-config.test.ts +170 -0
  225. package/src/tests/runner-polling-api.test.ts +25 -20
  226. package/src/tests/scheduled-tasks.test.ts +104 -0
  227. package/src/tests/scheduler-backoff.test.ts +166 -0
  228. package/src/tests/self-improvement.test.ts +541 -0
  229. package/src/tests/session-attach.test.ts +536 -0
  230. package/src/tests/session-costs.test.ts +267 -1
  231. package/src/tests/slack-actions.test.ts +133 -0
  232. package/src/tests/slack-assistant.test.ts +136 -0
  233. package/src/tests/slack-blocks.test.ts +246 -0
  234. package/src/tests/slack-metadata-inheritance.test.ts +243 -0
  235. package/src/tests/slack-queue-offline.test.ts +174 -0
  236. package/src/tests/slack-router.test.ts +181 -0
  237. package/src/tests/slack-thread-buffer.test.ts +305 -0
  238. package/src/tests/slack-thread-followups.test.ts +298 -0
  239. package/src/tests/slack-watcher.test.ts +101 -0
  240. package/src/tests/structured-output.test.ts +307 -0
  241. package/src/tests/swarm-repos.test.ts +198 -0
  242. package/src/tests/task-cancellation.test.ts +6 -4
  243. package/src/tests/task-working-dir.test.ts +176 -0
  244. package/src/tests/template-fetch.test.ts +490 -0
  245. package/src/tests/tool-annotations.test.ts +371 -0
  246. package/src/tests/tracker-tools.test.ts +184 -0
  247. package/src/tests/update-profile-agentid.test.ts +248 -0
  248. package/src/tests/update-profile-api.test.ts +143 -3
  249. package/src/tests/update-profile-auth.test.ts +195 -0
  250. package/src/tests/validation-adapters.test.ts +86 -0
  251. package/src/tests/vcs-provider.test.ts +27 -0
  252. package/src/tests/workflow-agent-task.test.ts +196 -0
  253. package/src/tests/workflow-async-v2.test.ts +508 -0
  254. package/src/tests/workflow-convergence.test.ts +541 -0
  255. package/src/tests/workflow-definition-validation.test.ts +366 -0
  256. package/src/tests/workflow-engine-v2.test.ts +691 -0
  257. package/src/tests/workflow-executors.test.ts +736 -0
  258. package/src/tests/workflow-http-v2.test.ts +599 -0
  259. package/src/tests/workflow-integration-io.test.ts +902 -0
  260. package/src/tests/workflow-io-schemas.test.ts +624 -0
  261. package/src/tests/workflow-registry.test.ts +592 -0
  262. package/src/tests/workflow-retry-v2.test.ts +401 -0
  263. package/src/tests/workflow-retry-validation.test.ts +282 -0
  264. package/src/tests/workflow-schedule-trigger.test.ts +104 -0
  265. package/src/tests/workflow-template.test.ts +288 -0
  266. package/src/tests/workflow-trigger-schema.test.ts +359 -0
  267. package/src/tests/workflow-triggers-v2.test.ts +264 -0
  268. package/src/tests/workflow-versions.test.ts +208 -0
  269. package/src/tests/workflow-workspace.test.ts +272 -0
  270. package/src/tests/x402-client.test.ts +117 -0
  271. package/src/tests/x402-config.test.ts +182 -0
  272. package/src/tests/x402-spending-tracker.test.ts +185 -0
  273. package/src/tools/cancel-task.ts +2 -0
  274. package/src/tools/context-diff.ts +171 -0
  275. package/src/tools/context-history.ts +138 -0
  276. package/src/tools/create-channel.ts +1 -0
  277. package/src/tools/db-query.ts +78 -0
  278. package/src/tools/delete-channel.ts +132 -0
  279. package/src/tools/epics/assign-task-to-epic.ts +1 -0
  280. package/src/tools/epics/create-epic.ts +3 -2
  281. package/src/tools/epics/delete-epic.ts +2 -0
  282. package/src/tools/epics/get-epic-details.ts +2 -0
  283. package/src/tools/epics/list-epics.ts +2 -0
  284. package/src/tools/epics/unassign-task-from-epic.ts +1 -0
  285. package/src/tools/epics/update-epic.ts +7 -4
  286. package/src/tools/get-swarm.ts +2 -0
  287. package/src/tools/get-task-details.ts +2 -0
  288. package/src/tools/get-tasks.ts +27 -1
  289. package/src/tools/inject-learning.ts +106 -0
  290. package/src/tools/join-swarm.ts +17 -7
  291. package/src/tools/list-channels.ts +2 -0
  292. package/src/tools/list-services.ts +2 -0
  293. package/src/tools/memory-get.ts +56 -0
  294. package/src/tools/memory-search.ts +131 -0
  295. package/src/tools/my-agent-info.ts +2 -0
  296. package/src/tools/poll-task.ts +2 -20
  297. package/src/tools/post-message.ts +1 -0
  298. package/src/tools/prompt-templates/delete.ts +86 -0
  299. package/src/tools/prompt-templates/get.ts +89 -0
  300. package/src/tools/prompt-templates/index.ts +5 -0
  301. package/src/tools/prompt-templates/list.ts +95 -0
  302. package/src/tools/prompt-templates/preview.ts +84 -0
  303. package/src/tools/prompt-templates/set.ts +117 -0
  304. package/src/tools/read-messages.ts +2 -0
  305. package/src/tools/register-agentmail-inbox.ts +166 -0
  306. package/src/tools/register-service.ts +2 -0
  307. package/src/tools/schedules/create-schedule.ts +134 -24
  308. package/src/tools/schedules/delete-schedule.ts +2 -0
  309. package/src/tools/schedules/list-schedules.ts +20 -4
  310. package/src/tools/schedules/run-schedule-now.ts +1 -0
  311. package/src/tools/schedules/update-schedule.ts +49 -17
  312. package/src/tools/send-task.ts +132 -10
  313. package/src/tools/slack-download-file.ts +4 -2
  314. package/src/tools/slack-list-channels.ts +2 -0
  315. package/src/tools/slack-post.ts +2 -0
  316. package/src/tools/slack-read.ts +2 -0
  317. package/src/tools/slack-reply.ts +2 -0
  318. package/src/tools/slack-upload-file.ts +2 -0
  319. package/src/tools/store-progress.ts +205 -4
  320. package/src/tools/swarm-config/delete-config.ts +87 -0
  321. package/src/tools/swarm-config/get-config.ts +108 -0
  322. package/src/tools/swarm-config/index.ts +4 -0
  323. package/src/tools/swarm-config/list-config.ts +99 -0
  324. package/src/tools/swarm-config/set-config.ts +118 -0
  325. package/src/tools/task-action.ts +50 -5
  326. package/src/tools/task-dedup.ts +97 -0
  327. package/src/tools/templates.ts +53 -0
  328. package/src/tools/tool-config.ts +124 -0
  329. package/src/tools/tracker/index.ts +6 -0
  330. package/src/tools/tracker/tracker-link-epic.ts +64 -0
  331. package/src/tools/tracker/tracker-link-task.ts +64 -0
  332. package/src/tools/tracker/tracker-map-agent.ts +57 -0
  333. package/src/tools/tracker/tracker-status.ts +56 -0
  334. package/src/tools/tracker/tracker-sync-status.ts +42 -0
  335. package/src/tools/tracker/tracker-unlink.ts +41 -0
  336. package/src/tools/unregister-service.ts +2 -0
  337. package/src/tools/update-profile.ts +172 -17
  338. package/src/tools/update-service-status.ts +2 -0
  339. package/src/tools/utils.ts +10 -1
  340. package/src/tools/workflows/create-workflow.ts +129 -0
  341. package/src/tools/workflows/delete-workflow.ts +42 -0
  342. package/src/tools/workflows/get-workflow-run.ts +59 -0
  343. package/src/tools/workflows/get-workflow.ts +53 -0
  344. package/src/tools/workflows/index.ts +9 -0
  345. package/src/tools/workflows/list-workflow-runs.ts +48 -0
  346. package/src/tools/workflows/list-workflows.ts +42 -0
  347. package/src/tools/workflows/retry-workflow-run.ts +40 -0
  348. package/src/tools/workflows/trigger-workflow.ts +96 -0
  349. package/src/tools/workflows/update-workflow.ts +133 -0
  350. package/src/tracker/types.ts +51 -0
  351. package/src/types.ts +530 -14
  352. package/src/utils/credentials.test.ts +156 -0
  353. package/src/utils/credentials.ts +50 -0
  354. package/src/utils/error-tracker.ts +190 -0
  355. package/src/vcs/index.ts +15 -0
  356. package/src/vcs/types.ts +5 -0
  357. package/src/workflows/checkpoint.ts +121 -0
  358. package/src/workflows/cooldown.ts +28 -0
  359. package/src/workflows/definition.ts +235 -0
  360. package/src/workflows/engine.ts +580 -0
  361. package/src/workflows/event-bus.ts +29 -0
  362. package/src/workflows/executors/agent-task.ts +103 -0
  363. package/src/workflows/executors/base.ts +86 -0
  364. package/src/workflows/executors/code-match.ts +88 -0
  365. package/src/workflows/executors/index.ts +16 -0
  366. package/src/workflows/executors/notify.ts +93 -0
  367. package/src/workflows/executors/property-match.ts +104 -0
  368. package/src/workflows/executors/raw-llm.ts +83 -0
  369. package/src/workflows/executors/registry.ts +76 -0
  370. package/src/workflows/executors/script.ts +103 -0
  371. package/src/workflows/executors/validate.ts +215 -0
  372. package/src/workflows/executors/vcs.ts +58 -0
  373. package/src/workflows/index.ts +61 -0
  374. package/src/workflows/input.ts +46 -0
  375. package/src/workflows/json-schema-validator.ts +118 -0
  376. package/src/workflows/recovery.ts +139 -0
  377. package/src/workflows/resume.ts +229 -0
  378. package/src/workflows/retry-poller.ts +216 -0
  379. package/src/workflows/template.ts +74 -0
  380. package/src/workflows/templates.ts +86 -0
  381. package/src/workflows/triggers.ts +124 -0
  382. package/src/workflows/validation.ts +104 -0
  383. package/src/workflows/version.ts +44 -0
  384. package/src/x402/cli.ts +140 -0
  385. package/src/x402/client.ts +192 -0
  386. package/src/x402/config.ts +131 -0
  387. package/src/x402/index.ts +37 -0
  388. package/src/x402/openfort-signer.ts +83 -0
  389. package/src/x402/spending-tracker.ts +109 -0
  390. package/templates/official/coder/CLAUDE.md +49 -0
  391. package/templates/official/coder/IDENTITY.md +28 -0
  392. package/templates/official/coder/SOUL.md +43 -0
  393. package/templates/official/coder/TOOLS.md +40 -0
  394. package/templates/official/coder/config.json +23 -0
  395. package/templates/official/coder/start-up.sh +23 -0
  396. package/templates/official/content-reviewer/CLAUDE.md +68 -0
  397. package/templates/official/content-reviewer/IDENTITY.md +28 -0
  398. package/templates/official/content-reviewer/SOUL.md +44 -0
  399. package/templates/official/content-reviewer/TOOLS.md +37 -0
  400. package/templates/official/content-reviewer/config.json +23 -0
  401. package/templates/official/content-reviewer/start-up.sh +23 -0
  402. package/templates/official/content-strategist/CLAUDE.md +63 -0
  403. package/templates/official/content-strategist/IDENTITY.md +33 -0
  404. package/templates/official/content-strategist/SOUL.md +48 -0
  405. package/templates/official/content-strategist/TOOLS.md +47 -0
  406. package/templates/official/content-strategist/config.json +23 -0
  407. package/templates/official/content-strategist/start-up.sh +23 -0
  408. package/templates/official/content-writer/CLAUDE.md +72 -0
  409. package/templates/official/content-writer/IDENTITY.md +30 -0
  410. package/templates/official/content-writer/SOUL.md +46 -0
  411. package/templates/official/content-writer/TOOLS.md +44 -0
  412. package/templates/official/content-writer/config.json +23 -0
  413. package/templates/official/content-writer/start-up.sh +23 -0
  414. package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
  415. package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
  416. package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
  417. package/templates/official/forward-deployed-engineer/config.json +21 -0
  418. package/templates/official/lead/CLAUDE.md +33 -0
  419. package/templates/official/lead/IDENTITY.md +36 -0
  420. package/templates/official/lead/SOUL.md +51 -0
  421. package/templates/official/lead/config.json +22 -0
  422. package/templates/official/researcher/CLAUDE.md +46 -0
  423. package/templates/official/researcher/IDENTITY.md +28 -0
  424. package/templates/official/researcher/SOUL.md +43 -0
  425. package/templates/official/researcher/config.json +21 -0
  426. package/templates/official/reviewer/CLAUDE.md +63 -0
  427. package/templates/official/reviewer/IDENTITY.md +28 -0
  428. package/templates/official/reviewer/SOUL.md +45 -0
  429. package/templates/official/reviewer/config.json +21 -0
  430. package/templates/official/tester/CLAUDE.md +53 -0
  431. package/templates/official/tester/IDENTITY.md +28 -0
  432. package/templates/official/tester/SOUL.md +55 -0
  433. package/templates/official/tester/config.json +21 -0
  434. package/templates/schema.ts +35 -0
  435. package/.claude/settings.local.json +0 -115
  436. package/.dockerignore +0 -61
  437. package/.editorconfig +0 -15
  438. package/.env.docker.example +0 -39
  439. package/.env.example +0 -40
  440. package/.github/workflows/ci.yml +0 -76
  441. package/.github/workflows/docker-and-deploy.yml +0 -117
  442. package/.wts-config.json +0 -4
  443. package/.wts-setup.ts +0 -102
  444. package/CLAUDE.md +0 -104
  445. package/CONTRIBUTING.md +0 -270
  446. package/DEPLOYMENT.md +0 -605
  447. package/Dockerfile +0 -57
  448. package/Dockerfile.worker +0 -157
  449. package/FAQ.md +0 -19
  450. package/MCP.md +0 -406
  451. package/UI.md +0 -40
  452. package/assets/agent-swarm-logo-orange.png +0 -0
  453. package/assets/agent-swarm-logo.png +0 -0
  454. package/assets/agent-swarm.mp4 +0 -0
  455. package/assets/agent-swarm.png +0 -0
  456. package/biome.json +0 -39
  457. package/deploy/DEPLOY.md +0 -60
  458. package/deploy/agent-swarm.service +0 -17
  459. package/deploy/docker-push.ts +0 -30
  460. package/deploy/install.ts +0 -85
  461. package/deploy/prod-db.ts +0 -42
  462. package/deploy/uninstall.ts +0 -12
  463. package/deploy/update.ts +0 -21
  464. package/docker-compose.example.yml +0 -159
  465. package/docker-entrypoint.sh +0 -352
  466. package/ecosystem.config.cjs +0 -66
  467. package/plugin/README.md +0 -1
  468. package/plugin/hooks/hooks.json +0 -71
  469. package/pyproject.toml +0 -9
  470. package/scripts/generate-mcp-docs.ts +0 -415
  471. package/slack-manifest.json +0 -71
  472. package/src/tests/get-inbox-message.test.ts +0 -145
  473. package/src/tools/get-inbox-message.ts +0 -89
  474. package/src/tools/inbox-delegate.ts +0 -113
  475. package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
  476. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
  477. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
  478. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
  479. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
  480. package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
  481. package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
  482. package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
  483. package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
  484. package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
  485. package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
  486. package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
  487. package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
  488. package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
  489. package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
  490. package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
  491. package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
  492. package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
  493. package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
  494. package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
  495. package/thoughts/shared/research/.gitkeep +0 -0
  496. package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
  497. package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
  498. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
  499. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
  500. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
  501. package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
  502. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
  503. package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
  504. package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
  505. package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
  506. package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
  507. package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
  508. package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
  509. package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
  510. package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
  511. package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
  512. package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
  513. package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
  514. package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
  515. package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
  516. package/tsconfig.json +0 -37
  517. package/ui/CLAUDE.md +0 -49
  518. package/ui/bun.lock +0 -771
  519. package/ui/index.html +0 -22
  520. package/ui/package-lock.json +0 -5290
  521. package/ui/package.json +0 -33
  522. package/ui/pnpm-lock.yaml +0 -3341
  523. package/ui/postcss.config.js +0 -6
  524. package/ui/public/logo.png +0 -0
  525. package/ui/src/App.tsx +0 -63
  526. package/ui/src/components/ActivityFeed.tsx +0 -440
  527. package/ui/src/components/AgentDetailPanel.tsx +0 -733
  528. package/ui/src/components/AgentsPanel.tsx +0 -815
  529. package/ui/src/components/ChatPanel.tsx +0 -1920
  530. package/ui/src/components/ConfigModal.tsx +0 -253
  531. package/ui/src/components/Dashboard.tsx +0 -832
  532. package/ui/src/components/EditAgentProfileModal.tsx +0 -433
  533. package/ui/src/components/EpicDetailPage.tsx +0 -741
  534. package/ui/src/components/EpicsPanel.tsx +0 -566
  535. package/ui/src/components/Header.tsx +0 -160
  536. package/ui/src/components/JsonViewer.tsx +0 -171
  537. package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
  538. package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
  539. package/ui/src/components/ServicesPanel.tsx +0 -622
  540. package/ui/src/components/SessionLogPanel.tsx +0 -1219
  541. package/ui/src/components/StatsBar.tsx +0 -321
  542. package/ui/src/components/StatusBadge.tsx +0 -168
  543. package/ui/src/components/TaskDetailPanel.tsx +0 -903
  544. package/ui/src/components/TasksPanel.tsx +0 -614
  545. package/ui/src/components/UsageCharts.tsx +0 -216
  546. package/ui/src/components/UsageTab.tsx +0 -394
  547. package/ui/src/hooks/queries.ts +0 -353
  548. package/ui/src/hooks/useAutoScroll.ts +0 -83
  549. package/ui/src/index.css +0 -257
  550. package/ui/src/lib/api.ts +0 -268
  551. package/ui/src/lib/config.ts +0 -35
  552. package/ui/src/lib/contentPreview.ts +0 -208
  553. package/ui/src/lib/theme.ts +0 -214
  554. package/ui/src/lib/utils.ts +0 -88
  555. package/ui/src/main.tsx +0 -28
  556. package/ui/src/types/api.ts +0 -323
  557. package/ui/src/vite-env.d.ts +0 -1
  558. package/ui/tailwind.config.js +0 -37
  559. package/ui/tsconfig.json +0 -31
  560. package/ui/vite.config.ts +0 -35
  561. /package/{thoughts/shared/plans → templates/community}/.gitkeep +0 -0
@@ -0,0 +1,139 @@
1
+ import {
2
+ getCompletedStepNodeIds,
3
+ getDb,
4
+ getStuckWorkflowRuns,
5
+ getWorkflow,
6
+ getWorkflowRun,
7
+ updateWorkflowRun,
8
+ updateWorkflowRunStep,
9
+ } from "../be/db";
10
+ import { checkpointStep } from "./checkpoint";
11
+ import { getSuccessors } from "./definition";
12
+ import { findReadyNodes, walkGraph } from "./engine";
13
+ import type { ExecutorRegistry } from "./executors/registry";
14
+
15
+ /**
16
+ * Recover incomplete workflow runs on server startup.
17
+ *
18
+ * Two cases:
19
+ * 1. `running` runs — were mid-execution when server died.
20
+ * Find completed steps, compute ready nodes, continue walking.
21
+ * 2. `waiting` runs — were waiting for a task that may have finished while we were down.
22
+ * Check if the linked task is done and resume/fail accordingly.
23
+ */
24
+ export async function recoverIncompleteRuns(registry: ExecutorRegistry): Promise<number> {
25
+ let recovered = 0;
26
+
27
+ // --- Case 1: Running runs that were interrupted mid-execution ---
28
+ recovered += await recoverRunningRuns(registry);
29
+
30
+ // --- Case 2: Waiting runs whose tasks may have finished ---
31
+ recovered += await recoverWaitingRuns(registry);
32
+
33
+ if (recovered > 0) {
34
+ console.log(`[workflows] Recovered ${recovered} incomplete run(s) on startup`);
35
+ }
36
+
37
+ return recovered;
38
+ }
39
+
40
+ /**
41
+ * Resume runs that were in "running" state when the server stopped.
42
+ * Uses checkpointed step data to find where to continue.
43
+ */
44
+ async function recoverRunningRuns(registry: ExecutorRegistry): Promise<number> {
45
+ // Query for all running runs by scanning steps
46
+ // We need to find runs where status = 'running' and figure out which nodes to resume
47
+ const runningRunIds = getRunIdsByStatus("running");
48
+ let recovered = 0;
49
+
50
+ for (const runId of runningRunIds) {
51
+ try {
52
+ const run = getWorkflowRun(runId);
53
+ if (!run || run.status !== "running") continue;
54
+
55
+ const workflow = getWorkflow(run.workflowId);
56
+ if (!workflow) continue;
57
+
58
+ const completedNodeIds = new Set(getCompletedStepNodeIds(runId));
59
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
60
+
61
+ // Find the next nodes that are ready to execute
62
+ const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
63
+ if (readyNodes.length === 0) {
64
+ // All nodes completed or nothing is ready — mark as completed
65
+ updateWorkflowRun(runId, {
66
+ status: "completed",
67
+ context: ctx,
68
+ finishedAt: new Date().toISOString(),
69
+ });
70
+ } else {
71
+ await walkGraph(workflow.definition, runId, ctx, readyNodes, registry, workflow.id);
72
+ }
73
+ recovered++;
74
+ } catch (err) {
75
+ console.error(`[workflows] Failed to recover running run ${runId}:`, err);
76
+ }
77
+ }
78
+
79
+ return recovered;
80
+ }
81
+
82
+ /**
83
+ * Check waiting runs whose linked tasks may have completed/failed/cancelled
84
+ * while the server was down.
85
+ */
86
+ async function recoverWaitingRuns(registry: ExecutorRegistry): Promise<number> {
87
+ const stuckRuns = getStuckWorkflowRuns();
88
+ let recovered = 0;
89
+
90
+ for (const stuck of stuckRuns) {
91
+ try {
92
+ const run = getWorkflowRun(stuck.runId);
93
+ const workflow = getWorkflow(stuck.workflowId);
94
+ if (!run || !workflow) continue;
95
+
96
+ if (stuck.taskStatus === "completed") {
97
+ // Task finished while we were down — checkpoint and resume
98
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
99
+ const stepOutput = { taskId: stuck.stepId, taskOutput: stuck.taskOutput };
100
+
101
+ checkpointStep(stuck.runId, stuck.stepId, stuck.nodeId, { output: stepOutput }, ctx);
102
+ updateWorkflowRun(stuck.runId, { status: "running" });
103
+
104
+ const successors = getSuccessors(workflow.definition, stuck.nodeId, "default");
105
+ await walkGraph(workflow.definition, stuck.runId, ctx, successors, registry, workflow.id);
106
+ } else {
107
+ // Task failed or cancelled — mark run failed
108
+ const reason =
109
+ stuck.taskStatus === "failed" ? "Task failed (recovered)" : "Task cancelled (recovered)";
110
+ const now = new Date().toISOString();
111
+ updateWorkflowRunStep(stuck.stepId, {
112
+ status: "failed",
113
+ error: reason,
114
+ finishedAt: now,
115
+ });
116
+ updateWorkflowRun(stuck.runId, {
117
+ status: "failed",
118
+ error: reason,
119
+ finishedAt: now,
120
+ });
121
+ }
122
+ recovered++;
123
+ } catch (err) {
124
+ console.error(`[workflows] Failed to recover waiting run ${stuck.runId}:`, err);
125
+ }
126
+ }
127
+
128
+ return recovered;
129
+ }
130
+
131
+ /**
132
+ * Get run IDs by status. Simple query since there's no dedicated function for this.
133
+ */
134
+ function getRunIdsByStatus(status: string): string[] {
135
+ const rows = getDb()
136
+ .prepare<{ id: string }, [string]>("SELECT id FROM workflow_runs WHERE status = ?")
137
+ .all(status);
138
+ return rows.map((r) => r.id);
139
+ }
@@ -0,0 +1,229 @@
1
+ import {
2
+ getCompletedStepNodeIds,
3
+ getWorkflow,
4
+ getWorkflowRun,
5
+ getWorkflowRunStep,
6
+ getWorkflowRunStepsByRunId,
7
+ updateWorkflowRun,
8
+ updateWorkflowRunStep,
9
+ } from "../be/db";
10
+ import { checkpointStep } from "./checkpoint";
11
+ import { findReadyNodes, walkGraph } from "./engine";
12
+ import type { WorkflowEventBus } from "./event-bus";
13
+ import type { ExecutorRegistry } from "./executors/registry";
14
+
15
+ interface TaskEvent {
16
+ taskId: string;
17
+ output?: string;
18
+ agentId?: string;
19
+ workflowRunId?: string;
20
+ workflowRunStepId?: string;
21
+ failureReason?: string;
22
+ }
23
+
24
+ /**
25
+ * Wire up event bus listeners for workflow resume on task lifecycle events.
26
+ */
27
+ export function setupWorkflowResumeListener(
28
+ eventBus: WorkflowEventBus,
29
+ registry: ExecutorRegistry,
30
+ ): void {
31
+ eventBus.on("task.completed", async (data: unknown) => {
32
+ const event = data as TaskEvent;
33
+ if (!event.workflowRunId || !event.workflowRunStepId) return;
34
+ try {
35
+ await resumeFromTaskCompletion(event, registry);
36
+ } catch (err) {
37
+ console.error("[workflows] Resume from task completion failed:", err);
38
+ }
39
+ });
40
+
41
+ eventBus.on("task.failed", async (data: unknown) => {
42
+ const event = data as TaskEvent;
43
+ if (!event.workflowRunId || !event.workflowRunStepId) return;
44
+ try {
45
+ await handleTaskFailure(event, event.failureReason ?? "Task failed", registry);
46
+ } catch (err) {
47
+ console.error("[workflows] Handle task failure error:", err);
48
+ }
49
+ });
50
+
51
+ eventBus.on("task.cancelled", async (data: unknown) => {
52
+ const event = data as TaskEvent;
53
+ if (!event.workflowRunId || !event.workflowRunStepId) return;
54
+ try {
55
+ await handleTaskFailure(event, "Task was cancelled", registry);
56
+ } catch (err) {
57
+ console.error("[workflows] Handle task cancellation error:", err);
58
+ }
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Resume a workflow after a linked task completes.
64
+ *
65
+ * 1. Verify run and step are in "waiting" state
66
+ * 2. Checkpoint step completion with task output
67
+ * 3. Set run status to "running"
68
+ * 4. Find successors and continue the graph walk
69
+ */
70
+ async function resumeFromTaskCompletion(
71
+ event: TaskEvent,
72
+ registry: ExecutorRegistry,
73
+ ): Promise<void> {
74
+ const run = getWorkflowRun(event.workflowRunId!);
75
+ if (!run || (run.status !== "waiting" && run.status !== "running")) return;
76
+
77
+ const step = getWorkflowRunStep(event.workflowRunStepId!);
78
+ if (!step || step.status !== "waiting") return;
79
+
80
+ const workflow = getWorkflow(run.workflowId);
81
+ if (!workflow) return;
82
+
83
+ // Checkpoint: atomic step completion + context update
84
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
85
+
86
+ // JSON-parse structured output so downstream nodes can access nested fields
87
+ let taskOutput: unknown = event.output;
88
+ if (event.output) {
89
+ try {
90
+ const parsed = JSON.parse(event.output);
91
+ if (typeof parsed === "object" && parsed !== null) {
92
+ taskOutput = parsed;
93
+ }
94
+ } catch {
95
+ // Not JSON — keep as string (non-structured output tasks)
96
+ }
97
+ }
98
+ const stepOutput = { taskId: event.taskId, taskOutput };
99
+
100
+ checkpointStep(run.id, step.id, step.nodeId, { output: stepOutput }, ctx);
101
+
102
+ // Set run back to running
103
+ updateWorkflowRun(run.id, { status: "running" });
104
+
105
+ // Use convergence-aware node detection instead of blindly passing successors.
106
+ // This prevents duplicate step creation for convergence nodes (e.g., fan-out → merge).
107
+ // findReadyNodes checks ALL predecessors are completed before marking a node ready.
108
+ const completedNodeIds = new Set(getCompletedStepNodeIds(run.id));
109
+ const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
110
+
111
+ if (readyNodes.length > 0) {
112
+ await walkGraph(workflow.definition, run.id, ctx, readyNodes, registry, workflow.id);
113
+ } else {
114
+ finalizeOrWait(run.id);
115
+ }
116
+ }
117
+
118
+ /**
119
+ * If no nodes are ready and no steps are still waiting, finalize the run.
120
+ * Otherwise set it back to waiting for the next task completion.
121
+ */
122
+ function finalizeOrWait(runId: string): void {
123
+ const steps = getWorkflowRunStepsByRunId(runId);
124
+ const hasWaiting = steps.some((s) => s.status === "waiting");
125
+ if (hasWaiting) {
126
+ updateWorkflowRun(runId, { status: "waiting" });
127
+ } else {
128
+ // All steps done (completed or failed) — finalize the run
129
+ updateWorkflowRun(runId, {
130
+ status: "completed",
131
+ finishedAt: new Date().toISOString(),
132
+ });
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Handle task failure/cancellation — respects workflow's onNodeFailure config.
138
+ * 'fail' (default): mark the entire run as failed.
139
+ * 'continue': treat as completed with error output, let convergence proceed.
140
+ */
141
+ async function handleTaskFailure(
142
+ event: TaskEvent,
143
+ reason: string,
144
+ registry: ExecutorRegistry,
145
+ ): Promise<void> {
146
+ const run = getWorkflowRun(event.workflowRunId!);
147
+ if (!run) return;
148
+
149
+ const workflow = getWorkflow(run.workflowId);
150
+ if (!workflow) return;
151
+
152
+ const onFailure = workflow.definition.onNodeFailure ?? "fail";
153
+
154
+ if (onFailure === "fail") {
155
+ markRunFailed(event, reason);
156
+ return;
157
+ }
158
+
159
+ // "continue": treat as completed with error output
160
+ const step = getWorkflowRunStep(event.workflowRunStepId!);
161
+ if (!step) return;
162
+
163
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
164
+ const stepOutput = {
165
+ taskId: event.taskId,
166
+ taskOutput: `[FAILED: ${reason}] This node failed or was cancelled.`,
167
+ };
168
+ checkpointStep(run.id, step.id, step.nodeId, { output: stepOutput }, ctx);
169
+
170
+ updateWorkflowRun(run.id, { status: "running" });
171
+ const completedNodeIds = new Set(getCompletedStepNodeIds(run.id));
172
+ const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
173
+
174
+ if (readyNodes.length > 0) {
175
+ await walkGraph(workflow.definition, run.id, ctx, readyNodes, registry, workflow.id);
176
+ } else {
177
+ finalizeOrWait(run.id);
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Mark a workflow run as failed when its linked task fails or is cancelled.
183
+ */
184
+ function markRunFailed(event: TaskEvent, reason: string): void {
185
+ const now = new Date().toISOString();
186
+ updateWorkflowRunStep(event.workflowRunStepId!, {
187
+ status: "failed",
188
+ error: reason,
189
+ finishedAt: now,
190
+ });
191
+ updateWorkflowRun(event.workflowRunId!, {
192
+ status: "failed",
193
+ error: reason,
194
+ finishedAt: now,
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Retry a failed workflow run from its failed step.
200
+ */
201
+ export async function retryFailedRun(runId: string, registry: ExecutorRegistry): Promise<void> {
202
+ const run = getWorkflowRun(runId);
203
+ if (!run || run.status !== "failed") throw new Error("Run is not in failed state");
204
+
205
+ const workflow = getWorkflow(run.workflowId);
206
+ if (!workflow) throw new Error("Workflow not found");
207
+
208
+ // Find the failed step
209
+ const steps = getWorkflowRunStepsByRunId(runId);
210
+ const failedStep = steps.find((s) => s.status === "failed");
211
+ if (!failedStep) throw new Error("No failed step found");
212
+
213
+ // Reset step and run
214
+ updateWorkflowRunStep(failedStep.id, { status: "pending", error: undefined });
215
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
216
+ updateWorkflowRun(runId, { status: "running", error: undefined, context: ctx });
217
+
218
+ // Resume from the failed node — use findReadyNodes for convergence safety
219
+ const completedNodeIds = new Set(getCompletedStepNodeIds(runId));
220
+ const readyNodes = findReadyNodes(workflow.definition, completedNodeIds);
221
+ const failedNode = workflow.definition.nodes.find((n) => n.id === failedStep.nodeId);
222
+ if (!failedNode) throw new Error(`Node ${failedStep.nodeId} not found in workflow definition`);
223
+
224
+ // Include the failed node if it's not already in ready nodes
225
+ const nodesToRun = readyNodes.some((n) => n.id === failedNode.id)
226
+ ? readyNodes
227
+ : [failedNode, ...readyNodes];
228
+ await walkGraph(workflow.definition, runId, ctx, nodesToRun, registry, workflow.id);
229
+ }
@@ -0,0 +1,216 @@
1
+ import {
2
+ getRetryableSteps,
3
+ getWorkflow,
4
+ getWorkflowRun,
5
+ updateWorkflowRun,
6
+ updateWorkflowRunStep,
7
+ } from "../be/db";
8
+ import type { RetryPolicy } from "../types";
9
+ import { checkpointStep, checkpointStepFailure } from "./checkpoint";
10
+ import { getSuccessors } from "./definition";
11
+ import { walkGraph } from "./engine";
12
+ import type { ExecutorRegistry } from "./executors/registry";
13
+ import { deepInterpolate } from "./template";
14
+ import { runStepValidation } from "./validation";
15
+
16
+ let pollerTimeout: ReturnType<typeof setTimeout> | null = null;
17
+
18
+ /**
19
+ * Start the retry poller.
20
+ *
21
+ * Uses setTimeout chaining (not setInterval) to prevent overlap —
22
+ * the next tick is scheduled only after the current one completes.
23
+ */
24
+ export function startRetryPoller(registry: ExecutorRegistry, intervalMs = 5000): void {
25
+ if (pollerTimeout !== null) return; // Already running
26
+
27
+ async function poll(): Promise<void> {
28
+ try {
29
+ const retryableSteps = getRetryableSteps();
30
+
31
+ for (const step of retryableSteps) {
32
+ try {
33
+ const run = getWorkflowRun(step.runId);
34
+ if (!run) continue;
35
+
36
+ const workflow = getWorkflow(run.workflowId);
37
+ if (!workflow) continue;
38
+
39
+ // Find the node definition for this step
40
+ const node = workflow.definition.nodes.find((n) => n.id === step.nodeId);
41
+ if (!node) continue;
42
+
43
+ console.log(
44
+ `[workflows] Retrying step ${step.nodeId} (attempt ${step.retryCount}) for run ${step.runId}`,
45
+ );
46
+
47
+ // If the run was failed (due to this step), set it back to running
48
+ if (run.status === "failed") {
49
+ updateWorkflowRun(run.id, {
50
+ status: "running",
51
+ error: undefined,
52
+ });
53
+ }
54
+
55
+ // Clear the retry marker so this step isn't picked up again
56
+ updateWorkflowRunStep(step.id, {
57
+ status: "running",
58
+ error: undefined,
59
+ nextRetryAt: undefined,
60
+ });
61
+
62
+ const ctx = (run.context ?? {}) as Record<string, unknown>;
63
+
64
+ // Deep-interpolate config
65
+ const { value: interpolatedValue } = deepInterpolate(node.config, ctx);
66
+ const interpolatedConfig = interpolatedValue as Record<string, unknown>;
67
+
68
+ // Get executor and re-run
69
+ const executor = registry.get(node.type);
70
+ const meta = {
71
+ runId: run.id,
72
+ stepId: step.id,
73
+ nodeId: step.nodeId,
74
+ workflowId: workflow.id,
75
+ dryRun: false,
76
+ };
77
+
78
+ try {
79
+ const result = await executor.run({
80
+ config: interpolatedConfig,
81
+ context: ctx,
82
+ meta,
83
+ });
84
+
85
+ if (result.status === "failed") {
86
+ // Re-failed — use the EXISTING retryCount from the step.
87
+ // checkpointStepFailure handles marking run as failed if no retries left,
88
+ // or setting nextRetryAt for the next poll cycle.
89
+ const retryPolicy = node.retry || executor.retryPolicy;
90
+ checkpointStepFailure(
91
+ run.id,
92
+ step.id,
93
+ result.error || "Retry failed",
94
+ step.retryCount,
95
+ retryPolicy,
96
+ );
97
+ } else {
98
+ // Success! Re-run validation if configured before checkpointing.
99
+ if (node.validation) {
100
+ const validationResult = await runStepValidation(
101
+ registry,
102
+ node,
103
+ result.output,
104
+ ctx,
105
+ meta,
106
+ );
107
+
108
+ if (validationResult.outcome === "halt") {
109
+ checkpointStepFailure(
110
+ run.id,
111
+ step.id,
112
+ "Validation failed (mustPass)",
113
+ step.retryCount,
114
+ );
115
+ continue;
116
+ }
117
+
118
+ if (validationResult.outcome === "retry") {
119
+ // Append validation context to history
120
+ if (validationResult.retryContext) {
121
+ const historyKey = `${node.id}_validations`;
122
+ const existing = (ctx[historyKey] as unknown[]) || [];
123
+ ctx[historyKey] = [...existing, validationResult.retryContext];
124
+ }
125
+ const retryPolicy = node.validation.retry || node.retry;
126
+ checkpointStepFailure(
127
+ run.id,
128
+ step.id,
129
+ "Validation failed, retrying",
130
+ step.retryCount,
131
+ retryPolicy,
132
+ );
133
+ continue;
134
+ }
135
+ }
136
+
137
+ // Validation passed (or no validation) — checkpoint and continue
138
+ checkpointStep(run.id, step.id, step.nodeId, result, ctx);
139
+
140
+ const port = result.nextPort || "default";
141
+ const successors = getSuccessors(workflow.definition, step.nodeId, port);
142
+ if (successors.length > 0) {
143
+ await walkGraph(
144
+ workflow.definition,
145
+ run.id,
146
+ ctx,
147
+ successors,
148
+ registry,
149
+ workflow.id,
150
+ );
151
+ } else {
152
+ // No successors — check if run is complete
153
+ updateWorkflowRun(run.id, {
154
+ status: "completed",
155
+ context: ctx,
156
+ finishedAt: new Date().toISOString(),
157
+ });
158
+ }
159
+ }
160
+ } catch (err) {
161
+ // Execution threw — treat as failure
162
+ const errorMsg = err instanceof Error ? err.message : String(err);
163
+ const retryPolicy = node.retry || executor.retryPolicy;
164
+ checkpointStepFailure(run.id, step.id, errorMsg, step.retryCount, retryPolicy);
165
+ }
166
+ } catch (err) {
167
+ console.error(`[workflows] Retry failed for step ${step.id}:`, err);
168
+ }
169
+ }
170
+ } catch (err) {
171
+ console.error("[workflows] Retry poller error:", err);
172
+ }
173
+
174
+ // Schedule next tick after completion
175
+ pollerTimeout = setTimeout(poll, intervalMs);
176
+ }
177
+
178
+ // Start the first tick
179
+ pollerTimeout = setTimeout(poll, intervalMs);
180
+ }
181
+
182
+ /**
183
+ * Stop the retry poller (for clean shutdown).
184
+ */
185
+ export function stopRetryPoller(): void {
186
+ if (pollerTimeout !== null) {
187
+ clearTimeout(pollerTimeout);
188
+ pollerTimeout = null;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Calculate retry delay based on policy and attempt number.
194
+ */
195
+ export function calculateDelay(policy: RetryPolicy, attempt: number): number {
196
+ let delay: number;
197
+
198
+ switch (policy.strategy) {
199
+ case "exponential": {
200
+ // Exponential with full jitter
201
+ const base = policy.baseDelayMs * 2 ** attempt;
202
+ delay = Math.random() * Math.min(base, policy.maxDelayMs);
203
+ break;
204
+ }
205
+ case "linear":
206
+ delay = policy.baseDelayMs * (attempt + 1);
207
+ break;
208
+ case "static":
209
+ delay = policy.baseDelayMs;
210
+ break;
211
+ default:
212
+ delay = policy.baseDelayMs;
213
+ }
214
+
215
+ return Math.min(delay, policy.maxDelayMs);
216
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Replace {{path.to.value}} tokens in a template string
3
+ * with values from the context object.
4
+ *
5
+ * Returns the interpolated result and a list of any unresolved token paths.
6
+ */
7
+
8
+ export interface InterpolateResult {
9
+ result: string;
10
+ unresolved: string[];
11
+ }
12
+
13
+ export function interpolate(template: string, ctx: Record<string, unknown>): InterpolateResult {
14
+ const unresolved: string[] = [];
15
+ const result = template.replace(/\{\{([^}]+)\}\}/g, (_match, path: string) => {
16
+ const keys = path.trim().split(".");
17
+ let value: unknown = ctx;
18
+ for (const key of keys) {
19
+ if (value == null || typeof value !== "object") {
20
+ unresolved.push(path.trim());
21
+ return "";
22
+ }
23
+ value = (value as Record<string, unknown>)[key];
24
+ }
25
+ if (value == null) {
26
+ unresolved.push(path.trim());
27
+ return "";
28
+ }
29
+ return typeof value === "object" ? safeStringify(value) : String(value);
30
+ });
31
+ return { result, unresolved };
32
+ }
33
+
34
+ /**
35
+ * Circular-reference-safe JSON.stringify for interpolation.
36
+ * Returns "[Circular]" instead of throwing on circular structures.
37
+ */
38
+ function safeStringify(value: unknown): string {
39
+ try {
40
+ return JSON.stringify(value);
41
+ } catch {
42
+ return "[Circular]";
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Deep-interpolate an arbitrary value tree (objects, arrays, strings).
48
+ * Non-string leaves are passed through unchanged.
49
+ */
50
+ export function deepInterpolate(
51
+ value: unknown,
52
+ ctx: Record<string, unknown>,
53
+ ): { value: unknown; unresolved: string[] } {
54
+ const allUnresolved: string[] = [];
55
+
56
+ function walk(v: unknown): unknown {
57
+ if (typeof v === "string") {
58
+ const { result, unresolved } = interpolate(v, ctx);
59
+ allUnresolved.push(...unresolved);
60
+ return result;
61
+ }
62
+ if (Array.isArray(v)) return v.map(walk);
63
+ if (v != null && typeof v === "object") {
64
+ const out: Record<string, unknown> = {};
65
+ for (const [k, val] of Object.entries(v)) {
66
+ out[k] = walk(val);
67
+ }
68
+ return out;
69
+ }
70
+ return v;
71
+ }
72
+
73
+ return { value: walk(value), unresolved: allUnresolved };
74
+ }