@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.
- package/README.md +271 -169
- package/openapi.json +5015 -0
- package/package.json +40 -7
- package/plugin/commands/close-issue.md +7 -3
- package/plugin/commands/create-pr.md +18 -12
- package/plugin/commands/implement-issue.md +7 -3
- package/plugin/commands/respond-github.md +8 -4
- package/plugin/commands/review-pr.md +44 -10
- package/plugin/commands/start-leader.md +1 -3
- package/plugin/commands/start-worker.md +1 -3
- package/plugin/commands/work-on-task.md +22 -3
- package/plugin/pi-skills/close-issue/SKILL.md +90 -0
- package/plugin/pi-skills/create-pr/SKILL.md +99 -0
- package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
- package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
- package/plugin/pi-skills/respond-github/SKILL.md +98 -0
- package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
- package/plugin/pi-skills/review-pr/SKILL.md +261 -0
- package/plugin/pi-skills/start-leader/SKILL.md +121 -0
- package/plugin/pi-skills/start-worker/SKILL.md +60 -0
- package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
- package/plugin/pi-skills/todos/SKILL.md +66 -0
- package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
- package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
- package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
- package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
- package/plugin/skills/artifacts/examples/static-report.sh +17 -0
- package/plugin/skills/artifacts/skill.md +71 -0
- package/src/agentmail/app.ts +65 -0
- package/src/agentmail/handlers.ts +262 -0
- package/src/agentmail/index.ts +9 -0
- package/src/agentmail/templates.ts +111 -0
- package/src/agentmail/types.ts +51 -0
- package/src/artifact-sdk/browser-sdk.ts +30 -0
- package/src/artifact-sdk/index.ts +2 -0
- package/src/artifact-sdk/localtunnel.d.ts +20 -0
- package/src/artifact-sdk/port.ts +12 -0
- package/src/artifact-sdk/server.ts +156 -0
- package/src/artifact-sdk/tunnel.ts +19 -0
- package/src/be/chunking.ts +193 -0
- package/src/be/db-queries/oauth.ts +90 -0
- package/src/be/db-queries/tracker.ts +182 -0
- package/src/be/db.ts +3327 -784
- package/src/be/embedding.ts +80 -0
- package/src/be/migrations/001_initial.sql +409 -0
- package/src/be/migrations/002_one_time_schedules.sql +59 -0
- package/src/be/migrations/003_workflows.sql +51 -0
- package/src/be/migrations/004_workflow_source.sql +81 -0
- package/src/be/migrations/005_epic_next_steps.sql +2 -0
- package/src/be/migrations/006_vcs_provider.sql +94 -0
- package/src/be/migrations/007_task_dir.sql +2 -0
- package/src/be/migrations/008_workflow_redesign.sql +85 -0
- package/src/be/migrations/009_tracker_integration.sql +144 -0
- package/src/be/migrations/010_step_diagnostics.sql +1 -0
- package/src/be/migrations/011_step_next_port.sql +1 -0
- package/src/be/migrations/012_trigger_schema.sql +1 -0
- package/src/be/migrations/013_task_output_schema.sql +2 -0
- package/src/be/migrations/014_prompt_templates.sql +33 -0
- package/src/be/migrations/015_workflow_workspace.sql +3 -0
- package/src/be/migrations/016_active_session_runner_session.sql +4 -0
- package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
- package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
- package/src/be/migrations/runner.ts +188 -0
- package/src/be/seed.ts +62 -0
- package/src/cli.tsx +231 -299
- package/src/commands/artifact.ts +241 -0
- package/src/commands/onboard/compose-generator.ts +169 -0
- package/src/commands/onboard/env-generator.ts +79 -0
- package/src/commands/onboard/manifest.ts +37 -0
- package/src/commands/onboard/presets.ts +85 -0
- package/src/commands/onboard/service-names.ts +47 -0
- package/src/commands/onboard/steps/core-credentials.tsx +111 -0
- package/src/commands/onboard/steps/custom-templates.tsx +168 -0
- package/src/commands/onboard/steps/generate.tsx +154 -0
- package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
- package/src/commands/onboard/steps/harness.tsx +21 -0
- package/src/commands/onboard/steps/health-check.tsx +171 -0
- package/src/commands/onboard/steps/integration-github.tsx +105 -0
- package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
- package/src/commands/onboard/steps/integration-menu.tsx +58 -0
- package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
- package/src/commands/onboard/steps/integration-slack.tsx +165 -0
- package/src/commands/onboard/steps/post-connect.tsx +145 -0
- package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
- package/src/commands/onboard/steps/post-task.tsx +103 -0
- package/src/commands/onboard/steps/prereq-check.tsx +178 -0
- package/src/commands/onboard/steps/review.tsx +82 -0
- package/src/commands/onboard/steps/start.tsx +97 -0
- package/src/commands/onboard/templates.ts +34 -0
- package/src/commands/onboard/types.ts +259 -0
- package/src/commands/onboard.tsx +425 -0
- package/src/commands/runner.ts +1540 -630
- package/src/commands/setup.tsx +23 -38
- package/src/commands/shared/client-config.ts +41 -0
- package/src/commands/templates.ts +172 -0
- package/src/github/app.ts +8 -0
- package/src/github/handlers.ts +384 -151
- package/src/github/index.ts +1 -0
- package/src/github/mentions-aliases.test.ts +73 -0
- package/src/github/mentions.test.ts +3 -3
- package/src/github/mentions.ts +32 -6
- package/src/github/templates.ts +398 -0
- package/src/github/types.ts +1 -0
- package/src/gitlab/auth.ts +63 -0
- package/src/gitlab/handlers.ts +368 -0
- package/src/gitlab/index.ts +19 -0
- package/src/gitlab/reactions.ts +104 -0
- package/src/gitlab/templates.ts +140 -0
- package/src/gitlab/types.ts +130 -0
- package/src/heartbeat/heartbeat.ts +434 -0
- package/src/heartbeat/index.ts +1 -0
- package/src/heartbeat/templates.ts +30 -0
- package/src/hooks/hook.ts +555 -4
- package/src/hooks/tool-loop-detection.test.ts +158 -0
- package/src/hooks/tool-loop-detection.ts +167 -0
- package/src/http/active-sessions.ts +199 -0
- package/src/http/agents.ts +328 -0
- package/src/http/config.ts +191 -0
- package/src/http/core.ts +309 -0
- package/src/http/db-query.ts +91 -0
- package/src/http/ecosystem.ts +63 -0
- package/src/http/epics.ts +460 -0
- package/src/http/index.ts +216 -0
- package/src/http/mcp.ts +77 -0
- package/src/http/memory.ts +168 -0
- package/src/http/openapi.ts +109 -0
- package/src/http/poll.ts +299 -0
- package/src/http/prompt-templates.ts +412 -0
- package/src/http/repos.ts +195 -0
- package/src/http/route-def.ts +123 -0
- package/src/http/schedules.ts +426 -0
- package/src/http/session-data.ts +241 -0
- package/src/http/stats.ts +174 -0
- package/src/http/tasks.ts +468 -0
- package/src/http/trackers/index.ts +10 -0
- package/src/http/trackers/linear.ts +187 -0
- package/src/http/types.ts +12 -0
- package/src/http/utils.ts +87 -0
- package/src/http/webhooks.ts +432 -0
- package/src/http/workflows.ts +530 -0
- package/src/http.ts +1 -1890
- package/src/linear/README.md +65 -0
- package/src/linear/app.ts +48 -0
- package/src/linear/client.ts +18 -0
- package/src/linear/index.ts +1 -0
- package/src/linear/oauth.ts +35 -0
- package/src/linear/outbound.ts +212 -0
- package/src/linear/sync.ts +567 -0
- package/src/linear/templates.ts +47 -0
- package/src/linear/types.ts +7 -0
- package/src/linear/webhook.ts +104 -0
- package/src/oauth/README.md +66 -0
- package/src/oauth/index.ts +6 -0
- package/src/oauth/wrapper.ts +204 -0
- package/src/prompts/base-prompt.ts +150 -265
- package/src/prompts/defaults.ts +196 -0
- package/src/prompts/registry.ts +57 -0
- package/src/prompts/resolver.ts +296 -0
- package/src/prompts/session-templates.ts +604 -0
- package/src/providers/claude-adapter.ts +442 -0
- package/src/providers/index.ts +24 -0
- package/src/providers/pi-mono-adapter.ts +442 -0
- package/src/providers/pi-mono-extension.ts +624 -0
- package/src/providers/pi-mono-mcp-client.ts +124 -0
- package/src/providers/types.ts +75 -0
- package/src/scheduler/scheduler.test.ts +2 -0
- package/src/scheduler/scheduler.ts +231 -40
- package/src/server.ts +97 -6
- package/src/slack/HEURISTICS.md +105 -0
- package/src/slack/actions.ts +133 -0
- package/src/slack/app.ts +7 -0
- package/src/slack/assistant.ts +118 -0
- package/src/slack/blocks.ts +233 -0
- package/src/slack/channel-activity.ts +177 -0
- package/src/slack/commands.ts +31 -17
- package/src/slack/files.ts +1 -1
- package/src/slack/handlers.test.ts +114 -1
- package/src/slack/handlers.ts +230 -55
- package/src/slack/responses.ts +120 -67
- package/src/slack/router.ts +17 -99
- package/src/slack/templates.ts +55 -0
- package/src/slack/thread-buffer.ts +213 -0
- package/src/slack/watcher.ts +119 -4
- package/src/tests/agent-activity.test.ts +247 -0
- package/src/tests/agentmail-filters.test.ts +97 -0
- package/src/tests/artifact-sdk.test.ts +800 -0
- package/src/tests/base-prompt.test.ts +264 -0
- package/src/tests/build-pi-skills.test.ts +127 -0
- package/src/tests/channel-activity.test.ts +363 -0
- package/src/tests/claude-adapter.test.ts +126 -0
- package/src/tests/context-versioning.test.ts +425 -0
- package/src/tests/db-queries-oauth.test.ts +197 -0
- package/src/tests/db-queries-tracker.test.ts +230 -0
- package/src/tests/epics.test.ts +3 -3
- package/src/tests/error-tracker.test.ts +368 -0
- package/src/tests/fetch-resolved-env.test.ts +167 -0
- package/src/tests/generate-default-claude-md.test.ts +9 -1
- package/src/tests/generate-identity-templates.test.ts +124 -0
- package/src/tests/gitlab-auth.test.ts +109 -0
- package/src/tests/gitlab-handlers.test.ts +691 -0
- package/src/tests/gitlab-vcs-db.test.ts +177 -0
- package/src/tests/heartbeat.test.ts +364 -0
- package/src/tests/http-api-integration.test.ts +1698 -0
- package/src/tests/linear-outbound-sync.test.ts +200 -0
- package/src/tests/linear-webhook.test.ts +406 -0
- package/src/tests/match-route.test.ts +187 -0
- package/src/tests/memory.test.ts +737 -0
- package/src/tests/migration-runner-regressions.test.ts +86 -0
- package/src/tests/model-control.test.ts +338 -0
- package/src/tests/oauth-wrapper.test.ts +147 -0
- package/src/tests/onboard-compose.test.ts +138 -0
- package/src/tests/onboard-env.test.ts +174 -0
- package/src/tests/onboard-manifest.test.ts +137 -0
- package/src/tests/pi-mono-adapter.test.ts +234 -0
- package/src/tests/pool-session-logs.test.ts +199 -0
- package/src/tests/progress-dedup.test.ts +98 -0
- package/src/tests/prompt-template-github.test.ts +682 -0
- package/src/tests/prompt-template-remaining.test.ts +504 -0
- package/src/tests/prompt-template-resolver.test.ts +621 -0
- package/src/tests/prompt-template-session.test.ts +363 -0
- package/src/tests/prompt-templates-db.test.ts +616 -0
- package/src/tests/provider-adapter.test.ts +122 -0
- package/src/tests/provider-command-format.test.ts +98 -0
- package/src/tests/reload-config.test.ts +170 -0
- package/src/tests/runner-polling-api.test.ts +25 -20
- package/src/tests/scheduled-tasks.test.ts +104 -0
- package/src/tests/scheduler-backoff.test.ts +166 -0
- package/src/tests/self-improvement.test.ts +541 -0
- package/src/tests/session-attach.test.ts +536 -0
- package/src/tests/session-costs.test.ts +267 -1
- package/src/tests/slack-actions.test.ts +133 -0
- package/src/tests/slack-assistant.test.ts +136 -0
- package/src/tests/slack-blocks.test.ts +246 -0
- package/src/tests/slack-metadata-inheritance.test.ts +243 -0
- package/src/tests/slack-queue-offline.test.ts +174 -0
- package/src/tests/slack-router.test.ts +181 -0
- package/src/tests/slack-thread-buffer.test.ts +305 -0
- package/src/tests/slack-thread-followups.test.ts +298 -0
- package/src/tests/slack-watcher.test.ts +101 -0
- package/src/tests/structured-output.test.ts +307 -0
- package/src/tests/swarm-repos.test.ts +198 -0
- package/src/tests/task-cancellation.test.ts +6 -4
- package/src/tests/task-working-dir.test.ts +176 -0
- package/src/tests/template-fetch.test.ts +490 -0
- package/src/tests/tool-annotations.test.ts +371 -0
- package/src/tests/tracker-tools.test.ts +184 -0
- package/src/tests/update-profile-agentid.test.ts +248 -0
- package/src/tests/update-profile-api.test.ts +143 -3
- package/src/tests/update-profile-auth.test.ts +195 -0
- package/src/tests/validation-adapters.test.ts +86 -0
- package/src/tests/vcs-provider.test.ts +27 -0
- package/src/tests/workflow-agent-task.test.ts +196 -0
- package/src/tests/workflow-async-v2.test.ts +508 -0
- package/src/tests/workflow-convergence.test.ts +541 -0
- package/src/tests/workflow-definition-validation.test.ts +366 -0
- package/src/tests/workflow-engine-v2.test.ts +691 -0
- package/src/tests/workflow-executors.test.ts +736 -0
- package/src/tests/workflow-http-v2.test.ts +599 -0
- package/src/tests/workflow-integration-io.test.ts +902 -0
- package/src/tests/workflow-io-schemas.test.ts +624 -0
- package/src/tests/workflow-registry.test.ts +592 -0
- package/src/tests/workflow-retry-v2.test.ts +401 -0
- package/src/tests/workflow-retry-validation.test.ts +282 -0
- package/src/tests/workflow-schedule-trigger.test.ts +104 -0
- package/src/tests/workflow-template.test.ts +288 -0
- package/src/tests/workflow-trigger-schema.test.ts +359 -0
- package/src/tests/workflow-triggers-v2.test.ts +264 -0
- package/src/tests/workflow-versions.test.ts +208 -0
- package/src/tests/workflow-workspace.test.ts +272 -0
- package/src/tests/x402-client.test.ts +117 -0
- package/src/tests/x402-config.test.ts +182 -0
- package/src/tests/x402-spending-tracker.test.ts +185 -0
- package/src/tools/cancel-task.ts +2 -0
- package/src/tools/context-diff.ts +171 -0
- package/src/tools/context-history.ts +138 -0
- package/src/tools/create-channel.ts +1 -0
- package/src/tools/db-query.ts +78 -0
- package/src/tools/delete-channel.ts +132 -0
- package/src/tools/epics/assign-task-to-epic.ts +1 -0
- package/src/tools/epics/create-epic.ts +3 -2
- package/src/tools/epics/delete-epic.ts +2 -0
- package/src/tools/epics/get-epic-details.ts +2 -0
- package/src/tools/epics/list-epics.ts +2 -0
- package/src/tools/epics/unassign-task-from-epic.ts +1 -0
- package/src/tools/epics/update-epic.ts +7 -4
- package/src/tools/get-swarm.ts +2 -0
- package/src/tools/get-task-details.ts +2 -0
- package/src/tools/get-tasks.ts +27 -1
- package/src/tools/inject-learning.ts +106 -0
- package/src/tools/join-swarm.ts +17 -7
- package/src/tools/list-channels.ts +2 -0
- package/src/tools/list-services.ts +2 -0
- package/src/tools/memory-get.ts +56 -0
- package/src/tools/memory-search.ts +131 -0
- package/src/tools/my-agent-info.ts +2 -0
- package/src/tools/poll-task.ts +2 -20
- package/src/tools/post-message.ts +1 -0
- package/src/tools/prompt-templates/delete.ts +86 -0
- package/src/tools/prompt-templates/get.ts +89 -0
- package/src/tools/prompt-templates/index.ts +5 -0
- package/src/tools/prompt-templates/list.ts +95 -0
- package/src/tools/prompt-templates/preview.ts +84 -0
- package/src/tools/prompt-templates/set.ts +117 -0
- package/src/tools/read-messages.ts +2 -0
- package/src/tools/register-agentmail-inbox.ts +166 -0
- package/src/tools/register-service.ts +2 -0
- package/src/tools/schedules/create-schedule.ts +134 -24
- package/src/tools/schedules/delete-schedule.ts +2 -0
- package/src/tools/schedules/list-schedules.ts +20 -4
- package/src/tools/schedules/run-schedule-now.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +49 -17
- package/src/tools/send-task.ts +132 -10
- package/src/tools/slack-download-file.ts +4 -2
- package/src/tools/slack-list-channels.ts +2 -0
- package/src/tools/slack-post.ts +2 -0
- package/src/tools/slack-read.ts +2 -0
- package/src/tools/slack-reply.ts +2 -0
- package/src/tools/slack-upload-file.ts +2 -0
- package/src/tools/store-progress.ts +205 -4
- package/src/tools/swarm-config/delete-config.ts +87 -0
- package/src/tools/swarm-config/get-config.ts +108 -0
- package/src/tools/swarm-config/index.ts +4 -0
- package/src/tools/swarm-config/list-config.ts +99 -0
- package/src/tools/swarm-config/set-config.ts +118 -0
- package/src/tools/task-action.ts +50 -5
- package/src/tools/task-dedup.ts +97 -0
- package/src/tools/templates.ts +53 -0
- package/src/tools/tool-config.ts +124 -0
- package/src/tools/tracker/index.ts +6 -0
- package/src/tools/tracker/tracker-link-epic.ts +64 -0
- package/src/tools/tracker/tracker-link-task.ts +64 -0
- package/src/tools/tracker/tracker-map-agent.ts +57 -0
- package/src/tools/tracker/tracker-status.ts +56 -0
- package/src/tools/tracker/tracker-sync-status.ts +42 -0
- package/src/tools/tracker/tracker-unlink.ts +41 -0
- package/src/tools/unregister-service.ts +2 -0
- package/src/tools/update-profile.ts +172 -17
- package/src/tools/update-service-status.ts +2 -0
- package/src/tools/utils.ts +10 -1
- package/src/tools/workflows/create-workflow.ts +129 -0
- package/src/tools/workflows/delete-workflow.ts +42 -0
- package/src/tools/workflows/get-workflow-run.ts +59 -0
- package/src/tools/workflows/get-workflow.ts +53 -0
- package/src/tools/workflows/index.ts +9 -0
- package/src/tools/workflows/list-workflow-runs.ts +48 -0
- package/src/tools/workflows/list-workflows.ts +42 -0
- package/src/tools/workflows/retry-workflow-run.ts +40 -0
- package/src/tools/workflows/trigger-workflow.ts +96 -0
- package/src/tools/workflows/update-workflow.ts +133 -0
- package/src/tracker/types.ts +51 -0
- package/src/types.ts +530 -14
- package/src/utils/credentials.test.ts +156 -0
- package/src/utils/credentials.ts +50 -0
- package/src/utils/error-tracker.ts +190 -0
- package/src/vcs/index.ts +15 -0
- package/src/vcs/types.ts +5 -0
- package/src/workflows/checkpoint.ts +121 -0
- package/src/workflows/cooldown.ts +28 -0
- package/src/workflows/definition.ts +235 -0
- package/src/workflows/engine.ts +580 -0
- package/src/workflows/event-bus.ts +29 -0
- package/src/workflows/executors/agent-task.ts +103 -0
- package/src/workflows/executors/base.ts +86 -0
- package/src/workflows/executors/code-match.ts +88 -0
- package/src/workflows/executors/index.ts +16 -0
- package/src/workflows/executors/notify.ts +93 -0
- package/src/workflows/executors/property-match.ts +104 -0
- package/src/workflows/executors/raw-llm.ts +83 -0
- package/src/workflows/executors/registry.ts +76 -0
- package/src/workflows/executors/script.ts +103 -0
- package/src/workflows/executors/validate.ts +215 -0
- package/src/workflows/executors/vcs.ts +58 -0
- package/src/workflows/index.ts +61 -0
- package/src/workflows/input.ts +46 -0
- package/src/workflows/json-schema-validator.ts +118 -0
- package/src/workflows/recovery.ts +139 -0
- package/src/workflows/resume.ts +229 -0
- package/src/workflows/retry-poller.ts +216 -0
- package/src/workflows/template.ts +74 -0
- package/src/workflows/templates.ts +86 -0
- package/src/workflows/triggers.ts +124 -0
- package/src/workflows/validation.ts +104 -0
- package/src/workflows/version.ts +44 -0
- package/src/x402/cli.ts +140 -0
- package/src/x402/client.ts +192 -0
- package/src/x402/config.ts +131 -0
- package/src/x402/index.ts +37 -0
- package/src/x402/openfort-signer.ts +83 -0
- package/src/x402/spending-tracker.ts +109 -0
- package/templates/official/coder/CLAUDE.md +49 -0
- package/templates/official/coder/IDENTITY.md +28 -0
- package/templates/official/coder/SOUL.md +43 -0
- package/templates/official/coder/TOOLS.md +40 -0
- package/templates/official/coder/config.json +23 -0
- package/templates/official/coder/start-up.sh +23 -0
- package/templates/official/content-reviewer/CLAUDE.md +68 -0
- package/templates/official/content-reviewer/IDENTITY.md +28 -0
- package/templates/official/content-reviewer/SOUL.md +44 -0
- package/templates/official/content-reviewer/TOOLS.md +37 -0
- package/templates/official/content-reviewer/config.json +23 -0
- package/templates/official/content-reviewer/start-up.sh +23 -0
- package/templates/official/content-strategist/CLAUDE.md +63 -0
- package/templates/official/content-strategist/IDENTITY.md +33 -0
- package/templates/official/content-strategist/SOUL.md +48 -0
- package/templates/official/content-strategist/TOOLS.md +47 -0
- package/templates/official/content-strategist/config.json +23 -0
- package/templates/official/content-strategist/start-up.sh +23 -0
- package/templates/official/content-writer/CLAUDE.md +72 -0
- package/templates/official/content-writer/IDENTITY.md +30 -0
- package/templates/official/content-writer/SOUL.md +46 -0
- package/templates/official/content-writer/TOOLS.md +44 -0
- package/templates/official/content-writer/config.json +23 -0
- package/templates/official/content-writer/start-up.sh +23 -0
- package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
- package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
- package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
- package/templates/official/forward-deployed-engineer/config.json +21 -0
- package/templates/official/lead/CLAUDE.md +33 -0
- package/templates/official/lead/IDENTITY.md +36 -0
- package/templates/official/lead/SOUL.md +51 -0
- package/templates/official/lead/config.json +22 -0
- package/templates/official/researcher/CLAUDE.md +46 -0
- package/templates/official/researcher/IDENTITY.md +28 -0
- package/templates/official/researcher/SOUL.md +43 -0
- package/templates/official/researcher/config.json +21 -0
- package/templates/official/reviewer/CLAUDE.md +63 -0
- package/templates/official/reviewer/IDENTITY.md +28 -0
- package/templates/official/reviewer/SOUL.md +45 -0
- package/templates/official/reviewer/config.json +21 -0
- package/templates/official/tester/CLAUDE.md +53 -0
- package/templates/official/tester/IDENTITY.md +28 -0
- package/templates/official/tester/SOUL.md +55 -0
- package/templates/official/tester/config.json +21 -0
- package/templates/schema.ts +35 -0
- package/.claude/settings.local.json +0 -115
- package/.dockerignore +0 -61
- package/.editorconfig +0 -15
- package/.env.docker.example +0 -39
- package/.env.example +0 -40
- package/.github/workflows/ci.yml +0 -76
- package/.github/workflows/docker-and-deploy.yml +0 -117
- package/.wts-config.json +0 -4
- package/.wts-setup.ts +0 -102
- package/CLAUDE.md +0 -104
- package/CONTRIBUTING.md +0 -270
- package/DEPLOYMENT.md +0 -605
- package/Dockerfile +0 -57
- package/Dockerfile.worker +0 -157
- package/FAQ.md +0 -19
- package/MCP.md +0 -406
- package/UI.md +0 -40
- package/assets/agent-swarm-logo-orange.png +0 -0
- package/assets/agent-swarm-logo.png +0 -0
- package/assets/agent-swarm.mp4 +0 -0
- package/assets/agent-swarm.png +0 -0
- package/biome.json +0 -39
- package/deploy/DEPLOY.md +0 -60
- package/deploy/agent-swarm.service +0 -17
- package/deploy/docker-push.ts +0 -30
- package/deploy/install.ts +0 -85
- package/deploy/prod-db.ts +0 -42
- package/deploy/uninstall.ts +0 -12
- package/deploy/update.ts +0 -21
- package/docker-compose.example.yml +0 -159
- package/docker-entrypoint.sh +0 -352
- package/ecosystem.config.cjs +0 -66
- package/plugin/README.md +0 -1
- package/plugin/hooks/hooks.json +0 -71
- package/pyproject.toml +0 -9
- package/scripts/generate-mcp-docs.ts +0 -415
- package/slack-manifest.json +0 -71
- package/src/tests/get-inbox-message.test.ts +0 -145
- package/src/tools/get-inbox-message.ts +0 -89
- package/src/tools/inbox-delegate.ts +0 -113
- package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
- package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
- package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
- package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
- package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
- package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
- package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
- package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
- package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
- package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
- package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
- package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
- package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
- package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
- package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
- package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
- package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
- package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
- package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
- package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
- package/thoughts/shared/research/.gitkeep +0 -0
- package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
- package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
- package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
- package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
- package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
- package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
- package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
- package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
- package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
- package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
- package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
- package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
- package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
- package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
- package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
- package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
- package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
- package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
- package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
- package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
- package/tsconfig.json +0 -37
- package/ui/CLAUDE.md +0 -49
- package/ui/bun.lock +0 -771
- package/ui/index.html +0 -22
- package/ui/package-lock.json +0 -5290
- package/ui/package.json +0 -33
- package/ui/pnpm-lock.yaml +0 -3341
- package/ui/postcss.config.js +0 -6
- package/ui/public/logo.png +0 -0
- package/ui/src/App.tsx +0 -63
- package/ui/src/components/ActivityFeed.tsx +0 -440
- package/ui/src/components/AgentDetailPanel.tsx +0 -733
- package/ui/src/components/AgentsPanel.tsx +0 -815
- package/ui/src/components/ChatPanel.tsx +0 -1920
- package/ui/src/components/ConfigModal.tsx +0 -253
- package/ui/src/components/Dashboard.tsx +0 -832
- package/ui/src/components/EditAgentProfileModal.tsx +0 -433
- package/ui/src/components/EpicDetailPage.tsx +0 -741
- package/ui/src/components/EpicsPanel.tsx +0 -566
- package/ui/src/components/Header.tsx +0 -160
- package/ui/src/components/JsonViewer.tsx +0 -171
- package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
- package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
- package/ui/src/components/ServicesPanel.tsx +0 -622
- package/ui/src/components/SessionLogPanel.tsx +0 -1219
- package/ui/src/components/StatsBar.tsx +0 -321
- package/ui/src/components/StatusBadge.tsx +0 -168
- package/ui/src/components/TaskDetailPanel.tsx +0 -903
- package/ui/src/components/TasksPanel.tsx +0 -614
- package/ui/src/components/UsageCharts.tsx +0 -216
- package/ui/src/components/UsageTab.tsx +0 -394
- package/ui/src/hooks/queries.ts +0 -353
- package/ui/src/hooks/useAutoScroll.ts +0 -83
- package/ui/src/index.css +0 -257
- package/ui/src/lib/api.ts +0 -268
- package/ui/src/lib/config.ts +0 -35
- package/ui/src/lib/contentPreview.ts +0 -208
- package/ui/src/lib/theme.ts +0 -214
- package/ui/src/lib/utils.ts +0 -88
- package/ui/src/main.tsx +0 -28
- package/ui/src/types/api.ts +0 -323
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/tailwind.config.js +0 -37
- package/ui/tsconfig.json +0 -31
- package/ui/vite.config.ts +0 -35
- /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
|
+
}
|