@desplega.ai/agent-swarm 1.20.0 → 1.51.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (561) hide show
  1. package/README.md +271 -169
  2. package/openapi.json +5015 -0
  3. package/package.json +40 -7
  4. package/plugin/commands/close-issue.md +7 -3
  5. package/plugin/commands/create-pr.md +18 -12
  6. package/plugin/commands/implement-issue.md +7 -3
  7. package/plugin/commands/respond-github.md +8 -4
  8. package/plugin/commands/review-pr.md +44 -10
  9. package/plugin/commands/start-leader.md +1 -3
  10. package/plugin/commands/start-worker.md +1 -3
  11. package/plugin/commands/work-on-task.md +22 -3
  12. package/plugin/pi-skills/close-issue/SKILL.md +90 -0
  13. package/plugin/pi-skills/create-pr/SKILL.md +99 -0
  14. package/plugin/pi-skills/implement-issue/SKILL.md +135 -0
  15. package/plugin/pi-skills/investigate-sentry-issue/SKILL.md +138 -0
  16. package/plugin/pi-skills/respond-github/SKILL.md +98 -0
  17. package/plugin/pi-skills/review-offered-task/SKILL.md +45 -0
  18. package/plugin/pi-skills/review-pr/SKILL.md +261 -0
  19. package/plugin/pi-skills/start-leader/SKILL.md +121 -0
  20. package/plugin/pi-skills/start-worker/SKILL.md +60 -0
  21. package/plugin/pi-skills/swarm-chat/SKILL.md +82 -0
  22. package/plugin/pi-skills/todos/SKILL.md +66 -0
  23. package/plugin/pi-skills/work-on-task/SKILL.md +65 -0
  24. package/plugin/skills/artifacts/examples/approval-flow.ts +34 -0
  25. package/plugin/skills/artifacts/examples/hono-dashboard.ts +31 -0
  26. package/plugin/skills/artifacts/examples/multi-artifact.ts +20 -0
  27. package/plugin/skills/artifacts/examples/static-report.sh +17 -0
  28. package/plugin/skills/artifacts/skill.md +71 -0
  29. package/src/agentmail/app.ts +65 -0
  30. package/src/agentmail/handlers.ts +262 -0
  31. package/src/agentmail/index.ts +9 -0
  32. package/src/agentmail/templates.ts +111 -0
  33. package/src/agentmail/types.ts +51 -0
  34. package/src/artifact-sdk/browser-sdk.ts +30 -0
  35. package/src/artifact-sdk/index.ts +2 -0
  36. package/src/artifact-sdk/localtunnel.d.ts +20 -0
  37. package/src/artifact-sdk/port.ts +12 -0
  38. package/src/artifact-sdk/server.ts +156 -0
  39. package/src/artifact-sdk/tunnel.ts +19 -0
  40. package/src/be/chunking.ts +193 -0
  41. package/src/be/db-queries/oauth.ts +90 -0
  42. package/src/be/db-queries/tracker.ts +182 -0
  43. package/src/be/db.ts +3327 -784
  44. package/src/be/embedding.ts +80 -0
  45. package/src/be/migrations/001_initial.sql +409 -0
  46. package/src/be/migrations/002_one_time_schedules.sql +59 -0
  47. package/src/be/migrations/003_workflows.sql +51 -0
  48. package/src/be/migrations/004_workflow_source.sql +81 -0
  49. package/src/be/migrations/005_epic_next_steps.sql +2 -0
  50. package/src/be/migrations/006_vcs_provider.sql +94 -0
  51. package/src/be/migrations/007_task_dir.sql +2 -0
  52. package/src/be/migrations/008_workflow_redesign.sql +85 -0
  53. package/src/be/migrations/009_tracker_integration.sql +144 -0
  54. package/src/be/migrations/010_step_diagnostics.sql +1 -0
  55. package/src/be/migrations/011_step_next_port.sql +1 -0
  56. package/src/be/migrations/012_trigger_schema.sql +1 -0
  57. package/src/be/migrations/013_task_output_schema.sql +2 -0
  58. package/src/be/migrations/014_prompt_templates.sql +33 -0
  59. package/src/be/migrations/015_workflow_workspace.sql +3 -0
  60. package/src/be/migrations/016_active_session_runner_session.sql +4 -0
  61. package/src/be/migrations/017_channel_activity_cursors.sql +6 -0
  62. package/src/be/migrations/018_fix_seed_double_version.sql +30 -0
  63. package/src/be/migrations/runner.ts +188 -0
  64. package/src/be/seed.ts +62 -0
  65. package/src/cli.tsx +231 -299
  66. package/src/commands/artifact.ts +241 -0
  67. package/src/commands/onboard/compose-generator.ts +169 -0
  68. package/src/commands/onboard/env-generator.ts +79 -0
  69. package/src/commands/onboard/manifest.ts +37 -0
  70. package/src/commands/onboard/presets.ts +85 -0
  71. package/src/commands/onboard/service-names.ts +47 -0
  72. package/src/commands/onboard/steps/core-credentials.tsx +111 -0
  73. package/src/commands/onboard/steps/custom-templates.tsx +168 -0
  74. package/src/commands/onboard/steps/generate.tsx +154 -0
  75. package/src/commands/onboard/steps/harness-credentials.tsx +195 -0
  76. package/src/commands/onboard/steps/harness.tsx +21 -0
  77. package/src/commands/onboard/steps/health-check.tsx +171 -0
  78. package/src/commands/onboard/steps/integration-github.tsx +105 -0
  79. package/src/commands/onboard/steps/integration-gitlab.tsx +79 -0
  80. package/src/commands/onboard/steps/integration-menu.tsx +58 -0
  81. package/src/commands/onboard/steps/integration-sentry.tsx +79 -0
  82. package/src/commands/onboard/steps/integration-slack.tsx +165 -0
  83. package/src/commands/onboard/steps/post-connect.tsx +145 -0
  84. package/src/commands/onboard/steps/post-dashboard.tsx +34 -0
  85. package/src/commands/onboard/steps/post-task.tsx +103 -0
  86. package/src/commands/onboard/steps/prereq-check.tsx +178 -0
  87. package/src/commands/onboard/steps/review.tsx +82 -0
  88. package/src/commands/onboard/steps/start.tsx +97 -0
  89. package/src/commands/onboard/templates.ts +34 -0
  90. package/src/commands/onboard/types.ts +259 -0
  91. package/src/commands/onboard.tsx +425 -0
  92. package/src/commands/runner.ts +1540 -630
  93. package/src/commands/setup.tsx +23 -38
  94. package/src/commands/shared/client-config.ts +41 -0
  95. package/src/commands/templates.ts +172 -0
  96. package/src/github/app.ts +8 -0
  97. package/src/github/handlers.ts +384 -151
  98. package/src/github/index.ts +1 -0
  99. package/src/github/mentions-aliases.test.ts +73 -0
  100. package/src/github/mentions.test.ts +3 -3
  101. package/src/github/mentions.ts +32 -6
  102. package/src/github/templates.ts +398 -0
  103. package/src/github/types.ts +1 -0
  104. package/src/gitlab/auth.ts +63 -0
  105. package/src/gitlab/handlers.ts +368 -0
  106. package/src/gitlab/index.ts +19 -0
  107. package/src/gitlab/reactions.ts +104 -0
  108. package/src/gitlab/templates.ts +140 -0
  109. package/src/gitlab/types.ts +130 -0
  110. package/src/heartbeat/heartbeat.ts +434 -0
  111. package/src/heartbeat/index.ts +1 -0
  112. package/src/heartbeat/templates.ts +30 -0
  113. package/src/hooks/hook.ts +555 -4
  114. package/src/hooks/tool-loop-detection.test.ts +158 -0
  115. package/src/hooks/tool-loop-detection.ts +167 -0
  116. package/src/http/active-sessions.ts +199 -0
  117. package/src/http/agents.ts +328 -0
  118. package/src/http/config.ts +191 -0
  119. package/src/http/core.ts +309 -0
  120. package/src/http/db-query.ts +91 -0
  121. package/src/http/ecosystem.ts +63 -0
  122. package/src/http/epics.ts +460 -0
  123. package/src/http/index.ts +216 -0
  124. package/src/http/mcp.ts +77 -0
  125. package/src/http/memory.ts +168 -0
  126. package/src/http/openapi.ts +109 -0
  127. package/src/http/poll.ts +299 -0
  128. package/src/http/prompt-templates.ts +412 -0
  129. package/src/http/repos.ts +195 -0
  130. package/src/http/route-def.ts +123 -0
  131. package/src/http/schedules.ts +426 -0
  132. package/src/http/session-data.ts +241 -0
  133. package/src/http/stats.ts +174 -0
  134. package/src/http/tasks.ts +468 -0
  135. package/src/http/trackers/index.ts +10 -0
  136. package/src/http/trackers/linear.ts +187 -0
  137. package/src/http/types.ts +12 -0
  138. package/src/http/utils.ts +87 -0
  139. package/src/http/webhooks.ts +432 -0
  140. package/src/http/workflows.ts +530 -0
  141. package/src/http.ts +1 -1890
  142. package/src/linear/README.md +65 -0
  143. package/src/linear/app.ts +48 -0
  144. package/src/linear/client.ts +18 -0
  145. package/src/linear/index.ts +1 -0
  146. package/src/linear/oauth.ts +35 -0
  147. package/src/linear/outbound.ts +212 -0
  148. package/src/linear/sync.ts +567 -0
  149. package/src/linear/templates.ts +47 -0
  150. package/src/linear/types.ts +7 -0
  151. package/src/linear/webhook.ts +104 -0
  152. package/src/oauth/README.md +66 -0
  153. package/src/oauth/index.ts +6 -0
  154. package/src/oauth/wrapper.ts +204 -0
  155. package/src/prompts/base-prompt.ts +150 -265
  156. package/src/prompts/defaults.ts +196 -0
  157. package/src/prompts/registry.ts +57 -0
  158. package/src/prompts/resolver.ts +296 -0
  159. package/src/prompts/session-templates.ts +604 -0
  160. package/src/providers/claude-adapter.ts +442 -0
  161. package/src/providers/index.ts +24 -0
  162. package/src/providers/pi-mono-adapter.ts +442 -0
  163. package/src/providers/pi-mono-extension.ts +624 -0
  164. package/src/providers/pi-mono-mcp-client.ts +124 -0
  165. package/src/providers/types.ts +75 -0
  166. package/src/scheduler/scheduler.test.ts +2 -0
  167. package/src/scheduler/scheduler.ts +231 -40
  168. package/src/server.ts +97 -6
  169. package/src/slack/HEURISTICS.md +105 -0
  170. package/src/slack/actions.ts +133 -0
  171. package/src/slack/app.ts +7 -0
  172. package/src/slack/assistant.ts +118 -0
  173. package/src/slack/blocks.ts +233 -0
  174. package/src/slack/channel-activity.ts +177 -0
  175. package/src/slack/commands.ts +31 -17
  176. package/src/slack/files.ts +1 -1
  177. package/src/slack/handlers.test.ts +114 -1
  178. package/src/slack/handlers.ts +230 -55
  179. package/src/slack/responses.ts +120 -67
  180. package/src/slack/router.ts +17 -99
  181. package/src/slack/templates.ts +55 -0
  182. package/src/slack/thread-buffer.ts +213 -0
  183. package/src/slack/watcher.ts +119 -4
  184. package/src/tests/agent-activity.test.ts +247 -0
  185. package/src/tests/agentmail-filters.test.ts +97 -0
  186. package/src/tests/artifact-sdk.test.ts +800 -0
  187. package/src/tests/base-prompt.test.ts +264 -0
  188. package/src/tests/build-pi-skills.test.ts +127 -0
  189. package/src/tests/channel-activity.test.ts +363 -0
  190. package/src/tests/claude-adapter.test.ts +126 -0
  191. package/src/tests/context-versioning.test.ts +425 -0
  192. package/src/tests/db-queries-oauth.test.ts +197 -0
  193. package/src/tests/db-queries-tracker.test.ts +230 -0
  194. package/src/tests/epics.test.ts +3 -3
  195. package/src/tests/error-tracker.test.ts +368 -0
  196. package/src/tests/fetch-resolved-env.test.ts +167 -0
  197. package/src/tests/generate-default-claude-md.test.ts +9 -1
  198. package/src/tests/generate-identity-templates.test.ts +124 -0
  199. package/src/tests/gitlab-auth.test.ts +109 -0
  200. package/src/tests/gitlab-handlers.test.ts +691 -0
  201. package/src/tests/gitlab-vcs-db.test.ts +177 -0
  202. package/src/tests/heartbeat.test.ts +364 -0
  203. package/src/tests/http-api-integration.test.ts +1698 -0
  204. package/src/tests/linear-outbound-sync.test.ts +200 -0
  205. package/src/tests/linear-webhook.test.ts +406 -0
  206. package/src/tests/match-route.test.ts +187 -0
  207. package/src/tests/memory.test.ts +737 -0
  208. package/src/tests/migration-runner-regressions.test.ts +86 -0
  209. package/src/tests/model-control.test.ts +338 -0
  210. package/src/tests/oauth-wrapper.test.ts +147 -0
  211. package/src/tests/onboard-compose.test.ts +138 -0
  212. package/src/tests/onboard-env.test.ts +174 -0
  213. package/src/tests/onboard-manifest.test.ts +137 -0
  214. package/src/tests/pi-mono-adapter.test.ts +234 -0
  215. package/src/tests/pool-session-logs.test.ts +199 -0
  216. package/src/tests/progress-dedup.test.ts +98 -0
  217. package/src/tests/prompt-template-github.test.ts +682 -0
  218. package/src/tests/prompt-template-remaining.test.ts +504 -0
  219. package/src/tests/prompt-template-resolver.test.ts +621 -0
  220. package/src/tests/prompt-template-session.test.ts +363 -0
  221. package/src/tests/prompt-templates-db.test.ts +616 -0
  222. package/src/tests/provider-adapter.test.ts +122 -0
  223. package/src/tests/provider-command-format.test.ts +98 -0
  224. package/src/tests/reload-config.test.ts +170 -0
  225. package/src/tests/runner-polling-api.test.ts +25 -20
  226. package/src/tests/scheduled-tasks.test.ts +104 -0
  227. package/src/tests/scheduler-backoff.test.ts +166 -0
  228. package/src/tests/self-improvement.test.ts +541 -0
  229. package/src/tests/session-attach.test.ts +536 -0
  230. package/src/tests/session-costs.test.ts +267 -1
  231. package/src/tests/slack-actions.test.ts +133 -0
  232. package/src/tests/slack-assistant.test.ts +136 -0
  233. package/src/tests/slack-blocks.test.ts +246 -0
  234. package/src/tests/slack-metadata-inheritance.test.ts +243 -0
  235. package/src/tests/slack-queue-offline.test.ts +174 -0
  236. package/src/tests/slack-router.test.ts +181 -0
  237. package/src/tests/slack-thread-buffer.test.ts +305 -0
  238. package/src/tests/slack-thread-followups.test.ts +298 -0
  239. package/src/tests/slack-watcher.test.ts +101 -0
  240. package/src/tests/structured-output.test.ts +307 -0
  241. package/src/tests/swarm-repos.test.ts +198 -0
  242. package/src/tests/task-cancellation.test.ts +6 -4
  243. package/src/tests/task-working-dir.test.ts +176 -0
  244. package/src/tests/template-fetch.test.ts +490 -0
  245. package/src/tests/tool-annotations.test.ts +371 -0
  246. package/src/tests/tracker-tools.test.ts +184 -0
  247. package/src/tests/update-profile-agentid.test.ts +248 -0
  248. package/src/tests/update-profile-api.test.ts +143 -3
  249. package/src/tests/update-profile-auth.test.ts +195 -0
  250. package/src/tests/validation-adapters.test.ts +86 -0
  251. package/src/tests/vcs-provider.test.ts +27 -0
  252. package/src/tests/workflow-agent-task.test.ts +196 -0
  253. package/src/tests/workflow-async-v2.test.ts +508 -0
  254. package/src/tests/workflow-convergence.test.ts +541 -0
  255. package/src/tests/workflow-definition-validation.test.ts +366 -0
  256. package/src/tests/workflow-engine-v2.test.ts +691 -0
  257. package/src/tests/workflow-executors.test.ts +736 -0
  258. package/src/tests/workflow-http-v2.test.ts +599 -0
  259. package/src/tests/workflow-integration-io.test.ts +902 -0
  260. package/src/tests/workflow-io-schemas.test.ts +624 -0
  261. package/src/tests/workflow-registry.test.ts +592 -0
  262. package/src/tests/workflow-retry-v2.test.ts +401 -0
  263. package/src/tests/workflow-retry-validation.test.ts +282 -0
  264. package/src/tests/workflow-schedule-trigger.test.ts +104 -0
  265. package/src/tests/workflow-template.test.ts +288 -0
  266. package/src/tests/workflow-trigger-schema.test.ts +359 -0
  267. package/src/tests/workflow-triggers-v2.test.ts +264 -0
  268. package/src/tests/workflow-versions.test.ts +208 -0
  269. package/src/tests/workflow-workspace.test.ts +272 -0
  270. package/src/tests/x402-client.test.ts +117 -0
  271. package/src/tests/x402-config.test.ts +182 -0
  272. package/src/tests/x402-spending-tracker.test.ts +185 -0
  273. package/src/tools/cancel-task.ts +2 -0
  274. package/src/tools/context-diff.ts +171 -0
  275. package/src/tools/context-history.ts +138 -0
  276. package/src/tools/create-channel.ts +1 -0
  277. package/src/tools/db-query.ts +78 -0
  278. package/src/tools/delete-channel.ts +132 -0
  279. package/src/tools/epics/assign-task-to-epic.ts +1 -0
  280. package/src/tools/epics/create-epic.ts +3 -2
  281. package/src/tools/epics/delete-epic.ts +2 -0
  282. package/src/tools/epics/get-epic-details.ts +2 -0
  283. package/src/tools/epics/list-epics.ts +2 -0
  284. package/src/tools/epics/unassign-task-from-epic.ts +1 -0
  285. package/src/tools/epics/update-epic.ts +7 -4
  286. package/src/tools/get-swarm.ts +2 -0
  287. package/src/tools/get-task-details.ts +2 -0
  288. package/src/tools/get-tasks.ts +27 -1
  289. package/src/tools/inject-learning.ts +106 -0
  290. package/src/tools/join-swarm.ts +17 -7
  291. package/src/tools/list-channels.ts +2 -0
  292. package/src/tools/list-services.ts +2 -0
  293. package/src/tools/memory-get.ts +56 -0
  294. package/src/tools/memory-search.ts +131 -0
  295. package/src/tools/my-agent-info.ts +2 -0
  296. package/src/tools/poll-task.ts +2 -20
  297. package/src/tools/post-message.ts +1 -0
  298. package/src/tools/prompt-templates/delete.ts +86 -0
  299. package/src/tools/prompt-templates/get.ts +89 -0
  300. package/src/tools/prompt-templates/index.ts +5 -0
  301. package/src/tools/prompt-templates/list.ts +95 -0
  302. package/src/tools/prompt-templates/preview.ts +84 -0
  303. package/src/tools/prompt-templates/set.ts +117 -0
  304. package/src/tools/read-messages.ts +2 -0
  305. package/src/tools/register-agentmail-inbox.ts +166 -0
  306. package/src/tools/register-service.ts +2 -0
  307. package/src/tools/schedules/create-schedule.ts +134 -24
  308. package/src/tools/schedules/delete-schedule.ts +2 -0
  309. package/src/tools/schedules/list-schedules.ts +20 -4
  310. package/src/tools/schedules/run-schedule-now.ts +1 -0
  311. package/src/tools/schedules/update-schedule.ts +49 -17
  312. package/src/tools/send-task.ts +132 -10
  313. package/src/tools/slack-download-file.ts +4 -2
  314. package/src/tools/slack-list-channels.ts +2 -0
  315. package/src/tools/slack-post.ts +2 -0
  316. package/src/tools/slack-read.ts +2 -0
  317. package/src/tools/slack-reply.ts +2 -0
  318. package/src/tools/slack-upload-file.ts +2 -0
  319. package/src/tools/store-progress.ts +205 -4
  320. package/src/tools/swarm-config/delete-config.ts +87 -0
  321. package/src/tools/swarm-config/get-config.ts +108 -0
  322. package/src/tools/swarm-config/index.ts +4 -0
  323. package/src/tools/swarm-config/list-config.ts +99 -0
  324. package/src/tools/swarm-config/set-config.ts +118 -0
  325. package/src/tools/task-action.ts +50 -5
  326. package/src/tools/task-dedup.ts +97 -0
  327. package/src/tools/templates.ts +53 -0
  328. package/src/tools/tool-config.ts +124 -0
  329. package/src/tools/tracker/index.ts +6 -0
  330. package/src/tools/tracker/tracker-link-epic.ts +64 -0
  331. package/src/tools/tracker/tracker-link-task.ts +64 -0
  332. package/src/tools/tracker/tracker-map-agent.ts +57 -0
  333. package/src/tools/tracker/tracker-status.ts +56 -0
  334. package/src/tools/tracker/tracker-sync-status.ts +42 -0
  335. package/src/tools/tracker/tracker-unlink.ts +41 -0
  336. package/src/tools/unregister-service.ts +2 -0
  337. package/src/tools/update-profile.ts +172 -17
  338. package/src/tools/update-service-status.ts +2 -0
  339. package/src/tools/utils.ts +10 -1
  340. package/src/tools/workflows/create-workflow.ts +129 -0
  341. package/src/tools/workflows/delete-workflow.ts +42 -0
  342. package/src/tools/workflows/get-workflow-run.ts +59 -0
  343. package/src/tools/workflows/get-workflow.ts +53 -0
  344. package/src/tools/workflows/index.ts +9 -0
  345. package/src/tools/workflows/list-workflow-runs.ts +48 -0
  346. package/src/tools/workflows/list-workflows.ts +42 -0
  347. package/src/tools/workflows/retry-workflow-run.ts +40 -0
  348. package/src/tools/workflows/trigger-workflow.ts +96 -0
  349. package/src/tools/workflows/update-workflow.ts +133 -0
  350. package/src/tracker/types.ts +51 -0
  351. package/src/types.ts +530 -14
  352. package/src/utils/credentials.test.ts +156 -0
  353. package/src/utils/credentials.ts +50 -0
  354. package/src/utils/error-tracker.ts +190 -0
  355. package/src/vcs/index.ts +15 -0
  356. package/src/vcs/types.ts +5 -0
  357. package/src/workflows/checkpoint.ts +121 -0
  358. package/src/workflows/cooldown.ts +28 -0
  359. package/src/workflows/definition.ts +235 -0
  360. package/src/workflows/engine.ts +580 -0
  361. package/src/workflows/event-bus.ts +29 -0
  362. package/src/workflows/executors/agent-task.ts +103 -0
  363. package/src/workflows/executors/base.ts +86 -0
  364. package/src/workflows/executors/code-match.ts +88 -0
  365. package/src/workflows/executors/index.ts +16 -0
  366. package/src/workflows/executors/notify.ts +93 -0
  367. package/src/workflows/executors/property-match.ts +104 -0
  368. package/src/workflows/executors/raw-llm.ts +83 -0
  369. package/src/workflows/executors/registry.ts +76 -0
  370. package/src/workflows/executors/script.ts +103 -0
  371. package/src/workflows/executors/validate.ts +215 -0
  372. package/src/workflows/executors/vcs.ts +58 -0
  373. package/src/workflows/index.ts +61 -0
  374. package/src/workflows/input.ts +46 -0
  375. package/src/workflows/json-schema-validator.ts +118 -0
  376. package/src/workflows/recovery.ts +139 -0
  377. package/src/workflows/resume.ts +229 -0
  378. package/src/workflows/retry-poller.ts +216 -0
  379. package/src/workflows/template.ts +74 -0
  380. package/src/workflows/templates.ts +86 -0
  381. package/src/workflows/triggers.ts +124 -0
  382. package/src/workflows/validation.ts +104 -0
  383. package/src/workflows/version.ts +44 -0
  384. package/src/x402/cli.ts +140 -0
  385. package/src/x402/client.ts +192 -0
  386. package/src/x402/config.ts +131 -0
  387. package/src/x402/index.ts +37 -0
  388. package/src/x402/openfort-signer.ts +83 -0
  389. package/src/x402/spending-tracker.ts +109 -0
  390. package/templates/official/coder/CLAUDE.md +49 -0
  391. package/templates/official/coder/IDENTITY.md +28 -0
  392. package/templates/official/coder/SOUL.md +43 -0
  393. package/templates/official/coder/TOOLS.md +40 -0
  394. package/templates/official/coder/config.json +23 -0
  395. package/templates/official/coder/start-up.sh +23 -0
  396. package/templates/official/content-reviewer/CLAUDE.md +68 -0
  397. package/templates/official/content-reviewer/IDENTITY.md +28 -0
  398. package/templates/official/content-reviewer/SOUL.md +44 -0
  399. package/templates/official/content-reviewer/TOOLS.md +37 -0
  400. package/templates/official/content-reviewer/config.json +23 -0
  401. package/templates/official/content-reviewer/start-up.sh +23 -0
  402. package/templates/official/content-strategist/CLAUDE.md +63 -0
  403. package/templates/official/content-strategist/IDENTITY.md +33 -0
  404. package/templates/official/content-strategist/SOUL.md +48 -0
  405. package/templates/official/content-strategist/TOOLS.md +47 -0
  406. package/templates/official/content-strategist/config.json +23 -0
  407. package/templates/official/content-strategist/start-up.sh +23 -0
  408. package/templates/official/content-writer/CLAUDE.md +72 -0
  409. package/templates/official/content-writer/IDENTITY.md +30 -0
  410. package/templates/official/content-writer/SOUL.md +46 -0
  411. package/templates/official/content-writer/TOOLS.md +44 -0
  412. package/templates/official/content-writer/config.json +23 -0
  413. package/templates/official/content-writer/start-up.sh +23 -0
  414. package/templates/official/forward-deployed-engineer/CLAUDE.md +54 -0
  415. package/templates/official/forward-deployed-engineer/IDENTITY.md +37 -0
  416. package/templates/official/forward-deployed-engineer/SOUL.md +55 -0
  417. package/templates/official/forward-deployed-engineer/config.json +21 -0
  418. package/templates/official/lead/CLAUDE.md +33 -0
  419. package/templates/official/lead/IDENTITY.md +36 -0
  420. package/templates/official/lead/SOUL.md +51 -0
  421. package/templates/official/lead/config.json +22 -0
  422. package/templates/official/researcher/CLAUDE.md +46 -0
  423. package/templates/official/researcher/IDENTITY.md +28 -0
  424. package/templates/official/researcher/SOUL.md +43 -0
  425. package/templates/official/researcher/config.json +21 -0
  426. package/templates/official/reviewer/CLAUDE.md +63 -0
  427. package/templates/official/reviewer/IDENTITY.md +28 -0
  428. package/templates/official/reviewer/SOUL.md +45 -0
  429. package/templates/official/reviewer/config.json +21 -0
  430. package/templates/official/tester/CLAUDE.md +53 -0
  431. package/templates/official/tester/IDENTITY.md +28 -0
  432. package/templates/official/tester/SOUL.md +55 -0
  433. package/templates/official/tester/config.json +21 -0
  434. package/templates/schema.ts +35 -0
  435. package/.claude/settings.local.json +0 -115
  436. package/.dockerignore +0 -61
  437. package/.editorconfig +0 -15
  438. package/.env.docker.example +0 -39
  439. package/.env.example +0 -40
  440. package/.github/workflows/ci.yml +0 -76
  441. package/.github/workflows/docker-and-deploy.yml +0 -117
  442. package/.wts-config.json +0 -4
  443. package/.wts-setup.ts +0 -102
  444. package/CLAUDE.md +0 -104
  445. package/CONTRIBUTING.md +0 -270
  446. package/DEPLOYMENT.md +0 -605
  447. package/Dockerfile +0 -57
  448. package/Dockerfile.worker +0 -157
  449. package/FAQ.md +0 -19
  450. package/MCP.md +0 -406
  451. package/UI.md +0 -40
  452. package/assets/agent-swarm-logo-orange.png +0 -0
  453. package/assets/agent-swarm-logo.png +0 -0
  454. package/assets/agent-swarm.mp4 +0 -0
  455. package/assets/agent-swarm.png +0 -0
  456. package/biome.json +0 -39
  457. package/deploy/DEPLOY.md +0 -60
  458. package/deploy/agent-swarm.service +0 -17
  459. package/deploy/docker-push.ts +0 -30
  460. package/deploy/install.ts +0 -85
  461. package/deploy/prod-db.ts +0 -42
  462. package/deploy/uninstall.ts +0 -12
  463. package/deploy/update.ts +0 -21
  464. package/docker-compose.example.yml +0 -159
  465. package/docker-entrypoint.sh +0 -352
  466. package/ecosystem.config.cjs +0 -66
  467. package/plugin/README.md +0 -1
  468. package/plugin/hooks/hooks.json +0 -71
  469. package/pyproject.toml +0 -9
  470. package/scripts/generate-mcp-docs.ts +0 -415
  471. package/slack-manifest.json +0 -71
  472. package/src/tests/get-inbox-message.test.ts +0 -145
  473. package/src/tools/get-inbox-message.ts +0 -89
  474. package/src/tools/inbox-delegate.ts +0 -113
  475. package/thoughts/shared/plans/2025-12-18-slack-integration.md +0 -1195
  476. package/thoughts/shared/plans/2025-12-19-agent-log-streaming.md +0 -732
  477. package/thoughts/shared/plans/2025-12-19-role-based-swarm-plugin.md +0 -361
  478. package/thoughts/shared/plans/2025-12-20-mobile-responsive-ui.md +0 -501
  479. package/thoughts/shared/plans/2025-12-20-startup-team-swarm.md +0 -560
  480. package/thoughts/shared/plans/2025-12-23-runner-level-polling.md +0 -934
  481. package/thoughts/shared/plans/2025-12-23-runner-session-logs.md +0 -1000
  482. package/thoughts/shared/plans/2025-12-23-worker-lead-spawn-triggers.md +0 -568
  483. package/thoughts/shared/plans/2026-01-09-inverse-teleport.md +0 -1516
  484. package/thoughts/shared/plans/2026-01-12-agent-rename-pm2-control.md +0 -1133
  485. package/thoughts/shared/plans/2026-01-12-github-app-integration.md +0 -380
  486. package/thoughts/shared/plans/2026-01-12-lead-inbox-model.md +0 -876
  487. package/thoughts/shared/plans/2026-01-12-ralph-wiggum-integration.md +0 -463
  488. package/thoughts/shared/plans/2026-01-13-agent-concurrency.md +0 -691
  489. package/thoughts/shared/plans/2026-01-13-github-assignment-handling.md +0 -690
  490. package/thoughts/shared/plans/2026-01-13-prevent-duplicate-trigger-processing.md +0 -1071
  491. package/thoughts/shared/plans/2026-01-14-fix-slack-thread-context.md +0 -507
  492. package/thoughts/shared/plans/2026-01-15-scheduled-tasks-implementation.md +0 -565
  493. package/thoughts/shared/plans/2026-01-15-usage-cost-tracking-ui.md +0 -1479
  494. package/thoughts/shared/plans/2026-01-16-epics-feature-implementation.md +0 -1230
  495. package/thoughts/shared/research/.gitkeep +0 -0
  496. package/thoughts/shared/research/2025-01-09-inverse-teleport-plan-review.md +0 -420
  497. package/thoughts/shared/research/2025-12-18-slack-integration.md +0 -442
  498. package/thoughts/shared/research/2025-12-19-agent-log-streaming.md +0 -339
  499. package/thoughts/shared/research/2025-12-19-agent-secrets-cli-research.md +0 -390
  500. package/thoughts/shared/research/2025-12-21-gemini-cli-integration.md +0 -376
  501. package/thoughts/shared/research/2025-12-22-runner-loop-architecture.md +0 -582
  502. package/thoughts/shared/research/2025-12-22-setup-experience-improvements.md +0 -264
  503. package/thoughts/shared/research/2026-01-13-lead-duplicate-trigger-processing.md +0 -223
  504. package/thoughts/shared/research/2026-01-14-lead-slack-thread-context.md +0 -277
  505. package/thoughts/shared/research/2026-01-15-ai-tracker-agent-swarm-integration.md +0 -376
  506. package/thoughts/shared/research/2026-01-15-auto-starting-processes-in-worker-containers.md +0 -787
  507. package/thoughts/shared/research/2026-01-15-scheduled-tasks.md +0 -390
  508. package/thoughts/shared/research/2026-01-16-epics-feature-research.md +0 -437
  509. package/thoughts/taras/plans/2026-01-22-agent-swarm-schemas.md +0 -98
  510. package/thoughts/taras/plans/2026-01-28-per-worker-claude-md.md +0 -617
  511. package/thoughts/taras/plans/2026-01-28-sentry-cli-integration.md +0 -214
  512. package/thoughts/taras/research/2026-01-22-vercel-cli-integration.md +0 -287
  513. package/thoughts/taras/research/2026-01-27-excessive-polling-issue.md +0 -311
  514. package/thoughts/taras/research/2026-01-28-per-worker-claude-md.md +0 -383
  515. package/thoughts/taras/research/2026-01-28-sentry-cli-integration.md +0 -240
  516. package/tsconfig.json +0 -37
  517. package/ui/CLAUDE.md +0 -49
  518. package/ui/bun.lock +0 -771
  519. package/ui/index.html +0 -22
  520. package/ui/package-lock.json +0 -5290
  521. package/ui/package.json +0 -33
  522. package/ui/pnpm-lock.yaml +0 -3341
  523. package/ui/postcss.config.js +0 -6
  524. package/ui/public/logo.png +0 -0
  525. package/ui/src/App.tsx +0 -63
  526. package/ui/src/components/ActivityFeed.tsx +0 -440
  527. package/ui/src/components/AgentDetailPanel.tsx +0 -733
  528. package/ui/src/components/AgentsPanel.tsx +0 -815
  529. package/ui/src/components/ChatPanel.tsx +0 -1920
  530. package/ui/src/components/ConfigModal.tsx +0 -253
  531. package/ui/src/components/Dashboard.tsx +0 -832
  532. package/ui/src/components/EditAgentProfileModal.tsx +0 -433
  533. package/ui/src/components/EpicDetailPage.tsx +0 -741
  534. package/ui/src/components/EpicsPanel.tsx +0 -566
  535. package/ui/src/components/Header.tsx +0 -160
  536. package/ui/src/components/JsonViewer.tsx +0 -171
  537. package/ui/src/components/ScheduledTaskDetailPanel.tsx +0 -517
  538. package/ui/src/components/ScheduledTasksPanel.tsx +0 -639
  539. package/ui/src/components/ServicesPanel.tsx +0 -622
  540. package/ui/src/components/SessionLogPanel.tsx +0 -1219
  541. package/ui/src/components/StatsBar.tsx +0 -321
  542. package/ui/src/components/StatusBadge.tsx +0 -168
  543. package/ui/src/components/TaskDetailPanel.tsx +0 -903
  544. package/ui/src/components/TasksPanel.tsx +0 -614
  545. package/ui/src/components/UsageCharts.tsx +0 -216
  546. package/ui/src/components/UsageTab.tsx +0 -394
  547. package/ui/src/hooks/queries.ts +0 -353
  548. package/ui/src/hooks/useAutoScroll.ts +0 -83
  549. package/ui/src/index.css +0 -257
  550. package/ui/src/lib/api.ts +0 -268
  551. package/ui/src/lib/config.ts +0 -35
  552. package/ui/src/lib/contentPreview.ts +0 -208
  553. package/ui/src/lib/theme.ts +0 -214
  554. package/ui/src/lib/utils.ts +0 -88
  555. package/ui/src/main.tsx +0 -28
  556. package/ui/src/types/api.ts +0 -323
  557. package/ui/src/vite-env.d.ts +0 -1
  558. package/ui/tailwind.config.js +0 -37
  559. package/ui/tsconfig.json +0 -31
  560. package/ui/vite.config.ts +0 -35
  561. /package/{thoughts/shared/plans → templates/community}/.gitkeep +0 -0
@@ -0,0 +1,425 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import {
4
+ closeDb,
5
+ createAgent,
6
+ createContextVersion,
7
+ getContextVersion,
8
+ getContextVersionHistory,
9
+ getLatestContextVersion,
10
+ initDb,
11
+ updateAgentProfile,
12
+ } from "../be/db";
13
+
14
+ const TEST_DB_PATH = "./test-context-versioning.sqlite";
15
+
16
+ function sha256(content: string): string {
17
+ const hasher = new Bun.CryptoHasher("sha256");
18
+ hasher.update(content);
19
+ return hasher.digest("hex");
20
+ }
21
+
22
+ describe("Context Versioning", () => {
23
+ const leadId = "aaaa0000-0000-4000-8000-000000000001";
24
+ const workerId = "bbbb0000-0000-4000-8000-000000000002";
25
+
26
+ beforeAll(async () => {
27
+ for (const suffix of ["", "-wal", "-shm"]) {
28
+ try {
29
+ await unlink(TEST_DB_PATH + suffix);
30
+ } catch {
31
+ // File doesn't exist
32
+ }
33
+ }
34
+
35
+ initDb(TEST_DB_PATH);
36
+
37
+ createAgent({ id: leadId, name: "Test Lead", isLead: true, status: "idle" });
38
+ createAgent({ id: workerId, name: "Test Worker", isLead: false, status: "idle" });
39
+ });
40
+
41
+ afterAll(async () => {
42
+ closeDb();
43
+ for (const suffix of ["", "-wal", "-shm"]) {
44
+ try {
45
+ await unlink(TEST_DB_PATH + suffix);
46
+ } catch {
47
+ // File doesn't exist
48
+ }
49
+ }
50
+ });
51
+
52
+ // ============================================================================
53
+ // createContextVersion + getContextVersion
54
+ // ============================================================================
55
+
56
+ describe("createContextVersion", () => {
57
+ test("creates a version and returns it with all fields", () => {
58
+ const version = createContextVersion({
59
+ agentId: workerId,
60
+ field: "soulMd",
61
+ content: "# Soul v1\nI am a test agent.",
62
+ version: 1,
63
+ changeSource: "system",
64
+ contentHash: sha256("# Soul v1\nI am a test agent."),
65
+ });
66
+
67
+ expect(version.id).toBeTruthy();
68
+ expect(version.agentId).toBe(workerId);
69
+ expect(version.field).toBe("soulMd");
70
+ expect(version.content).toBe("# Soul v1\nI am a test agent.");
71
+ expect(version.version).toBe(1);
72
+ expect(version.changeSource).toBe("system");
73
+ expect(version.changedByAgentId).toBeNull();
74
+ expect(version.changeReason).toBeNull();
75
+ expect(version.contentHash).toBe(sha256("# Soul v1\nI am a test agent."));
76
+ expect(version.previousVersionId).toBeNull();
77
+ expect(version.createdAt).toBeTruthy();
78
+ });
79
+
80
+ test("creates a version with optional fields populated", () => {
81
+ const version = createContextVersion({
82
+ agentId: workerId,
83
+ field: "identityMd",
84
+ content: "# Identity v1",
85
+ version: 1,
86
+ changeSource: "lead_coaching",
87
+ changedByAgentId: leadId,
88
+ changeReason: "Initial coaching",
89
+ contentHash: sha256("# Identity v1"),
90
+ });
91
+
92
+ expect(version.changedByAgentId).toBe(leadId);
93
+ expect(version.changeReason).toBe("Initial coaching");
94
+ });
95
+
96
+ test("chains versions with previousVersionId", () => {
97
+ const v1 = createContextVersion({
98
+ agentId: workerId,
99
+ field: "toolsMd",
100
+ content: "tools v1",
101
+ version: 1,
102
+ changeSource: "system",
103
+ contentHash: sha256("tools v1"),
104
+ });
105
+
106
+ const v2 = createContextVersion({
107
+ agentId: workerId,
108
+ field: "toolsMd",
109
+ content: "tools v2",
110
+ version: 2,
111
+ changeSource: "self_edit",
112
+ contentHash: sha256("tools v2"),
113
+ previousVersionId: v1.id,
114
+ });
115
+
116
+ expect(v2.previousVersionId).toBe(v1.id);
117
+ expect(v2.version).toBe(2);
118
+ });
119
+ });
120
+
121
+ // ============================================================================
122
+ // getContextVersion
123
+ // ============================================================================
124
+
125
+ describe("getContextVersion", () => {
126
+ test("returns a version by ID", () => {
127
+ const created = createContextVersion({
128
+ agentId: workerId,
129
+ field: "claudeMd",
130
+ content: "claude md content",
131
+ version: 1,
132
+ changeSource: "api",
133
+ contentHash: sha256("claude md content"),
134
+ });
135
+
136
+ const fetched = getContextVersion(created.id);
137
+ expect(fetched).not.toBeNull();
138
+ expect(fetched!.id).toBe(created.id);
139
+ expect(fetched!.content).toBe("claude md content");
140
+ });
141
+
142
+ test("returns null for non-existent ID", () => {
143
+ const result = getContextVersion("00000000-0000-4000-8000-999999999999");
144
+ expect(result).toBeNull();
145
+ });
146
+ });
147
+
148
+ // ============================================================================
149
+ // getLatestContextVersion
150
+ // ============================================================================
151
+
152
+ describe("getLatestContextVersion", () => {
153
+ test("returns the latest version for an agent+field", () => {
154
+ const content1 = `setup script v1 ${crypto.randomUUID()}`;
155
+ const content2 = `setup script v2 ${crypto.randomUUID()}`;
156
+
157
+ createContextVersion({
158
+ agentId: leadId,
159
+ field: "setupScript",
160
+ content: content1,
161
+ version: 1,
162
+ changeSource: "system",
163
+ contentHash: sha256(content1),
164
+ });
165
+
166
+ createContextVersion({
167
+ agentId: leadId,
168
+ field: "setupScript",
169
+ content: content2,
170
+ version: 2,
171
+ changeSource: "self_edit",
172
+ contentHash: sha256(content2),
173
+ });
174
+
175
+ const latest = getLatestContextVersion(leadId, "setupScript");
176
+ expect(latest).not.toBeNull();
177
+ expect(latest!.version).toBe(2);
178
+ expect(latest!.content).toBe(content2);
179
+ });
180
+
181
+ test("returns null when no versions exist for agent+field", () => {
182
+ const result = getLatestContextVersion("00000000-0000-4000-8000-999999999999", "soulMd");
183
+ expect(result).toBeNull();
184
+ });
185
+ });
186
+
187
+ // ============================================================================
188
+ // getContextVersionHistory
189
+ // ============================================================================
190
+
191
+ describe("getContextVersionHistory", () => {
192
+ const historyAgentId = "cccc0000-0000-4000-8000-000000000003";
193
+
194
+ beforeAll(() => {
195
+ createAgent({ id: historyAgentId, name: "History Agent", isLead: false, status: "idle" });
196
+
197
+ // Create 5 versions for soulMd
198
+ for (let i = 1; i <= 5; i++) {
199
+ const content = `soul version ${i}`;
200
+ createContextVersion({
201
+ agentId: historyAgentId,
202
+ field: "soulMd",
203
+ content,
204
+ version: i,
205
+ changeSource: i === 1 ? "system" : "self_edit",
206
+ contentHash: sha256(content),
207
+ });
208
+ }
209
+
210
+ // Create 2 versions for identityMd
211
+ for (let i = 1; i <= 2; i++) {
212
+ const content = `identity version ${i}`;
213
+ createContextVersion({
214
+ agentId: historyAgentId,
215
+ field: "identityMd",
216
+ content,
217
+ version: i,
218
+ changeSource: "api",
219
+ contentHash: sha256(content),
220
+ });
221
+ }
222
+ });
223
+
224
+ test("returns all versions for an agent (no field filter)", () => {
225
+ const history = getContextVersionHistory({ agentId: historyAgentId, limit: 50 });
226
+ expect(history.length).toBe(7); // 5 soulMd + 2 identityMd
227
+ });
228
+
229
+ test("filters by field", () => {
230
+ const history = getContextVersionHistory({
231
+ agentId: historyAgentId,
232
+ field: "soulMd",
233
+ limit: 50,
234
+ });
235
+ expect(history.length).toBe(5);
236
+ for (const v of history) {
237
+ expect(v.field).toBe("soulMd");
238
+ }
239
+ });
240
+
241
+ test("respects limit parameter", () => {
242
+ const history = getContextVersionHistory({
243
+ agentId: historyAgentId,
244
+ field: "soulMd",
245
+ limit: 3,
246
+ });
247
+ expect(history.length).toBe(3);
248
+ // Should be latest first (DESC order)
249
+ expect(history[0]!.version).toBe(5);
250
+ expect(history[1]!.version).toBe(4);
251
+ expect(history[2]!.version).toBe(3);
252
+ });
253
+
254
+ test("defaults limit to 10", () => {
255
+ const history = getContextVersionHistory({ agentId: historyAgentId });
256
+ expect(history.length).toBe(7); // Only 7 versions exist, so all returned
257
+ });
258
+
259
+ test("returns empty array for agent with no versions", () => {
260
+ const history = getContextVersionHistory({
261
+ agentId: "00000000-0000-4000-8000-999999999999",
262
+ });
263
+ expect(history).toEqual([]);
264
+ });
265
+ });
266
+
267
+ // ============================================================================
268
+ // updateAgentProfile — SHA-256 content hash dedup
269
+ // ============================================================================
270
+
271
+ describe("updateAgentProfile with versioning", () => {
272
+ const dedupAgentId = "dddd0000-0000-4000-8000-000000000004";
273
+
274
+ beforeAll(() => {
275
+ createAgent({ id: dedupAgentId, name: "Dedup Agent", isLead: false, status: "idle" });
276
+ });
277
+
278
+ test("creates a version when content changes", () => {
279
+ updateAgentProfile(dedupAgentId, { soulMd: "soul content A" }, { changeSource: "api" });
280
+
281
+ const latest = getLatestContextVersion(dedupAgentId, "soulMd");
282
+ expect(latest).not.toBeNull();
283
+ expect(latest!.content).toBe("soul content A");
284
+ expect(latest!.version).toBe(1);
285
+ expect(latest!.changeSource).toBe("api");
286
+ });
287
+
288
+ test("creates a new version when content changes again", () => {
289
+ updateAgentProfile(
290
+ dedupAgentId,
291
+ { soulMd: "soul content B" },
292
+ { changeSource: "self_edit", changedByAgentId: dedupAgentId },
293
+ );
294
+
295
+ const latest = getLatestContextVersion(dedupAgentId, "soulMd");
296
+ expect(latest).not.toBeNull();
297
+ expect(latest!.content).toBe("soul content B");
298
+ expect(latest!.version).toBe(2);
299
+ expect(latest!.changeSource).toBe("self_edit");
300
+ expect(latest!.changedByAgentId).toBe(dedupAgentId);
301
+ });
302
+
303
+ test("skips version creation when content is unchanged (dedup)", () => {
304
+ // Update with the same content
305
+ updateAgentProfile(
306
+ dedupAgentId,
307
+ { soulMd: "soul content B" },
308
+ { changeSource: "session_sync" },
309
+ );
310
+
311
+ const latest = getLatestContextVersion(dedupAgentId, "soulMd");
312
+ expect(latest).not.toBeNull();
313
+ // Version should still be 2 — no new version created
314
+ expect(latest!.version).toBe(2);
315
+ expect(latest!.changeSource).toBe("self_edit"); // unchanged from before
316
+ });
317
+
318
+ test("creates versions for multiple fields in one update", () => {
319
+ updateAgentProfile(
320
+ dedupAgentId,
321
+ {
322
+ identityMd: "identity content",
323
+ toolsMd: "tools content",
324
+ },
325
+ { changeSource: "api" },
326
+ );
327
+
328
+ const identityLatest = getLatestContextVersion(dedupAgentId, "identityMd");
329
+ const toolsLatest = getLatestContextVersion(dedupAgentId, "toolsMd");
330
+
331
+ expect(identityLatest).not.toBeNull();
332
+ expect(identityLatest!.content).toBe("identity content");
333
+ expect(identityLatest!.version).toBe(1);
334
+
335
+ expect(toolsLatest).not.toBeNull();
336
+ expect(toolsLatest!.content).toBe("tools content");
337
+ expect(toolsLatest!.version).toBe(1);
338
+ });
339
+
340
+ test("defaults changeSource to 'api' when no meta provided", () => {
341
+ updateAgentProfile(dedupAgentId, { claudeMd: "claude content" });
342
+
343
+ const latest = getLatestContextVersion(dedupAgentId, "claudeMd");
344
+ expect(latest).not.toBeNull();
345
+ expect(latest!.changeSource).toBe("api");
346
+ });
347
+
348
+ test("chains previousVersionId correctly", () => {
349
+ // soulMd already has v1 and v2, create v3
350
+ updateAgentProfile(dedupAgentId, { soulMd: "soul content C" }, { changeSource: "self_edit" });
351
+
352
+ const v3 = getLatestContextVersion(dedupAgentId, "soulMd");
353
+ expect(v3).not.toBeNull();
354
+ expect(v3!.version).toBe(3);
355
+ expect(v3!.previousVersionId).not.toBeNull();
356
+
357
+ // The previous version should be v2
358
+ const v2 = getContextVersion(v3!.previousVersionId!);
359
+ expect(v2).not.toBeNull();
360
+ expect(v2!.version).toBe(2);
361
+ });
362
+
363
+ test("returns updated agent even with versioning", () => {
364
+ const agent = updateAgentProfile(
365
+ dedupAgentId,
366
+ { soulMd: "soul content D" },
367
+ { changeSource: "api" },
368
+ );
369
+
370
+ expect(agent).not.toBeNull();
371
+ expect(agent!.soulMd).toBe("soul content D");
372
+ expect(agent!.id).toBe(dedupAgentId);
373
+ });
374
+ });
375
+
376
+ // ============================================================================
377
+ // Backfill / seed logic
378
+ // ============================================================================
379
+
380
+ describe("seedContextVersions (via initDb)", () => {
381
+ // seedContextVersions runs automatically during initDb.
382
+ // The worker and lead agents created in beforeAll had no soulMd/identityMd,
383
+ // so no versions should have been seeded for them.
384
+ // Test by creating an agent with content and re-running initDb (which re-seeds).
385
+
386
+ test("agents without content fields get no seeded versions", () => {
387
+ // workerId was created without any soulMd/identityMd content
388
+ // So no auto-seeded versions should exist for fields that were null
389
+ const history = getContextVersionHistory({
390
+ agentId: leadId,
391
+ field: "soulMd",
392
+ limit: 50,
393
+ });
394
+ // Lead agent had no soulMd at creation time, so no seeded version
395
+ // (any versions would be from explicit test calls)
396
+ // We can't test seeding directly since it ran at initDb time with empty agents
397
+ // But we verify the function doesn't crash on agents with null fields
398
+ expect(history).toBeInstanceOf(Array);
399
+ });
400
+ });
401
+
402
+ // ============================================================================
403
+ // Content hash consistency
404
+ // ============================================================================
405
+
406
+ describe("content hash consistency", () => {
407
+ test("same content produces same hash", () => {
408
+ const hash1 = sha256("hello world");
409
+ const hash2 = sha256("hello world");
410
+ expect(hash1).toBe(hash2);
411
+ });
412
+
413
+ test("different content produces different hash", () => {
414
+ const hash1 = sha256("hello world");
415
+ const hash2 = sha256("hello world!");
416
+ expect(hash1).not.toBe(hash2);
417
+ });
418
+
419
+ test("empty string has a valid hash", () => {
420
+ const hash = sha256("");
421
+ expect(hash).toBeTruthy();
422
+ expect(hash.length).toBe(64); // SHA-256 hex = 64 chars
423
+ });
424
+ });
425
+ });
@@ -0,0 +1,197 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { unlink } from "node:fs/promises";
3
+ import { closeDb, initDb } from "../be/db";
4
+ import {
5
+ deleteOAuthTokens,
6
+ getOAuthApp,
7
+ getOAuthTokens,
8
+ isTokenExpiringSoon,
9
+ storeOAuthTokens,
10
+ upsertOAuthApp,
11
+ } from "../be/db-queries/oauth";
12
+
13
+ const TEST_DB_PATH = "./test-db-queries-oauth.sqlite";
14
+
15
+ beforeAll(() => {
16
+ initDb(TEST_DB_PATH);
17
+ });
18
+
19
+ afterAll(async () => {
20
+ closeDb();
21
+ await unlink(TEST_DB_PATH).catch(() => {});
22
+ await unlink(`${TEST_DB_PATH}-wal`).catch(() => {});
23
+ await unlink(`${TEST_DB_PATH}-shm`).catch(() => {});
24
+ });
25
+
26
+ describe("OAuth Apps CRUD", () => {
27
+ test("getOAuthApp returns null for unknown provider", () => {
28
+ const result = getOAuthApp("nonexistent");
29
+ expect(result).toBeNull();
30
+ });
31
+
32
+ test("upsertOAuthApp creates a new app", () => {
33
+ upsertOAuthApp("test-provider", {
34
+ clientId: "client-123",
35
+ clientSecret: "secret-456",
36
+ authorizeUrl: "https://example.com/authorize",
37
+ tokenUrl: "https://example.com/token",
38
+ redirectUri: "https://example.com/callback",
39
+ scopes: "read,write",
40
+ });
41
+
42
+ const app = getOAuthApp("test-provider");
43
+ expect(app).not.toBeNull();
44
+ expect(app!.provider).toBe("test-provider");
45
+ expect(app!.clientId).toBe("client-123");
46
+ expect(app!.clientSecret).toBe("secret-456");
47
+ expect(app!.authorizeUrl).toBe("https://example.com/authorize");
48
+ expect(app!.tokenUrl).toBe("https://example.com/token");
49
+ expect(app!.redirectUri).toBe("https://example.com/callback");
50
+ expect(app!.scopes).toBe("read,write");
51
+ expect(app!.metadata).toBe("{}");
52
+ });
53
+
54
+ test("upsertOAuthApp updates existing app on conflict", () => {
55
+ upsertOAuthApp("test-provider", {
56
+ clientId: "client-updated",
57
+ clientSecret: "secret-updated",
58
+ authorizeUrl: "https://example.com/authorize-v2",
59
+ tokenUrl: "https://example.com/token-v2",
60
+ redirectUri: "https://example.com/callback-v2",
61
+ scopes: "read,write,admin",
62
+ metadata: '{"key": "value"}',
63
+ });
64
+
65
+ const app = getOAuthApp("test-provider");
66
+ expect(app).not.toBeNull();
67
+ expect(app!.clientId).toBe("client-updated");
68
+ expect(app!.scopes).toBe("read,write,admin");
69
+ expect(app!.metadata).toBe('{"key": "value"}');
70
+ });
71
+
72
+ test("multiple providers can coexist", () => {
73
+ upsertOAuthApp("provider-a", {
74
+ clientId: "a-client",
75
+ clientSecret: "a-secret",
76
+ authorizeUrl: "https://a.com/authorize",
77
+ tokenUrl: "https://a.com/token",
78
+ redirectUri: "https://a.com/callback",
79
+ scopes: "read",
80
+ });
81
+ upsertOAuthApp("provider-b", {
82
+ clientId: "b-client",
83
+ clientSecret: "b-secret",
84
+ authorizeUrl: "https://b.com/authorize",
85
+ tokenUrl: "https://b.com/token",
86
+ redirectUri: "https://b.com/callback",
87
+ scopes: "write",
88
+ });
89
+
90
+ const a = getOAuthApp("provider-a");
91
+ const b = getOAuthApp("provider-b");
92
+ expect(a!.clientId).toBe("a-client");
93
+ expect(b!.clientId).toBe("b-client");
94
+ });
95
+ });
96
+
97
+ describe("OAuth Tokens CRUD", () => {
98
+ test("getOAuthTokens returns null for unknown provider", () => {
99
+ const result = getOAuthTokens("nonexistent-tokens");
100
+ expect(result).toBeNull();
101
+ });
102
+
103
+ test("storeOAuthTokens creates tokens", () => {
104
+ // Need an oauth_app first (FK constraint)
105
+ upsertOAuthApp("token-test", {
106
+ clientId: "c",
107
+ clientSecret: "s",
108
+ authorizeUrl: "https://x.com/auth",
109
+ tokenUrl: "https://x.com/token",
110
+ redirectUri: "https://x.com/cb",
111
+ scopes: "read",
112
+ });
113
+
114
+ const futureDate = new Date(Date.now() + 3600000).toISOString();
115
+ storeOAuthTokens("token-test", {
116
+ accessToken: "access-abc",
117
+ refreshToken: "refresh-xyz",
118
+ expiresAt: futureDate,
119
+ scope: "read,write",
120
+ });
121
+
122
+ const tokens = getOAuthTokens("token-test");
123
+ expect(tokens).not.toBeNull();
124
+ expect(tokens!.provider).toBe("token-test");
125
+ expect(tokens!.accessToken).toBe("access-abc");
126
+ expect(tokens!.refreshToken).toBe("refresh-xyz");
127
+ expect(tokens!.scope).toBe("read,write");
128
+ });
129
+
130
+ test("storeOAuthTokens updates existing tokens (upsert)", () => {
131
+ const futureDate = new Date(Date.now() + 7200000).toISOString();
132
+ storeOAuthTokens("token-test", {
133
+ accessToken: "access-updated",
134
+ expiresAt: futureDate,
135
+ });
136
+
137
+ const tokens = getOAuthTokens("token-test");
138
+ expect(tokens!.accessToken).toBe("access-updated");
139
+ // refreshToken should be preserved (COALESCE)
140
+ expect(tokens!.refreshToken).toBe("refresh-xyz");
141
+ });
142
+
143
+ test("deleteOAuthTokens removes tokens", () => {
144
+ deleteOAuthTokens("token-test");
145
+ const tokens = getOAuthTokens("token-test");
146
+ expect(tokens).toBeNull();
147
+ });
148
+ });
149
+
150
+ describe("isTokenExpiringSoon", () => {
151
+ test("returns true when no tokens exist", () => {
152
+ expect(isTokenExpiringSoon("nonexistent")).toBe(true);
153
+ });
154
+
155
+ test("returns false for tokens expiring far in the future", () => {
156
+ upsertOAuthApp("expiry-test", {
157
+ clientId: "c",
158
+ clientSecret: "s",
159
+ authorizeUrl: "https://x.com/auth",
160
+ tokenUrl: "https://x.com/token",
161
+ redirectUri: "https://x.com/cb",
162
+ scopes: "read",
163
+ });
164
+
165
+ const farFuture = new Date(Date.now() + 24 * 3600000).toISOString();
166
+ storeOAuthTokens("expiry-test", {
167
+ accessToken: "a",
168
+ expiresAt: farFuture,
169
+ });
170
+
171
+ expect(isTokenExpiringSoon("expiry-test")).toBe(false);
172
+ });
173
+
174
+ test("returns true for tokens expiring within buffer", () => {
175
+ const almostExpired = new Date(Date.now() + 60000).toISOString(); // 1 minute from now
176
+ storeOAuthTokens("expiry-test", {
177
+ accessToken: "a",
178
+ expiresAt: almostExpired,
179
+ });
180
+
181
+ // Default buffer is 5 minutes, token expires in 1 minute → expiring soon
182
+ expect(isTokenExpiringSoon("expiry-test")).toBe(true);
183
+ });
184
+
185
+ test("respects custom buffer", () => {
186
+ const twoMinutes = new Date(Date.now() + 120000).toISOString();
187
+ storeOAuthTokens("expiry-test", {
188
+ accessToken: "a",
189
+ expiresAt: twoMinutes,
190
+ });
191
+
192
+ // With 1-minute buffer, 2-minute token is fine
193
+ expect(isTokenExpiringSoon("expiry-test", 60000)).toBe(false);
194
+ // With 3-minute buffer, 2-minute token is expiring soon
195
+ expect(isTokenExpiringSoon("expiry-test", 180000)).toBe(true);
196
+ });
197
+ });