@desplega.ai/agent-swarm 1.20.0 → 1.51.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (561) hide show
  1. package/README.md +271 -169
  2. package/openapi.json +5015 -0
  3. package/package.json +40 -7
  4. package/plugin/commands/close-issue.md +7 -3
  5. package/plugin/commands/create-pr.md +18 -12
  6. package/plugin/commands/implement-issue.md +7 -3
  7. package/plugin/commands/respond-github.md +8 -4
  8. package/plugin/commands/review-pr.md +44 -10
  9. package/plugin/commands/start-leader.md +1 -3
  10. package/plugin/commands/start-worker.md +1 -3
  11. package/plugin/commands/work-on-task.md +22 -3
  12. package/plugin/pi-skills/close-issue/SKILL.md +90 -0
  13. package/plugin/pi-skills/create-pr/SKILL.md +99 -0
  14. package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
  15. package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
  16. package/plugin/pi-skills/respond-github/SKILL.md +98 -0
  17. package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
  18. package/plugin/pi-skills/review-pr/SKILL.md +261 -0
  19. package/plugin/pi-skills/start-leader/SKILL.md +121 -0
  20. package/plugin/pi-skills/start-worker/SKILL.md +60 -0
  21. package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
  22. package/plugin/pi-skills/todos/SKILL.md +66 -0
  23. package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
  24. package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
  25. package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
  26. package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
  27. package/plugin/skills/artifacts/examples/static-report.sh +17 -0
  28. package/plugin/skills/artifacts/skill.md +71 -0
  29. package/src/agentmail/app.ts +65 -0
  30. package/src/agentmail/handlers.ts +262 -0
  31. package/src/agentmail/index.ts +9 -0
  32. package/src/agentmail/templates.ts +111 -0
  33. package/src/agentmail/types.ts +51 -0
  34. package/src/artifact-sdk/browser-sdk.ts +30 -0
  35. package/src/artifact-sdk/index.ts +2 -0
  36. package/src/artifact-sdk/localtunnel.d.ts +20 -0
  37. package/src/artifact-sdk/port.ts +12 -0
  38. package/src/artifact-sdk/server.ts +156 -0
  39. package/src/artifact-sdk/tunnel.ts +19 -0
  40. package/src/be/chunking.ts +193 -0
  41. package/src/be/db-queries/oauth.ts +90 -0
  42. package/src/be/db-queries/tracker.ts +182 -0
  43. package/src/be/db.ts +3327 -784
  44. package/src/be/embedding.ts +80 -0
  45. package/src/be/migrations/001_initial.sql +409 -0
  46. package/src/be/migrations/002_one_time_schedules.sql +59 -0
  47. package/src/be/migrations/003_workflows.sql +51 -0
  48. package/src/be/migrations/004_workflow_source.sql +81 -0
  49. package/src/be/migrations/005_epic_next_steps.sql +2 -0
  50. package/src/be/migrations/006_vcs_provider.sql +94 -0
  51. package/src/be/migrations/007_task_dir.sql +2 -0
  52. package/src/be/migrations/008_workflow_redesign.sql +85 -0
  53. package/src/be/migrations/009_tracker_integration.sql +144 -0
  54. package/src/be/migrations/010_step_diagnostics.sql +1 -0
  55. package/src/be/migrations/011_step_next_port.sql +1 -0
  56. package/src/be/migrations/012_trigger_schema.sql +1 -0
  57. package/src/be/migrations/013_task_output_schema.sql +2 -0
  58. package/src/be/migrations/014_prompt_templates.sql +33 -0
  59. package/src/be/migrations/015_workflow_workspace.sql +3 -0
  60. package/src/be/migrations/016_active_session_runner_session.sql +4 -0
  61. package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
  62. package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
  63. package/src/be/migrations/runner.ts +188 -0
  64. package/src/be/seed.ts +62 -0
  65. package/src/cli.tsx +231 -299
  66. package/src/commands/artifact.ts +241 -0
  67. package/src/commands/onboard/compose-generator.ts +169 -0
  68. package/src/commands/onboard/env-generator.ts +79 -0
  69. package/src/commands/onboard/manifest.ts +37 -0
  70. package/src/commands/onboard/presets.ts +85 -0
  71. package/src/commands/onboard/service-names.ts +47 -0
  72. package/src/commands/onboard/steps/core-credentials.tsx +111 -0
  73. package/src/commands/onboard/steps/custom-templates.tsx +168 -0
  74. package/src/commands/onboard/steps/generate.tsx +154 -0
  75. package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
  76. package/src/commands/onboard/steps/harness.tsx +21 -0
  77. package/src/commands/onboard/steps/health-check.tsx +171 -0
  78. package/src/commands/onboard/steps/integration-github.tsx +105 -0
  79. package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
  80. package/src/commands/onboard/steps/integration-menu.tsx +58 -0
  81. package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
  82. package/src/commands/onboard/steps/integration-slack.tsx +165 -0
  83. package/src/commands/onboard/steps/post-connect.tsx +145 -0
  84. package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
  85. package/src/commands/onboard/steps/post-task.tsx +103 -0
  86. package/src/commands/onboard/steps/prereq-check.tsx +178 -0
  87. package/src/commands/onboard/steps/review.tsx +82 -0
  88. package/src/commands/onboard/steps/start.tsx +97 -0
  89. package/src/commands/onboard/templates.ts +34 -0
  90. package/src/commands/onboard/types.ts +259 -0
  91. package/src/commands/onboard.tsx +425 -0
  92. package/src/commands/runner.ts +1540 -630
  93. package/src/commands/setup.tsx +23 -38
  94. package/src/commands/shared/client-config.ts +41 -0
  95. package/src/commands/templates.ts +172 -0
  96. package/src/github/app.ts +8 -0
  97. package/src/github/handlers.ts +384 -151
  98. package/src/github/index.ts +1 -0
  99. package/src/github/mentions-aliases.test.ts +73 -0
  100. package/src/github/mentions.test.ts +3 -3
  101. package/src/github/mentions.ts +32 -6
  102. package/src/github/templates.ts +398 -0
  103. package/src/github/types.ts +1 -0
  104. package/src/gitlab/auth.ts +63 -0
  105. package/src/gitlab/handlers.ts +368 -0
  106. package/src/gitlab/index.ts +19 -0
  107. package/src/gitlab/reactions.ts +104 -0
  108. package/src/gitlab/templates.ts +140 -0
  109. package/src/gitlab/types.ts +130 -0
  110. package/src/heartbeat/heartbeat.ts +434 -0
  111. package/src/heartbeat/index.ts +1 -0
  112. package/src/heartbeat/templates.ts +30 -0
  113. package/src/hooks/hook.ts +555 -4
  114. package/src/hooks/tool-loop-detection.test.ts +158 -0
  115. package/src/hooks/tool-loop-detection.ts +167 -0
  116. package/src/http/active-sessions.ts +199 -0
  117. package/src/http/agents.ts +328 -0
  118. package/src/http/config.ts +191 -0
  119. package/src/http/core.ts +309 -0
  120. package/src/http/db-query.ts +91 -0
  121. package/src/http/ecosystem.ts +63 -0
  122. package/src/http/epics.ts +460 -0
  123. package/src/http/index.ts +216 -0
  124. package/src/http/mcp.ts +77 -0
  125. package/src/http/memory.ts +168 -0
  126. package/src/http/openapi.ts +109 -0
  127. package/src/http/poll.ts +299 -0
  128. package/src/http/prompt-templates.ts +412 -0
  129. package/src/http/repos.ts +195 -0
  130. package/src/http/route-def.ts +123 -0
  131. package/src/http/schedules.ts +426 -0
  132. package/src/http/session-data.ts +241 -0
  133. package/src/http/stats.ts +174 -0
  134. package/src/http/tasks.ts +468 -0
  135. package/src/http/trackers/index.ts +10 -0
  136. package/src/http/trackers/linear.ts +187 -0
  137. package/src/http/types.ts +12 -0
  138. package/src/http/utils.ts +87 -0
  139. package/src/http/webhooks.ts +432 -0
  140. package/src/http/workflows.ts +530 -0
  141. package/src/http.ts +1 -1890
  142. package/src/linear/README.md +65 -0
  143. package/src/linear/app.ts +48 -0
  144. package/src/linear/client.ts +18 -0
  145. package/src/linear/index.ts +1 -0
  146. package/src/linear/oauth.ts +35 -0
  147. package/src/linear/outbound.ts +212 -0
  148. package/src/linear/sync.ts +567 -0
  149. package/src/linear/templates.ts +47 -0
  150. package/src/linear/types.ts +7 -0
  151. package/src/linear/webhook.ts +104 -0
  152. package/src/oauth/README.md +66 -0
  153. package/src/oauth/index.ts +6 -0
  154. package/src/oauth/wrapper.ts +204 -0
  155. package/src/prompts/base-prompt.ts +150 -265
  156. package/src/prompts/defaults.ts +196 -0
  157. package/src/prompts/registry.ts +57 -0
  158. package/src/prompts/resolver.ts +296 -0
  159. package/src/prompts/session-templates.ts +604 -0
  160. package/src/providers/claude-adapter.ts +442 -0
  161. package/src/providers/index.ts +24 -0
  162. package/src/providers/pi-mono-adapter.ts +442 -0
  163. package/src/providers/pi-mono-extension.ts +624 -0
  164. package/src/providers/pi-mono-mcp-client.ts +124 -0
  165. package/src/providers/types.ts +75 -0
  166. package/src/scheduler/scheduler.test.ts +2 -0
  167. package/src/scheduler/scheduler.ts +231 -40
  168. package/src/server.ts +97 -6
  169. package/src/slack/HEURISTICS.md +105 -0
  170. package/src/slack/actions.ts +133 -0
  171. package/src/slack/app.ts +7 -0
  172. package/src/slack/assistant.ts +118 -0
  173. package/src/slack/blocks.ts +233 -0
  174. package/src/slack/channel-activity.ts +177 -0
  175. package/src/slack/commands.ts +31 -17
  176. package/src/slack/files.ts +1 -1
  177. package/src/slack/handlers.test.ts +114 -1
  178. package/src/slack/handlers.ts +230 -55
  179. package/src/slack/responses.ts +120 -67
  180. package/src/slack/router.ts +17 -99
  181. package/src/slack/templates.ts +55 -0
  182. package/src/slack/thread-buffer.ts +213 -0
  183. package/src/slack/watcher.ts +119 -4
  184. package/src/tests/agent-activity.test.ts +247 -0
  185. package/src/tests/agentmail-filters.test.ts +97 -0
  186. package/src/tests/artifact-sdk.test.ts +800 -0
  187. package/src/tests/base-prompt.test.ts +264 -0
  188. package/src/tests/build-pi-skills.test.ts +127 -0
  189. package/src/tests/channel-activity.test.ts +363 -0
  190. package/src/tests/claude-adapter.test.ts +126 -0
  191. package/src/tests/context-versioning.test.ts +425 -0
  192. package/src/tests/db-queries-oauth.test.ts +197 -0
  193. package/src/tests/db-queries-tracker.test.ts +230 -0
  194. package/src/tests/epics.test.ts +3 -3
  195. package/src/tests/error-tracker.test.ts +368 -0
  196. package/src/tests/fetch-resolved-env.test.ts +167 -0
  197. package/src/tests/generate-default-claude-md.test.ts +9 -1
  198. package/src/tests/generate-identity-templates.test.ts +124 -0
  199. package/src/tests/gitlab-auth.test.ts +109 -0
  200. package/src/tests/gitlab-handlers.test.ts +691 -0
  201. package/src/tests/gitlab-vcs-db.test.ts +177 -0
  202. package/src/tests/heartbeat.test.ts +364 -0
  203. package/src/tests/http-api-integration.test.ts +1698 -0
  204. package/src/tests/linear-outbound-sync.test.ts +200 -0
  205. package/src/tests/linear-webhook.test.ts +406 -0
  206. package/src/tests/match-route.test.ts +187 -0
  207. package/src/tests/memory.test.ts +737 -0
  208. package/src/tests/migration-runner-regressions.test.ts +86 -0
  209. package/src/tests/model-control.test.ts +338 -0
  210. package/src/tests/oauth-wrapper.test.ts +147 -0
  211. package/src/tests/onboard-compose.test.ts +138 -0
  212. package/src/tests/onboard-env.test.ts +174 -0
  213. package/src/tests/onboard-manifest.test.ts +137 -0
  214. package/src/tests/pi-mono-adapter.test.ts +234 -0
  215. package/src/tests/pool-session-logs.test.ts +199 -0
  216. package/src/tests/progress-dedup.test.ts +98 -0
  217. package/src/tests/prompt-template-github.test.ts +682 -0
  218. package/src/tests/prompt-template-remaining.test.ts +504 -0
  219. package/src/tests/prompt-template-resolver.test.ts +621 -0
  220. package/src/tests/prompt-template-session.test.ts +363 -0
  221. package/src/tests/prompt-templates-db.test.ts +616 -0
  222. package/src/tests/provider-adapter.test.ts +122 -0
  223. package/src/tests/provider-command-format.test.ts +98 -0
  224. package/src/tests/reload-config.test.ts +170 -0
  225. package/src/tests/runner-polling-api.test.ts +25 -20
  226. package/src/tests/scheduled-tasks.test.ts +104 -0
  227. package/src/tests/scheduler-backoff.test.ts +166 -0
  228. package/src/tests/self-improvement.test.ts +541 -0
  229. package/src/tests/session-attach.test.ts +536 -0
  230. package/src/tests/session-costs.test.ts +267 -1
  231. package/src/tests/slack-actions.test.ts +133 -0
  232. package/src/tests/slack-assistant.test.ts +136 -0
  233. package/src/tests/slack-blocks.test.ts +246 -0
  234. package/src/tests/slack-metadata-inheritance.test.ts +243 -0
  235. package/src/tests/slack-queue-offline.test.ts +174 -0
  236. package/src/tests/slack-router.test.ts +181 -0
  237. package/src/tests/slack-thread-buffer.test.ts +305 -0
  238. package/src/tests/slack-thread-followups.test.ts +298 -0
  239. package/src/tests/slack-watcher.test.ts +101 -0
  240. package/src/tests/structured-output.test.ts +307 -0
  241. package/src/tests/swarm-repos.test.ts +198 -0
  242. package/src/tests/task-cancellation.test.ts +6 -4
  243. package/src/tests/task-working-dir.test.ts +176 -0
  244. package/src/tests/template-fetch.test.ts +490 -0
  245. package/src/tests/tool-annotations.test.ts +371 -0
  246. package/src/tests/tracker-tools.test.ts +184 -0
  247. package/src/tests/update-profile-agentid.test.ts +248 -0
  248. package/src/tests/update-profile-api.test.ts +143 -3
  249. package/src/tests/update-profile-auth.test.ts +195 -0
  250. package/src/tests/validation-adapters.test.ts +86 -0
  251. package/src/tests/vcs-provider.test.ts +27 -0
  252. package/src/tests/workflow-agent-task.test.ts +196 -0
  253. package/src/tests/workflow-async-v2.test.ts +508 -0
  254. package/src/tests/workflow-convergence.test.ts +541 -0
  255. package/src/tests/workflow-definition-validation.test.ts +366 -0
  256. package/src/tests/workflow-engine-v2.test.ts +691 -0
  257. package/src/tests/workflow-executors.test.ts +736 -0
  258. package/src/tests/workflow-http-v2.test.ts +599 -0
  259. package/src/tests/workflow-integration-io.test.ts +902 -0
  260. package/src/tests/workflow-io-schemas.test.ts +624 -0
  261. package/src/tests/workflow-registry.test.ts +592 -0
  262. package/src/tests/workflow-retry-v2.test.ts +401 -0
  263. package/src/tests/workflow-retry-validation.test.ts +282 -0
  264. package/src/tests/workflow-schedule-trigger.test.ts +104 -0
  265. package/src/tests/workflow-template.test.ts +288 -0
  266. package/src/tests/workflow-trigger-schema.test.ts +359 -0
  267. package/src/tests/workflow-triggers-v2.test.ts +264 -0
  268. package/src/tests/workflow-versions.test.ts +208 -0
  269. package/src/tests/workflow-workspace.test.ts +272 -0
  270. package/src/tests/x402-client.test.ts +117 -0
  271. package/src/tests/x402-config.test.ts +182 -0
  272. package/src/tests/x402-spending-tracker.test.ts +185 -0
  273. package/src/tools/cancel-task.ts +2 -0
  274. package/src/tools/context-diff.ts +171 -0
  275. package/src/tools/context-history.ts +138 -0
  276. package/src/tools/create-channel.ts +1 -0
  277. package/src/tools/db-query.ts +78 -0
  278. package/src/tools/delete-channel.ts +132 -0
  279. package/src/tools/epics/assign-task-to-epic.ts +1 -0
  280. package/src/tools/epics/create-epic.ts +3 -2
  281. package/src/tools/epics/delete-epic.ts +2 -0
  282. package/src/tools/epics/get-epic-details.ts +2 -0
  283. package/src/tools/epics/list-epics.ts +2 -0
  284. package/src/tools/epics/unassign-task-from-epic.ts +1 -0
  285. package/src/tools/epics/update-epic.ts +7 -4
  286. package/src/tools/get-swarm.ts +2 -0
  287. package/src/tools/get-task-details.ts +2 -0
  288. package/src/tools/get-tasks.ts +27 -1
  289. package/src/tools/inject-learning.ts +106 -0
  290. package/src/tools/join-swarm.ts +17 -7
  291. package/src/tools/list-channels.ts +2 -0
  292. package/src/tools/list-services.ts +2 -0
  293. package/src/tools/memory-get.ts +56 -0
  294. package/src/tools/memory-search.ts +131 -0
  295. package/src/tools/my-agent-info.ts +2 -0
  296. package/src/tools/poll-task.ts +2 -20
  297. package/src/tools/post-message.ts +1 -0
  298. package/src/tools/prompt-templates/delete.ts +86 -0
  299. package/src/tools/prompt-templates/get.ts +89 -0
  300. package/src/tools/prompt-templates/index.ts +5 -0
  301. package/src/tools/prompt-templates/list.ts +95 -0
  302. package/src/tools/prompt-templates/preview.ts +84 -0
  303. package/src/tools/prompt-templates/set.ts +117 -0
  304. package/src/tools/read-messages.ts +2 -0
  305. package/src/tools/register-agentmail-inbox.ts +166 -0
  306. package/src/tools/register-service.ts +2 -0
  307. package/src/tools/schedules/create-schedule.ts +134 -24
  308. package/src/tools/schedules/delete-schedule.ts +2 -0
  309. package/src/tools/schedules/list-schedules.ts +20 -4
  310. package/src/tools/schedules/run-schedule-now.ts +1 -0
  311. package/src/tools/schedules/update-schedule.ts +49 -17
  312. package/src/tools/send-task.ts +132 -10
  313. package/src/tools/slack-download-file.ts +4 -2
  314. package/src/tools/slack-list-channels.ts +2 -0
  315. package/src/tools/slack-post.ts +2 -0
  316. package/src/tools/slack-read.ts +2 -0
  317. package/src/tools/slack-reply.ts +2 -0
  318. package/src/tools/slack-upload-file.ts +2 -0
  319. package/src/tools/store-progress.ts +205 -4
  320. package/src/tools/swarm-config/delete-config.ts +87 -0
  321. package/src/tools/swarm-config/get-config.ts +108 -0
  322. package/src/tools/swarm-config/index.ts +4 -0
  323. package/src/tools/swarm-config/list-config.ts +99 -0
  324. package/src/tools/swarm-config/set-config.ts +118 -0
  325. package/src/tools/task-action.ts +50 -5
  326. package/src/tools/task-dedup.ts +97 -0
  327. package/src/tools/templates.ts +53 -0
  328. package/src/tools/tool-config.ts +124 -0
  329. package/src/tools/tracker/index.ts +6 -0
  330. package/src/tools/tracker/tracker-link-epic.ts +64 -0
  331. package/src/tools/tracker/tracker-link-task.ts +64 -0
  332. package/src/tools/tracker/tracker-map-agent.ts +57 -0
  333. package/src/tools/tracker/tracker-status.ts +56 -0
  334. package/src/tools/tracker/tracker-sync-status.ts +42 -0
  335. package/src/tools/tracker/tracker-unlink.ts +41 -0
  336. package/src/tools/unregister-service.ts +2 -0
  337. package/src/tools/update-profile.ts +172 -17
  338. package/src/tools/update-service-status.ts +2 -0
  339. package/src/tools/utils.ts +10 -1
  340. package/src/tools/workflows/create-workflow.ts +129 -0
  341. package/src/tools/workflows/delete-workflow.ts +42 -0
  342. package/src/tools/workflows/get-workflow-run.ts +59 -0
  343. package/src/tools/workflows/get-workflow.ts +53 -0
  344. package/src/tools/workflows/index.ts +9 -0
  345. package/src/tools/workflows/list-workflow-runs.ts +48 -0
  346. package/src/tools/workflows/list-workflows.ts +42 -0
  347. package/src/tools/workflows/retry-workflow-run.ts +40 -0
  348. package/src/tools/workflows/trigger-workflow.ts +96 -0
  349. package/src/tools/workflows/update-workflow.ts +133 -0
  350. package/src/tracker/types.ts +51 -0
  351. package/src/types.ts +530 -14
  352. package/src/utils/credentials.test.ts +156 -0
  353. package/src/utils/credentials.ts +50 -0
  354. package/src/utils/error-tracker.ts +190 -0
  355. package/src/vcs/index.ts +15 -0
  356. package/src/vcs/types.ts +5 -0
  357. package/src/workflows/checkpoint.ts +121 -0
  358. package/src/workflows/cooldown.ts +28 -0
  359. package/src/workflows/definition.ts +235 -0
  360. package/src/workflows/engine.ts +580 -0
  361. package/src/workflows/event-bus.ts +29 -0
  362. package/src/workflows/executors/agent-task.ts +103 -0
  363. package/src/workflows/executors/base.ts +86 -0
  364. package/src/workflows/executors/code-match.ts +88 -0
  365. package/src/workflows/executors/index.ts +16 -0
  366. package/src/workflows/executors/notify.ts +93 -0
  367. package/src/workflows/executors/property-match.ts +104 -0
  368. package/src/workflows/executors/raw-llm.ts +83 -0
  369. package/src/workflows/executors/registry.ts +76 -0
  370. package/src/workflows/executors/script.ts +103 -0
  371. package/src/workflows/executors/validate.ts +215 -0
  372. package/src/workflows/executors/vcs.ts +58 -0
  373. package/src/workflows/index.ts +61 -0
  374. package/src/workflows/input.ts +46 -0
  375. package/src/workflows/json-schema-validator.ts +118 -0
  376. package/src/workflows/recovery.ts +139 -0
  377. package/src/workflows/resume.ts +229 -0
  378. package/src/workflows/retry-poller.ts +216 -0
  379. package/src/workflows/template.ts +74 -0
  380. package/src/workflows/templates.ts +86 -0
  381. package/src/workflows/triggers.ts +124 -0
  382. package/src/workflows/validation.ts +104 -0
  383. package/src/workflows/version.ts +44 -0
  384. package/src/x402/cli.ts +140 -0
  385. package/src/x402/client.ts +192 -0
  386. package/src/x402/config.ts +131 -0
  387. package/src/x402/index.ts +37 -0
  388. package/src/x402/openfort-signer.ts +83 -0
  389. package/src/x402/spending-tracker.ts +109 -0
  390. package/templates/official/coder/CLAUDE.md +49 -0
  391. package/templates/official/coder/IDENTITY.md +28 -0
  392. package/templates/official/coder/SOUL.md +43 -0
  393. package/templates/official/coder/TOOLS.md +40 -0
  394. package/templates/official/coder/config.json +23 -0
  395. package/templates/official/coder/start-up.sh +23 -0
  396. package/templates/official/content-reviewer/CLAUDE.md +68 -0
  397. package/templates/official/content-reviewer/IDENTITY.md +28 -0
  398. package/templates/official/content-reviewer/SOUL.md +44 -0
  399. package/templates/official/content-reviewer/TOOLS.md +37 -0
  400. package/templates/official/content-reviewer/config.json +23 -0
  401. package/templates/official/content-reviewer/start-up.sh +23 -0
  402. package/templates/official/content-strategist/CLAUDE.md +63 -0
  403. package/templates/official/content-strategist/IDENTITY.md +33 -0
  404. package/templates/official/content-strategist/SOUL.md +48 -0
  405. package/templates/official/content-strategist/TOOLS.md +47 -0
  406. package/templates/official/content-strategist/config.json +23 -0
  407. package/templates/official/content-strategist/start-up.sh +23 -0
  408. package/templates/official/content-writer/CLAUDE.md +72 -0
  409. package/templates/official/content-writer/IDENTITY.md +30 -0
  410. package/templates/official/content-writer/SOUL.md +46 -0
  411. package/templates/official/content-writer/TOOLS.md +44 -0
  412. package/templates/official/content-writer/config.json +23 -0
  413. package/templates/official/content-writer/start-up.sh +23 -0
  414. package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
  415. package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
  416. package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
  417. package/templates/official/forward-deployed-engineer/config.json +21 -0
  418. package/templates/official/lead/CLAUDE.md +33 -0
  419. package/templates/official/lead/IDENTITY.md +36 -0
  420. package/templates/official/lead/SOUL.md +51 -0
  421. package/templates/official/lead/config.json +22 -0
  422. package/templates/official/researcher/CLAUDE.md +46 -0
  423. package/templates/official/researcher/IDENTITY.md +28 -0
  424. package/templates/official/researcher/SOUL.md +43 -0
  425. package/templates/official/researcher/config.json +21 -0
  426. package/templates/official/reviewer/CLAUDE.md +63 -0
  427. package/templates/official/reviewer/IDENTITY.md +28 -0
  428. package/templates/official/reviewer/SOUL.md +45 -0
  429. package/templates/official/reviewer/config.json +21 -0
  430. package/templates/official/tester/CLAUDE.md +53 -0
  431. package/templates/official/tester/IDENTITY.md +28 -0
  432. package/templates/official/tester/SOUL.md +55 -0
  433. package/templates/official/tester/config.json +21 -0
  434. package/templates/schema.ts +35 -0
  435. package/.claude/settings.local.json +0 -115
  436. package/.dockerignore +0 -61
  437. package/.editorconfig +0 -15
  438. package/.env.docker.example +0 -39
  439. package/.env.example +0 -40
  440. package/.github/workflows/ci.yml +0 -76
  441. package/.github/workflows/docker-and-deploy.yml +0 -117
  442. package/.wts-config.json +0 -4
  443. package/.wts-setup.ts +0 -102
  444. package/CLAUDE.md +0 -104
  445. package/CONTRIBUTING.md +0 -270
  446. package/DEPLOYMENT.md +0 -605
  447. package/Dockerfile +0 -57
  448. package/Dockerfile.worker +0 -157
  449. package/FAQ.md +0 -19
  450. package/MCP.md +0 -406
  451. package/UI.md +0 -40
  452. package/assets/agent-swarm-logo-orange.png +0 -0
  453. package/assets/agent-swarm-logo.png +0 -0
  454. package/assets/agent-swarm.mp4 +0 -0
  455. package/assets/agent-swarm.png +0 -0
  456. package/biome.json +0 -39
  457. package/deploy/DEPLOY.md +0 -60
  458. package/deploy/agent-swarm.service +0 -17
  459. package/deploy/docker-push.ts +0 -30
  460. package/deploy/install.ts +0 -85
  461. package/deploy/prod-db.ts +0 -42
  462. package/deploy/uninstall.ts +0 -12
  463. package/deploy/update.ts +0 -21
  464. package/docker-compose.example.yml +0 -159
  465. package/docker-entrypoint.sh +0 -352
  466. package/ecosystem.config.cjs +0 -66
  467. package/plugin/README.md +0 -1
  468. package/plugin/hooks/hooks.json +0 -71
  469. package/pyproject.toml +0 -9
  470. package/scripts/generate-mcp-docs.ts +0 -415
  471. package/slack-manifest.json +0 -71
  472. package/src/tests/get-inbox-message.test.ts +0 -145
  473. package/src/tools/get-inbox-message.ts +0 -89
  474. package/src/tools/inbox-delegate.ts +0 -113
  475. package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
  476. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
  477. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
  478. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
  479. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
  480. package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
  481. package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
  482. package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
  483. package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
  484. package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
  485. package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
  486. package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
  487. package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
  488. package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
  489. package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
  490. package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
  491. package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
  492. package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
  493. package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
  494. package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
  495. package/thoughts/shared/research/.gitkeep +0 -0
  496. package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
  497. package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
  498. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
  499. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
  500. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
  501. package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
  502. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
  503. package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
  504. package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
  505. package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
  506. package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
  507. package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
  508. package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
  509. package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
  510. package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
  511. package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
  512. package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
  513. package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
  514. package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
  515. package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
  516. package/tsconfig.json +0 -37
  517. package/ui/CLAUDE.md +0 -49
  518. package/ui/bun.lock +0 -771
  519. package/ui/index.html +0 -22
  520. package/ui/package-lock.json +0 -5290
  521. package/ui/package.json +0 -33
  522. package/ui/pnpm-lock.yaml +0 -3341
  523. package/ui/postcss.config.js +0 -6
  524. package/ui/public/logo.png +0 -0
  525. package/ui/src/App.tsx +0 -63
  526. package/ui/src/components/ActivityFeed.tsx +0 -440
  527. package/ui/src/components/AgentDetailPanel.tsx +0 -733
  528. package/ui/src/components/AgentsPanel.tsx +0 -815
  529. package/ui/src/components/ChatPanel.tsx +0 -1920
  530. package/ui/src/components/ConfigModal.tsx +0 -253
  531. package/ui/src/components/Dashboard.tsx +0 -832
  532. package/ui/src/components/EditAgentProfileModal.tsx +0 -433
  533. package/ui/src/components/EpicDetailPage.tsx +0 -741
  534. package/ui/src/components/EpicsPanel.tsx +0 -566
  535. package/ui/src/components/Header.tsx +0 -160
  536. package/ui/src/components/JsonViewer.tsx +0 -171
  537. package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
  538. package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
  539. package/ui/src/components/ServicesPanel.tsx +0 -622
  540. package/ui/src/components/SessionLogPanel.tsx +0 -1219
  541. package/ui/src/components/StatsBar.tsx +0 -321
  542. package/ui/src/components/StatusBadge.tsx +0 -168
  543. package/ui/src/components/TaskDetailPanel.tsx +0 -903
  544. package/ui/src/components/TasksPanel.tsx +0 -614
  545. package/ui/src/components/UsageCharts.tsx +0 -216
  546. package/ui/src/components/UsageTab.tsx +0 -394
  547. package/ui/src/hooks/queries.ts +0 -353
  548. package/ui/src/hooks/useAutoScroll.ts +0 -83
  549. package/ui/src/index.css +0 -257
  550. package/ui/src/lib/api.ts +0 -268
  551. package/ui/src/lib/config.ts +0 -35
  552. package/ui/src/lib/contentPreview.ts +0 -208
  553. package/ui/src/lib/theme.ts +0 -214
  554. package/ui/src/lib/utils.ts +0 -88
  555. package/ui/src/main.tsx +0 -28
  556. package/ui/src/types/api.ts +0 -323
  557. package/ui/src/vite-env.d.ts +0 -1
  558. package/ui/tailwind.config.js +0 -37
  559. package/ui/tsconfig.json +0 -31
  560. package/ui/vite.config.ts +0 -35
  561. /package/{thoughts/shared/plans → templates/community}/.gitkeep +0 -0
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Block Kit message builders for structured Slack messages.
3
+ *
4
+ * Consolidates getTaskLink and markdownToSlack (previously duplicated
5
+ * across responses.ts, handlers.ts, thread-buffer.ts).
6
+ */
7
+
8
+ const appUrl = process.env.APP_URL || "";
9
+
10
+ // Slack limits section text to 3000 chars; we use 2900 for safety
11
+ const MAX_SECTION_LENGTH = 2900;
12
+
13
+ // biome-ignore lint/suspicious/noExplicitAny: Slack block types are complex unions; we build plain objects
14
+ type SlackBlock = any;
15
+
16
+ // --- Shared utilities ---
17
+
18
+ /**
19
+ * Get a Slack-formatted link to the task in the dashboard, or just the short ID.
20
+ */
21
+ export function getTaskLink(taskId: string): string {
22
+ const shortId = taskId.slice(0, 8);
23
+ if (appUrl) {
24
+ return `<${appUrl}?tab=tasks&task=${taskId}&expand=true|\`${shortId}\`>`;
25
+ }
26
+ return `\`${shortId}\``;
27
+ }
28
+
29
+ /**
30
+ * Get a raw dashboard URL for a task (for link buttons).
31
+ */
32
+ export function getTaskUrl(taskId: string): string {
33
+ if (appUrl) {
34
+ return `${appUrl}?tab=tasks&task=${taskId}&expand=true`;
35
+ }
36
+ return "";
37
+ }
38
+
39
+ /**
40
+ * Convert GitHub-flavored markdown to Slack mrkdwn format.
41
+ *
42
+ * Key differences:
43
+ * - GitHub: **bold**, *italic*, ~~strike~~, [text](url)
44
+ * - Slack: *bold*, _italic_, ~strike~, <url|text>
45
+ */
46
+ export function markdownToSlack(text: string): string {
47
+ return (
48
+ text
49
+ // Headers to bold placeholder (# Header -> bold, protected from italic)
50
+ .replace(/^#{1,6}\s+(.+)$/gm, "\uE000$1\uE001")
51
+ // Bold **text** -> placeholder (to avoid italic chain converting *bold* to _italic_)
52
+ .replace(/\*\*(.+?)\*\*/g, "\uE000$1\uE001")
53
+ // Italic *text* -> _text_ (single asterisks, now safe from bold placeholders)
54
+ .replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, "_$1_")
55
+ // Restore bold from placeholder -> *text*
56
+ .replace(/\uE000(.+?)\uE001/g, "*$1*")
57
+ // Strikethrough ~~text~~ -> ~text~
58
+ .replace(/~~(.+?)~~/g, "~$1~")
59
+ // Links [text](url) -> <url|text>
60
+ .replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>")
61
+ // Inline code already works the same
62
+ // Bullet points already work the same
63
+ // Remove excessive blank lines
64
+ .replace(/\n{3,}/g, "\n\n")
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Split text into chunks that fit within Slack's section text limit.
70
+ */
71
+ function splitText(text: string): string[] {
72
+ if (text.length <= MAX_SECTION_LENGTH) return [text];
73
+
74
+ const chunks: string[] = [];
75
+ let remaining = text;
76
+ while (remaining.length > 0) {
77
+ if (remaining.length <= MAX_SECTION_LENGTH) {
78
+ chunks.push(remaining);
79
+ break;
80
+ }
81
+ // Try to split at a newline
82
+ let splitIdx = remaining.lastIndexOf("\n", MAX_SECTION_LENGTH);
83
+ if (splitIdx < MAX_SECTION_LENGTH / 2) {
84
+ // No good newline break, try splitting at space
85
+ splitIdx = remaining.lastIndexOf(" ", MAX_SECTION_LENGTH);
86
+ }
87
+ if (splitIdx < MAX_SECTION_LENGTH / 2) {
88
+ // No good break point at all, hard split
89
+ splitIdx = MAX_SECTION_LENGTH;
90
+ }
91
+ chunks.push(remaining.slice(0, splitIdx));
92
+ remaining = remaining.slice(splitIdx).trimStart();
93
+ }
94
+ return chunks;
95
+ }
96
+
97
+ // --- Block primitives ---
98
+
99
+ function contextBlock(...elements: string[]): SlackBlock {
100
+ return {
101
+ type: "context",
102
+ elements: elements.map((text) => ({ type: "mrkdwn", text })),
103
+ };
104
+ }
105
+
106
+ function sectionBlock(text: string): SlackBlock {
107
+ return { type: "section", text: { type: "mrkdwn", text } };
108
+ }
109
+
110
+ function cancelActionBlock(taskId: string): SlackBlock {
111
+ return {
112
+ type: "actions",
113
+ elements: [
114
+ {
115
+ type: "button",
116
+ text: { type: "plain_text", text: "Cancel" },
117
+ action_id: "cancel_task",
118
+ value: taskId,
119
+ style: "danger",
120
+ confirm: {
121
+ title: { type: "plain_text", text: "Cancel task?" },
122
+ text: { type: "mrkdwn", text: "This will cancel the task. Are you sure?" },
123
+ confirm: { type: "plain_text", text: "Yes, cancel" },
124
+ deny: { type: "plain_text", text: "Never mind" },
125
+ },
126
+ },
127
+ ],
128
+ };
129
+ }
130
+
131
+ // --- High-level block builders ---
132
+
133
+ /**
134
+ * Build blocks for a completed task response.
135
+ * Single-line header with agent/task metadata, then body content.
136
+ */
137
+ export function buildCompletedBlocks(opts: {
138
+ agentName: string;
139
+ taskId: string;
140
+ body: string;
141
+ duration?: string;
142
+ }): SlackBlock[] {
143
+ const taskLink = getTaskLink(opts.taskId);
144
+ let line = `✅ *${opts.agentName}* (${taskLink})`;
145
+ if (opts.duration) line += ` · ${opts.duration}`;
146
+
147
+ const blocks: SlackBlock[] = [sectionBlock(line)];
148
+ for (const chunk of splitText(opts.body)) {
149
+ blocks.push(sectionBlock(chunk));
150
+ }
151
+ return blocks;
152
+ }
153
+
154
+ /**
155
+ * Build blocks for a failed task response.
156
+ * Single-line header, then error in code block.
157
+ */
158
+ export function buildFailedBlocks(opts: {
159
+ agentName: string;
160
+ taskId: string;
161
+ reason: string;
162
+ duration?: string;
163
+ }): SlackBlock[] {
164
+ const taskLink = getTaskLink(opts.taskId);
165
+ let line = `❌ *${opts.agentName}* (${taskLink})`;
166
+ if (opts.duration) line += ` · ${opts.duration}`;
167
+
168
+ return [sectionBlock(line), sectionBlock(`\`\`\`${opts.reason}\`\`\``)];
169
+ }
170
+
171
+ /**
172
+ * Build blocks for a progress update.
173
+ * Single line with cancel button.
174
+ */
175
+ export function buildProgressBlocks(opts: {
176
+ agentName: string;
177
+ taskId: string;
178
+ progress: string;
179
+ }): SlackBlock[] {
180
+ const shortId = opts.taskId.slice(0, 8);
181
+ return [
182
+ sectionBlock(`⏳ *${opts.agentName}* (\`${shortId}\`): ${opts.progress}`),
183
+ cancelActionBlock(opts.taskId),
184
+ ];
185
+ }
186
+
187
+ /**
188
+ * Build blocks for a cancelled task card (used when cancel button is clicked).
189
+ */
190
+ export function buildCancelledBlocks(opts: { agentName: string; taskId: string }): SlackBlock[] {
191
+ const taskLink = getTaskLink(opts.taskId);
192
+ return [sectionBlock(`🚫 *${opts.agentName}* (${taskLink}) — Cancelled`)];
193
+ }
194
+
195
+ /**
196
+ * Build blocks for a consolidated task assignment summary.
197
+ * Each assignment/queue/failure is a single line.
198
+ */
199
+ export function buildAssignmentSummaryBlocks(results: {
200
+ assigned: Array<{ agentName: string; taskId: string }>;
201
+ queued: Array<{ agentName: string; taskId: string }>;
202
+ failed: Array<{ agentName: string; reason: string }>;
203
+ }): SlackBlock[] {
204
+ const lines: string[] = [];
205
+
206
+ for (const a of results.assigned) {
207
+ lines.push(`📡 Task assigned to: *${a.agentName}* (${getTaskLink(a.taskId)})`);
208
+ }
209
+ for (const q of results.queued) {
210
+ lines.push(`📡 Task queued for: *${q.agentName}* (${getTaskLink(q.taskId)})`);
211
+ }
212
+ for (const f of results.failed) {
213
+ lines.push(`⚠️ Could not assign to: *${f.agentName}* — ${f.reason}`);
214
+ }
215
+
216
+ return [sectionBlock(lines.join("\n"))];
217
+ }
218
+
219
+ /**
220
+ * Build blocks for buffer flush feedback.
221
+ */
222
+ export function buildBufferFlushBlocks(opts: {
223
+ messageCount: number;
224
+ taskId: string;
225
+ hasDependency: boolean;
226
+ }): SlackBlock[] {
227
+ const taskLink = getTaskLink(opts.taskId);
228
+ const text = opts.hasDependency
229
+ ? `${opts.messageCount} follow-up message(s) queued pending completion of current task`
230
+ : `${opts.messageCount} follow-up message(s) batched into task`;
231
+
232
+ return [contextBlock(`📡 _${text}_ (${taskLink})`)];
233
+ }
@@ -0,0 +1,177 @@
1
+ import { getSlackApp } from "./app";
2
+
3
+ export interface ChannelMessage {
4
+ channelId: string;
5
+ channelName?: string;
6
+ ts: string;
7
+ user: string;
8
+ text: string;
9
+ }
10
+
11
+ // ─── Caches ─────────────────────────────────────────────────────────────────
12
+
13
+ let cachedBotUserId: string | null = null;
14
+
15
+ interface ChannelCache {
16
+ channels: Array<{ id: string; name?: string }>;
17
+ fetchedAt: number;
18
+ }
19
+ let channelCache: ChannelCache | null = null;
20
+ const CHANNEL_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
21
+
22
+ // ─── Public API ─────────────────────────────────────────────────────────────
23
+
24
+ /**
25
+ * Fetch recent non-bot messages from channels the bot is in,
26
+ * filtering to only messages newer than the provided cursors.
27
+ *
28
+ * For channels with no cursor (first run), returns the latest message ts
29
+ * as a seed cursor without triggering — prevents cold-start flood.
30
+ *
31
+ * Returns messages sorted oldest-first, plus seed cursors for new channels.
32
+ */
33
+ export async function fetchChannelActivity(
34
+ cursors: Map<string, string>,
35
+ allowedChannelIds?: string[],
36
+ limit = 10,
37
+ ): Promise<{ messages: ChannelMessage[]; seedCursors: Map<string, string> }> {
38
+ const app = getSlackApp();
39
+ if (!app) return { messages: [], seedCursors: new Map() };
40
+
41
+ const client = app.client;
42
+
43
+ // Get channels the bot is a member of (cached, with pagination)
44
+ let channels = getCachedChannels();
45
+ if (!channels) {
46
+ channels = await fetchAllBotChannels(client);
47
+ setCachedChannels(channels);
48
+ }
49
+
50
+ // Filter by allowlist if configured
51
+ if (allowedChannelIds && allowedChannelIds.length > 0) {
52
+ const allowed = new Set(allowedChannelIds);
53
+ channels = channels.filter((ch) => allowed.has(ch.id));
54
+ }
55
+
56
+ if (channels.length === 0) return { messages: [], seedCursors: new Map() };
57
+
58
+ const messages: ChannelMessage[] = [];
59
+ const seedCursors = new Map<string, string>();
60
+
61
+ // Cache bot user ID (never changes during runtime)
62
+ if (!cachedBotUserId) {
63
+ const authResult = await client.auth.test();
64
+ cachedBotUserId = authResult.user_id as string;
65
+ }
66
+ const botUserId = cachedBotUserId;
67
+
68
+ for (const channel of channels) {
69
+ const channelId = channel.id;
70
+ const cursor = cursors.get(channelId);
71
+
72
+ try {
73
+ // Cold-start: no cursor for this channel — seed it without triggering
74
+ if (!cursor) {
75
+ const seedResult = await client.conversations.history({
76
+ channel: channelId,
77
+ limit: 1,
78
+ });
79
+ const latestMsg = seedResult.messages?.[0];
80
+ if (latestMsg?.ts) {
81
+ seedCursors.set(channelId, latestMsg.ts);
82
+ }
83
+ continue; // Don't trigger on first run
84
+ }
85
+
86
+ const historyResult = await client.conversations.history({
87
+ channel: channelId,
88
+ oldest: cursor,
89
+ limit,
90
+ });
91
+
92
+ for (const msg of historyResult.messages || []) {
93
+ // Skip the cursor message itself (oldest is inclusive)
94
+ if (msg.ts === cursor) continue;
95
+ // Skip bot messages (bot_id present, or subtype bot_message)
96
+ if (msg.bot_id || msg.subtype === "bot_message") continue;
97
+ // Skip our own bot's messages
98
+ if (msg.user === botUserId) continue;
99
+ // Skip messages without text or user
100
+ if (!msg.text?.trim() || !msg.user) continue;
101
+ // Skip thread replies (only top-level channel messages)
102
+ if (msg.thread_ts && msg.thread_ts !== msg.ts) continue;
103
+
104
+ messages.push({
105
+ channelId,
106
+ channelName: channel.name,
107
+ ts: msg.ts!,
108
+ user: msg.user,
109
+ text: msg.text,
110
+ });
111
+ }
112
+ } catch (err) {
113
+ // Log but don't fail — channel might have been archived or bot removed
114
+ console.warn(`[channel-activity] Failed to fetch history for ${channelId}: ${err}`);
115
+ }
116
+ }
117
+
118
+ // Sort by timestamp ascending (oldest first)
119
+ messages.sort((a, b) => Number.parseFloat(a.ts) - Number.parseFloat(b.ts));
120
+
121
+ return { messages, seedCursors };
122
+ }
123
+
124
+ // ─── Internal helpers ───────────────────────────────────────────────────────
125
+
126
+ function getCachedChannels(): Array<{ id: string; name?: string }> | null {
127
+ if (!channelCache) return null;
128
+ if (Date.now() - channelCache.fetchedAt > CHANNEL_CACHE_TTL_MS) {
129
+ channelCache = null;
130
+ return null;
131
+ }
132
+ return channelCache.channels;
133
+ }
134
+
135
+ function setCachedChannels(channels: Array<{ id: string; name?: string }>): void {
136
+ channelCache = { channels, fetchedAt: Date.now() };
137
+ }
138
+
139
+ /** Fetch all channels the bot is a member of, handling pagination */
140
+ async function fetchAllBotChannels(
141
+ client: ReturnType<typeof getSlackApp> extends infer A
142
+ ? A extends { client: infer C }
143
+ ? C
144
+ : never
145
+ : never,
146
+ ): Promise<Array<{ id: string; name?: string }>> {
147
+ const channels: Array<{ id: string; name?: string }> = [];
148
+ let nextCursor: string | undefined;
149
+
150
+ do {
151
+ const result = await (
152
+ client as {
153
+ conversations: {
154
+ list: (args: Record<string, unknown>) => Promise<{
155
+ channels?: Array<{ id?: string; name?: string; is_member?: boolean }>;
156
+ response_metadata?: { next_cursor?: string };
157
+ }>;
158
+ };
159
+ }
160
+ ).conversations.list({
161
+ types: "public_channel,private_channel",
162
+ exclude_archived: true,
163
+ limit: 200,
164
+ cursor: nextCursor || undefined,
165
+ });
166
+
167
+ for (const ch of result.channels || []) {
168
+ if (ch.id && ch.is_member) {
169
+ channels.push({ id: ch.id, name: ch.name });
170
+ }
171
+ }
172
+
173
+ nextCursor = result.response_metadata?.next_cursor || undefined;
174
+ } while (nextCursor);
175
+
176
+ return channels;
177
+ }
@@ -59,30 +59,44 @@ export function registerCommandHandler(app: App): void {
59
59
 
60
60
  app.command("/agent-swarm-help", async ({ ack, respond }) => {
61
61
  await ack();
62
+ console.log("[Slack] /agent-swarm-help command invoked");
62
63
 
63
- await respond({
64
- response_type: "ephemeral",
65
- blocks: [
66
- {
67
- type: "header",
68
- text: { type: "plain_text", text: "Agent Swarm Help" },
69
- },
70
- {
71
- type: "section",
72
- text: {
73
- type: "mrkdwn",
74
- text: `*How to assign tasks:*
64
+ const additiveSlack = process.env.ADDITIVE_SLACK === "true";
65
+
66
+ const sections = [
67
+ `*How to assign tasks:*
75
68
  • Mention an agent by name: \`Hey Alpha, can you review this code?\`
76
69
  • Use explicit ID: \`swarm#<uuid> please analyze the logs\`
77
70
  • Broadcast to all: \`swarm#all status report please\`
78
71
  • Mention the bot: \`@agent-swarm help me\` (routes to lead agent)
72
+ • Thread @mentions auto-route to the worker already active in that thread`,
73
+ ];
74
+
75
+ if (additiveSlack) {
76
+ sections.push(`*Additive Slack (enabled):*
77
+ • Thread replies (no @mention needed) are automatically buffered and batched into follow-up tasks
78
+ • \`!now <message>\` in a thread — Flush buffer immediately, skip dependency queue
79
+ • Follow-up tasks auto-depend on the active task in the thread for natural sequencing`);
80
+ }
79
81
 
80
- *Commands:*
82
+ sections.push(`*Commands:*
81
83
  • \`/agent-swarm-status\` - Show all agents and their current status
82
- • \`/agent-swarm-help\` - Show this help message`,
83
- },
84
- },
85
- ],
84
+ • \`/agent-swarm-help\` - Show this help message`);
85
+
86
+ const blocks = [
87
+ {
88
+ type: "header" as const,
89
+ text: { type: "plain_text" as const, text: "Agent Swarm Help" },
90
+ },
91
+ ...sections.map((text) => ({
92
+ type: "section" as const,
93
+ text: { type: "mrkdwn" as const, text },
94
+ })),
95
+ ];
96
+
97
+ await respond({
98
+ response_type: "ephemeral",
99
+ blocks,
86
100
  });
87
101
  });
88
102
  }
@@ -11,7 +11,7 @@ export const MAX_FILE_SIZE = 1073741824;
11
11
  /**
12
12
  * Default download directory for Slack files
13
13
  */
14
- export const DEFAULT_DOWNLOAD_DIR = "/workspace/shared/downloads/slack";
14
+ export const DEFAULT_DOWNLOAD_DIR = `/workspace/shared/downloads/${process.env.AGENT_ID || "default"}/slack`;
15
15
 
16
16
  /**
17
17
  * File object from Slack API (partial, commonly used fields)
@@ -1,5 +1,12 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { checkUserAccess, type UserFilterConfig } from "./handlers";
2
+ import type { SlackFile } from "./files";
3
+ import {
4
+ buildAttachmentText,
5
+ buildEffectiveText,
6
+ checkUserAccess,
7
+ formatFileSize,
8
+ type UserFilterConfig,
9
+ } from "./handlers";
3
10
 
4
11
  describe("checkUserAccess", () => {
5
12
  describe("when filtering is disabled (empty config)", () => {
@@ -163,3 +170,109 @@ describe("checkUserAccess", () => {
163
170
  });
164
171
  });
165
172
  });
173
+
174
+ // Helper to create a minimal SlackFile for testing
175
+ function makeFile(overrides: Partial<SlackFile> = {}): SlackFile {
176
+ return {
177
+ id: "F0TEST123",
178
+ name: "test_file.txt",
179
+ mimetype: "text/plain",
180
+ filetype: "txt",
181
+ size: 1024,
182
+ url_private: "https://files.slack.com/private",
183
+ url_private_download: "https://files.slack.com/download",
184
+ ...overrides,
185
+ };
186
+ }
187
+
188
+ describe("formatFileSize", () => {
189
+ test("formats bytes", () => {
190
+ expect(formatFileSize(0)).toBe("0 B");
191
+ expect(formatFileSize(512)).toBe("512 B");
192
+ expect(formatFileSize(1023)).toBe("1023 B");
193
+ });
194
+
195
+ test("formats kilobytes", () => {
196
+ expect(formatFileSize(1024)).toBe("1.0 KB");
197
+ expect(formatFileSize(1536)).toBe("1.5 KB");
198
+ expect(formatFileSize(1024 * 100)).toBe("100.0 KB");
199
+ });
200
+
201
+ test("formats megabytes", () => {
202
+ expect(formatFileSize(1024 * 1024)).toBe("1.0 MB");
203
+ expect(formatFileSize(1024 * 1024 * 2.5)).toBe("2.5 MB");
204
+ });
205
+
206
+ test("formats gigabytes", () => {
207
+ expect(formatFileSize(1024 * 1024 * 1024)).toBe("1.0 GB");
208
+ expect(formatFileSize(1024 * 1024 * 1024 * 1.5)).toBe("1.5 GB");
209
+ });
210
+ });
211
+
212
+ describe("buildAttachmentText", () => {
213
+ test("formats a single file", () => {
214
+ const files = [
215
+ makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 156000 }),
216
+ ];
217
+ expect(buildAttachmentText(files)).toBe("[File: voice.m4a (audio/mp4, 152.3 KB) id=F001]");
218
+ });
219
+
220
+ test("formats multiple files", () => {
221
+ const files = [
222
+ makeFile({ id: "F001", name: "photo.png", mimetype: "image/png", size: 2048000 }),
223
+ makeFile({ id: "F002", name: "doc.pdf", mimetype: "application/pdf", size: 512000 }),
224
+ ];
225
+ const result = buildAttachmentText(files);
226
+ expect(result).toContain("[File: photo.png (image/png,");
227
+ expect(result).toContain("[File: doc.pdf (application/pdf,");
228
+ expect(result).toContain("id=F001");
229
+ expect(result).toContain("id=F002");
230
+ expect(result.split("\n")).toHaveLength(2);
231
+ });
232
+
233
+ test("handles empty array", () => {
234
+ expect(buildAttachmentText([])).toBe("");
235
+ });
236
+ });
237
+
238
+ describe("buildEffectiveText", () => {
239
+ test("returns text as-is when no files", () => {
240
+ expect(buildEffectiveText("hello world")).toBe("hello world");
241
+ expect(buildEffectiveText("hello world", undefined)).toBe("hello world");
242
+ expect(buildEffectiveText("hello world", [])).toBe("hello world");
243
+ });
244
+
245
+ test("returns attachment text when no text", () => {
246
+ const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
247
+ const result = buildEffectiveText(undefined, files);
248
+ expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
249
+ });
250
+
251
+ test("returns attachment text when text is empty string", () => {
252
+ const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
253
+ const result = buildEffectiveText("", files);
254
+ expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
255
+ });
256
+
257
+ test("returns attachment text when text is whitespace only", () => {
258
+ const files = [makeFile({ id: "F001", name: "voice.m4a", mimetype: "audio/mp4", size: 1024 })];
259
+ const result = buildEffectiveText(" ", files);
260
+ expect(result).toBe("[File: voice.m4a (audio/mp4, 1.0 KB) id=F001]");
261
+ });
262
+
263
+ test("combines text and files when both present", () => {
264
+ const files = [
265
+ makeFile({ id: "F001", name: "screenshot.png", mimetype: "image/png", size: 2048 }),
266
+ ];
267
+ const result = buildEffectiveText("<@UBOT> fix this bug", files);
268
+ expect(result).toContain("<@UBOT> fix this bug");
269
+ expect(result).toContain("[File: screenshot.png (image/png,");
270
+ expect(result).toContain("\n\n");
271
+ });
272
+
273
+ test("returns empty string when neither text nor files", () => {
274
+ expect(buildEffectiveText(undefined)).toBe("");
275
+ expect(buildEffectiveText(undefined, [])).toBe("");
276
+ expect(buildEffectiveText("")).toBe("");
277
+ });
278
+ });