@desplega.ai/agent-swarm 1.49.0 → 1.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/openapi.json +2070 -728
- package/package.json +10 -1
- package/src/agentmail/handlers.ts +65 -10
- package/src/agentmail/templates.ts +111 -0
- package/src/be/db.ts +1233 -7
- 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/019_skills.sql +65 -0
- package/src/be/migrations/020_approval_requests.sql +41 -0
- package/src/be/seed.ts +62 -0
- package/src/be/skill-parser.ts +70 -0
- package/src/be/skill-sync.ts +106 -0
- package/src/commands/runner.ts +320 -132
- package/src/commands/templates.ts +172 -0
- package/src/github/handlers.ts +292 -77
- 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/gitlab/handlers.ts +63 -22
- package/src/gitlab/templates.ts +140 -0
- package/src/heartbeat/heartbeat.ts +19 -10
- package/src/heartbeat/templates.ts +30 -0
- package/src/http/active-sessions.ts +27 -0
- package/src/http/approval-requests.ts +247 -0
- package/src/http/config.ts +3 -3
- package/src/http/index.ts +9 -2
- package/src/http/poll.ts +135 -14
- package/src/http/prompt-templates.ts +412 -0
- package/src/http/schedules.ts +35 -0
- package/src/http/skills.ts +479 -0
- package/src/http/workflows.ts +8 -0
- package/src/linear/sync.ts +28 -4
- package/src/linear/templates.ts +47 -0
- package/src/prompts/base-prompt.ts +41 -490
- 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 +15 -2
- package/src/providers/pi-mono-extension.ts +5 -1
- package/src/scheduler/scheduler.ts +125 -91
- package/src/server.ts +44 -0
- package/src/slack/assistant.ts +7 -4
- package/src/slack/channel-activity.ts +177 -0
- package/src/slack/handlers.ts +21 -6
- package/src/slack/templates.ts +55 -0
- package/src/tests/approval-requests.test.ts +735 -0
- package/src/tests/artifact-sdk.test.ts +12 -12
- package/src/tests/base-prompt.test.ts +49 -49
- package/src/tests/channel-activity.test.ts +363 -0
- package/src/tests/heartbeat.test.ts +1 -0
- package/src/tests/linear-webhook.test.ts +7 -3
- package/src/tests/pool-session-logs.test.ts +199 -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/self-improvement.test.ts +8 -7
- package/src/tests/skill-parser.test.ts +178 -0
- package/src/tests/skill-sync.test.ts +171 -0
- package/src/tests/slack-metadata-inheritance.test.ts +1 -1
- package/src/tests/slack-thread-followups.test.ts +1 -1
- package/src/tests/structured-output.test.ts +0 -4
- package/src/tests/tool-annotations.test.ts +2 -1
- package/src/tests/update-profile-agentid.test.ts +248 -0
- package/src/tests/update-profile-auth.test.ts +195 -0
- package/src/tests/workflow-async-v2.test.ts +126 -4
- package/src/tests/workflow-definition-validation.test.ts +76 -0
- package/src/tests/workflow-executors.test.ts +4 -2
- package/src/tests/workflow-retry-v2.test.ts +1 -1
- package/src/tests/workflow-schedule-trigger.test.ts +104 -0
- package/src/tests/workflow-workspace.test.ts +272 -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/request-human-input.ts +106 -0
- package/src/tools/skills/index.ts +11 -0
- package/src/tools/skills/skill-create.ts +105 -0
- package/src/tools/skills/skill-delete.ts +67 -0
- package/src/tools/skills/skill-get.ts +75 -0
- package/src/tools/skills/skill-install-remote.ts +152 -0
- package/src/tools/skills/skill-install.ts +101 -0
- package/src/tools/skills/skill-list.ts +77 -0
- package/src/tools/skills/skill-publish.ts +123 -0
- package/src/tools/skills/skill-search.ts +43 -0
- package/src/tools/skills/skill-sync-remote.ts +128 -0
- package/src/tools/skills/skill-uninstall.ts +60 -0
- package/src/tools/skills/skill-update.ts +128 -0
- package/src/tools/store-progress.ts +22 -4
- package/src/tools/task-action.ts +20 -0
- package/src/tools/templates.ts +53 -0
- package/src/tools/tool-config.ts +23 -0
- package/src/tools/update-profile.ts +106 -34
- package/src/tools/workflows/create-workflow.ts +19 -1
- package/src/tools/workflows/update-workflow.ts +16 -1
- package/src/types.ts +109 -2
- package/src/workflows/definition.ts +30 -12
- package/src/workflows/engine.ts +40 -14
- package/src/workflows/executors/agent-task.ts +14 -3
- package/src/workflows/executors/human-in-the-loop.ts +160 -0
- package/src/workflows/executors/registry.ts +2 -0
- package/src/workflows/index.ts +1 -1
- package/src/workflows/recovery.ts +72 -0
- package/src/workflows/resume.ts +162 -12
- package/src/workflows/triggers.ts +31 -2
- package/src/workflows/version.ts +2 -0
- package/.claude/settings.json +0 -84
- package/.claude/settings.local.json +0 -117
- package/.dockerignore +0 -61
- package/.editorconfig +0 -15
- package/.entire/settings.json +0 -4
- package/.env.docker.example +0 -56
- package/.env.example +0 -78
- package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -78
- package/.github/ISSUE_TEMPLATE/community-template.yml +0 -77
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -60
- package/.github/PULL_REQUEST_TEMPLATE/community-template.md +0 -29
- package/.github/workflows/ci.yml +0 -52
- package/.github/workflows/docker-and-deploy.yml +0 -132
- package/.github/workflows/merge-gate.yml +0 -233
- package/.opencode/plugins/entire.ts +0 -133
- package/.superset/config.json +0 -6
- package/.wts-config.json +0 -4
- package/.wts-setup.ts +0 -171
- package/CHANGELOG.md +0 -447
- package/CLAUDE.md +0 -521
- package/CONTRIBUTING.md +0 -315
- package/DEPLOYMENT.md +0 -622
- package/Dockerfile +0 -65
- package/Dockerfile.worker +0 -189
- package/MCP.md +0 -841
- package/UI.md +0 -40
- package/api-entrypoint.sh +0 -56
- 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/depot.json +0 -1
- package/docker-compose.example.yml +0 -350
- package/docker-compose.local.yml +0 -119
- package/docker-entrypoint.sh +0 -632
- package/docs-site/app/api/search/route.ts +0 -4
- package/docs-site/app/docs/[[...slug]]/page.tsx +0 -87
- package/docs-site/app/docs/layout.tsx +0 -12
- package/docs-site/app/globals.css +0 -24
- package/docs-site/app/layout.config.tsx +0 -34
- package/docs-site/app/layout.tsx +0 -119
- package/docs-site/app/llms-full.txt/route.ts +0 -11
- package/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +0 -24
- package/docs-site/app/llms.txt/route.ts +0 -8
- package/docs-site/app/page.tsx +0 -5
- package/docs-site/app/robots.ts +0 -13
- package/docs-site/app/sitemap.ts +0 -37
- package/docs-site/components/api-page.client.tsx +0 -4
- package/docs-site/components/api-page.tsx +0 -7
- package/docs-site/components/mdx/mermaid.tsx +0 -55
- package/docs-site/content/docs/(documentation)/architecture/agents.mdx +0 -117
- package/docs-site/content/docs/(documentation)/architecture/hooks.mdx +0 -77
- package/docs-site/content/docs/(documentation)/architecture/memory.mdx +0 -96
- package/docs-site/content/docs/(documentation)/architecture/meta.json +0 -4
- package/docs-site/content/docs/(documentation)/architecture/overview.mdx +0 -172
- package/docs-site/content/docs/(documentation)/concepts/epics.mdx +0 -98
- package/docs-site/content/docs/(documentation)/concepts/meta.json +0 -4
- package/docs-site/content/docs/(documentation)/concepts/scheduling.mdx +0 -136
- package/docs-site/content/docs/(documentation)/concepts/services.mdx +0 -104
- package/docs-site/content/docs/(documentation)/concepts/task-lifecycle.mdx +0 -148
- package/docs-site/content/docs/(documentation)/concepts/workflows.mdx +0 -209
- package/docs-site/content/docs/(documentation)/contributing.mdx +0 -158
- package/docs-site/content/docs/(documentation)/getting-started.mdx +0 -157
- package/docs-site/content/docs/(documentation)/guides/agentmail-integration.mdx +0 -79
- package/docs-site/content/docs/(documentation)/guides/deployment.mdx +0 -171
- package/docs-site/content/docs/(documentation)/guides/github-integration.mdx +0 -81
- package/docs-site/content/docs/(documentation)/guides/gitlab-integration.mdx +0 -93
- package/docs-site/content/docs/(documentation)/guides/linear-integration.mdx +0 -98
- package/docs-site/content/docs/(documentation)/guides/meta.json +0 -13
- package/docs-site/content/docs/(documentation)/guides/sentry-integration.mdx +0 -52
- package/docs-site/content/docs/(documentation)/guides/slack-integration.mdx +0 -179
- package/docs-site/content/docs/(documentation)/guides/x402-payments.mdx +0 -154
- package/docs-site/content/docs/(documentation)/index.mdx +0 -65
- package/docs-site/content/docs/(documentation)/meta.json +0 -19
- package/docs-site/content/docs/(documentation)/reference/cli.mdx +0 -241
- package/docs-site/content/docs/(documentation)/reference/environment-variables.mdx +0 -205
- package/docs-site/content/docs/(documentation)/reference/mcp-tools.mdx +0 -449
- package/docs-site/content/docs/(documentation)/reference/meta.json +0 -4
- package/docs-site/content/docs/api-reference/active-sessions.mdx +0 -9
- package/docs-site/content/docs/api-reference/agents.mdx +0 -9
- package/docs-site/content/docs/api-reference/channels.mdx +0 -9
- package/docs-site/content/docs/api-reference/config.mdx +0 -9
- package/docs-site/content/docs/api-reference/debug.mdx +0 -9
- package/docs-site/content/docs/api-reference/ecosystem.mdx +0 -9
- package/docs-site/content/docs/api-reference/epics.mdx +0 -9
- package/docs-site/content/docs/api-reference/index.mdx +0 -32
- package/docs-site/content/docs/api-reference/memory.mdx +0 -9
- package/docs-site/content/docs/api-reference/meta.json +0 -25
- package/docs-site/content/docs/api-reference/poll.mdx +0 -9
- package/docs-site/content/docs/api-reference/repos.mdx +0 -9
- package/docs-site/content/docs/api-reference/schedules.mdx +0 -9
- package/docs-site/content/docs/api-reference/session-data.mdx +0 -9
- package/docs-site/content/docs/api-reference/stats.mdx +0 -9
- package/docs-site/content/docs/api-reference/tasks.mdx +0 -9
- package/docs-site/content/docs/api-reference/trackers.mdx +0 -9
- package/docs-site/content/docs/api-reference/webhooks.mdx +0 -9
- package/docs-site/content/docs/api-reference/workflows.mdx +0 -9
- package/docs-site/content/docs/meta.json +0 -3
- package/docs-site/lib/get-llm-text.ts +0 -10
- package/docs-site/lib/openapi.ts +0 -23
- package/docs-site/lib/source.ts +0 -8
- package/docs-site/mdx-components.tsx +0 -13
- package/docs-site/next.config.mjs +0 -29
- package/docs-site/package.json +0 -35
- package/docs-site/pnpm-lock.yaml +0 -5407
- package/docs-site/postcss.config.mjs +0 -8
- package/docs-site/public/logo.png +0 -0
- package/docs-site/scripts/generate-docs.ts +0 -171
- package/docs-site/source.config.ts +0 -17
- package/docs-site/tsconfig.json +0 -46
- package/ecosystem.config.cjs +0 -66
- package/landing/next.config.ts +0 -14
- package/landing/package.json +0 -31
- package/landing/pnpm-lock.yaml +0 -1091
- package/landing/postcss.config.mjs +0 -8
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/logo.png +0 -0
- package/landing/public/og-image.png +0 -0
- package/landing/public/omghost-desplega.svg +0 -30
- package/landing/public/omghost-openfort.svg +0 -9
- package/landing/src/app/actions/waitlist.ts +0 -25
- package/landing/src/app/blog/openfort-hackathon/page.tsx +0 -863
- package/landing/src/app/blog/page.tsx +0 -162
- package/landing/src/app/blog/swarm-metrics/page.tsx +0 -685
- package/landing/src/app/examples/page.tsx +0 -174
- package/landing/src/app/examples/x402/page.tsx +0 -456
- package/landing/src/app/globals.css +0 -122
- package/landing/src/app/layout.tsx +0 -134
- package/landing/src/app/page.tsx +0 -27
- package/landing/src/app/robots.ts +0 -13
- package/landing/src/app/sitemap.ts +0 -44
- package/landing/src/components/architecture.tsx +0 -163
- package/landing/src/components/cta.tsx +0 -52
- package/landing/src/components/features.tsx +0 -160
- package/landing/src/components/footer.tsx +0 -100
- package/landing/src/components/hero.tsx +0 -217
- package/landing/src/components/how-it-works.tsx +0 -165
- package/landing/src/components/navbar.tsx +0 -147
- package/landing/src/components/waitlist.tsx +0 -110
- package/landing/src/components/why-choose.tsx +0 -149
- package/landing/src/components/workshops.tsx +0 -328
- package/landing/src/lib/utils.ts +0 -6
- package/landing/tsconfig.json +0 -41
- package/misc/transcripts/2026-03-09-pi-mono-e2e-verification.md +0 -154
- package/new-ui/CLAUDE.md +0 -92
- package/new-ui/README.md +0 -73
- package/new-ui/biome.json +0 -42
- package/new-ui/components.json +0 -21
- package/new-ui/index.html +0 -25
- package/new-ui/package.json +0 -49
- package/new-ui/pnpm-lock.yaml +0 -4845
- package/new-ui/public/logo.png +0 -0
- package/new-ui/src/api/client.ts +0 -814
- package/new-ui/src/api/hooks/index.ts +0 -64
- package/new-ui/src/api/hooks/use-agents.ts +0 -58
- package/new-ui/src/api/hooks/use-channels.ts +0 -115
- package/new-ui/src/api/hooks/use-config-api.ts +0 -46
- package/new-ui/src/api/hooks/use-costs.ts +0 -122
- package/new-ui/src/api/hooks/use-db-query.ts +0 -29
- package/new-ui/src/api/hooks/use-epics.ts +0 -75
- package/new-ui/src/api/hooks/use-repos.ts +0 -61
- package/new-ui/src/api/hooks/use-schedules.ts +0 -81
- package/new-ui/src/api/hooks/use-services.ts +0 -16
- package/new-ui/src/api/hooks/use-stats.ts +0 -27
- package/new-ui/src/api/hooks/use-tasks.ts +0 -89
- package/new-ui/src/api/hooks/use-workflows.ts +0 -109
- package/new-ui/src/api/types.ts +0 -549
- package/new-ui/src/app/App.tsx +0 -13
- package/new-ui/src/app/providers.tsx +0 -32
- package/new-ui/src/app/router.tsx +0 -52
- package/new-ui/src/components/layout/app-header.tsx +0 -47
- package/new-ui/src/components/layout/app-sidebar.tsx +0 -128
- package/new-ui/src/components/layout/breadcrumbs.tsx +0 -57
- package/new-ui/src/components/layout/config-guard.tsx +0 -22
- package/new-ui/src/components/layout/root-layout.tsx +0 -40
- package/new-ui/src/components/layout/swarm-switcher.tsx +0 -85
- package/new-ui/src/components/shared/command-menu.tsx +0 -131
- package/new-ui/src/components/shared/data-grid.tsx +0 -141
- package/new-ui/src/components/shared/empty-state.tsx +0 -24
- package/new-ui/src/components/shared/error-boundary.tsx +0 -72
- package/new-ui/src/components/shared/json-viewer.tsx +0 -47
- package/new-ui/src/components/shared/name-connection-modal.tsx +0 -99
- package/new-ui/src/components/shared/page-skeleton.tsx +0 -16
- package/new-ui/src/components/shared/session-log-viewer.tsx +0 -364
- package/new-ui/src/components/shared/stats-bar.tsx +0 -132
- package/new-ui/src/components/shared/status-badge.tsx +0 -131
- package/new-ui/src/components/shared/usage-summary.tsx +0 -179
- package/new-ui/src/components/ui/alert-dialog.tsx +0 -176
- package/new-ui/src/components/ui/alert.tsx +0 -60
- package/new-ui/src/components/ui/avatar.tsx +0 -96
- package/new-ui/src/components/ui/badge.tsx +0 -46
- package/new-ui/src/components/ui/button.tsx +0 -62
- package/new-ui/src/components/ui/card.tsx +0 -75
- package/new-ui/src/components/ui/command.tsx +0 -160
- package/new-ui/src/components/ui/dialog.tsx +0 -143
- package/new-ui/src/components/ui/dropdown-menu.tsx +0 -226
- package/new-ui/src/components/ui/input.tsx +0 -21
- package/new-ui/src/components/ui/label.tsx +0 -19
- package/new-ui/src/components/ui/progress.tsx +0 -26
- package/new-ui/src/components/ui/scroll-area.tsx +0 -54
- package/new-ui/src/components/ui/select.tsx +0 -175
- package/new-ui/src/components/ui/separator.tsx +0 -28
- package/new-ui/src/components/ui/sheet.tsx +0 -132
- package/new-ui/src/components/ui/sidebar.tsx +0 -691
- package/new-ui/src/components/ui/skeleton.tsx +0 -13
- package/new-ui/src/components/ui/sonner.tsx +0 -35
- package/new-ui/src/components/ui/switch.tsx +0 -33
- package/new-ui/src/components/ui/table.tsx +0 -92
- package/new-ui/src/components/ui/tabs.tsx +0 -79
- package/new-ui/src/components/ui/textarea.tsx +0 -18
- package/new-ui/src/components/ui/tooltip.tsx +0 -51
- package/new-ui/src/components/workflows/action-node.tsx +0 -53
- package/new-ui/src/components/workflows/condition-node.tsx +0 -50
- package/new-ui/src/components/workflows/graph-utils.ts +0 -124
- package/new-ui/src/components/workflows/json-tree.tsx +0 -189
- package/new-ui/src/components/workflows/node-styles.ts +0 -10
- package/new-ui/src/components/workflows/step-detail-sheet.tsx +0 -87
- package/new-ui/src/components/workflows/trigger-node.tsx +0 -41
- package/new-ui/src/components/workflows/workflow-graph.tsx +0 -65
- package/new-ui/src/hooks/use-auto-scroll.ts +0 -82
- package/new-ui/src/hooks/use-config.ts +0 -203
- package/new-ui/src/hooks/use-keyboard-shortcuts.ts +0 -41
- package/new-ui/src/hooks/use-mobile.ts +0 -19
- package/new-ui/src/hooks/use-theme.ts +0 -60
- package/new-ui/src/lib/config.ts +0 -188
- package/new-ui/src/lib/slugs.ts +0 -71
- package/new-ui/src/lib/utils.ts +0 -120
- package/new-ui/src/main.tsx +0 -11
- package/new-ui/src/pages/agents/[id]/page.tsx +0 -492
- package/new-ui/src/pages/agents/page.tsx +0 -134
- package/new-ui/src/pages/chat/page.tsx +0 -674
- package/new-ui/src/pages/config/page.tsx +0 -1109
- package/new-ui/src/pages/dashboard/page.tsx +0 -454
- package/new-ui/src/pages/debug/page.tsx +0 -275
- package/new-ui/src/pages/epics/[id]/page.tsx +0 -809
- package/new-ui/src/pages/epics/page.tsx +0 -321
- package/new-ui/src/pages/not-found/page.tsx +0 -18
- package/new-ui/src/pages/repos/page.tsx +0 -369
- package/new-ui/src/pages/schedules/[id]/page.tsx +0 -664
- package/new-ui/src/pages/schedules/page.tsx +0 -477
- package/new-ui/src/pages/services/page.tsx +0 -128
- package/new-ui/src/pages/tasks/[id]/page.tsx +0 -670
- package/new-ui/src/pages/tasks/page.tsx +0 -592
- package/new-ui/src/pages/usage/page.tsx +0 -195
- package/new-ui/src/pages/workflow-runs/[id]/page.tsx +0 -363
- package/new-ui/src/pages/workflows/[id]/page.tsx +0 -417
- package/new-ui/src/pages/workflows/page.tsx +0 -266
- package/new-ui/src/styles/ag-grid.css +0 -36
- package/new-ui/src/styles/globals.css +0 -213
- package/new-ui/test-results/.last-run.json +0 -4
- package/new-ui/tsconfig.app.json +0 -34
- package/new-ui/tsconfig.json +0 -4
- package/new-ui/tsconfig.node.json +0 -26
- package/new-ui/vercel.json +0 -4
- package/new-ui/vite.config.ts +0 -28
- package/plugin/README.md +0 -1
- package/plugin/build-pi-skills.ts +0 -233
- package/plugin/hooks/hooks.json +0 -71
- package/prek.toml +0 -75
- package/pyproject.toml +0 -9
- package/scripts/check-db-boundary.sh +0 -60
- package/scripts/e2e-docker-provider.ts +0 -820
- package/scripts/e2e-io-schemas-test.ts +0 -807
- package/scripts/e2e-provider-test.ts +0 -220
- package/scripts/e2e-workflow-redesign.sh +0 -229
- package/scripts/e2e-workflow-test.sh +0 -285
- package/scripts/e2e-workflow-test.ts +0 -857
- package/scripts/generate-mcp-docs.ts +0 -415
- package/scripts/generate-openapi.ts +0 -26
- package/scripts/measure-tool-tokens.ts +0 -118
- package/scripts/x402-e2e-test.ts +0 -195
- package/scripts/x402-test-server.ts +0 -236
- package/scripts/x402-testnet-e2e.ts +0 -668
- package/slack-manifest.json +0 -88
- package/templates-ui/README.md +0 -46
- package/templates-ui/components.json +0 -17
- package/templates-ui/eslint.config.mjs +0 -18
- package/templates-ui/next.config.ts +0 -7
- package/templates-ui/package.json +0 -35
- package/templates-ui/pnpm-lock.yaml +0 -4571
- package/templates-ui/postcss.config.mjs +0 -7
- package/templates-ui/public/file.svg +0 -1
- package/templates-ui/public/globe.svg +0 -1
- package/templates-ui/public/logo.png +0 -0
- package/templates-ui/public/next.svg +0 -1
- package/templates-ui/public/vercel.svg +0 -1
- package/templates-ui/public/window.svg +0 -1
- package/templates-ui/src/app/[category]/[name]/page.tsx +0 -89
- package/templates-ui/src/app/api/templates/[...slug]/route.ts +0 -52
- package/templates-ui/src/app/api/templates/route.ts +0 -18
- package/templates-ui/src/app/builder/page.tsx +0 -37
- package/templates-ui/src/app/globals.css +0 -94
- package/templates-ui/src/app/layout.tsx +0 -79
- package/templates-ui/src/app/page.tsx +0 -38
- package/templates-ui/src/app/robots.ts +0 -11
- package/templates-ui/src/app/sitemap.ts +0 -31
- package/templates-ui/src/components/compose-builder.tsx +0 -442
- package/templates-ui/src/components/compose-preview.tsx +0 -117
- package/templates-ui/src/components/file-preview.tsx +0 -77
- package/templates-ui/src/components/footer.tsx +0 -40
- package/templates-ui/src/components/header.tsx +0 -41
- package/templates-ui/src/components/template-card.tsx +0 -87
- package/templates-ui/src/components/template-detail.tsx +0 -125
- package/templates-ui/src/components/template-gallery.tsx +0 -263
- package/templates-ui/src/components/ui/badge.tsx +0 -36
- package/templates-ui/src/components/ui/button.tsx +0 -57
- package/templates-ui/src/components/ui/card.tsx +0 -76
- package/templates-ui/src/components/ui/separator.tsx +0 -31
- package/templates-ui/src/components/ui/tooltip.tsx +0 -32
- package/templates-ui/src/lib/compose-generator.ts +0 -241
- package/templates-ui/src/lib/templates.ts +0 -137
- package/templates-ui/src/lib/utils.ts +0 -6
- package/templates-ui/tsconfig.json +0 -34
- package/thoughts/research/2026-02-28-openfort-viem-x402-research.md +0 -679
- package/thoughts/research/2026-02-28-x402-payments-research.md +0 -686
- package/thoughts/researcher/plans/2026-02-20-agent-self-improvement-plan.md +0 -282
- package/thoughts/researcher/research/2026-02-20-agent-self-improvement.md +0 -492
- package/thoughts/shared/plans/.gitkeep +0 -0
- 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/plans/2026-02-26-mcp-tool-context-reduction.md +0 -282
- package/thoughts/shared/plans/2026-03-02-claude-context-mode-integration.md +0 -328
- package/thoughts/shared/plans/2026-03-02-code-level-heartbeat.md +0 -224
- 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/shared/research/2026-02-26-cliffy-mcp-tools.md +0 -159
- package/thoughts/shared/research/2026-03-03-database-migration-system-refactor.md +0 -337
- package/thoughts/swarm-researcher/plans/2026-02-23-openclaw-improvements-plan.md +0 -778
- package/thoughts/swarm-researcher/plans/2026-02-26-artifacts-localtunnel-plan.md +0 -1269
- package/thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md +0 -411
- package/thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md +0 -724
- package/thoughts/taras/brainstorms/2026-03-20-prompt-template-registry.md +0 -443
- package/thoughts/taras/brainstorms/2026-03-20-setup-cli-onboarding.md +0 -307
- 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/plans/2026-02-20-auto-improvement.md +0 -803
- package/thoughts/taras/plans/2026-02-20-env-management.md +0 -538
- package/thoughts/taras/plans/2026-02-20-memory-system.md +0 -882
- package/thoughts/taras/plans/2026-02-20-repos-knowledge.md +0 -806
- package/thoughts/taras/plans/2026-02-20-session-attach.md +0 -647
- package/thoughts/taras/plans/2026-02-20-worker-identity.md +0 -820
- package/thoughts/taras/plans/2026-02-25-feat-new-ui-visual-redesign-plan.md +0 -768
- package/thoughts/taras/plans/2026-03-04-fix-buildSystemPrompt-missing-fields.md +0 -77
- package/thoughts/taras/plans/2026-03-04-new-ui-missing-actions.md +0 -543
- package/thoughts/taras/plans/2026-03-06-one-time-scheduled-tasks.md +0 -373
- package/thoughts/taras/plans/2026-03-08-memory-self-improvement-enhancements.md +0 -512
- package/thoughts/taras/plans/2026-03-08-pi-mono-provider-implementation.md +0 -919
- package/thoughts/taras/plans/2026-03-09-templates-registry.md +0 -723
- package/thoughts/taras/plans/2026-03-10-task-working-directory.md +0 -371
- package/thoughts/taras/plans/2026-03-11-archil-per-agent-write-strategy.md +0 -621
- package/thoughts/taras/plans/2026-03-12-eliminate-inbox-route-to-tasks.md +0 -61
- package/thoughts/taras/plans/2026-03-12-slack-thread-followup-additive.md +0 -488
- package/thoughts/taras/plans/2026-03-13-slack-ai-improvements.md +0 -644
- package/thoughts/taras/plans/2026-03-16-route-wrapper-openapi.md +0 -636
- package/thoughts/taras/plans/2026-03-17-multi-api-config.md +0 -444
- package/thoughts/taras/plans/2026-03-18-agent-fs-integration.md +0 -591
- package/thoughts/taras/plans/2026-03-18-debug-db-explorer.md +0 -446
- package/thoughts/taras/plans/2026-03-18-workflow-redesign.md +0 -987
- package/thoughts/taras/plans/2026-03-19-compound-learnings.md +0 -403
- package/thoughts/taras/plans/2026-03-19-ticket-tracker-linear-integration.md +0 -860
- package/thoughts/taras/plans/2026-03-19-workflow-io-schemas-and-bugs.md +0 -899
- package/thoughts/taras/plans/2026-03-20-setup-cli-onboarding.md +0 -874
- package/thoughts/taras/plans/2026-03-20-workflow-structured-output-validation-workspace.md +0 -723
- 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/thoughts/taras/research/2026-02-19-agent-native-swarm-architecture.md +0 -390
- package/thoughts/taras/research/2026-02-19-swarm-gaps-implementation.md +0 -594
- package/thoughts/taras/research/2026-02-25-dashboard-ui-design-best-practices.md +0 -825
- package/thoughts/taras/research/2026-02-26-task-detail-page-redesign.md +0 -393
- package/thoughts/taras/research/2026-03-03-new-ui-missing-actions.md +0 -168
- package/thoughts/taras/research/2026-03-05-pi-mono-provider-research.md +0 -230
- package/thoughts/taras/research/2026-03-06-workflow-engine-design.md +0 -445
- package/thoughts/taras/research/2026-03-08-drive-loop-concept.md +0 -375
- package/thoughts/taras/research/2026-03-08-pi-mono-deep-dive.md +0 -869
- package/thoughts/taras/research/2026-03-09-templates-registry.md +0 -373
- package/thoughts/taras/research/2026-03-10-agent-working-directory.md +0 -223
- package/thoughts/taras/research/2026-03-10-configurable-event-prompts.md +0 -339
- package/thoughts/taras/research/2026-03-11-archil-production-setup.md +0 -181
- package/thoughts/taras/research/2026-03-11-archil-shared-disk-write-strategies.md +0 -437
- package/thoughts/taras/research/2026-03-13-slack-ai-features.md +0 -258
- package/thoughts/taras/research/2026-03-16-openapi-docs-generation.md +0 -335
- package/thoughts/taras/research/2026-03-16-route-wrapper-openapi.md +0 -670
- package/thoughts/taras/research/2026-03-16-slack-thread-followups-e2e.md +0 -54
- package/thoughts/taras/research/2026-03-18-agent-fs-integration.md +0 -558
- package/thoughts/taras/research/2026-03-18-linear-integration-finalization.md +0 -526
- package/thoughts/taras/research/2026-03-18-workflow-redesign.md +0 -797
- package/thoughts/taras/research/2026-03-19-workflow-node-io-schemas-and-bugs.md +0 -563
- package/thoughts/taras/research/2026-03-19-workflow-structured-output-validation-workspace.md +0 -486
- package/thoughts/taras/research/2026-03-20-prompt-template-registry.md +0 -469
- package/tsconfig.json +0 -37
|
@@ -1,857 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* E2E Workflow Engine Test
|
|
4
|
-
*
|
|
5
|
-
* Spins up the full API server with a clean DB, optionally builds & runs
|
|
6
|
-
* Docker lead+worker containers, then exercises the workflow REST API:
|
|
7
|
-
* - CRUD lifecycle
|
|
8
|
-
* - Trigger with property-match routing (pass + reject)
|
|
9
|
-
* - Code-match node execution
|
|
10
|
-
* - Disabled workflow rejection
|
|
11
|
-
* - Webhook secret auth
|
|
12
|
-
* - Failed-run retry
|
|
13
|
-
* - Delete
|
|
14
|
-
*
|
|
15
|
-
* Usage:
|
|
16
|
-
* bun scripts/e2e-workflow-test.ts # API-only (no docker)
|
|
17
|
-
* bun scripts/e2e-workflow-test.ts --with-docker # Also build+spawn docker lead/worker
|
|
18
|
-
* E2E_PORT=13099 bun scripts/e2e-workflow-test.ts # Custom port
|
|
19
|
-
*/
|
|
20
|
-
import { type Subprocess, $ } from "bun";
|
|
21
|
-
|
|
22
|
-
// ─── Config ─────────────────────────────────────────────────────────────────
|
|
23
|
-
const PORT = process.env.E2E_PORT || "13099";
|
|
24
|
-
const API_KEY = process.env.API_KEY || "123123";
|
|
25
|
-
const BASE_URL = `http://localhost:${PORT}`;
|
|
26
|
-
const DB_PATH = `/tmp/e2e-workflow-${Date.now()}.sqlite`;
|
|
27
|
-
const AGENT_ID = crypto.randomUUID();
|
|
28
|
-
const WITH_DOCKER = process.argv.includes("--with-docker");
|
|
29
|
-
const DOCKER_IMAGE = "agent-swarm-worker:e2e";
|
|
30
|
-
const LEAD_CONTAINER = `e2e-lead-${Date.now()}`;
|
|
31
|
-
const WORKER_CONTAINER = `e2e-worker-${Date.now()}`;
|
|
32
|
-
const LEAD_PORT = Number(PORT) + 101; // e.g. 13200
|
|
33
|
-
const WORKER_PORT = Number(PORT) + 102; // e.g. 13201
|
|
34
|
-
|
|
35
|
-
// ─── State ──────────────────────────────────────────────────────────────────
|
|
36
|
-
let apiProc: Subprocess | null = null;
|
|
37
|
-
const dockerContainers: string[] = [];
|
|
38
|
-
let pass = 0;
|
|
39
|
-
let fail = 0;
|
|
40
|
-
let cleaningUp = false;
|
|
41
|
-
|
|
42
|
-
// ─── Helpers ────────────────────────────────────────────────────────────────
|
|
43
|
-
function log(msg: string) {
|
|
44
|
-
console.log(`\x1b[1;34m[E2E]\x1b[0m ${msg}`);
|
|
45
|
-
}
|
|
46
|
-
function logPass(name: string) {
|
|
47
|
-
console.log(`\x1b[1;32m PASS\x1b[0m ${name}`);
|
|
48
|
-
pass++;
|
|
49
|
-
}
|
|
50
|
-
function logFail(name: string, detail: string) {
|
|
51
|
-
console.log(`\x1b[1;31m FAIL\x1b[0m ${name}: ${detail}`);
|
|
52
|
-
fail++;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function assert(condition: boolean, name: string, detail = "") {
|
|
56
|
-
if (condition) logPass(name);
|
|
57
|
-
else logFail(name, detail || "assertion failed");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function assertEq(actual: unknown, expected: unknown, name: string) {
|
|
61
|
-
if (actual === expected) logPass(name);
|
|
62
|
-
else logFail(name, `expected '${expected}', got '${actual}'`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function api<T = unknown>(
|
|
66
|
-
method: string,
|
|
67
|
-
path: string,
|
|
68
|
-
body?: unknown,
|
|
69
|
-
): Promise<{ status: number; data: T }> {
|
|
70
|
-
const opts: RequestInit = {
|
|
71
|
-
method,
|
|
72
|
-
headers: {
|
|
73
|
-
"Content-Type": "application/json",
|
|
74
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
75
|
-
"X-Agent-ID": AGENT_ID,
|
|
76
|
-
},
|
|
77
|
-
};
|
|
78
|
-
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
79
|
-
const res = await fetch(`${BASE_URL}${path}`, opts);
|
|
80
|
-
let data: T;
|
|
81
|
-
try {
|
|
82
|
-
data = (await res.json()) as T;
|
|
83
|
-
} catch {
|
|
84
|
-
data = null as T;
|
|
85
|
-
}
|
|
86
|
-
return { status: res.status, data };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** fetch without X-Agent-ID header (for auth tests) */
|
|
90
|
-
async function fetchNoAgent(
|
|
91
|
-
method: string,
|
|
92
|
-
path: string,
|
|
93
|
-
body?: unknown,
|
|
94
|
-
): Promise<{ status: number }> {
|
|
95
|
-
const opts: RequestInit = {
|
|
96
|
-
method,
|
|
97
|
-
headers: {
|
|
98
|
-
"Content-Type": "application/json",
|
|
99
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
if (body !== undefined) opts.body = JSON.stringify(body);
|
|
103
|
-
const res = await fetch(`${BASE_URL}${path}`, opts);
|
|
104
|
-
try {
|
|
105
|
-
await res.text();
|
|
106
|
-
} catch {
|
|
107
|
-
/* drain */
|
|
108
|
-
}
|
|
109
|
-
return { status: res.status };
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function waitForServer(url: string, timeoutMs = 30_000): Promise<void> {
|
|
113
|
-
const start = Date.now();
|
|
114
|
-
while (Date.now() - start < timeoutMs) {
|
|
115
|
-
try {
|
|
116
|
-
const res = await fetch(`${url}/api/agents`, {
|
|
117
|
-
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
118
|
-
});
|
|
119
|
-
if (res.ok) return;
|
|
120
|
-
} catch {
|
|
121
|
-
// not ready yet
|
|
122
|
-
}
|
|
123
|
-
await Bun.sleep(500);
|
|
124
|
-
}
|
|
125
|
-
throw new Error(`Server at ${url} did not start within ${timeoutMs}ms`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// ─── Cleanup ────────────────────────────────────────────────────────────────
|
|
129
|
-
async function cleanup() {
|
|
130
|
-
if (cleaningUp) return;
|
|
131
|
-
cleaningUp = true;
|
|
132
|
-
log("Cleaning up...");
|
|
133
|
-
|
|
134
|
-
// Stop docker containers
|
|
135
|
-
for (const name of dockerContainers) {
|
|
136
|
-
try {
|
|
137
|
-
await $`docker stop ${name}`.quiet();
|
|
138
|
-
log(`Stopped container ${name}`);
|
|
139
|
-
} catch {
|
|
140
|
-
// already stopped
|
|
141
|
-
}
|
|
142
|
-
try {
|
|
143
|
-
await $`docker rm -f ${name}`.quiet();
|
|
144
|
-
} catch {
|
|
145
|
-
// already removed
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Kill API process
|
|
150
|
-
if (apiProc) {
|
|
151
|
-
try {
|
|
152
|
-
apiProc.kill("SIGTERM");
|
|
153
|
-
// Give it a moment to shut down gracefully
|
|
154
|
-
await Bun.sleep(1000);
|
|
155
|
-
if (apiProc.exitCode === null) apiProc.kill("SIGKILL");
|
|
156
|
-
} catch {
|
|
157
|
-
// already dead
|
|
158
|
-
}
|
|
159
|
-
apiProc = null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Remove temp DB files
|
|
163
|
-
for (const suffix of ["", "-wal", "-shm"]) {
|
|
164
|
-
try {
|
|
165
|
-
await $`rm -f ${DB_PATH}${suffix}`.quiet();
|
|
166
|
-
} catch {
|
|
167
|
-
// fine
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
log("Cleanup complete.");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
process.on("SIGINT", async () => {
|
|
175
|
-
await cleanup();
|
|
176
|
-
process.exit(fail > 0 ? 1 : 0);
|
|
177
|
-
});
|
|
178
|
-
process.on("SIGTERM", async () => {
|
|
179
|
-
await cleanup();
|
|
180
|
-
process.exit(fail > 0 ? 1 : 0);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// ─── Step 0: Start API server ───────────────────────────────────────────────
|
|
184
|
-
async function startApi() {
|
|
185
|
-
log(`Starting API server on port ${PORT} with DB at ${DB_PATH}...`);
|
|
186
|
-
apiProc = Bun.spawn(["bun", "src/http.ts"], {
|
|
187
|
-
cwd: import.meta.dir + "/..",
|
|
188
|
-
env: {
|
|
189
|
-
...process.env,
|
|
190
|
-
PORT,
|
|
191
|
-
API_KEY,
|
|
192
|
-
DATABASE_PATH: DB_PATH,
|
|
193
|
-
SLACK_DISABLE: "true",
|
|
194
|
-
GITHUB_DISABLE: "true",
|
|
195
|
-
HEARTBEAT_DISABLE: "true",
|
|
196
|
-
// Prevent agentmail init from failing
|
|
197
|
-
AGENTMAIL_DISABLE: "true",
|
|
198
|
-
},
|
|
199
|
-
stdout: "pipe",
|
|
200
|
-
stderr: "pipe",
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
// Stream API logs in background (prefix with [API])
|
|
204
|
-
const decoder = new TextDecoder();
|
|
205
|
-
const pipeOutput = async (stream: ReadableStream<Uint8Array>, prefix: string) => {
|
|
206
|
-
const reader = stream.getReader();
|
|
207
|
-
let buffer = "";
|
|
208
|
-
try {
|
|
209
|
-
while (true) {
|
|
210
|
-
const { done, value } = await reader.read();
|
|
211
|
-
if (done) break;
|
|
212
|
-
buffer += decoder.decode(value, { stream: true });
|
|
213
|
-
const lines = buffer.split("\n");
|
|
214
|
-
buffer = lines.pop() || "";
|
|
215
|
-
for (const line of lines) {
|
|
216
|
-
if (line.trim()) console.log(` \x1b[90m${prefix}\x1b[0m ${line}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
} catch {
|
|
220
|
-
// stream closed
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
pipeOutput(apiProc.stdout, "[API]");
|
|
224
|
-
pipeOutput(apiProc.stderr, "[API]");
|
|
225
|
-
|
|
226
|
-
await waitForServer(BASE_URL);
|
|
227
|
-
log("API server is up.");
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// ─── Step 1: Build Docker ───────────────────────────────────────────────────
|
|
231
|
-
async function buildDocker() {
|
|
232
|
-
log("Building Docker worker image...");
|
|
233
|
-
const projectRoot = import.meta.dir + "/..";
|
|
234
|
-
await $`docker build -f Dockerfile.worker -t ${DOCKER_IMAGE} ${projectRoot}`;
|
|
235
|
-
log("Docker image built.");
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// ─── Step 2: Spawn lead + worker containers ─────────────────────────────────
|
|
239
|
-
async function spawnContainers() {
|
|
240
|
-
const projectRoot = import.meta.dir + "/..";
|
|
241
|
-
const envDockerPath = `${projectRoot}/.env.docker`;
|
|
242
|
-
|
|
243
|
-
// Lead
|
|
244
|
-
log(`Spawning lead container (${LEAD_CONTAINER}) on port ${LEAD_PORT}...`);
|
|
245
|
-
await $`docker run --rm -d \
|
|
246
|
-
--name ${LEAD_CONTAINER} \
|
|
247
|
-
--env-file ${envDockerPath} \
|
|
248
|
-
-e MCP_BASE_URL=http://host.docker.internal:${PORT} \
|
|
249
|
-
-e AGENT_ROLE=lead \
|
|
250
|
-
-e MAX_CONCURRENT_TASKS=1 \
|
|
251
|
-
-p ${String(LEAD_PORT)}:3000 \
|
|
252
|
-
${DOCKER_IMAGE}`.quiet();
|
|
253
|
-
dockerContainers.push(LEAD_CONTAINER);
|
|
254
|
-
log("Lead container started.");
|
|
255
|
-
|
|
256
|
-
// Worker
|
|
257
|
-
log(`Spawning worker container (${WORKER_CONTAINER}) on port ${WORKER_PORT}...`);
|
|
258
|
-
await $`docker run --rm -d \
|
|
259
|
-
--name ${WORKER_CONTAINER} \
|
|
260
|
-
--env-file ${envDockerPath} \
|
|
261
|
-
-e MCP_BASE_URL=http://host.docker.internal:${PORT} \
|
|
262
|
-
-e AGENT_ROLE=worker \
|
|
263
|
-
-e MAX_CONCURRENT_TASKS=1 \
|
|
264
|
-
-p ${String(WORKER_PORT)}:3000 \
|
|
265
|
-
${DOCKER_IMAGE}`.quiet();
|
|
266
|
-
dockerContainers.push(WORKER_CONTAINER);
|
|
267
|
-
log("Worker container started.");
|
|
268
|
-
|
|
269
|
-
// Wait a few seconds for containers to register with the API
|
|
270
|
-
log("Waiting for containers to register...");
|
|
271
|
-
await Bun.sleep(5000);
|
|
272
|
-
|
|
273
|
-
// Verify containers are running
|
|
274
|
-
const leadRunning =
|
|
275
|
-
(await $`docker inspect -f '{{.State.Running}}' ${LEAD_CONTAINER}`.quiet().text()).trim() ===
|
|
276
|
-
"true";
|
|
277
|
-
const workerRunning =
|
|
278
|
-
(await $`docker inspect -f '{{.State.Running}}' ${WORKER_CONTAINER}`.quiet().text()).trim() ===
|
|
279
|
-
"true";
|
|
280
|
-
assert(leadRunning, "Lead container is running");
|
|
281
|
-
assert(workerRunning, "Worker container is running");
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// ─── Workflow Definitions (configurable) ────────────────────────────────────
|
|
285
|
-
interface WorkflowScenario {
|
|
286
|
-
name: string;
|
|
287
|
-
definition: {
|
|
288
|
-
nodes: Array<{ id: string; type: string; config: Record<string, unknown> }>;
|
|
289
|
-
edges: Array<{ id: string; source: string; sourcePort: string; target: string }>;
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const SCENARIOS: Record<string, WorkflowScenario> = {
|
|
294
|
-
/** trigger-webhook → property-match (priority > 5) → create-task */
|
|
295
|
-
triagePriority: {
|
|
296
|
-
name: "e2e-triage-priority",
|
|
297
|
-
definition: {
|
|
298
|
-
nodes: [
|
|
299
|
-
{ id: "t1", type: "trigger-webhook", config: {} },
|
|
300
|
-
{
|
|
301
|
-
id: "pm1",
|
|
302
|
-
type: "property-match",
|
|
303
|
-
config: {
|
|
304
|
-
conditions: [{ field: "trigger.priority", op: "gt", value: 5 }],
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
id: "ct1",
|
|
309
|
-
type: "create-task",
|
|
310
|
-
config: { template: "High priority: {{trigger.title}}" },
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
edges: [
|
|
314
|
-
{ id: "e1", source: "t1", sourcePort: "default", target: "pm1" },
|
|
315
|
-
{ id: "e2", source: "pm1", sourcePort: "true", target: "ct1" },
|
|
316
|
-
],
|
|
317
|
-
},
|
|
318
|
-
},
|
|
319
|
-
|
|
320
|
-
/** trigger-webhook → code-match (checks tags) → create-task */
|
|
321
|
-
codeMatchTags: {
|
|
322
|
-
name: "e2e-code-match-tags",
|
|
323
|
-
definition: {
|
|
324
|
-
nodes: [
|
|
325
|
-
{ id: "t1", type: "trigger-webhook", config: {} },
|
|
326
|
-
{
|
|
327
|
-
id: "cm1",
|
|
328
|
-
type: "code-match",
|
|
329
|
-
config: {
|
|
330
|
-
code: "(input) => input.trigger.tags && input.trigger.tags.includes('urgent')",
|
|
331
|
-
outputPorts: ["true", "false"],
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
id: "ct1",
|
|
336
|
-
type: "create-task",
|
|
337
|
-
config: { template: "Urgent: {{trigger.title}}" },
|
|
338
|
-
},
|
|
339
|
-
],
|
|
340
|
-
edges: [
|
|
341
|
-
{ id: "e1", source: "t1", sourcePort: "default", target: "cm1" },
|
|
342
|
-
{ id: "e2", source: "cm1", sourcePort: "true", target: "ct1" },
|
|
343
|
-
],
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
|
|
347
|
-
/** trigger-webhook → code-match that throws → should fail */
|
|
348
|
-
failingCodeMatch: {
|
|
349
|
-
name: "e2e-failing-code-match",
|
|
350
|
-
definition: {
|
|
351
|
-
nodes: [
|
|
352
|
-
{ id: "t1", type: "trigger-webhook", config: {} },
|
|
353
|
-
{
|
|
354
|
-
id: "cm1",
|
|
355
|
-
type: "code-match",
|
|
356
|
-
config: {
|
|
357
|
-
code: '(input) => { throw new Error("intentional failure"); }',
|
|
358
|
-
outputPorts: ["true", "false"],
|
|
359
|
-
},
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
edges: [{ id: "e1", source: "t1", sourcePort: "default", target: "cm1" }],
|
|
363
|
-
},
|
|
364
|
-
},
|
|
365
|
-
|
|
366
|
-
/** trigger-webhook only (minimal, for edge-case tests) */
|
|
367
|
-
minimal: {
|
|
368
|
-
name: "e2e-minimal",
|
|
369
|
-
definition: {
|
|
370
|
-
nodes: [{ id: "t1", type: "trigger-webhook", config: {} }],
|
|
371
|
-
edges: [],
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
|
|
375
|
-
/** trigger-webhook → create-task (trivial task for worker E2E) */
|
|
376
|
-
workerE2E: {
|
|
377
|
-
name: "e2e-worker-flow",
|
|
378
|
-
definition: {
|
|
379
|
-
nodes: [
|
|
380
|
-
{ id: "t1", type: "trigger-webhook", config: {} },
|
|
381
|
-
{
|
|
382
|
-
id: "ct1",
|
|
383
|
-
type: "create-task",
|
|
384
|
-
config: {
|
|
385
|
-
template:
|
|
386
|
-
"Reply with ONLY the text 'E2E_OK' and nothing else. Do not use any tools. Just output E2E_OK.",
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
],
|
|
390
|
-
edges: [{ id: "e1", source: "t1", sourcePort: "default", target: "ct1" }],
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
};
|
|
394
|
-
|
|
395
|
-
// ─── Test Suites ────────────────────────────────────────────────────────────
|
|
396
|
-
|
|
397
|
-
interface WfJson {
|
|
398
|
-
id: string;
|
|
399
|
-
name: string;
|
|
400
|
-
enabled: boolean;
|
|
401
|
-
webhookSecret: string;
|
|
402
|
-
}
|
|
403
|
-
interface RunJson {
|
|
404
|
-
id: string;
|
|
405
|
-
runId: string;
|
|
406
|
-
}
|
|
407
|
-
interface RunDetailJson {
|
|
408
|
-
id: string;
|
|
409
|
-
status: string;
|
|
410
|
-
steps: Array<{
|
|
411
|
-
id: string;
|
|
412
|
-
nodeId: string;
|
|
413
|
-
status: string;
|
|
414
|
-
error?: string;
|
|
415
|
-
output?: unknown;
|
|
416
|
-
}>;
|
|
417
|
-
}
|
|
418
|
-
interface TaskJson {
|
|
419
|
-
id: string;
|
|
420
|
-
status: string;
|
|
421
|
-
task: string; // the task description field
|
|
422
|
-
source: string;
|
|
423
|
-
workflowRunId: string | null;
|
|
424
|
-
workflowRunStepId: string | null;
|
|
425
|
-
output?: string;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
async function testWorkflowCrud() {
|
|
429
|
-
log("Test: Workflow CRUD lifecycle");
|
|
430
|
-
|
|
431
|
-
// Create
|
|
432
|
-
const { status: createStatus, data: wf } = await api<WfJson>("POST", "/api/workflows", {
|
|
433
|
-
name: SCENARIOS.triagePriority.name,
|
|
434
|
-
definition: SCENARIOS.triagePriority.definition,
|
|
435
|
-
});
|
|
436
|
-
assertEq(createStatus, 201, "Create workflow returns 201");
|
|
437
|
-
assert(typeof wf.id === "string" && wf.id.length > 0, "Create workflow returns id");
|
|
438
|
-
assertEq(wf.name, SCENARIOS.triagePriority.name, "Create workflow returns correct name");
|
|
439
|
-
assertEq(wf.enabled, true, "Create workflow returns enabled=true");
|
|
440
|
-
|
|
441
|
-
// List
|
|
442
|
-
const { data: list } = await api<WfJson[]>("GET", "/api/workflows");
|
|
443
|
-
assert(
|
|
444
|
-
Array.isArray(list) && list.some((w) => w.id === wf.id),
|
|
445
|
-
"List workflows contains created workflow",
|
|
446
|
-
);
|
|
447
|
-
|
|
448
|
-
// Get single
|
|
449
|
-
const { status: getStatus, data: single } = await api<WfJson>(
|
|
450
|
-
"GET",
|
|
451
|
-
`/api/workflows/${wf.id}`,
|
|
452
|
-
);
|
|
453
|
-
assertEq(getStatus, 200, "Get workflow by id returns 200");
|
|
454
|
-
assertEq(single.name, SCENARIOS.triagePriority.name, "Get workflow returns correct name");
|
|
455
|
-
|
|
456
|
-
// Update
|
|
457
|
-
const { data: updated } = await api<WfJson>("PUT", `/api/workflows/${wf.id}`, {
|
|
458
|
-
name: "e2e-triage-updated",
|
|
459
|
-
});
|
|
460
|
-
assertEq(updated.name, "e2e-triage-updated", "Update workflow name");
|
|
461
|
-
|
|
462
|
-
// 404 on unknown
|
|
463
|
-
const { status: notFoundStatus } = await api("GET", "/api/workflows/nonexistent-id");
|
|
464
|
-
assertEq(notFoundStatus, 404, "GET unknown workflow returns 404");
|
|
465
|
-
|
|
466
|
-
return wf.id;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
async function testTriggerHighPriority(workflowId: string) {
|
|
470
|
-
log("Test: Trigger workflow (high priority → task created)");
|
|
471
|
-
|
|
472
|
-
const { status, data } = await api<RunJson>("POST", `/api/workflows/${workflowId}/trigger`, {
|
|
473
|
-
priority: 8,
|
|
474
|
-
title: "Server is on fire",
|
|
475
|
-
});
|
|
476
|
-
assertEq(status, 201, "Trigger returns 201");
|
|
477
|
-
assert(typeof data.runId === "string", "Trigger returns runId");
|
|
478
|
-
|
|
479
|
-
// Wait for async execution
|
|
480
|
-
await Bun.sleep(1000);
|
|
481
|
-
|
|
482
|
-
const { data: run } = await api<RunDetailJson>("GET", `/api/workflow-runs/${data.runId}`);
|
|
483
|
-
assert(Array.isArray(run.steps) && run.steps.length > 0, "Run has steps");
|
|
484
|
-
|
|
485
|
-
// All 3 nodes should be visited: trigger, property-match, create-task
|
|
486
|
-
assert(run.steps.length >= 3, `All 3 nodes executed (got ${run.steps.length} steps)`);
|
|
487
|
-
|
|
488
|
-
// Run should be waiting (create-task is async) or completed
|
|
489
|
-
assert(
|
|
490
|
-
run.status === "waiting" || run.status === "completed",
|
|
491
|
-
`Run status is waiting or completed (got ${run.status})`,
|
|
492
|
-
);
|
|
493
|
-
|
|
494
|
-
// Verify the create-task step actually created a real task
|
|
495
|
-
const ctStep = run.steps.find((s) => s.nodeId === "ct1");
|
|
496
|
-
const ctOutput = ctStep?.output as { taskId?: string } | undefined;
|
|
497
|
-
assert(!!ctOutput?.taskId, "create-task step output contains taskId");
|
|
498
|
-
|
|
499
|
-
if (ctOutput?.taskId) {
|
|
500
|
-
const { status: taskStatus, data: task } = await api<TaskJson>(
|
|
501
|
-
"GET",
|
|
502
|
-
`/api/tasks/${ctOutput.taskId}`,
|
|
503
|
-
);
|
|
504
|
-
assertEq(taskStatus, 200, "Created task exists in DB");
|
|
505
|
-
assertEq(task.source, "workflow", "Task source is 'workflow'");
|
|
506
|
-
assert(
|
|
507
|
-
task.task.includes("Server is on fire"),
|
|
508
|
-
`Task description interpolated correctly: "${task.task}"`,
|
|
509
|
-
);
|
|
510
|
-
assertEq(task.workflowRunId, data.runId, "Task linked to workflow run");
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// List runs
|
|
514
|
-
const { data: runs } = await api<Array<{ id: string }>>(
|
|
515
|
-
"GET",
|
|
516
|
-
`/api/workflows/${workflowId}/runs`,
|
|
517
|
-
);
|
|
518
|
-
assert(
|
|
519
|
-
Array.isArray(runs) && runs.some((r) => r.id === data.runId),
|
|
520
|
-
"List runs contains our run",
|
|
521
|
-
);
|
|
522
|
-
|
|
523
|
-
return data.runId;
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
async function testTriggerLowPriority(workflowId: string) {
|
|
527
|
-
log("Test: Trigger workflow (low priority → no task)");
|
|
528
|
-
|
|
529
|
-
const { data } = await api<RunJson>("POST", `/api/workflows/${workflowId}/trigger`, {
|
|
530
|
-
priority: 2,
|
|
531
|
-
title: "Minor typo",
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
await Bun.sleep(500);
|
|
535
|
-
|
|
536
|
-
const { data: run } = await api<RunDetailJson>("GET", `/api/workflow-runs/${data.runId}`);
|
|
537
|
-
assertEq(run.status, "completed", "Low priority run completes (no async nodes reached)");
|
|
538
|
-
|
|
539
|
-
// Only 2 steps: trigger + property-match (ct1 skipped because pm1 returned false)
|
|
540
|
-
assertEq(run.steps.length, 2, "Only 2 nodes executed (trigger, property-match — task skipped)");
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
async function testDisabledWorkflow(workflowId: string) {
|
|
544
|
-
log("Test: Disabled workflow returns 400");
|
|
545
|
-
|
|
546
|
-
await api("PUT", `/api/workflows/${workflowId}`, { enabled: false });
|
|
547
|
-
const { status } = await api("POST", `/api/workflows/${workflowId}/trigger`, {});
|
|
548
|
-
assertEq(status, 400, "Trigger disabled workflow returns 400");
|
|
549
|
-
|
|
550
|
-
// Re-enable
|
|
551
|
-
await api("PUT", `/api/workflows/${workflowId}`, { enabled: true });
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
async function testCodeMatch() {
|
|
555
|
-
log("Test: Code-match node execution");
|
|
556
|
-
|
|
557
|
-
// Create code-match workflow
|
|
558
|
-
const { data: wf } = await api<WfJson>("POST", "/api/workflows", {
|
|
559
|
-
name: SCENARIOS.codeMatchTags.name,
|
|
560
|
-
definition: SCENARIOS.codeMatchTags.definition,
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// Trigger with matching tags → should reach create-task
|
|
564
|
-
const { data: run1 } = await api<RunJson>("POST", `/api/workflows/${wf.id}/trigger`, {
|
|
565
|
-
tags: ["urgent", "bug"],
|
|
566
|
-
title: "DB connection leak",
|
|
567
|
-
});
|
|
568
|
-
await Bun.sleep(500);
|
|
569
|
-
const { data: detail1 } = await api<RunDetailJson>("GET", `/api/workflow-runs/${run1.runId}`);
|
|
570
|
-
assert(
|
|
571
|
-
detail1.steps.length >= 3,
|
|
572
|
-
`Code-match true: all 3 nodes executed (got ${detail1.steps.length})`,
|
|
573
|
-
);
|
|
574
|
-
|
|
575
|
-
// Trigger without matching tags → should stop at code-match
|
|
576
|
-
const { data: run2 } = await api<RunJson>("POST", `/api/workflows/${wf.id}/trigger`, {
|
|
577
|
-
tags: ["minor"],
|
|
578
|
-
title: "Typo in readme",
|
|
579
|
-
});
|
|
580
|
-
await Bun.sleep(500);
|
|
581
|
-
const { data: detail2 } = await api<RunDetailJson>("GET", `/api/workflow-runs/${run2.runId}`);
|
|
582
|
-
assertEq(
|
|
583
|
-
detail2.steps.length,
|
|
584
|
-
2,
|
|
585
|
-
"Code-match false: only 2 nodes executed (trigger, code-match — task skipped)",
|
|
586
|
-
);
|
|
587
|
-
assertEq(detail2.status, "completed", "Code-match false run completed");
|
|
588
|
-
|
|
589
|
-
// Cleanup
|
|
590
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
async function testFailedRunRetry() {
|
|
594
|
-
log("Test: Failed run and retry");
|
|
595
|
-
|
|
596
|
-
// Create workflow with code that throws
|
|
597
|
-
const { data: wf } = await api<WfJson>("POST", "/api/workflows", {
|
|
598
|
-
name: SCENARIOS.failingCodeMatch.name,
|
|
599
|
-
definition: SCENARIOS.failingCodeMatch.definition,
|
|
600
|
-
});
|
|
601
|
-
|
|
602
|
-
const { data: triggerData } = await api<RunJson>("POST", `/api/workflows/${wf.id}/trigger`, {});
|
|
603
|
-
await Bun.sleep(500);
|
|
604
|
-
|
|
605
|
-
const { data: run } = await api<RunDetailJson>(
|
|
606
|
-
"GET",
|
|
607
|
-
`/api/workflow-runs/${triggerData.runId}`,
|
|
608
|
-
);
|
|
609
|
-
assertEq(run.status, "failed", "Failing code-match marks run as failed");
|
|
610
|
-
|
|
611
|
-
// Retry endpoint should respond (it will fail again since the code still throws)
|
|
612
|
-
const { status: retryStatus } = await api(
|
|
613
|
-
"POST",
|
|
614
|
-
`/api/workflow-runs/${triggerData.runId}/retry`,
|
|
615
|
-
);
|
|
616
|
-
assert(
|
|
617
|
-
retryStatus === 200 || retryStatus === 400,
|
|
618
|
-
`Retry endpoint responds (status=${retryStatus})`,
|
|
619
|
-
);
|
|
620
|
-
|
|
621
|
-
// Cleanup
|
|
622
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
async function testWebhookSecretAuth() {
|
|
626
|
-
log("Test: Webhook secret auth");
|
|
627
|
-
|
|
628
|
-
// Create a minimal workflow to get its ID
|
|
629
|
-
const { data: wf } = await api<WfJson>("POST", "/api/workflows", {
|
|
630
|
-
name: `e2e-secret-auth-${Date.now()}`,
|
|
631
|
-
definition: SCENARIOS.minimal.definition,
|
|
632
|
-
});
|
|
633
|
-
|
|
634
|
-
// Trigger without X-Agent-ID and wrong secret → 401
|
|
635
|
-
const { status } = await fetchNoAgent(
|
|
636
|
-
"POST",
|
|
637
|
-
`/api/workflows/${wf.id}/trigger?secret=wrong-secret`,
|
|
638
|
-
{},
|
|
639
|
-
);
|
|
640
|
-
assertEq(status, 401, "Trigger without agentId and wrong secret returns 401");
|
|
641
|
-
|
|
642
|
-
// Cleanup
|
|
643
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
async function testDeleteWorkflow(workflowId: string) {
|
|
647
|
-
log("Test: Delete workflow");
|
|
648
|
-
|
|
649
|
-
const { status: delStatus } = await api("DELETE", `/api/workflows/${workflowId}`);
|
|
650
|
-
assertEq(delStatus, 204, "Delete workflow returns 204");
|
|
651
|
-
|
|
652
|
-
const { status: getStatus } = await api("GET", `/api/workflows/${workflowId}`);
|
|
653
|
-
assertEq(getStatus, 404, "Get deleted workflow returns 404");
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
async function testValidation() {
|
|
657
|
-
log("Test: Validation");
|
|
658
|
-
|
|
659
|
-
const { status } = await api("POST", "/api/workflows", {
|
|
660
|
-
name: "bad-workflow",
|
|
661
|
-
definition: { notValidAtAll: true },
|
|
662
|
-
});
|
|
663
|
-
assertEq(status, 400, "Invalid definition returns 400");
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/** Poll a resource until a predicate is true or timeout */
|
|
667
|
-
async function pollUntil<T>(
|
|
668
|
-
label: string,
|
|
669
|
-
fetchFn: () => Promise<T>,
|
|
670
|
-
predicate: (data: T) => boolean,
|
|
671
|
-
timeoutMs = 300_000,
|
|
672
|
-
intervalMs = 3_000,
|
|
673
|
-
): Promise<T | null> {
|
|
674
|
-
const start = Date.now();
|
|
675
|
-
while (Date.now() - start < timeoutMs) {
|
|
676
|
-
const data = await fetchFn();
|
|
677
|
-
if (predicate(data)) return data;
|
|
678
|
-
const elapsed = Math.round((Date.now() - start) / 1000);
|
|
679
|
-
process.stdout.write(`\r \x1b[90m... ${label} (${elapsed}s)\x1b[0m`);
|
|
680
|
-
await Bun.sleep(intervalMs);
|
|
681
|
-
}
|
|
682
|
-
process.stdout.write("\n");
|
|
683
|
-
return null;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Full end-to-end test: workflow engine → Docker worker → Claude runner → resume.
|
|
688
|
-
*
|
|
689
|
-
* Scenario: trigger-webhook → create-task (pre-assigned to worker agent)
|
|
690
|
-
*
|
|
691
|
-
* Verified flow:
|
|
692
|
-
* 1. Create workflow with a create-task node that generates a trivial Claude task
|
|
693
|
-
* 2. Trigger the workflow via HTTP — run enters "waiting" state
|
|
694
|
-
* 3. create-task node creates a real agent_task in the DB, assigned to a Docker worker
|
|
695
|
-
* 4. Docker worker picks up the task via /api/poll
|
|
696
|
-
* 5. Claude inside the container executes the task (outputs "E2E_OK")
|
|
697
|
-
* 6. Worker calls completeTask() → event bus emits "task.completed"
|
|
698
|
-
* 7. Resume listener (setupWorkflowResumeListener) catches the event
|
|
699
|
-
* 8. resumeFromTaskCompletion() marks the step completed and walks remaining DAG
|
|
700
|
-
* 9. Workflow run transitions from "waiting" → "completed"
|
|
701
|
-
*
|
|
702
|
-
* This is the most important test — it validates the entire async workflow lifecycle
|
|
703
|
-
* including the handoff between the workflow engine and the Claude worker runtime.
|
|
704
|
-
*/
|
|
705
|
-
async function testWorkerE2EFlow() {
|
|
706
|
-
log("Test: Full E2E — workflow → task → worker picks up → task completes → run resumes");
|
|
707
|
-
log(" (This test requires Docker workers and may take a few minutes)");
|
|
708
|
-
|
|
709
|
-
// 0. Find a registered worker agent to pre-assign the task to
|
|
710
|
-
const { data: agentsResp } = await api<{ agents: Array<{ id: string; role: string }> }>(
|
|
711
|
-
"GET",
|
|
712
|
-
"/api/agents",
|
|
713
|
-
);
|
|
714
|
-
const agents = agentsResp.agents ?? [];
|
|
715
|
-
const workerAgent = agents.find((a) => a.role === "worker") ?? agents[0];
|
|
716
|
-
assert(!!workerAgent, "Worker E2E: found registered agent for task assignment");
|
|
717
|
-
if (!workerAgent) return;
|
|
718
|
-
log(` Using agent ${workerAgent.id} (role=${workerAgent.role}) for task assignment`);
|
|
719
|
-
|
|
720
|
-
// 1. Create workflow with create-task pre-assigned to the worker agent
|
|
721
|
-
const workerDef = {
|
|
722
|
-
nodes: [
|
|
723
|
-
{ id: "t1", type: "trigger-webhook", config: {} },
|
|
724
|
-
{
|
|
725
|
-
id: "ct1",
|
|
726
|
-
type: "create-task",
|
|
727
|
-
config: {
|
|
728
|
-
template:
|
|
729
|
-
"Reply with ONLY the text 'E2E_OK' and nothing else. Do not use any tools. Just output E2E_OK.",
|
|
730
|
-
agentId: workerAgent.id,
|
|
731
|
-
},
|
|
732
|
-
},
|
|
733
|
-
],
|
|
734
|
-
edges: [{ id: "e1", source: "t1", sourcePort: "default", target: "ct1" }],
|
|
735
|
-
};
|
|
736
|
-
const { data: wf } = await api<WfJson>("POST", "/api/workflows", {
|
|
737
|
-
name: "e2e-worker-flow",
|
|
738
|
-
definition: workerDef,
|
|
739
|
-
});
|
|
740
|
-
assert(!!wf.id, "Worker E2E: workflow created");
|
|
741
|
-
|
|
742
|
-
// 2. Trigger the workflow
|
|
743
|
-
const { data: triggerResult } = await api<RunJson>(
|
|
744
|
-
"POST",
|
|
745
|
-
`/api/workflows/${wf.id}/trigger`,
|
|
746
|
-
{},
|
|
747
|
-
);
|
|
748
|
-
const runId = triggerResult.runId;
|
|
749
|
-
assert(!!runId, "Worker E2E: workflow triggered, got runId");
|
|
750
|
-
|
|
751
|
-
// 3. Wait a moment, then get the run to find the created task
|
|
752
|
-
await Bun.sleep(1500);
|
|
753
|
-
const { data: run } = await api<RunDetailJson>("GET", `/api/workflow-runs/${runId}`);
|
|
754
|
-
assertEq(run.status, "waiting", "Worker E2E: run is in 'waiting' state");
|
|
755
|
-
|
|
756
|
-
const ctStep = run.steps.find((s) => s.nodeId === "ct1");
|
|
757
|
-
const ctOutput = ctStep?.output as { taskId?: string } | undefined;
|
|
758
|
-
assert(!!ctOutput?.taskId, "Worker E2E: create-task produced a taskId");
|
|
759
|
-
const taskId = ctOutput?.taskId;
|
|
760
|
-
if (!taskId) {
|
|
761
|
-
logFail("Worker E2E", "No taskId — cannot continue worker flow test");
|
|
762
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// 4. Verify task exists
|
|
767
|
-
const { data: task } = await api<TaskJson>("GET", `/api/tasks/${taskId}`);
|
|
768
|
-
assert(
|
|
769
|
-
["pending", "in-progress", "in_progress", "unassigned"].includes(task.status),
|
|
770
|
-
`Worker E2E: task initial status is '${task.status}'`,
|
|
771
|
-
);
|
|
772
|
-
|
|
773
|
-
// 5. Poll for task completion (worker picks it up, Claude runs it)
|
|
774
|
-
log(" Waiting for worker to pick up and complete task...");
|
|
775
|
-
const completedTask = await pollUntil<TaskJson>(
|
|
776
|
-
"waiting for task completion",
|
|
777
|
-
async () => (await api<TaskJson>("GET", `/api/tasks/${taskId}`)).data,
|
|
778
|
-
(t) => t.status === "completed" || t.status === "failed" || t.status === "cancelled",
|
|
779
|
-
300_000, // 5 min timeout
|
|
780
|
-
5_000,
|
|
781
|
-
);
|
|
782
|
-
console.log(""); // clear the poll status line
|
|
783
|
-
|
|
784
|
-
if (!completedTask) {
|
|
785
|
-
logFail("Worker E2E: task completed within timeout", "Timed out after 5 minutes");
|
|
786
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
assertEq(completedTask.status, "completed", "Worker E2E: task completed successfully");
|
|
791
|
-
log(` Task output: "${(completedTask.output ?? "").slice(0, 100)}"`);
|
|
792
|
-
|
|
793
|
-
// 6. Give the event bus a moment to fire the resume
|
|
794
|
-
await Bun.sleep(2000);
|
|
795
|
-
|
|
796
|
-
// 7. Verify workflow run resumed and completed
|
|
797
|
-
const { data: finalRun } = await api<RunDetailJson>("GET", `/api/workflow-runs/${runId}`);
|
|
798
|
-
assertEq(finalRun.status, "completed", "Worker E2E: workflow run completed after task finished");
|
|
799
|
-
|
|
800
|
-
// Check all steps are completed
|
|
801
|
-
const allStepsCompleted = finalRun.steps.every((s) => s.status === "completed");
|
|
802
|
-
assert(allStepsCompleted, "Worker E2E: all workflow steps are completed");
|
|
803
|
-
|
|
804
|
-
// Cleanup
|
|
805
|
-
await api("DELETE", `/api/workflows/${wf.id}`);
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
// ─── Main ───────────────────────────────────────────────────────────────────
|
|
809
|
-
async function main() {
|
|
810
|
-
log("=== E2E Workflow Engine Test ===");
|
|
811
|
-
log(`PORT=${PORT} DB=${DB_PATH} DOCKER=${WITH_DOCKER}`);
|
|
812
|
-
|
|
813
|
-
try {
|
|
814
|
-
// Step 0: Start API
|
|
815
|
-
await startApi();
|
|
816
|
-
|
|
817
|
-
// Step 1+2: Docker (optional)
|
|
818
|
-
if (WITH_DOCKER) {
|
|
819
|
-
await buildDocker();
|
|
820
|
-
await spawnContainers();
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
// Step 3+4+5: Run test suites
|
|
824
|
-
const workflowId = await testWorkflowCrud();
|
|
825
|
-
await testTriggerHighPriority(workflowId);
|
|
826
|
-
await testTriggerLowPriority(workflowId);
|
|
827
|
-
await testDisabledWorkflow(workflowId);
|
|
828
|
-
await testCodeMatch();
|
|
829
|
-
await testFailedRunRetry();
|
|
830
|
-
await testWebhookSecretAuth();
|
|
831
|
-
await testValidation();
|
|
832
|
-
await testDeleteWorkflow(workflowId);
|
|
833
|
-
|
|
834
|
-
// Step 6: Full worker E2E (only with Docker)
|
|
835
|
-
if (WITH_DOCKER) {
|
|
836
|
-
await testWorkerE2EFlow();
|
|
837
|
-
} else {
|
|
838
|
-
log("Skipping worker E2E flow test (run with --with-docker to enable)");
|
|
839
|
-
}
|
|
840
|
-
} catch (err) {
|
|
841
|
-
logFail("UNEXPECTED ERROR", String(err));
|
|
842
|
-
console.error(err);
|
|
843
|
-
} finally {
|
|
844
|
-
// Step 6: Cleanup
|
|
845
|
-
await cleanup();
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
// Summary
|
|
849
|
-
console.log("");
|
|
850
|
-
log("=========================================");
|
|
851
|
-
log(`Results: ${pass} passed, ${fail} failed`);
|
|
852
|
-
log("=========================================");
|
|
853
|
-
|
|
854
|
-
process.exit(fail > 0 ? 1 : 0);
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
main();
|