@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
package/src/http.ts CHANGED
@@ -1,1890 +1 @@
1
- import { randomUUID } from "node:crypto";
2
- import {
3
- createServer as createHttpServer,
4
- type IncomingMessage,
5
- type Server,
6
- type ServerResponse,
7
- } from "node:http";
8
- import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
- import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
10
- import { createServer, hasCapability } from "@/server";
11
- import {
12
- assignTaskToEpic,
13
- claimInboxMessages,
14
- claimMentions,
15
- claimOfferedTask,
16
- closeDb,
17
- completeTask,
18
- createAgent,
19
- createEpic,
20
- createSessionCost,
21
- createSessionLogs,
22
- createTaskExtended,
23
- deleteEpic,
24
- failTask,
25
- getActiveTaskCount,
26
- getAgentById,
27
- getAgentWithTasks,
28
- getAllAgents,
29
- getAllAgentsWithTasks,
30
- getAllChannels,
31
- getAllLogs,
32
- getAllServices,
33
- getAllSessionCosts,
34
- getAllTasks,
35
- getChannelById,
36
- getChannelMessages,
37
- getDb,
38
- getEpicById,
39
- getEpics,
40
- getEpicsWithProgressUpdates,
41
- getEpicWithProgress,
42
- getInboxSummary,
43
- getLogsByAgentId,
44
- getLogsByTaskId,
45
- getOfferedTasksForAgent,
46
- getPausedTasksForAgent,
47
- getPendingTaskForAgent,
48
- getRecentlyCancelledTasksForAgent,
49
- getRecentlyFinishedWorkerTasks,
50
- getScheduledTasks,
51
- getServicesByAgentId,
52
- getSessionCostsByAgentId,
53
- getSessionCostsByTaskId,
54
- getSessionLogsByTaskId,
55
- getTaskById,
56
- getTaskStats,
57
- getTasksByEpicId,
58
- getTasksCount,
59
- getUnassignedTasksCount,
60
- hasCapacity,
61
- markEpicsProgressNotified,
62
- markTasksNotified,
63
- pauseTask,
64
- postMessage,
65
- resetEmptyPollCount,
66
- resumeTask,
67
- shouldBlockPolling,
68
- startTask,
69
- updateAgentMaxTasks,
70
- updateAgentName,
71
- updateAgentProfile,
72
- updateAgentStatus,
73
- updateAgentStatusFromCapacity,
74
- updateEpic,
75
- } from "./be/db";
76
- import type {
77
- CheckRunEvent,
78
- CheckSuiteEvent,
79
- CommentEvent,
80
- IssueEvent,
81
- PullRequestEvent,
82
- PullRequestReviewEvent,
83
- WorkflowRunEvent,
84
- } from "./github";
85
- import {
86
- handleCheckRun,
87
- handleCheckSuite,
88
- handleComment,
89
- handleIssue,
90
- handlePullRequest,
91
- handlePullRequestReview,
92
- handleWorkflowRun,
93
- initGitHub,
94
- isGitHubEnabled,
95
- verifyWebhookSignature,
96
- } from "./github";
97
- import { startSlackApp, stopSlackApp } from "./slack";
98
- import type { AgentLog, AgentStatus, EpicStatus, SessionCost } from "./types";
99
-
100
- const port = parseInt(process.env.PORT || process.argv[2] || "3013", 10);
101
- const apiKey = process.env.API_KEY || "";
102
-
103
- // Use globalThis to persist state across hot reloads
104
- const globalState = globalThis as typeof globalThis & {
105
- __httpServer?: Server<typeof IncomingMessage, typeof ServerResponse>;
106
- __transports?: Record<string, StreamableHTTPServerTransport>;
107
- __sigintRegistered?: boolean;
108
- };
109
-
110
- // Clean up previous server on hot reload
111
- if (globalState.__httpServer) {
112
- console.log("[HTTP] Hot reload detected, closing previous server...");
113
- globalState.__httpServer.close();
114
- }
115
-
116
- const transports: Record<string, StreamableHTTPServerTransport> = globalState.__transports ?? {};
117
-
118
- function setCorsHeaders(res: ServerResponse) {
119
- res.setHeader("Access-Control-Allow-Origin", "*");
120
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
121
- res.setHeader("Access-Control-Allow-Headers", "*");
122
- res.setHeader("Access-Control-Expose-Headers", "*");
123
- }
124
-
125
- function parseQueryParams(url: string): URLSearchParams {
126
- const queryIndex = url.indexOf("?");
127
- if (queryIndex === -1) return new URLSearchParams();
128
- return new URLSearchParams(url.slice(queryIndex + 1));
129
- }
130
-
131
- function getPathSegments(url: string): string[] {
132
- const pathEnd = url.indexOf("?");
133
- const path = pathEnd === -1 ? url : url.slice(0, pathEnd);
134
- return path.split("/").filter(Boolean);
135
- }
136
-
137
- /** Add capacity info to agent response */
138
- function agentWithCapacity<T extends { id: string; maxTasks?: number }>(
139
- agent: T,
140
- ): T & { capacity: { current: number; max: number; available: number } } {
141
- const activeCount = getActiveTaskCount(agent.id);
142
- const max = agent.maxTasks ?? 1;
143
- return {
144
- ...agent,
145
- capacity: {
146
- current: activeCount,
147
- max,
148
- available: Math.max(0, max - activeCount),
149
- },
150
- };
151
- }
152
-
153
- const httpServer = createHttpServer(async (req, res) => {
154
- const startTime = performance.now();
155
- let statusCode = 200;
156
-
157
- // Wrap writeHead to capture status code
158
- const originalWriteHead = res.writeHead.bind(res);
159
- res.writeHead = (code: number, ...args: unknown[]) => {
160
- statusCode = code;
161
- // @ts-expect-error - writeHead has multiple overloads
162
- return originalWriteHead(code, ...args);
163
- };
164
-
165
- // Log request completion
166
- const logRequest = () => {
167
- const elapsed = (performance.now() - startTime).toFixed(1);
168
- const statusEmoji = statusCode >= 400 ? "⚠️" : "✓";
169
- console.log(`[HTTP] ${statusEmoji} ${req.method} ${req.url} → ${statusCode} (${elapsed}ms)`);
170
- };
171
-
172
- // Ensure we log on response finish
173
- res.on("finish", logRequest);
174
-
175
- // Log errors
176
- res.on("error", (err) => {
177
- console.error(`[HTTP] ❌ ${req.method} ${req.url} → Error: ${err.message}`);
178
- });
179
-
180
- setCorsHeaders(res);
181
-
182
- // Handle preflight
183
- if (req.method === "OPTIONS") {
184
- res.writeHead(204);
185
- res.end();
186
- return;
187
- }
188
-
189
- const sessionId = req.headers["mcp-session-id"] as string | undefined;
190
- const myAgentId = req.headers["x-agent-id"] as string | undefined;
191
-
192
- if (req.url === "/health") {
193
- // Read version from package.json
194
- const version = (await Bun.file("package.json").json()).version;
195
-
196
- res.writeHead(200, { "Content-Type": "application/json" });
197
- res.end(
198
- JSON.stringify({
199
- status: "ok",
200
- version,
201
- }),
202
- );
203
-
204
- return;
205
- }
206
-
207
- // API key authentication (if API_KEY is configured)
208
- // Skip auth for GitHub webhook (it has its own signature verification)
209
- const isGitHubWebhook = req.url?.startsWith("/api/github/webhook");
210
- if (apiKey && !isGitHubWebhook) {
211
- const authHeader = req.headers.authorization;
212
- const providedKey = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
213
-
214
- if (providedKey !== apiKey) {
215
- res.writeHead(401, { "Content-Type": "application/json" });
216
- res.end(JSON.stringify({ error: "Unauthorized" }));
217
- return;
218
- }
219
- }
220
-
221
- if (req.method === "GET" && (req.url === "/me" || req.url?.startsWith("/me?"))) {
222
- if (!myAgentId) {
223
- res.writeHead(400, { "Content-Type": "application/json" });
224
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
225
- return;
226
- }
227
-
228
- const agent = getAgentById(myAgentId);
229
-
230
- if (!agent) {
231
- res.writeHead(404, { "Content-Type": "application/json" });
232
- res.end(JSON.stringify({ error: "Agent not found" }));
233
- return;
234
- }
235
-
236
- // Check for ?include=inbox query param
237
- const includeInbox = parseQueryParams(req.url || "").get("include") === "inbox";
238
-
239
- // Add capacity info and polling limit check to agent response
240
- const agentResponse = {
241
- ...agentWithCapacity(agent),
242
- shouldBlockPolling: shouldBlockPolling(myAgentId),
243
- };
244
-
245
- if (includeInbox) {
246
- const inbox = getInboxSummary(myAgentId);
247
- res.writeHead(200, { "Content-Type": "application/json" });
248
- res.end(JSON.stringify({ ...agentResponse, inbox }));
249
- return;
250
- }
251
-
252
- res.writeHead(200, { "Content-Type": "application/json" });
253
- res.end(JSON.stringify(agentResponse));
254
- return;
255
- }
256
-
257
- // GET /cancelled-tasks - Check for recently cancelled tasks (for hook cancellation detection)
258
- // Supports optional ?taskId= query param for checking specific task cancellation
259
- if (
260
- req.method === "GET" &&
261
- (req.url === "/cancelled-tasks" || req.url?.startsWith("/cancelled-tasks?"))
262
- ) {
263
- if (!myAgentId) {
264
- res.writeHead(400, { "Content-Type": "application/json" });
265
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
266
- return;
267
- }
268
-
269
- const agent = getAgentById(myAgentId);
270
- if (!agent) {
271
- res.writeHead(404, { "Content-Type": "application/json" });
272
- res.end(JSON.stringify({ error: "Agent not found" }));
273
- return;
274
- }
275
-
276
- // Check for specific taskId query param
277
- const queryParams = parseQueryParams(req.url || "");
278
- const taskId = queryParams.get("taskId");
279
-
280
- if (taskId) {
281
- // Check if specific task is cancelled
282
- const task = getTaskById(taskId);
283
- if (task && task.status === "cancelled") {
284
- res.writeHead(200, { "Content-Type": "application/json" });
285
- res.end(
286
- JSON.stringify({
287
- cancelled: [
288
- {
289
- id: task.id,
290
- task: task.task,
291
- failureReason: task.failureReason,
292
- },
293
- ],
294
- }),
295
- );
296
- return;
297
- }
298
- // Task not found or not cancelled
299
- res.writeHead(200, { "Content-Type": "application/json" });
300
- res.end(JSON.stringify({ cancelled: [] }));
301
- return;
302
- }
303
-
304
- // No taskId - return all recently cancelled tasks for this agent
305
- const cancelledTasks = getRecentlyCancelledTasksForAgent(myAgentId);
306
- res.writeHead(200, { "Content-Type": "application/json" });
307
- res.end(JSON.stringify({ cancelled: cancelledTasks }));
308
- return;
309
- }
310
-
311
- if (req.method === "POST" && req.url === "/ping") {
312
- if (!myAgentId) {
313
- res.writeHead(400, { "Content-Type": "application/json" });
314
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
315
- return;
316
- }
317
-
318
- const tx = getDb().transaction(() => {
319
- const agent = getAgentById(myAgentId);
320
-
321
- if (!agent) {
322
- res.writeHead(404, { "Content-Type": "application/json" });
323
- res.end(JSON.stringify({ error: "Agent not found" }));
324
- return false;
325
- }
326
-
327
- let status: AgentStatus = "idle";
328
-
329
- if (agent.status === "busy") {
330
- status = "busy";
331
- }
332
-
333
- updateAgentStatus(agent.id, status);
334
-
335
- return true;
336
- });
337
-
338
- if (!tx()) {
339
- return;
340
- }
341
-
342
- res.writeHead(204);
343
- res.end();
344
- return;
345
- }
346
-
347
- if (req.method === "POST" && req.url === "/close") {
348
- if (!myAgentId) {
349
- res.writeHead(400, { "Content-Type": "application/json" });
350
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
351
- return;
352
- }
353
-
354
- const tx = getDb().transaction(() => {
355
- const agent = getAgentById(myAgentId);
356
-
357
- if (!agent) {
358
- res.writeHead(404, { "Content-Type": "application/json" });
359
- res.end(JSON.stringify({ error: "Agent not found" }));
360
- return false;
361
- }
362
-
363
- updateAgentStatus(agent.id, "offline");
364
-
365
- return true;
366
- });
367
-
368
- if (!tx()) {
369
- return;
370
- }
371
-
372
- res.writeHead(204);
373
- res.end();
374
- return;
375
- }
376
-
377
- // ============================================================================
378
- // Runner-Level Polling Endpoints
379
- // ============================================================================
380
-
381
- const pathSegments = getPathSegments(req.url || "");
382
-
383
- // POST /api/agents - Register a new agent (or return existing if already registered)
384
- if (
385
- req.method === "POST" &&
386
- pathSegments[0] === "api" &&
387
- pathSegments[1] === "agents" &&
388
- !pathSegments[2]
389
- ) {
390
- // Parse request body
391
- const chunks: Buffer[] = [];
392
- for await (const chunk of req) {
393
- chunks.push(chunk);
394
- }
395
- const body = JSON.parse(Buffer.concat(chunks).toString());
396
-
397
- // Validate required fields
398
- if (!body.name || typeof body.name !== "string") {
399
- res.writeHead(400, { "Content-Type": "application/json" });
400
- res.end(JSON.stringify({ error: "Missing or invalid 'name' field" }));
401
- return;
402
- }
403
-
404
- // Use X-Agent-ID header if provided, otherwise generate new UUID
405
- const agentId = myAgentId || crypto.randomUUID();
406
-
407
- // Use transaction to ensure atomicity of check-and-create/update
408
- const result = getDb().transaction(() => {
409
- // Check if agent already exists
410
- const existingAgent = getAgentById(agentId);
411
- if (existingAgent) {
412
- // Update status to idle if offline
413
- if (existingAgent.status === "offline") {
414
- updateAgentStatus(existingAgent.id, "idle");
415
- }
416
- // Update maxTasks if provided (allows runner to sync its MAX_CONCURRENT_TASKS)
417
- if (body.maxTasks !== undefined && body.maxTasks !== existingAgent.maxTasks) {
418
- updateAgentMaxTasks(existingAgent.id, body.maxTasks);
419
- }
420
- // Reset empty poll count on re-registration (agent is starting fresh)
421
- resetEmptyPollCount(existingAgent.id);
422
- return { agent: getAgentById(agentId), created: false };
423
- }
424
-
425
- // Create new agent
426
- const agent = createAgent({
427
- id: agentId,
428
- name: body.name,
429
- isLead: body.isLead ?? false,
430
- status: "idle",
431
- description: body.description,
432
- role: body.role,
433
- capabilities: body.capabilities,
434
- maxTasks: body.maxTasks ?? 1,
435
- });
436
-
437
- return { agent, created: true };
438
- })();
439
-
440
- res.writeHead(result.created ? 201 : 200, { "Content-Type": "application/json" });
441
- res.end(JSON.stringify(result.agent));
442
- return;
443
- }
444
-
445
- // GET /api/poll - Poll for triggers (tasks, mentions, etc.)
446
- if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "poll") {
447
- if (!myAgentId) {
448
- res.writeHead(400, { "Content-Type": "application/json" });
449
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
450
- return;
451
- }
452
-
453
- // Use transaction for consistent reads across all trigger checks
454
- let result:
455
- | { error: string; status: number }
456
- | { trigger: { type: string; [key: string]: unknown } | null };
457
- try {
458
- result = getDb().transaction(() => {
459
- const agent = getAgentById(myAgentId);
460
- if (!agent) {
461
- return { error: "Agent not found", status: 404 };
462
- }
463
-
464
- // Check for offered tasks first (highest priority for both workers and leads)
465
- // Atomically claim the task for review to prevent duplicate processing
466
- const offeredTasks = getOfferedTasksForAgent(myAgentId);
467
- const firstOfferedTask = offeredTasks[0];
468
- if (firstOfferedTask) {
469
- const claimedTask = claimOfferedTask(firstOfferedTask.id, myAgentId);
470
- if (claimedTask) {
471
- return {
472
- trigger: {
473
- type: "task_offered",
474
- taskId: claimedTask.id,
475
- task: claimedTask,
476
- },
477
- };
478
- }
479
- }
480
-
481
- // Check for pending tasks (assigned directly to this agent)
482
- // Only return a task if agent has capacity (server-side enforcement)
483
- if (hasCapacity(myAgentId)) {
484
- const pendingTask = getPendingTaskForAgent(myAgentId);
485
- if (pendingTask) {
486
- // Mark task as in_progress immediately to prevent duplicate polling
487
- startTask(pendingTask.id);
488
- return {
489
- trigger: {
490
- type: "task_assigned",
491
- taskId: pendingTask.id,
492
- task: { ...pendingTask, status: "in_progress" },
493
- },
494
- };
495
- }
496
- }
497
-
498
- if (agent.isLead) {
499
- // === LEAD-SPECIFIC TRIGGERS ===
500
-
501
- // Check for unread Slack inbox messages (highest priority for lead)
502
- // Atomically claim messages to prevent duplicate processing
503
- const claimedInbox = claimInboxMessages(myAgentId, 5);
504
- if (claimedInbox.length > 0) {
505
- return {
506
- trigger: {
507
- type: "slack_inbox_message",
508
- count: claimedInbox.length,
509
- messages: claimedInbox,
510
- },
511
- };
512
- }
513
-
514
- // Check for unread mentions (internal chat) - atomically claim them
515
- const claimedChannels = claimMentions(myAgentId);
516
- if (claimedChannels.length > 0) {
517
- // Recalculate inbox summary now that we've claimed
518
- const inbox = getInboxSummary(myAgentId);
519
- return {
520
- trigger: {
521
- type: "unread_mentions",
522
- mentionsCount: inbox.mentionsCount,
523
- claimedChannels: claimedChannels.map((c) => c.channelId), // Include for tracking
524
- },
525
- };
526
- }
527
-
528
- // Check for recently finished worker tasks
529
- const finishedTasks = getRecentlyFinishedWorkerTasks();
530
- if (finishedTasks.length > 0) {
531
- // Atomically mark as notified within this transaction
532
- const taskIds = finishedTasks.map((t) => t.id);
533
- markTasksNotified(taskIds);
534
-
535
- return {
536
- trigger: {
537
- type: "tasks_finished",
538
- count: finishedTasks.length,
539
- tasks: finishedTasks,
540
- },
541
- };
542
- }
543
-
544
- // Check for epic progress updates (tasks completed/failed for active epics)
545
- // This trigger helps lead plan next steps for epics - similar to ralph loop
546
- const epicsWithUpdates = getEpicsWithProgressUpdates();
547
- if (epicsWithUpdates.length > 0) {
548
- // Atomically mark as notified within this transaction
549
- const epicIds = epicsWithUpdates.map((e) => e.epic.id);
550
- markEpicsProgressNotified(epicIds);
551
-
552
- return {
553
- trigger: {
554
- type: "epic_progress_changed",
555
- count: epicsWithUpdates.length,
556
- epics: epicsWithUpdates,
557
- },
558
- };
559
- }
560
- } else {
561
- // === WORKER-SPECIFIC TRIGGERS ===
562
-
563
- // Check for unassigned tasks in pool (workers can claim)
564
- // NOTE: This trigger is intentionally unprotected from duplicate processing.
565
- // Multiple workers should all receive this notification so they can compete
566
- // to claim tasks. The actual claiming happens via task-action tool with
567
- // atomic SQL guards in claimTask().
568
- const unassignedCount = getUnassignedTasksCount();
569
- if (unassignedCount > 0) {
570
- return {
571
- trigger: {
572
- type: "pool_tasks_available",
573
- count: unassignedCount,
574
- },
575
- };
576
- }
577
- }
578
-
579
- // No trigger found
580
- return { trigger: null };
581
- })();
582
- } catch (error) {
583
- console.error("[/api/poll] Database error:", error);
584
- res.writeHead(500, { "Content-Type": "application/json" });
585
- res.end(
586
- JSON.stringify({
587
- error: "Database error occurred while polling for triggers",
588
- details: error instanceof Error ? error.message : String(error),
589
- }),
590
- );
591
- return;
592
- }
593
-
594
- // Handle error case
595
- if ("error" in result) {
596
- res.writeHead(result.status ?? 500, { "Content-Type": "application/json" });
597
- res.end(JSON.stringify({ error: result.error }));
598
- return;
599
- }
600
-
601
- res.writeHead(200, { "Content-Type": "application/json" });
602
- res.end(JSON.stringify(result));
603
- return;
604
- }
605
-
606
- // POST /api/session-logs - Store session logs (batch)
607
- if (req.method === "POST" && pathSegments[0] === "api" && pathSegments[1] === "session-logs") {
608
- // Parse request body
609
- const chunks: Buffer[] = [];
610
- for await (const chunk of req) {
611
- chunks.push(chunk);
612
- }
613
- const body = JSON.parse(Buffer.concat(chunks).toString());
614
-
615
- // Validate required fields
616
- if (!body.sessionId || typeof body.sessionId !== "string") {
617
- res.writeHead(400, { "Content-Type": "application/json" });
618
- res.end(JSON.stringify({ error: "Missing or invalid 'sessionId' field" }));
619
- return;
620
- }
621
-
622
- if (typeof body.iteration !== "number" || body.iteration < 1) {
623
- res.writeHead(400, { "Content-Type": "application/json" });
624
- res.end(JSON.stringify({ error: "Missing or invalid 'iteration' field" }));
625
- return;
626
- }
627
-
628
- if (!Array.isArray(body.lines) || body.lines.length === 0) {
629
- res.writeHead(400, { "Content-Type": "application/json" });
630
- res.end(JSON.stringify({ error: "Missing or invalid 'lines' array" }));
631
- return;
632
- }
633
-
634
- try {
635
- createSessionLogs({
636
- taskId: body.taskId || undefined,
637
- sessionId: body.sessionId,
638
- iteration: body.iteration,
639
- cli: body.cli || "claude",
640
- lines: body.lines,
641
- });
642
-
643
- res.writeHead(201, { "Content-Type": "application/json" });
644
- res.end(JSON.stringify({ success: true, count: body.lines.length }));
645
- } catch (error) {
646
- console.error("[HTTP] Failed to create session logs:", error);
647
- res.writeHead(500, { "Content-Type": "application/json" });
648
- res.end(JSON.stringify({ error: "Failed to store session logs" }));
649
- }
650
- return;
651
- }
652
-
653
- // GET /api/tasks/:id/session-logs - Get session logs for a task
654
- if (
655
- req.method === "GET" &&
656
- pathSegments[0] === "api" &&
657
- pathSegments[1] === "tasks" &&
658
- pathSegments[2] &&
659
- pathSegments[3] === "session-logs"
660
- ) {
661
- const taskId = pathSegments[2];
662
- const task = getTaskById(taskId);
663
-
664
- if (!task) {
665
- res.writeHead(404, { "Content-Type": "application/json" });
666
- res.end(JSON.stringify({ error: "Task not found" }));
667
- return;
668
- }
669
-
670
- const logs = getSessionLogsByTaskId(taskId);
671
- res.writeHead(200, { "Content-Type": "application/json" });
672
- res.end(JSON.stringify({ logs }));
673
- return;
674
- }
675
-
676
- // POST /api/session-costs - Store session cost record
677
- if (req.method === "POST" && pathSegments[0] === "api" && pathSegments[1] === "session-costs") {
678
- // Parse request body
679
- const chunks: Buffer[] = [];
680
- for await (const chunk of req) {
681
- chunks.push(chunk);
682
- }
683
- const body = JSON.parse(Buffer.concat(chunks).toString());
684
-
685
- // Validate required fields
686
- if (!body.sessionId || typeof body.sessionId !== "string") {
687
- res.writeHead(400, { "Content-Type": "application/json" });
688
- res.end(JSON.stringify({ error: "Missing or invalid 'sessionId' field" }));
689
- return;
690
- }
691
-
692
- if (!body.agentId || typeof body.agentId !== "string") {
693
- res.writeHead(400, { "Content-Type": "application/json" });
694
- res.end(JSON.stringify({ error: "Missing or invalid 'agentId' field" }));
695
- return;
696
- }
697
-
698
- if (typeof body.totalCostUsd !== "number") {
699
- res.writeHead(400, { "Content-Type": "application/json" });
700
- res.end(JSON.stringify({ error: "Missing or invalid 'totalCostUsd' field" }));
701
- return;
702
- }
703
-
704
- try {
705
- const cost = createSessionCost({
706
- sessionId: body.sessionId,
707
- taskId: body.taskId || undefined,
708
- agentId: body.agentId,
709
- totalCostUsd: body.totalCostUsd,
710
- inputTokens: body.inputTokens ?? 0,
711
- outputTokens: body.outputTokens ?? 0,
712
- cacheReadTokens: body.cacheReadTokens ?? 0,
713
- cacheWriteTokens: body.cacheWriteTokens ?? 0,
714
- durationMs: body.durationMs ?? 0,
715
- numTurns: body.numTurns ?? 1,
716
- model: body.model || "opus",
717
- isError: body.isError ?? false,
718
- });
719
-
720
- res.writeHead(201, { "Content-Type": "application/json" });
721
- res.end(JSON.stringify({ success: true, cost }));
722
- } catch (error) {
723
- console.error("[HTTP] Failed to create session cost:", error);
724
- res.writeHead(500, { "Content-Type": "application/json" });
725
- res.end(JSON.stringify({ error: "Failed to store session cost" }));
726
- }
727
- return;
728
- }
729
-
730
- // GET /api/session-costs - Query session costs with filters
731
- if (
732
- req.method === "GET" &&
733
- pathSegments[0] === "api" &&
734
- pathSegments[1] === "session-costs" &&
735
- !pathSegments[2]
736
- ) {
737
- const costsQueryParams = parseQueryParams(req.url || "");
738
- const agentId = costsQueryParams.get("agentId");
739
- const taskId = costsQueryParams.get("taskId");
740
- const limitParam = costsQueryParams.get("limit");
741
- const limit = limitParam ? parseInt(limitParam, 10) : 100;
742
-
743
- let costs: SessionCost[];
744
- if (taskId) {
745
- costs = getSessionCostsByTaskId(taskId);
746
- } else if (agentId) {
747
- costs = getSessionCostsByAgentId(agentId, limit);
748
- } else {
749
- costs = getAllSessionCosts(limit);
750
- }
751
-
752
- res.writeHead(200, { "Content-Type": "application/json" });
753
- res.end(JSON.stringify({ costs }));
754
- return;
755
- }
756
-
757
- // GET /ecosystem - Generate PM2 ecosystem config for agent's services
758
- if (req.method === "GET" && req.url === "/ecosystem") {
759
- if (!myAgentId) {
760
- res.writeHead(400, { "Content-Type": "application/json" });
761
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
762
- return;
763
- }
764
-
765
- const services = getServicesByAgentId(myAgentId);
766
-
767
- // Generate PM2 ecosystem format
768
- const ecosystem = {
769
- apps: services
770
- .filter((s) => s.script) // Only include services with script path
771
- .map((s) => {
772
- const app: Record<string, unknown> = {
773
- name: s.name,
774
- script: s.script,
775
- };
776
-
777
- if (s.cwd) app.cwd = s.cwd;
778
- if (s.interpreter) app.interpreter = s.interpreter;
779
- if (s.args && s.args.length > 0) app.args = s.args;
780
- if (s.env && Object.keys(s.env).length > 0) app.env = s.env;
781
- if (s.port)
782
- app.env = { ...((app.env as Record<string, string>) || {}), PORT: String(s.port) };
783
-
784
- return app;
785
- }),
786
- };
787
-
788
- res.writeHead(200, { "Content-Type": "application/json" });
789
- res.end(JSON.stringify(ecosystem));
790
- return;
791
- }
792
-
793
- // ============================================================================
794
- // GitHub Webhook Endpoint
795
- // ============================================================================
796
-
797
- // POST /api/github/webhook - Handle GitHub webhook events
798
- if (
799
- req.method === "POST" &&
800
- pathSegments[0] === "api" &&
801
- pathSegments[1] === "github" &&
802
- pathSegments[2] === "webhook"
803
- ) {
804
- // Check if GitHub integration is enabled
805
- if (!isGitHubEnabled()) {
806
- res.writeHead(503, { "Content-Type": "application/json" });
807
- res.end(JSON.stringify({ error: "GitHub integration not configured" }));
808
- return;
809
- }
810
-
811
- // Get event type and signature
812
- const eventType = req.headers["x-github-event"] as string | undefined;
813
- const signature = req.headers["x-hub-signature-256"] as string | undefined;
814
-
815
- // Parse request body
816
- const chunks: Buffer[] = [];
817
- for await (const chunk of req) {
818
- chunks.push(chunk);
819
- }
820
- const rawBody = Buffer.concat(chunks).toString();
821
-
822
- // Verify webhook signature
823
- const isValid = await verifyWebhookSignature(rawBody, signature ?? null);
824
- if (!isValid) {
825
- console.log("[GitHub] Invalid webhook signature");
826
- res.writeHead(401, { "Content-Type": "application/json" });
827
- res.end(JSON.stringify({ error: "Invalid signature" }));
828
- return;
829
- }
830
-
831
- // Handle ping event (webhook setup verification)
832
- if (eventType === "ping") {
833
- console.log("[GitHub] Received ping event - webhook configured successfully");
834
- res.writeHead(200, { "Content-Type": "application/json" });
835
- res.end(JSON.stringify({ message: "pong" }));
836
- return;
837
- }
838
-
839
- // Parse JSON body
840
- let body: unknown;
841
- try {
842
- body = JSON.parse(rawBody);
843
- } catch {
844
- res.writeHead(400, { "Content-Type": "application/json" });
845
- res.end(JSON.stringify({ error: "Invalid JSON body" }));
846
- return;
847
- }
848
-
849
- console.log(`[GitHub] Received ${eventType} event`);
850
-
851
- // Route to appropriate handler
852
- let result: { created: boolean; taskId?: string } = { created: false };
853
-
854
- try {
855
- switch (eventType) {
856
- case "pull_request":
857
- result = await handlePullRequest(body as PullRequestEvent);
858
- break;
859
- case "issues":
860
- result = await handleIssue(body as IssueEvent);
861
- break;
862
- case "issue_comment":
863
- result = await handleComment(body as CommentEvent, "issue_comment");
864
- break;
865
- case "pull_request_review_comment":
866
- result = await handleComment(body as CommentEvent, "pull_request_review_comment");
867
- break;
868
- case "pull_request_review":
869
- result = await handlePullRequestReview(body as PullRequestReviewEvent);
870
- break;
871
- case "check_run":
872
- result = await handleCheckRun(body as CheckRunEvent);
873
- break;
874
- case "check_suite":
875
- result = await handleCheckSuite(body as CheckSuiteEvent);
876
- break;
877
- case "workflow_run":
878
- result = await handleWorkflowRun(body as WorkflowRunEvent);
879
- break;
880
- default:
881
- console.log(`[GitHub] Ignoring unsupported event type: ${eventType}`);
882
- }
883
-
884
- res.writeHead(200, { "Content-Type": "application/json" });
885
- res.end(JSON.stringify(result));
886
- } catch (err) {
887
- const errorMessage = err instanceof Error ? err.message : String(err);
888
- console.error(`[GitHub] ❌ Error handling ${eventType} event: ${errorMessage}`);
889
- if (err instanceof Error && err.stack) {
890
- console.error(err.stack);
891
- }
892
- res.writeHead(500, { "Content-Type": "application/json" });
893
- res.end(JSON.stringify({ error: "Internal server error", message: errorMessage }));
894
- }
895
- return;
896
- }
897
-
898
- // ============================================================================
899
- // REST API Endpoints (for frontend dashboard)
900
- // ============================================================================
901
-
902
- const queryParams = parseQueryParams(req.url || "");
903
-
904
- // GET /api/agents - List all agents (optionally with tasks)
905
- if (
906
- req.method === "GET" &&
907
- pathSegments[0] === "api" &&
908
- pathSegments[1] === "agents" &&
909
- !pathSegments[2]
910
- ) {
911
- const includeTasks = queryParams.get("include") === "tasks";
912
- const agents = includeTasks ? getAllAgentsWithTasks() : getAllAgents();
913
- const agentsWithCapacity = agents.map(agentWithCapacity);
914
- res.writeHead(200, { "Content-Type": "application/json" });
915
- res.end(JSON.stringify({ agents: agentsWithCapacity }));
916
- return;
917
- }
918
-
919
- // PUT /api/agents/:id/name - Update agent name (check before GET to avoid conflict)
920
- if (
921
- req.method === "PUT" &&
922
- pathSegments[0] === "api" &&
923
- pathSegments[1] === "agents" &&
924
- pathSegments[2] &&
925
- pathSegments[3] === "name"
926
- ) {
927
- const agentId = pathSegments[2];
928
-
929
- // Parse request body
930
- const chunks: Buffer[] = [];
931
- for await (const chunk of req) {
932
- chunks.push(chunk as Buffer);
933
- }
934
- const bodyText = Buffer.concat(chunks).toString();
935
-
936
- let body: { name?: string };
937
- try {
938
- body = JSON.parse(bodyText);
939
- } catch {
940
- res.writeHead(400, { "Content-Type": "application/json" });
941
- res.end(JSON.stringify({ error: "Invalid JSON" }));
942
- return;
943
- }
944
-
945
- if (!body.name || typeof body.name !== "string" || !body.name.trim()) {
946
- res.writeHead(400, { "Content-Type": "application/json" });
947
- res.end(JSON.stringify({ error: "Invalid name" }));
948
- return;
949
- }
950
-
951
- try {
952
- const agent = updateAgentName(agentId, body.name.trim());
953
- if (!agent) {
954
- res.writeHead(404, { "Content-Type": "application/json" });
955
- res.end(JSON.stringify({ error: "Agent not found" }));
956
- return;
957
- }
958
-
959
- res.writeHead(200, { "Content-Type": "application/json" });
960
- res.end(JSON.stringify(agentWithCapacity(agent)));
961
- } catch (error) {
962
- res.writeHead(409, { "Content-Type": "application/json" });
963
- res.end(JSON.stringify({ error: (error as Error).message }));
964
- }
965
- return;
966
- }
967
-
968
- // PUT /api/agents/:id/profile - Update agent profile (role, description, capabilities)
969
- if (
970
- req.method === "PUT" &&
971
- pathSegments[0] === "api" &&
972
- pathSegments[1] === "agents" &&
973
- pathSegments[2] &&
974
- pathSegments[3] === "profile"
975
- ) {
976
- const agentId = pathSegments[2];
977
-
978
- // Parse request body
979
- const chunks: Buffer[] = [];
980
- for await (const chunk of req) {
981
- chunks.push(chunk as Buffer);
982
- }
983
- const bodyText = Buffer.concat(chunks).toString();
984
-
985
- let body: { role?: string; description?: string; capabilities?: string[]; claudeMd?: string };
986
- try {
987
- body = JSON.parse(bodyText);
988
- } catch {
989
- res.writeHead(400, { "Content-Type": "application/json" });
990
- res.end(JSON.stringify({ error: "Invalid JSON" }));
991
- return;
992
- }
993
-
994
- // At least one field must be provided
995
- if (
996
- body.role === undefined &&
997
- body.description === undefined &&
998
- body.capabilities === undefined &&
999
- body.claudeMd === undefined
1000
- ) {
1001
- res.writeHead(400, { "Content-Type": "application/json" });
1002
- res.end(
1003
- JSON.stringify({
1004
- error:
1005
- "At least one field (role, description, capabilities, or claudeMd) must be provided",
1006
- }),
1007
- );
1008
- return;
1009
- }
1010
-
1011
- // Validate role length if provided
1012
- if (body.role !== undefined && body.role.length > 100) {
1013
- res.writeHead(400, { "Content-Type": "application/json" });
1014
- res.end(JSON.stringify({ error: "Role must be 100 characters or less" }));
1015
- return;
1016
- }
1017
-
1018
- // Validate capabilities if provided
1019
- if (body.capabilities !== undefined && !Array.isArray(body.capabilities)) {
1020
- res.writeHead(400, { "Content-Type": "application/json" });
1021
- res.end(JSON.stringify({ error: "Capabilities must be an array of strings" }));
1022
- return;
1023
- }
1024
-
1025
- // Validate claudeMd size if provided (max 64KB)
1026
- if (body.claudeMd !== undefined && body.claudeMd.length > 65536) {
1027
- res.writeHead(400, { "Content-Type": "application/json" });
1028
- res.end(JSON.stringify({ error: "claudeMd must be 64KB or less" }));
1029
- return;
1030
- }
1031
-
1032
- const agent = updateAgentProfile(agentId, {
1033
- role: body.role,
1034
- description: body.description,
1035
- capabilities: body.capabilities,
1036
- claudeMd: body.claudeMd,
1037
- });
1038
-
1039
- if (!agent) {
1040
- res.writeHead(404, { "Content-Type": "application/json" });
1041
- res.end(JSON.stringify({ error: "Agent not found" }));
1042
- return;
1043
- }
1044
-
1045
- res.writeHead(200, { "Content-Type": "application/json" });
1046
- res.end(JSON.stringify(agentWithCapacity(agent)));
1047
- return;
1048
- }
1049
-
1050
- // GET /api/agents/:id - Get single agent (optionally with tasks)
1051
- if (
1052
- req.method === "GET" &&
1053
- pathSegments[0] === "api" &&
1054
- pathSegments[1] === "agents" &&
1055
- pathSegments[2] &&
1056
- !pathSegments[3]
1057
- ) {
1058
- const agentId = pathSegments[2];
1059
- const includeTasks = queryParams.get("include") === "tasks";
1060
- const agent = includeTasks ? getAgentWithTasks(agentId) : getAgentById(agentId);
1061
-
1062
- if (!agent) {
1063
- res.writeHead(404, { "Content-Type": "application/json" });
1064
- res.end(JSON.stringify({ error: "Agent not found" }));
1065
- return;
1066
- }
1067
-
1068
- res.writeHead(200, { "Content-Type": "application/json" });
1069
- res.end(JSON.stringify(agentWithCapacity(agent)));
1070
- return;
1071
- }
1072
-
1073
- // GET /api/tasks - List all tasks (with optional filters: status, agentId, search)
1074
- if (
1075
- req.method === "GET" &&
1076
- pathSegments[0] === "api" &&
1077
- pathSegments[1] === "tasks" &&
1078
- !pathSegments[2]
1079
- ) {
1080
- const status = queryParams.get("status") as import("./types").AgentTaskStatus | null;
1081
- const agentId = queryParams.get("agentId");
1082
- const search = queryParams.get("search");
1083
- const filters = {
1084
- status: status || undefined,
1085
- agentId: agentId || undefined,
1086
- search: search || undefined,
1087
- };
1088
- const tasks = getAllTasks(filters);
1089
- const total = getTasksCount(filters);
1090
- res.writeHead(200, { "Content-Type": "application/json" });
1091
- res.end(JSON.stringify({ tasks, total }));
1092
- return;
1093
- }
1094
-
1095
- // POST /api/tasks - Create a new task
1096
- if (
1097
- req.method === "POST" &&
1098
- pathSegments[0] === "api" &&
1099
- pathSegments[1] === "tasks" &&
1100
- !pathSegments[2]
1101
- ) {
1102
- // Parse request body
1103
- const chunks: Buffer[] = [];
1104
- for await (const chunk of req) {
1105
- chunks.push(chunk);
1106
- }
1107
- const body = JSON.parse(Buffer.concat(chunks).toString());
1108
-
1109
- // Validate required fields
1110
- if (!body.task || typeof body.task !== "string") {
1111
- res.writeHead(400, { "Content-Type": "application/json" });
1112
- res.end(JSON.stringify({ error: "Missing or invalid 'task' field" }));
1113
- return;
1114
- }
1115
-
1116
- try {
1117
- // Create task with provided options
1118
- const task = createTaskExtended(body.task, {
1119
- agentId: body.agentId || undefined,
1120
- creatorAgentId: myAgentId || undefined,
1121
- taskType: body.taskType || undefined,
1122
- tags: body.tags || undefined,
1123
- priority: body.priority || 50,
1124
- dependsOn: body.dependsOn || undefined,
1125
- offeredTo: body.offeredTo || undefined,
1126
- source: body.source || "api",
1127
- });
1128
-
1129
- res.writeHead(201, { "Content-Type": "application/json" });
1130
- res.end(JSON.stringify(task));
1131
- } catch (error) {
1132
- console.error("[HTTP] Failed to create task:", error);
1133
- res.writeHead(500, { "Content-Type": "application/json" });
1134
- res.end(JSON.stringify({ error: "Failed to create task" }));
1135
- }
1136
- return;
1137
- }
1138
-
1139
- // GET /api/tasks/:id - Get single task with logs
1140
- if (
1141
- req.method === "GET" &&
1142
- pathSegments[0] === "api" &&
1143
- pathSegments[1] === "tasks" &&
1144
- pathSegments[2]
1145
- ) {
1146
- const taskId = pathSegments[2];
1147
- const task = getTaskById(taskId);
1148
-
1149
- if (!task) {
1150
- res.writeHead(404, { "Content-Type": "application/json" });
1151
- res.end(JSON.stringify({ error: "Task not found" }));
1152
- return;
1153
- }
1154
-
1155
- const logs = getLogsByTaskId(taskId);
1156
- res.writeHead(200, { "Content-Type": "application/json" });
1157
- res.end(JSON.stringify({ ...task, logs }));
1158
- return;
1159
- }
1160
-
1161
- // POST /api/tasks/:id/finish - Mark task as completed or failed (runner wrapper endpoint)
1162
- // This endpoint is called by the runner when a Claude process exits to ensure task status is updated
1163
- if (
1164
- req.method === "POST" &&
1165
- pathSegments[0] === "api" &&
1166
- pathSegments[1] === "tasks" &&
1167
- pathSegments[2] &&
1168
- pathSegments[3] === "finish"
1169
- ) {
1170
- if (!myAgentId) {
1171
- res.writeHead(400, { "Content-Type": "application/json" });
1172
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
1173
- return;
1174
- }
1175
-
1176
- const taskId = pathSegments[2];
1177
-
1178
- // Parse request body
1179
- const chunks: Buffer[] = [];
1180
- for await (const chunk of req) {
1181
- chunks.push(chunk);
1182
- }
1183
- const body = JSON.parse(Buffer.concat(chunks).toString());
1184
-
1185
- // Validate status field
1186
- if (!body.status || !["completed", "failed"].includes(body.status)) {
1187
- res.writeHead(400, { "Content-Type": "application/json" });
1188
- res.end(
1189
- JSON.stringify({
1190
- error: "Missing or invalid 'status' field (must be 'completed' or 'failed')",
1191
- }),
1192
- );
1193
- return;
1194
- }
1195
-
1196
- const result = getDb().transaction(() => {
1197
- const task = getTaskById(taskId);
1198
-
1199
- if (!task) {
1200
- return { error: "Task not found", status: 404 };
1201
- }
1202
-
1203
- // Only allow the assigned agent (or task creator if unassigned) to finish the task
1204
- if (task.agentId && task.agentId !== myAgentId) {
1205
- return { error: "Task is assigned to another agent", status: 403 };
1206
- }
1207
-
1208
- // Only finish tasks that are in_progress (prevent double-finishing)
1209
- if (task.status !== "in_progress") {
1210
- // Task already finished or not started - return success with current state
1211
- return { task, alreadyFinished: true };
1212
- }
1213
-
1214
- let updatedTask: typeof task;
1215
- if (body.status === "completed") {
1216
- const result = completeTask(
1217
- taskId,
1218
- body.output || "Completed by runner wrapper (no explicit output)",
1219
- );
1220
- if (!result) {
1221
- return { error: "Failed to complete task", status: 500 };
1222
- }
1223
- updatedTask = result;
1224
- } else {
1225
- const result = failTask(
1226
- taskId,
1227
- body.failureReason || "Process exited without explicit completion",
1228
- );
1229
- if (!result) {
1230
- return { error: "Failed to mark task as failed", status: 500 };
1231
- }
1232
- updatedTask = result;
1233
- }
1234
-
1235
- // Update agent status based on remaining capacity
1236
- if (task.agentId) {
1237
- updateAgentStatusFromCapacity(task.agentId);
1238
- }
1239
-
1240
- return { task: updatedTask };
1241
- })();
1242
-
1243
- if ("error" in result) {
1244
- res.writeHead(result.status ?? 500, { "Content-Type": "application/json" });
1245
- res.end(JSON.stringify({ error: result.error }));
1246
- return;
1247
- }
1248
-
1249
- res.writeHead(200, { "Content-Type": "application/json" });
1250
- res.end(
1251
- JSON.stringify({
1252
- success: true,
1253
- alreadyFinished: result.alreadyFinished ?? false,
1254
- task: result.task,
1255
- }),
1256
- );
1257
- return;
1258
- }
1259
-
1260
- // GET /api/paused-tasks - Get paused tasks for this agent
1261
- if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "paused-tasks") {
1262
- if (!myAgentId) {
1263
- res.writeHead(400, { "Content-Type": "application/json" });
1264
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
1265
- return;
1266
- }
1267
-
1268
- const pausedTasks = getPausedTasksForAgent(myAgentId);
1269
- res.writeHead(200, { "Content-Type": "application/json" });
1270
- res.end(JSON.stringify({ tasks: pausedTasks }));
1271
- return;
1272
- }
1273
-
1274
- // POST /api/tasks/:id/pause - Pause an in-progress task (for graceful shutdown)
1275
- if (
1276
- req.method === "POST" &&
1277
- pathSegments[0] === "api" &&
1278
- pathSegments[1] === "tasks" &&
1279
- pathSegments[2] &&
1280
- pathSegments[3] === "pause"
1281
- ) {
1282
- if (!myAgentId) {
1283
- res.writeHead(400, { "Content-Type": "application/json" });
1284
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
1285
- return;
1286
- }
1287
-
1288
- const taskId = pathSegments[2];
1289
- const task = getTaskById(taskId);
1290
-
1291
- if (!task) {
1292
- res.writeHead(404, { "Content-Type": "application/json" });
1293
- res.end(JSON.stringify({ error: "Task not found" }));
1294
- return;
1295
- }
1296
-
1297
- // Only allow the assigned agent to pause their own task
1298
- if (task.agentId !== myAgentId) {
1299
- res.writeHead(403, { "Content-Type": "application/json" });
1300
- res.end(JSON.stringify({ error: "Task belongs to another agent" }));
1301
- return;
1302
- }
1303
-
1304
- if (task.status !== "in_progress") {
1305
- res.writeHead(400, { "Content-Type": "application/json" });
1306
- res.end(JSON.stringify({ error: `Task status is '${task.status}', not 'in_progress'` }));
1307
- return;
1308
- }
1309
-
1310
- const pausedTask = pauseTask(taskId);
1311
- if (!pausedTask) {
1312
- res.writeHead(500, { "Content-Type": "application/json" });
1313
- res.end(JSON.stringify({ error: "Failed to pause task" }));
1314
- return;
1315
- }
1316
-
1317
- res.writeHead(200, { "Content-Type": "application/json" });
1318
- res.end(JSON.stringify({ success: true, task: pausedTask }));
1319
- return;
1320
- }
1321
-
1322
- // POST /api/tasks/:id/resume - Resume a paused task
1323
- if (
1324
- req.method === "POST" &&
1325
- pathSegments[0] === "api" &&
1326
- pathSegments[1] === "tasks" &&
1327
- pathSegments[2] &&
1328
- pathSegments[3] === "resume"
1329
- ) {
1330
- if (!myAgentId) {
1331
- res.writeHead(400, { "Content-Type": "application/json" });
1332
- res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
1333
- return;
1334
- }
1335
-
1336
- const taskId = pathSegments[2];
1337
- const task = getTaskById(taskId);
1338
-
1339
- if (!task) {
1340
- res.writeHead(404, { "Content-Type": "application/json" });
1341
- res.end(JSON.stringify({ error: "Task not found" }));
1342
- return;
1343
- }
1344
-
1345
- // Only allow the assigned agent to resume their own task
1346
- if (task.agentId !== myAgentId) {
1347
- res.writeHead(403, { "Content-Type": "application/json" });
1348
- res.end(JSON.stringify({ error: "Task belongs to another agent" }));
1349
- return;
1350
- }
1351
-
1352
- if (task.status !== "paused") {
1353
- res.writeHead(400, { "Content-Type": "application/json" });
1354
- res.end(JSON.stringify({ error: `Task status is '${task.status}', not 'paused'` }));
1355
- return;
1356
- }
1357
-
1358
- const resumedTask = resumeTask(taskId);
1359
- if (!resumedTask) {
1360
- res.writeHead(500, { "Content-Type": "application/json" });
1361
- res.end(JSON.stringify({ error: "Failed to resume task" }));
1362
- return;
1363
- }
1364
-
1365
- res.writeHead(200, { "Content-Type": "application/json" });
1366
- res.end(JSON.stringify({ success: true, task: resumedTask }));
1367
- return;
1368
- }
1369
-
1370
- // GET /api/logs - List recent logs (optionally filtered by agentId)
1371
- if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "logs") {
1372
- const limitParam = queryParams.get("limit");
1373
- const limit = limitParam ? parseInt(limitParam, 10) : 100;
1374
- const agentId = queryParams.get("agentId");
1375
- let logs: AgentLog[] = [];
1376
- if (agentId) {
1377
- logs = getLogsByAgentId(agentId).slice(0, limit);
1378
- } else {
1379
- logs = getAllLogs(limit);
1380
- }
1381
- res.writeHead(200, { "Content-Type": "application/json" });
1382
- res.end(JSON.stringify({ logs }));
1383
- return;
1384
- }
1385
-
1386
- // GET /api/stats - Dashboard summary stats
1387
- if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "stats") {
1388
- const agents = getAllAgents();
1389
- const taskStats = getTaskStats();
1390
-
1391
- const stats = {
1392
- agents: {
1393
- total: agents.length,
1394
- idle: agents.filter((a) => a.status === "idle").length,
1395
- busy: agents.filter((a) => a.status === "busy").length,
1396
- offline: agents.filter((a) => a.status === "offline").length,
1397
- },
1398
- tasks: {
1399
- total: taskStats.total,
1400
- unassigned: taskStats.unassigned,
1401
- offered: taskStats.offered,
1402
- reviewing: taskStats.reviewing,
1403
- pending: taskStats.pending,
1404
- in_progress: taskStats.in_progress,
1405
- paused: taskStats.paused,
1406
- completed: taskStats.completed,
1407
- failed: taskStats.failed,
1408
- },
1409
- };
1410
-
1411
- res.writeHead(200, { "Content-Type": "application/json" });
1412
- res.end(JSON.stringify(stats));
1413
- return;
1414
- }
1415
-
1416
- // GET /api/services - List all services (with optional filters: status, agentId, name)
1417
- if (
1418
- req.method === "GET" &&
1419
- pathSegments[0] === "api" &&
1420
- pathSegments[1] === "services" &&
1421
- !pathSegments[2]
1422
- ) {
1423
- const status = queryParams.get("status") as import("./types").ServiceStatus | null;
1424
- const agentId = queryParams.get("agentId");
1425
- const name = queryParams.get("name");
1426
- const services = getAllServices({
1427
- status: status || undefined,
1428
- agentId: agentId || undefined,
1429
- name: name || undefined,
1430
- });
1431
- res.writeHead(200, { "Content-Type": "application/json" });
1432
- res.end(JSON.stringify({ services }));
1433
- return;
1434
- }
1435
-
1436
- // GET /api/scheduled-tasks - List all scheduled tasks (with optional filters: enabled, name)
1437
- if (
1438
- req.method === "GET" &&
1439
- pathSegments[0] === "api" &&
1440
- pathSegments[1] === "scheduled-tasks" &&
1441
- !pathSegments[2]
1442
- ) {
1443
- const enabledParam = queryParams.get("enabled");
1444
- const name = queryParams.get("name");
1445
- const scheduledTasks = getScheduledTasks({
1446
- enabled: enabledParam !== null ? enabledParam === "true" : undefined,
1447
- name: name || undefined,
1448
- });
1449
- res.writeHead(200, { "Content-Type": "application/json" });
1450
- res.end(JSON.stringify({ scheduledTasks }));
1451
- return;
1452
- }
1453
-
1454
- // ============================================================================
1455
- // Epic Endpoints
1456
- // ============================================================================
1457
-
1458
- // GET /api/epics - List all epics
1459
- if (
1460
- req.method === "GET" &&
1461
- pathSegments[0] === "api" &&
1462
- pathSegments[1] === "epics" &&
1463
- !pathSegments[2]
1464
- ) {
1465
- const status = queryParams.get("status") as EpicStatus | null;
1466
- const search = queryParams.get("search");
1467
- const leadAgentId = queryParams.get("leadAgentId");
1468
- const epics = getEpics({
1469
- status: status || undefined,
1470
- search: search || undefined,
1471
- leadAgentId: leadAgentId || undefined,
1472
- });
1473
- res.writeHead(200, { "Content-Type": "application/json" });
1474
- res.end(JSON.stringify({ epics, total: epics.length }));
1475
- return;
1476
- }
1477
-
1478
- // POST /api/epics - Create a new epic
1479
- if (
1480
- req.method === "POST" &&
1481
- pathSegments[0] === "api" &&
1482
- pathSegments[1] === "epics" &&
1483
- !pathSegments[2]
1484
- ) {
1485
- const chunks: Buffer[] = [];
1486
- for await (const chunk of req) {
1487
- chunks.push(chunk);
1488
- }
1489
- const body = JSON.parse(Buffer.concat(chunks).toString());
1490
-
1491
- if (!body.name || !body.goal) {
1492
- res.writeHead(400, { "Content-Type": "application/json" });
1493
- res.end(JSON.stringify({ error: "Missing required fields: name, goal" }));
1494
- return;
1495
- }
1496
-
1497
- try {
1498
- const epic = createEpic({
1499
- ...body,
1500
- createdByAgentId: myAgentId || undefined,
1501
- });
1502
- res.writeHead(201, { "Content-Type": "application/json" });
1503
- res.end(JSON.stringify(epic));
1504
- } catch (_error) {
1505
- res.writeHead(500, { "Content-Type": "application/json" });
1506
- res.end(JSON.stringify({ error: "Failed to create epic" }));
1507
- }
1508
- return;
1509
- }
1510
-
1511
- // GET /api/epics/:id - Get single epic with progress and tasks
1512
- if (
1513
- req.method === "GET" &&
1514
- pathSegments[0] === "api" &&
1515
- pathSegments[1] === "epics" &&
1516
- pathSegments[2] &&
1517
- !pathSegments[3]
1518
- ) {
1519
- const epicId = pathSegments[2];
1520
- const epic = getEpicWithProgress(epicId);
1521
-
1522
- if (!epic) {
1523
- res.writeHead(404, { "Content-Type": "application/json" });
1524
- res.end(JSON.stringify({ error: "Epic not found" }));
1525
- return;
1526
- }
1527
-
1528
- const tasks = getTasksByEpicId(epicId);
1529
- res.writeHead(200, { "Content-Type": "application/json" });
1530
- res.end(JSON.stringify({ ...epic, tasks }));
1531
- return;
1532
- }
1533
-
1534
- // PUT /api/epics/:id - Update an epic
1535
- if (
1536
- req.method === "PUT" &&
1537
- pathSegments[0] === "api" &&
1538
- pathSegments[1] === "epics" &&
1539
- pathSegments[2] &&
1540
- !pathSegments[3]
1541
- ) {
1542
- const epicId = pathSegments[2];
1543
- const chunks: Buffer[] = [];
1544
- for await (const chunk of req) {
1545
- chunks.push(chunk);
1546
- }
1547
- const body = JSON.parse(Buffer.concat(chunks).toString());
1548
-
1549
- const epic = updateEpic(epicId, body);
1550
- if (!epic) {
1551
- res.writeHead(404, { "Content-Type": "application/json" });
1552
- res.end(JSON.stringify({ error: "Epic not found" }));
1553
- return;
1554
- }
1555
-
1556
- res.writeHead(200, { "Content-Type": "application/json" });
1557
- res.end(JSON.stringify(epic));
1558
- return;
1559
- }
1560
-
1561
- // DELETE /api/epics/:id - Delete an epic
1562
- if (
1563
- req.method === "DELETE" &&
1564
- pathSegments[0] === "api" &&
1565
- pathSegments[1] === "epics" &&
1566
- pathSegments[2] &&
1567
- !pathSegments[3]
1568
- ) {
1569
- const epicId = pathSegments[2];
1570
- const deleted = deleteEpic(epicId);
1571
-
1572
- if (!deleted) {
1573
- res.writeHead(404, { "Content-Type": "application/json" });
1574
- res.end(JSON.stringify({ error: "Epic not found" }));
1575
- return;
1576
- }
1577
-
1578
- res.writeHead(200, { "Content-Type": "application/json" });
1579
- res.end(JSON.stringify({ success: true }));
1580
- return;
1581
- }
1582
-
1583
- // POST /api/epics/:id/tasks - Add task to epic (create new or assign existing)
1584
- if (
1585
- req.method === "POST" &&
1586
- pathSegments[0] === "api" &&
1587
- pathSegments[1] === "epics" &&
1588
- pathSegments[2] &&
1589
- pathSegments[3] === "tasks"
1590
- ) {
1591
- const epicId = pathSegments[2];
1592
- const epic = getEpicById(epicId);
1593
-
1594
- if (!epic) {
1595
- res.writeHead(404, { "Content-Type": "application/json" });
1596
- res.end(JSON.stringify({ error: "Epic not found" }));
1597
- return;
1598
- }
1599
-
1600
- const chunks: Buffer[] = [];
1601
- for await (const chunk of req) {
1602
- chunks.push(chunk);
1603
- }
1604
- const body = JSON.parse(Buffer.concat(chunks).toString());
1605
-
1606
- // If taskId provided, assign existing task
1607
- if (body.taskId) {
1608
- const task = assignTaskToEpic(body.taskId, epicId);
1609
- if (!task) {
1610
- res.writeHead(404, { "Content-Type": "application/json" });
1611
- res.end(JSON.stringify({ error: "Task not found" }));
1612
- return;
1613
- }
1614
- res.writeHead(200, { "Content-Type": "application/json" });
1615
- res.end(JSON.stringify(task));
1616
- return;
1617
- }
1618
-
1619
- // Otherwise create new task in this epic
1620
- if (!body.task) {
1621
- res.writeHead(400, { "Content-Type": "application/json" });
1622
- res.end(JSON.stringify({ error: "Missing task description or taskId" }));
1623
- return;
1624
- }
1625
-
1626
- try {
1627
- const task = createTaskExtended(body.task, {
1628
- ...body,
1629
- epicId,
1630
- creatorAgentId: myAgentId || undefined,
1631
- tags: [...(body.tags || []), `epic:${epic.name}`],
1632
- source: "api",
1633
- });
1634
- res.writeHead(201, { "Content-Type": "application/json" });
1635
- res.end(JSON.stringify(task));
1636
- } catch (_error) {
1637
- res.writeHead(500, { "Content-Type": "application/json" });
1638
- res.end(JSON.stringify({ error: "Failed to create task" }));
1639
- }
1640
- return;
1641
- }
1642
-
1643
- // GET /api/channels - List all channels
1644
- if (
1645
- req.method === "GET" &&
1646
- pathSegments[0] === "api" &&
1647
- pathSegments[1] === "channels" &&
1648
- !pathSegments[2]
1649
- ) {
1650
- const channels = getAllChannels();
1651
- res.writeHead(200, { "Content-Type": "application/json" });
1652
- res.end(JSON.stringify({ channels }));
1653
- return;
1654
- }
1655
-
1656
- // GET /api/channels/:id/messages - Get messages in a channel
1657
- if (
1658
- req.method === "GET" &&
1659
- pathSegments[0] === "api" &&
1660
- pathSegments[1] === "channels" &&
1661
- pathSegments[2] &&
1662
- pathSegments[3] === "messages" &&
1663
- !pathSegments[4]
1664
- ) {
1665
- const channelId = pathSegments[2];
1666
- const channel = getChannelById(channelId);
1667
-
1668
- if (!channel) {
1669
- res.writeHead(404, { "Content-Type": "application/json" });
1670
- res.end(JSON.stringify({ error: "Channel not found" }));
1671
- return;
1672
- }
1673
-
1674
- const limitParam = queryParams.get("limit");
1675
- const limit = limitParam ? parseInt(limitParam, 10) : 50;
1676
- const since = queryParams.get("since") || undefined;
1677
- const before = queryParams.get("before") || undefined;
1678
-
1679
- const messages = getChannelMessages(channelId, { limit, since, before });
1680
- res.writeHead(200, { "Content-Type": "application/json" });
1681
- res.end(JSON.stringify({ messages }));
1682
- return;
1683
- }
1684
-
1685
- // GET /api/channels/:id/messages/:messageId/thread - Get thread messages
1686
- if (
1687
- req.method === "GET" &&
1688
- pathSegments[0] === "api" &&
1689
- pathSegments[1] === "channels" &&
1690
- pathSegments[2] &&
1691
- pathSegments[3] === "messages" &&
1692
- pathSegments[4] &&
1693
- pathSegments[5] === "thread"
1694
- ) {
1695
- const channelId = pathSegments[2];
1696
- const parentMessageId = pathSegments[4];
1697
-
1698
- const channel = getChannelById(channelId);
1699
- if (!channel) {
1700
- res.writeHead(404, { "Content-Type": "application/json" });
1701
- res.end(JSON.stringify({ error: "Channel not found" }));
1702
- return;
1703
- }
1704
-
1705
- // Get all messages that reply to this message
1706
- const allMessages = getChannelMessages(channelId, { limit: 1000 });
1707
- const threadMessages = allMessages.filter((m) => m.replyToId === parentMessageId);
1708
-
1709
- res.writeHead(200, { "Content-Type": "application/json" });
1710
- res.end(JSON.stringify({ messages: threadMessages }));
1711
- return;
1712
- }
1713
-
1714
- // POST /api/channels/:id/messages - Post a message
1715
- if (
1716
- req.method === "POST" &&
1717
- pathSegments[0] === "api" &&
1718
- pathSegments[1] === "channels" &&
1719
- pathSegments[2] &&
1720
- pathSegments[3] === "messages"
1721
- ) {
1722
- const channelId = pathSegments[2];
1723
- const channel = getChannelById(channelId);
1724
-
1725
- if (!channel) {
1726
- res.writeHead(404, { "Content-Type": "application/json" });
1727
- res.end(JSON.stringify({ error: "Channel not found" }));
1728
- return;
1729
- }
1730
-
1731
- // Parse request body
1732
- const chunks: Buffer[] = [];
1733
- for await (const chunk of req) {
1734
- chunks.push(chunk);
1735
- }
1736
- const body = JSON.parse(Buffer.concat(chunks).toString());
1737
-
1738
- if (!body.content || typeof body.content !== "string") {
1739
- res.writeHead(400, { "Content-Type": "application/json" });
1740
- res.end(JSON.stringify({ error: "Missing or invalid content" }));
1741
- return;
1742
- }
1743
-
1744
- // agentId is optional (null for human users)
1745
- const agentId = body.agentId || null;
1746
-
1747
- // If agentId provided, verify agent exists
1748
- if (agentId) {
1749
- const agent = getAgentById(agentId);
1750
- if (!agent) {
1751
- res.writeHead(400, { "Content-Type": "application/json" });
1752
- res.end(JSON.stringify({ error: "Invalid agentId" }));
1753
- return;
1754
- }
1755
- }
1756
-
1757
- const message = postMessage(channelId, agentId, body.content, {
1758
- replyToId: body.replyToId,
1759
- mentions: body.mentions,
1760
- });
1761
-
1762
- res.writeHead(201, { "Content-Type": "application/json" });
1763
- res.end(JSON.stringify(message));
1764
- return;
1765
- }
1766
-
1767
- if (req.url !== "/mcp") {
1768
- res.writeHead(404);
1769
- res.end("Not Found");
1770
- return;
1771
- }
1772
-
1773
- if (req.method === "POST") {
1774
- const chunks: Buffer[] = [];
1775
- for await (const chunk of req) {
1776
- chunks.push(chunk);
1777
- }
1778
- const body = JSON.parse(Buffer.concat(chunks).toString());
1779
-
1780
- let transport: StreamableHTTPServerTransport;
1781
-
1782
- if (sessionId && transports[sessionId]) {
1783
- transport = transports[sessionId];
1784
- } else if (!sessionId && isInitializeRequest(body)) {
1785
- transport = new StreamableHTTPServerTransport({
1786
- sessionIdGenerator: () => randomUUID(),
1787
- onsessioninitialized: (id) => {
1788
- transports[id] = transport;
1789
- },
1790
- onsessionclosed: (id) => {
1791
- delete transports[id];
1792
- },
1793
- });
1794
-
1795
- transport.onclose = () => {
1796
- if (transport.sessionId) {
1797
- delete transports[transport.sessionId];
1798
- }
1799
- };
1800
-
1801
- const server = createServer();
1802
- await server.connect(transport);
1803
- } else {
1804
- res.writeHead(400, { "Content-Type": "application/json" });
1805
- res.end(
1806
- JSON.stringify({
1807
- jsonrpc: "2.0",
1808
- error: { code: -32000, message: "Invalid session" },
1809
- id: null,
1810
- }),
1811
- );
1812
- return;
1813
- }
1814
-
1815
- await transport.handleRequest(req, res, body);
1816
- return;
1817
- }
1818
-
1819
- if (req.method === "GET" || req.method === "DELETE") {
1820
- if (sessionId && transports[sessionId]) {
1821
- await transports[sessionId].handleRequest(req, res);
1822
- return;
1823
- }
1824
- res.writeHead(400);
1825
- res.end("Invalid session");
1826
- return;
1827
- }
1828
-
1829
- res.writeHead(405);
1830
- res.end("Method not allowed");
1831
- });
1832
-
1833
- // Store references in globalThis for hot reload persistence
1834
- globalState.__httpServer = httpServer;
1835
- globalState.__transports = transports;
1836
-
1837
- async function shutdown() {
1838
- console.log("Shutting down HTTP server...");
1839
-
1840
- // Stop scheduler (if enabled)
1841
- if (hasCapability("scheduling")) {
1842
- const { stopScheduler } = await import("./scheduler");
1843
- stopScheduler();
1844
- }
1845
-
1846
- // Stop Slack bot
1847
- await stopSlackApp();
1848
-
1849
- // Close all active transports (SSE connections, etc.)
1850
- for (const [id, transport] of Object.entries(transports)) {
1851
- console.log(`[HTTP] Closing transport ${id}`);
1852
- transport.close();
1853
- delete transports[id];
1854
- }
1855
-
1856
- // Close all active connections forcefully
1857
- httpServer.closeAllConnections();
1858
- httpServer.close(() => {
1859
- closeDb();
1860
- console.log("MCP HTTP server closed, and database connection closed");
1861
- process.exit(0);
1862
- });
1863
- }
1864
-
1865
- // Only register SIGINT handler once (avoid duplicates on hot reload)
1866
- if (!globalState.__sigintRegistered) {
1867
- globalState.__sigintRegistered = true;
1868
- process.on("SIGINT", shutdown);
1869
- }
1870
-
1871
- httpServer
1872
- .listen(port, async () => {
1873
- console.log(`MCP HTTP server running on http://localhost:${port}/mcp`);
1874
-
1875
- // Start Slack bot (if configured)
1876
- await startSlackApp();
1877
-
1878
- // Initialize GitHub webhook handler (if configured)
1879
- initGitHub();
1880
-
1881
- // Start scheduler (if enabled)
1882
- if (hasCapability("scheduling")) {
1883
- const { startScheduler } = await import("./scheduler");
1884
- const intervalMs = Number(process.env.SCHEDULER_INTERVAL_MS) || 10000;
1885
- startScheduler(intervalMs);
1886
- }
1887
- })
1888
- .on("error", (err) => {
1889
- console.error("HTTP Server Error:", err);
1890
- });
1
+ import "./http/index";