@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,691 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
+
import { unlink } from "node:fs/promises";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
closeDb,
|
|
6
|
+
createWorkflow,
|
|
7
|
+
deleteWorkflow,
|
|
8
|
+
getWorkflowRun,
|
|
9
|
+
getWorkflowRunStepsByRunId,
|
|
10
|
+
initDb,
|
|
11
|
+
} from "../be/db";
|
|
12
|
+
import type { Workflow, WorkflowDefinition } from "../types";
|
|
13
|
+
import { shouldSkipCooldown } from "../workflows/cooldown";
|
|
14
|
+
import { findReadyNodes, startWorkflowExecution, walkGraph } from "../workflows/engine";
|
|
15
|
+
import {
|
|
16
|
+
BaseExecutor,
|
|
17
|
+
type ExecutorDependencies,
|
|
18
|
+
type ExecutorResult,
|
|
19
|
+
} from "../workflows/executors/base";
|
|
20
|
+
import { ExecutorRegistry } from "../workflows/executors/registry";
|
|
21
|
+
import { resolveInputs } from "../workflows/input";
|
|
22
|
+
|
|
23
|
+
const TEST_DB_PATH = "./test-workflow-engine-v2.sqlite";
|
|
24
|
+
|
|
25
|
+
// ─── Test Executors ──────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
class EchoExecutor extends BaseExecutor<typeof EchoExecutor.schema, typeof EchoExecutor.outSchema> {
|
|
28
|
+
static readonly schema = z.object({ message: z.string() });
|
|
29
|
+
static readonly outSchema = z.object({ echo: z.string() });
|
|
30
|
+
|
|
31
|
+
readonly type = "echo";
|
|
32
|
+
readonly mode = "instant" as const;
|
|
33
|
+
readonly configSchema = EchoExecutor.schema;
|
|
34
|
+
readonly outputSchema = EchoExecutor.outSchema;
|
|
35
|
+
|
|
36
|
+
protected async execute(
|
|
37
|
+
config: z.infer<typeof EchoExecutor.schema>,
|
|
38
|
+
): Promise<ExecutorResult<z.infer<typeof EchoExecutor.outSchema>>> {
|
|
39
|
+
return { status: "success", output: { echo: config.message } };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
class CounterExecutor extends BaseExecutor<
|
|
44
|
+
typeof CounterExecutor.schema,
|
|
45
|
+
typeof CounterExecutor.outSchema
|
|
46
|
+
> {
|
|
47
|
+
static readonly schema = z.object({ value: z.number() });
|
|
48
|
+
static readonly outSchema = z.object({ doubled: z.number() });
|
|
49
|
+
|
|
50
|
+
readonly type = "counter";
|
|
51
|
+
readonly mode = "instant" as const;
|
|
52
|
+
readonly configSchema = CounterExecutor.schema;
|
|
53
|
+
readonly outputSchema = CounterExecutor.outSchema;
|
|
54
|
+
|
|
55
|
+
protected async execute(
|
|
56
|
+
config: z.infer<typeof CounterExecutor.schema>,
|
|
57
|
+
): Promise<ExecutorResult<z.infer<typeof CounterExecutor.outSchema>>> {
|
|
58
|
+
return { status: "success", output: { doubled: config.value * 2 } };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class BranchExecutor extends BaseExecutor<
|
|
63
|
+
typeof BranchExecutor.schema,
|
|
64
|
+
typeof BranchExecutor.outSchema
|
|
65
|
+
> {
|
|
66
|
+
static readonly schema = z.object({
|
|
67
|
+
conditions: z.array(z.object({ field: z.string(), op: z.string(), value: z.unknown() })),
|
|
68
|
+
});
|
|
69
|
+
static readonly outSchema = z.object({ passed: z.boolean() });
|
|
70
|
+
|
|
71
|
+
readonly type = "property-match";
|
|
72
|
+
readonly mode = "instant" as const;
|
|
73
|
+
readonly configSchema = BranchExecutor.schema;
|
|
74
|
+
readonly outputSchema = BranchExecutor.outSchema;
|
|
75
|
+
|
|
76
|
+
protected async execute(
|
|
77
|
+
config: z.infer<typeof BranchExecutor.schema>,
|
|
78
|
+
context: Readonly<Record<string, unknown>>,
|
|
79
|
+
): Promise<ExecutorResult<z.infer<typeof BranchExecutor.outSchema>>> {
|
|
80
|
+
// Simple evaluation: check first condition
|
|
81
|
+
const cond = config.conditions[0];
|
|
82
|
+
if (!cond) return { status: "success", output: { passed: true }, nextPort: "true" };
|
|
83
|
+
|
|
84
|
+
const fieldPath = cond.field.split(".");
|
|
85
|
+
let value: unknown = context;
|
|
86
|
+
for (const key of fieldPath) {
|
|
87
|
+
if (value == null || typeof value !== "object") {
|
|
88
|
+
value = undefined;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
value = (value as Record<string, unknown>)[key];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let passed = false;
|
|
95
|
+
if (cond.op === "eq") passed = value === cond.value;
|
|
96
|
+
if (cond.op === "neq") passed = value !== cond.value;
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
status: "success",
|
|
100
|
+
output: { passed },
|
|
101
|
+
nextPort: passed ? "true" : "false",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class SlowExecutor extends BaseExecutor<typeof SlowExecutor.schema, typeof SlowExecutor.outSchema> {
|
|
107
|
+
static readonly schema = z.object({ delayMs: z.number() });
|
|
108
|
+
static readonly outSchema = z.object({ finished: z.boolean() });
|
|
109
|
+
|
|
110
|
+
readonly type = "slow";
|
|
111
|
+
readonly mode = "instant" as const;
|
|
112
|
+
readonly configSchema = SlowExecutor.schema;
|
|
113
|
+
readonly outputSchema = SlowExecutor.outSchema;
|
|
114
|
+
|
|
115
|
+
protected async execute(
|
|
116
|
+
config: z.infer<typeof SlowExecutor.schema>,
|
|
117
|
+
): Promise<ExecutorResult<z.infer<typeof SlowExecutor.outSchema>>> {
|
|
118
|
+
await new Promise((resolve) => setTimeout(resolve, config.delayMs));
|
|
119
|
+
return { status: "success", output: { finished: true } };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class FailingExecutor extends BaseExecutor<
|
|
124
|
+
typeof FailingExecutor.schema,
|
|
125
|
+
typeof FailingExecutor.outSchema
|
|
126
|
+
> {
|
|
127
|
+
static readonly schema = z.object({ errorMsg: z.string().optional() });
|
|
128
|
+
static readonly outSchema = z.object({});
|
|
129
|
+
|
|
130
|
+
readonly type = "failing";
|
|
131
|
+
readonly mode = "instant" as const;
|
|
132
|
+
readonly configSchema = FailingExecutor.schema;
|
|
133
|
+
readonly outputSchema = FailingExecutor.outSchema;
|
|
134
|
+
|
|
135
|
+
protected async execute(
|
|
136
|
+
config: z.infer<typeof FailingExecutor.schema>,
|
|
137
|
+
): Promise<ExecutorResult<z.infer<typeof FailingExecutor.outSchema>>> {
|
|
138
|
+
return { status: "failed", error: config.errorMsg || "intentional failure" };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
class ValidatePassExecutor extends BaseExecutor<
|
|
143
|
+
typeof ValidatePassExecutor.schema,
|
|
144
|
+
typeof ValidatePassExecutor.outSchema
|
|
145
|
+
> {
|
|
146
|
+
static readonly schema = z
|
|
147
|
+
.object({
|
|
148
|
+
targetNodeId: z.string(),
|
|
149
|
+
})
|
|
150
|
+
.passthrough();
|
|
151
|
+
static readonly outSchema = z.object({
|
|
152
|
+
pass: z.boolean(),
|
|
153
|
+
reasoning: z.string(),
|
|
154
|
+
confidence: z.number(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
readonly type = "validate";
|
|
158
|
+
readonly mode = "instant" as const;
|
|
159
|
+
readonly configSchema = ValidatePassExecutor.schema;
|
|
160
|
+
readonly outputSchema = ValidatePassExecutor.outSchema;
|
|
161
|
+
|
|
162
|
+
protected async execute(
|
|
163
|
+
config: z.infer<typeof ValidatePassExecutor.schema>,
|
|
164
|
+
_context: Readonly<Record<string, unknown>>,
|
|
165
|
+
): Promise<ExecutorResult<z.infer<typeof ValidatePassExecutor.outSchema>>> {
|
|
166
|
+
// Check if the config signals this should fail
|
|
167
|
+
const shouldFail = (config as Record<string, unknown>).shouldFail === true;
|
|
168
|
+
return {
|
|
169
|
+
status: "success",
|
|
170
|
+
output: {
|
|
171
|
+
pass: !shouldFail,
|
|
172
|
+
reasoning: shouldFail ? "Forced failure" : "All good",
|
|
173
|
+
confidence: 1.0,
|
|
174
|
+
},
|
|
175
|
+
nextPort: shouldFail ? "fail" : "pass",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ─── Mock Dependencies ───────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
const mockDeps: ExecutorDependencies = {
|
|
183
|
+
db: {} as typeof import("../be/db"),
|
|
184
|
+
eventBus: { emit: () => {}, on: () => {}, off: () => {} },
|
|
185
|
+
interpolate: (t: string) => t,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
function createTestRegistry(): ExecutorRegistry {
|
|
189
|
+
const registry = new ExecutorRegistry();
|
|
190
|
+
registry.register(new EchoExecutor(mockDeps));
|
|
191
|
+
registry.register(new CounterExecutor(mockDeps));
|
|
192
|
+
registry.register(new BranchExecutor(mockDeps));
|
|
193
|
+
registry.register(new SlowExecutor(mockDeps));
|
|
194
|
+
registry.register(new FailingExecutor(mockDeps));
|
|
195
|
+
registry.register(new ValidatePassExecutor(mockDeps));
|
|
196
|
+
return registry;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let workflowCounter = 0;
|
|
200
|
+
const createdWorkflowIds: string[] = [];
|
|
201
|
+
|
|
202
|
+
/** Create a workflow persisted to the test DB */
|
|
203
|
+
function makeWorkflow(def: WorkflowDefinition, overrides?: Partial<Workflow>): Workflow {
|
|
204
|
+
workflowCounter++;
|
|
205
|
+
const workflow = createWorkflow({
|
|
206
|
+
name: overrides?.name || `test-workflow-${workflowCounter}-${Date.now()}`,
|
|
207
|
+
definition: def,
|
|
208
|
+
triggers: overrides?.triggers,
|
|
209
|
+
cooldown: overrides?.cooldown,
|
|
210
|
+
input: overrides?.input,
|
|
211
|
+
});
|
|
212
|
+
// Track for cleanup
|
|
213
|
+
createdWorkflowIds.push(workflow.id);
|
|
214
|
+
return { ...workflow, ...overrides };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ─── Tests ───────────────────────────────────────────────────
|
|
218
|
+
|
|
219
|
+
describe("Workflow Engine v2 (Phase 3)", () => {
|
|
220
|
+
beforeAll(async () => {
|
|
221
|
+
try {
|
|
222
|
+
await unlink(TEST_DB_PATH);
|
|
223
|
+
} catch {
|
|
224
|
+
// File doesn't exist
|
|
225
|
+
}
|
|
226
|
+
initDb(TEST_DB_PATH);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
afterAll(async () => {
|
|
230
|
+
// Clean up created workflows
|
|
231
|
+
for (const id of createdWorkflowIds) {
|
|
232
|
+
try {
|
|
233
|
+
deleteWorkflow(id);
|
|
234
|
+
} catch {
|
|
235
|
+
// Already deleted
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
closeDb();
|
|
239
|
+
try {
|
|
240
|
+
await unlink(TEST_DB_PATH);
|
|
241
|
+
await unlink(`${TEST_DB_PATH}-wal`);
|
|
242
|
+
await unlink(`${TEST_DB_PATH}-shm`);
|
|
243
|
+
} catch {
|
|
244
|
+
// Files may not exist
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// ─── Linear Workflow ──────────────────────────────────────
|
|
249
|
+
|
|
250
|
+
describe("Linear workflow execution", () => {
|
|
251
|
+
test("executes 3 instant nodes to completion, context accumulates", async () => {
|
|
252
|
+
const registry = createTestRegistry();
|
|
253
|
+
const def: WorkflowDefinition = {
|
|
254
|
+
nodes: [
|
|
255
|
+
{ id: "step1", type: "echo", config: { message: "hello" }, next: "step2" },
|
|
256
|
+
{ id: "step2", type: "echo", config: { message: "world" }, next: "step3" },
|
|
257
|
+
{ id: "step3", type: "echo", config: { message: "done" } },
|
|
258
|
+
],
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const workflow = makeWorkflow(def);
|
|
262
|
+
const runId = await startWorkflowExecution(workflow, { test: true }, registry);
|
|
263
|
+
|
|
264
|
+
const run = getWorkflowRun(runId);
|
|
265
|
+
expect(run).not.toBeNull();
|
|
266
|
+
expect(run!.status).toBe("completed");
|
|
267
|
+
|
|
268
|
+
// Verify context accumulated
|
|
269
|
+
expect(run!.context).toBeDefined();
|
|
270
|
+
const ctx = run!.context as Record<string, unknown>;
|
|
271
|
+
expect(ctx.step1).toEqual({ echo: "hello" });
|
|
272
|
+
expect(ctx.step2).toEqual({ echo: "world" });
|
|
273
|
+
expect(ctx.step3).toEqual({ echo: "done" });
|
|
274
|
+
|
|
275
|
+
// Verify 3 steps created
|
|
276
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
277
|
+
expect(steps).toHaveLength(3);
|
|
278
|
+
expect(steps.every((s) => s.status === "completed")).toBe(true);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// ─── Branching Workflow ───────────────────────────────────
|
|
283
|
+
|
|
284
|
+
describe("Branching workflow", () => {
|
|
285
|
+
test("follows true port on property-match", async () => {
|
|
286
|
+
const registry = createTestRegistry();
|
|
287
|
+
const def: WorkflowDefinition = {
|
|
288
|
+
nodes: [
|
|
289
|
+
{ id: "start", type: "echo", config: { message: "data" }, next: "check" },
|
|
290
|
+
{
|
|
291
|
+
id: "check",
|
|
292
|
+
type: "property-match",
|
|
293
|
+
config: {
|
|
294
|
+
conditions: [{ field: "start.echo", op: "eq", value: "data" }],
|
|
295
|
+
},
|
|
296
|
+
next: { true: "ok", false: "notok" },
|
|
297
|
+
},
|
|
298
|
+
{ id: "ok", type: "echo", config: { message: "passed" } },
|
|
299
|
+
{ id: "notok", type: "echo", config: { message: "failed" } },
|
|
300
|
+
],
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const workflow = makeWorkflow(def);
|
|
304
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
305
|
+
|
|
306
|
+
const run = getWorkflowRun(runId);
|
|
307
|
+
expect(run!.status).toBe("completed");
|
|
308
|
+
|
|
309
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
310
|
+
const stepNodeIds = steps.map((s) => s.nodeId);
|
|
311
|
+
expect(stepNodeIds).toContain("start");
|
|
312
|
+
expect(stepNodeIds).toContain("check");
|
|
313
|
+
expect(stepNodeIds).toContain("ok");
|
|
314
|
+
expect(stepNodeIds).not.toContain("notok");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("follows false port on property-match", async () => {
|
|
318
|
+
const registry = createTestRegistry();
|
|
319
|
+
const def: WorkflowDefinition = {
|
|
320
|
+
nodes: [
|
|
321
|
+
{ id: "start", type: "echo", config: { message: "data" }, next: "check" },
|
|
322
|
+
{
|
|
323
|
+
id: "check",
|
|
324
|
+
type: "property-match",
|
|
325
|
+
config: {
|
|
326
|
+
conditions: [{ field: "start.echo", op: "eq", value: "different" }],
|
|
327
|
+
},
|
|
328
|
+
next: { true: "ok", false: "notok" },
|
|
329
|
+
},
|
|
330
|
+
{ id: "ok", type: "echo", config: { message: "passed" } },
|
|
331
|
+
{ id: "notok", type: "echo", config: { message: "failed" } },
|
|
332
|
+
],
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const workflow = makeWorkflow(def);
|
|
336
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
337
|
+
|
|
338
|
+
const run = getWorkflowRun(runId);
|
|
339
|
+
expect(run!.status).toBe("completed");
|
|
340
|
+
|
|
341
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
342
|
+
const stepNodeIds = steps.map((s) => s.nodeId);
|
|
343
|
+
expect(stepNodeIds).toContain("notok");
|
|
344
|
+
expect(stepNodeIds).not.toContain("ok");
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
// ─── Memoization ──────────────────────────────────────────
|
|
349
|
+
|
|
350
|
+
describe("Memoization (idempotency)", () => {
|
|
351
|
+
test("re-walking with completed steps skips them", async () => {
|
|
352
|
+
const registry = createTestRegistry();
|
|
353
|
+
const def: WorkflowDefinition = {
|
|
354
|
+
nodes: [
|
|
355
|
+
{ id: "a", type: "echo", config: { message: "first" }, next: "b" },
|
|
356
|
+
{ id: "b", type: "echo", config: { message: "second" } },
|
|
357
|
+
],
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
// Execute the workflow fully first
|
|
361
|
+
const workflow = makeWorkflow(def);
|
|
362
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
363
|
+
|
|
364
|
+
const run = getWorkflowRun(runId);
|
|
365
|
+
expect(run!.status).toBe("completed");
|
|
366
|
+
|
|
367
|
+
// Now walk the graph again with the same runId — steps should be skipped
|
|
368
|
+
const ctx: Record<string, unknown> = { trigger: {} };
|
|
369
|
+
const entryNodes = def.nodes.filter((n) => n.id === "a");
|
|
370
|
+
await walkGraph(def, runId, ctx, entryNodes, registry, workflow.id);
|
|
371
|
+
|
|
372
|
+
// Should still have only 2 steps (no duplicates)
|
|
373
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
374
|
+
expect(steps).toHaveLength(2);
|
|
375
|
+
|
|
376
|
+
// Context should be re-populated from stored outputs
|
|
377
|
+
expect(ctx.a).toEqual({ echo: "first" });
|
|
378
|
+
expect(ctx.b).toEqual({ echo: "second" });
|
|
379
|
+
});
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
// ─── Timeout ──────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
describe("Timeout", () => {
|
|
385
|
+
test("step fails when executor exceeds timeout", async () => {
|
|
386
|
+
const registry = createTestRegistry();
|
|
387
|
+
const def: WorkflowDefinition = {
|
|
388
|
+
nodes: [
|
|
389
|
+
// 60s delay should exceed the 30s default timeout
|
|
390
|
+
{ id: "slow", type: "slow", config: { delayMs: 60_000 } },
|
|
391
|
+
],
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const workflow = makeWorkflow(def);
|
|
395
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
396
|
+
|
|
397
|
+
const run = getWorkflowRun(runId);
|
|
398
|
+
expect(run!.status).toBe("failed");
|
|
399
|
+
expect(run!.error).toContain("timed out");
|
|
400
|
+
}, 35_000); // Allow test up to 35s
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// ─── Validation ───────────────────────────────────────────
|
|
404
|
+
|
|
405
|
+
describe("Validation", () => {
|
|
406
|
+
test("validation pass allows step to complete", async () => {
|
|
407
|
+
const registry = createTestRegistry();
|
|
408
|
+
const def: WorkflowDefinition = {
|
|
409
|
+
nodes: [
|
|
410
|
+
{
|
|
411
|
+
id: "step1",
|
|
412
|
+
type: "echo",
|
|
413
|
+
config: { message: "validated" },
|
|
414
|
+
validation: {
|
|
415
|
+
executor: "validate",
|
|
416
|
+
config: { shouldFail: false },
|
|
417
|
+
mustPass: true,
|
|
418
|
+
},
|
|
419
|
+
next: "step2",
|
|
420
|
+
},
|
|
421
|
+
{ id: "step2", type: "echo", config: { message: "after" } },
|
|
422
|
+
],
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const workflow = makeWorkflow(def);
|
|
426
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
427
|
+
|
|
428
|
+
const run = getWorkflowRun(runId);
|
|
429
|
+
expect(run!.status).toBe("completed");
|
|
430
|
+
|
|
431
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
432
|
+
expect(steps).toHaveLength(2);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("validation halt (mustPass) fails the run", async () => {
|
|
436
|
+
const registry = createTestRegistry();
|
|
437
|
+
const def: WorkflowDefinition = {
|
|
438
|
+
nodes: [
|
|
439
|
+
{
|
|
440
|
+
id: "step1",
|
|
441
|
+
type: "echo",
|
|
442
|
+
config: { message: "will fail validation" },
|
|
443
|
+
validation: {
|
|
444
|
+
executor: "validate",
|
|
445
|
+
config: { shouldFail: true },
|
|
446
|
+
mustPass: true,
|
|
447
|
+
},
|
|
448
|
+
next: "step2",
|
|
449
|
+
},
|
|
450
|
+
{ id: "step2", type: "echo", config: { message: "never reached" } },
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
const workflow = makeWorkflow(def);
|
|
455
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
456
|
+
|
|
457
|
+
const run = getWorkflowRun(runId);
|
|
458
|
+
expect(run!.status).toBe("failed");
|
|
459
|
+
expect(run!.error).toContain("Validation failed");
|
|
460
|
+
|
|
461
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
462
|
+
const nodeIds = steps.map((s) => s.nodeId);
|
|
463
|
+
expect(nodeIds).not.toContain("step2");
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
test("validation failure without mustPass is advisory (allows completion)", async () => {
|
|
467
|
+
const registry = createTestRegistry();
|
|
468
|
+
const def: WorkflowDefinition = {
|
|
469
|
+
nodes: [
|
|
470
|
+
{
|
|
471
|
+
id: "step1",
|
|
472
|
+
type: "echo",
|
|
473
|
+
config: { message: "advisory" },
|
|
474
|
+
validation: {
|
|
475
|
+
executor: "validate",
|
|
476
|
+
config: { shouldFail: true },
|
|
477
|
+
mustPass: false,
|
|
478
|
+
},
|
|
479
|
+
next: "step2",
|
|
480
|
+
},
|
|
481
|
+
{ id: "step2", type: "echo", config: { message: "reached" } },
|
|
482
|
+
],
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const workflow = makeWorkflow(def);
|
|
486
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
487
|
+
|
|
488
|
+
const run = getWorkflowRun(runId);
|
|
489
|
+
expect(run!.status).toBe("completed");
|
|
490
|
+
|
|
491
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
492
|
+
expect(steps).toHaveLength(2);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// ─── Cooldown ─────────────────────────────────────────────
|
|
497
|
+
|
|
498
|
+
describe("Cooldown", () => {
|
|
499
|
+
test("shouldSkipCooldown returns true within cooldown window", async () => {
|
|
500
|
+
const { createWorkflowRun, updateWorkflowRun } = await import("../be/db");
|
|
501
|
+
const def: WorkflowDefinition = {
|
|
502
|
+
nodes: [{ id: "a", type: "echo", config: {} }],
|
|
503
|
+
};
|
|
504
|
+
const workflow = makeWorkflow(def, { cooldown: { hours: 1 } });
|
|
505
|
+
|
|
506
|
+
// Create a successful run that just finished
|
|
507
|
+
const runId = crypto.randomUUID();
|
|
508
|
+
createWorkflowRun({ id: runId, workflowId: workflow.id });
|
|
509
|
+
updateWorkflowRun(runId, {
|
|
510
|
+
status: "completed",
|
|
511
|
+
finishedAt: new Date().toISOString(),
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Should skip — within 1 hour cooldown
|
|
515
|
+
const skip = shouldSkipCooldown(workflow.id, { hours: 1 });
|
|
516
|
+
expect(skip).toBe(true);
|
|
517
|
+
|
|
518
|
+
// Should not skip — with 0 second cooldown
|
|
519
|
+
const noSkip = shouldSkipCooldown(workflow.id, { seconds: 0 });
|
|
520
|
+
expect(noSkip).toBe(false);
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
test("startWorkflowExecution creates skipped run on cooldown", async () => {
|
|
524
|
+
const registry = createTestRegistry();
|
|
525
|
+
const def: WorkflowDefinition = {
|
|
526
|
+
nodes: [{ id: "a", type: "echo", config: { message: "hi" } }],
|
|
527
|
+
};
|
|
528
|
+
const workflow = makeWorkflow(def, { cooldown: { hours: 1 } });
|
|
529
|
+
|
|
530
|
+
// Run the workflow once (should succeed)
|
|
531
|
+
const run1Id = await startWorkflowExecution(workflow, {}, registry);
|
|
532
|
+
const run1 = getWorkflowRun(run1Id);
|
|
533
|
+
expect(run1!.status).toBe("completed");
|
|
534
|
+
|
|
535
|
+
// Run again — should be skipped due to cooldown
|
|
536
|
+
const run2Id = await startWorkflowExecution(workflow, {}, registry);
|
|
537
|
+
const run2 = getWorkflowRun(run2Id);
|
|
538
|
+
expect(run2!.status).toBe("skipped");
|
|
539
|
+
expect(run2!.error).toBe("cooldown");
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
// ─── Input Resolution ─────────────────────────────────────
|
|
544
|
+
|
|
545
|
+
describe("Input resolution", () => {
|
|
546
|
+
test("resolves environment variables", () => {
|
|
547
|
+
process.env.TEST_WORKFLOW_VAR = "resolved_value";
|
|
548
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: testing env var syntax
|
|
549
|
+
const result = resolveInputs({ myVar: "${TEST_WORKFLOW_VAR}" });
|
|
550
|
+
expect(result.myVar).toBe("resolved_value");
|
|
551
|
+
delete process.env.TEST_WORKFLOW_VAR;
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
test("passes through literal strings", () => {
|
|
555
|
+
const result = resolveInputs({ name: "literal value" });
|
|
556
|
+
expect(result.name).toBe("literal value");
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
test("throws on missing environment variable", () => {
|
|
560
|
+
// biome-ignore lint/suspicious/noTemplateCurlyInString: testing env var syntax
|
|
561
|
+
expect(() => resolveInputs({ bad: "${NONEXISTENT_WORKFLOW_VAR_12345}" })).toThrow("not set");
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
// ─── findReadyNodes ───────────────────────────────────────
|
|
566
|
+
|
|
567
|
+
describe("findReadyNodes", () => {
|
|
568
|
+
test("entry nodes are ready when nothing is completed", () => {
|
|
569
|
+
const def: WorkflowDefinition = {
|
|
570
|
+
nodes: [
|
|
571
|
+
{ id: "a", type: "echo", config: {}, next: "b" },
|
|
572
|
+
{ id: "b", type: "echo", config: {} },
|
|
573
|
+
],
|
|
574
|
+
};
|
|
575
|
+
const ready = findReadyNodes(def, new Set());
|
|
576
|
+
expect(ready).toHaveLength(1);
|
|
577
|
+
expect(ready[0].id).toBe("a");
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
test("successor is ready when predecessor is completed", () => {
|
|
581
|
+
const def: WorkflowDefinition = {
|
|
582
|
+
nodes: [
|
|
583
|
+
{ id: "a", type: "echo", config: {}, next: "b" },
|
|
584
|
+
{ id: "b", type: "echo", config: {}, next: "c" },
|
|
585
|
+
{ id: "c", type: "echo", config: {} },
|
|
586
|
+
],
|
|
587
|
+
};
|
|
588
|
+
const ready = findReadyNodes(def, new Set(["a"]));
|
|
589
|
+
expect(ready).toHaveLength(1);
|
|
590
|
+
expect(ready[0].id).toBe("b");
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
test("convergence node waits for all predecessors", () => {
|
|
594
|
+
const def: WorkflowDefinition = {
|
|
595
|
+
nodes: [
|
|
596
|
+
{ id: "a", type: "echo", config: {}, next: { x: "b", y: "c" } },
|
|
597
|
+
{ id: "b", type: "echo", config: {}, next: "d" },
|
|
598
|
+
{ id: "c", type: "echo", config: {}, next: "d" },
|
|
599
|
+
{ id: "d", type: "echo", config: {} },
|
|
600
|
+
],
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
// Only a and b completed — d should NOT be ready (c not done)
|
|
604
|
+
const ready1 = findReadyNodes(def, new Set(["a", "b"]));
|
|
605
|
+
expect(ready1.map((n) => n.id)).toContain("c");
|
|
606
|
+
expect(ready1.map((n) => n.id)).not.toContain("d");
|
|
607
|
+
|
|
608
|
+
// a, b, and c completed — d should be ready
|
|
609
|
+
const ready2 = findReadyNodes(def, new Set(["a", "b", "c"]));
|
|
610
|
+
expect(ready2).toHaveLength(1);
|
|
611
|
+
expect(ready2[0].id).toBe("d");
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
test("returns empty when all nodes completed", () => {
|
|
615
|
+
const def: WorkflowDefinition = {
|
|
616
|
+
nodes: [
|
|
617
|
+
{ id: "a", type: "echo", config: {}, next: "b" },
|
|
618
|
+
{ id: "b", type: "echo", config: {} },
|
|
619
|
+
],
|
|
620
|
+
};
|
|
621
|
+
const ready = findReadyNodes(def, new Set(["a", "b"]));
|
|
622
|
+
expect(ready).toHaveLength(0);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// ─── Failure Handling ─────────────────────────────────────
|
|
627
|
+
|
|
628
|
+
describe("Step failure", () => {
|
|
629
|
+
test("failing executor marks run as failed", async () => {
|
|
630
|
+
const registry = createTestRegistry();
|
|
631
|
+
const def: WorkflowDefinition = {
|
|
632
|
+
nodes: [{ id: "fail", type: "failing", config: { errorMsg: "boom" } }],
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
const workflow = makeWorkflow(def);
|
|
636
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
637
|
+
|
|
638
|
+
const run = getWorkflowRun(runId);
|
|
639
|
+
expect(run!.status).toBe("failed");
|
|
640
|
+
expect(run!.error).toContain("boom");
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
test("failure stops downstream execution", async () => {
|
|
644
|
+
const registry = createTestRegistry();
|
|
645
|
+
const def: WorkflowDefinition = {
|
|
646
|
+
nodes: [
|
|
647
|
+
{ id: "fail", type: "failing", config: { errorMsg: "early fail" }, next: "after" },
|
|
648
|
+
{ id: "after", type: "echo", config: { message: "unreachable" } },
|
|
649
|
+
],
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const workflow = makeWorkflow(def);
|
|
653
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
654
|
+
|
|
655
|
+
const run = getWorkflowRun(runId);
|
|
656
|
+
expect(run!.status).toBe("failed");
|
|
657
|
+
|
|
658
|
+
const steps = getWorkflowRunStepsByRunId(runId);
|
|
659
|
+
const nodeIds = steps.map((s) => s.nodeId);
|
|
660
|
+
expect(nodeIds).not.toContain("after");
|
|
661
|
+
});
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// ─── Context Interpolation ────────────────────────────────
|
|
665
|
+
|
|
666
|
+
describe("Context interpolation", () => {
|
|
667
|
+
test("config values are interpolated from context", async () => {
|
|
668
|
+
const registry = createTestRegistry();
|
|
669
|
+
const def: WorkflowDefinition = {
|
|
670
|
+
nodes: [
|
|
671
|
+
{ id: "first", type: "echo", config: { message: "hello" }, next: "second" },
|
|
672
|
+
{
|
|
673
|
+
id: "second",
|
|
674
|
+
type: "echo",
|
|
675
|
+
inputs: { firstEcho: "first.echo" },
|
|
676
|
+
config: { message: "got: {{firstEcho}}" },
|
|
677
|
+
},
|
|
678
|
+
],
|
|
679
|
+
};
|
|
680
|
+
|
|
681
|
+
const workflow = makeWorkflow(def);
|
|
682
|
+
const runId = await startWorkflowExecution(workflow, {}, registry);
|
|
683
|
+
|
|
684
|
+
const run = getWorkflowRun(runId);
|
|
685
|
+
expect(run!.status).toBe("completed");
|
|
686
|
+
expect((run!.context as Record<string, unknown>).second).toEqual({
|
|
687
|
+
echo: "got: hello",
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
});
|
|
691
|
+
});
|