@desplega.ai/agent-swarm 1.20.0 → 1.49.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/.claude/settings.json +84 -0
- package/.claude/settings.local.json +3 -1
- package/.entire/settings.json +4 -0
- package/.env.docker.example +40 -23
- package/.env.example +39 -1
- package/.github/ISSUE_TEMPLATE/bug_report.yml +78 -0
- package/.github/ISSUE_TEMPLATE/community-template.yml +77 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +60 -0
- package/.github/PULL_REQUEST_TEMPLATE/community-template.md +29 -0
- package/.github/workflows/ci.yml +3 -27
- package/.github/workflows/docker-and-deploy.yml +20 -5
- package/.github/workflows/merge-gate.yml +233 -0
- package/.opencode/plugins/entire.ts +133 -0
- package/.superset/config.json +6 -0
- package/.wts-setup.ts +103 -34
- package/CHANGELOG.md +447 -0
- package/CLAUDE.md +422 -5
- package/CONTRIBUTING.md +46 -1
- package/DEPLOYMENT.md +26 -9
- package/Dockerfile +15 -7
- package/Dockerfile.worker +99 -67
- package/MCP.md +452 -17
- package/README.md +271 -169
- package/api-entrypoint.sh +56 -0
- package/depot.json +1 -0
- package/docker-compose.example.yml +194 -3
- package/docker-compose.local.yml +119 -0
- package/docker-entrypoint.sh +393 -113
- package/docs-site/app/api/search/route.ts +4 -0
- package/docs-site/app/docs/[[...slug]]/page.tsx +87 -0
- package/docs-site/app/docs/layout.tsx +12 -0
- package/docs-site/app/globals.css +24 -0
- package/docs-site/app/layout.config.tsx +34 -0
- package/docs-site/app/layout.tsx +119 -0
- package/docs-site/app/llms-full.txt/route.ts +11 -0
- package/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +24 -0
- package/docs-site/app/llms.txt/route.ts +8 -0
- package/docs-site/app/page.tsx +5 -0
- package/docs-site/app/robots.ts +13 -0
- package/docs-site/app/sitemap.ts +37 -0
- package/docs-site/components/api-page.client.tsx +4 -0
- package/docs-site/components/api-page.tsx +7 -0
- package/docs-site/components/mdx/mermaid.tsx +55 -0
- package/docs-site/content/docs/(documentation)/architecture/agents.mdx +117 -0
- package/docs-site/content/docs/(documentation)/architecture/hooks.mdx +77 -0
- package/docs-site/content/docs/(documentation)/architecture/memory.mdx +96 -0
- package/docs-site/content/docs/(documentation)/architecture/meta.json +4 -0
- package/docs-site/content/docs/(documentation)/architecture/overview.mdx +172 -0
- package/docs-site/content/docs/(documentation)/concepts/epics.mdx +98 -0
- package/docs-site/content/docs/(documentation)/concepts/meta.json +4 -0
- package/docs-site/content/docs/(documentation)/concepts/scheduling.mdx +136 -0
- package/docs-site/content/docs/(documentation)/concepts/services.mdx +104 -0
- package/docs-site/content/docs/(documentation)/concepts/task-lifecycle.mdx +148 -0
- package/docs-site/content/docs/(documentation)/concepts/workflows.mdx +209 -0
- package/docs-site/content/docs/(documentation)/contributing.mdx +158 -0
- package/docs-site/content/docs/(documentation)/getting-started.mdx +157 -0
- package/docs-site/content/docs/(documentation)/guides/agentmail-integration.mdx +79 -0
- package/docs-site/content/docs/(documentation)/guides/deployment.mdx +171 -0
- package/docs-site/content/docs/(documentation)/guides/github-integration.mdx +81 -0
- package/docs-site/content/docs/(documentation)/guides/gitlab-integration.mdx +93 -0
- package/docs-site/content/docs/(documentation)/guides/linear-integration.mdx +98 -0
- package/docs-site/content/docs/(documentation)/guides/meta.json +13 -0
- package/docs-site/content/docs/(documentation)/guides/sentry-integration.mdx +52 -0
- package/docs-site/content/docs/(documentation)/guides/slack-integration.mdx +179 -0
- package/docs-site/content/docs/(documentation)/guides/x402-payments.mdx +154 -0
- package/docs-site/content/docs/(documentation)/index.mdx +65 -0
- package/docs-site/content/docs/(documentation)/meta.json +19 -0
- package/docs-site/content/docs/(documentation)/reference/cli.mdx +241 -0
- package/docs-site/content/docs/(documentation)/reference/environment-variables.mdx +205 -0
- package/docs-site/content/docs/(documentation)/reference/mcp-tools.mdx +449 -0
- package/docs-site/content/docs/(documentation)/reference/meta.json +4 -0
- package/docs-site/content/docs/api-reference/active-sessions.mdx +9 -0
- package/docs-site/content/docs/api-reference/agents.mdx +9 -0
- package/docs-site/content/docs/api-reference/channels.mdx +9 -0
- package/docs-site/content/docs/api-reference/config.mdx +9 -0
- package/docs-site/content/docs/api-reference/debug.mdx +9 -0
- package/docs-site/content/docs/api-reference/ecosystem.mdx +9 -0
- package/docs-site/content/docs/api-reference/epics.mdx +9 -0
- package/docs-site/content/docs/api-reference/index.mdx +32 -0
- package/docs-site/content/docs/api-reference/memory.mdx +9 -0
- package/docs-site/content/docs/api-reference/meta.json +25 -0
- package/docs-site/content/docs/api-reference/poll.mdx +9 -0
- package/docs-site/content/docs/api-reference/repos.mdx +9 -0
- package/docs-site/content/docs/api-reference/schedules.mdx +9 -0
- package/docs-site/content/docs/api-reference/session-data.mdx +9 -0
- package/docs-site/content/docs/api-reference/stats.mdx +9 -0
- package/docs-site/content/docs/api-reference/tasks.mdx +9 -0
- package/docs-site/content/docs/api-reference/trackers.mdx +9 -0
- package/docs-site/content/docs/api-reference/webhooks.mdx +9 -0
- package/docs-site/content/docs/api-reference/workflows.mdx +9 -0
- package/docs-site/content/docs/meta.json +3 -0
- package/docs-site/lib/get-llm-text.ts +10 -0
- package/docs-site/lib/openapi.ts +23 -0
- package/docs-site/lib/source.ts +8 -0
- package/docs-site/mdx-components.tsx +13 -0
- package/docs-site/next.config.mjs +29 -0
- package/docs-site/package.json +35 -0
- package/docs-site/pnpm-lock.yaml +5407 -0
- package/docs-site/postcss.config.mjs +8 -0
- package/docs-site/scripts/generate-docs.ts +171 -0
- package/docs-site/source.config.ts +17 -0
- package/docs-site/tsconfig.json +46 -0
- package/ecosystem.config.cjs +4 -4
- package/landing/next.config.ts +14 -0
- package/landing/package.json +31 -0
- package/landing/pnpm-lock.yaml +1091 -0
- package/landing/postcss.config.mjs +8 -0
- 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 +30 -0
- package/landing/public/omghost-openfort.svg +9 -0
- package/landing/src/app/actions/waitlist.ts +25 -0
- package/landing/src/app/blog/openfort-hackathon/page.tsx +863 -0
- package/landing/src/app/blog/page.tsx +162 -0
- package/landing/src/app/blog/swarm-metrics/page.tsx +685 -0
- package/landing/src/app/examples/page.tsx +174 -0
- package/landing/src/app/examples/x402/page.tsx +456 -0
- package/landing/src/app/globals.css +122 -0
- package/landing/src/app/layout.tsx +134 -0
- package/landing/src/app/page.tsx +27 -0
- package/landing/src/app/robots.ts +13 -0
- package/landing/src/app/sitemap.ts +44 -0
- package/landing/src/components/architecture.tsx +163 -0
- package/landing/src/components/cta.tsx +52 -0
- package/landing/src/components/features.tsx +160 -0
- package/landing/src/components/footer.tsx +100 -0
- package/landing/src/components/hero.tsx +217 -0
- package/landing/src/components/how-it-works.tsx +165 -0
- package/landing/src/components/navbar.tsx +147 -0
- package/landing/src/components/waitlist.tsx +110 -0
- package/landing/src/components/why-choose.tsx +149 -0
- package/landing/src/components/workshops.tsx +328 -0
- package/landing/src/lib/utils.ts +6 -0
- package/landing/tsconfig.json +41 -0
- package/misc/transcripts/2026-03-09-pi-mono-e2e-verification.md +154 -0
- package/new-ui/CLAUDE.md +92 -0
- package/new-ui/README.md +73 -0
- package/new-ui/biome.json +42 -0
- package/new-ui/components.json +21 -0
- package/new-ui/index.html +25 -0
- package/new-ui/package.json +49 -0
- package/new-ui/pnpm-lock.yaml +4845 -0
- package/new-ui/public/logo.png +0 -0
- package/new-ui/src/api/client.ts +814 -0
- package/new-ui/src/api/hooks/index.ts +64 -0
- package/new-ui/src/api/hooks/use-agents.ts +58 -0
- package/new-ui/src/api/hooks/use-channels.ts +115 -0
- package/new-ui/src/api/hooks/use-config-api.ts +46 -0
- package/new-ui/src/api/hooks/use-costs.ts +122 -0
- package/new-ui/src/api/hooks/use-db-query.ts +29 -0
- package/new-ui/src/api/hooks/use-epics.ts +75 -0
- package/new-ui/src/api/hooks/use-repos.ts +61 -0
- package/new-ui/src/api/hooks/use-schedules.ts +81 -0
- package/new-ui/src/api/hooks/use-services.ts +16 -0
- package/new-ui/src/api/hooks/use-stats.ts +27 -0
- package/new-ui/src/api/hooks/use-tasks.ts +89 -0
- package/new-ui/src/api/hooks/use-workflows.ts +109 -0
- package/{ui/src/types/api.ts → new-ui/src/api/types.ts} +236 -10
- package/new-ui/src/app/App.tsx +13 -0
- package/new-ui/src/app/providers.tsx +32 -0
- package/new-ui/src/app/router.tsx +52 -0
- package/new-ui/src/components/layout/app-header.tsx +47 -0
- package/new-ui/src/components/layout/app-sidebar.tsx +128 -0
- package/new-ui/src/components/layout/breadcrumbs.tsx +57 -0
- package/new-ui/src/components/layout/config-guard.tsx +22 -0
- package/new-ui/src/components/layout/root-layout.tsx +40 -0
- package/new-ui/src/components/layout/swarm-switcher.tsx +85 -0
- package/new-ui/src/components/shared/command-menu.tsx +131 -0
- package/new-ui/src/components/shared/data-grid.tsx +141 -0
- package/new-ui/src/components/shared/empty-state.tsx +24 -0
- package/new-ui/src/components/shared/error-boundary.tsx +72 -0
- package/new-ui/src/components/shared/json-viewer.tsx +47 -0
- package/new-ui/src/components/shared/name-connection-modal.tsx +99 -0
- package/new-ui/src/components/shared/page-skeleton.tsx +16 -0
- package/new-ui/src/components/shared/session-log-viewer.tsx +364 -0
- package/new-ui/src/components/shared/stats-bar.tsx +132 -0
- package/new-ui/src/components/shared/status-badge.tsx +131 -0
- package/new-ui/src/components/shared/usage-summary.tsx +179 -0
- package/new-ui/src/components/ui/alert-dialog.tsx +176 -0
- package/new-ui/src/components/ui/alert.tsx +60 -0
- package/new-ui/src/components/ui/avatar.tsx +96 -0
- package/new-ui/src/components/ui/badge.tsx +46 -0
- package/new-ui/src/components/ui/button.tsx +62 -0
- package/new-ui/src/components/ui/card.tsx +75 -0
- package/new-ui/src/components/ui/command.tsx +160 -0
- package/new-ui/src/components/ui/dialog.tsx +143 -0
- package/new-ui/src/components/ui/dropdown-menu.tsx +226 -0
- package/new-ui/src/components/ui/input.tsx +21 -0
- package/new-ui/src/components/ui/label.tsx +19 -0
- package/new-ui/src/components/ui/progress.tsx +26 -0
- package/new-ui/src/components/ui/scroll-area.tsx +54 -0
- package/new-ui/src/components/ui/select.tsx +175 -0
- package/new-ui/src/components/ui/separator.tsx +28 -0
- package/new-ui/src/components/ui/sheet.tsx +132 -0
- package/new-ui/src/components/ui/sidebar.tsx +691 -0
- package/new-ui/src/components/ui/skeleton.tsx +13 -0
- package/new-ui/src/components/ui/sonner.tsx +35 -0
- package/new-ui/src/components/ui/switch.tsx +33 -0
- package/new-ui/src/components/ui/table.tsx +92 -0
- package/new-ui/src/components/ui/tabs.tsx +79 -0
- package/new-ui/src/components/ui/textarea.tsx +18 -0
- package/new-ui/src/components/ui/tooltip.tsx +51 -0
- package/new-ui/src/components/workflows/action-node.tsx +53 -0
- package/new-ui/src/components/workflows/condition-node.tsx +50 -0
- package/new-ui/src/components/workflows/graph-utils.ts +124 -0
- package/new-ui/src/components/workflows/json-tree.tsx +189 -0
- package/new-ui/src/components/workflows/node-styles.ts +10 -0
- package/new-ui/src/components/workflows/step-detail-sheet.tsx +87 -0
- package/new-ui/src/components/workflows/trigger-node.tsx +41 -0
- package/new-ui/src/components/workflows/workflow-graph.tsx +65 -0
- package/{ui/src/hooks/useAutoScroll.ts → new-ui/src/hooks/use-auto-scroll.ts} +0 -1
- package/new-ui/src/hooks/use-config.ts +203 -0
- package/new-ui/src/hooks/use-keyboard-shortcuts.ts +41 -0
- package/new-ui/src/hooks/use-mobile.ts +19 -0
- package/new-ui/src/hooks/use-theme.ts +60 -0
- package/new-ui/src/lib/config.ts +188 -0
- package/new-ui/src/lib/slugs.ts +71 -0
- package/{ui → new-ui}/src/lib/utils.ts +32 -0
- package/new-ui/src/main.tsx +11 -0
- package/new-ui/src/pages/agents/[id]/page.tsx +492 -0
- package/new-ui/src/pages/agents/page.tsx +134 -0
- package/new-ui/src/pages/chat/page.tsx +674 -0
- package/new-ui/src/pages/config/page.tsx +1109 -0
- package/new-ui/src/pages/dashboard/page.tsx +454 -0
- package/new-ui/src/pages/debug/page.tsx +275 -0
- package/new-ui/src/pages/epics/[id]/page.tsx +809 -0
- package/new-ui/src/pages/epics/page.tsx +321 -0
- package/new-ui/src/pages/not-found/page.tsx +18 -0
- package/new-ui/src/pages/repos/page.tsx +369 -0
- package/new-ui/src/pages/schedules/[id]/page.tsx +664 -0
- package/new-ui/src/pages/schedules/page.tsx +477 -0
- package/new-ui/src/pages/services/page.tsx +128 -0
- package/new-ui/src/pages/tasks/[id]/page.tsx +670 -0
- package/new-ui/src/pages/tasks/page.tsx +592 -0
- package/new-ui/src/pages/usage/page.tsx +195 -0
- package/new-ui/src/pages/workflow-runs/[id]/page.tsx +363 -0
- package/new-ui/src/pages/workflows/[id]/page.tsx +417 -0
- package/new-ui/src/pages/workflows/page.tsx +266 -0
- package/new-ui/src/styles/ag-grid.css +36 -0
- package/new-ui/src/styles/globals.css +213 -0
- package/new-ui/test-results/.last-run.json +4 -0
- package/{ui/tsconfig.json → new-ui/tsconfig.app.json} +7 -4
- package/new-ui/tsconfig.json +4 -0
- package/new-ui/tsconfig.node.json +26 -0
- package/new-ui/vercel.json +4 -0
- package/new-ui/vite.config.ts +28 -0
- package/openapi.json +4436 -0
- package/package.json +31 -7
- package/plugin/build-pi-skills.ts +233 -0
- package/plugin/commands/close-issue.md +7 -3
- package/plugin/commands/create-pr.md +18 -12
- package/plugin/commands/implement-issue.md +7 -3
- package/plugin/commands/respond-github.md +8 -4
- package/plugin/commands/review-pr.md +44 -10
- package/plugin/commands/start-leader.md +1 -3
- package/plugin/commands/start-worker.md +1 -3
- package/plugin/commands/work-on-task.md +22 -3
- package/plugin/pi-skills/close-issue/SKILL.md +90 -0
- package/plugin/pi-skills/create-pr/SKILL.md +99 -0
- package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
- package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
- package/plugin/pi-skills/respond-github/SKILL.md +98 -0
- package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
- package/plugin/pi-skills/review-pr/SKILL.md +261 -0
- package/plugin/pi-skills/start-leader/SKILL.md +121 -0
- package/plugin/pi-skills/start-worker/SKILL.md +60 -0
- package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
- package/plugin/pi-skills/todos/SKILL.md +66 -0
- package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
- package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
- package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
- package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
- package/plugin/skills/artifacts/examples/static-report.sh +17 -0
- package/plugin/skills/artifacts/skill.md +71 -0
- package/prek.toml +75 -0
- package/scripts/check-db-boundary.sh +60 -0
- package/scripts/e2e-docker-provider.ts +820 -0
- package/scripts/e2e-io-schemas-test.ts +807 -0
- package/scripts/e2e-provider-test.ts +220 -0
- package/scripts/e2e-workflow-redesign.sh +229 -0
- package/scripts/e2e-workflow-test.sh +285 -0
- package/scripts/e2e-workflow-test.ts +857 -0
- package/scripts/generate-openapi.ts +26 -0
- package/scripts/measure-tool-tokens.ts +118 -0
- package/scripts/x402-e2e-test.ts +195 -0
- package/scripts/x402-test-server.ts +236 -0
- package/scripts/x402-testnet-e2e.ts +668 -0
- package/slack-manifest.json +17 -0
- package/src/agentmail/app.ts +65 -0
- package/src/agentmail/handlers.ts +207 -0
- package/src/agentmail/index.ts +9 -0
- package/src/agentmail/types.ts +51 -0
- package/src/artifact-sdk/browser-sdk.ts +30 -0
- package/src/artifact-sdk/index.ts +2 -0
- package/src/artifact-sdk/localtunnel.d.ts +20 -0
- package/src/artifact-sdk/port.ts +12 -0
- package/src/artifact-sdk/server.ts +156 -0
- package/src/artifact-sdk/tunnel.ts +19 -0
- package/src/be/chunking.ts +193 -0
- package/src/be/db-queries/oauth.ts +90 -0
- package/src/be/db-queries/tracker.ts +182 -0
- package/src/be/db.ts +2729 -770
- package/src/be/embedding.ts +80 -0
- package/src/be/migrations/001_initial.sql +409 -0
- package/src/be/migrations/002_one_time_schedules.sql +59 -0
- package/src/be/migrations/003_workflows.sql +51 -0
- package/src/be/migrations/004_workflow_source.sql +81 -0
- package/src/be/migrations/005_epic_next_steps.sql +2 -0
- package/src/be/migrations/006_vcs_provider.sql +94 -0
- package/src/be/migrations/007_task_dir.sql +2 -0
- package/src/be/migrations/008_workflow_redesign.sql +85 -0
- package/src/be/migrations/009_tracker_integration.sql +144 -0
- package/src/be/migrations/010_step_diagnostics.sql +1 -0
- package/src/be/migrations/011_step_next_port.sql +1 -0
- package/src/be/migrations/012_trigger_schema.sql +1 -0
- package/src/be/migrations/013_task_output_schema.sql +2 -0
- package/src/be/migrations/runner.ts +188 -0
- package/src/cli.tsx +231 -299
- package/src/commands/artifact.ts +241 -0
- package/src/commands/onboard/compose-generator.ts +169 -0
- package/src/commands/onboard/env-generator.ts +79 -0
- package/src/commands/onboard/manifest.ts +37 -0
- package/src/commands/onboard/presets.ts +85 -0
- package/src/commands/onboard/service-names.ts +47 -0
- package/src/commands/onboard/steps/core-credentials.tsx +111 -0
- package/src/commands/onboard/steps/custom-templates.tsx +168 -0
- package/src/commands/onboard/steps/generate.tsx +154 -0
- package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
- package/src/commands/onboard/steps/harness.tsx +21 -0
- package/src/commands/onboard/steps/health-check.tsx +171 -0
- package/src/commands/onboard/steps/integration-github.tsx +105 -0
- package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
- package/src/commands/onboard/steps/integration-menu.tsx +58 -0
- package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
- package/src/commands/onboard/steps/integration-slack.tsx +165 -0
- package/src/commands/onboard/steps/post-connect.tsx +145 -0
- package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
- package/src/commands/onboard/steps/post-task.tsx +103 -0
- package/src/commands/onboard/steps/prereq-check.tsx +178 -0
- package/src/commands/onboard/steps/review.tsx +82 -0
- package/src/commands/onboard/steps/start.tsx +97 -0
- package/src/commands/onboard/templates.ts +34 -0
- package/src/commands/onboard/types.ts +259 -0
- package/src/commands/onboard.tsx +425 -0
- package/src/commands/runner.ts +1376 -559
- package/src/commands/setup.tsx +23 -38
- package/src/commands/shared/client-config.ts +41 -0
- package/src/github/app.ts +8 -0
- package/src/github/handlers.ts +93 -75
- package/src/github/index.ts +1 -0
- package/src/github/types.ts +1 -0
- package/src/gitlab/auth.ts +63 -0
- package/src/gitlab/handlers.ts +327 -0
- package/src/gitlab/index.ts +19 -0
- package/src/gitlab/reactions.ts +104 -0
- package/src/gitlab/types.ts +130 -0
- package/src/heartbeat/heartbeat.ts +425 -0
- package/src/heartbeat/index.ts +1 -0
- package/src/hooks/hook.ts +555 -4
- package/src/hooks/tool-loop-detection.test.ts +158 -0
- package/src/hooks/tool-loop-detection.ts +167 -0
- package/src/http/active-sessions.ts +172 -0
- package/src/http/agents.ts +328 -0
- package/src/http/config.ts +191 -0
- package/src/http/core.ts +309 -0
- package/src/http/db-query.ts +91 -0
- package/src/http/ecosystem.ts +63 -0
- package/src/http/epics.ts +460 -0
- package/src/http/index.ts +213 -0
- package/src/http/mcp.ts +77 -0
- package/src/http/memory.ts +168 -0
- package/src/http/openapi.ts +109 -0
- package/src/http/poll.ts +178 -0
- package/src/http/repos.ts +195 -0
- package/src/http/route-def.ts +123 -0
- package/src/http/schedules.ts +391 -0
- package/src/http/session-data.ts +241 -0
- package/src/http/stats.ts +174 -0
- package/src/http/tasks.ts +468 -0
- package/src/http/trackers/index.ts +10 -0
- package/src/http/trackers/linear.ts +187 -0
- package/src/http/types.ts +12 -0
- package/src/http/utils.ts +87 -0
- package/src/http/webhooks.ts +432 -0
- package/src/http/workflows.ts +522 -0
- package/src/http.ts +1 -1890
- package/src/linear/README.md +65 -0
- package/src/linear/app.ts +48 -0
- package/src/linear/client.ts +18 -0
- package/src/linear/index.ts +1 -0
- package/src/linear/oauth.ts +35 -0
- package/src/linear/outbound.ts +212 -0
- package/src/linear/sync.ts +543 -0
- package/src/linear/types.ts +7 -0
- package/src/linear/webhook.ts +104 -0
- package/src/oauth/README.md +66 -0
- package/src/oauth/index.ts +6 -0
- package/src/oauth/wrapper.ts +204 -0
- package/src/prompts/base-prompt.ts +366 -24
- package/src/prompts/defaults.ts +196 -0
- package/src/providers/claude-adapter.ts +429 -0
- package/src/providers/index.ts +24 -0
- package/src/providers/pi-mono-adapter.ts +442 -0
- package/src/providers/pi-mono-extension.ts +620 -0
- package/src/providers/pi-mono-mcp-client.ts +124 -0
- package/src/providers/types.ts +75 -0
- package/src/scheduler/scheduler.test.ts +2 -0
- package/src/scheduler/scheduler.ts +186 -29
- package/src/server.ts +82 -6
- package/src/slack/HEURISTICS.md +105 -0
- package/src/slack/actions.ts +133 -0
- package/src/slack/app.ts +7 -0
- package/src/slack/assistant.ts +115 -0
- package/src/slack/blocks.ts +233 -0
- package/src/slack/commands.ts +31 -17
- package/src/slack/files.ts +1 -1
- package/src/slack/handlers.test.ts +114 -1
- package/src/slack/handlers.ts +212 -52
- package/src/slack/responses.ts +120 -67
- package/src/slack/router.ts +17 -99
- package/src/slack/thread-buffer.ts +213 -0
- package/src/slack/watcher.ts +119 -4
- package/src/tests/agent-activity.test.ts +247 -0
- package/src/tests/agentmail-filters.test.ts +97 -0
- package/src/tests/artifact-sdk.test.ts +800 -0
- package/src/tests/base-prompt.test.ts +264 -0
- package/src/tests/build-pi-skills.test.ts +127 -0
- package/src/tests/claude-adapter.test.ts +126 -0
- package/src/tests/context-versioning.test.ts +425 -0
- package/src/tests/db-queries-oauth.test.ts +197 -0
- package/src/tests/db-queries-tracker.test.ts +230 -0
- package/src/tests/epics.test.ts +3 -3
- package/src/tests/error-tracker.test.ts +368 -0
- package/src/tests/fetch-resolved-env.test.ts +167 -0
- package/src/tests/generate-default-claude-md.test.ts +9 -1
- package/src/tests/generate-identity-templates.test.ts +124 -0
- package/src/tests/gitlab-auth.test.ts +109 -0
- package/src/tests/gitlab-handlers.test.ts +691 -0
- package/src/tests/gitlab-vcs-db.test.ts +177 -0
- package/src/tests/heartbeat.test.ts +363 -0
- package/src/tests/http-api-integration.test.ts +1698 -0
- package/src/tests/linear-outbound-sync.test.ts +200 -0
- package/src/tests/linear-webhook.test.ts +402 -0
- package/src/tests/match-route.test.ts +187 -0
- package/src/tests/memory.test.ts +737 -0
- package/src/tests/migration-runner-regressions.test.ts +86 -0
- package/src/tests/model-control.test.ts +338 -0
- package/src/tests/oauth-wrapper.test.ts +147 -0
- package/src/tests/onboard-compose.test.ts +138 -0
- package/src/tests/onboard-env.test.ts +174 -0
- package/src/tests/onboard-manifest.test.ts +137 -0
- package/src/tests/pi-mono-adapter.test.ts +234 -0
- package/src/tests/progress-dedup.test.ts +98 -0
- package/src/tests/provider-adapter.test.ts +122 -0
- package/src/tests/provider-command-format.test.ts +98 -0
- package/src/tests/reload-config.test.ts +170 -0
- package/src/tests/runner-polling-api.test.ts +25 -20
- package/src/tests/scheduled-tasks.test.ts +104 -0
- package/src/tests/scheduler-backoff.test.ts +166 -0
- package/src/tests/self-improvement.test.ts +540 -0
- package/src/tests/session-attach.test.ts +536 -0
- package/src/tests/session-costs.test.ts +267 -1
- package/src/tests/slack-actions.test.ts +133 -0
- package/src/tests/slack-assistant.test.ts +136 -0
- package/src/tests/slack-blocks.test.ts +246 -0
- package/src/tests/slack-metadata-inheritance.test.ts +243 -0
- package/src/tests/slack-queue-offline.test.ts +174 -0
- package/src/tests/slack-router.test.ts +181 -0
- package/src/tests/slack-thread-buffer.test.ts +305 -0
- package/src/tests/slack-thread-followups.test.ts +298 -0
- package/src/tests/slack-watcher.test.ts +101 -0
- package/src/tests/structured-output.test.ts +311 -0
- package/src/tests/swarm-repos.test.ts +198 -0
- package/src/tests/task-cancellation.test.ts +6 -4
- package/src/tests/task-working-dir.test.ts +176 -0
- package/src/tests/template-fetch.test.ts +490 -0
- package/src/tests/tool-annotations.test.ts +371 -0
- package/src/tests/tracker-tools.test.ts +184 -0
- package/src/tests/update-profile-api.test.ts +143 -3
- package/src/tests/validation-adapters.test.ts +86 -0
- package/src/tests/vcs-provider.test.ts +27 -0
- package/src/tests/workflow-agent-task.test.ts +196 -0
- package/src/tests/workflow-async-v2.test.ts +386 -0
- package/src/tests/workflow-convergence.test.ts +541 -0
- package/src/tests/workflow-definition-validation.test.ts +290 -0
- package/src/tests/workflow-engine-v2.test.ts +691 -0
- package/src/tests/workflow-executors.test.ts +736 -0
- package/src/tests/workflow-http-v2.test.ts +599 -0
- package/src/tests/workflow-integration-io.test.ts +902 -0
- package/src/tests/workflow-io-schemas.test.ts +624 -0
- package/src/tests/workflow-registry.test.ts +592 -0
- package/src/tests/workflow-retry-v2.test.ts +401 -0
- package/src/tests/workflow-retry-validation.test.ts +282 -0
- package/src/tests/workflow-template.test.ts +288 -0
- package/src/tests/workflow-trigger-schema.test.ts +359 -0
- package/src/tests/workflow-triggers-v2.test.ts +264 -0
- package/src/tests/workflow-versions.test.ts +208 -0
- package/src/tests/x402-client.test.ts +117 -0
- package/src/tests/x402-config.test.ts +182 -0
- package/src/tests/x402-spending-tracker.test.ts +185 -0
- package/src/tools/cancel-task.ts +2 -0
- package/src/tools/context-diff.ts +171 -0
- package/src/tools/context-history.ts +138 -0
- package/src/tools/create-channel.ts +1 -0
- package/src/tools/db-query.ts +78 -0
- package/src/tools/delete-channel.ts +132 -0
- package/src/tools/epics/assign-task-to-epic.ts +1 -0
- package/src/tools/epics/create-epic.ts +3 -2
- package/src/tools/epics/delete-epic.ts +2 -0
- package/src/tools/epics/get-epic-details.ts +2 -0
- package/src/tools/epics/list-epics.ts +2 -0
- package/src/tools/epics/unassign-task-from-epic.ts +1 -0
- package/src/tools/epics/update-epic.ts +7 -4
- package/src/tools/get-swarm.ts +2 -0
- package/src/tools/get-task-details.ts +2 -0
- package/src/tools/get-tasks.ts +27 -1
- package/src/tools/inject-learning.ts +106 -0
- package/src/tools/join-swarm.ts +17 -7
- package/src/tools/list-channels.ts +2 -0
- package/src/tools/list-services.ts +2 -0
- package/src/tools/memory-get.ts +56 -0
- package/src/tools/memory-search.ts +131 -0
- package/src/tools/my-agent-info.ts +2 -0
- package/src/tools/poll-task.ts +2 -20
- package/src/tools/post-message.ts +1 -0
- package/src/tools/read-messages.ts +2 -0
- package/src/tools/register-agentmail-inbox.ts +166 -0
- package/src/tools/register-service.ts +2 -0
- package/src/tools/schedules/create-schedule.ts +134 -24
- package/src/tools/schedules/delete-schedule.ts +2 -0
- package/src/tools/schedules/list-schedules.ts +20 -4
- package/src/tools/schedules/run-schedule-now.ts +1 -0
- package/src/tools/schedules/update-schedule.ts +49 -17
- package/src/tools/send-task.ts +132 -10
- package/src/tools/slack-download-file.ts +4 -2
- package/src/tools/slack-list-channels.ts +2 -0
- package/src/tools/slack-post.ts +2 -0
- package/src/tools/slack-read.ts +2 -0
- package/src/tools/slack-reply.ts +2 -0
- package/src/tools/slack-upload-file.ts +2 -0
- package/src/tools/store-progress.ts +187 -4
- package/src/tools/swarm-config/delete-config.ts +87 -0
- package/src/tools/swarm-config/get-config.ts +108 -0
- package/src/tools/swarm-config/index.ts +4 -0
- package/src/tools/swarm-config/list-config.ts +99 -0
- package/src/tools/swarm-config/set-config.ts +118 -0
- package/src/tools/task-action.ts +30 -5
- package/src/tools/task-dedup.ts +97 -0
- package/src/tools/tool-config.ts +117 -0
- package/src/tools/tracker/index.ts +6 -0
- package/src/tools/tracker/tracker-link-epic.ts +64 -0
- package/src/tools/tracker/tracker-link-task.ts +64 -0
- package/src/tools/tracker/tracker-map-agent.ts +57 -0
- package/src/tools/tracker/tracker-status.ts +56 -0
- package/src/tools/tracker/tracker-sync-status.ts +42 -0
- package/src/tools/tracker/tracker-unlink.ts +41 -0
- package/src/tools/unregister-service.ts +2 -0
- package/src/tools/update-profile.ts +93 -10
- package/src/tools/update-service-status.ts +2 -0
- package/src/tools/utils.ts +10 -1
- package/src/tools/workflows/create-workflow.ts +111 -0
- package/src/tools/workflows/delete-workflow.ts +42 -0
- package/src/tools/workflows/get-workflow-run.ts +59 -0
- package/src/tools/workflows/get-workflow.ts +53 -0
- package/src/tools/workflows/index.ts +9 -0
- package/src/tools/workflows/list-workflow-runs.ts +48 -0
- package/src/tools/workflows/list-workflows.ts +42 -0
- package/src/tools/workflows/retry-workflow-run.ts +40 -0
- package/src/tools/workflows/trigger-workflow.ts +96 -0
- package/src/tools/workflows/update-workflow.ts +118 -0
- package/src/tracker/types.ts +51 -0
- package/src/types.ts +477 -14
- package/src/utils/credentials.test.ts +156 -0
- package/src/utils/credentials.ts +50 -0
- package/src/utils/error-tracker.ts +190 -0
- package/src/vcs/index.ts +15 -0
- package/src/vcs/types.ts +5 -0
- package/src/workflows/checkpoint.ts +121 -0
- package/src/workflows/cooldown.ts +28 -0
- package/src/workflows/definition.ts +217 -0
- package/src/workflows/engine.ts +554 -0
- package/src/workflows/event-bus.ts +29 -0
- package/src/workflows/executors/agent-task.ts +92 -0
- package/src/workflows/executors/base.ts +86 -0
- package/src/workflows/executors/code-match.ts +88 -0
- package/src/workflows/executors/index.ts +16 -0
- package/src/workflows/executors/notify.ts +93 -0
- package/src/workflows/executors/property-match.ts +104 -0
- package/src/workflows/executors/raw-llm.ts +83 -0
- package/src/workflows/executors/registry.ts +76 -0
- package/src/workflows/executors/script.ts +103 -0
- package/src/workflows/executors/validate.ts +215 -0
- package/src/workflows/executors/vcs.ts +58 -0
- package/src/workflows/index.ts +61 -0
- package/src/workflows/input.ts +46 -0
- package/src/workflows/json-schema-validator.ts +118 -0
- package/src/workflows/recovery.ts +139 -0
- package/src/workflows/resume.ts +143 -0
- package/src/workflows/retry-poller.ts +216 -0
- package/src/workflows/template.ts +74 -0
- package/src/workflows/templates.ts +86 -0
- package/src/workflows/triggers.ts +95 -0
- package/src/workflows/validation.ts +104 -0
- package/src/workflows/version.ts +42 -0
- package/src/x402/cli.ts +140 -0
- package/src/x402/client.ts +192 -0
- package/src/x402/config.ts +131 -0
- package/src/x402/index.ts +37 -0
- package/src/x402/openfort-signer.ts +83 -0
- package/src/x402/spending-tracker.ts +109 -0
- package/templates/community/.gitkeep +0 -0
- package/templates/official/coder/CLAUDE.md +49 -0
- package/templates/official/coder/IDENTITY.md +28 -0
- package/templates/official/coder/SOUL.md +43 -0
- package/templates/official/coder/TOOLS.md +40 -0
- package/templates/official/coder/config.json +23 -0
- package/templates/official/coder/start-up.sh +23 -0
- package/templates/official/content-reviewer/CLAUDE.md +68 -0
- package/templates/official/content-reviewer/IDENTITY.md +28 -0
- package/templates/official/content-reviewer/SOUL.md +44 -0
- package/templates/official/content-reviewer/TOOLS.md +37 -0
- package/templates/official/content-reviewer/config.json +23 -0
- package/templates/official/content-reviewer/start-up.sh +23 -0
- package/templates/official/content-strategist/CLAUDE.md +63 -0
- package/templates/official/content-strategist/IDENTITY.md +33 -0
- package/templates/official/content-strategist/SOUL.md +48 -0
- package/templates/official/content-strategist/TOOLS.md +47 -0
- package/templates/official/content-strategist/config.json +23 -0
- package/templates/official/content-strategist/start-up.sh +23 -0
- package/templates/official/content-writer/CLAUDE.md +72 -0
- package/templates/official/content-writer/IDENTITY.md +30 -0
- package/templates/official/content-writer/SOUL.md +46 -0
- package/templates/official/content-writer/TOOLS.md +44 -0
- package/templates/official/content-writer/config.json +23 -0
- package/templates/official/content-writer/start-up.sh +23 -0
- package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
- package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
- package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
- package/templates/official/forward-deployed-engineer/config.json +21 -0
- package/templates/official/lead/CLAUDE.md +33 -0
- package/templates/official/lead/IDENTITY.md +36 -0
- package/templates/official/lead/SOUL.md +51 -0
- package/templates/official/lead/config.json +22 -0
- package/templates/official/researcher/CLAUDE.md +46 -0
- package/templates/official/researcher/IDENTITY.md +28 -0
- package/templates/official/researcher/SOUL.md +43 -0
- package/templates/official/researcher/config.json +21 -0
- package/templates/official/reviewer/CLAUDE.md +63 -0
- package/templates/official/reviewer/IDENTITY.md +28 -0
- package/templates/official/reviewer/SOUL.md +45 -0
- package/templates/official/reviewer/config.json +21 -0
- package/templates/official/tester/CLAUDE.md +53 -0
- package/templates/official/tester/IDENTITY.md +28 -0
- package/templates/official/tester/SOUL.md +55 -0
- package/templates/official/tester/config.json +21 -0
- package/templates/schema.ts +35 -0
- package/templates-ui/README.md +46 -0
- package/templates-ui/components.json +17 -0
- package/templates-ui/eslint.config.mjs +18 -0
- package/templates-ui/next.config.ts +7 -0
- package/templates-ui/package.json +35 -0
- package/templates-ui/pnpm-lock.yaml +4571 -0
- package/templates-ui/postcss.config.mjs +7 -0
- package/templates-ui/public/file.svg +1 -0
- package/templates-ui/public/globe.svg +1 -0
- package/templates-ui/public/logo.png +0 -0
- package/templates-ui/public/next.svg +1 -0
- package/templates-ui/public/vercel.svg +1 -0
- package/templates-ui/public/window.svg +1 -0
- package/templates-ui/src/app/[category]/[name]/page.tsx +89 -0
- package/templates-ui/src/app/api/templates/[...slug]/route.ts +52 -0
- package/templates-ui/src/app/api/templates/route.ts +18 -0
- package/templates-ui/src/app/builder/page.tsx +37 -0
- package/templates-ui/src/app/globals.css +94 -0
- package/templates-ui/src/app/layout.tsx +79 -0
- package/templates-ui/src/app/page.tsx +38 -0
- package/templates-ui/src/app/robots.ts +11 -0
- package/templates-ui/src/app/sitemap.ts +31 -0
- package/templates-ui/src/components/compose-builder.tsx +442 -0
- package/templates-ui/src/components/compose-preview.tsx +117 -0
- package/templates-ui/src/components/file-preview.tsx +77 -0
- package/templates-ui/src/components/footer.tsx +40 -0
- package/templates-ui/src/components/header.tsx +41 -0
- package/templates-ui/src/components/template-card.tsx +87 -0
- package/templates-ui/src/components/template-detail.tsx +125 -0
- package/templates-ui/src/components/template-gallery.tsx +263 -0
- package/templates-ui/src/components/ui/badge.tsx +36 -0
- package/templates-ui/src/components/ui/button.tsx +57 -0
- package/templates-ui/src/components/ui/card.tsx +76 -0
- package/templates-ui/src/components/ui/separator.tsx +31 -0
- package/templates-ui/src/components/ui/tooltip.tsx +32 -0
- package/templates-ui/src/lib/compose-generator.ts +241 -0
- package/templates-ui/src/lib/templates.ts +137 -0
- package/templates-ui/src/lib/utils.ts +6 -0
- package/templates-ui/tsconfig.json +34 -0
- package/thoughts/research/2026-02-28-openfort-viem-x402-research.md +679 -0
- package/thoughts/research/2026-02-28-x402-payments-research.md +686 -0
- package/thoughts/researcher/plans/2026-02-20-agent-self-improvement-plan.md +282 -0
- package/thoughts/researcher/research/2026-02-20-agent-self-improvement.md +492 -0
- package/thoughts/shared/plans/2026-02-26-mcp-tool-context-reduction.md +282 -0
- package/thoughts/shared/plans/2026-03-02-claude-context-mode-integration.md +328 -0
- package/thoughts/shared/plans/2026-03-02-code-level-heartbeat.md +224 -0
- package/thoughts/shared/research/2026-02-26-cliffy-mcp-tools.md +159 -0
- package/thoughts/shared/research/2026-03-03-database-migration-system-refactor.md +337 -0
- package/thoughts/swarm-researcher/plans/2026-02-23-openclaw-improvements-plan.md +778 -0
- package/thoughts/swarm-researcher/plans/2026-02-26-artifacts-localtunnel-plan.md +1269 -0
- package/thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md +411 -0
- package/thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md +724 -0
- package/thoughts/taras/brainstorms/2026-03-20-prompt-template-registry.md +443 -0
- package/thoughts/taras/brainstorms/2026-03-20-setup-cli-onboarding.md +307 -0
- package/thoughts/taras/plans/2026-02-20-auto-improvement.md +803 -0
- package/thoughts/taras/plans/2026-02-20-env-management.md +538 -0
- package/thoughts/taras/plans/2026-02-20-memory-system.md +882 -0
- package/thoughts/taras/plans/2026-02-20-repos-knowledge.md +806 -0
- package/thoughts/taras/plans/2026-02-20-session-attach.md +647 -0
- package/thoughts/taras/plans/2026-02-20-worker-identity.md +820 -0
- package/thoughts/taras/plans/2026-02-25-feat-new-ui-visual-redesign-plan.md +768 -0
- package/thoughts/taras/plans/2026-03-04-fix-buildSystemPrompt-missing-fields.md +77 -0
- package/thoughts/taras/plans/2026-03-04-new-ui-missing-actions.md +543 -0
- package/thoughts/taras/plans/2026-03-06-one-time-scheduled-tasks.md +373 -0
- package/thoughts/taras/plans/2026-03-08-memory-self-improvement-enhancements.md +512 -0
- package/thoughts/taras/plans/2026-03-08-pi-mono-provider-implementation.md +919 -0
- package/thoughts/taras/plans/2026-03-09-templates-registry.md +723 -0
- package/thoughts/taras/plans/2026-03-10-task-working-directory.md +371 -0
- package/thoughts/taras/plans/2026-03-11-archil-per-agent-write-strategy.md +621 -0
- package/thoughts/taras/plans/2026-03-12-eliminate-inbox-route-to-tasks.md +61 -0
- package/thoughts/taras/plans/2026-03-12-slack-thread-followup-additive.md +488 -0
- package/thoughts/taras/plans/2026-03-13-slack-ai-improvements.md +644 -0
- package/thoughts/taras/plans/2026-03-16-route-wrapper-openapi.md +636 -0
- package/thoughts/taras/plans/2026-03-17-multi-api-config.md +444 -0
- package/thoughts/taras/plans/2026-03-18-agent-fs-integration.md +591 -0
- package/thoughts/taras/plans/2026-03-18-debug-db-explorer.md +446 -0
- package/thoughts/taras/plans/2026-03-18-workflow-redesign.md +987 -0
- package/thoughts/taras/plans/2026-03-19-compound-learnings.md +403 -0
- package/thoughts/taras/plans/2026-03-19-ticket-tracker-linear-integration.md +860 -0
- package/thoughts/taras/plans/2026-03-19-workflow-io-schemas-and-bugs.md +899 -0
- package/thoughts/taras/plans/2026-03-20-setup-cli-onboarding.md +874 -0
- package/thoughts/taras/plans/2026-03-20-workflow-structured-output-validation-workspace.md +723 -0
- package/thoughts/taras/research/2026-02-19-agent-native-swarm-architecture.md +390 -0
- package/thoughts/taras/research/2026-02-19-swarm-gaps-implementation.md +594 -0
- package/thoughts/taras/research/2026-02-25-dashboard-ui-design-best-practices.md +825 -0
- package/thoughts/taras/research/2026-02-26-task-detail-page-redesign.md +393 -0
- package/thoughts/taras/research/2026-03-03-new-ui-missing-actions.md +168 -0
- package/thoughts/taras/research/2026-03-05-pi-mono-provider-research.md +230 -0
- package/thoughts/taras/research/2026-03-06-workflow-engine-design.md +445 -0
- package/thoughts/taras/research/2026-03-08-drive-loop-concept.md +375 -0
- package/thoughts/taras/research/2026-03-08-pi-mono-deep-dive.md +869 -0
- package/thoughts/taras/research/2026-03-09-templates-registry.md +373 -0
- package/thoughts/taras/research/2026-03-10-agent-working-directory.md +223 -0
- package/thoughts/taras/research/2026-03-10-configurable-event-prompts.md +339 -0
- package/thoughts/taras/research/2026-03-11-archil-production-setup.md +181 -0
- package/thoughts/taras/research/2026-03-11-archil-shared-disk-write-strategies.md +437 -0
- package/thoughts/taras/research/2026-03-13-slack-ai-features.md +258 -0
- package/thoughts/taras/research/2026-03-16-openapi-docs-generation.md +335 -0
- package/thoughts/taras/research/2026-03-16-route-wrapper-openapi.md +670 -0
- package/thoughts/taras/research/2026-03-16-slack-thread-followups-e2e.md +54 -0
- package/thoughts/taras/research/2026-03-18-agent-fs-integration.md +558 -0
- package/thoughts/taras/research/2026-03-18-linear-integration-finalization.md +526 -0
- package/thoughts/taras/research/2026-03-18-workflow-redesign.md +797 -0
- package/thoughts/taras/research/2026-03-19-workflow-node-io-schemas-and-bugs.md +563 -0
- package/thoughts/taras/research/2026-03-19-workflow-structured-output-validation-workspace.md +486 -0
- package/thoughts/taras/research/2026-03-20-prompt-template-registry.md +469 -0
- package/tsconfig.json +1 -1
- package/FAQ.md +0 -19
- package/src/tests/get-inbox-message.test.ts +0 -145
- package/src/tools/get-inbox-message.ts +0 -89
- package/src/tools/inbox-delegate.ts +0 -113
- package/ui/CLAUDE.md +0 -49
- package/ui/bun.lock +0 -771
- package/ui/index.html +0 -22
- package/ui/package-lock.json +0 -5290
- package/ui/package.json +0 -33
- package/ui/pnpm-lock.yaml +0 -3341
- package/ui/postcss.config.js +0 -6
- package/ui/src/App.tsx +0 -63
- package/ui/src/components/ActivityFeed.tsx +0 -440
- package/ui/src/components/AgentDetailPanel.tsx +0 -733
- package/ui/src/components/AgentsPanel.tsx +0 -815
- package/ui/src/components/ChatPanel.tsx +0 -1920
- package/ui/src/components/ConfigModal.tsx +0 -253
- package/ui/src/components/Dashboard.tsx +0 -832
- package/ui/src/components/EditAgentProfileModal.tsx +0 -433
- package/ui/src/components/EpicDetailPage.tsx +0 -741
- package/ui/src/components/EpicsPanel.tsx +0 -566
- package/ui/src/components/Header.tsx +0 -160
- package/ui/src/components/JsonViewer.tsx +0 -171
- package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
- package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
- package/ui/src/components/ServicesPanel.tsx +0 -622
- package/ui/src/components/SessionLogPanel.tsx +0 -1219
- package/ui/src/components/StatsBar.tsx +0 -321
- package/ui/src/components/StatusBadge.tsx +0 -168
- package/ui/src/components/TaskDetailPanel.tsx +0 -903
- package/ui/src/components/TasksPanel.tsx +0 -614
- package/ui/src/components/UsageCharts.tsx +0 -216
- package/ui/src/components/UsageTab.tsx +0 -394
- package/ui/src/hooks/queries.ts +0 -353
- package/ui/src/index.css +0 -257
- package/ui/src/lib/api.ts +0 -268
- package/ui/src/lib/config.ts +0 -35
- package/ui/src/lib/contentPreview.ts +0 -208
- package/ui/src/lib/theme.ts +0 -214
- package/ui/src/main.tsx +0 -28
- package/ui/src/vite-env.d.ts +0 -1
- package/ui/tailwind.config.js +0 -37
- package/ui/vite.config.ts +0 -35
- /package/{ui → docs-site}/public/logo.png +0 -0
|
@@ -0,0 +1,1269 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-02-26
|
|
3
|
+
author: Researcher (16990304-76e4-4017-b991-f3e37b34cf73)
|
|
4
|
+
topic: "Implementation Plan: Agent Artifacts via Localtunnel"
|
|
5
|
+
status: ready
|
|
6
|
+
related_research: thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md
|
|
7
|
+
repo: desplega-ai/agent-swarm
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Implementation Plan: Agent Artifacts via Localtunnel
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
This plan implements the "artifacts" feature — agents can serve HTML pages, interactive apps, and rich content via public URLs using localtunnel tunnels to `lt.desplega.ai`. The feature enables agents to produce visual deliverables (dashboards, approval flows, data viewers) beyond text-only Slack/task outputs.
|
|
15
|
+
|
|
16
|
+
**Repo:** `desplega-ai/agent-swarm` (all work is in this repo)
|
|
17
|
+
|
|
18
|
+
> **Upstream dependencies:** The `maxSockets` bug fix in `desplega-ai/localtunnel-server` has already been merged. The custom username feature in the localtunnel client (`"hi"` → `"swarm"`) is deferred to post-MVP — we'll use the default `"hi"` username for now.
|
|
19
|
+
|
|
20
|
+
**Implementation order:** Phase 1 → 2 → 3 → 4 → 5
|
|
21
|
+
|
|
22
|
+
All 5 phases are in agent-swarm. Each phase is independently deployable and testable.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Current State
|
|
27
|
+
|
|
28
|
+
- Localtunnel infrastructure is live at `lt.desplega.ai` with wildcard DNS via Caddy
|
|
29
|
+
- Client (`@desplega.ai/localtunnel` v2.2.0) supports deterministic subdomains and `--auth` flag
|
|
30
|
+
- Server (`desplega-ai/localtunnel-server` v0.1.3) supports HTTP Basic Auth (timing-safe) and 409 conflict handling
|
|
31
|
+
- Agent IDs (UUIDs) work as subdomains — tested and confirmed (36-char with hyphens, 8-char prefix, 32-char no-hyphens all work)
|
|
32
|
+
- Auth uses hardcoded username `"hi"` — acceptable for MVP (deferred to post-MVP)
|
|
33
|
+
- ~~`maxSockets` bug in server~~ — **already merged** (fixed `ClientManager.js` to pass `maxTcpSockets`)
|
|
34
|
+
- No artifact-related code exists in agent-swarm yet
|
|
35
|
+
|
|
36
|
+
## Desired End State
|
|
37
|
+
|
|
38
|
+
- Agents can serve artifacts via `createArtifactServer()` from `src/artifact-sdk/`
|
|
39
|
+
- CLI provides `artifact serve|list|stop` commands
|
|
40
|
+
- Multiple artifacts per worker, each with dynamic port and unique subdomain (`{agentId}-{name}.lt.desplega.ai`)
|
|
41
|
+
- Browser SDK (`/@swarm/sdk.js`) enables HTML artifacts to call swarm API
|
|
42
|
+
- Proxy middleware (`/@swarm/api/*`) routes browser requests to MCP server
|
|
43
|
+
- `/artifacts` skill provides detailed usage docs, examples, and references (base prompt just mentions the skill)
|
|
44
|
+
- Artifact content stored in persisted paths (`/workspace/personal/artifacts/` by default)
|
|
45
|
+
- Stop hook auto-closes tunnels on session end
|
|
46
|
+
- Docker image includes `@desplega.ai/localtunnel` globally
|
|
47
|
+
|
|
48
|
+
## What We're NOT Doing
|
|
49
|
+
|
|
50
|
+
- No npm publishing of `artifact-sdk` — it lives in-repo
|
|
51
|
+
- No raw TCP/UDP tunneling — HTTP only
|
|
52
|
+
- No multi-agent artifact sharing (each agent owns its artifacts)
|
|
53
|
+
- No WebSocket support in the browser SDK (REST-only proxy for MVP)
|
|
54
|
+
- No HMAC-derived per-agent passwords (using shared API_KEY for MVP)
|
|
55
|
+
- No custom auth username for MVP — using default `"hi"` (deferred to post-MVP)
|
|
56
|
+
- No `artifact open` command — agents run headless in Docker, no browser to open
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Pre-MVP: Upstream Dependencies (Completed / Deferred)
|
|
61
|
+
|
|
62
|
+
### ~~Localtunnel Server — `maxSockets` Bug~~ ✅ MERGED
|
|
63
|
+
|
|
64
|
+
The property name mismatch (`maxSockets` → `maxTcpSockets`) in `desplega-ai/localtunnel-server` has already been fixed and merged. No action needed.
|
|
65
|
+
|
|
66
|
+
### Localtunnel Client — Custom Username ⏳ DEFERRED (post-MVP)
|
|
67
|
+
|
|
68
|
+
Adding a `username` option to `@desplega.ai/localtunnel` so we can use `"swarm"` instead of `"hi"` is deferred. For MVP, we use the default username `"hi"` — it works fine, just looks less polished in the auth prompt. Can be revisited after the core feature is working.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Phase 1: Artifact SDK — Core Module
|
|
73
|
+
|
|
74
|
+
**Goal:** Create `src/artifact-sdk/` in the agent-swarm repo with `createArtifactServer()`, dynamic port allocation, localtunnel integration, and service registry. Artifact content defaults to the persisted path `/workspace/personal/artifacts/`.
|
|
75
|
+
|
|
76
|
+
**Repo:** `desplega-ai/agent-swarm`
|
|
77
|
+
|
|
78
|
+
### Persisted Artifact Storage
|
|
79
|
+
|
|
80
|
+
Artifact content (HTML, JS, static files) must be stored in a persisted directory so it survives container restarts. Default: `/workspace/personal/artifacts/<artifact-name>/`.
|
|
81
|
+
|
|
82
|
+
- `/workspace/personal/` is a Docker volume — persists across sessions
|
|
83
|
+
- Each artifact gets a subdirectory: `/workspace/personal/artifacts/pr-review-42/`
|
|
84
|
+
- Agents can also use `/workspace/shared/artifacts/` for cross-agent visibility
|
|
85
|
+
- The SDK and CLI default to `/workspace/personal/artifacts/` when no explicit path is given
|
|
86
|
+
|
|
87
|
+
### Dependencies to add
|
|
88
|
+
|
|
89
|
+
**`package.json`** — Add runtime dependencies:
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"dependencies": {
|
|
93
|
+
"hono": "^4.0.0",
|
|
94
|
+
"@desplega.ai/localtunnel": "^2.2.0",
|
|
95
|
+
"get-port": "^7.0.0"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
> **Note:** `get-port` is a tiny ESM module that finds available TCP ports. Alternative: use Bun's `Bun.serve({ port: 0 })` which auto-assigns a free port.
|
|
101
|
+
|
|
102
|
+
### Files to create
|
|
103
|
+
|
|
104
|
+
**`src/artifact-sdk/index.ts`** — Public exports
|
|
105
|
+
```typescript
|
|
106
|
+
export { createArtifactServer } from './server';
|
|
107
|
+
export type { ArtifactServerOptions, ArtifactServer } from './server';
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**`src/artifact-sdk/port.ts`** — Dynamic port allocation
|
|
111
|
+
```typescript
|
|
112
|
+
import { createServer } from 'node:net';
|
|
113
|
+
|
|
114
|
+
export async function getAvailablePort(): Promise<number> {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
const server = createServer();
|
|
117
|
+
server.listen(0, () => {
|
|
118
|
+
const { port } = server.address() as { port: number };
|
|
119
|
+
server.close(() => resolve(port));
|
|
120
|
+
});
|
|
121
|
+
server.on('error', reject);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**`src/artifact-sdk/tunnel.ts`** — Localtunnel wrapper
|
|
127
|
+
```typescript
|
|
128
|
+
import localtunnel from '@desplega.ai/localtunnel';
|
|
129
|
+
|
|
130
|
+
interface TunnelOptions {
|
|
131
|
+
port: number;
|
|
132
|
+
subdomain: string;
|
|
133
|
+
auth?: string;
|
|
134
|
+
username?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function createTunnel(opts: TunnelOptions) {
|
|
138
|
+
const tunnel = await localtunnel({
|
|
139
|
+
port: opts.port,
|
|
140
|
+
subdomain: opts.subdomain,
|
|
141
|
+
auth: opts.auth,
|
|
142
|
+
username: opts.username || 'hi', // default 'hi' for MVP (custom username deferred)
|
|
143
|
+
// host defaults to lt.desplega.ai in our fork
|
|
144
|
+
});
|
|
145
|
+
return tunnel;
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**`src/artifact-sdk/proxy.ts`** — `/@swarm/api/*` proxy middleware for Hono
|
|
150
|
+
```typescript
|
|
151
|
+
import { Hono } from 'hono';
|
|
152
|
+
|
|
153
|
+
export function swarmProxyMiddleware(mcpBaseUrl: string, apiKey: string, agentId: string) {
|
|
154
|
+
const proxy = new Hono();
|
|
155
|
+
|
|
156
|
+
proxy.all('/@swarm/api/*', async (c) => {
|
|
157
|
+
const path = c.req.path.replace('/@swarm/api', '');
|
|
158
|
+
const targetUrl = `${mcpBaseUrl}${path}`;
|
|
159
|
+
const res = await fetch(targetUrl, {
|
|
160
|
+
method: c.req.method,
|
|
161
|
+
headers: {
|
|
162
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
163
|
+
'X-Agent-ID': agentId,
|
|
164
|
+
'Content-Type': 'application/json',
|
|
165
|
+
},
|
|
166
|
+
body: c.req.method !== 'GET' ? await c.req.text() : undefined,
|
|
167
|
+
});
|
|
168
|
+
return new Response(res.body, {
|
|
169
|
+
status: res.status,
|
|
170
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
return proxy;
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**`src/artifact-sdk/browser-sdk.ts`** — Browser SDK source (served as `/@swarm/sdk.js`)
|
|
179
|
+
```typescript
|
|
180
|
+
// This is a string template that gets served as JavaScript to the browser
|
|
181
|
+
export const BROWSER_SDK_JS = `
|
|
182
|
+
class SwarmSDK {
|
|
183
|
+
constructor() {
|
|
184
|
+
this._configPromise = fetch('/@swarm/config').then(r => r.json());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async createTask(opts) { return this._post('/@swarm/api/tasks', opts); }
|
|
188
|
+
async getTasks(filters) { return this._get('/@swarm/api/tasks?' + new URLSearchParams(filters)); }
|
|
189
|
+
async getTaskDetails(id) { return this._get('/@swarm/api/tasks/' + id); }
|
|
190
|
+
async postMessage(opts) { return this._post('/@swarm/api/messages', opts); }
|
|
191
|
+
async readMessages(opts) { return this._get('/@swarm/api/messages?' + new URLSearchParams(opts)); }
|
|
192
|
+
async getSwarm() { return this._get('/@swarm/api/agents'); }
|
|
193
|
+
|
|
194
|
+
async _post(url, body) {
|
|
195
|
+
const res = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
|
196
|
+
return res.json();
|
|
197
|
+
}
|
|
198
|
+
async _get(url) {
|
|
199
|
+
const res = await fetch(url);
|
|
200
|
+
return res.json();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
window.SwarmSDK = SwarmSDK;
|
|
205
|
+
`;
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**`src/artifact-sdk/server.ts`** — Main `createArtifactServer()` implementation
|
|
209
|
+
```typescript
|
|
210
|
+
import { Hono } from 'hono';
|
|
211
|
+
import { serveStatic } from 'hono/bun';
|
|
212
|
+
import { getAvailablePort } from './port';
|
|
213
|
+
import { createTunnel } from './tunnel';
|
|
214
|
+
import { swarmProxyMiddleware } from './proxy';
|
|
215
|
+
import { BROWSER_SDK_JS } from './browser-sdk';
|
|
216
|
+
|
|
217
|
+
export interface ArtifactServerOptions {
|
|
218
|
+
name: string;
|
|
219
|
+
static?: string;
|
|
220
|
+
app?: Hono;
|
|
221
|
+
port?: number;
|
|
222
|
+
auth?: boolean;
|
|
223
|
+
subdomain?: string;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface ArtifactServer {
|
|
227
|
+
start(): Promise<void>;
|
|
228
|
+
stop(): Promise<void>;
|
|
229
|
+
url: string;
|
|
230
|
+
port: number;
|
|
231
|
+
tunnel: any;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function createArtifactServer(opts: ArtifactServerOptions): ArtifactServer {
|
|
235
|
+
const agentId = process.env.AGENT_ID || 'unknown';
|
|
236
|
+
const apiKey = process.env.API_KEY || '';
|
|
237
|
+
const mcpBaseUrl = process.env.MCP_BASE_URL || 'http://localhost:3013';
|
|
238
|
+
|
|
239
|
+
const app = new Hono();
|
|
240
|
+
|
|
241
|
+
// Inject swarm middleware
|
|
242
|
+
app.get('/@swarm/sdk.js', (c) => {
|
|
243
|
+
c.header('Content-Type', 'application/javascript');
|
|
244
|
+
return c.body(BROWSER_SDK_JS);
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
app.get('/@swarm/config', (c) => {
|
|
248
|
+
return c.json({ agentId, artifactName: opts.name });
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// API proxy
|
|
252
|
+
app.all('/@swarm/api/*', async (c) => {
|
|
253
|
+
const path = c.req.path.replace('/@swarm/api', '/api');
|
|
254
|
+
const targetUrl = `${mcpBaseUrl}${path}`;
|
|
255
|
+
const headers: Record<string, string> = {
|
|
256
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
257
|
+
'X-Agent-ID': agentId,
|
|
258
|
+
};
|
|
259
|
+
if (c.req.method !== 'GET') {
|
|
260
|
+
headers['Content-Type'] = 'application/json';
|
|
261
|
+
}
|
|
262
|
+
const res = await fetch(targetUrl, {
|
|
263
|
+
method: c.req.method,
|
|
264
|
+
headers,
|
|
265
|
+
body: c.req.method !== 'GET' ? await c.req.text() : undefined,
|
|
266
|
+
});
|
|
267
|
+
return new Response(res.body, { status: res.status });
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// User app or static serving
|
|
271
|
+
if (opts.app) {
|
|
272
|
+
app.route('/', opts.app);
|
|
273
|
+
} else if (opts.static) {
|
|
274
|
+
app.use('/*', serveStatic({ root: opts.static }));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
278
|
+
let tunnel: any = null;
|
|
279
|
+
let actualPort = 0;
|
|
280
|
+
|
|
281
|
+
const artifact: ArtifactServer = {
|
|
282
|
+
url: '',
|
|
283
|
+
port: 0,
|
|
284
|
+
tunnel: null,
|
|
285
|
+
|
|
286
|
+
async start() {
|
|
287
|
+
actualPort = opts.port || await getAvailablePort();
|
|
288
|
+
server = Bun.serve({ port: actualPort, fetch: app.fetch });
|
|
289
|
+
artifact.port = actualPort;
|
|
290
|
+
|
|
291
|
+
const subdomain = opts.subdomain || `${agentId}-${opts.name}`;
|
|
292
|
+
const authPassword = opts.auth === false ? undefined : apiKey;
|
|
293
|
+
|
|
294
|
+
tunnel = await createTunnel({
|
|
295
|
+
port: actualPort,
|
|
296
|
+
subdomain,
|
|
297
|
+
auth: authPassword,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
artifact.url = tunnel.url;
|
|
301
|
+
artifact.tunnel = tunnel;
|
|
302
|
+
|
|
303
|
+
// Register in service registry
|
|
304
|
+
try {
|
|
305
|
+
await fetch(`${mcpBaseUrl}/api/services`, {
|
|
306
|
+
method: 'POST',
|
|
307
|
+
headers: {
|
|
308
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
309
|
+
'X-Agent-ID': agentId,
|
|
310
|
+
'Content-Type': 'application/json',
|
|
311
|
+
},
|
|
312
|
+
body: JSON.stringify({
|
|
313
|
+
script: `artifact-${opts.name}`,
|
|
314
|
+
metadata: {
|
|
315
|
+
type: 'artifact',
|
|
316
|
+
artifactName: opts.name,
|
|
317
|
+
port: actualPort,
|
|
318
|
+
publicUrl: tunnel.url,
|
|
319
|
+
},
|
|
320
|
+
}),
|
|
321
|
+
});
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.warn('Failed to register artifact in service registry:', e);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
console.log(`Artifact "${opts.name}" live at ${tunnel.url} (port ${actualPort})`);
|
|
327
|
+
},
|
|
328
|
+
|
|
329
|
+
async stop() {
|
|
330
|
+
if (tunnel) {
|
|
331
|
+
await tunnel.close();
|
|
332
|
+
tunnel = null;
|
|
333
|
+
}
|
|
334
|
+
if (server) {
|
|
335
|
+
server.stop();
|
|
336
|
+
server = null;
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
return artifact;
|
|
342
|
+
}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Success Criteria
|
|
346
|
+
|
|
347
|
+
#### Automated Verification
|
|
348
|
+
- [ ] `bun run typecheck` passes (no TS errors in new files)
|
|
349
|
+
- [ ] `bun run lint` passes (biome lint)
|
|
350
|
+
- [ ] `bun test` passes (existing tests unaffected)
|
|
351
|
+
|
|
352
|
+
#### Manual Verification
|
|
353
|
+
|
|
354
|
+
**Test 1: Dynamic port allocation**
|
|
355
|
+
```bash
|
|
356
|
+
cd /workspace/repos/agent-swarm
|
|
357
|
+
|
|
358
|
+
# Create a test script
|
|
359
|
+
cat > /tmp/test-artifact-port.ts << 'EOF'
|
|
360
|
+
import { getAvailablePort } from './src/artifact-sdk/port';
|
|
361
|
+
|
|
362
|
+
const port1 = await getAvailablePort();
|
|
363
|
+
const port2 = await getAvailablePort();
|
|
364
|
+
console.log(`Port 1: ${port1}`);
|
|
365
|
+
console.log(`Port 2: ${port2}`);
|
|
366
|
+
console.assert(port1 !== port2, 'Ports should be different');
|
|
367
|
+
console.assert(port1 > 0 && port1 < 65536, 'Port 1 in valid range');
|
|
368
|
+
console.assert(port2 > 0 && port2 < 65536, 'Port 2 in valid range');
|
|
369
|
+
console.log('PASS: Dynamic port allocation works');
|
|
370
|
+
EOF
|
|
371
|
+
|
|
372
|
+
bun run /tmp/test-artifact-port.ts
|
|
373
|
+
# Expected output:
|
|
374
|
+
# Port 1: <some number, e.g. 49152>
|
|
375
|
+
# Port 2: <different number, e.g. 49153>
|
|
376
|
+
# PASS: Dynamic port allocation works
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
**Test 2: Artifact server starts and serves content**
|
|
380
|
+
```bash
|
|
381
|
+
# Create test content
|
|
382
|
+
mkdir -p /tmp/test-artifact
|
|
383
|
+
echo '<h1>Test Artifact</h1>' > /tmp/test-artifact/index.html
|
|
384
|
+
|
|
385
|
+
# Create test script
|
|
386
|
+
cat > /tmp/test-artifact-serve.ts << 'EOF'
|
|
387
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
388
|
+
|
|
389
|
+
const server = createArtifactServer({
|
|
390
|
+
name: 'test-artifact',
|
|
391
|
+
static: '/tmp/test-artifact',
|
|
392
|
+
auth: false, // disable tunnel auth for local test
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
await server.start();
|
|
396
|
+
console.log(`Server running on port ${server.port}`);
|
|
397
|
+
console.log(`URL: ${server.url}`);
|
|
398
|
+
|
|
399
|
+
// Test local access
|
|
400
|
+
const res = await fetch(`http://localhost:${server.port}/`);
|
|
401
|
+
const body = await res.text();
|
|
402
|
+
console.log(`Status: ${res.status}`);
|
|
403
|
+
console.log(`Body: ${body}`);
|
|
404
|
+
console.assert(res.status === 200, 'Expected HTTP 200');
|
|
405
|
+
console.assert(body.includes('Test Artifact'), 'Expected body to contain "Test Artifact"');
|
|
406
|
+
|
|
407
|
+
// Test /@swarm/config endpoint
|
|
408
|
+
const configRes = await fetch(`http://localhost:${server.port}/@swarm/config`);
|
|
409
|
+
const config = await configRes.json();
|
|
410
|
+
console.log(`Config: ${JSON.stringify(config)}`);
|
|
411
|
+
console.assert(config.artifactName === 'test-artifact', 'Expected artifactName');
|
|
412
|
+
|
|
413
|
+
// Test /@swarm/sdk.js endpoint
|
|
414
|
+
const sdkRes = await fetch(`http://localhost:${server.port}/@swarm/sdk.js`);
|
|
415
|
+
console.assert(sdkRes.status === 200, 'SDK endpoint returns 200');
|
|
416
|
+
console.assert(sdkRes.headers.get('content-type')?.includes('javascript'), 'SDK has JS content type');
|
|
417
|
+
|
|
418
|
+
await server.stop();
|
|
419
|
+
console.log('PASS: Artifact server starts and serves content');
|
|
420
|
+
EOF
|
|
421
|
+
|
|
422
|
+
cd /workspace/repos/agent-swarm
|
|
423
|
+
AGENT_ID=test-agent API_KEY=test-key bun run /tmp/test-artifact-serve.ts
|
|
424
|
+
# Expected output:
|
|
425
|
+
# Server running on port <dynamic>
|
|
426
|
+
# URL: https://test-agent-test-artifact.lt.desplega.ai
|
|
427
|
+
# Status: 200
|
|
428
|
+
# Body: <h1>Test Artifact</h1>
|
|
429
|
+
# Config: {"agentId":"test-agent","artifactName":"test-artifact"}
|
|
430
|
+
# PASS: Artifact server starts and serves content
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Test 3: Multiple artifacts on different ports**
|
|
434
|
+
```bash
|
|
435
|
+
cat > /tmp/test-multi-artifact.ts << 'EOF'
|
|
436
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
437
|
+
|
|
438
|
+
const artifact1 = createArtifactServer({ name: 'app-one', static: '/tmp/test-artifact' });
|
|
439
|
+
const artifact2 = createArtifactServer({ name: 'app-two', static: '/tmp/test-artifact' });
|
|
440
|
+
|
|
441
|
+
await artifact1.start();
|
|
442
|
+
await artifact2.start();
|
|
443
|
+
|
|
444
|
+
console.log(`Artifact 1: port=${artifact1.port}, url=${artifact1.url}`);
|
|
445
|
+
console.log(`Artifact 2: port=${artifact2.port}, url=${artifact2.url}`);
|
|
446
|
+
|
|
447
|
+
console.assert(artifact1.port !== artifact2.port, 'Ports should differ');
|
|
448
|
+
console.assert(artifact1.url !== artifact2.url, 'URLs should differ');
|
|
449
|
+
console.assert(artifact1.url.includes('app-one'), 'URL 1 should contain artifact name');
|
|
450
|
+
console.assert(artifact2.url.includes('app-two'), 'URL 2 should contain artifact name');
|
|
451
|
+
|
|
452
|
+
// Both should serve content
|
|
453
|
+
const res1 = await fetch(`http://localhost:${artifact1.port}/`);
|
|
454
|
+
const res2 = await fetch(`http://localhost:${artifact2.port}/`);
|
|
455
|
+
console.assert(res1.status === 200, 'Artifact 1 serves content');
|
|
456
|
+
console.assert(res2.status === 200, 'Artifact 2 serves content');
|
|
457
|
+
|
|
458
|
+
await artifact1.stop();
|
|
459
|
+
await artifact2.stop();
|
|
460
|
+
console.log('PASS: Multiple artifacts on different ports');
|
|
461
|
+
EOF
|
|
462
|
+
|
|
463
|
+
cd /workspace/repos/agent-swarm
|
|
464
|
+
AGENT_ID=test-agent API_KEY=test-key bun run /tmp/test-multi-artifact.ts
|
|
465
|
+
# Expected:
|
|
466
|
+
# Artifact 1: port=<N>, url=https://test-agent-app-one.lt.desplega.ai
|
|
467
|
+
# Artifact 2: port=<M>, url=https://test-agent-app-two.lt.desplega.ai
|
|
468
|
+
# PASS: Multiple artifacts on different ports
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
**Test 4: Tunnel connectivity (end-to-end via lt.desplega.ai)**
|
|
472
|
+
```bash
|
|
473
|
+
cat > /tmp/test-artifact-e2e.ts << 'EOF'
|
|
474
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
475
|
+
import { Hono } from 'hono';
|
|
476
|
+
|
|
477
|
+
const app = new Hono();
|
|
478
|
+
app.get('/', (c) => c.text('Hello from artifact E2E test!'));
|
|
479
|
+
|
|
480
|
+
const server = createArtifactServer({
|
|
481
|
+
name: 'e2e-test',
|
|
482
|
+
app,
|
|
483
|
+
auth: false, // simpler for testing
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await server.start();
|
|
487
|
+
console.log(`Artifact URL: ${server.url}`);
|
|
488
|
+
|
|
489
|
+
// Wait for tunnel to stabilize
|
|
490
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
491
|
+
|
|
492
|
+
// Test via public URL
|
|
493
|
+
const res = await fetch(server.url, {
|
|
494
|
+
headers: { 'Bypass-Tunnel-Reminder': 'true' }
|
|
495
|
+
});
|
|
496
|
+
const body = await res.text();
|
|
497
|
+
console.log(`Public URL status: ${res.status}`);
|
|
498
|
+
console.log(`Public URL body: ${body}`);
|
|
499
|
+
console.assert(res.status === 200, 'Expected HTTP 200 via tunnel');
|
|
500
|
+
console.assert(body.includes('Hello from artifact E2E test'), 'Expected correct body via tunnel');
|
|
501
|
+
|
|
502
|
+
await server.stop();
|
|
503
|
+
console.log('PASS: End-to-end tunnel connectivity');
|
|
504
|
+
EOF
|
|
505
|
+
|
|
506
|
+
cd /workspace/repos/agent-swarm
|
|
507
|
+
AGENT_ID=$(cat /proc/sys/kernel/random/uuid | tr -d '-' | head -c 32) \
|
|
508
|
+
API_KEY=test-key \
|
|
509
|
+
bun run /tmp/test-artifact-e2e.ts
|
|
510
|
+
# Expected:
|
|
511
|
+
# Artifact URL: https://<uuid>-e2e-test.lt.desplega.ai
|
|
512
|
+
# Public URL status: 200
|
|
513
|
+
# Public URL body: Hello from artifact E2E test!
|
|
514
|
+
# PASS: End-to-end tunnel connectivity
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
---
|
|
518
|
+
|
|
519
|
+
## Phase 2: CLI `artifact` Command
|
|
520
|
+
|
|
521
|
+
**Goal:** Add `artifact serve|list|stop` subcommands to the agent-swarm CLI.
|
|
522
|
+
|
|
523
|
+
**Repo:** `desplega-ai/agent-swarm`
|
|
524
|
+
|
|
525
|
+
### Files to create/modify
|
|
526
|
+
|
|
527
|
+
**`src/commands/artifact.ts`** — NEW: Artifact command module
|
|
528
|
+
|
|
529
|
+
Implements 3 subcommands:
|
|
530
|
+
- `serve <path> --name <name> [--port <port>] [--no-auth] [--subdomain <sub>]`
|
|
531
|
+
- `list` — queries service registry for `metadata.type === "artifact"`
|
|
532
|
+
- `stop <name>` — stops PM2 process, closes tunnel, unregisters service
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
// Pattern follows existing commands — export an async function
|
|
536
|
+
export async function runArtifact(subcommand: string, args: Record<string, any>) {
|
|
537
|
+
switch (subcommand) {
|
|
538
|
+
case 'serve': return artifactServe(args);
|
|
539
|
+
case 'list': return artifactList(args);
|
|
540
|
+
case 'stop': return artifactStop(args);
|
|
541
|
+
default: console.error(`Unknown artifact subcommand: ${subcommand}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
For `serve`:
|
|
547
|
+
1. If path is a directory → create a Hono app with `serveStatic`
|
|
548
|
+
2. If path is a `.js`/`.ts` file → start via PM2 (`pm2 start <script> --name artifact-<name>`)
|
|
549
|
+
3. Create tunnel via artifact SDK
|
|
550
|
+
4. Register service
|
|
551
|
+
|
|
552
|
+
For `list`:
|
|
553
|
+
1. Call `GET /api/services` with auth headers
|
|
554
|
+
2. Filter for `metadata.type === "artifact"`
|
|
555
|
+
3. Format as table output
|
|
556
|
+
|
|
557
|
+
For `stop`:
|
|
558
|
+
1. Find artifact in service registry by name
|
|
559
|
+
2. `pm2 delete artifact-<name>` (via child_process)
|
|
560
|
+
3. Call `DELETE /api/services/:id` to unregister
|
|
561
|
+
|
|
562
|
+
**`src/cli.tsx`** — Add dispatch for `artifact` command
|
|
563
|
+
|
|
564
|
+
In the `App` component's command switch (around line 562-598):
|
|
565
|
+
```typescript
|
|
566
|
+
case "artifact":
|
|
567
|
+
// artifact commands don't need Ink rendering
|
|
568
|
+
const { runArtifact } = await import('./commands/artifact');
|
|
569
|
+
await runArtifact(args.additionalArgs?.[0] || 'help', args);
|
|
570
|
+
exit();
|
|
571
|
+
break;
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Success Criteria
|
|
575
|
+
|
|
576
|
+
#### Automated Verification
|
|
577
|
+
- [ ] `bun run typecheck` passes
|
|
578
|
+
- [ ] `bun run lint` passes
|
|
579
|
+
- [ ] `bun test` passes
|
|
580
|
+
|
|
581
|
+
#### Manual Verification
|
|
582
|
+
|
|
583
|
+
**Test 1: `artifact serve` with static directory**
|
|
584
|
+
```bash
|
|
585
|
+
cd /workspace/repos/agent-swarm
|
|
586
|
+
|
|
587
|
+
# Create test content in persisted path
|
|
588
|
+
mkdir -p /workspace/personal/artifacts/cli-test
|
|
589
|
+
echo '<h1>CLI Artifact Test</h1>' > /workspace/personal/artifacts/cli-test/index.html
|
|
590
|
+
|
|
591
|
+
# Run the CLI command
|
|
592
|
+
AGENT_ID=test-cli-agent API_KEY=test-key \
|
|
593
|
+
bun run src/cli.tsx artifact serve /workspace/personal/artifacts/cli-test --name cli-test
|
|
594
|
+
|
|
595
|
+
# Expected output:
|
|
596
|
+
# Artifact "cli-test" live at https://test-cli-agent-cli-test.lt.desplega.ai (port <dynamic>)
|
|
597
|
+
|
|
598
|
+
# In another terminal, verify:
|
|
599
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
600
|
+
"https://test-cli-agent-cli-test.lt.desplega.ai"
|
|
601
|
+
# Expected: HTTP 200, body contains "<h1>CLI Artifact Test</h1>"
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
**Test 2: `artifact list`**
|
|
605
|
+
```bash
|
|
606
|
+
# While artifact from Test 1 is running:
|
|
607
|
+
AGENT_ID=test-cli-agent API_KEY=test-key MCP_BASE_URL=http://localhost:3013 \
|
|
608
|
+
bun run src/cli.tsx artifact list
|
|
609
|
+
|
|
610
|
+
# Expected output (table format):
|
|
611
|
+
# NAME AGENT PORT URL STATUS
|
|
612
|
+
# cli-test test-cli-agent <N> https://test-cli-agent-cli-test.lt.desplega.ai healthy
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Test 3: `artifact stop`**
|
|
616
|
+
```bash
|
|
617
|
+
AGENT_ID=test-cli-agent API_KEY=test-key MCP_BASE_URL=http://localhost:3013 \
|
|
618
|
+
bun run src/cli.tsx artifact stop cli-test
|
|
619
|
+
|
|
620
|
+
# Expected: "Artifact 'cli-test' stopped."
|
|
621
|
+
|
|
622
|
+
# Verify it's gone:
|
|
623
|
+
bun run src/cli.tsx artifact list
|
|
624
|
+
# Expected: empty list or "No active artifacts"
|
|
625
|
+
|
|
626
|
+
# Verify tunnel is closed:
|
|
627
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
628
|
+
"https://test-cli-agent-cli-test.lt.desplega.ai"
|
|
629
|
+
# Expected: connection error or 502 (tunnel no longer exists)
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
**Test 4: `artifact serve` with custom Hono app**
|
|
633
|
+
```bash
|
|
634
|
+
cat > /tmp/test-custom-server.ts << 'EOF'
|
|
635
|
+
import { Hono } from 'hono';
|
|
636
|
+
const app = new Hono();
|
|
637
|
+
app.get('/', (c) => c.text('Custom server works!'));
|
|
638
|
+
app.get('/api/data', (c) => c.json({ items: [1, 2, 3] }));
|
|
639
|
+
export default app;
|
|
640
|
+
EOF
|
|
641
|
+
|
|
642
|
+
AGENT_ID=test-cli-agent API_KEY=test-key \
|
|
643
|
+
bun run src/cli.tsx artifact serve /tmp/test-custom-server.ts --name custom-app
|
|
644
|
+
|
|
645
|
+
# Verify custom routes work
|
|
646
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
647
|
+
"https://test-cli-agent-custom-app.lt.desplega.ai"
|
|
648
|
+
# Expected: "Custom server works!"
|
|
649
|
+
|
|
650
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
651
|
+
"https://test-cli-agent-custom-app.lt.desplega.ai/api/data"
|
|
652
|
+
# Expected: {"items":[1,2,3]}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
---
|
|
656
|
+
|
|
657
|
+
## Phase 3: Browser SDK + API Proxy
|
|
658
|
+
|
|
659
|
+
**Goal:** Build the browser-side SDK (`/@swarm/sdk.js`) and the `/@swarm/api/*` proxy middleware that allows HTML artifacts to call swarm API endpoints.
|
|
660
|
+
|
|
661
|
+
**Repo:** `desplega-ai/agent-swarm`
|
|
662
|
+
|
|
663
|
+
### Files to modify
|
|
664
|
+
|
|
665
|
+
This phase primarily refines `src/artifact-sdk/browser-sdk.ts` and `src/artifact-sdk/proxy.ts` created in Phase 3.
|
|
666
|
+
|
|
667
|
+
**`src/artifact-sdk/browser-sdk.ts`** — Expand with full swarm API coverage:
|
|
668
|
+
- `createTask(opts)` → `POST /@swarm/api/tasks`
|
|
669
|
+
- `getTasks(filters)` → `GET /@swarm/api/tasks`
|
|
670
|
+
- `getTaskDetails(id)` → `GET /@swarm/api/tasks/:id`
|
|
671
|
+
- `storeProgress(taskId, data)` → `POST /@swarm/api/tasks/:id/progress`
|
|
672
|
+
- `postMessage(opts)` → `POST /@swarm/api/messages`
|
|
673
|
+
- `readMessages(opts)` → `GET /@swarm/api/messages`
|
|
674
|
+
- `getSwarm()` → `GET /@swarm/api/agents`
|
|
675
|
+
- `listServices()` → `GET /@swarm/api/services`
|
|
676
|
+
- `listEpics(opts)` → `GET /@swarm/api/epics`
|
|
677
|
+
- `slackReply(opts)` → `POST /@swarm/api/slack/reply`
|
|
678
|
+
|
|
679
|
+
**`src/artifact-sdk/proxy.ts`** — Ensure correct header forwarding and error handling:
|
|
680
|
+
- Forward `Authorization` and `X-Agent-ID` headers
|
|
681
|
+
- Handle non-JSON responses (file downloads, etc.)
|
|
682
|
+
- Return proper CORS headers for browser requests
|
|
683
|
+
- Handle proxy errors gracefully (return 502 with error message)
|
|
684
|
+
|
|
685
|
+
### Success Criteria
|
|
686
|
+
|
|
687
|
+
#### Automated Verification
|
|
688
|
+
- [ ] `bun run typecheck` passes
|
|
689
|
+
- [ ] `bun run lint` passes
|
|
690
|
+
|
|
691
|
+
#### Manual Verification
|
|
692
|
+
|
|
693
|
+
**Test 1: SDK serves correctly and is executable in browser context**
|
|
694
|
+
```bash
|
|
695
|
+
cat > /tmp/test-sdk-serve.ts << 'EOF'
|
|
696
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
697
|
+
import { Hono } from 'hono';
|
|
698
|
+
|
|
699
|
+
const app = new Hono();
|
|
700
|
+
app.get('/', (c) => c.html(`
|
|
701
|
+
<!DOCTYPE html>
|
|
702
|
+
<html>
|
|
703
|
+
<head><title>SDK Test</title></head>
|
|
704
|
+
<body>
|
|
705
|
+
<h1>SDK Test Page</h1>
|
|
706
|
+
<div id="result"></div>
|
|
707
|
+
<script src="/@swarm/sdk.js"></script>
|
|
708
|
+
<script>
|
|
709
|
+
const swarm = new SwarmSDK();
|
|
710
|
+
document.getElementById('result').textContent = 'SDK loaded: ' + (typeof SwarmSDK);
|
|
711
|
+
</script>
|
|
712
|
+
</body>
|
|
713
|
+
</html>
|
|
714
|
+
`));
|
|
715
|
+
|
|
716
|
+
const server = createArtifactServer({ name: 'sdk-test', app, auth: false });
|
|
717
|
+
await server.start();
|
|
718
|
+
console.log('SDK test server at', server.url);
|
|
719
|
+
console.log('Local: http://localhost:' + server.port);
|
|
720
|
+
|
|
721
|
+
// Verify SDK endpoint returns JavaScript
|
|
722
|
+
const sdkRes = await fetch(`http://localhost:${server.port}/@swarm/sdk.js`);
|
|
723
|
+
const sdkBody = await sdkRes.text();
|
|
724
|
+
console.assert(sdkRes.status === 200, 'SDK returns 200');
|
|
725
|
+
console.assert(sdkBody.includes('class SwarmSDK'), 'SDK contains SwarmSDK class');
|
|
726
|
+
console.assert(sdkBody.includes('createTask'), 'SDK contains createTask method');
|
|
727
|
+
console.log('SDK content length:', sdkBody.length, 'bytes');
|
|
728
|
+
|
|
729
|
+
// Verify config endpoint
|
|
730
|
+
const configRes = await fetch(`http://localhost:${server.port}/@swarm/config`);
|
|
731
|
+
const config = await configRes.json();
|
|
732
|
+
console.assert(config.artifactName === 'sdk-test', 'Config has correct artifact name');
|
|
733
|
+
|
|
734
|
+
await server.stop();
|
|
735
|
+
console.log('PASS: Browser SDK serves correctly');
|
|
736
|
+
EOF
|
|
737
|
+
|
|
738
|
+
cd /workspace/repos/agent-swarm
|
|
739
|
+
AGENT_ID=test-agent API_KEY=test-key bun run /tmp/test-sdk-serve.ts
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
**Test 2: API proxy forwards requests correctly**
|
|
743
|
+
```bash
|
|
744
|
+
cat > /tmp/test-proxy.ts << 'EOF'
|
|
745
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
746
|
+
import { Hono } from 'hono';
|
|
747
|
+
|
|
748
|
+
const app = new Hono();
|
|
749
|
+
app.get('/', (c) => c.text('Proxy test'));
|
|
750
|
+
|
|
751
|
+
const server = createArtifactServer({ name: 'proxy-test', app, auth: false });
|
|
752
|
+
await server.start();
|
|
753
|
+
|
|
754
|
+
// Test proxy endpoint — this will try to reach MCP_BASE_URL
|
|
755
|
+
// In test env, MCP might not be available, so we check the proxy behavior
|
|
756
|
+
const proxyRes = await fetch(`http://localhost:${server.port}/@swarm/api/agents`);
|
|
757
|
+
console.log(`Proxy status: ${proxyRes.status}`);
|
|
758
|
+
// Expected: 200 (if MCP server is reachable) or 502 (if not)
|
|
759
|
+
// Either way, the proxy should not crash
|
|
760
|
+
|
|
761
|
+
const proxyRes2 = await fetch(`http://localhost:${server.port}/@swarm/api/tasks`, {
|
|
762
|
+
method: 'POST',
|
|
763
|
+
headers: { 'Content-Type': 'application/json' },
|
|
764
|
+
body: JSON.stringify({ task: 'test task from proxy' }),
|
|
765
|
+
});
|
|
766
|
+
console.log(`Proxy POST status: ${proxyRes2.status}`);
|
|
767
|
+
|
|
768
|
+
await server.stop();
|
|
769
|
+
console.log('PASS: API proxy handles requests');
|
|
770
|
+
EOF
|
|
771
|
+
|
|
772
|
+
cd /workspace/repos/agent-swarm
|
|
773
|
+
AGENT_ID=test-agent API_KEY=test-key MCP_BASE_URL=http://localhost:3013 \
|
|
774
|
+
bun run /tmp/test-proxy.ts
|
|
775
|
+
# Expected: proxy returns status codes (200 if MCP available, 502 if not)
|
|
776
|
+
# Key check: no crashes, graceful error handling
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
**Test 3: End-to-end browser SDK via tunnel (live MCP server)**
|
|
780
|
+
```bash
|
|
781
|
+
# This test requires a running MCP server — run on a worker container
|
|
782
|
+
cat > /tmp/test-sdk-e2e.ts << 'EOF'
|
|
783
|
+
import { createArtifactServer } from './src/artifact-sdk';
|
|
784
|
+
import { Hono } from 'hono';
|
|
785
|
+
|
|
786
|
+
const app = new Hono();
|
|
787
|
+
app.get('/', (c) => c.html(`
|
|
788
|
+
<html>
|
|
789
|
+
<body>
|
|
790
|
+
<h1>E2E SDK Test</h1>
|
|
791
|
+
<script src="/@swarm/sdk.js"></script>
|
|
792
|
+
<script>
|
|
793
|
+
(async () => {
|
|
794
|
+
const swarm = new SwarmSDK();
|
|
795
|
+
try {
|
|
796
|
+
const agents = await swarm.getSwarm();
|
|
797
|
+
document.body.innerHTML += '<p>Agents: ' + JSON.stringify(agents) + '</p>';
|
|
798
|
+
} catch(e) {
|
|
799
|
+
document.body.innerHTML += '<p>Error: ' + e.message + '</p>';
|
|
800
|
+
}
|
|
801
|
+
})();
|
|
802
|
+
</script>
|
|
803
|
+
</body>
|
|
804
|
+
</html>
|
|
805
|
+
`));
|
|
806
|
+
|
|
807
|
+
const server = createArtifactServer({ name: 'sdk-e2e', app });
|
|
808
|
+
await server.start();
|
|
809
|
+
console.log(`Test at: ${server.url}`);
|
|
810
|
+
|
|
811
|
+
// Verify proxy works through tunnel
|
|
812
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
813
|
+
const res = await fetch(`${server.url}/@swarm/api/agents`, {
|
|
814
|
+
headers: { 'Bypass-Tunnel-Reminder': 'true' }
|
|
815
|
+
});
|
|
816
|
+
console.log(`Proxy via tunnel: ${res.status}`);
|
|
817
|
+
const agents = await res.json();
|
|
818
|
+
console.log(`Agents count: ${agents.length || Object.keys(agents).length}`);
|
|
819
|
+
|
|
820
|
+
await server.stop();
|
|
821
|
+
console.log('PASS: E2E SDK via tunnel works');
|
|
822
|
+
EOF
|
|
823
|
+
|
|
824
|
+
cd /workspace/repos/agent-swarm
|
|
825
|
+
bun run /tmp/test-sdk-e2e.ts
|
|
826
|
+
# Expected: proxy returns agents list via tunnel
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
---
|
|
830
|
+
|
|
831
|
+
## Phase 4: Artifacts Skill + Base Prompt Mention
|
|
832
|
+
|
|
833
|
+
**Goal:** Create a `/artifacts` skill (Claude Code skill) that provides detailed artifact usage documentation, code examples, and reference files. The base prompt only mentions the skill exists — all detailed instructions live in the skill.
|
|
834
|
+
|
|
835
|
+
**Repo:** `desplega-ai/agent-swarm`
|
|
836
|
+
|
|
837
|
+
### Why a skill instead of a prompt section?
|
|
838
|
+
|
|
839
|
+
Putting all artifact documentation inline in `BASE_PROMPT_ARTIFACTS` would bloat the system prompt for every session, even when agents aren't using artifacts. A skill is loaded on-demand when the agent invokes `/artifacts`, keeping the base prompt lean.
|
|
840
|
+
|
|
841
|
+
### Files to create
|
|
842
|
+
|
|
843
|
+
**`skills/artifacts/skill.md`** — Skill definition (loaded when `/artifacts` is invoked)
|
|
844
|
+
|
|
845
|
+
```markdown
|
|
846
|
+
# Artifacts — Serving Interactive Web Content
|
|
847
|
+
|
|
848
|
+
## Quick Start
|
|
849
|
+
|
|
850
|
+
### Static content
|
|
851
|
+
```bash
|
|
852
|
+
# Create your content in a persisted directory
|
|
853
|
+
mkdir -p /workspace/personal/artifacts/my-report
|
|
854
|
+
echo '<h1>My Report</h1>' > /workspace/personal/artifacts/my-report/index.html
|
|
855
|
+
|
|
856
|
+
# Serve it (auto-assigns a free port, creates tunnel)
|
|
857
|
+
artifact serve /workspace/personal/artifacts/my-report --name "my-report"
|
|
858
|
+
# → https://{agentId}-my-report.lt.desplega.ai
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
### Programmatic (custom Hono server)
|
|
862
|
+
```typescript
|
|
863
|
+
import { createArtifactServer } from '../artifact-sdk';
|
|
864
|
+
import { Hono } from 'hono';
|
|
865
|
+
|
|
866
|
+
const app = new Hono();
|
|
867
|
+
app.get('/', (c) => c.html('<h1>Dashboard</h1>'));
|
|
868
|
+
|
|
869
|
+
const server = createArtifactServer({ name: 'dashboard', app });
|
|
870
|
+
await server.start();
|
|
871
|
+
console.log(`Live at: ${server.url}`);
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
## CLI Commands
|
|
875
|
+
- `artifact serve <path> --name <name>` — Start serving content
|
|
876
|
+
- `artifact list` — List active artifacts with ports and URLs
|
|
877
|
+
- `artifact stop <name>` — Stop an artifact and close its tunnel
|
|
878
|
+
|
|
879
|
+
## Multiple Artifacts
|
|
880
|
+
Each artifact gets its own port (auto-assigned) and subdomain. You can serve multiple simultaneously.
|
|
881
|
+
|
|
882
|
+
## Browser SDK
|
|
883
|
+
HTML artifacts can interact with the swarm API:
|
|
884
|
+
```html
|
|
885
|
+
<script src="/@swarm/sdk.js"></script>
|
|
886
|
+
<script>
|
|
887
|
+
const swarm = new SwarmSDK();
|
|
888
|
+
await swarm.createTask({ task: 'Do something' });
|
|
889
|
+
const agents = await swarm.getSwarm();
|
|
890
|
+
</script>
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
## Auth
|
|
894
|
+
Artifacts are protected by HTTP Basic Auth (username: `hi`, password: API key). Credentials are auto-configured.
|
|
895
|
+
|
|
896
|
+
## Storage
|
|
897
|
+
Always store artifact content in persisted directories:
|
|
898
|
+
- `/workspace/personal/artifacts/` — per-agent, persists across sessions (default)
|
|
899
|
+
- `/workspace/shared/artifacts/` — shared across swarm
|
|
900
|
+
|
|
901
|
+
See the `examples/` directory for complete working examples.
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
**`skills/artifacts/examples/`** — Reference examples directory
|
|
905
|
+
|
|
906
|
+
```
|
|
907
|
+
skills/artifacts/
|
|
908
|
+
├── skill.md # Main skill documentation
|
|
909
|
+
└── examples/
|
|
910
|
+
├── static-report.sh # Minimal static HTML artifact
|
|
911
|
+
├── hono-dashboard.ts # Custom Hono app with API routes
|
|
912
|
+
├── approval-flow.ts # Interactive approval form with Browser SDK
|
|
913
|
+
└── multi-artifact.ts # Multiple artifacts from one agent
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
Each example file is a complete, copy-pasteable script that agents can reference or adapt.
|
|
917
|
+
|
|
918
|
+
**Example: `skills/artifacts/examples/static-report.sh`**
|
|
919
|
+
```bash
|
|
920
|
+
#!/bin/bash
|
|
921
|
+
# Serve a static HTML report as an artifact
|
|
922
|
+
ARTIFACT_DIR="/workspace/personal/artifacts/my-report"
|
|
923
|
+
mkdir -p "$ARTIFACT_DIR"
|
|
924
|
+
|
|
925
|
+
cat > "$ARTIFACT_DIR/index.html" << 'HTML'
|
|
926
|
+
<!DOCTYPE html>
|
|
927
|
+
<html>
|
|
928
|
+
<head><title>Agent Report</title></head>
|
|
929
|
+
<body>
|
|
930
|
+
<h1>Analysis Report</h1>
|
|
931
|
+
<p>Generated by agent on $(date)</p>
|
|
932
|
+
</body>
|
|
933
|
+
</html>
|
|
934
|
+
HTML
|
|
935
|
+
|
|
936
|
+
artifact serve "$ARTIFACT_DIR" --name "my-report"
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
**Example: `skills/artifacts/examples/approval-flow.ts`**
|
|
940
|
+
```typescript
|
|
941
|
+
import { createArtifactServer } from '../artifact-sdk';
|
|
942
|
+
import { Hono } from 'hono';
|
|
943
|
+
|
|
944
|
+
const app = new Hono();
|
|
945
|
+
app.get('/', (c) => c.html(`
|
|
946
|
+
<!DOCTYPE html>
|
|
947
|
+
<html>
|
|
948
|
+
<head><title>Approval Required</title></head>
|
|
949
|
+
<body>
|
|
950
|
+
<h1>PR #42 — Review Required</h1>
|
|
951
|
+
<p>Agent wants to merge this PR. Please review.</p>
|
|
952
|
+
<button id="approve">Approve</button>
|
|
953
|
+
<button id="reject">Reject</button>
|
|
954
|
+
<script src="/@swarm/sdk.js"></script>
|
|
955
|
+
<script>
|
|
956
|
+
const swarm = new SwarmSDK();
|
|
957
|
+
document.getElementById('approve').onclick = async () => {
|
|
958
|
+
await swarm.createTask({ task: 'Merge PR #42 — human approved' });
|
|
959
|
+
document.body.innerHTML = '<h1>Approved! Task created.</h1>';
|
|
960
|
+
};
|
|
961
|
+
document.getElementById('reject').onclick = async () => {
|
|
962
|
+
await swarm.createTask({ task: 'PR #42 rejected by human — needs changes' });
|
|
963
|
+
document.body.innerHTML = '<h1>Rejected. Agent notified.</h1>';
|
|
964
|
+
};
|
|
965
|
+
</script>
|
|
966
|
+
</body>
|
|
967
|
+
</html>
|
|
968
|
+
`));
|
|
969
|
+
|
|
970
|
+
const server = createArtifactServer({ name: 'approval-pr-42', app });
|
|
971
|
+
await server.start();
|
|
972
|
+
console.log(`Approval artifact at: ${server.url}`);
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
### Files to modify
|
|
976
|
+
|
|
977
|
+
**`src/prompts/base-prompt.ts`** — Add a brief mention (not the full docs)
|
|
978
|
+
|
|
979
|
+
Add to `BASE_PROMPT_SERVICES` or as a separate small section:
|
|
980
|
+
|
|
981
|
+
```typescript
|
|
982
|
+
const BASE_PROMPT_ARTIFACTS_MENTION = `
|
|
983
|
+
### Artifacts
|
|
984
|
+
|
|
985
|
+
Agents can serve interactive web content (HTML pages, dashboards, approval flows) via public URLs using localtunnel.
|
|
986
|
+
Use the \`/artifacts\` skill for detailed instructions, examples, and API reference.
|
|
987
|
+
Artifact content should be stored in \`/workspace/personal/artifacts/\` (persisted across sessions).
|
|
988
|
+
`;
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
Gate it the same way as services:
|
|
992
|
+
```typescript
|
|
993
|
+
if (!args.capabilities || args.capabilities.includes("artifacts")) {
|
|
994
|
+
prompt += BASE_PROMPT_ARTIFACTS_MENTION;
|
|
995
|
+
}
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
### Success Criteria
|
|
999
|
+
|
|
1000
|
+
#### Automated Verification
|
|
1001
|
+
- [ ] `bun run typecheck` passes
|
|
1002
|
+
- [ ] `bun run lint` passes
|
|
1003
|
+
- [ ] Skill file validates (correct markdown format)
|
|
1004
|
+
|
|
1005
|
+
#### Manual Verification
|
|
1006
|
+
|
|
1007
|
+
**Test 1: Skill file exists and is properly formatted**
|
|
1008
|
+
```bash
|
|
1009
|
+
cd /workspace/repos/agent-swarm
|
|
1010
|
+
|
|
1011
|
+
# Verify skill structure
|
|
1012
|
+
ls -la skills/artifacts/
|
|
1013
|
+
# Expected: skill.md, examples/ directory
|
|
1014
|
+
|
|
1015
|
+
ls -la skills/artifacts/examples/
|
|
1016
|
+
# Expected: static-report.sh, hono-dashboard.ts, approval-flow.ts, multi-artifact.ts
|
|
1017
|
+
|
|
1018
|
+
# Verify skill.md contains key sections
|
|
1019
|
+
grep -c "Quick Start\|CLI Commands\|Browser SDK\|Storage\|Auth" skills/artifacts/skill.md
|
|
1020
|
+
# Expected: 5 (all sections present)
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
**Test 2: Base prompt mentions artifacts skill (brief)**
|
|
1024
|
+
```bash
|
|
1025
|
+
cat > /tmp/test-prompt-skill.ts << 'EOF'
|
|
1026
|
+
import { getBasePrompt } from './src/prompts/base-prompt';
|
|
1027
|
+
|
|
1028
|
+
const prompt = getBasePrompt({
|
|
1029
|
+
role: 'worker',
|
|
1030
|
+
agentId: 'test-agent-id',
|
|
1031
|
+
swarmUrl: 'https://test.swarm.example.com',
|
|
1032
|
+
capabilities: ['core', 'artifacts', 'services'],
|
|
1033
|
+
});
|
|
1034
|
+
|
|
1035
|
+
// Check brief mention is present
|
|
1036
|
+
const hasMention = prompt.includes('/artifacts');
|
|
1037
|
+
const hasPersonalPath = prompt.includes('/workspace/personal/artifacts/');
|
|
1038
|
+
console.log(`Has /artifacts skill mention: ${hasMention}`);
|
|
1039
|
+
console.log(`Has persisted path mention: ${hasPersonalPath}`);
|
|
1040
|
+
console.assert(hasMention, 'Prompt should mention /artifacts skill');
|
|
1041
|
+
console.assert(hasPersonalPath, 'Prompt should mention persisted path');
|
|
1042
|
+
|
|
1043
|
+
// Check it does NOT contain the full detailed docs
|
|
1044
|
+
const hasFullDocs = prompt.includes('createArtifactServer');
|
|
1045
|
+
console.log(`Has full docs inline: ${hasFullDocs}`);
|
|
1046
|
+
console.assert(!hasFullDocs, 'Prompt should NOT contain full artifact docs — those go in the skill');
|
|
1047
|
+
|
|
1048
|
+
console.log('PASS: Base prompt mentions skill without bloating');
|
|
1049
|
+
EOF
|
|
1050
|
+
|
|
1051
|
+
cd /workspace/repos/agent-swarm
|
|
1052
|
+
bun run /tmp/test-prompt-skill.ts
|
|
1053
|
+
# Expected: all assertions pass
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
**Test 3: Base prompt excludes mention when capability not set**
|
|
1057
|
+
```bash
|
|
1058
|
+
cat > /tmp/test-prompt-no-artifact.ts << 'EOF'
|
|
1059
|
+
import { getBasePrompt } from './src/prompts/base-prompt';
|
|
1060
|
+
|
|
1061
|
+
const prompt = getBasePrompt({
|
|
1062
|
+
role: 'worker',
|
|
1063
|
+
agentId: 'test-agent-id',
|
|
1064
|
+
swarmUrl: 'https://test.swarm.example.com',
|
|
1065
|
+
capabilities: ['core', 'services'], // No 'artifacts'
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
const hasMention = prompt.includes('/artifacts');
|
|
1069
|
+
console.assert(!hasMention, 'Prompt should NOT mention artifacts without capability');
|
|
1070
|
+
console.log('PASS: Artifacts mention excluded without capability');
|
|
1071
|
+
EOF
|
|
1072
|
+
|
|
1073
|
+
cd /workspace/repos/agent-swarm
|
|
1074
|
+
bun run /tmp/test-prompt-no-artifact.ts
|
|
1075
|
+
# Expected: assertion passes
|
|
1076
|
+
```
|
|
1077
|
+
|
|
1078
|
+
---
|
|
1079
|
+
|
|
1080
|
+
## Phase 5: Hook Integration + Docker
|
|
1081
|
+
|
|
1082
|
+
**Goal:** Add tunnel cleanup to the Stop hook and install `@desplega.ai/localtunnel` in the Docker image.
|
|
1083
|
+
|
|
1084
|
+
**Repo:** `desplega-ai/agent-swarm`
|
|
1085
|
+
|
|
1086
|
+
### Files to modify
|
|
1087
|
+
|
|
1088
|
+
**`src/hooks/hook.ts`** — Stop hook handler (around line 911-1046)
|
|
1089
|
+
|
|
1090
|
+
Add tunnel cleanup before the existing Stop logic:
|
|
1091
|
+
|
|
1092
|
+
```typescript
|
|
1093
|
+
case "Stop": {
|
|
1094
|
+
// NEW: Close any artifact tunnels managed by PM2
|
|
1095
|
+
try {
|
|
1096
|
+
const { execSync } = await import('child_process');
|
|
1097
|
+
// List PM2 processes matching artifact-* pattern
|
|
1098
|
+
const pm2List = execSync('pm2 jlist 2>/dev/null || echo "[]"', { encoding: 'utf-8' });
|
|
1099
|
+
const processes = JSON.parse(pm2List);
|
|
1100
|
+
const artifactProcesses = processes.filter((p: any) =>
|
|
1101
|
+
p.name?.startsWith('artifact-')
|
|
1102
|
+
);
|
|
1103
|
+
|
|
1104
|
+
for (const proc of artifactProcesses) {
|
|
1105
|
+
try {
|
|
1106
|
+
execSync(`pm2 delete ${proc.name} 2>/dev/null`);
|
|
1107
|
+
log(`Stopped artifact process: ${proc.name}`);
|
|
1108
|
+
} catch {
|
|
1109
|
+
// Process might already be stopped
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
if (artifactProcesses.length > 0) {
|
|
1114
|
+
log(`Cleaned up ${artifactProcesses.length} artifact process(es)`);
|
|
1115
|
+
}
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
// Non-fatal: PM2 might not be available
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// ... existing Stop hook logic continues ...
|
|
1121
|
+
}
|
|
1122
|
+
```
|
|
1123
|
+
|
|
1124
|
+
**`Dockerfile.worker`** — Install localtunnel client globally
|
|
1125
|
+
|
|
1126
|
+
Add after the existing `npm install -g` commands (around line 55-60):
|
|
1127
|
+
|
|
1128
|
+
```dockerfile
|
|
1129
|
+
# Install localtunnel client for artifact tunneling
|
|
1130
|
+
RUN npm install -g @desplega.ai/localtunnel
|
|
1131
|
+
```
|
|
1132
|
+
|
|
1133
|
+
### Success Criteria
|
|
1134
|
+
|
|
1135
|
+
#### Automated Verification
|
|
1136
|
+
- [ ] `bun run typecheck` passes
|
|
1137
|
+
- [ ] `bun run lint` passes
|
|
1138
|
+
- [ ] `bun test` passes
|
|
1139
|
+
- [ ] Docker build succeeds: `docker build -f Dockerfile.worker -t agent-swarm-test .`
|
|
1140
|
+
|
|
1141
|
+
#### Manual Verification
|
|
1142
|
+
|
|
1143
|
+
**Test 1: Stop hook cleans up artifact processes**
|
|
1144
|
+
```bash
|
|
1145
|
+
cd /workspace/repos/agent-swarm
|
|
1146
|
+
|
|
1147
|
+
# Start a fake artifact process via PM2
|
|
1148
|
+
echo 'setInterval(() => {}, 1000)' > /tmp/fake-artifact.js
|
|
1149
|
+
pm2 start /tmp/fake-artifact.js --name artifact-test-cleanup
|
|
1150
|
+
|
|
1151
|
+
# Verify it's running
|
|
1152
|
+
pm2 list | grep artifact-test-cleanup
|
|
1153
|
+
# Expected: shows "artifact-test-cleanup" with status "online"
|
|
1154
|
+
|
|
1155
|
+
# Simulate Stop hook execution
|
|
1156
|
+
echo '{"hook_event_name":"Stop","session_id":"test-session","transcript_path":"/dev/null"}' | \
|
|
1157
|
+
bun run src/cli.tsx hook
|
|
1158
|
+
|
|
1159
|
+
# Verify artifact process was cleaned up
|
|
1160
|
+
pm2 list | grep artifact-test-cleanup
|
|
1161
|
+
# Expected: not found (process deleted)
|
|
1162
|
+
|
|
1163
|
+
# If pm2 list still shows it, the hook didn't clean it up — debug needed
|
|
1164
|
+
pm2 delete artifact-test-cleanup 2>/dev/null # manual cleanup
|
|
1165
|
+
echo "PASS: Stop hook cleans up artifact processes"
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
**Test 2: Localtunnel installed in Docker image**
|
|
1169
|
+
```bash
|
|
1170
|
+
cd /workspace/repos/agent-swarm
|
|
1171
|
+
|
|
1172
|
+
# Build the Docker image (this tests Dockerfile changes)
|
|
1173
|
+
docker build -f Dockerfile.worker -t agent-swarm-artifact-test . 2>&1 | tail -5
|
|
1174
|
+
# Expected: build succeeds
|
|
1175
|
+
|
|
1176
|
+
# Verify localtunnel is available
|
|
1177
|
+
docker run --rm agent-swarm-artifact-test lt --version
|
|
1178
|
+
# Expected: prints version number (e.g., "2.2.0")
|
|
1179
|
+
|
|
1180
|
+
docker run --rm agent-swarm-artifact-test which lt
|
|
1181
|
+
# Expected: /usr/local/bin/lt or similar path
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
**Test 3: Full lifecycle — serve, access, stop, cleanup**
|
|
1185
|
+
```bash
|
|
1186
|
+
# This test runs inside a worker container (or locally with env vars)
|
|
1187
|
+
cd /workspace/repos/agent-swarm
|
|
1188
|
+
|
|
1189
|
+
# 1. Create content in persisted path
|
|
1190
|
+
mkdir -p /workspace/personal/artifacts/lifecycle
|
|
1191
|
+
echo '<h1>Lifecycle Test</h1>' > /workspace/personal/artifacts/lifecycle/index.html
|
|
1192
|
+
|
|
1193
|
+
# 2. Start artifact
|
|
1194
|
+
AGENT_ID=lifecycle-test API_KEY=test-key \
|
|
1195
|
+
bun run src/cli.tsx artifact serve /workspace/personal/artifacts/lifecycle --name lifecycle &
|
|
1196
|
+
SERVE_PID=$!
|
|
1197
|
+
sleep 3 # wait for tunnel
|
|
1198
|
+
|
|
1199
|
+
# 3. Verify it's accessible
|
|
1200
|
+
AGENT_ID=lifecycle-test API_KEY=test-key \
|
|
1201
|
+
bun run src/cli.tsx artifact list
|
|
1202
|
+
# Expected: shows "lifecycle" artifact with URL and port
|
|
1203
|
+
|
|
1204
|
+
# 4. Access via public URL
|
|
1205
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
1206
|
+
"https://lifecycle-test-lifecycle.lt.desplega.ai" 2>/dev/null
|
|
1207
|
+
# Expected: HTTP 200, body contains "Lifecycle Test"
|
|
1208
|
+
|
|
1209
|
+
# 5. Stop artifact
|
|
1210
|
+
AGENT_ID=lifecycle-test API_KEY=test-key \
|
|
1211
|
+
bun run src/cli.tsx artifact stop lifecycle
|
|
1212
|
+
# Expected: "Artifact 'lifecycle' stopped."
|
|
1213
|
+
|
|
1214
|
+
# 6. Verify tunnel is closed
|
|
1215
|
+
curl -H "Bypass-Tunnel-Reminder: true" \
|
|
1216
|
+
"https://lifecycle-test-lifecycle.lt.desplega.ai" 2>/dev/null
|
|
1217
|
+
# Expected: connection error or 502
|
|
1218
|
+
|
|
1219
|
+
# 7. Cleanup
|
|
1220
|
+
kill $SERVE_PID 2>/dev/null
|
|
1221
|
+
echo "PASS: Full lifecycle test"
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
---
|
|
1225
|
+
|
|
1226
|
+
## Testing Strategy Summary
|
|
1227
|
+
|
|
1228
|
+
### Automated Tests (CI)
|
|
1229
|
+
|
|
1230
|
+
| Test | Command | What it Verifies |
|
|
1231
|
+
|------|---------|-----------------|
|
|
1232
|
+
| TypeScript compilation | `bun run typecheck` | All new code compiles without errors |
|
|
1233
|
+
| Linting | `bun run lint` | Code style/formatting passes biome |
|
|
1234
|
+
| Unit tests | `bun test` | Existing tests not broken by new code |
|
|
1235
|
+
|
|
1236
|
+
### Manual Tests (Agent-Executed)
|
|
1237
|
+
|
|
1238
|
+
| Phase | Test | Critical Path |
|
|
1239
|
+
|-------|------|---------------|
|
|
1240
|
+
| 1 | Dynamic port allocation | two artifacts get different ports |
|
|
1241
|
+
| 1 | Static content serving | HTTP 200 with correct body from persisted path |
|
|
1242
|
+
| 1 | Multiple artifacts | independent ports and URLs |
|
|
1243
|
+
| 1 | E2E tunnel connectivity | public URL returns correct response |
|
|
1244
|
+
| 2 | CLI `artifact serve` | tunnel URL accessible |
|
|
1245
|
+
| 2 | CLI `artifact list` | shows running artifacts |
|
|
1246
|
+
| 2 | CLI `artifact stop` | tunnel closed, service unregistered |
|
|
1247
|
+
| 3 | SDK endpoint | serves JavaScript with SwarmSDK class |
|
|
1248
|
+
| 3 | API proxy | forwards requests to MCP server |
|
|
1249
|
+
| 4 | Skill file exists | skill.md + examples/ present with correct structure |
|
|
1250
|
+
| 4 | Prompt mentions skill | brief `/artifacts` mention in prompt, no full docs inline |
|
|
1251
|
+
| 4 | Prompt gated by capability | mention absent without `artifacts` capability |
|
|
1252
|
+
| 5 | Stop hook cleanup | PM2 artifact processes deleted |
|
|
1253
|
+
| 5 | Docker build | image builds, `lt` binary available |
|
|
1254
|
+
| 5 | Full lifecycle | serve → access → list → stop → verify closed |
|
|
1255
|
+
|
|
1256
|
+
---
|
|
1257
|
+
|
|
1258
|
+
## References
|
|
1259
|
+
|
|
1260
|
+
- **Research document:** `thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md`
|
|
1261
|
+
- **CLI entry point:** `src/cli.tsx` (command dispatch in `App` switch, lines 562-598)
|
|
1262
|
+
- **Command pattern:** `src/commands/worker.ts` (thin wrapper with `RunnerConfig`)
|
|
1263
|
+
- **Capabilities:** `src/server.ts:72-83` (`hasCapability()`, `getEnabledCapabilities()`)
|
|
1264
|
+
- **Prompt assembly:** `src/prompts/base-prompt.ts:347-406` (`getBasePrompt()`)
|
|
1265
|
+
- **Hook system:** `src/hooks/hook.ts:911-1046` (Stop hook handler)
|
|
1266
|
+
- **HTTP server:** `src/http.ts` (raw `node:http`, manual routing)
|
|
1267
|
+
- **Docker:** `Dockerfile.worker` (multi-stage build, Ubuntu 24.04 runtime)
|
|
1268
|
+
- **Entrypoint:** `docker-entrypoint.sh` (env var setup, PM2 init, repo cloning)
|
|
1269
|
+
- **Dependencies:** `package.json` (no hono or localtunnel yet — need adding)
|