@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.
Files changed (810) hide show
  1. package/.claude/settings.json +84 -0
  2. package/.claude/settings.local.json +3 -1
  3. package/.entire/settings.json +4 -0
  4. package/.env.docker.example +40 -23
  5. package/.env.example +39 -1
  6. package/.github/ISSUE_TEMPLATE/bug_report.yml +78 -0
  7. package/.github/ISSUE_TEMPLATE/community-template.yml +77 -0
  8. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  9. package/.github/ISSUE_TEMPLATE/feature_request.yml +60 -0
  10. package/.github/PULL_REQUEST_TEMPLATE/community-template.md +29 -0
  11. package/.github/workflows/ci.yml +3 -27
  12. package/.github/workflows/docker-and-deploy.yml +20 -5
  13. package/.github/workflows/merge-gate.yml +233 -0
  14. package/.opencode/plugins/entire.ts +133 -0
  15. package/.superset/config.json +6 -0
  16. package/.wts-setup.ts +103 -34
  17. package/CHANGELOG.md +447 -0
  18. package/CLAUDE.md +422 -5
  19. package/CONTRIBUTING.md +46 -1
  20. package/DEPLOYMENT.md +26 -9
  21. package/Dockerfile +15 -7
  22. package/Dockerfile.worker +99 -67
  23. package/MCP.md +452 -17
  24. package/README.md +271 -169
  25. package/api-entrypoint.sh +56 -0
  26. package/depot.json +1 -0
  27. package/docker-compose.example.yml +194 -3
  28. package/docker-compose.local.yml +119 -0
  29. package/docker-entrypoint.sh +393 -113
  30. package/docs-site/app/api/search/route.ts +4 -0
  31. package/docs-site/app/docs/[[...slug]]/page.tsx +87 -0
  32. package/docs-site/app/docs/layout.tsx +12 -0
  33. package/docs-site/app/globals.css +24 -0
  34. package/docs-site/app/layout.config.tsx +34 -0
  35. package/docs-site/app/layout.tsx +119 -0
  36. package/docs-site/app/llms-full.txt/route.ts +11 -0
  37. package/docs-site/app/llms.mdx/docs/[[...slug]]/route.ts +24 -0
  38. package/docs-site/app/llms.txt/route.ts +8 -0
  39. package/docs-site/app/page.tsx +5 -0
  40. package/docs-site/app/robots.ts +13 -0
  41. package/docs-site/app/sitemap.ts +37 -0
  42. package/docs-site/components/api-page.client.tsx +4 -0
  43. package/docs-site/components/api-page.tsx +7 -0
  44. package/docs-site/components/mdx/mermaid.tsx +55 -0
  45. package/docs-site/content/docs/(documentation)/architecture/agents.mdx +117 -0
  46. package/docs-site/content/docs/(documentation)/architecture/hooks.mdx +77 -0
  47. package/docs-site/content/docs/(documentation)/architecture/memory.mdx +96 -0
  48. package/docs-site/content/docs/(documentation)/architecture/meta.json +4 -0
  49. package/docs-site/content/docs/(documentation)/architecture/overview.mdx +172 -0
  50. package/docs-site/content/docs/(documentation)/concepts/epics.mdx +98 -0
  51. package/docs-site/content/docs/(documentation)/concepts/meta.json +4 -0
  52. package/docs-site/content/docs/(documentation)/concepts/scheduling.mdx +136 -0
  53. package/docs-site/content/docs/(documentation)/concepts/services.mdx +104 -0
  54. package/docs-site/content/docs/(documentation)/concepts/task-lifecycle.mdx +148 -0
  55. package/docs-site/content/docs/(documentation)/concepts/workflows.mdx +209 -0
  56. package/docs-site/content/docs/(documentation)/contributing.mdx +158 -0
  57. package/docs-site/content/docs/(documentation)/getting-started.mdx +157 -0
  58. package/docs-site/content/docs/(documentation)/guides/agentmail-integration.mdx +79 -0
  59. package/docs-site/content/docs/(documentation)/guides/deployment.mdx +171 -0
  60. package/docs-site/content/docs/(documentation)/guides/github-integration.mdx +81 -0
  61. package/docs-site/content/docs/(documentation)/guides/gitlab-integration.mdx +93 -0
  62. package/docs-site/content/docs/(documentation)/guides/linear-integration.mdx +98 -0
  63. package/docs-site/content/docs/(documentation)/guides/meta.json +13 -0
  64. package/docs-site/content/docs/(documentation)/guides/sentry-integration.mdx +52 -0
  65. package/docs-site/content/docs/(documentation)/guides/slack-integration.mdx +179 -0
  66. package/docs-site/content/docs/(documentation)/guides/x402-payments.mdx +154 -0
  67. package/docs-site/content/docs/(documentation)/index.mdx +65 -0
  68. package/docs-site/content/docs/(documentation)/meta.json +19 -0
  69. package/docs-site/content/docs/(documentation)/reference/cli.mdx +241 -0
  70. package/docs-site/content/docs/(documentation)/reference/environment-variables.mdx +205 -0
  71. package/docs-site/content/docs/(documentation)/reference/mcp-tools.mdx +449 -0
  72. package/docs-site/content/docs/(documentation)/reference/meta.json +4 -0
  73. package/docs-site/content/docs/api-reference/active-sessions.mdx +9 -0
  74. package/docs-site/content/docs/api-reference/agents.mdx +9 -0
  75. package/docs-site/content/docs/api-reference/channels.mdx +9 -0
  76. package/docs-site/content/docs/api-reference/config.mdx +9 -0
  77. package/docs-site/content/docs/api-reference/debug.mdx +9 -0
  78. package/docs-site/content/docs/api-reference/ecosystem.mdx +9 -0
  79. package/docs-site/content/docs/api-reference/epics.mdx +9 -0
  80. package/docs-site/content/docs/api-reference/index.mdx +32 -0
  81. package/docs-site/content/docs/api-reference/memory.mdx +9 -0
  82. package/docs-site/content/docs/api-reference/meta.json +25 -0
  83. package/docs-site/content/docs/api-reference/poll.mdx +9 -0
  84. package/docs-site/content/docs/api-reference/repos.mdx +9 -0
  85. package/docs-site/content/docs/api-reference/schedules.mdx +9 -0
  86. package/docs-site/content/docs/api-reference/session-data.mdx +9 -0
  87. package/docs-site/content/docs/api-reference/stats.mdx +9 -0
  88. package/docs-site/content/docs/api-reference/tasks.mdx +9 -0
  89. package/docs-site/content/docs/api-reference/trackers.mdx +9 -0
  90. package/docs-site/content/docs/api-reference/webhooks.mdx +9 -0
  91. package/docs-site/content/docs/api-reference/workflows.mdx +9 -0
  92. package/docs-site/content/docs/meta.json +3 -0
  93. package/docs-site/lib/get-llm-text.ts +10 -0
  94. package/docs-site/lib/openapi.ts +23 -0
  95. package/docs-site/lib/source.ts +8 -0
  96. package/docs-site/mdx-components.tsx +13 -0
  97. package/docs-site/next.config.mjs +29 -0
  98. package/docs-site/package.json +35 -0
  99. package/docs-site/pnpm-lock.yaml +5407 -0
  100. package/docs-site/postcss.config.mjs +8 -0
  101. package/docs-site/scripts/generate-docs.ts +171 -0
  102. package/docs-site/source.config.ts +17 -0
  103. package/docs-site/tsconfig.json +46 -0
  104. package/ecosystem.config.cjs +4 -4
  105. package/landing/next.config.ts +14 -0
  106. package/landing/package.json +31 -0
  107. package/landing/pnpm-lock.yaml +1091 -0
  108. package/landing/postcss.config.mjs +8 -0
  109. package/landing/public/apple-touch-icon.png +0 -0
  110. package/landing/public/favicon.ico +0 -0
  111. package/landing/public/logo.png +0 -0
  112. package/landing/public/og-image.png +0 -0
  113. package/landing/public/omghost-desplega.svg +30 -0
  114. package/landing/public/omghost-openfort.svg +9 -0
  115. package/landing/src/app/actions/waitlist.ts +25 -0
  116. package/landing/src/app/blog/openfort-hackathon/page.tsx +863 -0
  117. package/landing/src/app/blog/page.tsx +162 -0
  118. package/landing/src/app/blog/swarm-metrics/page.tsx +685 -0
  119. package/landing/src/app/examples/page.tsx +174 -0
  120. package/landing/src/app/examples/x402/page.tsx +456 -0
  121. package/landing/src/app/globals.css +122 -0
  122. package/landing/src/app/layout.tsx +134 -0
  123. package/landing/src/app/page.tsx +27 -0
  124. package/landing/src/app/robots.ts +13 -0
  125. package/landing/src/app/sitemap.ts +44 -0
  126. package/landing/src/components/architecture.tsx +163 -0
  127. package/landing/src/components/cta.tsx +52 -0
  128. package/landing/src/components/features.tsx +160 -0
  129. package/landing/src/components/footer.tsx +100 -0
  130. package/landing/src/components/hero.tsx +217 -0
  131. package/landing/src/components/how-it-works.tsx +165 -0
  132. package/landing/src/components/navbar.tsx +147 -0
  133. package/landing/src/components/waitlist.tsx +110 -0
  134. package/landing/src/components/why-choose.tsx +149 -0
  135. package/landing/src/components/workshops.tsx +328 -0
  136. package/landing/src/lib/utils.ts +6 -0
  137. package/landing/tsconfig.json +41 -0
  138. package/misc/transcripts/2026-03-09-pi-mono-e2e-verification.md +154 -0
  139. package/new-ui/CLAUDE.md +92 -0
  140. package/new-ui/README.md +73 -0
  141. package/new-ui/biome.json +42 -0
  142. package/new-ui/components.json +21 -0
  143. package/new-ui/index.html +25 -0
  144. package/new-ui/package.json +49 -0
  145. package/new-ui/pnpm-lock.yaml +4845 -0
  146. package/new-ui/public/logo.png +0 -0
  147. package/new-ui/src/api/client.ts +814 -0
  148. package/new-ui/src/api/hooks/index.ts +64 -0
  149. package/new-ui/src/api/hooks/use-agents.ts +58 -0
  150. package/new-ui/src/api/hooks/use-channels.ts +115 -0
  151. package/new-ui/src/api/hooks/use-config-api.ts +46 -0
  152. package/new-ui/src/api/hooks/use-costs.ts +122 -0
  153. package/new-ui/src/api/hooks/use-db-query.ts +29 -0
  154. package/new-ui/src/api/hooks/use-epics.ts +75 -0
  155. package/new-ui/src/api/hooks/use-repos.ts +61 -0
  156. package/new-ui/src/api/hooks/use-schedules.ts +81 -0
  157. package/new-ui/src/api/hooks/use-services.ts +16 -0
  158. package/new-ui/src/api/hooks/use-stats.ts +27 -0
  159. package/new-ui/src/api/hooks/use-tasks.ts +89 -0
  160. package/new-ui/src/api/hooks/use-workflows.ts +109 -0
  161. package/{ui/src/types/api.ts → new-ui/src/api/types.ts} +236 -10
  162. package/new-ui/src/app/App.tsx +13 -0
  163. package/new-ui/src/app/providers.tsx +32 -0
  164. package/new-ui/src/app/router.tsx +52 -0
  165. package/new-ui/src/components/layout/app-header.tsx +47 -0
  166. package/new-ui/src/components/layout/app-sidebar.tsx +128 -0
  167. package/new-ui/src/components/layout/breadcrumbs.tsx +57 -0
  168. package/new-ui/src/components/layout/config-guard.tsx +22 -0
  169. package/new-ui/src/components/layout/root-layout.tsx +40 -0
  170. package/new-ui/src/components/layout/swarm-switcher.tsx +85 -0
  171. package/new-ui/src/components/shared/command-menu.tsx +131 -0
  172. package/new-ui/src/components/shared/data-grid.tsx +141 -0
  173. package/new-ui/src/components/shared/empty-state.tsx +24 -0
  174. package/new-ui/src/components/shared/error-boundary.tsx +72 -0
  175. package/new-ui/src/components/shared/json-viewer.tsx +47 -0
  176. package/new-ui/src/components/shared/name-connection-modal.tsx +99 -0
  177. package/new-ui/src/components/shared/page-skeleton.tsx +16 -0
  178. package/new-ui/src/components/shared/session-log-viewer.tsx +364 -0
  179. package/new-ui/src/components/shared/stats-bar.tsx +132 -0
  180. package/new-ui/src/components/shared/status-badge.tsx +131 -0
  181. package/new-ui/src/components/shared/usage-summary.tsx +179 -0
  182. package/new-ui/src/components/ui/alert-dialog.tsx +176 -0
  183. package/new-ui/src/components/ui/alert.tsx +60 -0
  184. package/new-ui/src/components/ui/avatar.tsx +96 -0
  185. package/new-ui/src/components/ui/badge.tsx +46 -0
  186. package/new-ui/src/components/ui/button.tsx +62 -0
  187. package/new-ui/src/components/ui/card.tsx +75 -0
  188. package/new-ui/src/components/ui/command.tsx +160 -0
  189. package/new-ui/src/components/ui/dialog.tsx +143 -0
  190. package/new-ui/src/components/ui/dropdown-menu.tsx +226 -0
  191. package/new-ui/src/components/ui/input.tsx +21 -0
  192. package/new-ui/src/components/ui/label.tsx +19 -0
  193. package/new-ui/src/components/ui/progress.tsx +26 -0
  194. package/new-ui/src/components/ui/scroll-area.tsx +54 -0
  195. package/new-ui/src/components/ui/select.tsx +175 -0
  196. package/new-ui/src/components/ui/separator.tsx +28 -0
  197. package/new-ui/src/components/ui/sheet.tsx +132 -0
  198. package/new-ui/src/components/ui/sidebar.tsx +691 -0
  199. package/new-ui/src/components/ui/skeleton.tsx +13 -0
  200. package/new-ui/src/components/ui/sonner.tsx +35 -0
  201. package/new-ui/src/components/ui/switch.tsx +33 -0
  202. package/new-ui/src/components/ui/table.tsx +92 -0
  203. package/new-ui/src/components/ui/tabs.tsx +79 -0
  204. package/new-ui/src/components/ui/textarea.tsx +18 -0
  205. package/new-ui/src/components/ui/tooltip.tsx +51 -0
  206. package/new-ui/src/components/workflows/action-node.tsx +53 -0
  207. package/new-ui/src/components/workflows/condition-node.tsx +50 -0
  208. package/new-ui/src/components/workflows/graph-utils.ts +124 -0
  209. package/new-ui/src/components/workflows/json-tree.tsx +189 -0
  210. package/new-ui/src/components/workflows/node-styles.ts +10 -0
  211. package/new-ui/src/components/workflows/step-detail-sheet.tsx +87 -0
  212. package/new-ui/src/components/workflows/trigger-node.tsx +41 -0
  213. package/new-ui/src/components/workflows/workflow-graph.tsx +65 -0
  214. package/{ui/src/hooks/useAutoScroll.ts → new-ui/src/hooks/use-auto-scroll.ts} +0 -1
  215. package/new-ui/src/hooks/use-config.ts +203 -0
  216. package/new-ui/src/hooks/use-keyboard-shortcuts.ts +41 -0
  217. package/new-ui/src/hooks/use-mobile.ts +19 -0
  218. package/new-ui/src/hooks/use-theme.ts +60 -0
  219. package/new-ui/src/lib/config.ts +188 -0
  220. package/new-ui/src/lib/slugs.ts +71 -0
  221. package/{ui → new-ui}/src/lib/utils.ts +32 -0
  222. package/new-ui/src/main.tsx +11 -0
  223. package/new-ui/src/pages/agents/[id]/page.tsx +492 -0
  224. package/new-ui/src/pages/agents/page.tsx +134 -0
  225. package/new-ui/src/pages/chat/page.tsx +674 -0
  226. package/new-ui/src/pages/config/page.tsx +1109 -0
  227. package/new-ui/src/pages/dashboard/page.tsx +454 -0
  228. package/new-ui/src/pages/debug/page.tsx +275 -0
  229. package/new-ui/src/pages/epics/[id]/page.tsx +809 -0
  230. package/new-ui/src/pages/epics/page.tsx +321 -0
  231. package/new-ui/src/pages/not-found/page.tsx +18 -0
  232. package/new-ui/src/pages/repos/page.tsx +369 -0
  233. package/new-ui/src/pages/schedules/[id]/page.tsx +664 -0
  234. package/new-ui/src/pages/schedules/page.tsx +477 -0
  235. package/new-ui/src/pages/services/page.tsx +128 -0
  236. package/new-ui/src/pages/tasks/[id]/page.tsx +670 -0
  237. package/new-ui/src/pages/tasks/page.tsx +592 -0
  238. package/new-ui/src/pages/usage/page.tsx +195 -0
  239. package/new-ui/src/pages/workflow-runs/[id]/page.tsx +363 -0
  240. package/new-ui/src/pages/workflows/[id]/page.tsx +417 -0
  241. package/new-ui/src/pages/workflows/page.tsx +266 -0
  242. package/new-ui/src/styles/ag-grid.css +36 -0
  243. package/new-ui/src/styles/globals.css +213 -0
  244. package/new-ui/test-results/.last-run.json +4 -0
  245. package/{ui/tsconfig.json → new-ui/tsconfig.app.json} +7 -4
  246. package/new-ui/tsconfig.json +4 -0
  247. package/new-ui/tsconfig.node.json +26 -0
  248. package/new-ui/vercel.json +4 -0
  249. package/new-ui/vite.config.ts +28 -0
  250. package/openapi.json +4436 -0
  251. package/package.json +31 -7
  252. package/plugin/build-pi-skills.ts +233 -0
  253. package/plugin/commands/close-issue.md +7 -3
  254. package/plugin/commands/create-pr.md +18 -12
  255. package/plugin/commands/implement-issue.md +7 -3
  256. package/plugin/commands/respond-github.md +8 -4
  257. package/plugin/commands/review-pr.md +44 -10
  258. package/plugin/commands/start-leader.md +1 -3
  259. package/plugin/commands/start-worker.md +1 -3
  260. package/plugin/commands/work-on-task.md +22 -3
  261. package/plugin/pi-skills/close-issue/SKILL.md +90 -0
  262. package/plugin/pi-skills/create-pr/SKILL.md +99 -0
  263. package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
  264. package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
  265. package/plugin/pi-skills/respond-github/SKILL.md +98 -0
  266. package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
  267. package/plugin/pi-skills/review-pr/SKILL.md +261 -0
  268. package/plugin/pi-skills/start-leader/SKILL.md +121 -0
  269. package/plugin/pi-skills/start-worker/SKILL.md +60 -0
  270. package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
  271. package/plugin/pi-skills/todos/SKILL.md +66 -0
  272. package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
  273. package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
  274. package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
  275. package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
  276. package/plugin/skills/artifacts/examples/static-report.sh +17 -0
  277. package/plugin/skills/artifacts/skill.md +71 -0
  278. package/prek.toml +75 -0
  279. package/scripts/check-db-boundary.sh +60 -0
  280. package/scripts/e2e-docker-provider.ts +820 -0
  281. package/scripts/e2e-io-schemas-test.ts +807 -0
  282. package/scripts/e2e-provider-test.ts +220 -0
  283. package/scripts/e2e-workflow-redesign.sh +229 -0
  284. package/scripts/e2e-workflow-test.sh +285 -0
  285. package/scripts/e2e-workflow-test.ts +857 -0
  286. package/scripts/generate-openapi.ts +26 -0
  287. package/scripts/measure-tool-tokens.ts +118 -0
  288. package/scripts/x402-e2e-test.ts +195 -0
  289. package/scripts/x402-test-server.ts +236 -0
  290. package/scripts/x402-testnet-e2e.ts +668 -0
  291. package/slack-manifest.json +17 -0
  292. package/src/agentmail/app.ts +65 -0
  293. package/src/agentmail/handlers.ts +207 -0
  294. package/src/agentmail/index.ts +9 -0
  295. package/src/agentmail/types.ts +51 -0
  296. package/src/artifact-sdk/browser-sdk.ts +30 -0
  297. package/src/artifact-sdk/index.ts +2 -0
  298. package/src/artifact-sdk/localtunnel.d.ts +20 -0
  299. package/src/artifact-sdk/port.ts +12 -0
  300. package/src/artifact-sdk/server.ts +156 -0
  301. package/src/artifact-sdk/tunnel.ts +19 -0
  302. package/src/be/chunking.ts +193 -0
  303. package/src/be/db-queries/oauth.ts +90 -0
  304. package/src/be/db-queries/tracker.ts +182 -0
  305. package/src/be/db.ts +2729 -770
  306. package/src/be/embedding.ts +80 -0
  307. package/src/be/migrations/001_initial.sql +409 -0
  308. package/src/be/migrations/002_one_time_schedules.sql +59 -0
  309. package/src/be/migrations/003_workflows.sql +51 -0
  310. package/src/be/migrations/004_workflow_source.sql +81 -0
  311. package/src/be/migrations/005_epic_next_steps.sql +2 -0
  312. package/src/be/migrations/006_vcs_provider.sql +94 -0
  313. package/src/be/migrations/007_task_dir.sql +2 -0
  314. package/src/be/migrations/008_workflow_redesign.sql +85 -0
  315. package/src/be/migrations/009_tracker_integration.sql +144 -0
  316. package/src/be/migrations/010_step_diagnostics.sql +1 -0
  317. package/src/be/migrations/011_step_next_port.sql +1 -0
  318. package/src/be/migrations/012_trigger_schema.sql +1 -0
  319. package/src/be/migrations/013_task_output_schema.sql +2 -0
  320. package/src/be/migrations/runner.ts +188 -0
  321. package/src/cli.tsx +231 -299
  322. package/src/commands/artifact.ts +241 -0
  323. package/src/commands/onboard/compose-generator.ts +169 -0
  324. package/src/commands/onboard/env-generator.ts +79 -0
  325. package/src/commands/onboard/manifest.ts +37 -0
  326. package/src/commands/onboard/presets.ts +85 -0
  327. package/src/commands/onboard/service-names.ts +47 -0
  328. package/src/commands/onboard/steps/core-credentials.tsx +111 -0
  329. package/src/commands/onboard/steps/custom-templates.tsx +168 -0
  330. package/src/commands/onboard/steps/generate.tsx +154 -0
  331. package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
  332. package/src/commands/onboard/steps/harness.tsx +21 -0
  333. package/src/commands/onboard/steps/health-check.tsx +171 -0
  334. package/src/commands/onboard/steps/integration-github.tsx +105 -0
  335. package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
  336. package/src/commands/onboard/steps/integration-menu.tsx +58 -0
  337. package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
  338. package/src/commands/onboard/steps/integration-slack.tsx +165 -0
  339. package/src/commands/onboard/steps/post-connect.tsx +145 -0
  340. package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
  341. package/src/commands/onboard/steps/post-task.tsx +103 -0
  342. package/src/commands/onboard/steps/prereq-check.tsx +178 -0
  343. package/src/commands/onboard/steps/review.tsx +82 -0
  344. package/src/commands/onboard/steps/start.tsx +97 -0
  345. package/src/commands/onboard/templates.ts +34 -0
  346. package/src/commands/onboard/types.ts +259 -0
  347. package/src/commands/onboard.tsx +425 -0
  348. package/src/commands/runner.ts +1376 -559
  349. package/src/commands/setup.tsx +23 -38
  350. package/src/commands/shared/client-config.ts +41 -0
  351. package/src/github/app.ts +8 -0
  352. package/src/github/handlers.ts +93 -75
  353. package/src/github/index.ts +1 -0
  354. package/src/github/types.ts +1 -0
  355. package/src/gitlab/auth.ts +63 -0
  356. package/src/gitlab/handlers.ts +327 -0
  357. package/src/gitlab/index.ts +19 -0
  358. package/src/gitlab/reactions.ts +104 -0
  359. package/src/gitlab/types.ts +130 -0
  360. package/src/heartbeat/heartbeat.ts +425 -0
  361. package/src/heartbeat/index.ts +1 -0
  362. package/src/hooks/hook.ts +555 -4
  363. package/src/hooks/tool-loop-detection.test.ts +158 -0
  364. package/src/hooks/tool-loop-detection.ts +167 -0
  365. package/src/http/active-sessions.ts +172 -0
  366. package/src/http/agents.ts +328 -0
  367. package/src/http/config.ts +191 -0
  368. package/src/http/core.ts +309 -0
  369. package/src/http/db-query.ts +91 -0
  370. package/src/http/ecosystem.ts +63 -0
  371. package/src/http/epics.ts +460 -0
  372. package/src/http/index.ts +213 -0
  373. package/src/http/mcp.ts +77 -0
  374. package/src/http/memory.ts +168 -0
  375. package/src/http/openapi.ts +109 -0
  376. package/src/http/poll.ts +178 -0
  377. package/src/http/repos.ts +195 -0
  378. package/src/http/route-def.ts +123 -0
  379. package/src/http/schedules.ts +391 -0
  380. package/src/http/session-data.ts +241 -0
  381. package/src/http/stats.ts +174 -0
  382. package/src/http/tasks.ts +468 -0
  383. package/src/http/trackers/index.ts +10 -0
  384. package/src/http/trackers/linear.ts +187 -0
  385. package/src/http/types.ts +12 -0
  386. package/src/http/utils.ts +87 -0
  387. package/src/http/webhooks.ts +432 -0
  388. package/src/http/workflows.ts +522 -0
  389. package/src/http.ts +1 -1890
  390. package/src/linear/README.md +65 -0
  391. package/src/linear/app.ts +48 -0
  392. package/src/linear/client.ts +18 -0
  393. package/src/linear/index.ts +1 -0
  394. package/src/linear/oauth.ts +35 -0
  395. package/src/linear/outbound.ts +212 -0
  396. package/src/linear/sync.ts +543 -0
  397. package/src/linear/types.ts +7 -0
  398. package/src/linear/webhook.ts +104 -0
  399. package/src/oauth/README.md +66 -0
  400. package/src/oauth/index.ts +6 -0
  401. package/src/oauth/wrapper.ts +204 -0
  402. package/src/prompts/base-prompt.ts +366 -24
  403. package/src/prompts/defaults.ts +196 -0
  404. package/src/providers/claude-adapter.ts +429 -0
  405. package/src/providers/index.ts +24 -0
  406. package/src/providers/pi-mono-adapter.ts +442 -0
  407. package/src/providers/pi-mono-extension.ts +620 -0
  408. package/src/providers/pi-mono-mcp-client.ts +124 -0
  409. package/src/providers/types.ts +75 -0
  410. package/src/scheduler/scheduler.test.ts +2 -0
  411. package/src/scheduler/scheduler.ts +186 -29
  412. package/src/server.ts +82 -6
  413. package/src/slack/HEURISTICS.md +105 -0
  414. package/src/slack/actions.ts +133 -0
  415. package/src/slack/app.ts +7 -0
  416. package/src/slack/assistant.ts +115 -0
  417. package/src/slack/blocks.ts +233 -0
  418. package/src/slack/commands.ts +31 -17
  419. package/src/slack/files.ts +1 -1
  420. package/src/slack/handlers.test.ts +114 -1
  421. package/src/slack/handlers.ts +212 -52
  422. package/src/slack/responses.ts +120 -67
  423. package/src/slack/router.ts +17 -99
  424. package/src/slack/thread-buffer.ts +213 -0
  425. package/src/slack/watcher.ts +119 -4
  426. package/src/tests/agent-activity.test.ts +247 -0
  427. package/src/tests/agentmail-filters.test.ts +97 -0
  428. package/src/tests/artifact-sdk.test.ts +800 -0
  429. package/src/tests/base-prompt.test.ts +264 -0
  430. package/src/tests/build-pi-skills.test.ts +127 -0
  431. package/src/tests/claude-adapter.test.ts +126 -0
  432. package/src/tests/context-versioning.test.ts +425 -0
  433. package/src/tests/db-queries-oauth.test.ts +197 -0
  434. package/src/tests/db-queries-tracker.test.ts +230 -0
  435. package/src/tests/epics.test.ts +3 -3
  436. package/src/tests/error-tracker.test.ts +368 -0
  437. package/src/tests/fetch-resolved-env.test.ts +167 -0
  438. package/src/tests/generate-default-claude-md.test.ts +9 -1
  439. package/src/tests/generate-identity-templates.test.ts +124 -0
  440. package/src/tests/gitlab-auth.test.ts +109 -0
  441. package/src/tests/gitlab-handlers.test.ts +691 -0
  442. package/src/tests/gitlab-vcs-db.test.ts +177 -0
  443. package/src/tests/heartbeat.test.ts +363 -0
  444. package/src/tests/http-api-integration.test.ts +1698 -0
  445. package/src/tests/linear-outbound-sync.test.ts +200 -0
  446. package/src/tests/linear-webhook.test.ts +402 -0
  447. package/src/tests/match-route.test.ts +187 -0
  448. package/src/tests/memory.test.ts +737 -0
  449. package/src/tests/migration-runner-regressions.test.ts +86 -0
  450. package/src/tests/model-control.test.ts +338 -0
  451. package/src/tests/oauth-wrapper.test.ts +147 -0
  452. package/src/tests/onboard-compose.test.ts +138 -0
  453. package/src/tests/onboard-env.test.ts +174 -0
  454. package/src/tests/onboard-manifest.test.ts +137 -0
  455. package/src/tests/pi-mono-adapter.test.ts +234 -0
  456. package/src/tests/progress-dedup.test.ts +98 -0
  457. package/src/tests/provider-adapter.test.ts +122 -0
  458. package/src/tests/provider-command-format.test.ts +98 -0
  459. package/src/tests/reload-config.test.ts +170 -0
  460. package/src/tests/runner-polling-api.test.ts +25 -20
  461. package/src/tests/scheduled-tasks.test.ts +104 -0
  462. package/src/tests/scheduler-backoff.test.ts +166 -0
  463. package/src/tests/self-improvement.test.ts +540 -0
  464. package/src/tests/session-attach.test.ts +536 -0
  465. package/src/tests/session-costs.test.ts +267 -1
  466. package/src/tests/slack-actions.test.ts +133 -0
  467. package/src/tests/slack-assistant.test.ts +136 -0
  468. package/src/tests/slack-blocks.test.ts +246 -0
  469. package/src/tests/slack-metadata-inheritance.test.ts +243 -0
  470. package/src/tests/slack-queue-offline.test.ts +174 -0
  471. package/src/tests/slack-router.test.ts +181 -0
  472. package/src/tests/slack-thread-buffer.test.ts +305 -0
  473. package/src/tests/slack-thread-followups.test.ts +298 -0
  474. package/src/tests/slack-watcher.test.ts +101 -0
  475. package/src/tests/structured-output.test.ts +311 -0
  476. package/src/tests/swarm-repos.test.ts +198 -0
  477. package/src/tests/task-cancellation.test.ts +6 -4
  478. package/src/tests/task-working-dir.test.ts +176 -0
  479. package/src/tests/template-fetch.test.ts +490 -0
  480. package/src/tests/tool-annotations.test.ts +371 -0
  481. package/src/tests/tracker-tools.test.ts +184 -0
  482. package/src/tests/update-profile-api.test.ts +143 -3
  483. package/src/tests/validation-adapters.test.ts +86 -0
  484. package/src/tests/vcs-provider.test.ts +27 -0
  485. package/src/tests/workflow-agent-task.test.ts +196 -0
  486. package/src/tests/workflow-async-v2.test.ts +386 -0
  487. package/src/tests/workflow-convergence.test.ts +541 -0
  488. package/src/tests/workflow-definition-validation.test.ts +290 -0
  489. package/src/tests/workflow-engine-v2.test.ts +691 -0
  490. package/src/tests/workflow-executors.test.ts +736 -0
  491. package/src/tests/workflow-http-v2.test.ts +599 -0
  492. package/src/tests/workflow-integration-io.test.ts +902 -0
  493. package/src/tests/workflow-io-schemas.test.ts +624 -0
  494. package/src/tests/workflow-registry.test.ts +592 -0
  495. package/src/tests/workflow-retry-v2.test.ts +401 -0
  496. package/src/tests/workflow-retry-validation.test.ts +282 -0
  497. package/src/tests/workflow-template.test.ts +288 -0
  498. package/src/tests/workflow-trigger-schema.test.ts +359 -0
  499. package/src/tests/workflow-triggers-v2.test.ts +264 -0
  500. package/src/tests/workflow-versions.test.ts +208 -0
  501. package/src/tests/x402-client.test.ts +117 -0
  502. package/src/tests/x402-config.test.ts +182 -0
  503. package/src/tests/x402-spending-tracker.test.ts +185 -0
  504. package/src/tools/cancel-task.ts +2 -0
  505. package/src/tools/context-diff.ts +171 -0
  506. package/src/tools/context-history.ts +138 -0
  507. package/src/tools/create-channel.ts +1 -0
  508. package/src/tools/db-query.ts +78 -0
  509. package/src/tools/delete-channel.ts +132 -0
  510. package/src/tools/epics/assign-task-to-epic.ts +1 -0
  511. package/src/tools/epics/create-epic.ts +3 -2
  512. package/src/tools/epics/delete-epic.ts +2 -0
  513. package/src/tools/epics/get-epic-details.ts +2 -0
  514. package/src/tools/epics/list-epics.ts +2 -0
  515. package/src/tools/epics/unassign-task-from-epic.ts +1 -0
  516. package/src/tools/epics/update-epic.ts +7 -4
  517. package/src/tools/get-swarm.ts +2 -0
  518. package/src/tools/get-task-details.ts +2 -0
  519. package/src/tools/get-tasks.ts +27 -1
  520. package/src/tools/inject-learning.ts +106 -0
  521. package/src/tools/join-swarm.ts +17 -7
  522. package/src/tools/list-channels.ts +2 -0
  523. package/src/tools/list-services.ts +2 -0
  524. package/src/tools/memory-get.ts +56 -0
  525. package/src/tools/memory-search.ts +131 -0
  526. package/src/tools/my-agent-info.ts +2 -0
  527. package/src/tools/poll-task.ts +2 -20
  528. package/src/tools/post-message.ts +1 -0
  529. package/src/tools/read-messages.ts +2 -0
  530. package/src/tools/register-agentmail-inbox.ts +166 -0
  531. package/src/tools/register-service.ts +2 -0
  532. package/src/tools/schedules/create-schedule.ts +134 -24
  533. package/src/tools/schedules/delete-schedule.ts +2 -0
  534. package/src/tools/schedules/list-schedules.ts +20 -4
  535. package/src/tools/schedules/run-schedule-now.ts +1 -0
  536. package/src/tools/schedules/update-schedule.ts +49 -17
  537. package/src/tools/send-task.ts +132 -10
  538. package/src/tools/slack-download-file.ts +4 -2
  539. package/src/tools/slack-list-channels.ts +2 -0
  540. package/src/tools/slack-post.ts +2 -0
  541. package/src/tools/slack-read.ts +2 -0
  542. package/src/tools/slack-reply.ts +2 -0
  543. package/src/tools/slack-upload-file.ts +2 -0
  544. package/src/tools/store-progress.ts +187 -4
  545. package/src/tools/swarm-config/delete-config.ts +87 -0
  546. package/src/tools/swarm-config/get-config.ts +108 -0
  547. package/src/tools/swarm-config/index.ts +4 -0
  548. package/src/tools/swarm-config/list-config.ts +99 -0
  549. package/src/tools/swarm-config/set-config.ts +118 -0
  550. package/src/tools/task-action.ts +30 -5
  551. package/src/tools/task-dedup.ts +97 -0
  552. package/src/tools/tool-config.ts +117 -0
  553. package/src/tools/tracker/index.ts +6 -0
  554. package/src/tools/tracker/tracker-link-epic.ts +64 -0
  555. package/src/tools/tracker/tracker-link-task.ts +64 -0
  556. package/src/tools/tracker/tracker-map-agent.ts +57 -0
  557. package/src/tools/tracker/tracker-status.ts +56 -0
  558. package/src/tools/tracker/tracker-sync-status.ts +42 -0
  559. package/src/tools/tracker/tracker-unlink.ts +41 -0
  560. package/src/tools/unregister-service.ts +2 -0
  561. package/src/tools/update-profile.ts +93 -10
  562. package/src/tools/update-service-status.ts +2 -0
  563. package/src/tools/utils.ts +10 -1
  564. package/src/tools/workflows/create-workflow.ts +111 -0
  565. package/src/tools/workflows/delete-workflow.ts +42 -0
  566. package/src/tools/workflows/get-workflow-run.ts +59 -0
  567. package/src/tools/workflows/get-workflow.ts +53 -0
  568. package/src/tools/workflows/index.ts +9 -0
  569. package/src/tools/workflows/list-workflow-runs.ts +48 -0
  570. package/src/tools/workflows/list-workflows.ts +42 -0
  571. package/src/tools/workflows/retry-workflow-run.ts +40 -0
  572. package/src/tools/workflows/trigger-workflow.ts +96 -0
  573. package/src/tools/workflows/update-workflow.ts +118 -0
  574. package/src/tracker/types.ts +51 -0
  575. package/src/types.ts +477 -14
  576. package/src/utils/credentials.test.ts +156 -0
  577. package/src/utils/credentials.ts +50 -0
  578. package/src/utils/error-tracker.ts +190 -0
  579. package/src/vcs/index.ts +15 -0
  580. package/src/vcs/types.ts +5 -0
  581. package/src/workflows/checkpoint.ts +121 -0
  582. package/src/workflows/cooldown.ts +28 -0
  583. package/src/workflows/definition.ts +217 -0
  584. package/src/workflows/engine.ts +554 -0
  585. package/src/workflows/event-bus.ts +29 -0
  586. package/src/workflows/executors/agent-task.ts +92 -0
  587. package/src/workflows/executors/base.ts +86 -0
  588. package/src/workflows/executors/code-match.ts +88 -0
  589. package/src/workflows/executors/index.ts +16 -0
  590. package/src/workflows/executors/notify.ts +93 -0
  591. package/src/workflows/executors/property-match.ts +104 -0
  592. package/src/workflows/executors/raw-llm.ts +83 -0
  593. package/src/workflows/executors/registry.ts +76 -0
  594. package/src/workflows/executors/script.ts +103 -0
  595. package/src/workflows/executors/validate.ts +215 -0
  596. package/src/workflows/executors/vcs.ts +58 -0
  597. package/src/workflows/index.ts +61 -0
  598. package/src/workflows/input.ts +46 -0
  599. package/src/workflows/json-schema-validator.ts +118 -0
  600. package/src/workflows/recovery.ts +139 -0
  601. package/src/workflows/resume.ts +143 -0
  602. package/src/workflows/retry-poller.ts +216 -0
  603. package/src/workflows/template.ts +74 -0
  604. package/src/workflows/templates.ts +86 -0
  605. package/src/workflows/triggers.ts +95 -0
  606. package/src/workflows/validation.ts +104 -0
  607. package/src/workflows/version.ts +42 -0
  608. package/src/x402/cli.ts +140 -0
  609. package/src/x402/client.ts +192 -0
  610. package/src/x402/config.ts +131 -0
  611. package/src/x402/index.ts +37 -0
  612. package/src/x402/openfort-signer.ts +83 -0
  613. package/src/x402/spending-tracker.ts +109 -0
  614. package/templates/community/.gitkeep +0 -0
  615. package/templates/official/coder/CLAUDE.md +49 -0
  616. package/templates/official/coder/IDENTITY.md +28 -0
  617. package/templates/official/coder/SOUL.md +43 -0
  618. package/templates/official/coder/TOOLS.md +40 -0
  619. package/templates/official/coder/config.json +23 -0
  620. package/templates/official/coder/start-up.sh +23 -0
  621. package/templates/official/content-reviewer/CLAUDE.md +68 -0
  622. package/templates/official/content-reviewer/IDENTITY.md +28 -0
  623. package/templates/official/content-reviewer/SOUL.md +44 -0
  624. package/templates/official/content-reviewer/TOOLS.md +37 -0
  625. package/templates/official/content-reviewer/config.json +23 -0
  626. package/templates/official/content-reviewer/start-up.sh +23 -0
  627. package/templates/official/content-strategist/CLAUDE.md +63 -0
  628. package/templates/official/content-strategist/IDENTITY.md +33 -0
  629. package/templates/official/content-strategist/SOUL.md +48 -0
  630. package/templates/official/content-strategist/TOOLS.md +47 -0
  631. package/templates/official/content-strategist/config.json +23 -0
  632. package/templates/official/content-strategist/start-up.sh +23 -0
  633. package/templates/official/content-writer/CLAUDE.md +72 -0
  634. package/templates/official/content-writer/IDENTITY.md +30 -0
  635. package/templates/official/content-writer/SOUL.md +46 -0
  636. package/templates/official/content-writer/TOOLS.md +44 -0
  637. package/templates/official/content-writer/config.json +23 -0
  638. package/templates/official/content-writer/start-up.sh +23 -0
  639. package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
  640. package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
  641. package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
  642. package/templates/official/forward-deployed-engineer/config.json +21 -0
  643. package/templates/official/lead/CLAUDE.md +33 -0
  644. package/templates/official/lead/IDENTITY.md +36 -0
  645. package/templates/official/lead/SOUL.md +51 -0
  646. package/templates/official/lead/config.json +22 -0
  647. package/templates/official/researcher/CLAUDE.md +46 -0
  648. package/templates/official/researcher/IDENTITY.md +28 -0
  649. package/templates/official/researcher/SOUL.md +43 -0
  650. package/templates/official/researcher/config.json +21 -0
  651. package/templates/official/reviewer/CLAUDE.md +63 -0
  652. package/templates/official/reviewer/IDENTITY.md +28 -0
  653. package/templates/official/reviewer/SOUL.md +45 -0
  654. package/templates/official/reviewer/config.json +21 -0
  655. package/templates/official/tester/CLAUDE.md +53 -0
  656. package/templates/official/tester/IDENTITY.md +28 -0
  657. package/templates/official/tester/SOUL.md +55 -0
  658. package/templates/official/tester/config.json +21 -0
  659. package/templates/schema.ts +35 -0
  660. package/templates-ui/README.md +46 -0
  661. package/templates-ui/components.json +17 -0
  662. package/templates-ui/eslint.config.mjs +18 -0
  663. package/templates-ui/next.config.ts +7 -0
  664. package/templates-ui/package.json +35 -0
  665. package/templates-ui/pnpm-lock.yaml +4571 -0
  666. package/templates-ui/postcss.config.mjs +7 -0
  667. package/templates-ui/public/file.svg +1 -0
  668. package/templates-ui/public/globe.svg +1 -0
  669. package/templates-ui/public/logo.png +0 -0
  670. package/templates-ui/public/next.svg +1 -0
  671. package/templates-ui/public/vercel.svg +1 -0
  672. package/templates-ui/public/window.svg +1 -0
  673. package/templates-ui/src/app/[category]/[name]/page.tsx +89 -0
  674. package/templates-ui/src/app/api/templates/[...slug]/route.ts +52 -0
  675. package/templates-ui/src/app/api/templates/route.ts +18 -0
  676. package/templates-ui/src/app/builder/page.tsx +37 -0
  677. package/templates-ui/src/app/globals.css +94 -0
  678. package/templates-ui/src/app/layout.tsx +79 -0
  679. package/templates-ui/src/app/page.tsx +38 -0
  680. package/templates-ui/src/app/robots.ts +11 -0
  681. package/templates-ui/src/app/sitemap.ts +31 -0
  682. package/templates-ui/src/components/compose-builder.tsx +442 -0
  683. package/templates-ui/src/components/compose-preview.tsx +117 -0
  684. package/templates-ui/src/components/file-preview.tsx +77 -0
  685. package/templates-ui/src/components/footer.tsx +40 -0
  686. package/templates-ui/src/components/header.tsx +41 -0
  687. package/templates-ui/src/components/template-card.tsx +87 -0
  688. package/templates-ui/src/components/template-detail.tsx +125 -0
  689. package/templates-ui/src/components/template-gallery.tsx +263 -0
  690. package/templates-ui/src/components/ui/badge.tsx +36 -0
  691. package/templates-ui/src/components/ui/button.tsx +57 -0
  692. package/templates-ui/src/components/ui/card.tsx +76 -0
  693. package/templates-ui/src/components/ui/separator.tsx +31 -0
  694. package/templates-ui/src/components/ui/tooltip.tsx +32 -0
  695. package/templates-ui/src/lib/compose-generator.ts +241 -0
  696. package/templates-ui/src/lib/templates.ts +137 -0
  697. package/templates-ui/src/lib/utils.ts +6 -0
  698. package/templates-ui/tsconfig.json +34 -0
  699. package/thoughts/research/2026-02-28-openfort-viem-x402-research.md +679 -0
  700. package/thoughts/research/2026-02-28-x402-payments-research.md +686 -0
  701. package/thoughts/researcher/plans/2026-02-20-agent-self-improvement-plan.md +282 -0
  702. package/thoughts/researcher/research/2026-02-20-agent-self-improvement.md +492 -0
  703. package/thoughts/shared/plans/2026-02-26-mcp-tool-context-reduction.md +282 -0
  704. package/thoughts/shared/plans/2026-03-02-claude-context-mode-integration.md +328 -0
  705. package/thoughts/shared/plans/2026-03-02-code-level-heartbeat.md +224 -0
  706. package/thoughts/shared/research/2026-02-26-cliffy-mcp-tools.md +159 -0
  707. package/thoughts/shared/research/2026-03-03-database-migration-system-refactor.md +337 -0
  708. package/thoughts/swarm-researcher/plans/2026-02-23-openclaw-improvements-plan.md +778 -0
  709. package/thoughts/swarm-researcher/plans/2026-02-26-artifacts-localtunnel-plan.md +1269 -0
  710. package/thoughts/swarm-researcher/research/2026-02-23-openclaw-vs-agent-swarm-comparison.md +411 -0
  711. package/thoughts/swarm-researcher/research/2026-02-26-artifacts-localtunnel.md +724 -0
  712. package/thoughts/taras/brainstorms/2026-03-20-prompt-template-registry.md +443 -0
  713. package/thoughts/taras/brainstorms/2026-03-20-setup-cli-onboarding.md +307 -0
  714. package/thoughts/taras/plans/2026-02-20-auto-improvement.md +803 -0
  715. package/thoughts/taras/plans/2026-02-20-env-management.md +538 -0
  716. package/thoughts/taras/plans/2026-02-20-memory-system.md +882 -0
  717. package/thoughts/taras/plans/2026-02-20-repos-knowledge.md +806 -0
  718. package/thoughts/taras/plans/2026-02-20-session-attach.md +647 -0
  719. package/thoughts/taras/plans/2026-02-20-worker-identity.md +820 -0
  720. package/thoughts/taras/plans/2026-02-25-feat-new-ui-visual-redesign-plan.md +768 -0
  721. package/thoughts/taras/plans/2026-03-04-fix-buildSystemPrompt-missing-fields.md +77 -0
  722. package/thoughts/taras/plans/2026-03-04-new-ui-missing-actions.md +543 -0
  723. package/thoughts/taras/plans/2026-03-06-one-time-scheduled-tasks.md +373 -0
  724. package/thoughts/taras/plans/2026-03-08-memory-self-improvement-enhancements.md +512 -0
  725. package/thoughts/taras/plans/2026-03-08-pi-mono-provider-implementation.md +919 -0
  726. package/thoughts/taras/plans/2026-03-09-templates-registry.md +723 -0
  727. package/thoughts/taras/plans/2026-03-10-task-working-directory.md +371 -0
  728. package/thoughts/taras/plans/2026-03-11-archil-per-agent-write-strategy.md +621 -0
  729. package/thoughts/taras/plans/2026-03-12-eliminate-inbox-route-to-tasks.md +61 -0
  730. package/thoughts/taras/plans/2026-03-12-slack-thread-followup-additive.md +488 -0
  731. package/thoughts/taras/plans/2026-03-13-slack-ai-improvements.md +644 -0
  732. package/thoughts/taras/plans/2026-03-16-route-wrapper-openapi.md +636 -0
  733. package/thoughts/taras/plans/2026-03-17-multi-api-config.md +444 -0
  734. package/thoughts/taras/plans/2026-03-18-agent-fs-integration.md +591 -0
  735. package/thoughts/taras/plans/2026-03-18-debug-db-explorer.md +446 -0
  736. package/thoughts/taras/plans/2026-03-18-workflow-redesign.md +987 -0
  737. package/thoughts/taras/plans/2026-03-19-compound-learnings.md +403 -0
  738. package/thoughts/taras/plans/2026-03-19-ticket-tracker-linear-integration.md +860 -0
  739. package/thoughts/taras/plans/2026-03-19-workflow-io-schemas-and-bugs.md +899 -0
  740. package/thoughts/taras/plans/2026-03-20-setup-cli-onboarding.md +874 -0
  741. package/thoughts/taras/plans/2026-03-20-workflow-structured-output-validation-workspace.md +723 -0
  742. package/thoughts/taras/research/2026-02-19-agent-native-swarm-architecture.md +390 -0
  743. package/thoughts/taras/research/2026-02-19-swarm-gaps-implementation.md +594 -0
  744. package/thoughts/taras/research/2026-02-25-dashboard-ui-design-best-practices.md +825 -0
  745. package/thoughts/taras/research/2026-02-26-task-detail-page-redesign.md +393 -0
  746. package/thoughts/taras/research/2026-03-03-new-ui-missing-actions.md +168 -0
  747. package/thoughts/taras/research/2026-03-05-pi-mono-provider-research.md +230 -0
  748. package/thoughts/taras/research/2026-03-06-workflow-engine-design.md +445 -0
  749. package/thoughts/taras/research/2026-03-08-drive-loop-concept.md +375 -0
  750. package/thoughts/taras/research/2026-03-08-pi-mono-deep-dive.md +869 -0
  751. package/thoughts/taras/research/2026-03-09-templates-registry.md +373 -0
  752. package/thoughts/taras/research/2026-03-10-agent-working-directory.md +223 -0
  753. package/thoughts/taras/research/2026-03-10-configurable-event-prompts.md +339 -0
  754. package/thoughts/taras/research/2026-03-11-archil-production-setup.md +181 -0
  755. package/thoughts/taras/research/2026-03-11-archil-shared-disk-write-strategies.md +437 -0
  756. package/thoughts/taras/research/2026-03-13-slack-ai-features.md +258 -0
  757. package/thoughts/taras/research/2026-03-16-openapi-docs-generation.md +335 -0
  758. package/thoughts/taras/research/2026-03-16-route-wrapper-openapi.md +670 -0
  759. package/thoughts/taras/research/2026-03-16-slack-thread-followups-e2e.md +54 -0
  760. package/thoughts/taras/research/2026-03-18-agent-fs-integration.md +558 -0
  761. package/thoughts/taras/research/2026-03-18-linear-integration-finalization.md +526 -0
  762. package/thoughts/taras/research/2026-03-18-workflow-redesign.md +797 -0
  763. package/thoughts/taras/research/2026-03-19-workflow-node-io-schemas-and-bugs.md +563 -0
  764. package/thoughts/taras/research/2026-03-19-workflow-structured-output-validation-workspace.md +486 -0
  765. package/thoughts/taras/research/2026-03-20-prompt-template-registry.md +469 -0
  766. package/tsconfig.json +1 -1
  767. package/FAQ.md +0 -19
  768. package/src/tests/get-inbox-message.test.ts +0 -145
  769. package/src/tools/get-inbox-message.ts +0 -89
  770. package/src/tools/inbox-delegate.ts +0 -113
  771. package/ui/CLAUDE.md +0 -49
  772. package/ui/bun.lock +0 -771
  773. package/ui/index.html +0 -22
  774. package/ui/package-lock.json +0 -5290
  775. package/ui/package.json +0 -33
  776. package/ui/pnpm-lock.yaml +0 -3341
  777. package/ui/postcss.config.js +0 -6
  778. package/ui/src/App.tsx +0 -63
  779. package/ui/src/components/ActivityFeed.tsx +0 -440
  780. package/ui/src/components/AgentDetailPanel.tsx +0 -733
  781. package/ui/src/components/AgentsPanel.tsx +0 -815
  782. package/ui/src/components/ChatPanel.tsx +0 -1920
  783. package/ui/src/components/ConfigModal.tsx +0 -253
  784. package/ui/src/components/Dashboard.tsx +0 -832
  785. package/ui/src/components/EditAgentProfileModal.tsx +0 -433
  786. package/ui/src/components/EpicDetailPage.tsx +0 -741
  787. package/ui/src/components/EpicsPanel.tsx +0 -566
  788. package/ui/src/components/Header.tsx +0 -160
  789. package/ui/src/components/JsonViewer.tsx +0 -171
  790. package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
  791. package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
  792. package/ui/src/components/ServicesPanel.tsx +0 -622
  793. package/ui/src/components/SessionLogPanel.tsx +0 -1219
  794. package/ui/src/components/StatsBar.tsx +0 -321
  795. package/ui/src/components/StatusBadge.tsx +0 -168
  796. package/ui/src/components/TaskDetailPanel.tsx +0 -903
  797. package/ui/src/components/TasksPanel.tsx +0 -614
  798. package/ui/src/components/UsageCharts.tsx +0 -216
  799. package/ui/src/components/UsageTab.tsx +0 -394
  800. package/ui/src/hooks/queries.ts +0 -353
  801. package/ui/src/index.css +0 -257
  802. package/ui/src/lib/api.ts +0 -268
  803. package/ui/src/lib/config.ts +0 -35
  804. package/ui/src/lib/contentPreview.ts +0 -208
  805. package/ui/src/lib/theme.ts +0 -214
  806. package/ui/src/main.tsx +0 -28
  807. package/ui/src/vite-env.d.ts +0 -1
  808. package/ui/tailwind.config.js +0 -37
  809. package/ui/vite.config.ts +0 -35
  810. /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)