crewswarm 0.8.1-beta

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 (362) hide show
  1. package/.env.example +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +316 -0
  4. package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js +1 -0
  5. package/apps/dashboard/dist/assets/chat-core-BwSoInmZ.js.br +0 -0
  6. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +1 -0
  7. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  8. package/apps/dashboard/dist/assets/components-CSUb80ze.js +1 -0
  9. package/apps/dashboard/dist/assets/components-CSUb80ze.js.br +0 -0
  10. package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js +1 -0
  11. package/apps/dashboard/dist/assets/core-utils-CAVnDoe1.js.br +0 -0
  12. package/apps/dashboard/dist/assets/index-CF0aJRtC.css +1 -0
  13. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  14. package/apps/dashboard/dist/assets/index-Px49zu76.js +2 -0
  15. package/apps/dashboard/dist/assets/index-Px49zu76.js.br +0 -0
  16. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js +1 -0
  17. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  18. package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js +1 -0
  19. package/apps/dashboard/dist/assets/setup-wizard-i3eEixlo.js.br +0 -0
  20. package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js +1 -0
  21. package/apps/dashboard/dist/assets/tab-agents-tab-BThdsdJY.js.br +0 -0
  22. package/apps/dashboard/dist/assets/tab-benchmarks-tab-DfCuAClu.js +1 -0
  23. package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js +1 -0
  24. package/apps/dashboard/dist/assets/tab-comms-tab-eHpOSBhG.js.br +0 -0
  25. package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js +1 -0
  26. package/apps/dashboard/dist/assets/tab-contacts-tab-yEegNyO4.js.br +0 -0
  27. package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js +1 -0
  28. package/apps/dashboard/dist/assets/tab-engines-tab-C3DYxTwy.js.br +0 -0
  29. package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js +1 -0
  30. package/apps/dashboard/dist/assets/tab-memory-tab-C59BYFQD.js.br +0 -0
  31. package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js +1 -0
  32. package/apps/dashboard/dist/assets/tab-models-tab-9Ur7pXWA.js.br +0 -0
  33. package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js +1 -0
  34. package/apps/dashboard/dist/assets/tab-pm-loop-tab-D7mnDelU.js.br +0 -0
  35. package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js +1 -0
  36. package/apps/dashboard/dist/assets/tab-projects-tab-C6h2Mv1K.js.br +0 -0
  37. package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js +1 -0
  38. package/apps/dashboard/dist/assets/tab-prompts-tab-C0wZvWK3.js.br +0 -0
  39. package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js +1 -0
  40. package/apps/dashboard/dist/assets/tab-services-tab-DBj_w3bc.js.br +0 -0
  41. package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js +1 -0
  42. package/apps/dashboard/dist/assets/tab-settings-tab-ezeqAjZk.js.br +0 -0
  43. package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js +1 -0
  44. package/apps/dashboard/dist/assets/tab-skills-tab-BYdU2whk.js.br +0 -0
  45. package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js +1 -0
  46. package/apps/dashboard/dist/assets/tab-spending-tab-Bg6w9t_p.js.br +0 -0
  47. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js +1 -0
  48. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BBV9HB2X.js.br +0 -0
  49. package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js +1 -0
  50. package/apps/dashboard/dist/assets/tab-swarm-tab-ChqLlEVs.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js +1 -0
  52. package/apps/dashboard/dist/assets/tab-usage-tab-B2UWXenJ.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js +1 -0
  54. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js +1 -0
  56. package/apps/dashboard/dist/assets/tab-workflows-tab-6QSXLJ0i.js.br +0 -0
  57. package/apps/dashboard/dist/favicon.png +0 -0
  58. package/apps/dashboard/dist/index.html +6466 -0
  59. package/apps/dashboard/dist/index.html.br +0 -0
  60. package/apps/dashboard/dist/index.html.gz +0 -0
  61. package/apps/dashboard/dist/signup.html +446 -0
  62. package/apps/dashboard/index.html +6442 -0
  63. package/apps/dashboard/package.json +15 -0
  64. package/apps/dashboard/src/app.js +2823 -0
  65. package/apps/dashboard/src/app.js.br +0 -0
  66. package/apps/dashboard/src/app.js.gz +0 -0
  67. package/apps/dashboard/src/chat/chat-actions.js +1847 -0
  68. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  69. package/apps/dashboard/src/chat/unified-messages.js +327 -0
  70. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  71. package/apps/dashboard/src/cli-process.js +208 -0
  72. package/apps/dashboard/src/cli-process.js.br +0 -0
  73. package/apps/dashboard/src/cli-process.js.gz +0 -0
  74. package/apps/dashboard/src/components/active-tasks-panel.js +175 -0
  75. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  76. package/apps/dashboard/src/core/api.js +18 -0
  77. package/apps/dashboard/src/core/api.js.br +0 -0
  78. package/apps/dashboard/src/core/dom.js +220 -0
  79. package/apps/dashboard/src/core/dom.js.br +0 -0
  80. package/apps/dashboard/src/core/state.js +91 -0
  81. package/apps/dashboard/src/core/state.js.br +0 -0
  82. package/apps/dashboard/src/core/task-manager.js +134 -0
  83. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  84. package/apps/dashboard/src/orchestration-status.js +127 -0
  85. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  86. package/apps/dashboard/src/setup-wizard.js +555 -0
  87. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  88. package/apps/dashboard/src/styles.css +2085 -0
  89. package/apps/dashboard/src/styles.css.br +0 -0
  90. package/apps/dashboard/src/styles.css.gz +0 -0
  91. package/apps/dashboard/src/tabs/agents-tab.js +2237 -0
  92. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  93. package/apps/dashboard/src/tabs/benchmarks-tab.js +229 -0
  94. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  95. package/apps/dashboard/src/tabs/comms-tab.js +955 -0
  96. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  97. package/apps/dashboard/src/tabs/contacts-tab.js +654 -0
  98. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  99. package/apps/dashboard/src/tabs/engines-tab.js +175 -0
  100. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  101. package/apps/dashboard/src/tabs/memory-tab.js +182 -0
  102. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/models-tab.js +441 -0
  104. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/pm-loop-tab.js +185 -0
  106. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/projects-tab.js +663 -0
  108. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  110. package/apps/dashboard/src/tabs/prompts-tab.js +160 -0
  111. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  112. package/apps/dashboard/src/tabs/services-tab.js +202 -0
  113. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  114. package/apps/dashboard/src/tabs/settings-tab.js +803 -0
  115. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  116. package/apps/dashboard/src/tabs/skills-tab.js +284 -0
  117. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  118. package/apps/dashboard/src/tabs/spending-tab.js +173 -0
  119. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  120. package/apps/dashboard/src/tabs/swarm-chat-tab.js +660 -0
  121. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/swarm-tab.js +538 -0
  123. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/usage-tab.js +390 -0
  125. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/waves-tab.js +238 -0
  127. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/workflows-tab.js +747 -0
  129. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  130. package/apps/vibe/.crew/agent-memory/pipeline.json +249 -0
  131. package/apps/vibe/.crew/cost.json +17 -0
  132. package/apps/vibe/.crew/json-parse-metrics.jsonl +22 -0
  133. package/apps/vibe/.crew/pipeline-metrics.jsonl +22 -0
  134. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +5 -0
  135. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +5 -0
  136. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +1 -0
  137. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +5 -0
  138. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +5 -0
  139. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +5 -0
  140. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +5 -0
  141. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +5 -0
  142. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +5 -0
  143. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +5 -0
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +5 -0
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +5 -0
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +5 -0
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +5 -0
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +5 -0
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +5 -0
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +5 -0
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +5 -0
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +2 -0
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +5 -0
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +5 -0
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +5 -0
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +5 -0
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +5 -0
  158. package/apps/vibe/.crew/sandbox.json +7 -0
  159. package/apps/vibe/.crew/session.json +285 -0
  160. package/apps/vibe/.crew/training-data.jsonl +0 -0
  161. package/apps/vibe/.github/workflows/studio-quality.yml +37 -0
  162. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +12 -0
  163. package/apps/vibe/.studio-data/project-messages/general.jsonl +54 -0
  164. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +10 -0
  165. package/apps/vibe/ARCHITECTURE.md +3393 -0
  166. package/apps/vibe/QUICK-REFERENCE.md +211 -0
  167. package/apps/vibe/README.md +76 -0
  168. package/apps/vibe/ROADMAP.md +41 -0
  169. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +35 -0
  170. package/apps/vibe/VISUAL-GUIDE.md +378 -0
  171. package/apps/vibe/capture-demo.mjs +160 -0
  172. package/apps/vibe/capture-vibe-assets.mjs +71 -0
  173. package/apps/vibe/capture-vibe-video.mjs +260 -0
  174. package/apps/vibe/check-buttons.js +41 -0
  175. package/apps/vibe/diagnose.html +106 -0
  176. package/apps/vibe/fix-buttons.js +103 -0
  177. package/apps/vibe/index.html +3401 -0
  178. package/apps/vibe/package-lock.json +920 -0
  179. package/apps/vibe/package.json +31 -0
  180. package/apps/vibe/public/favicon.png +0 -0
  181. package/apps/vibe/scripts/studio-pty-host.py +117 -0
  182. package/apps/vibe/server.mjs +1835 -0
  183. package/apps/vibe/src/main.js +2846 -0
  184. package/apps/vibe/src/register-all-languages.js +98 -0
  185. package/apps/vibe/start-studio.sh +11 -0
  186. package/apps/vibe/test/accessibility-tests.js +77 -0
  187. package/apps/vibe/test/browser-performance-audit.mjs +205 -0
  188. package/apps/vibe/test/performance-tests.js +120 -0
  189. package/apps/vibe/test/security-tests.js +213 -0
  190. package/apps/vibe/tests/e2e.local.mjs +54 -0
  191. package/apps/vibe/tests/server.smoke.mjs +106 -0
  192. package/apps/vibe/update_website.mjs +74 -0
  193. package/apps/vibe/vite.config.js +19 -0
  194. package/apps/vibe/watch-server.mjs +108 -0
  195. package/contrib/openclaw-plugin/README.md +199 -0
  196. package/contrib/openclaw-plugin/index.ts +306 -0
  197. package/contrib/openclaw-plugin/openclaw.plugin.json +41 -0
  198. package/contrib/openclaw-plugin/package.json +27 -0
  199. package/contrib/openclaw-plugin/skills/crewswarm/SKILL.md +88 -0
  200. package/crew-lead.mjs +649 -0
  201. package/engines/claude-code.json +36 -0
  202. package/engines/codex.json +37 -0
  203. package/engines/crew-cli.json +42 -0
  204. package/engines/cursor.json +40 -0
  205. package/engines/docker-sandbox.json +38 -0
  206. package/engines/gemini-cli.json +75 -0
  207. package/engines/opencode.json +31 -0
  208. package/gateway-bridge.mjs +1575 -0
  209. package/install.sh +738 -0
  210. package/lib/agent-registry.mjs +232 -0
  211. package/lib/agents/daemon.mjs +121 -0
  212. package/lib/agents/dispatch.mjs +225 -0
  213. package/lib/agents/permissions.mjs +90 -0
  214. package/lib/agents/platform-formatting.mjs +102 -0
  215. package/lib/agents/registry.mjs +81 -0
  216. package/lib/agents/tool-instructions.mjs +257 -0
  217. package/lib/agents/validation.mjs +75 -0
  218. package/lib/approval/policy-manager.mjs +221 -0
  219. package/lib/autoharness/index.mjs +391 -0
  220. package/lib/bridges/cli-executor.mjs +332 -0
  221. package/lib/bridges/gateway-ws.mjs +345 -0
  222. package/lib/bridges/integration.mjs +229 -0
  223. package/lib/bridges/rag-helper.mjs +90 -0
  224. package/lib/browser/opencode-passthrough-filter.js +44 -0
  225. package/lib/browser/passthrough-stderr.js +109 -0
  226. package/lib/chat/autonomous-mentions.mjs +373 -0
  227. package/lib/chat/history.mjs +82 -0
  228. package/lib/chat/mention-routing-intent.mjs +136 -0
  229. package/lib/chat/participants.mjs +95 -0
  230. package/lib/chat/project-messages-rag.mjs +265 -0
  231. package/lib/chat/project-messages.mjs +479 -0
  232. package/lib/chat/shared-chat-prompt-overlay.mjs +52 -0
  233. package/lib/chat/thread-binding.mjs +34 -0
  234. package/lib/chat/unified-history.mjs +223 -0
  235. package/lib/chat/unified-wrapper.mjs +41 -0
  236. package/lib/cli-process-tracker.mjs +228 -0
  237. package/lib/collections/index.mjs +433 -0
  238. package/lib/contacts/identity-linker.mjs +248 -0
  239. package/lib/contacts/index.mjs +341 -0
  240. package/lib/crew-judge/PROMPT.md +93 -0
  241. package/lib/crew-judge/judge.mjs +260 -0
  242. package/lib/crew-lead/agent-manager.mjs +125 -0
  243. package/lib/crew-lead/background.mjs +270 -0
  244. package/lib/crew-lead/brain.mjs +110 -0
  245. package/lib/crew-lead/chat-handler.mjs +2603 -0
  246. package/lib/crew-lead/chat-handler.mjs.bak +1274 -0
  247. package/lib/crew-lead/classifier.mjs +83 -0
  248. package/lib/crew-lead/http-server.mjs +4824 -0
  249. package/lib/crew-lead/intent.mjs +102 -0
  250. package/lib/crew-lead/interval-manager.mjs +41 -0
  251. package/lib/crew-lead/llm-caller.mjs +544 -0
  252. package/lib/crew-lead/prompts.mjs +392 -0
  253. package/lib/crew-lead/retry-manager.mjs +118 -0
  254. package/lib/crew-lead/tools.mjs +318 -0
  255. package/lib/crew-lead/wave-dispatcher.mjs +798 -0
  256. package/lib/crew-lead/waves-config.json +73 -0
  257. package/lib/crew-lead/waves-loader.mjs +110 -0
  258. package/lib/crew-lead/ws-router.mjs +428 -0
  259. package/lib/dispatch/parsers.mjs +299 -0
  260. package/lib/domain-planning/detector.mjs +196 -0
  261. package/lib/domain-planning/prompts/crew-pm-cli.md +96 -0
  262. package/lib/domain-planning/prompts/crew-pm-core.md +122 -0
  263. package/lib/domain-planning/prompts/crew-pm-frontend.md +111 -0
  264. package/lib/engines/crew-cli-sandbox.mjs +422 -0
  265. package/lib/engines/crew-cli.mjs +155 -0
  266. package/lib/engines/cursor-launcher.mjs +110 -0
  267. package/lib/engines/engine-registry.mjs +253 -0
  268. package/lib/engines/llm-direct.mjs +184 -0
  269. package/lib/engines/opencode.mjs +256 -0
  270. package/lib/engines/ouroboros.mjs +114 -0
  271. package/lib/engines/rt-envelope.mjs +1643 -0
  272. package/lib/engines/rt-envelope.mjs.backup-current +870 -0
  273. package/lib/engines/runners.mjs +1367 -0
  274. package/lib/gemini-cli-passthrough-noise.mjs +37 -0
  275. package/lib/integrations/code-search.mjs +259 -0
  276. package/lib/integrations/greptile.mjs +148 -0
  277. package/lib/integrations/multimodal.mjs +313 -0
  278. package/lib/integrations/telegram-streaming.mjs +153 -0
  279. package/lib/integrations/tts.mjs +312 -0
  280. package/lib/integrations/twitter-links.mjs +294 -0
  281. package/lib/memory/shared-adapter.mjs +296 -0
  282. package/lib/pipeline/manager.mjs +539 -0
  283. package/lib/preferences/extractor.mjs +347 -0
  284. package/lib/project-dir.mjs +20 -0
  285. package/lib/runtime/config.mjs +388 -0
  286. package/lib/runtime/dlq.mjs +170 -0
  287. package/lib/runtime/log-rotation.mjs +82 -0
  288. package/lib/runtime/logger.mjs +58 -0
  289. package/lib/runtime/memory.mjs +421 -0
  290. package/lib/runtime/paths.mjs +76 -0
  291. package/lib/runtime/project-dir.mjs +127 -0
  292. package/lib/runtime/spending.mjs +204 -0
  293. package/lib/runtime/startup-guard.mjs +291 -0
  294. package/lib/runtime/task-lease.mjs +234 -0
  295. package/lib/runtime/telemetry-schema.mjs +208 -0
  296. package/lib/runtime/telemetry.mjs +101 -0
  297. package/lib/runtime/utils.mjs +64 -0
  298. package/lib/skills/index.mjs +265 -0
  299. package/lib/tools/browser.mjs +135 -0
  300. package/lib/tools/executor.mjs +913 -0
  301. package/lib/types.d.ts +57 -0
  302. package/package.json +106 -0
  303. package/pm-loop.mjs +1626 -0
  304. package/prompts/coder-back.md +27 -0
  305. package/prompts/coder-front.md +27 -0
  306. package/prompts/coder.md +28 -0
  307. package/prompts/copywriter.md +17 -0
  308. package/prompts/fixer.md +39 -0
  309. package/prompts/frontend.md +23 -0
  310. package/prompts/github.md +24 -0
  311. package/prompts/main.md +39 -0
  312. package/prompts/pm-cli.md +95 -0
  313. package/prompts/pm-core.md +121 -0
  314. package/prompts/pm-frontend.md +110 -0
  315. package/prompts/pm.md +234 -0
  316. package/prompts/qa.md +44 -0
  317. package/prompts/security.md +19 -0
  318. package/scripts/build-crew-chat.sh +28 -0
  319. package/scripts/build-llms-full.mjs +52 -0
  320. package/scripts/chatmock-login.sh +16 -0
  321. package/scripts/chatmock-serve.sh +16 -0
  322. package/scripts/check-dashboard.mjs +88 -0
  323. package/scripts/crew-scribe.mjs +326 -0
  324. package/scripts/dashboard-helpers.mjs +391 -0
  325. package/scripts/dashboard-validation.mjs +198 -0
  326. package/scripts/dashboard.mjs +9717 -0
  327. package/scripts/dlq-replay.mjs +61 -0
  328. package/scripts/doctor.mjs +196 -0
  329. package/scripts/file-lock.mjs +186 -0
  330. package/scripts/fresh-machine-smoke.sh +323 -0
  331. package/scripts/generate-changelog.mjs +227 -0
  332. package/scripts/generate-openapi.mjs +334 -0
  333. package/scripts/health-check.mjs +229 -0
  334. package/scripts/install-docker.sh +213 -0
  335. package/scripts/mcp-server.mjs +1625 -0
  336. package/scripts/opencrew-rt-daemon.mjs +568 -0
  337. package/scripts/openswitchctl +646 -0
  338. package/scripts/refactor-configs.mjs +39 -0
  339. package/scripts/release-check.sh +46 -0
  340. package/scripts/resolve-node-bin.sh +25 -0
  341. package/scripts/restart-all-from-repo.sh +329 -0
  342. package/scripts/restart-crew-lead.sh +98 -0
  343. package/scripts/restart-dashboard.sh +104 -0
  344. package/scripts/restart-service.sh +274 -0
  345. package/scripts/run-accessibility-audit.mjs +356 -0
  346. package/scripts/run-integration-bounded.mjs +188 -0
  347. package/scripts/run-scheduled-pipeline.mjs +230 -0
  348. package/scripts/run.mjs +41 -0
  349. package/scripts/scan-skills.mjs +79 -0
  350. package/scripts/setup-firewall.sh +128 -0
  351. package/scripts/smoke-dispatch.mjs +149 -0
  352. package/scripts/smoke.sh +163 -0
  353. package/scripts/start-crew.mjs +328 -0
  354. package/scripts/start.mjs +146 -0
  355. package/scripts/swiftbar-restart-service.sh +19 -0
  356. package/scripts/sync-agents.mjs +152 -0
  357. package/scripts/sync-prompts.mjs +79 -0
  358. package/scripts/validate-config.mjs +337 -0
  359. package/scripts/wow.mjs +89 -0
  360. package/telegram-bridge.mjs +2421 -0
  361. package/unified-orchestrator.mjs +519 -0
  362. package/whatsapp-bridge.mjs +1481 -0
@@ -0,0 +1,2237 @@
1
+ import { getJSON, postJSON } from "../core/api.js";
2
+ import { showNotification, renderStatusBadge } from "../core/dom.js";
3
+ import {
4
+ sortAgents,
5
+ state,
6
+ persistState,
7
+ restoreScrollPosition,
8
+ } from "../core/state.js";
9
+
10
+ let hideAllViews = () => {};
11
+ let setNavActive = () => {};
12
+ let refreshAgents = () => {};
13
+
14
+ export function initAgentsTab(deps = {}) {
15
+ hideAllViews = deps.hideAllViews || hideAllViews;
16
+ setNavActive = deps.setNavActive || setNavActive;
17
+ refreshAgents = deps.refreshAgents || refreshAgents;
18
+ }
19
+
20
+ export function showAgents() {
21
+ hideAllViews();
22
+ document.getElementById("agentsView").classList.add("active");
23
+ setNavActive("navAgents");
24
+ state.activeTab = "agents";
25
+ persistState();
26
+
27
+ // Skip re-fetch if agents are already rendered
28
+ const agentsBox =
29
+ document.getElementById("agentsList") ||
30
+ document.getElementById("agentsView");
31
+ const alreadyLoaded = agentsBox && agentsBox.querySelector(".agent-card");
32
+ if (!alreadyLoaded) {
33
+ loadAgents_cfg();
34
+ } else {
35
+ restoreScrollPosition("agents");
36
+ }
37
+ }
38
+
39
+ // ── Agents UI ──────────────────────────────────────────────────────────────
40
+ let _allModels = [];
41
+ let _modelsByProvider = {}; // { "cerebras": ["llama3.1-8b", ...], ... }
42
+ let _ocModels = [];
43
+ let _engines = [];
44
+
45
+ const ENGINE_ICONS = {
46
+ opencode: "⚑",
47
+ cursor: "πŸ–±",
48
+ claudecode: "πŸ€–",
49
+ "claude-code": "πŸ€–",
50
+ codex: "🟣",
51
+ gemini: "πŸ”΅",
52
+ "gemini-cli": "πŸ”΅",
53
+ "crew-cli": "πŸ”§",
54
+ "docker-sandbox": "🐳",
55
+ direct: "πŸ’¬",
56
+ };
57
+
58
+ const ENGINE_COLORS = {
59
+ opencode: "#22c55e",
60
+ cursor: "#38bdf8",
61
+ claudecode: "#f59e0b",
62
+ "claude-code": "#f59e0b",
63
+ codex: "#a855f7",
64
+ gemini: "#4285f4",
65
+ "gemini-cli": "#4285f4",
66
+ "crew-cli": "#10b981",
67
+ "docker-sandbox": "#0ea5e9",
68
+ direct: "#6366f1",
69
+ };
70
+
71
+ const ELEVENLABS_VOICES = [
72
+ { id: "21m00Tcm4TlvDq8ikWAM", name: "Rachel" },
73
+ { id: "pNInz6obpgDQGcFmaJgB", name: "Adam" },
74
+ { id: "EXAVITQu4vr4xnSDxMaL", name: "Bella" },
75
+ { id: "XB0fDUnXU5powFXDhCwa", name: "Charlotte" },
76
+ ];
77
+
78
+ const GOOGLE_VOICES = [
79
+ { id: "en-US-Neural2-C", name: "US Female C" },
80
+ { id: "en-US-Neural2-A", name: "US Male A" },
81
+ { id: "en-GB-Neural2-A", name: "UK Female A" },
82
+ { id: "en-AU-Neural2-A", name: "AU Female A" },
83
+ ];
84
+
85
+ const KNOWN_VOICES = [...ELEVENLABS_VOICES, ...GOOGLE_VOICES];
86
+
87
+ function getVoicePresetValue(voiceId = "") {
88
+ if (!voiceId) return "";
89
+ return KNOWN_VOICES.some((voice) => voice.id === voiceId) ? voiceId : "custom";
90
+ }
91
+
92
+ function getVoiceDisplayName(voiceId = "") {
93
+ return KNOWN_VOICES.find((voice) => voice.id === voiceId)?.name || voiceId;
94
+ }
95
+
96
+ function buildVoiceOptions(selectedVoiceId = "") {
97
+ const elevenlabs = ELEVENLABS_VOICES.map(
98
+ (voice) =>
99
+ `<option value="${voice.id}" ${selectedVoiceId === voice.id ? "selected" : ""}>${voice.name}</option>`,
100
+ ).join("");
101
+ const google = GOOGLE_VOICES.map(
102
+ (voice) =>
103
+ `<option value="${voice.id}" ${selectedVoiceId === voice.id ? "selected" : ""}>${voice.name}</option>`,
104
+ ).join("");
105
+
106
+ return `
107
+ <option value="" ${!selectedVoiceId ? "selected" : ""}>Use default voice</option>
108
+ <optgroup label="ElevenLabs">${elevenlabs}</optgroup>
109
+ <optgroup label="Google TTS">${google}</optgroup>
110
+ <option value="custom" ${getVoicePresetValue(selectedVoiceId) === "custom" ? "selected" : ""}>Custom voice ID…</option>
111
+ `;
112
+ }
113
+
114
+ // crewswarm gateway-bridge tool definitions
115
+ const CREWSWARM_TOOLS = [
116
+ { id: "write_file", desc: "Write files to disk (@@WRITE_FILE)" },
117
+ { id: "read_file", desc: "Read files from disk (@@READ_FILE)" },
118
+ { id: "mkdir", desc: "Create directories (@@MKDIR)" },
119
+ { id: "run_cmd", desc: "Run whitelisted shell commands (@@RUN_CMD)" },
120
+ { id: "git", desc: "Git & GitHub CLI operations" },
121
+ { id: "web_search", desc: "Web search (Brave Search β€” @@WEB_SEARCH)" },
122
+ { id: "web_fetch", desc: "Fetch URLs (@@WEB_FETCH)" },
123
+ { id: "dispatch", desc: "Dispatch tasks to other agents" },
124
+ { id: "telegram", desc: "Send Telegram messages (@@TELEGRAM)" },
125
+ ];
126
+
127
+ // Role-based tool defaults β€” applied when "Apply role defaults" is clicked
128
+ const AGENT_TOOL_DEFAULTS = {
129
+ "crew-qa": ["read_file"],
130
+ "crew-coder": ["write_file", "read_file", "mkdir", "run_cmd"],
131
+ "crew-coder-front": ["write_file", "read_file", "mkdir", "run_cmd"],
132
+ "crew-coder-back": ["write_file", "read_file", "mkdir", "run_cmd"],
133
+ "crew-frontend": ["write_file", "read_file", "mkdir", "run_cmd"],
134
+ "crew-fixer": ["write_file", "read_file", "mkdir", "run_cmd"],
135
+ "crew-github": ["read_file", "run_cmd", "git"],
136
+ "crew-pm": ["read_file", "dispatch"],
137
+ "crew-main": ["read_file", "write_file", "run_cmd", "dispatch"],
138
+ "crew-security": ["read_file", "run_cmd"],
139
+ "crew-copywriter": ["write_file", "read_file"],
140
+ "crew-telegram": ["telegram", "read_file"],
141
+ "crew-lead": ["dispatch"],
142
+ };
143
+
144
+ function getToolDefaults(agentId) {
145
+ if (AGENT_TOOL_DEFAULTS[agentId]) return AGENT_TOOL_DEFAULTS[agentId];
146
+ // Fuzzy match β€” e.g. crew-coder-3 β†’ coder defaults
147
+ for (const [key, val] of Object.entries(AGENT_TOOL_DEFAULTS)) {
148
+ if (agentId.startsWith(key) || agentId.includes(key.replace("crew-", "")))
149
+ return val;
150
+ }
151
+ return ["read_file", "write_file", "mkdir", "run_cmd"]; // sensible default for unknown roles
152
+ }
153
+
154
+ async function applyToolPreset(agentId) {
155
+ const defaults = getToolDefaults(agentId);
156
+ const container = document.getElementById("tools-" + agentId);
157
+ if (!container) return;
158
+ container.querySelectorAll("input[type=checkbox]").forEach((cb) => {
159
+ cb.checked = defaults.includes(cb.dataset.tool);
160
+ });
161
+ await saveAgentTools(agentId);
162
+ showNotification("Role defaults applied for " + agentId);
163
+ }
164
+
165
+ async function loadAgents_cfg() {
166
+ const list = document.getElementById("agentsList");
167
+ list.innerHTML =
168
+ '<div class="meta" style="padding:20px;">Loading agents…</div>';
169
+ try {
170
+ const data = await getJSON("/api/agents-config");
171
+ _allModels = data.allModels || [];
172
+ _modelsByProvider = data.modelsByProvider || {};
173
+ const agents = sortAgents(data.agents || []);
174
+ if (!agents.length) {
175
+ list.innerHTML =
176
+ '<div class="meta" style="padding:20px;">No agents found in config. Check ~/.crewswarm/crewswarm.json</div>';
177
+ return;
178
+ }
179
+ list.innerHTML = "";
180
+ agents.forEach((a) => {
181
+ const card = document.createElement("div");
182
+ card.className = "agent-card";
183
+ card.id = "agent-card-" + a.id;
184
+ const modelOpts = _allModels
185
+ .map(
186
+ (m) =>
187
+ `<option value="${m}" ${m === a.model ? "selected" : ""}>${m}</option>`,
188
+ )
189
+ .join("");
190
+ const customOpt =
191
+ !a.model || _allModels.includes(a.model)
192
+ ? ""
193
+ : `<option value="${a.model}" selected>${a.model} (custom)</option>`;
194
+ const liveDot = renderStatusBadge(a.liveness, a.ageSec);
195
+ const voiceConfig = a.voice || {};
196
+ const currentVoiceId = voiceConfig.voiceId || voiceConfig.voice || "";
197
+ const customVoiceValue =
198
+ getVoicePresetValue(currentVoiceId) === "custom" ? currentVoiceId : "";
199
+ card.innerHTML = `
200
+ <div class="agent-card-header">
201
+ <div class="agent-avatar" id="avatar-${a.id}" style="position:relative;">${a.emoji}</div>
202
+ <div class="agent-meta">
203
+ <div class="agent-id" style="display:flex;align-items:center;">${liveDot}${a.id} <span class="meta" style="font-weight:400;margin-left:4px;">Β· ${a.name}</span>
204
+ ${MODEL_ROLE[a.id] ? '<span style="font-size:9px;font-weight:700;letter-spacing:0.04em;padding:1px 6px;border-radius:4px;margin-left:8px;' + (ROLE_STYLE[MODEL_ROLE[a.id]] || "") + '">' + MODEL_ROLE[a.id] + "</span>" : ""}
205
+ <span id="coding-dot-${a.id}" style="display:none;margin-left:8px;align-items:center;gap:4px;font-size:11px;color:var(--accent);">
206
+ <span style="display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--accent);animation:pulse 1s ease-in-out infinite;"></span>coding
207
+ </span>
208
+ </div>
209
+ <div id="cur-model-${a.id}" style="margin-top:3px;display:flex;flex-wrap:wrap;align-items:center;gap:6px;">
210
+ <span style="font-size:11px;font-family:'SF Mono',monospace;color:${BROKEN_MODELS.has(a.model) ? "var(--red-hi)" : "var(--text-2)"};" title="Conversation model β€” used for direct replies and chat">
211
+ ${BROKEN_MODELS.has(a.model) ? "⚠ " : "πŸ’¬ "}${a.model || "(none)"}
212
+ </span>
213
+ ${a.useCursorCli ? '<span style="font-size:11px;font-family:monospace;color:var(--purple);" title="Cursor CLI β€” routing tasks through Cursor agent subagents">⚑ cursor</span>' : ""}
214
+ ${a.useClaudeCode ? '<span style="font-size:11px;font-family:monospace;color:var(--green-hi);" title="Claude Code CLI β€” routing tasks through claude -p">πŸ€– claude</span>' : ""}
215
+ ${a.useCodex ? '<span style="font-size:11px;font-family:monospace;color:var(--purple);" title="Codex CLI β€” routing tasks through codex exec">🟣 ' + (a.codexModel || "codex") + "</span>" : ""}
216
+ ${a.useGeminiCli ? '<span style="font-size:11px;font-family:monospace;color:#4285f4;" title="Gemini CLI β€” routing tasks through gemini -p">πŸ”΅ gemini</span>' : ""}
217
+ ${a.useCrewCLI ? '<span style="font-size:11px;font-family:monospace;color:#10b981;" title="Crew CLI β€” routing tasks through crew-cli native agents">πŸ”§ ' + (a.crewCliModel || "crew-cli") + "</span>" : ""}
218
+ ${a.opencodeModel && !a.useCursorCli && !a.useClaudeCode && !a.useCodex && !a.useGeminiCli && !a.useCrewCLI ? '<span style="font-size:11px;font-family:monospace;color:' + (BROKEN_MODELS.has(a.opencodeModel) ? "var(--red-hi)" : "var(--green-hi)") + ';" title="OpenCode model β€” used when routing tasks through OpenCode CLI">⚑ ' + a.opencodeModel + "</span>" : ""}
219
+ ${BROKEN_MODELS.has(a.model) ? '<span style="font-size:10px;font-weight:600;color:var(--red-hi);background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);padding:1px 6px;border-radius:4px;">BROKEN β€” REASSIGN</span>' : ""}
220
+ </div>
221
+ </div>
222
+ <button class="btn-ghost" style="font-size:11px; padding:4px 10px;" data-action="toggleAgentBody" data-arg="${a.id}">Edit β–Ύ</button>
223
+ <button class="btn-ghost" style="font-size:11px; padding:4px 10px; color:var(--red); border-color:rgba(248,113,113,0.3);" data-action="deleteAgent" data-arg="${a.id}">βœ•</button>
224
+ </div>
225
+ <div class="agent-body" id="body-${a.id}" style="display:none;">
226
+ <div>
227
+ <div class="field-label" style="display:flex;align-items:center;gap:8px;">
228
+ <span>πŸ’¬ Conversation Model</span>
229
+ <span style="font-size:10px;font-weight:400;color:var(--text-3);">Used for direct replies, planning, and chat. <strong style="color:var(--text-2);">Not used when OpenCode is enabled.</strong></span>
230
+ </div>
231
+ ${BROKEN_MODELS.has(a.model) ? '<div style="font-size:11px;color:var(--red-hi);background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.25);border-radius:5px;padding:6px 10px;margin-bottom:8px;">⚠ Current model <code>' + a.model + "</code> is broken (returns empty responses). Please reassign.</div>" : ""}
232
+ <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
233
+ <select id="model-${a.id}" style="flex:1; min-width:200px;" onchange="syncModelText('${a.id}')">${customOpt}${modelOpts}</select>
234
+ <input id="modeltext-${a.id}" type="text" placeholder="or type provider/model…" value="${a.model || ""}" style="flex:1; min-width:160px; font-size:12px;" oninput="syncModelSelect('${a.id}')" />
235
+ <button data-action="saveAgentModel" data-arg="${a.id}" class="btn-green" style="white-space:nowrap;">Save</button>
236
+ </div>
237
+ <div style="margin-top:8px; display:flex; gap:8px; align-items:center; flex-wrap:wrap;">
238
+ <span style="font-size:11px;color:var(--text-3);white-space:nowrap;">↩ Fallback:</span>
239
+ ${(() => {
240
+ const fbCustomOpt =
241
+ a.fallbackModel && !_allModels.includes(a.fallbackModel)
242
+ ? `<option value="${a.fallbackModel}" selected>${a.fallbackModel} (custom)</option>`
243
+ : "";
244
+ const fbOpts = _allModels
245
+ .map(
246
+ (m) =>
247
+ `<option value="${m}" ${m === a.fallbackModel ? "selected" : ""}>${m}</option>`,
248
+ )
249
+ .join("");
250
+ return `<select id="fmodel-${a.id}" style="flex:1;min-width:180px;font-size:11px;" onchange="syncFallbackText('${a.id}')"><option value="">β€” none β€”</option>${fbCustomOpt}${fbOpts}</select>`;
251
+ })()}
252
+ <input id="fallback-${a.id}" type="text" placeholder="or type any model…"
253
+ value="${a.fallbackModel || ""}"
254
+ style="flex:1; min-width:140px; font-size:11px; color:var(--text-2);"
255
+ oninput="syncFallbackSelect('${a.id}')" />
256
+ <button data-action="saveAgentFallback" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:11px;">Save</button>
257
+ </div>
258
+ </div>
259
+ <div>
260
+ <div class="field-label">Display name &amp; emoji</div>
261
+ <div style="display:flex; gap:8px;">
262
+ <input id="aname-${a.id}" type="text" value="${a.name}" placeholder="Display name" style="flex:1;" />
263
+ <div class="emoji-picker-wrap">
264
+ <button type="button" class="emoji-btn" id="aemoji-btn-${a.id}" data-action="toggleEmojiPicker" data-arg="${a.id}" title="Pick emoji">${a.emoji || "πŸ€–"}</button>
265
+ <input type="hidden" id="aemoji-${a.id}" value="${a.emoji || "πŸ€–"}" />
266
+ <div class="emoji-picker-panel" id="aemoji-panel-${a.id}">
267
+ <div class="emoji-grid" id="aemoji-grid-${a.id}"></div>
268
+ </div>
269
+ </div>
270
+ <button data-action="saveAgentIdentity" data-arg="${a.id}" class="btn-ghost">Save</button>
271
+ </div>
272
+ <div style="margin-top:8px;">
273
+ <div class="field-label" style="margin-bottom:4px;">Role / Theme <span style="font-weight:400; color:var(--text-3); font-size:11px;">β€” used by PM router to assign tasks (e.g. "iOS/Swift developer (SwiftUI, UIKit)")</span></div>
274
+ <input id="atheme-${a.id}" type="text" value="${a.theme || ""}" placeholder="Describe what this agent specialises in..." style="width:100%;" />
275
+ </div>
276
+ <div style="margin-top:10px; padding:10px; background:var(--surface-2); border:1px solid var(--border); border-radius:8px;">
277
+ <div class="field-label" style="margin-bottom:6px;">Voice / TTS</div>
278
+ <div style="display:flex; gap:8px; flex-wrap:wrap; align-items:center;">
279
+ <select id="voice-provider-${a.id}" style="min-width:140px; flex:0 0 160px;">
280
+ <option value="auto" ${(voiceConfig.provider || "auto") === "auto" ? "selected" : ""}>Auto provider</option>
281
+ <option value="elevenlabs" ${voiceConfig.provider === "elevenlabs" ? "selected" : ""}>ElevenLabs</option>
282
+ <option value="google" ${voiceConfig.provider === "google" ? "selected" : ""}>Google TTS</option>
283
+ </select>
284
+ <select id="voice-preset-${a.id}" style="flex:1; min-width:220px;" onchange="toggleAgentVoiceCustom('${a.id}')">
285
+ ${buildVoiceOptions(currentVoiceId)}
286
+ </select>
287
+ <input id="voice-custom-${a.id}" type="text" placeholder="Custom voice ID or Google voice name"
288
+ value="${customVoiceValue}"
289
+ style="flex:1; min-width:200px; display:${getVoicePresetValue(currentVoiceId) === "custom" ? "block" : "none"};" />
290
+ <button data-action="saveAgentVoice" data-arg="${a.id}" class="btn-ghost">Save voice</button>
291
+ </div>
292
+ <div class="meta" style="margin-top:6px;">Used by Telegram and WhatsApp TTS when voice replies are enabled for the recipient.</div>
293
+ </div>
294
+ </div>
295
+ <div>
296
+ <div style="display:flex; align-items:center; gap:10px; margin-bottom:6px;">
297
+ <div class="field-label" style="margin:0;">System Prompt</div>
298
+ ${!a.systemPrompt ? '<span style="font-size:11px; color:var(--yellow);">⚠ No prompt set β€” agent has no role context</span>' : ""}
299
+ <select style="font-size:11px; padding:3px 8px; margin-left:auto;" onchange="applyAgentPromptPreset('${a.id}', this.value); this.value=''">
300
+ ${buildPresetOptions()}
301
+ </select>
302
+ </div>
303
+ <textarea id="prompt-${a.id}" rows="5" placeholder="Describe this agent's role. It's injected at the top of every task.">${a.systemPrompt || ""}</textarea>
304
+ <div style="margin-top:8px; display:flex; gap:8px;">
305
+ <button data-action="saveAgentPrompt" data-arg="${a.id}" class="btn-ghost">Save prompt</button>
306
+ </div>
307
+ </div>
308
+ <div style="border-top:1px solid var(--border); padding-top:10px;">
309
+ <div class="field-label" style="margin-bottom:8px;">Session</div>
310
+ <div style="display:flex; gap:8px; align-items:center; margin-bottom:12px;">
311
+ <button data-action="resetAgentSession" data-arg="${a.id}" class="btn-ghost" style="font-size:12px;">β†Ί Reset context window</button>
312
+ <span style="font-size:11px; color:var(--text-3);">Clears accumulated token context. Shared memory is re-injected on next task.</span>
313
+ </div>
314
+ </div>
315
+ <div style="border-top:1px solid var(--border); padding-top:10px;">
316
+ <div class="field-label" style="display:flex; align-items:center; gap:8px; margin-bottom:4px;">
317
+ <span>crewswarm β€” Agent Tools</span>
318
+ <span style="font-size:10px; font-weight:600; color:var(--accent); padding:2px 6px; border-radius:4px; background:rgba(56,189,248,0.08); border:1px solid rgba(56,189,248,0.25);">gateway-bridge</span>
319
+ </div>
320
+ <div class="meta" style="margin-bottom:10px; font-size:11px;">Controls which tools this agent can execute on disk and network. Enforced by gateway-bridge on every task β€” only checked tools are active.</div>
321
+ <div id="tools-${a.id}" style="display:grid; grid-template-columns:repeat(auto-fill,minmax(210px,1fr)); gap:6px; margin-bottom:12px;">
322
+ ${CREWSWARM_TOOLS.map(
323
+ (t) => `
324
+ <label style="display:flex; align-items:flex-start; gap:7px; font-size:12px; color:var(--text-2); cursor:pointer; padding:6px 8px; border-radius:5px; border:1px solid var(--border); background:var(--bg-card2);">
325
+ <input type="checkbox" data-tool="${t.id}" ${(a.alsoAllow || []).includes(t.id) ? "checked" : ""} style="accent-color:var(--accent); margin-top:2px; flex-shrink:0;" />
326
+ <div>
327
+ <code style="font-size:11px; color:var(--text-1);">${t.id}</code>
328
+ <div style="font-size:10px; color:var(--text-3); margin-top:2px; line-height:1.3;">${t.desc}</div>
329
+ </div>
330
+ </label>
331
+ `,
332
+ ).join("")}
333
+ </div>
334
+ <div style="display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
335
+ <button data-action="saveAgentTools" data-arg="${a.id}" class="btn-ghost" style="font-size:12px;">Save tools</button>
336
+ <button data-action="applyToolPreset" data-arg="${a.id}" class="btn-ghost" style="font-size:12px; color:var(--text-3);">↩ Role defaults</button>
337
+ </div>
338
+ <div class="meta">Workspace: <code style="font-size:11px;">${a.workspace}</code></div>
339
+ </div>
340
+ <div style="border-top:1px solid var(--border); padding-top:10px;">
341
+ <div class="field-label" style="display:flex; align-items:center; gap:8px; margin-bottom:8px;">
342
+ <span>⚑ Execution Route</span>
343
+ <span style="font-size:10px; font-weight:600; color:var(--text-3); padding:2px 6px; border-radius:4px; background:var(--surface-2);">pick one β€” mutually exclusive</span>
344
+ </div>
345
+ <div id="engine-buttons-${a.id}" style="display:flex; gap:6px; margin-bottom:10px; flex-wrap:wrap;">
346
+ <!-- Dynamically populated from /api/engines -->
347
+ </div>
348
+ <div id="loop-row-${a.id}" style="display:${a.useOpenCode || a.useCursorCli || a.useClaudeCode || a.useCodex || a.useGeminiCli || a.useCrewCLI ? "flex" : "none"}; align-items:center; gap:10px; margin-bottom:10px; padding:8px 10px; background:var(--surface-2); border-radius:8px; border:1px solid var(--border);">
349
+ <label style="display:flex; align-items:center; gap:8px; cursor:pointer; flex:1;">
350
+ <input type="checkbox" id="loop-toggle-${a.id}" ${a.opencodeLoop ? "checked" : ""} onchange="saveAgentLoop('${a.id}')" style="width:14px; height:14px; cursor:pointer;" />
351
+ <span style="font-size:12px; font-weight:600; color:var(--text-1);">πŸ” Ouroboros Loop</span>
352
+ <span style="font-size:11px; color:var(--text-3);">LLM decomposes task β†’ engine runs each step β†’ feeds result back until DONE</span>
353
+ </label>
354
+ <div style="display:flex; align-items:center; gap:6px;">
355
+ <span style="font-size:11px; color:var(--text-3); white-space:nowrap;">Max rounds:</span>
356
+ <input type="number" id="loop-rounds-${a.id}" min="1" max="20" value="${a.opencodeLoopMaxRounds || 10}" class="inp-xs" style="width:52px;text-align:center;" onchange="saveAgentLoop('${a.id}')" />
357
+ </div>
358
+ </div>
359
+ <div id="oc-model-row-${a.id}" style="display:${a.useOpenCode && !a.useCursorCli ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:6px;">
360
+ <select id="oc-model-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncOcModelText('${a.id}')"></select>
361
+ <input id="oc-modeltext-${a.id}" type="text" placeholder="opencode/model…" value="${a.opencodeModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
362
+ <button data-action="saveOpenCodeConfig" data-arg="${a.id}" class="btn-green" style="white-space:nowrap; font-size:12px;">Save</button>
363
+ </div>
364
+ <div id="oc-fallback-row-${a.id}" style="display:${a.useOpenCode && !a.useCursorCli ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
365
+ <span style="font-size:11px; color:var(--text-3); white-space:nowrap;">↩ Fallback:</span>
366
+ <select id="oc-fallback-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncOcFallbackText('${a.id}')"></select>
367
+ <input id="oc-fallback-${a.id}" type="text" placeholder="opencode/model or leave blank" value="${a.opencodeFallbackModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
368
+ <button data-action="saveOpenCodeFallback" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:12px;">Save</button>
369
+ </div>
370
+ <div id="cursor-model-row-${a.id}" style="display:${a.useCursorCli ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
371
+ <select id="cursor-model-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncCursorModelText('${a.id}')"></select>
372
+ <input id="cursor-model-txt-${a.id}" type="text" placeholder="sonnet-4.6 or leave blank for auto" value="${a.cursorCliModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
373
+ <button data-action="saveCursorCliConfig" data-arg="${a.id}" class="btn-sky" style="white-space:nowrap; font-size:12px;">Save</button>
374
+ </div>
375
+ <div id="claudecode-model-row-${a.id}" style="display:${a.useClaudeCode ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
376
+ <select id="claudecode-model-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncClaudeCodeModelText('${a.id}')">
377
+ <option value="">β€” auto (claude-sonnet-4-5) β€”</option>
378
+ <option value="claude-opus-4-5" ${(a.claudeCodeModel || "") === "claude-opus-4-5" ? "selected" : ""}>claude-opus-4-5 β€” best reasoning</option>
379
+ <option value="claude-sonnet-4-5" ${(a.claudeCodeModel || "") === "claude-sonnet-4-5" ? "selected" : ""}>claude-sonnet-4-5 β€” best coding</option>
380
+ <option value="claude-haiku-4-5" ${(a.claudeCodeModel || "") === "claude-haiku-4-5" ? "selected" : ""}>claude-haiku-4-5 β€” fast &amp; cheap</option>
381
+ </select>
382
+ <input id="claudecode-model-txt-${a.id}" type="text" placeholder="claude-sonnet-4-5 or leave blank" value="${a.claudeCodeModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
383
+ <button data-action="saveClaudeCodeConfig" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:12px; color:#f59e0b; border-color:rgba(245,158,11,0.3);">Save</button>
384
+ </div>
385
+ <div id="codex-model-row-${a.id}" style="display:${a.useCodex ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
386
+ <select id="codex-model-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncCodexModelText('${a.id}')">
387
+ <option value="">β€” auto (Codex default) β€”</option>
388
+ <option value="gpt-5.3-codex" ${(a.codexModel || "") === "gpt-5.3-codex" ? "selected" : ""}>gpt-5.3-codex</option>
389
+ <option value="gpt-5.2-codex" ${(a.codexModel || "") === "gpt-5.2-codex" ? "selected" : ""}>gpt-5.2-codex</option>
390
+ <option value="gpt-5.1-codex" ${(a.codexModel || "") === "gpt-5.1-codex" ? "selected" : ""}>gpt-5.1-codex</option>
391
+ <option value="gpt-5.1-codex-mini" ${(a.codexModel || "") === "gpt-5.1-codex-mini" ? "selected" : ""}>gpt-5.1-codex-mini</option>
392
+ <option value="codex-mini" ${(a.codexModel || "") === "codex-mini" ? "selected" : ""}>codex-mini</option>
393
+ </select>
394
+ <input id="codex-model-txt-${a.id}" type="text" placeholder="gpt-5.3-codex or leave blank for auto" value="${a.codexModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
395
+ <button data-action="saveCodexConfig" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:12px; color:#a855f7; border-color:rgba(168,85,247,0.3);">Save</button>
396
+ </div>
397
+ <div id="gemini-model-row-${a.id}" style="display:${a.useGeminiCli ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:10px;">
398
+ <select id="gemini-model-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncGeminiModelText('${a.id}')">
399
+ <option value="">β€” auto (gemini-2.5-flash) β€”</option>
400
+ <option value="gemini-2.5-flash" ${(a.geminiCliModel || "") === "gemini-2.5-flash" ? "selected" : ""}>gemini-2.5-flash β€” fast &amp; cheap</option>
401
+ <option value="gemini-2.5-pro" ${(a.geminiCliModel || "") === "gemini-2.5-pro" ? "selected" : ""}>gemini-2.5-pro β€” best reasoning</option>
402
+ <option value="gemini-2.0-flash" ${(a.geminiCliModel || "") === "gemini-2.0-flash" ? "selected" : ""}>gemini-2.0-flash β€” ultra fast</option>
403
+ </select>
404
+ <input id="gemini-model-txt-${a.id}" type="text" placeholder="gemini-2.5-flash or leave blank for auto" value="${a.geminiCliModel || ""}" style="flex:1; min-width:160px; font-size:12px;" />
405
+ <button data-action="saveGeminiCliConfig" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:12px; color:#4285f4; border-color:rgba(66,133,244,0.3);">Save</button>
406
+ </div>
407
+ <div id="crew-cli-config-row-${a.id}" style="display:${a.useCrewCLI ? "flex" : "none"}; gap:8px; align-items:center; flex-wrap:wrap; padding:10px; background:var(--surface-2); border-radius:8px; border:1px solid var(--border); margin-bottom:10px;">
408
+ <span style="font-size:12px; font-weight:600; color:var(--text-1);">πŸ”§ Crew CLI Mode Active</span>
409
+ <select id="crew-cli-model-sel-${a.id}" style="flex:1; min-width:200px; font-size:12px;" onchange="syncCrewCliModelText('${a.id}')"></select>
410
+ <input id="crew-cli-model-txt-${a.id}" type="text" placeholder="provider/model or leave blank for default" value="${a.crewCliModel || ""}" style="flex:1; min-width:180px; font-size:12px;" />
411
+ <button data-action="saveCrewCLIConfig" data-arg="${a.id}" class="btn-ghost" style="white-space:nowrap; font-size:12px; color:#10b981; border-color:rgba(16,185,129,0.3);">Save</button>
412
+ </div>
413
+ </div>
414
+ <div style="border-top:1px solid var(--border); padding:10px 16px; display:flex; align-items:center; justify-content:space-between; gap:8px;">
415
+ <div style="font-size:11px; color:var(--text-3);">
416
+ Session context accumulates over time. Reset clears the conversation history and re-injects shared memory.
417
+ </div>
418
+ <button data-action="resetAgentSession" data-arg="${a.id}" class="btn-ghost" style="font-size:12px; white-space:nowrap; color:var(--amber); border-color:rgba(245,158,11,0.3);">β†Ί Reset session</button>
419
+ </div>
420
+ </div>
421
+ `;
422
+ list.appendChild(card);
423
+ });
424
+ // Re-populate model selects with grouped optgroups + role-specific recommendations
425
+ agents.forEach((a) => {
426
+ const sel = document.getElementById("model-" + a.id);
427
+ const agentRole = MODEL_ROLE[a.id] || null;
428
+ if (sel) populateModelDropdown("model-" + a.id, a.model, agentRole);
429
+ });
430
+ // Load engines and populate route buttons
431
+ await loadEnginesAndPopulateButtons(agents);
432
+ // Load OpenCode models and populate dropdowns
433
+ loadOcModels().then(() => {
434
+ agents.forEach((a) => {
435
+ populateOcModelDropdown("oc-model-" + a.id, a.opencodeModel || "");
436
+ populateOcModelDropdown(
437
+ "oc-fallback-sel-" + a.id,
438
+ a.opencodeFallbackModel || "",
439
+ );
440
+ populateCursorModelDropdown(
441
+ "cursor-model-sel-" + a.id,
442
+ a.cursorCliModel || "",
443
+ );
444
+ populateGenericModelDropdown(
445
+ "crew-cli-model-sel-" + a.id,
446
+ a.crewCliModel || "",
447
+ );
448
+ });
449
+ });
450
+ } catch (e) {
451
+ list.innerHTML =
452
+ '<div class="meta" style="padding:20px; color:var(--red);">Error: ' +
453
+ e.message +
454
+ "</div>";
455
+ }
456
+ }
457
+
458
+ async function loadEnginesAndPopulateButtons(agents) {
459
+ try {
460
+ const data = await getJSON("/api/engines");
461
+ _engines = data.engines || [];
462
+
463
+ agents.forEach((a) => {
464
+ const container = document.getElementById(`engine-buttons-${a.id}`);
465
+ if (!container) return;
466
+
467
+ // Clear existing buttons before re-populating
468
+ container.innerHTML = "";
469
+
470
+ // Check which engine is active for this agent
471
+ const activeEngine = a.useOpenCode
472
+ ? "opencode"
473
+ : a.useCursorCli
474
+ ? "cursor"
475
+ : a.useClaudeCode
476
+ ? "claude-code"
477
+ : a.useCodex
478
+ ? "codex"
479
+ : a.useGeminiCli
480
+ ? "gemini-cli"
481
+ : a.useCrewCLI
482
+ ? "crew-cli"
483
+ : "direct";
484
+
485
+ // Direct API button (always available)
486
+ const directBtn = createEngineButton(
487
+ a.id,
488
+ "direct",
489
+ "πŸ’¬ Direct API",
490
+ activeEngine === "direct",
491
+ "#6366f1",
492
+ );
493
+ container.appendChild(directBtn);
494
+
495
+ // Dynamic engine buttons from JSON
496
+ _engines
497
+ .filter((e) => e.ready) // Only show installed engines
498
+ .sort((a, b) => String(a.label || a.id).localeCompare(String(b.label || b.id)))
499
+ .forEach((eng) => {
500
+ const icon = ENGINE_ICONS[eng.id] || "πŸ”§";
501
+ const color = ENGINE_COLORS[eng.id] || eng.color || "#6b7280";
502
+ const label = `${icon} ${eng.label}`;
503
+ const hint = eng.installUrl ? "(ready)" : "";
504
+ const btn = createEngineButton(
505
+ a.id,
506
+ eng.id,
507
+ label,
508
+ activeEngine === eng.id,
509
+ color,
510
+ hint,
511
+ );
512
+ container.appendChild(btn);
513
+ });
514
+ });
515
+ } catch (err) {
516
+ console.error("Failed to load engines:", err);
517
+ }
518
+ }
519
+
520
+ function createEngineButton(
521
+ agentId,
522
+ engineId,
523
+ label,
524
+ isActive,
525
+ color,
526
+ hint = "",
527
+ ) {
528
+ const btn = document.createElement("button");
529
+ btn.id = `route-${engineId}-${agentId}`;
530
+ btn.dataset.action = "setRoute";
531
+ btn.dataset.arg = agentId;
532
+ btn.dataset.arg2 = engineId;
533
+
534
+ const borderColor = isActive ? color : "var(--border)";
535
+ const bgColor = isActive ? `${color}20` : "var(--surface-2)";
536
+ const textColor = isActive ? color : "var(--text-2)";
537
+
538
+ btn.style.cssText = `
539
+ font-size:11px; font-weight:600; padding:5px 12px; border-radius:6px; cursor:pointer;
540
+ border:1px solid ${borderColor}; background:${bgColor}; color:${textColor};
541
+ `;
542
+
543
+ btn.innerHTML =
544
+ label +
545
+ (hint
546
+ ? ` <span style="font-size:10px; font-weight:400; opacity:0.7;">${hint}</span>`
547
+ : "");
548
+
549
+ return btn;
550
+ }
551
+
552
+ function toggleAgentBody(id) {
553
+ const body = document.getElementById("body-" + id);
554
+ body.style.display = body.style.display === "none" ? "grid" : "none";
555
+ }
556
+
557
+ async function resetAgentSessionRT(agentId) {
558
+ if (
559
+ !confirm(
560
+ "Reset session for " +
561
+ agentId +
562
+ "?\\n\\nThis clears accumulated conversation context. Shared memory (memory/*.md) is preserved and re-injected on next task.",
563
+ )
564
+ )
565
+ return;
566
+ try {
567
+ const r = await postJSON("/api/agents/reset-session", { agentId });
568
+ if (r.ok) {
569
+ showNotification("Session reset for " + agentId);
570
+ } else {
571
+ showNotification("Reset failed: " + (r.error || "unknown"), true);
572
+ }
573
+ } catch (e) {
574
+ showNotification("Reset error: " + e.message, true);
575
+ }
576
+ }
577
+
578
+ async function deleteAgent(agentId) {
579
+ if (!confirm('Delete agent "' + agentId + '"? This cannot be undone.'))
580
+ return;
581
+ // Remove card from DOM instantly so it feels immediate
582
+ const card = document.getElementById("agent-card-" + agentId);
583
+ if (card) card.style.opacity = "0.3";
584
+ try {
585
+ await postJSON("/api/agents-config/delete", { agentId });
586
+ if (card) card.remove();
587
+ showNotification("Agent " + agentId + " deleted");
588
+ await loadAgents_cfg();
589
+ } catch (e) {
590
+ if (card) card.style.opacity = "1";
591
+ showNotification("Delete failed: " + e.message, true);
592
+ }
593
+ }
594
+
595
+ function syncModelText(agentId) {
596
+ const sel = document.getElementById("model-" + agentId);
597
+ const txt = document.getElementById("modeltext-" + agentId);
598
+ if (txt) txt.value = sel.value;
599
+ }
600
+ function syncModelSelect(agentId) {
601
+ const txt = document.getElementById("modeltext-" + agentId);
602
+ const sel = document.getElementById("model-" + agentId);
603
+ if (!sel) return;
604
+ const typed = txt.value.trim();
605
+ const match = [...sel.options].find((o) => o.value === typed);
606
+ sel.value = match ? typed : "";
607
+ }
608
+ function syncFallbackText(agentId) {
609
+ const sel = document.getElementById("fmodel-" + agentId);
610
+ const txt = document.getElementById("fallback-" + agentId);
611
+ if (txt) txt.value = sel.value;
612
+ }
613
+ function syncFallbackSelect(agentId) {
614
+ const txt = document.getElementById("fallback-" + agentId);
615
+ const sel = document.getElementById("fmodel-" + agentId);
616
+ if (!sel) return;
617
+ const typed = txt.value.trim();
618
+ const match = [...sel.options].find((o) => o.value === typed);
619
+ sel.value = match ? typed : "";
620
+ }
621
+ // Expose sync helpers globally β€” onchange="" attributes in dynamic HTML need window scope
622
+ window.syncModelText = syncModelText;
623
+ window.syncModelSelect = syncModelSelect;
624
+ window.syncFallbackText = syncFallbackText;
625
+ window.syncFallbackSelect = syncFallbackSelect;
626
+ window.toggleAgentVoiceCustom = function (agentId) {
627
+ const preset = document.getElementById("voice-preset-" + agentId);
628
+ const custom = document.getElementById("voice-custom-" + agentId);
629
+ if (!preset || !custom) return;
630
+ custom.style.display = preset.value === "custom" ? "block" : "none";
631
+ };
632
+
633
+ async function saveAgentVoice(agentId) {
634
+ const provider = document.getElementById("voice-provider-" + agentId)?.value || "auto";
635
+ const preset = document.getElementById("voice-preset-" + agentId)?.value || "";
636
+ const custom = document.getElementById("voice-custom-" + agentId)?.value.trim() || "";
637
+ const selectedVoiceId = preset === "custom" ? custom : preset;
638
+
639
+ const voice = selectedVoiceId
640
+ ? {
641
+ provider,
642
+ voiceId: selectedVoiceId,
643
+ voice: selectedVoiceId,
644
+ name: getVoiceDisplayName(selectedVoiceId),
645
+ }
646
+ : null;
647
+
648
+ try {
649
+ await postJSON("/api/agents-config/update", { agentId, voice });
650
+ showNotification(
651
+ voice
652
+ ? `Voice saved for ${agentId}`
653
+ : `Voice reset to default for ${agentId}`,
654
+ );
655
+ } catch (e) {
656
+ showNotification("Failed: " + e.message, true);
657
+ }
658
+ }
659
+
660
+ async function resetAgentSession(agentId) {
661
+ if (
662
+ !confirm(
663
+ "Reset context window for " +
664
+ agentId +
665
+ "?\\n\\nThis clears the agent's accumulated conversation history. Shared memory files will be re-injected on the next task.",
666
+ )
667
+ )
668
+ return;
669
+ showNotification("Resetting " + agentId + " session...");
670
+ try {
671
+ await postJSON("/api/agents-config/reset-session", { agentId });
672
+ showNotification(agentId + " session reset");
673
+ } catch (e) {
674
+ showNotification("Reset failed: " + e.message, true);
675
+ }
676
+ }
677
+
678
+ function refreshModelHeader(agentId, model, opencodeModel) {
679
+ const el = document.getElementById("cur-model-" + agentId);
680
+ if (!el) return;
681
+ const chatBroken = BROKEN_MODELS.has(model);
682
+ const ocBroken = opencodeModel && BROKEN_MODELS.has(opencodeModel);
683
+ el.innerHTML =
684
+ `<span style="font-size:11px;font-family:'SF Mono',monospace;color:${chatBroken ? "var(--red-hi)" : "var(--text-2)"};" title="Conversation model">${chatBroken ? "⚠ " : "πŸ’¬ "}${model || "(none)"}</span>` +
685
+ (opencodeModel
686
+ ? `<span style="font-size:11px;font-family:'SF Mono',monospace;color:${ocBroken ? "var(--red-hi)" : "var(--green-hi)"};" title="OpenCode model">⚑ ${opencodeModel}</span>`
687
+ : "") +
688
+ (chatBroken
689
+ ? `<span style="font-size:10px;font-weight:600;color:var(--red-hi);background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);padding:1px 6px;border-radius:4px;">BROKEN β€” REASSIGN</span>`
690
+ : "");
691
+ }
692
+
693
+ async function saveAgentModel(agentId) {
694
+ const txt = document.getElementById("modeltext-" + agentId);
695
+ const sel = document.getElementById("model-" + agentId);
696
+ const model = (txt && txt.value.trim()) || (sel && sel.value) || "";
697
+ if (!model) {
698
+ showNotification("Select or type a model", true);
699
+ return;
700
+ }
701
+ if (BROKEN_MODELS.has(model)) {
702
+ showNotification(
703
+ "⚠ That model returns empty responses β€” choose another",
704
+ true,
705
+ );
706
+ return;
707
+ }
708
+ try {
709
+ await postJSON("/api/agents-config/update", { agentId, model });
710
+ const ocModel =
711
+ document.getElementById("oc-modeltext-" + agentId)?.value.trim() || "";
712
+ refreshModelHeader(agentId, model, ocModel);
713
+ showNotification(`${agentId} β†’ ${model}`);
714
+ } catch (e) {
715
+ showNotification("Failed: " + e.message, true);
716
+ }
717
+ }
718
+
719
+ async function saveAgentFallback(agentId) {
720
+ const inp = document.getElementById("fallback-" + agentId);
721
+ const fallbackModel = inp?.value.trim() || "";
722
+ try {
723
+ await postJSON("/api/agents-config/update", { agentId, fallbackModel });
724
+ showNotification(
725
+ fallbackModel
726
+ ? `Fallback set: ${fallbackModel}`
727
+ : `Fallback cleared for ${agentId}`,
728
+ );
729
+ } catch (e) {
730
+ showNotification("Failed: " + e.message, true);
731
+ }
732
+ }
733
+
734
+ // ── OpenCode per-agent config ───────────────────────────────────────────────
735
+ let _ocModelsCache = null;
736
+
737
+ async function loadOcModels() {
738
+ if (_ocModelsCache) return _ocModelsCache;
739
+ try {
740
+ const r = await fetch("/api/opencode-models");
741
+ const d = await r.json();
742
+ _ocModelsCache = Array.isArray(d.models) ? d.models : [];
743
+ } catch {
744
+ _ocModelsCache = [];
745
+ }
746
+ return _ocModelsCache;
747
+ }
748
+
749
+ const OC_MODEL_LABELS = {
750
+ "opencode/big-pickle": "Big Pickle (Stealth)",
751
+ "opencode/trinity-large-preview-free": "Trinity Large Preview (Stealth)",
752
+ "opencode/gpt-5": "GPT 5",
753
+ "opencode/gpt-5-codex": "GPT 5 Codex",
754
+ "opencode/gpt-5-nano": "GPT 5 Nano",
755
+ "opencode/gpt-5.1": "GPT 5.1",
756
+ "opencode/gpt-5.1-codex": "GPT 5.1 Codex",
757
+ "opencode/gpt-5.1-codex-max": "GPT 5.1 Codex Max",
758
+ "opencode/gpt-5.1-codex-mini": "GPT 5.1 Codex Mini",
759
+ "opencode/gpt-5.2": "GPT 5.2",
760
+ "opencode/gpt-5.2-codex": "GPT 5.2 Codex",
761
+ "opencode/alpha-gpt-5.3-codex": "GPT 5.3 Codex (alpha)",
762
+ "opencode/alpha-gpt-5.4": "GPT 5.4 (alpha)",
763
+ "opencode/claude-sonnet-4": "Claude Sonnet 4",
764
+ "opencode/claude-sonnet-4-5": "Claude Sonnet 4.5",
765
+ "opencode/claude-sonnet-4-6": "Claude Sonnet 4.6",
766
+ "opencode/claude-opus-4-1": "Claude Opus 4.1",
767
+ "opencode/claude-opus-4-5": "Claude Opus 4.5",
768
+ "opencode/claude-opus-4-6": "Claude Opus 4.6",
769
+ "opencode/claude-haiku-4-5": "Claude Haiku 4.5",
770
+ "opencode/claude-3-5-haiku": "Claude 3.5 Haiku",
771
+ "opencode/gemini-3-flash": "Gemini 3 Flash",
772
+ "opencode/gemini-3-pro": "Gemini 3 Pro",
773
+ "opencode/gemini-3.1-pro": "Gemini 3.1 Pro",
774
+ "opencode/kimi-k2": "Kimi K2",
775
+ "opencode/kimi-k2-thinking": "Kimi K2 Thinking",
776
+ "opencode/kimi-k2.5": "Kimi K2.5",
777
+ "opencode/kimi-k2.5-free": "Kimi K2.5 Free",
778
+ "opencode/glm-4.6": "GLM 4.6 (Z.ai)",
779
+ "opencode/glm-4.7": "GLM 4.7 (Z.ai)",
780
+ "opencode/glm-5": "GLM 5 (Z.ai)",
781
+ "opencode/glm-5-free": "GLM 5 Free (Z.ai)",
782
+ "opencode/minimax-m2.1": "MiniMax M2.1",
783
+ "opencode/minimax-m2.1-free": "MiniMax M2.1 Free",
784
+ "opencode/minimax-m2.5": "MiniMax M2.5",
785
+ "opencode/minimax-m2.5-free": "MiniMax M2.5 Free",
786
+ };
787
+
788
+ function populateOcModelDropdown(selectId, currentVal) {
789
+ const sel = document.getElementById(selectId);
790
+ if (!sel) return;
791
+ sel.innerHTML = '<option value="">β€” select model β€”</option>';
792
+
793
+ // Merge OpenCode server models + all provider models so Groq/xAI/etc all appear
794
+ const ocModels = (_ocModelsCache || []).map((m) =>
795
+ typeof m === "string"
796
+ ? m
797
+ : m.provider
798
+ ? m.provider + "/" + m.id
799
+ : m.id || m.name || String(m),
800
+ );
801
+ const allCombined = [...new Set([...ocModels, ...(_allModels || [])])].filter(
802
+ Boolean,
803
+ );
804
+
805
+ const grouped = {};
806
+ allCombined.forEach((full) => {
807
+ const provider = full.includes("/") ? full.split("/")[0] : "other";
808
+ if (!grouped[provider]) grouped[provider] = [];
809
+ grouped[provider].push(full);
810
+ });
811
+
812
+ for (const [provider, ids] of Object.entries(grouped)) {
813
+ const grp = document.createElement("optgroup");
814
+ grp.label = provider.toUpperCase();
815
+ ids.forEach((full) => {
816
+ const opt = document.createElement("option");
817
+ opt.value = full;
818
+ opt.textContent = OC_MODEL_LABELS[full] || full;
819
+ if (full === currentVal) opt.selected = true;
820
+ grp.appendChild(opt);
821
+ });
822
+ sel.appendChild(grp);
823
+ }
824
+
825
+ if (currentVal && !sel.value) {
826
+ const opt = document.createElement("option");
827
+ opt.value = currentVal;
828
+ opt.textContent = (OC_MODEL_LABELS[currentVal] || currentVal) + " (custom)";
829
+ opt.selected = true;
830
+ sel.prepend(opt);
831
+ }
832
+ }
833
+
834
+ // Cursor CLI subscription models (populated from agent models command)
835
+ const CURSOR_CLI_MODELS = [
836
+ { id: "", label: "β€” auto (subscription default) β€”" },
837
+ {
838
+ id: "sonnet-4.5-thinking",
839
+ label: "Claude 4.5 Sonnet (Thinking) β€” default",
840
+ },
841
+ {
842
+ id: "opus-4.6-thinking",
843
+ label: "Claude 4.6 Opus (Thinking) β€” best reasoning",
844
+ },
845
+ { id: "opus-4.6", label: "Claude 4.6 Opus" },
846
+ { id: "sonnet-4.6-thinking", label: "Claude 4.6 Sonnet (Thinking)" },
847
+ { id: "sonnet-4.6", label: "Claude 4.6 Sonnet β€” best coding" },
848
+ { id: "sonnet-4.5", label: "Claude 4.5 Sonnet" },
849
+ { id: "gpt-5.3-codex-xhigh", label: "GPT-5.3 Codex XHigh" },
850
+ { id: "gpt-5.3-codex-high", label: "GPT-5.3 Codex High" },
851
+ { id: "gpt-5.3-codex", label: "GPT-5.3 Codex" },
852
+ { id: "gpt-5.3-codex-fast", label: "GPT-5.3 Codex Fast" },
853
+ { id: "gpt-5.2", label: "GPT-5.2" },
854
+ { id: "gemini-3.1-pro", label: "Gemini 3.1 Pro" },
855
+ { id: "gemini-3-flash", label: "Gemini 3 Flash" },
856
+ { id: "grok", label: "Grok" },
857
+ { id: "kimi-k2.5", label: "Kimi K2.5" },
858
+ ];
859
+
860
+ function populateCursorModelDropdown(selId, currentVal) {
861
+ const sel = document.getElementById(selId);
862
+ if (!sel) return;
863
+ sel.innerHTML = CURSOR_CLI_MODELS.map(
864
+ (m) =>
865
+ '<option value="' +
866
+ m.id +
867
+ '"' +
868
+ (m.id === (currentVal || "") ? " selected" : "") +
869
+ ">" +
870
+ m.label +
871
+ "</option>",
872
+ ).join("");
873
+ }
874
+
875
+ function syncCursorModelText(agentId) {
876
+ const sel = document.getElementById("cursor-model-sel-" + agentId);
877
+ const txt = document.getElementById("cursor-model-txt-" + agentId);
878
+ if (sel && txt) txt.value = sel.value;
879
+ }
880
+ window.syncCursorModelText = syncCursorModelText;
881
+
882
+ function populateGenericModelDropdown(selectId, currentVal) {
883
+ const sel = document.getElementById(selectId);
884
+ if (!sel) return;
885
+ sel.innerHTML = '<option value="">β€” auto / default β€”</option>';
886
+
887
+ const grouped = {};
888
+ (_allModels || []).forEach((full) => {
889
+ const provider = full.includes("/") ? full.split("/")[0] : "other";
890
+ if (!grouped[provider]) grouped[provider] = [];
891
+ grouped[provider].push(full);
892
+ });
893
+
894
+ for (const [provider, ids] of Object.entries(grouped)) {
895
+ const grp = document.createElement("optgroup");
896
+ grp.label = provider.toUpperCase();
897
+ ids.forEach((full) => {
898
+ const opt = document.createElement("option");
899
+ opt.value = full;
900
+ opt.textContent = full;
901
+ if (full === currentVal) opt.selected = true;
902
+ grp.appendChild(opt);
903
+ });
904
+ sel.appendChild(grp);
905
+ }
906
+
907
+ if (currentVal && !sel.value) {
908
+ const opt = document.createElement("option");
909
+ opt.value = currentVal;
910
+ opt.textContent = currentVal + " (custom)";
911
+ opt.selected = true;
912
+ sel.prepend(opt);
913
+ }
914
+ }
915
+
916
+ // route toggle β€” mutually exclusive
917
+ async function setRoute(agentId, route) {
918
+ // Map engine IDs to agent config keys
919
+ const ENGINE_CONFIG_MAP = {
920
+ direct: {},
921
+ opencode: { useOpenCode: true },
922
+ cursor: { useCursorCli: true },
923
+ "claude-code": { useClaudeCode: true },
924
+ claudecode: { useClaudeCode: true },
925
+ codex: { useCodex: true },
926
+ "gemini-cli": { useGeminiCli: true },
927
+ gemini: { useGeminiCli: true },
928
+ "crew-cli": { useCrewCLI: true },
929
+ };
930
+
931
+ // Get the config update for this engine
932
+ const configUpdate = ENGINE_CONFIG_MAP[route] || {};
933
+
934
+ // Reset all engine flags
935
+ const payload = {
936
+ agentId,
937
+ useOpenCode: false,
938
+ useCursorCli: false,
939
+ useClaudeCode: false,
940
+ useCodex: false,
941
+ useGeminiCli: false,
942
+ useCrewCLI: false,
943
+ ...configUpdate,
944
+ };
945
+
946
+ // Update all button styles dynamically
947
+ const container = document.getElementById(`engine-buttons-${agentId}`);
948
+ if (container) {
949
+ const allButtons = container.querySelectorAll("button");
950
+ allButtons.forEach((btn) => {
951
+ const btnRoute = btn.dataset.arg2;
952
+ const eng = _engines.find((e) => e.id === btnRoute) || {};
953
+ const color = ENGINE_COLORS[btnRoute] || eng.color || "#6b7280";
954
+ const isActive = btnRoute === route;
955
+
956
+ btn.style.borderColor = isActive ? color : "var(--border)";
957
+ btn.style.background = isActive ? `${color}20` : "var(--surface-2)";
958
+ btn.style.color = isActive ? color : "var(--text-2)";
959
+ });
960
+ }
961
+
962
+ // Show/hide model configuration rows
963
+ const anyEngine = route !== "direct";
964
+ const ocRow = document.getElementById("oc-model-row-" + agentId);
965
+ const ocFbRow = document.getElementById("oc-fallback-row-" + agentId);
966
+ const cursorRow = document.getElementById("cursor-model-row-" + agentId);
967
+ const ccRow = document.getElementById("claudecode-model-row-" + agentId);
968
+ const codexRow = document.getElementById("codex-model-row-" + agentId);
969
+ const geminiRow = document.getElementById("gemini-model-row-" + agentId);
970
+ const crewCliRow = document.getElementById("crew-cli-config-row-" + agentId);
971
+ const loopRow = document.getElementById("loop-row-" + agentId);
972
+
973
+ if (ocRow) ocRow.style.display = route === "opencode" ? "flex" : "none";
974
+ if (ocFbRow) ocFbRow.style.display = route === "opencode" ? "flex" : "none";
975
+ if (cursorRow) cursorRow.style.display = route === "cursor" ? "flex" : "none";
976
+ if (ccRow)
977
+ ccRow.style.display =
978
+ route === "claude-code" || route === "claudecode" ? "flex" : "none";
979
+ if (codexRow) codexRow.style.display = route === "codex" ? "flex" : "none";
980
+ if (geminiRow)
981
+ geminiRow.style.display =
982
+ route === "gemini-cli" || route === "gemini" ? "flex" : "none";
983
+ if (crewCliRow)
984
+ crewCliRow.style.display = route === "crew-cli" ? "flex" : "none";
985
+ if (loopRow) loopRow.style.display = anyEngine ? "flex" : "none";
986
+
987
+ // Save to backend
988
+ try {
989
+ await postJSON("/api/agents-config/update", payload);
990
+ const eng = _engines.find((e) => e.id === route) || { label: route };
991
+ showNotification(`${agentId} β†’ ${eng.label || route}`);
992
+ } catch (e) {
993
+ showNotification("Failed: " + e.message, true);
994
+ }
995
+ }
996
+
997
+ async function saveCursorCliConfig(agentId) {
998
+ const cursorCliModel = (
999
+ document.getElementById("cursor-model-txt-" + agentId)?.value || ""
1000
+ ).trim();
1001
+ try {
1002
+ await postJSON("/api/agents-config/update", { agentId, cursorCliModel });
1003
+ showNotification(agentId + " Cursor model β†’ " + (cursorCliModel || "auto"));
1004
+ } catch (e) {
1005
+ showNotification("Failed: " + e.message, true);
1006
+ }
1007
+ }
1008
+
1009
+ async function saveClaudeCodeConfig(agentId) {
1010
+ const claudeCodeModel = (
1011
+ document.getElementById("claudecode-model-txt-" + agentId)?.value || ""
1012
+ ).trim();
1013
+ try {
1014
+ await postJSON("/api/agents-config/update", { agentId, claudeCodeModel });
1015
+ showNotification(
1016
+ agentId + " Claude Code model β†’ " + (claudeCodeModel || "auto"),
1017
+ );
1018
+ } catch (e) {
1019
+ showNotification("Failed: " + e.message, true);
1020
+ }
1021
+ }
1022
+
1023
+ function syncCodexModelText(agentId) {
1024
+ const sel = document.getElementById("codex-model-sel-" + agentId);
1025
+ const txt = document.getElementById("codex-model-txt-" + agentId);
1026
+ if (sel && txt) txt.value = sel.value;
1027
+ }
1028
+ window.syncCodexModelText = syncCodexModelText;
1029
+
1030
+ async function saveCodexConfig(agentId) {
1031
+ const codexModel = (
1032
+ document.getElementById("codex-model-txt-" + agentId)?.value || ""
1033
+ ).trim();
1034
+ try {
1035
+ await postJSON("/api/agents-config/update", { agentId, codexModel });
1036
+ showNotification(agentId + " Codex model β†’ " + (codexModel || "auto"));
1037
+ } catch (e) {
1038
+ showNotification("Failed: " + e.message, true);
1039
+ }
1040
+ }
1041
+
1042
+ function syncGeminiModelText(agentId) {
1043
+ const sel = document.getElementById("gemini-model-sel-" + agentId);
1044
+ const txt = document.getElementById("gemini-model-txt-" + agentId);
1045
+ if (sel && txt) txt.value = sel.value;
1046
+ }
1047
+ window.syncGeminiModelText = syncGeminiModelText;
1048
+
1049
+ async function saveGeminiCliConfig(agentId) {
1050
+ const geminiCliModel = (
1051
+ document.getElementById("gemini-model-txt-" + agentId)?.value || ""
1052
+ ).trim();
1053
+ try {
1054
+ await postJSON("/api/agents-config/update", { agentId, geminiCliModel });
1055
+ showNotification(agentId + " Gemini model β†’ " + (geminiCliModel || "auto"));
1056
+ } catch (e) {
1057
+ showNotification("Failed: " + e.message, true);
1058
+ }
1059
+ }
1060
+
1061
+ async function saveCrewCLIConfig(agentId) {
1062
+ const crewCliModel = (
1063
+ document.getElementById("crew-cli-model-txt-" + agentId)?.value || ""
1064
+ ).trim();
1065
+ try {
1066
+ await postJSON("/api/agents-config/update", {
1067
+ agentId,
1068
+ useCrewCLI: true,
1069
+ crewCliModel,
1070
+ });
1071
+ showNotification(
1072
+ agentId + " Crew CLI model β†’ " + (crewCliModel || "default"),
1073
+ );
1074
+ } catch (e) {
1075
+ showNotification("Failed: " + e.message, true);
1076
+ }
1077
+ }
1078
+
1079
+ function syncCrewCliModelText(agentId) {
1080
+ const sel = document.getElementById("crew-cli-model-sel-" + agentId);
1081
+ const txt = document.getElementById("crew-cli-model-txt-" + agentId);
1082
+ if (sel && txt) txt.value = sel.value;
1083
+ }
1084
+ window.syncCrewCliModelText = syncCrewCliModelText;
1085
+
1086
+ async function saveAgentLoop(agentId) {
1087
+ const enabled =
1088
+ document.getElementById("loop-toggle-" + agentId)?.checked ?? false;
1089
+ const maxRoundsRaw = document.getElementById("loop-rounds-" + agentId)?.value;
1090
+ const opencodeLoopMaxRounds = Math.min(
1091
+ 20,
1092
+ Math.max(1, parseInt(maxRoundsRaw || "10", 10)),
1093
+ );
1094
+ try {
1095
+ await postJSON("/api/agents-config/update", {
1096
+ agentId,
1097
+ opencodeLoop: enabled,
1098
+ opencodeLoopMaxRounds,
1099
+ });
1100
+ showNotification(
1101
+ agentId +
1102
+ " loop " +
1103
+ (enabled ? `ON (${opencodeLoopMaxRounds} rounds max)` : "OFF"),
1104
+ );
1105
+ } catch (e) {
1106
+ showNotification("Failed: " + e.message, true);
1107
+ }
1108
+ }
1109
+ window.saveAgentLoop = saveAgentLoop;
1110
+
1111
+ function syncClaudeCodeModelText(agentId) {
1112
+ const sel = document.getElementById("claudecode-model-sel-" + agentId);
1113
+ const txt = document.getElementById("claudecode-model-txt-" + agentId);
1114
+ if (sel && txt) txt.value = sel.value;
1115
+ }
1116
+ window.syncClaudeCodeModelText = syncClaudeCodeModelText;
1117
+
1118
+ function toggleOpenCodeUI(agentId) {
1119
+ // Legacy β€” kept for any stale references; use setRoute instead
1120
+ const checked = document.getElementById("oc-toggle-" + agentId)?.checked;
1121
+ if (checked !== undefined) setRoute(agentId, checked ? "opencode" : "direct");
1122
+ }
1123
+
1124
+ function syncOcModelText(agentId) {
1125
+ const sel = document.getElementById("oc-model-" + agentId);
1126
+ const txt = document.getElementById("oc-modeltext-" + agentId);
1127
+ if (sel && txt && sel.value) txt.value = sel.value;
1128
+ }
1129
+ window.syncOcModelText = syncOcModelText;
1130
+
1131
+ function syncOcFallbackText(agentId) {
1132
+ const sel = document.getElementById("oc-fallback-sel-" + agentId);
1133
+ const txt = document.getElementById("oc-fallback-" + agentId);
1134
+ if (sel && txt && sel.value) txt.value = sel.value;
1135
+ }
1136
+
1137
+ async function saveOpenCodeFallback(agentId) {
1138
+ const opencodeFallbackModel = (
1139
+ document.getElementById("oc-fallback-" + agentId)?.value || ""
1140
+ ).trim();
1141
+ try {
1142
+ await postJSON("/api/agents-config/update", {
1143
+ agentId,
1144
+ opencodeFallbackModel,
1145
+ });
1146
+ showNotification(
1147
+ opencodeFallbackModel
1148
+ ? agentId + " OC fallback β†’ " + opencodeFallbackModel
1149
+ : "OC fallback cleared for " + agentId,
1150
+ );
1151
+ } catch (e) {
1152
+ showNotification("Failed: " + e.message, true);
1153
+ }
1154
+ }
1155
+
1156
+ async function saveOpenCodeConfig(agentId) {
1157
+ // Only saves the opencodeModel β€” route (useOpenCode flag) is set by the route buttons via setRoute().
1158
+ // Reading the old oc-toggle checkbox here was a bug: the checkbox no longer exists, causing it
1159
+ // to always send useOpenCode:false and toast "β†’ direct LLM" even when OpenCode route was active.
1160
+ const opencodeModel = (
1161
+ document.getElementById("oc-modeltext-" + agentId)?.value || ""
1162
+ ).trim();
1163
+ try {
1164
+ await postJSON("/api/agents-config/update", { agentId, opencodeModel });
1165
+ const chatModel =
1166
+ document.getElementById("modeltext-" + agentId)?.value.trim() || "";
1167
+ refreshModelHeader(agentId, chatModel, opencodeModel);
1168
+ showNotification(agentId + " OC model β†’ " + (opencodeModel || "default"));
1169
+ } catch (e) {
1170
+ showNotification("Failed: " + e.message, true);
1171
+ }
1172
+ }
1173
+
1174
+ async function saveCursorCliToggle(agentId) {
1175
+ // Legacy shim β€” delegates to setRoute
1176
+ const useCursorCli =
1177
+ document.getElementById("cursor-cli-toggle-" + agentId)?.checked || false;
1178
+ await setRoute(agentId, useCursorCli ? "cursor" : "direct");
1179
+ }
1180
+
1181
+ // Bulk route setter β€” apply a route to all coding agents at once
1182
+ async function bulkSetRoute(route, model) {
1183
+ // All agents that write code or docs (have write_file/mkdir access)
1184
+ const CODING_AGENTS = [
1185
+ "crew-coder",
1186
+ "crew-coder-front",
1187
+ "crew-coder-back",
1188
+ "crew-frontend",
1189
+ "crew-fixer",
1190
+ "crew-architect",
1191
+ "crew-ml",
1192
+ "crew-copywriter",
1193
+ "crew-main",
1194
+ "crew-pm",
1195
+ "crew-mega",
1196
+ "crew-lead",
1197
+ ];
1198
+
1199
+ // Get engine info from API
1200
+ const enginesData = await getJSON("/api/engines").catch(() => ({
1201
+ engines: [],
1202
+ }));
1203
+ const engine = enginesData.engines.find((e) => e.id === route);
1204
+ const label = engine
1205
+ ? engine.label
1206
+ : route === "direct"
1207
+ ? "Direct API"
1208
+ : route;
1209
+
1210
+ showNotification("Applying " + label + " to all coding agents…");
1211
+ for (const agentId of CODING_AGENTS) {
1212
+ try {
1213
+ const payload = {
1214
+ agentId,
1215
+ useOpenCode: false,
1216
+ useCursorCli: false,
1217
+ useClaudeCode: false,
1218
+ useCodex: false,
1219
+ useGeminiCli: false,
1220
+ useCrewCLI: false,
1221
+ useDockerSandbox: false,
1222
+ };
1223
+
1224
+ // Set the selected route flag to true
1225
+ if (route === "opencode") payload.useOpenCode = true;
1226
+ else if (route === "cursor") payload.useCursorCli = true;
1227
+ else if (route === "claudecode" || route === "claude-code")
1228
+ payload.useClaudeCode = true;
1229
+ else if (route === "codex") payload.useCodex = true;
1230
+ else if (route === "gemini" || route === "gemini-cli")
1231
+ payload.useGeminiCli = true;
1232
+ else if (route === "crew-cli") payload.useCrewCLI = true;
1233
+ else if (route === "docker-sandbox") payload.useDockerSandbox = true;
1234
+
1235
+ if (model && route === "cursor") payload.cursorCliModel = model;
1236
+ if (model && route === "opencode") payload.opencodeModel = model;
1237
+ if (model && route === "claudecode") payload.claudeCodeModel = model;
1238
+ if (model && route === "codex") payload.codexModel = model;
1239
+ if (model && route === "gemini") payload.geminiCliModel = model;
1240
+ if (model && route === "crew-cli") payload.crewCliModel = model;
1241
+ await postJSON("/api/agents-config/update", payload);
1242
+ // Small delay to prevent rapid-fire saves that create backup storms
1243
+ await new Promise(r => setTimeout(r, 50));
1244
+ } catch (e) {
1245
+ console.error("bulkSetRoute failed for", agentId, e.message);
1246
+ }
1247
+ }
1248
+ showNotification(
1249
+ "Done β€” " +
1250
+ CODING_AGENTS.length +
1251
+ " agents set to " +
1252
+ label +
1253
+ (model ? " (" + model + ")" : ""),
1254
+ );
1255
+ // Reload config to refresh UI with new routes
1256
+ await loadAgents_cfg();
1257
+ }
1258
+
1259
+ const AGENT_EMOJIS = [
1260
+ "πŸ€–",
1261
+ "🧠",
1262
+ "⚑",
1263
+ "πŸ”₯",
1264
+ "🎯",
1265
+ "πŸ›‘οΈ",
1266
+ "πŸ”§",
1267
+ "πŸ›",
1268
+ "πŸ”¬",
1269
+ "πŸ“‹",
1270
+ "✍️",
1271
+ "πŸ™",
1272
+ "🎨",
1273
+ "πŸ–₯️",
1274
+ "πŸ“±",
1275
+ "πŸ”’",
1276
+ "πŸ“Š",
1277
+ "πŸš€",
1278
+ "πŸ’‘",
1279
+ "🌐",
1280
+ "βš™οΈ",
1281
+ "🦊",
1282
+ "🦾",
1283
+ "πŸ’»",
1284
+ "πŸ—οΈ",
1285
+ "πŸ”",
1286
+ "πŸ“",
1287
+ "πŸ’¬",
1288
+ "πŸ§ͺ",
1289
+ "🎭",
1290
+ ];
1291
+
1292
+ function toggleEmojiPicker(agentId) {
1293
+ const panel = document.getElementById("aemoji-panel-" + agentId);
1294
+ const grid = document.getElementById("aemoji-grid-" + agentId);
1295
+ const isOpen = panel.classList.contains("open");
1296
+ document
1297
+ .querySelectorAll(".emoji-picker-panel.open")
1298
+ .forEach((p) => p.classList.remove("open"));
1299
+ if (isOpen) return;
1300
+ if (!grid.hasChildNodes()) {
1301
+ grid.innerHTML = AGENT_EMOJIS.map(
1302
+ (e) =>
1303
+ '<div class="emoji-opt" data-agent="' +
1304
+ agentId +
1305
+ '" data-emoji="' +
1306
+ e +
1307
+ '" title="' +
1308
+ e +
1309
+ '">' +
1310
+ e +
1311
+ "</div>",
1312
+ ).join("");
1313
+ grid.addEventListener("click", function (ev) {
1314
+ const opt = ev.target.closest(".emoji-opt");
1315
+ if (opt) selectEmoji(opt.dataset.agent, opt.dataset.emoji);
1316
+ });
1317
+ }
1318
+ panel.classList.add("open");
1319
+ }
1320
+
1321
+ function selectEmoji(agentId, emoji) {
1322
+ const isNew = agentId === "__new__";
1323
+ const inputEl = isNew
1324
+ ? document.getElementById("naEmoji")
1325
+ : document.getElementById("aemoji-" + agentId);
1326
+ const btnEl = isNew
1327
+ ? document.getElementById("naEmoji-btn")
1328
+ : document.getElementById("aemoji-btn-" + agentId);
1329
+ if (inputEl) inputEl.value = emoji;
1330
+ if (btnEl) btnEl.textContent = emoji;
1331
+ document.getElementById("aemoji-panel-" + agentId).classList.remove("open");
1332
+ }
1333
+
1334
+ // close picker when clicking outside
1335
+ document.addEventListener("click", (e) => {
1336
+ if (!e.target.closest(".emoji-picker-wrap")) {
1337
+ document
1338
+ .querySelectorAll(".emoji-picker-panel.open")
1339
+ .forEach((p) => p.classList.remove("open"));
1340
+ }
1341
+ });
1342
+
1343
+ async function saveAgentIdentity(agentId) {
1344
+ const name = document.getElementById("aname-" + agentId).value.trim();
1345
+ const emoji = document.getElementById("aemoji-" + agentId).value.trim();
1346
+ const theme = document.getElementById("atheme-" + agentId)?.value.trim();
1347
+ try {
1348
+ await postJSON("/api/agents-config/update", {
1349
+ agentId,
1350
+ name,
1351
+ emoji,
1352
+ theme,
1353
+ });
1354
+ showNotification("Identity saved for " + agentId);
1355
+ } catch (e) {
1356
+ showNotification("Failed: " + e.message, true);
1357
+ }
1358
+ }
1359
+
1360
+ window.applyAgentPromptPreset = function (agentId, preset) {
1361
+ if (!preset || !PROMPT_PRESETS[preset]) return;
1362
+ const ta = document.getElementById("prompt-" + agentId);
1363
+ if (ta) ta.value = PROMPT_PRESETS[preset];
1364
+ // Auto-fill the theme/role field with the preset's display name (strip leading emoji + whitespace)
1365
+ const themeEl = document.getElementById("atheme-" + agentId);
1366
+ if (themeEl) {
1367
+ const opt = PRESET_OPTIONS.find((p) => p.value === preset);
1368
+ if (opt)
1369
+ themeEl.value = opt.label
1370
+ .replace(
1371
+ /^[\u{1F000}-\u{1FFFF}\u2600-\u27BF\uFE0F\u20D0-\u20FF\s]+/u,
1372
+ "",
1373
+ )
1374
+ .trim();
1375
+ }
1376
+ };
1377
+
1378
+ async function saveAgentPrompt(agentId) {
1379
+ const systemPrompt = document.getElementById("prompt-" + agentId).value;
1380
+ try {
1381
+ await postJSON("/api/agents-config/update", { agentId, systemPrompt });
1382
+ showNotification("Prompt saved for " + agentId);
1383
+ } catch (e) {
1384
+ showNotification("Failed: " + e.message, true);
1385
+ }
1386
+ }
1387
+
1388
+ async function startCrew() {
1389
+ try {
1390
+ showNotification("Starting crew bridge daemons…");
1391
+ const r = await postJSON("/api/crew/start", {});
1392
+ showNotification(r.message || "Crew started");
1393
+ } catch (e) {
1394
+ showNotification("Crew start failed: " + e.message, true);
1395
+ }
1396
+ }
1397
+
1398
+ const NEW_AGENT_TOOL_PRESETS = {
1399
+ coder: ["write_file", "read_file", "mkdir", "run_cmd"], // frontend, backend, fullstack, ios, android, data, aiml, api, db, rn, web3, automation, fixer
1400
+ writer: ["write_file", "read_file"], // copywriter, docs, design (no shell exec)
1401
+ reviewer: ["read_file"], // qa, strict read-only audit
1402
+ security: ["read_file", "run_cmd"], // security auditor β€” run scanners but never write
1403
+ orchestrator: ["read_file", "dispatch"], // pm, planner β€” routes tasks but doesn't write files
1404
+ coordinator: ["write_file", "read_file", "run_cmd", "dispatch"], // main/lead β€” full access + dispatch, no git
1405
+ devops: ["read_file", "run_cmd", "git"], // devops, github ops
1406
+ comms: ["telegram", "read_file"], // telegram notification agent
1407
+ };
1408
+
1409
+ export function applyNewAgentToolPreset() {
1410
+ const preset = document.getElementById("naToolPreset").value;
1411
+ if (!preset || !NEW_AGENT_TOOL_PRESETS[preset]) return;
1412
+ const allowed = NEW_AGENT_TOOL_PRESETS[preset];
1413
+ document.querySelectorAll(".naToolCheck").forEach((cb) => {
1414
+ cb.checked = allowed.includes(cb.dataset.tool);
1415
+ });
1416
+ }
1417
+
1418
+ async function saveAgentTools(agentId) {
1419
+ const container = document.getElementById("tools-" + agentId);
1420
+ const checked = [
1421
+ ...container.querySelectorAll("input[type=checkbox]:checked"),
1422
+ ].map((el) => el.dataset.tool);
1423
+ try {
1424
+ await postJSON("/api/agents-config/update", {
1425
+ agentId,
1426
+ alsoAllow: checked,
1427
+ });
1428
+ showNotification("Tools saved for " + agentId);
1429
+ } catch (e) {
1430
+ showNotification("Failed: " + e.message, true);
1431
+ }
1432
+ }
1433
+
1434
+ // Single source of truth for all preset options β€” used by both new-agent form and edit cards
1435
+ const PRESET_OPTIONS = [
1436
+ { value: "frontend", label: "🎨 Frontend (HTML/CSS/JS)" },
1437
+ { value: "backend", label: "βš™οΈ Backend (Node/API/scripts)" },
1438
+ { value: "fullstack", label: "🧱 Full-stack coder" },
1439
+ { value: "ios", label: "πŸ“± iOS / Swift developer" },
1440
+ { value: "android", label: "πŸ€– Android / Kotlin developer" },
1441
+ { value: "devops", label: "πŸ”§ DevOps / Infrastructure" },
1442
+ { value: "data", label: "πŸ“Š Data / Analytics / Python" },
1443
+ { value: "security", label: "πŸ›‘οΈ Security auditor" },
1444
+ { value: "qa", label: "πŸ§ͺ QA / tester" },
1445
+ { value: "github", label: "πŸ™ Git & GitHub ops" },
1446
+ { value: "writer", label: "✍️ Content / copywriter" },
1447
+ { value: "design", label: "πŸ–ŒοΈ UI/UX designer" },
1448
+ { value: "pm", label: "πŸ“‹ Product manager / planner" },
1449
+ { value: "aiml", label: "πŸ€– AI / ML engineer" },
1450
+ { value: "api", label: "πŸ”Œ API designer (REST/GraphQL)" },
1451
+ { value: "database", label: "πŸ—„οΈ Database specialist" },
1452
+ { value: "reactnative", label: "πŸ“± React Native (cross-platform)" },
1453
+ { value: "web3", label: "🌐 Web3 / Blockchain (Solidity)" },
1454
+ { value: "automation", label: "πŸ•·οΈ Automation / scraping" },
1455
+ { value: "docs", label: "πŸ“– Technical docs writer" },
1456
+ { value: "orchestrator", label: "🧠 Orchestrator / PM loop" },
1457
+ { value: "lead", label: "🦊 Team lead / coordinator" },
1458
+ { value: "main", label: "⚑ Main agent (general)" },
1459
+ ];
1460
+ function buildPresetOptions(placeholder) {
1461
+ var ph = placeholder || "Presets\u2026";
1462
+ var opts = PRESET_OPTIONS.map(function (p) {
1463
+ return '<option value="' + p.value + '">' + p.label + "</option>";
1464
+ }).join("");
1465
+ return '<option value="">' + ph + "</option>" + opts;
1466
+ }
1467
+
1468
+ const PROMPT_PRESETS = {
1469
+ frontend: `Frontend implementation specialist. Apple/Linear/Vercel-level polish is the baseline.
1470
+
1471
+ ## Design standard
1472
+ - Typography: system font stack or Inter. 16-18px body, 1.5 line-height. Weight hierarchy (400/500/600/700).
1473
+ - Spacing: 8px grid. Section padding 48-96px. Let content breathe.
1474
+ - Color: muted neutrals + one accent. Dark mode via CSS custom properties. No pure black (#000).
1475
+ - Motion: 200-300ms ease-out. Fade + translateY for reveals. Respect prefers-reduced-motion.
1476
+ - Layout: mobile-first, CSS Grid + Flexbox, max-width 1200px. Full-bleed hero sections.
1477
+ - Components: rounded corners (8-12px), soft layered shadows, no hard borders.
1478
+ - Accessibility: semantic HTML, focus-visible, 4.5:1 contrast, aria-labels.
1479
+
1480
+ ## Research β€” use these sources
1481
+ - @@WEB_FETCH https://developer.apple.com/design/human-interface-guidelines for Apple HIG
1482
+ - @@WEB_SEARCH site:uiverse.io [component] for copy-pasteable HTML/CSS examples (7000+ free)
1483
+ - @@WEB_SEARCH site:css-tricks.com [technique] for CSS guides
1484
+ - @@WEB_SEARCH awwwards [page type] OR onepagelove [page type] for design inspiration
1485
+ - @@WEB_FETCH https://developer.mozilla.org/en-US/docs/Web/CSS/[property] for CSS reference
1486
+ - @@WEB_SEARCH site:codepen.io [component] vanilla CSS for interactive examples
1487
+
1488
+ ## Rules
1489
+ - ALWAYS read existing files before editing. Match the design system in place.
1490
+ - If no design system exists, establish CSS custom properties (--color-*, --space-*, --radius-*).
1491
+ - Test mental model: 375px, 768px, 1440px β€” all three must look intentional.`,
1492
+
1493
+ backend: `Backend specialist. Node.js, APIs, databases, server logic.
1494
+
1495
+ ## Standards
1496
+ - ES modules, async/await, no callbacks. Prefer native Node APIs over dependencies.
1497
+ - Every endpoint: input validation, error handling, proper HTTP status codes, structured JSON responses.
1498
+ - Database: parameterized queries only (never string interpolation), connection pooling, transactions for multi-step writes.
1499
+ - Auth: bcrypt/argon2 for passwords, JWT with short expiry + refresh tokens. Never plaintext.
1500
+ - Logging: structured (JSON), include request ID, timestamp, level.
1501
+ - Config via env vars, never hardcoded secrets. Validate required env vars at startup.
1502
+ - @@WEB_SEARCH for library APIs and docs when using packages you haven't used recently.
1503
+
1504
+ ## Rules
1505
+ - ALWAYS read existing files before editing. Match patterns and naming.
1506
+ - Think about failures: what happens when the request fails, DB is down, or input is malformed?`,
1507
+
1508
+ fullstack: `Full-stack coding specialist. Clean, readable code across the entire stack.
1509
+
1510
+ ## Standards
1511
+ - Small functions, clear names, no dead code. Error handling everywhere.
1512
+ - ES modules (import/export), async/await. Match existing code patterns.
1513
+ - Frontend: semantic HTML, accessible, responsive. Backend: validate inputs, handle errors, proper status codes.
1514
+ - @@WEB_SEARCH for API docs and library usage when using unfamiliar packages.
1515
+
1516
+ ## Rules
1517
+ - ALWAYS read existing files before editing β€” understand what exists.
1518
+ - Surgical edits only β€” change what's asked, nothing else.
1519
+ - Trace the happy path and one error path mentally before reporting done.`,
1520
+
1521
+ qa: `QA specialist. Systematic audits backed by evidence from the actual code.
1522
+
1523
+ ## Process
1524
+ 1. @@READ_FILE every file you audit β€” no exceptions
1525
+ 2. Check against: error handling, input validation, edge cases, security, performance, correctness
1526
+ 3. Report ONLY issues you can point to in the actual code with real line numbers
1527
+
1528
+ ## Output format
1529
+ ### CRITICAL β€” Line N: [issue] β†’ Fix: [exact code]
1530
+ ### HIGH β€” Line N: [issue] β†’ Fix: [exact code]
1531
+ ### MEDIUM / LOW
1532
+ ### Summary: X issues. Verdict: PASS / PASS WITH WARNINGS / FAIL
1533
+
1534
+ ## Rules
1535
+ - Do NOT invent line numbers. Only cite what you read.
1536
+ - CRITICAL issues = FAIL verdict. No exceptions.
1537
+ - You are NOT a coordinator β€” do NOT use @@DISPATCH.
1538
+ - @@WEB_SEARCH best practices or known vulnerability patterns when unsure.`,
1539
+
1540
+ github: `Git and GitHub specialist.
1541
+
1542
+ ## Before any operation
1543
+ - git status, git config user.name, git config user.email
1544
+ - For PRs: gh auth status
1545
+
1546
+ ## Commit standard
1547
+ - Conventional commits: feat(scope):, fix(scope):, chore:, docs:, refactor:, test:
1548
+ - Subject ≀72 chars. Body explains WHY, not what.
1549
+ - Stage specific files β€” never git add -A unless asked.
1550
+ - Never commit: .env, *.pem, *credentials*, API keys.
1551
+
1552
+ ## Rules
1553
+ - Never force-push to main or master.
1554
+ - Always git diff --stat before committing.
1555
+ - One logical change per commit.`,
1556
+
1557
+ writer: `Content and copywriting specialist.
1558
+
1559
+ ## Voice
1560
+ - Clear, confident, human. Short sentences. Active voice. Cut every word that doesn't earn its place.
1561
+ - Headlines: benefit-first, specific, no jargon. "Ship 10x faster" beats "Leverage AI-powered solutions."
1562
+ - No buzzwords: leverage, synergy, cutting-edge, revolutionary, seamless, robust.
1563
+ - No filler: "In today's fast-paced world..." β€” delete it.
1564
+ - Numbers > adjectives. "3 agents, 12 seconds" beats "multiple agents, incredibly fast."
1565
+
1566
+ ## Research β€” mandatory
1567
+ - @@WEB_SEARCH competitors, market positioning, and facts BEFORE writing. Never invent claims.
1568
+ - @@WEB_FETCH reference sites for tone/style inspiration.
1569
+
1570
+ ## Rules
1571
+ - ALWAYS @@WRITE_FILE your output β€” never just show text in chat.
1572
+ - Read existing content first to match voice. After draft, cut 30%.`,
1573
+
1574
+ ios: `iOS/Swift specialist. SwiftUI, UIKit, and native Apple platform code.
1575
+
1576
+ ## Standards
1577
+ - SwiftUI for new views unless the project uses UIKit exclusively.
1578
+ - Swift naming: camelCase vars, PascalCase types. async/await over completion handlers.
1579
+ - Use @MainActor for UI updates. Structured concurrency with TaskGroup when appropriate.
1580
+ - Follow MVVM with ObservableObject/Observable. Keep views thin.
1581
+ - @@WEB_SEARCH Apple developer docs and WWDC sessions for current APIs.
1582
+
1583
+ ## Rules
1584
+ - ALWAYS read existing Swift files before editing.
1585
+ - Handle optionals safely β€” guard let / if let, never force-unwrap in production.
1586
+ - Support Dynamic Type and VoiceOver accessibility.`,
1587
+
1588
+ android: `Android/Kotlin specialist. Jetpack Compose, Android SDK, and modern Android architecture.
1589
+
1590
+ ## Standards
1591
+ - Jetpack Compose for new UI unless the project uses XML layouts.
1592
+ - Architecture: MVVM with ViewModel, StateFlow/SharedFlow, Hilt for DI.
1593
+ - Coroutines and Flow for async. Structured concurrency with viewModelScope.
1594
+ - Follow Material 3 design guidelines.
1595
+ - @@WEB_SEARCH Android developer docs for current API patterns and Compose components.
1596
+
1597
+ ## Rules
1598
+ - ALWAYS read existing files before editing. Match architecture patterns.
1599
+ - Handle configuration changes properly. Test on multiple screen sizes.`,
1600
+
1601
+ devops: `DevOps and infrastructure specialist. CI/CD, Docker, shell scripts, IaC.
1602
+
1603
+ ## Standards
1604
+ - Idempotent scripts β€” safe to run multiple times.
1605
+ - Dockerfiles: multi-stage builds, non-root user, minimal base images, .dockerignore.
1606
+ - CI/CD: fail fast, cache dependencies, pin action versions.
1607
+ - IaC: Terraform state management, modular configs, no hardcoded values.
1608
+ - @@WEB_SEARCH current best practices for tools and cloud services.
1609
+
1610
+ ## Rules
1611
+ - ALWAYS read existing configs before editing. Never blindly overwrite deployment configs.
1612
+ - Secrets in env vars or secret managers, never in source.
1613
+ - Write clear inline comments in all scripts and configs.`,
1614
+
1615
+ data: `Data and analytics specialist. Python, SQL, pandas, data pipelines.
1616
+
1617
+ ## Standards
1618
+ - Clean Python with type hints and docstrings. Validate inputs, handle nulls explicitly.
1619
+ - pandas/polars for transformation, matplotlib/plotly for visualization.
1620
+ - SQL: parameterized queries, CTEs for readability, explain plans for optimization.
1621
+ - @@WEB_SEARCH for library APIs, dataset documentation, and statistical methods.
1622
+
1623
+ ## Rules
1624
+ - ALWAYS read existing data files and schemas before writing code.
1625
+ - NEVER overwrite raw data. Transform into new files/tables.
1626
+ - Reproducibility: set random seeds, log parameters, version datasets.`,
1627
+
1628
+ security: `Security auditor. OWASP-aware, evidence-based.
1629
+
1630
+ ## Audit checklist
1631
+ - Secrets: hardcoded API keys/tokens/passwords, .env in source, secrets in logs or client code
1632
+ - Injection: SQL string concat, unescaped user input (XSS), user input in exec/spawn, path traversal
1633
+ - Auth: missing auth on protected routes, broken sessions, privilege escalation, CORS misconfiguration
1634
+ - Data: plaintext passwords, sensitive data in URLs, missing rate limiting, no input validation
1635
+ - @@WEB_SEARCH to verify if a pattern is actually exploitable when unsure
1636
+
1637
+ ## Rules
1638
+ - @@READ_FILE every file before reporting. Never guess.
1639
+ - Report only β€” NEVER modify files.
1640
+ - Output: severity + file:line + vulnerability + exact remediation.
1641
+ - Overall risk: CRITICAL / HIGH / MODERATE / LOW.`,
1642
+
1643
+ design: `UI/UX design and implementation specialist. You ship premium, production-ready interfaces.
1644
+
1645
+ ## Design DNA β€” Apple.com, Linear.app, Vercel.com, Stripe.com level quality.
1646
+ - Reduction: remove every element that doesn't serve the user's goal. White space is a feature.
1647
+ - Typography: Inter or system stack. Scale 14/16/20/28/40/56px. Weight 400/500/600/700. Line-height 1.5 body, 1.2 display.
1648
+ - Color: neutrals (gray-50β†’950) + one accent. Dark mode first via custom properties. No pure #000.
1649
+ - Spacing: 8px grid. Sections 64-96px vertical pad. Cards 24-32px. CSS gap everywhere.
1650
+ - Shadows: layered β€” sm (0 1px 2px), md (0 4px 16px), lg (0 12px 48px). rgba(0,0,0,0.06-0.12).
1651
+ - Motion: 200ms ease-out on interactive elements. Fade + translateY(8px) for reveals. Skeleton screens over spinners.
1652
+ - Layout: mobile-first (640/768/1024/1280). Max-width 1200px. CSS Grid pages, Flexbox components.
1653
+
1654
+ ## Research β€” use these sources
1655
+ - @@WEB_FETCH https://developer.apple.com/design/human-interface-guidelines for Apple HIG
1656
+ - @@WEB_SEARCH site:uiverse.io [component] for copy-pasteable HTML/CSS examples (7000+ free)
1657
+ - @@WEB_SEARCH site:css-tricks.com [technique] for CSS technique guides
1658
+ - @@WEB_SEARCH awwwards [page type] OR onepagelove [page type] for design inspiration
1659
+ - @@WEB_SEARCH site:codepen.io [component] vanilla CSS for interactive examples
1660
+
1661
+ ## Rules
1662
+ - Accessible: focus-visible, aria-labels, 4.5:1 contrast, semantic HTML.`,
1663
+
1664
+ pm: `Product manager and project planner. Task decomposition and roadmap management.
1665
+
1666
+ ## Planning principles
1667
+ - Every task: independently deliverable. If it can't be tested alone, split it.
1668
+ - Imperative form: "Create X", "Add Y to Z", "Fix W in file F". Never "Improve" or "Look into."
1669
+ - Each task β†’ one agent, one file path, one deliverable.
1670
+ - Include acceptance criteria: what does done look like? What should the agent verify?
1671
+ - Task size: completable in 1-2 minutes of LLM work. Bigger = split.
1672
+
1673
+ ## Anti-patterns
1674
+ - "Improve the landing page" β†’ too vague. Which section? What's wrong?
1675
+ - "Set up the backend" β†’ too broad. Which endpoint? What data? What auth?
1676
+ - Tasks without file paths β†’ agent won't know where to work.
1677
+
1678
+ ## Rules
1679
+ - Flag missing requirements before handoff.
1680
+ - @@WEB_SEARCH to research approaches for unfamiliar features.
1681
+ - Update ROADMAP.md with [ ] checkboxes.`,
1682
+
1683
+ aiml: `AI/ML engineering specialist. Model training, fine-tuning, eval, and MLOps.
1684
+
1685
+ ## Standards
1686
+ - Reproducibility: set random seeds, log all hyperparameters, version datasets.
1687
+ - Data: validate schema before training. Check for nulls, duplicates, class imbalance.
1688
+ - Training: early stopping, gradient clipping, learning rate scheduling.
1689
+ - Evaluation: never eval on training data. Hold out test set. Report confidence intervals.
1690
+ - Code: type hints, docstrings on public APIs, structured logging.
1691
+
1692
+ ## Research β€” critical for ML
1693
+ - @@WEB_SEARCH for model cards, API docs, library versions before implementation.
1694
+ - @@WEB_FETCH HuggingFace docs, paper abstracts, or API references.
1695
+ - @@WEB_SEARCH "[library] breaking changes" when using specific versions.
1696
+
1697
+ ## Rules
1698
+ - ALWAYS read existing code before modifying. Pin dependency versions.
1699
+ - Never hardcode paths to datasets or models β€” use env vars or config.`,
1700
+
1701
+ api: `API design specialist. REST and GraphQL APIs.
1702
+
1703
+ ## Standards
1704
+ - OpenAPI/Swagger specs for all new endpoints. Schema-first design.
1705
+ - REST: correct HTTP verbs (GET=read, POST=create, PUT=replace, PATCH=update, DELETE=remove).
1706
+ - Status codes: 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 422 Unprocessable, 429 Rate Limited, 500 Server Error.
1707
+ - Consistent naming: plural nouns for resources (/users, /orders), kebab-case.
1708
+ - Pagination: cursor-based for large datasets. Include total count and next/prev links.
1709
+ - Versioning: URL prefix (/v1/) or Accept header.
1710
+ - @@WEB_SEARCH site:swagger.io/docs [topic] for OpenAPI spec reference.
1711
+ - @@WEB_FETCH https://developer.mozilla.org/en-US/docs/Web/HTTP/Status for status codes.
1712
+
1713
+ ## Rules
1714
+ - ALWAYS read existing routes and schemas before adding new ones. Match patterns.
1715
+ - Output both the spec and a working implementation stub.`,
1716
+
1717
+ database: `Database specialist. SQL, migrations, indexes, and query optimization.
1718
+
1719
+ ## Standards
1720
+ - Idempotent migrations (safe to re-run). Use IF NOT EXISTS / IF EXISTS guards.
1721
+ - Indexes: all foreign keys, frequently queried columns, composite indexes for common WHERE+ORDER BY.
1722
+ - Naming: snake_case tables, singular (user not users). FK: target_table_id. Index: idx_table_column.
1723
+ - Always explain query plans for optimization changes.
1724
+ - @@WEB_SEARCH site:use-the-index-luke.com [topic] for SQL indexing best practices.
1725
+ - @@WEB_SEARCH [database engine] documentation [topic] for engine-specific syntax.
1726
+
1727
+ ## Rules
1728
+ - ALWAYS read existing schema before writing migrations.
1729
+ - NEVER drop columns or tables without explicit instruction.
1730
+ - Transactions for multi-table changes. Rollback strategy for every migration.`,
1731
+
1732
+ reactnative: `React Native specialist. Cross-platform mobile with Expo or bare RN.
1733
+
1734
+ ## Standards
1735
+ - Functional components with hooks. StyleSheet.create for all styles.
1736
+ - Navigation: React Navigation with typed routes. Deep linking support.
1737
+ - State: Zustand or React Query for server state. Context sparingly.
1738
+ - Platform differences: Platform.select, Platform.OS checks, platform-specific files (.ios.tsx/.android.tsx).
1739
+ - @@WEB_SEARCH React Native docs and Expo SDK for current APIs.
1740
+
1741
+ ## Rules
1742
+ - ALWAYS read existing components and navigation before editing.
1743
+ - Test mental model on both iOS and Android.
1744
+ - Handle safe areas, keyboard avoidance, and different screen sizes.`,
1745
+
1746
+ web3: `Web3 and blockchain specialist. Solidity smart contracts and dApp frontends.
1747
+
1748
+ ## Standards
1749
+ - Storage layout: NEVER change variable order in upgradeable contracts.
1750
+ - NatSpec comments on all public and external functions.
1751
+ - OpenZeppelin for standard patterns (ERC20, ERC721, AccessControl, Ownable).
1752
+ - Gas optimization: pack storage vars, use calldata over memory for read-only, avoid loops over unbounded arrays.
1753
+ - @@WEB_SEARCH site:docs.openzeppelin.com [pattern] for audited contract implementations.
1754
+ - @@WEB_SEARCH EIP-[number] for Ethereum standard specifications.
1755
+
1756
+ ## Rules
1757
+ - ALWAYS read existing contracts before editing.
1758
+ - Test all contracts with Hardhat or Foundry before reporting done.
1759
+ - Check: reentrancy guards, integer overflow (Solidity 0.8+ safe), access control on state-changing functions.`,
1760
+
1761
+ automation: `Automation and web scraping specialist. Playwright, Puppeteer, Python scrapers.
1762
+
1763
+ ## Standards
1764
+ - Playwright for JS-heavy sites, requests+BeautifulSoup for static HTML.
1765
+ - Always check for APIs first (@@WEB_SEARCH) β€” scraping is the fallback, not the default.
1766
+ - Handle: pagination, login flows, dynamic content, CAPTCHAs (flag, don't bypass).
1767
+ - Retry logic with exponential backoff for flaky requests.
1768
+ - @@WEB_FETCH to read a page before deciding the scraping approach.
1769
+
1770
+ ## Rules
1771
+ - Store raw data before transforming β€” never lose the source.
1772
+ - Respect robots.txt and rate-limit requests (1-2 req/sec default).
1773
+ - Output structured data (JSON/CSV) with clear field names.`,
1774
+
1775
+ docs: `Technical documentation writer. API docs, READMEs, developer guides.
1776
+
1777
+ ## Standards
1778
+ - Write for the reader β€” assume minimal context, include working examples.
1779
+ - Structure: Overview β†’ Installation β†’ Quick Start β†’ Usage β†’ API Reference β†’ Examples β†’ Troubleshooting.
1780
+ - Code examples must be copy-pasteable and actually work.
1781
+ - @@WEB_SEARCH for prior art, best practices, or similar docs for reference.
1782
+ - @@WEB_FETCH specific doc pages before paraphrasing or referencing.
1783
+
1784
+ ## Rules
1785
+ - ALWAYS read the code you're documenting before writing.
1786
+ - Keep docs in sync with implementation β€” flag discrepancies.
1787
+ - Markdown output unless another format is requested.
1788
+ - No fluff paragraphs. Scannable: headers, bullets, code blocks.`,
1789
+
1790
+ orchestrator: `PM loop orchestrator. Roadmap reading, task expansion, specialist routing.
1791
+
1792
+ ## Standards
1793
+ - Break each roadmap item into a single, scoped, actionable task.
1794
+ - Include exact file paths and acceptance criteria in every task.
1795
+ - Route to the right specialist based on work type.
1796
+ - @@WEB_SEARCH to research approaches for unfamiliar features.
1797
+
1798
+ ## Rules
1799
+ - NEVER implement tasks yourself β€” planning and delegation only.
1800
+ - Keep task descriptions under 200 words.
1801
+ - Mark items done only after confirmation from the executing agent.`,
1802
+
1803
+ lead: `Team lead and coordinator. Delegation, progress tracking, blocker escalation.
1804
+
1805
+ ## Rules
1806
+ - Assign tasks to the right agent based on their specialty.
1807
+ - Track what's in progress and what's blocked.
1808
+ - Escalate failures to crew-fixer and report status.
1809
+ - Do NOT implement tasks yourself β€” delegate everything.
1810
+ - Communicate clearly: who is doing what, and what's blocked.`,
1811
+
1812
+ main: `Main agent and general-purpose coordinator. Fallback for tasks that don't fit a specialist.
1813
+
1814
+ ## Rules
1815
+ - Triage requests β€” handle directly or delegate to the right specialist.
1816
+ - @@WEB_SEARCH and @@WEB_FETCH for research tasks.
1817
+ - Write and edit files directly for general tasks.
1818
+ - Keep responses concise and action-oriented.
1819
+ - You're the catch-all β€” if something falls through the cracks, you handle it.`,
1820
+ };
1821
+
1822
+ const PRESET_META = {
1823
+ frontend: { id: "crew-coder-front", name: "Frontend Coder", emoji: "🎨" },
1824
+ backend: { id: "crew-coder-back", name: "Backend Coder", emoji: "βš™οΈ" },
1825
+ fullstack: { id: "crew-coder", name: "Full-stack Coder", emoji: "🧱" },
1826
+ ios: { id: "crew-coder-ios", name: "iOS Coder", emoji: "πŸ“±" },
1827
+ android: { id: "crew-coder-android", name: "Android Coder", emoji: "πŸ€–" },
1828
+ devops: { id: "crew-devops", name: "DevOps Engineer", emoji: "πŸ”§" },
1829
+ data: { id: "crew-data", name: "Data Engineer", emoji: "πŸ“Š" },
1830
+ security: { id: "crew-security", name: "Security Auditor", emoji: "πŸ›‘οΈ" },
1831
+ qa: { id: "crew-qa", name: "QA Tester", emoji: "πŸ§ͺ" },
1832
+ github: { id: "crew-github", name: "Git Ops", emoji: "πŸ™" },
1833
+ writer: { id: "crew-copywriter", name: "Copywriter", emoji: "✍️" },
1834
+ design: { id: "crew-design", name: "UI/UX Designer", emoji: "πŸ–ŒοΈ" },
1835
+ pm: { id: "crew-pm-agent", name: "Product Manager", emoji: "πŸ“‹" },
1836
+ aiml: { id: "crew-aiml", name: "AI/ML Engineer", emoji: "πŸ€–" },
1837
+ api: { id: "crew-api", name: "API Designer", emoji: "πŸ”Œ" },
1838
+ database: { id: "crew-database", name: "Database Specialist", emoji: "πŸ—„οΈ" },
1839
+ reactnative: { id: "crew-rn", name: "React Native Dev", emoji: "πŸ“±" },
1840
+ web3: { id: "crew-web3", name: "Web3 Engineer", emoji: "🌐" },
1841
+ automation: { id: "crew-automation", name: "Automation Bot", emoji: "πŸ•·οΈ" },
1842
+ docs: { id: "crew-docs", name: "Docs Writer", emoji: "πŸ“–" },
1843
+ orchestrator: {
1844
+ id: "crew-orchestrator",
1845
+ name: "Orchestrator",
1846
+ emoji: "🧠",
1847
+ },
1848
+ lead: { id: "crew-lead", name: "Crew Lead", emoji: "🦊" },
1849
+ main: { id: "crew-main", name: "Main Agent", emoji: "⚑" },
1850
+ };
1851
+
1852
+ export function applyPromptPreset() {
1853
+ const val = document.getElementById("naPromptPreset").value;
1854
+ if (!val || !PROMPT_PRESETS[val]) return;
1855
+ document.getElementById("naPrompt").value = PROMPT_PRESETS[val];
1856
+ const meta = PRESET_META[val];
1857
+ if (meta) {
1858
+ const idEl = document.getElementById("naId");
1859
+ const nameEl = document.getElementById("naName");
1860
+ const emojiEl = document.getElementById("naEmoji");
1861
+ if (idEl && !idEl.value) idEl.value = meta.id;
1862
+ if (nameEl && !nameEl.value) nameEl.value = meta.name;
1863
+ if (emojiEl && !emojiEl.value) emojiEl.value = meta.emoji;
1864
+ }
1865
+ // Auto-fill role/theme from the preset's display label (strip leading emoji)
1866
+ const themeEl = document.getElementById("naTheme");
1867
+ if (themeEl) {
1868
+ const opt = PRESET_OPTIONS.find((p) => p.value === val);
1869
+ if (opt)
1870
+ themeEl.value = opt.label
1871
+ .replace(
1872
+ /^[\u{1F000}-\u{1FFFF}\u2600-\u27BF\uFE0F\u20D0-\u20FF\s]+/u,
1873
+ "",
1874
+ )
1875
+ .trim();
1876
+ }
1877
+ }
1878
+
1879
+ // Models confirmed broken via API testing β€” return empty strings
1880
+ const BROKEN_MODELS = new Set([
1881
+ "groq/openai/gpt-oss-120b",
1882
+ "groq/openai/gpt-oss-20b",
1883
+ ]);
1884
+
1885
+ // Role classification for badge display (matches MODEL-ROLE-OPTIMIZATION.md)
1886
+ const MODEL_ROLE = {
1887
+ // PLANNER β€” task decomposition, routing
1888
+ "crew-pm": "PLANNER",
1889
+ "crew-orchestrator": "PLANNER",
1890
+ orchestrator: "PLANNER",
1891
+ // WORKER β€” code generation, implementation
1892
+ "crew-coder": "WORKER",
1893
+ "crew-coder-back": "WORKER",
1894
+ "crew-coder-front": "WORKER",
1895
+ "crew-frontend": "WORKER",
1896
+ "crew-fixer": "WORKER",
1897
+ // JUDGE β€” cycle decisions
1898
+ "crew-judge": "JUDGE",
1899
+ // ANALYST β€” QA, security, review
1900
+ "crew-qa": "ANALYST",
1901
+ "crew-security": "ANALYST",
1902
+ // COORDINATOR β€” triage, delegation
1903
+ "crew-lead": "COORDINATOR",
1904
+ "crew-main": "COORDINATOR",
1905
+ // OTHER
1906
+ "crew-architect": "COORDINATOR",
1907
+ "crew-ml": "ANALYST",
1908
+ "crew-mega": "COORDINATOR",
1909
+ "crew-researcher": "COORDINATOR",
1910
+ "crew-copywriter": "WORKER",
1911
+ "crew-github": "WORKER",
1912
+ "crew-seo": "COORDINATOR",
1913
+ };
1914
+ const ROLE_STYLE = {
1915
+ PLANNER:
1916
+ "background:rgba(139,92,246,0.12);border:1px solid rgba(139,92,246,0.35);color:#a78bfa;",
1917
+ WORKER:
1918
+ "background:rgba(34,197,94,0.10);border:1px solid rgba(34,197,94,0.30);color:var(--green-hi);",
1919
+ JUDGE:
1920
+ "background:rgba(245,158,11,0.10);border:1px solid rgba(245,158,11,0.30);color:var(--yellow);",
1921
+ ANALYST:
1922
+ "background:rgba(239,68,68,0.10);border:1px solid rgba(239,68,68,0.30);color:var(--red-hi);",
1923
+ COORDINATOR:
1924
+ "background:rgba(56,189,248,0.10);border:1px solid rgba(56,189,248,0.30);color:var(--accent);",
1925
+ };
1926
+
1927
+ // Top 10 models per role (from MODEL-ROLE-OPTIMIZATION.md)
1928
+ const TOP_MODELS_BY_ROLE = {
1929
+ PLANNER: [
1930
+ "groq/llama-3.3-70b-versatile",
1931
+ "cerebras/llama-3.3-70b",
1932
+ "groq/llama-3.1-8b-instant",
1933
+ "google/gemini-2.0-flash",
1934
+ "openai/gpt-4o-mini",
1935
+ "xai/grok-3-mini-fast",
1936
+ "mistral/mistral-small-latest",
1937
+ "deepseek/deepseek-coder",
1938
+ "anthropic/claude-haiku-4-5",
1939
+ "cerebras/llama-3.1-8b",
1940
+ ],
1941
+ WORKER: [
1942
+ "anthropic/claude-sonnet-4-20250514",
1943
+ "openai/codex-mini-latest",
1944
+ "deepseek/deepseek-chat",
1945
+ "opencode/gpt-5.2-codex",
1946
+ "opencode/big-pickle",
1947
+ "mistral/codestral-latest",
1948
+ "openai/gpt-4.1",
1949
+ "google/models/gemini-2.5-flash",
1950
+ "xai/grok-3",
1951
+ "anthropic/claude-sonnet-3-5",
1952
+ ],
1953
+ JUDGE: [
1954
+ "groq/llama-3.3-70b-versatile",
1955
+ "cerebras/llama-3.3-70b",
1956
+ "deepseek/deepseek-reasoner",
1957
+ "openai/o4-mini",
1958
+ "xai/grok-3-mini",
1959
+ "google/gemini-2.0-flash",
1960
+ "groq/llama-3.1-8b-instant",
1961
+ "mistral/mistral-small-latest",
1962
+ "anthropic/claude-haiku-4-5",
1963
+ "deepseek/deepseek-chat",
1964
+ ],
1965
+ ANALYST: [
1966
+ "anthropic/claude-sonnet-4-20250514",
1967
+ "deepseek/deepseek-reasoner",
1968
+ "openai/o4-mini",
1969
+ "deepseek/deepseek-chat",
1970
+ "xai/grok-3",
1971
+ "mistral/mistral-large-latest",
1972
+ "google/models/gemini-2.5-flash",
1973
+ "groq/llama-3.3-70b-versatile",
1974
+ "anthropic/claude-haiku-4-5",
1975
+ "google/gemini-2.0-flash",
1976
+ ],
1977
+ COORDINATOR: [
1978
+ "anthropic/claude-sonnet-4-20250514",
1979
+ "openai/gpt-4.1",
1980
+ "xai/grok-3",
1981
+ "google/models/gemini-2.5-flash",
1982
+ "deepseek/deepseek-chat",
1983
+ "perplexity/sonar-pro",
1984
+ "opencode/big-pickle",
1985
+ "openai/gpt-4o",
1986
+ "groq/llama-3.3-70b-versatile",
1987
+ "xai/grok-3-mini",
1988
+ ],
1989
+ };
1990
+
1991
+ export function populateModelDropdown(selectId, currentVal, agentRole = null) {
1992
+ const sel = document.getElementById(selectId);
1993
+ sel.innerHTML = '<option value="">β€” select a model β€”</option>';
1994
+
1995
+ // Get top models for this role
1996
+ const topModels =
1997
+ agentRole && TOP_MODELS_BY_ROLE[agentRole]
1998
+ ? TOP_MODELS_BY_ROLE[agentRole]
1999
+ : [];
2000
+ const topModelsSet = new Set(topModels);
2001
+
2002
+ if (Object.keys(_modelsByProvider).length) {
2003
+ // If we have role-specific recommendations, show them first
2004
+ if (topModels.length > 0) {
2005
+ const grp = document.createElement("optgroup");
2006
+ grp.label = `⭐ RECOMMENDED FOR ${agentRole}`;
2007
+ topModels.forEach((modelId) => {
2008
+ const broken = BROKEN_MODELS.has(modelId);
2009
+ const opt = document.createElement("option");
2010
+ opt.value = modelId;
2011
+ opt.textContent = "⭐ " + (broken ? "⚠ BROKEN β€” " : "") + modelId;
2012
+ if (broken) opt.style.color = "var(--red-hi)";
2013
+ if (modelId === currentVal) opt.selected = true;
2014
+ grp.appendChild(opt);
2015
+ });
2016
+ sel.appendChild(grp);
2017
+ }
2018
+
2019
+ // Then show all models grouped by provider
2020
+ for (const [provider, models] of Object.entries(_modelsByProvider)) {
2021
+ const grp = document.createElement("optgroup");
2022
+ grp.label =
2023
+ provider.toUpperCase() + (topModels.length > 0 ? " (All Models)" : "");
2024
+ models.forEach(({ id, name }) => {
2025
+ const full = provider + "/" + id;
2026
+ // Skip if already in recommended list
2027
+ if (topModelsSet.has(full)) return;
2028
+ const broken = BROKEN_MODELS.has(full);
2029
+ const opt = document.createElement("option");
2030
+ opt.value = full;
2031
+ opt.textContent =
2032
+ (broken ? "⚠ BROKEN β€” " : "") +
2033
+ (name ? name + " (" + id + ")" : full);
2034
+ if (broken) opt.style.color = "var(--red-hi)";
2035
+ if (full === currentVal) opt.selected = true;
2036
+ grp.appendChild(opt);
2037
+ });
2038
+ sel.appendChild(grp);
2039
+ }
2040
+ } else {
2041
+ // Fallback to flat list
2042
+ if (topModels.length > 0) {
2043
+ const grp = document.createElement("optgroup");
2044
+ grp.label = `⭐ RECOMMENDED FOR ${agentRole}`;
2045
+ topModels.forEach((modelId) => {
2046
+ const broken = BROKEN_MODELS.has(modelId);
2047
+ const opt = document.createElement("option");
2048
+ opt.value = modelId;
2049
+ opt.textContent = "⭐ " + (broken ? "⚠ BROKEN β€” " : "") + modelId;
2050
+ if (broken) opt.style.color = "var(--red-hi)";
2051
+ if (modelId === currentVal) opt.selected = true;
2052
+ grp.appendChild(opt);
2053
+ });
2054
+ sel.appendChild(grp);
2055
+ }
2056
+
2057
+ _allModels.forEach((m) => {
2058
+ if (topModelsSet.has(m)) return; // Skip recommended ones
2059
+ const broken = BROKEN_MODELS.has(m);
2060
+ const opt = document.createElement("option");
2061
+ opt.value = m;
2062
+ opt.textContent = (broken ? "⚠ BROKEN β€” " : "") + m;
2063
+ if (broken) opt.style.color = "var(--red-hi)";
2064
+ if (m === currentVal) opt.selected = true;
2065
+ sel.appendChild(opt);
2066
+ });
2067
+ }
2068
+ // If current value not in list, add it as custom
2069
+ if (
2070
+ currentVal &&
2071
+ !_allModels.includes(currentVal) &&
2072
+ !topModelsSet.has(currentVal)
2073
+ ) {
2074
+ const opt = document.createElement("option");
2075
+ opt.value = currentVal;
2076
+ opt.textContent = currentVal + " (custom)";
2077
+ opt.selected = true;
2078
+ sel.prepend(opt);
2079
+ }
2080
+ }
2081
+
2082
+ document.getElementById("newAgentBtn").onclick = () => {
2083
+ document.getElementById("newAgentForm").style.display = "block";
2084
+ populateModelDropdown("naModel", "");
2085
+ // Populate preset dropdown dynamically (PRESET_OPTIONS is client-side only)
2086
+ const sel = document.getElementById("naPromptPreset");
2087
+ if (sel && sel.options.length <= 1) {
2088
+ PRESET_OPTIONS.forEach((p) => {
2089
+ const opt = document.createElement("option");
2090
+ opt.value = p.value;
2091
+ opt.textContent = p.label;
2092
+ sel.appendChild(opt);
2093
+ });
2094
+ }
2095
+ // Populate tool checkboxes dynamically (CREWSWARM_TOOLS is not available at HTML parse time)
2096
+ const grid = document.getElementById("naToolsGrid");
2097
+ if (grid && grid.querySelectorAll(".naToolCheck").length === 0) {
2098
+ grid.innerHTML = CREWSWARM_TOOLS.map(
2099
+ (t) => `
2100
+ <label style="display:flex; align-items:flex-start; gap:7px; font-size:12px; color:var(--text-2); cursor:pointer; padding:6px 8px; border-radius:5px; border:1px solid var(--border); background:var(--bg-card2);">
2101
+ <input type="checkbox" class="naToolCheck" data-tool="${t.id}" style="accent-color:var(--accent); margin-top:2px; flex-shrink:0;" />
2102
+ <div>
2103
+ <code style="font-size:11px; color:var(--text-1);">${t.id}</code>
2104
+ <div style="font-size:10px; color:var(--text-3); margin-top:2px; line-height:1.3;">${t.desc}</div>
2105
+ </div>
2106
+ </label>
2107
+ `,
2108
+ ).join("");
2109
+ }
2110
+ };
2111
+ document.getElementById("naCancelBtn").onclick = () => {
2112
+ document.getElementById("newAgentForm").style.display = "none";
2113
+ };
2114
+ document.getElementById("naCreateBtn").onclick = async () => {
2115
+ const rawId = document.getElementById("naId").value.trim();
2116
+ const id =
2117
+ rawId && !rawId.startsWith("crew-") && rawId !== "orchestrator"
2118
+ ? `crew-${rawId}`
2119
+ : rawId;
2120
+ const model = document.getElementById("naModel").value.trim();
2121
+ const name = document.getElementById("naName").value.trim();
2122
+ const emoji = document.getElementById("naEmoji").value.trim();
2123
+ const theme = document.getElementById("naTheme").value.trim();
2124
+ const systemPrompt = document.getElementById("naPrompt").value.trim();
2125
+ const naTools = [...document.querySelectorAll(".naToolCheck:checked")].map(
2126
+ (cb) => cb.dataset.tool,
2127
+ );
2128
+ const alsoAllow = naTools.length ? naTools : getToolDefaults(id);
2129
+ if (!id || !model) {
2130
+ showNotification("Agent ID and model are required", true);
2131
+ return;
2132
+ }
2133
+ try {
2134
+ await postJSON("/api/agents-config/create", {
2135
+ id,
2136
+ model,
2137
+ name,
2138
+ emoji,
2139
+ theme,
2140
+ systemPrompt,
2141
+ alsoAllow,
2142
+ });
2143
+ showNotification(
2144
+ `Agent "${id}" created β€” restart gateway-bridge to activate it on the RT bus.`,
2145
+ );
2146
+ document.getElementById("newAgentForm").style.display = "none";
2147
+ ["naId", "naName", "naTheme", "naPrompt"].forEach((x) => {
2148
+ document.getElementById(x).value = "";
2149
+ });
2150
+ document.getElementById("naEmoji").value = "πŸ”₯";
2151
+ document.getElementById("naEmoji-btn").textContent = "πŸ”₯";
2152
+ document.getElementById("naModel").innerHTML =
2153
+ '<option value="">β€” select a model β€”</option>';
2154
+ document.getElementById("naPromptPreset").value = "";
2155
+ loadAgents_cfg();
2156
+ } catch (e) {
2157
+ showNotification("Failed: " + e.message, true);
2158
+ }
2159
+ };
2160
+ document.getElementById("refreshAgentsBtn").onclick = loadAgents_cfg;
2161
+
2162
+ document.getElementById("bulkOptimizeBtn").onclick = async () => {
2163
+ if (
2164
+ !confirm(
2165
+ "Apply role-optimized models to all agents?\n\nβ€’ Planners β†’ Fast, cheap models\nβ€’ Workers β†’ Precision models\nβ€’ Judges β†’ Fast reasoning\nβ€’ Analysts β†’ Reasoning models\nβ€’ Coordinators β†’ Balanced\n\nSee docs/MODEL-ROLE-OPTIMIZATION.md for details.",
2166
+ )
2167
+ )
2168
+ return;
2169
+
2170
+ const mode = "value"; // Could add UI to pick free/quality/value
2171
+ const preset = {
2172
+ "crew-pm": "groq/llama-3.3-70b-versatile",
2173
+ "crew-orchestrator": "groq/llama-3.3-70b-versatile",
2174
+ "crew-judge": "groq/llama-3.3-70b-versatile",
2175
+ "crew-coder": "google/models/gemini-2.5-flash-lite",
2176
+ "crew-coder-front": "google/models/gemini-2.5-flash-lite",
2177
+ "crew-coder-back": "google/models/gemini-2.5-flash-lite",
2178
+ "crew-fixer": "deepseek/deepseek-reasoner",
2179
+ "crew-qa": "deepseek/deepseek-reasoner",
2180
+ "crew-security": "deepseek/deepseek-reasoner",
2181
+ "crew-main": "google/models/gemini-2.5-flash-lite",
2182
+ "crew-lead": "google/models/gemini-2.5-flash-lite",
2183
+ "crew-frontend": "google/models/gemini-2.5-flash-lite",
2184
+ "crew-copywriter": "groq/llama-3.3-70b-versatile",
2185
+ "crew-researcher": "perplexity/sonar-pro",
2186
+ "crew-architect": "deepseek/deepseek-reasoner",
2187
+ "crew-seo": "groq/llama-3.3-70b-versatile",
2188
+ "crew-ml": "deepseek/deepseek-reasoner",
2189
+ "crew-github": "groq/llama-3.3-70b-versatile",
2190
+ };
2191
+
2192
+ showNotification("Applying role-optimized models…");
2193
+ let updated = 0;
2194
+
2195
+ for (const [agentId, model] of Object.entries(preset)) {
2196
+ try {
2197
+ await postJSON("/api/agents-config/update", { agentId, model });
2198
+ updated++;
2199
+ // Small delay to prevent rapid-fire saves that create backup storms
2200
+ await new Promise(r => setTimeout(r, 50));
2201
+ } catch (e) {
2202
+ console.error(`Failed to update ${agentId}:`, e.message);
2203
+ }
2204
+ }
2205
+
2206
+ showNotification(
2207
+ `βœ… Updated ${updated} agents β€” Restart bridges to activate`,
2208
+ );
2209
+ await loadAgents_cfg();
2210
+ };
2211
+
2212
+ // ── End agents UI ──────────────────────────────────────────────────────────
2213
+
2214
+ export {
2215
+ loadAgents_cfg,
2216
+ applyToolPreset,
2217
+ toggleAgentBody,
2218
+ deleteAgent,
2219
+ saveAgentModel,
2220
+ saveAgentFallback,
2221
+ saveAgentVoice,
2222
+ toggleEmojiPicker,
2223
+ saveAgentIdentity,
2224
+ saveAgentPrompt,
2225
+ resetAgentSession,
2226
+ saveAgentTools,
2227
+ setRoute,
2228
+ saveOpenCodeConfig,
2229
+ saveOpenCodeFallback,
2230
+ saveCursorCliConfig,
2231
+ saveClaudeCodeConfig,
2232
+ saveCodexConfig,
2233
+ saveGeminiCliConfig,
2234
+ saveCrewCLIConfig,
2235
+ bulkSetRoute,
2236
+ startCrew,
2237
+ };