@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,820 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Docker-based E2E Provider Test
|
|
4
|
-
*
|
|
5
|
-
* Builds the Docker worker image, starts a local API server with a clean DB,
|
|
6
|
-
* registers agents, creates tasks, and runs Docker workers against them.
|
|
7
|
-
* Tests both Claude and pi-mono providers with the same image.
|
|
8
|
-
*
|
|
9
|
-
* Test scenarios (each can be run independently via --test flag):
|
|
10
|
-
* 1. basic — Worker picks up task, completes it, cost + session recorded
|
|
11
|
-
* 2. cancel — Cancel a task mid-execution, verify tool_call is blocked
|
|
12
|
-
* 3. resume — Pause a task, restart worker, verify it resumes
|
|
13
|
-
* 4. tool-loop — Feed a task that triggers tool loop detection
|
|
14
|
-
* 5. summarize — Verify session summarization produces a memory entry
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* bun scripts/e2e-docker-provider.ts --provider claude
|
|
18
|
-
* bun scripts/e2e-docker-provider.ts --provider pi
|
|
19
|
-
* bun scripts/e2e-docker-provider.ts --provider both
|
|
20
|
-
* bun scripts/e2e-docker-provider.ts --provider claude --test basic
|
|
21
|
-
* bun scripts/e2e-docker-provider.ts --provider claude --test basic,cancel
|
|
22
|
-
* E2E_PORT=13099 bun scripts/e2e-docker-provider.ts --provider both --test basic
|
|
23
|
-
*/
|
|
24
|
-
import { $, type Subprocess } from "bun";
|
|
25
|
-
|
|
26
|
-
// ─── Arg Parsing ─────────────────────────────────────────────────────────────
|
|
27
|
-
function parseArg(name: string, fallback: string): string {
|
|
28
|
-
const flag = process.argv.find((a) => a.startsWith(`--${name}=`));
|
|
29
|
-
if (flag) return flag.split("=")[1]!;
|
|
30
|
-
const idx = process.argv.indexOf(`--${name}`);
|
|
31
|
-
if (idx >= 0 && process.argv[idx + 1]) return process.argv[idx + 1]!;
|
|
32
|
-
return fallback;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// ─── Config ──────────────────────────────────────────────────────────────────
|
|
36
|
-
const PORT = process.env.E2E_PORT || "13099";
|
|
37
|
-
const API_KEY = process.env.API_KEY || "123123";
|
|
38
|
-
const BASE_URL = `http://localhost:${PORT}`;
|
|
39
|
-
const DB_PATH = `/tmp/e2e-docker-${Date.now()}.sqlite`;
|
|
40
|
-
const DOCKER_IMAGE = "agent-swarm-worker:e2e";
|
|
41
|
-
const CONTAINER_PREFIX = "e2e-docker";
|
|
42
|
-
const WORKER_PORT_BASE = 13200; // Docker host port for workers
|
|
43
|
-
|
|
44
|
-
const PROVIDER_ARG = parseArg("provider", "claude");
|
|
45
|
-
const TEST_ARG = parseArg("test", "all");
|
|
46
|
-
const SKIP_BUILD = process.argv.includes("--skip-build");
|
|
47
|
-
const TIMEOUT_MS = Number.parseInt(process.env.E2E_TIMEOUT || "120000", 10); // 2 min per test
|
|
48
|
-
|
|
49
|
-
if (!["claude", "pi", "both"].includes(PROVIDER_ARG)) {
|
|
50
|
-
console.error(`Invalid --provider: ${PROVIDER_ARG}. Supported: claude, pi, both`);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const PROVIDERS = PROVIDER_ARG === "both" ? ["claude", "pi"] : [PROVIDER_ARG];
|
|
55
|
-
const ALL_TESTS = ["basic", "cancel", "resume", "tool-loop", "summarize"];
|
|
56
|
-
const TESTS_TO_RUN = TEST_ARG === "all" ? ALL_TESTS : TEST_ARG.split(",");
|
|
57
|
-
|
|
58
|
-
for (const t of TESTS_TO_RUN) {
|
|
59
|
-
if (!ALL_TESTS.includes(t)) {
|
|
60
|
-
console.error(`Unknown test: ${t}. Available: ${ALL_TESTS.join(", ")}`);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ─── State ───────────────────────────────────────────────────────────────────
|
|
66
|
-
let apiProc: Subprocess | null = null;
|
|
67
|
-
const activeContainers: string[] = [];
|
|
68
|
-
let pass = 0;
|
|
69
|
-
let fail = 0;
|
|
70
|
-
let skip = 0;
|
|
71
|
-
let cleaningUp = false;
|
|
72
|
-
|
|
73
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
74
|
-
function log(msg: string) {
|
|
75
|
-
console.log(`\x1b[1;36m[E2E-Docker]\x1b[0m ${msg}`);
|
|
76
|
-
}
|
|
77
|
-
function logPass(name: string) {
|
|
78
|
-
console.log(`\x1b[1;32m ✓ PASS\x1b[0m ${name}`);
|
|
79
|
-
pass++;
|
|
80
|
-
}
|
|
81
|
-
function logFail(name: string, detail: string) {
|
|
82
|
-
console.log(`\x1b[1;31m ✗ FAIL\x1b[0m ${name}: ${detail}`);
|
|
83
|
-
fail++;
|
|
84
|
-
}
|
|
85
|
-
function logSkip(name: string, reason: string) {
|
|
86
|
-
console.log(`\x1b[1;33m ○ SKIP\x1b[0m ${name}: ${reason}`);
|
|
87
|
-
skip++;
|
|
88
|
-
}
|
|
89
|
-
function logSection(title: string) {
|
|
90
|
-
console.log(`\n\x1b[1;35m═══ ${title} ═══\x1b[0m`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async function api(method: string, path: string, body?: unknown, agentId?: string) {
|
|
94
|
-
const headers: Record<string, string> = {
|
|
95
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
96
|
-
"Content-Type": "application/json",
|
|
97
|
-
};
|
|
98
|
-
if (agentId) headers["X-Agent-ID"] = agentId;
|
|
99
|
-
|
|
100
|
-
const resp = await fetch(`${BASE_URL}${path}`, {
|
|
101
|
-
method,
|
|
102
|
-
headers,
|
|
103
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
104
|
-
});
|
|
105
|
-
if (!resp.ok) {
|
|
106
|
-
const text = await resp.text();
|
|
107
|
-
throw new Error(`API ${method} ${path} returned ${resp.status}: ${text}`);
|
|
108
|
-
}
|
|
109
|
-
return resp.json();
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function apiSafe(method: string, path: string, body?: unknown, agentId?: string) {
|
|
113
|
-
try {
|
|
114
|
-
return await api(method, path, body, agentId);
|
|
115
|
-
} catch (err) {
|
|
116
|
-
return { error: String(err) };
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function waitForApi(maxWaitMs = 15000): Promise<boolean> {
|
|
121
|
-
const start = Date.now();
|
|
122
|
-
while (Date.now() - start < maxWaitMs) {
|
|
123
|
-
try {
|
|
124
|
-
const resp = await fetch(`${BASE_URL}/health`);
|
|
125
|
-
if (resp.ok) return true;
|
|
126
|
-
} catch {}
|
|
127
|
-
await Bun.sleep(500);
|
|
128
|
-
}
|
|
129
|
-
return false;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** Poll a condition until it's true or timeout */
|
|
133
|
-
async function pollUntil(
|
|
134
|
-
fn: () => Promise<boolean>,
|
|
135
|
-
timeoutMs: number,
|
|
136
|
-
intervalMs = 2000,
|
|
137
|
-
): Promise<boolean> {
|
|
138
|
-
const start = Date.now();
|
|
139
|
-
while (Date.now() - start < timeoutMs) {
|
|
140
|
-
if (await fn()) return true;
|
|
141
|
-
await Bun.sleep(intervalMs);
|
|
142
|
-
}
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Start a Docker worker container for the given provider */
|
|
147
|
-
async function startWorker(
|
|
148
|
-
provider: string,
|
|
149
|
-
agentId: string,
|
|
150
|
-
hostPort: number,
|
|
151
|
-
extraEnv: Record<string, string> = {},
|
|
152
|
-
): Promise<string> {
|
|
153
|
-
const containerName = `${CONTAINER_PREFIX}-${provider}-${Date.now()}`;
|
|
154
|
-
|
|
155
|
-
const envFlags: string[] = [
|
|
156
|
-
"-e",
|
|
157
|
-
`HARNESS_PROVIDER=${provider}`,
|
|
158
|
-
"-e",
|
|
159
|
-
`API_KEY=${API_KEY}`,
|
|
160
|
-
"-e",
|
|
161
|
-
`AGENT_ID=${agentId}`,
|
|
162
|
-
"-e",
|
|
163
|
-
`MCP_BASE_URL=http://host.docker.internal:${PORT}`,
|
|
164
|
-
"-e",
|
|
165
|
-
"AGENT_ROLE=worker",
|
|
166
|
-
"-e",
|
|
167
|
-
"YOLO=true",
|
|
168
|
-
"-e",
|
|
169
|
-
"STARTUP_SCRIPT_STRICT=false",
|
|
170
|
-
"-e",
|
|
171
|
-
"SLACK_DISABLE=true",
|
|
172
|
-
"-e",
|
|
173
|
-
"GITHUB_DISABLE=true",
|
|
174
|
-
"-e",
|
|
175
|
-
"MAX_CONCURRENT_TASKS=1",
|
|
176
|
-
];
|
|
177
|
-
|
|
178
|
-
// Provider-specific auth
|
|
179
|
-
if (provider === "claude") {
|
|
180
|
-
const token = process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
181
|
-
if (!token) throw new Error("CLAUDE_CODE_OAUTH_TOKEN required for claude provider");
|
|
182
|
-
envFlags.push("-e", `CLAUDE_CODE_OAUTH_TOKEN=${token}`);
|
|
183
|
-
} else if (provider === "pi") {
|
|
184
|
-
// pi-mono can use OPENROUTER_API_KEY or ANTHROPIC_API_KEY
|
|
185
|
-
const orKey = process.env.OPENROUTER_API_KEY;
|
|
186
|
-
const antKey = process.env.ANTHROPIC_API_KEY;
|
|
187
|
-
if (orKey) envFlags.push("-e", `OPENROUTER_API_KEY=${orKey}`);
|
|
188
|
-
if (antKey) envFlags.push("-e", `ANTHROPIC_API_KEY=${antKey}`);
|
|
189
|
-
if (!orKey && !antKey)
|
|
190
|
-
throw new Error("OPENROUTER_API_KEY or ANTHROPIC_API_KEY required for pi provider");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
for (const [k, v] of Object.entries(extraEnv)) {
|
|
194
|
-
envFlags.push("-e", `${k}=${v}`);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const args = [
|
|
198
|
-
"docker",
|
|
199
|
-
"run",
|
|
200
|
-
"--rm",
|
|
201
|
-
"-d",
|
|
202
|
-
"--name",
|
|
203
|
-
containerName,
|
|
204
|
-
...envFlags,
|
|
205
|
-
"-p",
|
|
206
|
-
`${hostPort}:3000`,
|
|
207
|
-
DOCKER_IMAGE,
|
|
208
|
-
];
|
|
209
|
-
|
|
210
|
-
const result = await $`${args}`.text();
|
|
211
|
-
const containerId = result.trim().slice(0, 12);
|
|
212
|
-
activeContainers.push(containerName);
|
|
213
|
-
log(`Started container ${containerName} (${containerId}) on port ${hostPort}`);
|
|
214
|
-
return containerName;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/** Get Docker container logs */
|
|
218
|
-
async function containerLogs(name: string, tail = 50): Promise<string> {
|
|
219
|
-
try {
|
|
220
|
-
return await $`docker logs --tail ${tail} ${name} 2>&1`.text();
|
|
221
|
-
} catch {
|
|
222
|
-
return "<no logs available>";
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
/** Stop and remove a Docker container */
|
|
227
|
-
async function stopContainer(name: string): Promise<void> {
|
|
228
|
-
try {
|
|
229
|
-
await $`docker stop -t 5 ${name} 2>/dev/null`.quiet();
|
|
230
|
-
} catch {}
|
|
231
|
-
const idx = activeContainers.indexOf(name);
|
|
232
|
-
if (idx >= 0) activeContainers.splice(idx, 1);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async function cleanup() {
|
|
236
|
-
if (cleaningUp) return;
|
|
237
|
-
cleaningUp = true;
|
|
238
|
-
log("Cleaning up...");
|
|
239
|
-
|
|
240
|
-
// Stop all containers
|
|
241
|
-
for (const name of [...activeContainers]) {
|
|
242
|
-
await stopContainer(name);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Stop API server
|
|
246
|
-
if (apiProc) {
|
|
247
|
-
apiProc.kill("SIGTERM");
|
|
248
|
-
await apiProc.exited.catch(() => {});
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Remove temp DB files
|
|
252
|
-
for (const suffix of ["", "-wal", "-shm"]) {
|
|
253
|
-
try {
|
|
254
|
-
await $`rm -f ${DB_PATH}${suffix}`.quiet();
|
|
255
|
-
} catch {}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
process.on("SIGINT", async () => {
|
|
260
|
-
await cleanup();
|
|
261
|
-
process.exit(1);
|
|
262
|
-
});
|
|
263
|
-
process.on("SIGTERM", async () => {
|
|
264
|
-
await cleanup();
|
|
265
|
-
process.exit(1);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// ─── Test Implementations ────────────────────────────────────────────────────
|
|
269
|
-
|
|
270
|
-
/** Test: basic — Worker picks up task, completes, cost + session recorded */
|
|
271
|
-
async function testBasic(provider: string, portOffset: number) {
|
|
272
|
-
const testName = `[${provider}] basic`;
|
|
273
|
-
logSection(`${testName}: Task completion + cost recording`);
|
|
274
|
-
|
|
275
|
-
// Register agent
|
|
276
|
-
const joinResult = await api("POST", "/api/agents", {
|
|
277
|
-
name: `e2e-${provider}-basic`,
|
|
278
|
-
role: "worker",
|
|
279
|
-
status: "online",
|
|
280
|
-
harnessProvider: provider,
|
|
281
|
-
});
|
|
282
|
-
const agentId = joinResult?.agent?.id || joinResult?.id || joinResult?.id;
|
|
283
|
-
if (!agentId) {
|
|
284
|
-
logFail(testName, `Agent registration failed: ${JSON.stringify(joinResult)}`);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
logPass(`${testName}: Agent registered ${agentId.slice(0, 8)}`);
|
|
288
|
-
|
|
289
|
-
// Create task
|
|
290
|
-
const taskResult = await api("POST", "/api/tasks", {
|
|
291
|
-
task: "Say 'hello world' and nothing else. Do not use any tools. Just output text.",
|
|
292
|
-
agentId,
|
|
293
|
-
source: "api",
|
|
294
|
-
});
|
|
295
|
-
const taskId = taskResult?.task?.id || taskResult?.id || taskResult?.id;
|
|
296
|
-
if (!taskId) {
|
|
297
|
-
logFail(testName, `Task creation failed: ${JSON.stringify(taskResult)}`);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
logPass(`${testName}: Task created ${taskId.slice(0, 8)}`);
|
|
301
|
-
|
|
302
|
-
// Start worker container
|
|
303
|
-
const hostPort = WORKER_PORT_BASE + portOffset;
|
|
304
|
-
let containerName: string;
|
|
305
|
-
try {
|
|
306
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
307
|
-
} catch (err) {
|
|
308
|
-
logFail(testName, `Container start failed: ${err}`);
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// Poll until task completes or timeout
|
|
313
|
-
const completed = await pollUntil(async () => {
|
|
314
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
315
|
-
const status = details?.task?.status || details?.status;
|
|
316
|
-
if (status === "completed" || status === "failed") return true;
|
|
317
|
-
return false;
|
|
318
|
-
}, TIMEOUT_MS);
|
|
319
|
-
|
|
320
|
-
// Check results
|
|
321
|
-
const finalTask = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
322
|
-
const status = finalTask?.task?.status || finalTask?.status;
|
|
323
|
-
|
|
324
|
-
if (!completed) {
|
|
325
|
-
const logs = await containerLogs(containerName);
|
|
326
|
-
logFail(
|
|
327
|
-
testName,
|
|
328
|
-
`Timed out after ${TIMEOUT_MS / 1000}s. Status: ${status}\nLast logs:\n${logs}`,
|
|
329
|
-
);
|
|
330
|
-
await stopContainer(containerName);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
if (status === "completed") {
|
|
335
|
-
logPass(`${testName}: Task completed`);
|
|
336
|
-
} else {
|
|
337
|
-
logFail(testName, `Task status: ${status} (expected completed)`);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Check session ID
|
|
341
|
-
const sessionId = finalTask?.task?.claudeSessionId || finalTask?.claudeSessionId;
|
|
342
|
-
if (sessionId) {
|
|
343
|
-
logPass(`${testName}: Session ID recorded (${sessionId.slice(0, 8)})`);
|
|
344
|
-
} else {
|
|
345
|
-
logFail(testName, "No session ID on task");
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
// Wait for runner to finish post-completion work (cost save, log flush, etc.)
|
|
349
|
-
// The task may be marked "completed" by hooks before the runner's
|
|
350
|
-
// waitForCompletion() resolves and saves cost data.
|
|
351
|
-
await Bun.sleep(10000);
|
|
352
|
-
|
|
353
|
-
// Grab container logs before checking cost (for diagnostics)
|
|
354
|
-
const workerLogs = await containerLogs(containerName, 200);
|
|
355
|
-
const diagLines = workerLogs
|
|
356
|
-
.split("\n")
|
|
357
|
-
.filter(
|
|
358
|
-
(l) =>
|
|
359
|
-
l.includes("cost") ||
|
|
360
|
-
l.includes("Cost") ||
|
|
361
|
-
l.includes("result") ||
|
|
362
|
-
l.includes("save") ||
|
|
363
|
-
l.includes("[runner]") ||
|
|
364
|
-
l.includes("exit"),
|
|
365
|
-
);
|
|
366
|
-
if (diagLines.length > 0) {
|
|
367
|
-
log(`${testName}: Diagnostic log lines:\n${diagLines.join("\n")}`);
|
|
368
|
-
} else {
|
|
369
|
-
log(`${testName}: DIAG: No diagnostic lines found. Total log lines: ${workerLogs.split("\n").length}`);
|
|
370
|
-
// Show last 20 lines for debugging
|
|
371
|
-
const lastLines = workerLogs.split("\n").slice(-20).join("\n");
|
|
372
|
-
log(`${testName}: Last 20 lines:\n${lastLines}`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Poll for cost data (may take time if Stop hook runs summarization)
|
|
376
|
-
let costEntries: Array<{ totalCostUsd: number }> = [];
|
|
377
|
-
const costFound = await pollUntil(async () => {
|
|
378
|
-
const costs = await apiSafe("GET", `/api/session-costs?agentId=${agentId}`);
|
|
379
|
-
costEntries = costs?.costs || [];
|
|
380
|
-
if (costEntries.length > 0) return true;
|
|
381
|
-
// Also check unfiltered
|
|
382
|
-
const allCosts = await apiSafe("GET", "/api/session-costs");
|
|
383
|
-
const allEntries = allCosts?.costs || [];
|
|
384
|
-
if (allEntries.length > 0) {
|
|
385
|
-
log(
|
|
386
|
-
`${testName}: DIAG: No costs for agentId=${agentId}, but ${allEntries.length} total. Sample: ${JSON.stringify(allEntries[0])}`,
|
|
387
|
-
);
|
|
388
|
-
return true; // Cost exists, just wrong agentId filter
|
|
389
|
-
}
|
|
390
|
-
return false;
|
|
391
|
-
}, 30000, 3000);
|
|
392
|
-
|
|
393
|
-
if (costEntries.length > 0) {
|
|
394
|
-
const total = costEntries.reduce(
|
|
395
|
-
(sum: number, c: { totalCostUsd: number }) => sum + c.totalCostUsd,
|
|
396
|
-
0,
|
|
397
|
-
);
|
|
398
|
-
logPass(`${testName}: Cost recorded ($${total.toFixed(4)}, ${costEntries.length} entries)`);
|
|
399
|
-
} else if (costFound) {
|
|
400
|
-
logSkip(`${testName}: Cost data`, "Costs exist but not linked to this agentId");
|
|
401
|
-
} else {
|
|
402
|
-
logFail(testName, "No cost data recorded (polled for 30s)");
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Check session logs
|
|
406
|
-
const logs = await apiSafe("GET", `/api/session-logs?taskId=${taskId}`);
|
|
407
|
-
const logEntries = logs?.logs || [];
|
|
408
|
-
if (logEntries.length > 0) {
|
|
409
|
-
logPass(`${testName}: Session logs recorded (${logEntries.length} entries)`);
|
|
410
|
-
} else {
|
|
411
|
-
logSkip(`${testName}: Session logs`, "No log entries (may be expected for trivial tasks)");
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
await stopContainer(containerName);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/** Test: cancel — Cancel a task mid-execution, verify blocking */
|
|
418
|
-
async function testCancel(provider: string, portOffset: number) {
|
|
419
|
-
const testName = `[${provider}] cancel`;
|
|
420
|
-
logSection(`${testName}: Task cancellation mid-execution`);
|
|
421
|
-
|
|
422
|
-
// Register agent
|
|
423
|
-
const joinResult = await api("POST", "/api/agents", {
|
|
424
|
-
name: `e2e-${provider}-cancel`,
|
|
425
|
-
role: "worker",
|
|
426
|
-
status: "online",
|
|
427
|
-
harnessProvider: provider,
|
|
428
|
-
});
|
|
429
|
-
const agentId = joinResult?.agent?.id || joinResult?.id;
|
|
430
|
-
if (!agentId) {
|
|
431
|
-
logFail(testName, `Agent registration failed`);
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Create a task that takes a while (uses tools, so cancellation hook fires)
|
|
436
|
-
const taskResult = await api("POST", "/api/tasks", {
|
|
437
|
-
task: "List all files in the current directory recursively. Then create a file called 'test-output.txt' with the listing.",
|
|
438
|
-
agentId,
|
|
439
|
-
source: "api",
|
|
440
|
-
});
|
|
441
|
-
const taskId = taskResult?.task?.id || taskResult?.id;
|
|
442
|
-
if (!taskId) {
|
|
443
|
-
logFail(testName, `Task creation failed`);
|
|
444
|
-
return;
|
|
445
|
-
}
|
|
446
|
-
logPass(`${testName}: Task created ${taskId.slice(0, 8)}`);
|
|
447
|
-
|
|
448
|
-
// Start worker
|
|
449
|
-
const hostPort = WORKER_PORT_BASE + portOffset;
|
|
450
|
-
let containerName: string;
|
|
451
|
-
try {
|
|
452
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
453
|
-
} catch (err) {
|
|
454
|
-
logFail(testName, `Container start failed: ${err}`);
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Wait for task to start (status changes from pending/assigned to in_progress)
|
|
459
|
-
const started = await pollUntil(async () => {
|
|
460
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
461
|
-
return details?.task?.status || details?.status === "in_progress";
|
|
462
|
-
}, 30000);
|
|
463
|
-
|
|
464
|
-
if (!started) {
|
|
465
|
-
// Even if not in_progress, try cancelling anyway after a delay
|
|
466
|
-
await Bun.sleep(10000);
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Cancel the task
|
|
470
|
-
log(`${testName}: Cancelling task...`);
|
|
471
|
-
await apiSafe("POST", `/api/tasks/${taskId}/cancel`, { reason: "E2E cancellation test" });
|
|
472
|
-
|
|
473
|
-
// Poll until task reaches cancelled/completed/failed state
|
|
474
|
-
const finished = await pollUntil(async () => {
|
|
475
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
476
|
-
const s = details?.task?.status || details?.status;
|
|
477
|
-
return s === "cancelled" || s === "completed" || s === "failed";
|
|
478
|
-
}, TIMEOUT_MS);
|
|
479
|
-
|
|
480
|
-
const finalTask = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
481
|
-
const finalStatus = finalTask?.task?.status || finalTask?.status;
|
|
482
|
-
|
|
483
|
-
if (finalStatus === "cancelled") {
|
|
484
|
-
logPass(`${testName}: Task cancelled successfully`);
|
|
485
|
-
} else if (finished) {
|
|
486
|
-
logFail(testName, `Task ended with status: ${finalStatus} (expected cancelled)`);
|
|
487
|
-
} else {
|
|
488
|
-
logFail(testName, `Timed out waiting for cancellation. Status: ${finalStatus}`);
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
await stopContainer(containerName);
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
/** Test: resume — Pause a task, restart worker, verify it resumes */
|
|
495
|
-
async function testResume(provider: string, portOffset: number) {
|
|
496
|
-
const testName = `[${provider}] resume`;
|
|
497
|
-
logSection(`${testName}: Session resume after restart`);
|
|
498
|
-
|
|
499
|
-
// Register agent
|
|
500
|
-
const joinResult = await api("POST", "/api/agents", {
|
|
501
|
-
name: `e2e-${provider}-resume`,
|
|
502
|
-
role: "worker",
|
|
503
|
-
status: "online",
|
|
504
|
-
harnessProvider: provider,
|
|
505
|
-
});
|
|
506
|
-
const agentId = joinResult?.agent?.id || joinResult?.id;
|
|
507
|
-
if (!agentId) {
|
|
508
|
-
logFail(testName, `Agent registration failed`);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Create a multi-step task
|
|
513
|
-
const taskResult = await api("POST", "/api/tasks", {
|
|
514
|
-
task: "Step 1: Create a file /workspace/resume-test-1.txt with content 'step1'. Step 2: Create a file /workspace/resume-test-2.txt with content 'step2'. Step 3: Create a file /workspace/resume-test-3.txt with content 'step3'.",
|
|
515
|
-
agentId,
|
|
516
|
-
source: "api",
|
|
517
|
-
});
|
|
518
|
-
const taskId = taskResult?.task?.id || taskResult?.id;
|
|
519
|
-
if (!taskId) {
|
|
520
|
-
logFail(testName, `Task creation failed`);
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
logPass(`${testName}: Task created ${taskId.slice(0, 8)}`);
|
|
524
|
-
|
|
525
|
-
// Start worker
|
|
526
|
-
const hostPort = WORKER_PORT_BASE + portOffset;
|
|
527
|
-
let containerName: string;
|
|
528
|
-
try {
|
|
529
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
530
|
-
} catch (err) {
|
|
531
|
-
logFail(testName, `Container start failed: ${err}`);
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
// Wait for task to start
|
|
536
|
-
const started = await pollUntil(async () => {
|
|
537
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
538
|
-
return details?.task?.status || details?.status === "in_progress";
|
|
539
|
-
}, 30000);
|
|
540
|
-
|
|
541
|
-
if (!started) {
|
|
542
|
-
logFail(testName, "Task never reached in_progress");
|
|
543
|
-
await stopContainer(containerName);
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Wait a bit, then kill the container abruptly
|
|
548
|
-
await Bun.sleep(5000);
|
|
549
|
-
const taskBefore = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
550
|
-
const sessionIdBefore = taskBefore?.task?.claudeSessionId || taskBefore?.claudeSessionId;
|
|
551
|
-
log(`${testName}: Session ID before restart: ${sessionIdBefore?.slice(0, 8) || "none"}`);
|
|
552
|
-
|
|
553
|
-
// Kill container (simulates crash)
|
|
554
|
-
await stopContainer(containerName);
|
|
555
|
-
log(`${testName}: Container killed. Restarting...`);
|
|
556
|
-
await Bun.sleep(2000);
|
|
557
|
-
|
|
558
|
-
// Reset task to assigned so worker picks it up again
|
|
559
|
-
// The runner should detect the existing sessionId and --resume
|
|
560
|
-
await apiSafe("POST", `/api/tasks/${taskId}/reassign`, { assigneeId: agentId });
|
|
561
|
-
|
|
562
|
-
// Start new worker
|
|
563
|
-
try {
|
|
564
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
565
|
-
} catch (err) {
|
|
566
|
-
logFail(testName, `Container restart failed: ${err}`);
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Wait for task to complete
|
|
571
|
-
const completed = await pollUntil(async () => {
|
|
572
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
573
|
-
const s = details?.task?.status || details?.status;
|
|
574
|
-
return s === "completed" || s === "failed";
|
|
575
|
-
}, TIMEOUT_MS);
|
|
576
|
-
|
|
577
|
-
const finalTask = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
578
|
-
const finalStatus = finalTask?.task?.status || finalTask?.status;
|
|
579
|
-
const sessionIdAfter = finalTask?.task?.claudeSessionId || finalTask?.claudeSessionId;
|
|
580
|
-
|
|
581
|
-
if (completed && finalStatus === "completed") {
|
|
582
|
-
logPass(`${testName}: Task completed after resume`);
|
|
583
|
-
} else {
|
|
584
|
-
logFail(testName, `Task status: ${finalStatus}`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Check if session ID changed (it may or may not, depending on resume support)
|
|
588
|
-
if (sessionIdBefore && sessionIdAfter) {
|
|
589
|
-
if (sessionIdBefore === sessionIdAfter) {
|
|
590
|
-
logPass(`${testName}: Same session ID — resume worked`);
|
|
591
|
-
} else {
|
|
592
|
-
logSkip(
|
|
593
|
-
`${testName}: Session ID changed`,
|
|
594
|
-
"Provider may not support resume or session was stale",
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
await stopContainer(containerName);
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/** Test: tool-loop — Trigger tool loop detection */
|
|
603
|
-
async function testToolLoop(provider: string, portOffset: number) {
|
|
604
|
-
const testName = `[${provider}] tool-loop`;
|
|
605
|
-
logSection(`${testName}: Tool loop detection`);
|
|
606
|
-
|
|
607
|
-
// Register agent
|
|
608
|
-
const joinResult = await api("POST", "/api/agents", {
|
|
609
|
-
name: `e2e-${provider}-loop`,
|
|
610
|
-
role: "worker",
|
|
611
|
-
status: "online",
|
|
612
|
-
harnessProvider: provider,
|
|
613
|
-
});
|
|
614
|
-
const agentId = joinResult?.agent?.id || joinResult?.id;
|
|
615
|
-
if (!agentId) {
|
|
616
|
-
logFail(testName, `Agent registration failed`);
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
// Create a task likely to trigger tool loops
|
|
621
|
-
const taskResult = await api("POST", "/api/tasks", {
|
|
622
|
-
task: "Read the file /workspace/nonexistent-file.txt and tell me its contents. Keep trying until you succeed. Do not give up.",
|
|
623
|
-
agentId,
|
|
624
|
-
source: "api",
|
|
625
|
-
});
|
|
626
|
-
const taskId = taskResult?.task?.id || taskResult?.id;
|
|
627
|
-
if (!taskId) {
|
|
628
|
-
logFail(testName, `Task creation failed`);
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
logPass(`${testName}: Task created ${taskId.slice(0, 8)}`);
|
|
632
|
-
|
|
633
|
-
// Start worker
|
|
634
|
-
const hostPort = WORKER_PORT_BASE + portOffset;
|
|
635
|
-
let containerName: string;
|
|
636
|
-
try {
|
|
637
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
638
|
-
} catch (err) {
|
|
639
|
-
logFail(testName, `Container start failed: ${err}`);
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Wait for task to complete/fail (loop detection should stop it)
|
|
644
|
-
const completed = await pollUntil(async () => {
|
|
645
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
646
|
-
const s = details?.task?.status || details?.status;
|
|
647
|
-
return s === "completed" || s === "failed";
|
|
648
|
-
}, TIMEOUT_MS);
|
|
649
|
-
|
|
650
|
-
// Check container logs for loop detection markers
|
|
651
|
-
const logs = await containerLogs(containerName, 200);
|
|
652
|
-
const loopDetected =
|
|
653
|
-
logs.includes("LOOP DETECTED") || logs.includes("loop") || logs.includes("blocked");
|
|
654
|
-
|
|
655
|
-
if (loopDetected) {
|
|
656
|
-
logPass(`${testName}: Tool loop detection triggered`);
|
|
657
|
-
} else if (completed) {
|
|
658
|
-
logSkip(`${testName}: Loop detection`, "Task completed without triggering loop detection");
|
|
659
|
-
} else {
|
|
660
|
-
logFail(testName, "Task timed out without loop detection");
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
await stopContainer(containerName);
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
/** Test: summarize — Verify session summarization produces memory entry */
|
|
667
|
-
async function testSummarize(provider: string, portOffset: number) {
|
|
668
|
-
const testName = `[${provider}] summarize`;
|
|
669
|
-
logSection(`${testName}: Session summarization`);
|
|
670
|
-
|
|
671
|
-
// Register agent
|
|
672
|
-
const joinResult = await api("POST", "/api/agents", {
|
|
673
|
-
name: `e2e-${provider}-summarize`,
|
|
674
|
-
role: "worker",
|
|
675
|
-
status: "online",
|
|
676
|
-
harnessProvider: provider,
|
|
677
|
-
});
|
|
678
|
-
const agentId = joinResult?.agent?.id || joinResult?.id;
|
|
679
|
-
if (!agentId) {
|
|
680
|
-
logFail(testName, `Agent registration failed`);
|
|
681
|
-
return;
|
|
682
|
-
}
|
|
683
|
-
|
|
684
|
-
// Create a task with enough substance to summarize
|
|
685
|
-
const taskResult = await api("POST", "/api/tasks", {
|
|
686
|
-
task: "Create a file /workspace/summary-test.txt explaining what 2+2 equals and why. Use the Write tool.",
|
|
687
|
-
agentId,
|
|
688
|
-
source: "api",
|
|
689
|
-
});
|
|
690
|
-
const taskId = taskResult?.task?.id || taskResult?.id;
|
|
691
|
-
if (!taskId) {
|
|
692
|
-
logFail(testName, `Task creation failed`);
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
695
|
-
logPass(`${testName}: Task created ${taskId.slice(0, 8)}`);
|
|
696
|
-
|
|
697
|
-
// Start worker
|
|
698
|
-
const hostPort = WORKER_PORT_BASE + portOffset;
|
|
699
|
-
let containerName: string;
|
|
700
|
-
try {
|
|
701
|
-
containerName = await startWorker(provider, agentId, hostPort);
|
|
702
|
-
} catch (err) {
|
|
703
|
-
logFail(testName, `Container start failed: ${err}`);
|
|
704
|
-
return;
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Wait for task to complete
|
|
708
|
-
const completed = await pollUntil(async () => {
|
|
709
|
-
const details = await apiSafe("GET", `/api/tasks/${taskId}`);
|
|
710
|
-
const s = details?.task?.status || details?.status;
|
|
711
|
-
return s === "completed" || s === "failed";
|
|
712
|
-
}, TIMEOUT_MS);
|
|
713
|
-
|
|
714
|
-
if (!completed) {
|
|
715
|
-
logFail(testName, "Task timed out");
|
|
716
|
-
await stopContainer(containerName);
|
|
717
|
-
return;
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
// Wait a bit for summarization to complete (runs in Stop hook)
|
|
721
|
-
await Bun.sleep(10000);
|
|
722
|
-
|
|
723
|
-
// Check for memory entries from this agent
|
|
724
|
-
const memories = await apiSafe("GET", `/api/memory?agentId=${agentId}`);
|
|
725
|
-
const memEntries = memories?.memories || memories?.entries || [];
|
|
726
|
-
|
|
727
|
-
if (memEntries.length > 0) {
|
|
728
|
-
logPass(`${testName}: Memory entry created (${memEntries.length} entries)`);
|
|
729
|
-
} else {
|
|
730
|
-
logSkip(
|
|
731
|
-
`${testName}: Memory entry`,
|
|
732
|
-
"No memory entries found (summarization may have failed silently)",
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
await stopContainer(containerName);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
// ─── Test Router ─────────────────────────────────────────────────────────────
|
|
740
|
-
const TEST_FUNCTIONS: Record<string, (provider: string, portOffset: number) => Promise<void>> = {
|
|
741
|
-
basic: testBasic,
|
|
742
|
-
cancel: testCancel,
|
|
743
|
-
resume: testResume,
|
|
744
|
-
"tool-loop": testToolLoop,
|
|
745
|
-
summarize: testSummarize,
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
749
|
-
async function main() {
|
|
750
|
-
log(`Providers: ${PROVIDERS.join(", ")}`);
|
|
751
|
-
log(`Tests: ${TESTS_TO_RUN.join(", ")}`);
|
|
752
|
-
log(`API port: ${PORT}, DB: ${DB_PATH}`);
|
|
753
|
-
log(`Timeout per test: ${TIMEOUT_MS / 1000}s`);
|
|
754
|
-
|
|
755
|
-
// 1. Build Docker image (unless --skip-build)
|
|
756
|
-
if (!SKIP_BUILD) {
|
|
757
|
-
logSection("Building Docker image");
|
|
758
|
-
try {
|
|
759
|
-
await $`docker build -f Dockerfile.worker -t ${DOCKER_IMAGE} .`.quiet();
|
|
760
|
-
logPass("Docker image built");
|
|
761
|
-
} catch (err) {
|
|
762
|
-
logFail("Docker build", String(err));
|
|
763
|
-
process.exit(1);
|
|
764
|
-
}
|
|
765
|
-
} else {
|
|
766
|
-
log("Skipping Docker build (--skip-build)");
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
// 2. Start API server
|
|
770
|
-
logSection("Starting API server");
|
|
771
|
-
apiProc = Bun.spawn(["bun", "run", "src/http.ts"], {
|
|
772
|
-
env: {
|
|
773
|
-
...process.env,
|
|
774
|
-
PORT,
|
|
775
|
-
API_KEY,
|
|
776
|
-
DATABASE_PATH: DB_PATH,
|
|
777
|
-
SLACK_DISABLE: "true",
|
|
778
|
-
GITHUB_DISABLE: "true",
|
|
779
|
-
},
|
|
780
|
-
stdout: "pipe",
|
|
781
|
-
stderr: "pipe",
|
|
782
|
-
});
|
|
783
|
-
|
|
784
|
-
if (!(await waitForApi())) {
|
|
785
|
-
logFail("API startup", "Server did not become healthy within 15s");
|
|
786
|
-
await cleanup();
|
|
787
|
-
process.exit(1);
|
|
788
|
-
}
|
|
789
|
-
logPass("API server started");
|
|
790
|
-
|
|
791
|
-
// 3. Run tests for each provider
|
|
792
|
-
let portOffset = 0;
|
|
793
|
-
for (const provider of PROVIDERS) {
|
|
794
|
-
logSection(`Provider: ${provider}`);
|
|
795
|
-
for (const testName of TESTS_TO_RUN) {
|
|
796
|
-
const testFn = TEST_FUNCTIONS[testName];
|
|
797
|
-
if (!testFn) continue;
|
|
798
|
-
try {
|
|
799
|
-
await testFn(provider, portOffset++);
|
|
800
|
-
} catch (err) {
|
|
801
|
-
logFail(`[${provider}] ${testName}`, `Unhandled error: ${err}`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
// 4. Summary
|
|
807
|
-
logSection("Results");
|
|
808
|
-
log(
|
|
809
|
-
`\x1b[1;32m${pass} passed\x1b[0m, \x1b[1;31m${fail} failed\x1b[0m, \x1b[1;33m${skip} skipped\x1b[0m`,
|
|
810
|
-
);
|
|
811
|
-
|
|
812
|
-
await cleanup();
|
|
813
|
-
process.exit(fail > 0 ? 1 : 0);
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
main().catch(async (err) => {
|
|
817
|
-
console.error("E2E test error:", err);
|
|
818
|
-
await cleanup();
|
|
819
|
-
process.exit(1);
|
|
820
|
-
});
|