crewswarm 0.9.2 → 0.9.4

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 (228) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  3. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  5. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  6. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  8. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  9. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  10. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  11. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  12. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  14. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  18. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  22. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  23. package/apps/dashboard/dist/index.html +135 -15
  24. package/apps/dashboard/dist/index.html.br +0 -0
  25. package/apps/dashboard/dist/index.html.gz +0 -0
  26. package/apps/vibe/README.md +2 -2
  27. package/apps/vibe/package.json +1 -1
  28. package/apps/vibe/server.mjs +101 -56
  29. package/crew-lead.mjs +34 -4
  30. package/lib/bridges/cli-executor.mjs +1 -1
  31. package/lib/bridges/gateway-ws.mjs +4 -0
  32. package/lib/browser/passthrough-stderr.js +1 -0
  33. package/lib/chat/project-messages.mjs +3 -5
  34. package/lib/cli-process-tracker.mjs +3 -2
  35. package/lib/contacts/identity-linker.mjs +1 -0
  36. package/lib/crew-judge/judge.mjs +19 -18
  37. package/lib/crew-lead/agent-manager.mjs +1 -1
  38. package/lib/crew-lead/background.mjs +14 -1
  39. package/lib/crew-lead/chat-handler.mjs +38 -1
  40. package/lib/crew-lead/http-server.mjs +106 -57
  41. package/lib/crew-lead/llm-caller.mjs +24 -8
  42. package/lib/crew-lead/prompts.mjs +14 -1
  43. package/lib/crew-lead/tools.mjs +3 -2
  44. package/lib/crew-lead/wave-dispatcher.mjs +19 -5
  45. package/lib/crew-lead/ws-router.mjs +219 -27
  46. package/lib/engines/crew-cli.mjs +1 -1
  47. package/lib/engines/engine-registry.mjs +14 -3
  48. package/lib/engines/rt-envelope.mjs +1 -0
  49. package/lib/engines/runners.mjs +28 -4
  50. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  51. package/lib/integrations/code-search.mjs +4 -3
  52. package/lib/memory/shared-adapter.mjs +23 -10
  53. package/lib/pipeline/manager.mjs +2 -1
  54. package/lib/runtime/config.mjs +1 -1
  55. package/lib/runtime/paths.mjs +12 -8
  56. package/lib/runtime/spending.mjs +2 -1
  57. package/package.json +42 -14
  58. package/scripts/capture-build-flow.mjs +118 -0
  59. package/scripts/coverage-report.mjs +209 -0
  60. package/scripts/coverage-summary.mjs +47 -0
  61. package/scripts/dashboard-validation.mjs +76 -0
  62. package/scripts/dashboard.mjs +1667 -551
  63. package/scripts/generate-openapi.mjs +683 -277
  64. package/scripts/live-bridge-matrix.mjs +79 -0
  65. package/scripts/live-cli-matrix.mjs +166 -0
  66. package/scripts/live-crewchat-check.mjs +42 -0
  67. package/scripts/live-engine-matrix.mjs +50 -0
  68. package/scripts/live-provider-failover-matrix.mjs +107 -0
  69. package/scripts/live-provider-matrix.mjs +228 -0
  70. package/scripts/restart-all-from-repo.sh +4 -4
  71. package/scripts/restart-service.sh +12 -9
  72. package/scripts/smoke-dispatch.mjs +4 -1
  73. package/scripts/test-blast-radius.mjs +204 -0
  74. package/scripts/test-report-summary.mjs +88 -0
  75. package/scripts/test-reporter.mjs +651 -0
  76. package/scripts/test-rerun.mjs +136 -0
  77. package/scripts/tmux-bridge +130 -0
  78. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
  79. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  80. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  81. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  82. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  83. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  84. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  85. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  86. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  87. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  88. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  89. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  90. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  91. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
  92. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  93. package/apps/dashboard/index.html +0 -6529
  94. package/apps/dashboard/package.json +0 -15
  95. package/apps/dashboard/src/app.js +0 -2828
  96. package/apps/dashboard/src/app.js.br +0 -0
  97. package/apps/dashboard/src/app.js.gz +0 -0
  98. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  99. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  100. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  101. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  102. package/apps/dashboard/src/cli-process.js +0 -208
  103. package/apps/dashboard/src/cli-process.js.br +0 -0
  104. package/apps/dashboard/src/cli-process.js.gz +0 -0
  105. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  106. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  107. package/apps/dashboard/src/core/api.js +0 -18
  108. package/apps/dashboard/src/core/api.js.br +0 -0
  109. package/apps/dashboard/src/core/dom.js +0 -228
  110. package/apps/dashboard/src/core/dom.js.br +0 -0
  111. package/apps/dashboard/src/core/state.js +0 -91
  112. package/apps/dashboard/src/core/state.js.br +0 -0
  113. package/apps/dashboard/src/core/task-manager.js +0 -134
  114. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  115. package/apps/dashboard/src/orchestration-status.js +0 -127
  116. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  117. package/apps/dashboard/src/setup-wizard.js +0 -562
  118. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  119. package/apps/dashboard/src/styles.css +0 -2085
  120. package/apps/dashboard/src/styles.css.br +0 -0
  121. package/apps/dashboard/src/styles.css.gz +0 -0
  122. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  123. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  125. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  127. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  129. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  131. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  133. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  135. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  137. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  139. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  140. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  141. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  142. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  143. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  144. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  145. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  146. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  147. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  148. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  149. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  150. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  151. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  152. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  153. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  154. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  155. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  156. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  157. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  158. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  159. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  160. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  161. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  162. package/apps/vibe/.crew/cost.json +0 -17
  163. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  164. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  172. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  173. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  174. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  175. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  176. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  177. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  178. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  179. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  180. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  181. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  182. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  183. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  184. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  185. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  186. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  187. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  188. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  189. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  190. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  191. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  192. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  193. package/apps/vibe/.crew/sandbox.json +0 -7
  194. package/apps/vibe/.crew/session.json +0 -330
  195. package/apps/vibe/.crew/training-data.jsonl +0 -0
  196. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  197. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  198. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  199. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  200. package/apps/vibe/ARCHITECTURE.md +0 -3393
  201. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  202. package/apps/vibe/ROADMAP.md +0 -41
  203. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  204. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  205. package/apps/vibe/capture-demo.mjs +0 -160
  206. package/apps/vibe/capture-full-demo.mjs +0 -255
  207. package/apps/vibe/capture-quickstart.mjs +0 -256
  208. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  209. package/apps/vibe/capture-vibe-video.mjs +0 -260
  210. package/apps/vibe/check-buttons.js +0 -41
  211. package/apps/vibe/diagnose.html +0 -106
  212. package/apps/vibe/fix-buttons.js +0 -103
  213. package/apps/vibe/index.html +0 -3404
  214. package/apps/vibe/package-lock.json +0 -920
  215. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  216. package/apps/vibe/src/main.js +0 -2940
  217. package/apps/vibe/src/register-all-languages.js +0 -98
  218. package/apps/vibe/start-studio.sh +0 -11
  219. package/apps/vibe/test/accessibility-tests.js +0 -77
  220. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  221. package/apps/vibe/test/performance-tests.js +0 -120
  222. package/apps/vibe/test/security-tests.js +0 -213
  223. package/apps/vibe/tests/e2e.local.mjs +0 -54
  224. package/apps/vibe/tests/server.smoke.mjs +0 -106
  225. package/apps/vibe/update_website.mjs +0 -74
  226. package/apps/vibe/vite.config.js +0 -19
  227. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  228. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -1,450 +0,0 @@
1
- /**
2
- * Models / Providers / Search-tools tab — extracted from app.js
3
- * Deps: getJSON, postJSON (core/api), showNotification (core/dom)
4
- * Inject: initModelsTab({ hideAllViews, setNavActive, loadAgents: loadAgents_cfg })
5
- */
6
-
7
- import { getJSON, postJSON } from '../core/api.js';
8
- import { showNotification, showLoading, showError } from '../core/dom.js';
9
-
10
- let _hideAllViews = () => {};
11
- let _setNavActive = () => {};
12
- let _loadAgents = () => {};
13
-
14
- export function initModelsTab({ hideAllViews, setNavActive, loadAgents } = {}) {
15
- _hideAllViews = hideAllViews || _hideAllViews;
16
- _setNavActive = setNavActive || _setNavActive;
17
- _loadAgents = loadAgents || _loadAgents;
18
- }
19
-
20
- // ── Constants ──────────────────────────────────────────────────────────────────
21
-
22
- const BUILTIN_PROVIDERS = [
23
- { id:'groq', label:'Groq', icon:'⚡', url:'https://console.groq.com/keys', hint:'Fast inference — great for crew-coder, crew-fixer' },
24
- { id:'fireworks', label:'Fireworks AI', icon:'🎆', url:'https://fireworks.ai/', hint:'OpenAI-compatible inference platform — fast serverless models, custom deployments, and easy model discovery' },
25
- { id:'anthropic', label:'Anthropic', icon:'🟣', url:'https://console.anthropic.com/', hint:'Claude models — best for complex reasoning tasks' },
26
- { id:'openai', label:'OpenAI (API)', icon:'🟢', url:'https://platform.openai.com/api-keys', hint:'GPT-4o and o-series — pay per use with API key' },
27
- { id:'cerebras', label:'Cerebras', icon:'🧠', url:'https://cloud.cerebras.ai/', hint:'Ultra-fast inference on Cerebras hardware — llama-3.3-70b at 2,000 tok/s' },
28
- { id:'nvidia', label:'NVIDIA NIM', icon:'🎮', url:'https://build.nvidia.com/explore/discover', hint:'NVIDIA NIM microservices — Llama, Mistral, Phi and more' },
29
- { id:'openrouter', label:'OpenRouter', icon:'🔀', url:'https://openrouter.ai/keys', hint:'One API key for 400+ models — Claude, GPT-4, Gemini, Hunter Alpha, Llama and more' },
30
- { id:'perplexity', label:'Perplexity', icon:'🔍', url:'https://www.perplexity.ai/settings/api', hint:'Sonar Pro — ideal for crew-pm research tasks' },
31
- { id:'mistral', label:'Mistral', icon:'🌀', url:'https://console.mistral.ai/', hint:'Open-weight models, efficient mid-tier tasks' },
32
- { id:'deepseek', label:'DeepSeek', icon:'🌊', url:'https://platform.deepseek.com/', hint:'Low cost, strong coding performance' },
33
- { id:'together', label:'Together AI', icon:'🤝', url:'https://api.together.ai/', hint:'OpenAI-compatible access to strong open models like Qwen, DeepSeek, Llama, and more' },
34
- { id:'xai', label:'xAI (Grok)', icon:'𝕏', url:'https://console.x.ai/', hint:'Grok models with real-time X/Twitter access, vision (grok-vision-beta), 128K context — ideal for research, social media analysis' },
35
- { id:'huggingface', label:'Hugging Face', icon:'🤗', url:'https://huggingface.co/settings/tokens', hint:'Open-source model hub — access thousands of models via Inference API' },
36
- { id:'venice', label:'Venice AI', icon:'🏖️', url:'https://venice.ai/settings/api', hint:'Privacy-focused inference — no logging, no training on your data' },
37
- { id:'moonshot', label:'Moonshot / Kimi', icon:'🌙', url:'https://platform.moonshot.cn/console/api-keys', hint:'128K+ context windows — strong on long codebases, Chinese + English' },
38
- { id:'minimax', label:'MiniMax', icon:'✨', url:'https://www.minimaxi.com/', hint:'Chinese LLM provider — competitive pricing, multilingual' },
39
- { id:'volcengine', label:'Volcengine', icon:'🌋', url:'https://console.volcengine.com/ark', hint:'ByteDance Doubao models — fast inference' },
40
- { id:'qianfan', label:'Baidu Qianfan', icon:'🔵', url:'https://console.bce.baidu.com/qianfan/', hint:'Baidu ERNIE models — strong on Chinese language and reasoning' },
41
- { id:'ollama', label:'Ollama', icon:'🏠', url:'https://ollama.com/download', hint:'Local models — no API key needed, runs offline' },
42
- { id:'vllm', label:'vLLM', icon:'⚡', url:'https://docs.vllm.ai/', hint:'Self-hosted inference server — any open model, OpenAI-compatible' },
43
- { id:'sglang', label:'SGLang', icon:'⚡', url:'https://github.com/sgl-project/sglang', hint:'Self-hosted inference server — fast structured generation' },
44
- { id:'openai-local', label:'OpenAI (local)', icon:'🟢', url:'https://github.com/RayBytes/ChatMock', hint:'ChatMock — use ChatGPT Plus/Pro subscription. Run ChatMock server first (e.g. port 8000). Key ignored.' },
45
- ];
46
-
47
- const SEARCH_TOOLS = [
48
- { id:'parallel', label:'Parallel', icon:'🔬', url:'https://platform.parallel.ai/signup', hint:'Deep research & web synthesis — used by crew-pm for project planning', envKey:'PARALLEL_API_KEY' },
49
- { id:'brave', label:'Brave Search', icon:'🦁', url:'https://api.search.brave.com/', hint:'Fast web search (~700ms) — best for quick agent lookups', envKey:'BRAVE_API_KEY' },
50
- ];
51
-
52
- const PROVIDER_ICONS = {
53
- opencode:'🚀', groq:'⚡', fireworks:'🎆', nvidia:'🎮', ollama:'🏠', 'openai-local':'🟢', xai:'𝕏',
54
- google:'🔵', deepseek:'🌊', openai:'🟢', perplexity:'🔍', cerebras:'🧠', mistral:'🌀',
55
- together:'🤝', cohere:'🔶', anthropic:'🟣', openrouter:'🔀', huggingface:'🤗',
56
- venice:'🏖️', moonshot:'🌙', minimax:'✨', volcengine:'🌋', qianfan:'🔵', vllm:'⚡', sglang:'⚡',
57
- };
58
-
59
- // ── Tab entry point ────────────────────────────────────────────────────────────
60
-
61
- export function showModels() {
62
- _hideAllViews();
63
- document.getElementById('modelsView').classList.add('active');
64
- _setNavActive('navModels');
65
- loadRTToken_local();
66
- loadBuiltinProviders();
67
- loadSearchTools();
68
- }
69
-
70
- export function showProviders() { showModels(); }
71
-
72
- // ── RT Token (mirrored here for models view; source of truth in settings-tab) ─
73
-
74
- async function loadRTToken_local() {
75
- try {
76
- const d = await getJSON('/api/settings/rt-token');
77
- const badge = document.getElementById('rtTokenBadge');
78
- const inp = document.getElementById('rtTokenInput');
79
- if (!badge) return;
80
- if (d.token) {
81
- badge.textContent = 'set ✓';
82
- badge.style.background = 'rgba(52,211,153,0.15)';
83
- badge.style.color = 'var(--green)';
84
- badge.style.borderColor = 'rgba(52,211,153,0.3)';
85
- if (inp) inp.placeholder = '••••••••••••••••••••••• (saved)';
86
- } else {
87
- badge.textContent = 'not set';
88
- badge.style.background = 'rgba(251,191,36,0.15)';
89
- badge.style.color = 'var(--yellow)';
90
- badge.style.borderColor = 'rgba(251,191,36,0.3)';
91
- }
92
- } catch {}
93
- }
94
-
95
- // ── Search tools ───────────────────────────────────────────────────────────────
96
-
97
- export async function loadSearchTools() {
98
- const list = document.getElementById('searchToolsList');
99
- if (!list) return;
100
- let saved = {};
101
- try { saved = (await getJSON('/api/search-tools')).keys || {}; } catch {}
102
- list.innerHTML = SEARCH_TOOLS.map(p => {
103
- const hasKey = !!saved[p.id];
104
- const badge = hasKey
105
- ? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">set ✓</span>`
106
- : `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
107
- return `<div class="card" style="margin-bottom:8px;">
108
- <div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".st-body">
109
- <span style="font-size:18px;width:24px;text-align:center;">${p.icon}</span>
110
- <div style="flex:1;">
111
- <div style="font-weight:600;font-size:13px;">${p.label}</div>
112
- <div style="font-size:11px;color:var(--text-2);">${p.hint}</div>
113
- </div>
114
- ${badge}
115
- <span style="color:var(--text-2);font-size:12px;">▾</span>
116
- </div>
117
- <div class="st-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
118
- <div style="display:flex;gap:8px;">
119
- <input id="st_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;" />
120
- <button data-action="saveSearchTool" data-arg="${p.id}" class="btn-purple">Save</button>
121
- <button data-action="testSearchTool" data-arg="${p.id}" class="btn-ghost">Test</button>
122
- <a href="${p.url}" target="_blank" class="btn-ghost" style="text-decoration:none;font-size:12px;">Keys ↗</a>
123
- </div>
124
- <div style="font-size:11px;color:var(--text-2);margin-top:6px;">Saved as <code style="background:rgba(255,255,255,0.06);padding:1px 5px;border-radius:4px;">${p.envKey}</code> in environment</div>
125
- <div id="st_status_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
126
- </div>
127
- </div>`;
128
- }).join('');
129
- }
130
-
131
- export async function saveSearchTool(toolId) {
132
- const inp = document.getElementById('st_' + toolId);
133
- const key = inp?.value?.trim();
134
- if (!key) { showNotification('Paste an API key first', 'error'); return; }
135
- try {
136
- await postJSON('/api/search-tools/save', { toolId, key });
137
- showNotification('Key saved', 'success');
138
- loadSearchTools();
139
- } catch(e) { showNotification('Save failed: ' + e.message, 'error'); }
140
- }
141
-
142
- export async function testSearchTool(toolId) {
143
- const statusEl = document.getElementById('st_status_' + toolId);
144
- statusEl.textContent = 'Testing…';
145
- try {
146
- const r = await postJSON('/api/search-tools/test', { toolId });
147
- statusEl.style.color = r.ok ? 'var(--green)' : 'var(--red)';
148
- statusEl.textContent = r.ok ? '✓ ' + (r.message || 'Connected') : '✗ ' + (r.error || 'Failed');
149
- } catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
150
- }
151
-
152
- // ── Built-in providers ─────────────────────────────────────────────────────────
153
-
154
- export async function loadBuiltinProviders() {
155
- const list = document.getElementById('builtinProvidersList');
156
- if (!list) return;
157
- let saved = {};
158
- try { saved = (await getJSON('/api/providers/builtin')).keys || {}; } catch {}
159
- const builtinIds = new Set(BUILTIN_PROVIDERS.map(p => p.id));
160
-
161
- let html = BUILTIN_PROVIDERS.map(p => {
162
- const hasKey = !!saved[p.id];
163
- const isOllama = p.id === 'ollama';
164
- const isOpenAiLocal = p.id === 'openai-local';
165
- const badge = hasKey || isOllama || isOpenAiLocal
166
- ? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">${(isOllama || isOpenAiLocal) && !hasKey ? 'local' : 'set ✓'}</span>`
167
- : `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
168
- return `<div class="card" style="margin-bottom:8px;">
169
- <div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".bp-body">
170
- <span style="font-size:18px;width:24px;text-align:center;">${p.icon}</span>
171
- <div style="flex:1;">
172
- <div style="font-weight:600;font-size:13px;">${p.label}</div>
173
- <div style="font-size:11px;color:var(--text-2);">${p.hint}</div>
174
- </div>
175
- ${badge}
176
- <span style="color:var(--text-2);font-size:12px;">▾</span>
177
- </div>
178
- <div class="bp-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
179
- ${isOllama ? `<div style="font-size:12px;color:var(--text-2);margin-bottom:8px;">Ollama runs locally — no API key required. Make sure Ollama is running on port 11434.</div>` : ''}
180
- <div style="display:flex;gap:8px;flex-wrap:wrap;">
181
- ${isOllama ? '' : `<input id="bp_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;min-width:180px;" />`}
182
- ${isOllama
183
- ? `<button data-action="testBuiltinProvider" data-arg="${p.id}" class="btn-ghost">Test Connection</button>
184
- <button data-action="fetchBuiltinModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>`
185
- : `<button data-action="saveBuiltinKey" data-arg="${p.id}" class="btn-purple">Save</button>
186
- <button data-action="testBuiltinProvider" data-arg="${p.id}" class="btn-ghost">Test</button>
187
- <button data-action="fetchBuiltinModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>
188
- <a href="${p.url}" target="_blank" class="btn-ghost" style="text-decoration:none;font-size:12px;">Keys ↗</a>`}
189
- </div>
190
- <div id="bp_status_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
191
- <div id="bp_models_${p.id}" style="margin-top:8px;display:none;">
192
- <span style="font-size:11px;color:var(--text-2);display:block;margin-bottom:4px;">Models (<span id="bp_mcount_${p.id}">0</span>):</span>
193
- <span id="bp_mtags_${p.id}"></span>
194
- </div>
195
- </div>
196
- </div>`;
197
- }).join('');
198
-
199
- try {
200
- const data = await getJSON('/api/providers');
201
- const customs = (data.providers || []).filter(p => !builtinIds.has(p.id) && p.id !== 'greptile');
202
- if (customs.length) {
203
- html += `<div style="font-size:11px;font-weight:600;color:var(--text-2);text-transform:uppercase;letter-spacing:0.08em;margin:14px 0 8px;padding:0 2px;">Custom Providers</div>`;
204
- html += customs.map(p => {
205
- const icon = PROVIDER_ICONS[p.id] || '🔌';
206
- const hasKey = p.hasKey;
207
- const badge = hasKey
208
- ? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">key set ✓</span>`
209
- : `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
210
- const modelCount = p.models?.length || 0;
211
- return `<div class="card" style="margin-bottom:8px;">
212
- <div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".cp-body">
213
- <span style="font-size:18px;width:24px;text-align:center;">${icon}</span>
214
- <div style="flex:1;">
215
- <div style="font-weight:600;font-size:13px;">${p.id}</div>
216
- <div style="font-size:11px;color:var(--text-2);">${p.baseUrl}${modelCount ? ' · ' + modelCount + ' models' : ''}</div>
217
- </div>
218
- ${badge}
219
- <span style="color:var(--text-2);font-size:12px;">▾</span>
220
- </div>
221
- <div class="cp-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
222
- <div style="display:flex;gap:8px;flex-wrap:wrap;">
223
- <input id="key_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;min-width:180px;" />
224
- <button data-action="saveKey" data-arg="${p.id}" class="btn-purple">Save</button>
225
- <button data-action="testKey" data-arg="${p.id}" class="btn-ghost">Test</button>
226
- <button data-action="fetchModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>
227
- </div>
228
- <div style="font-size:11px;color:var(--text-2);margin-top:6px;">Base URL: <code style="font-size:10px;">${p.baseUrl}</code></div>
229
- <div id="test_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
230
- <div id="mwrap_${p.id}" style="margin-top:8px;${modelCount ? '' : 'display:none;'}">
231
- <span style="font-size:11px;color:var(--text-2);">Models (<span id="mcount_${p.id}">${modelCount}</span>):</span>
232
- <span id="mtags_${p.id}">${(p.models||[]).map(m => '<span class="model-tag">' + (m.id||m) + '</span>').join('')}</span>
233
- </div>
234
- </div>
235
- </div>`;
236
- }).join('');
237
- }
238
- } catch {}
239
-
240
- list.innerHTML = html;
241
- }
242
-
243
- export async function saveBuiltinKey(providerId) {
244
- const inp = document.getElementById('bp_' + providerId);
245
- const key = inp?.value?.trim();
246
- if (!key && providerId !== 'openai-local') { showNotification('Paste an API key first', 'error'); return; }
247
- await postJSON('/api/providers/builtin/save', { providerId, apiKey: key || '' });
248
- if (inp) inp.value = '';
249
- showNotification('Key saved — fetching models…');
250
- await loadBuiltinProviders();
251
- try {
252
- const r = await postJSON('/api/providers/fetch-models', { providerId });
253
- if (r.ok) {
254
- const tags = document.getElementById('bp_mtags_' + providerId);
255
- const count = document.getElementById('bp_mcount_' + providerId);
256
- const wrap = document.getElementById('bp_models_' + providerId);
257
- const status = document.getElementById('bp_status_' + providerId);
258
- if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
259
- if (count) count.textContent = r.models.length;
260
- if (wrap) wrap.style.display = 'block';
261
- if (status) { status.style.color = 'var(--green)'; status.textContent = '✓ ' + r.models.length + ' models'; }
262
- showNotification('Key saved for ' + providerId + ' — ' + r.models.length + ' models ready');
263
- _loadAgents();
264
- } else {
265
- showNotification('Key saved — could not fetch models: ' + (r.error || 'unknown'), 'warning');
266
- }
267
- } catch(e) {
268
- showNotification('Key saved — model fetch failed: ' + e.message, 'warning');
269
- }
270
- }
271
-
272
- export async function testBuiltinProvider(providerId) {
273
- const statusEl = document.getElementById('bp_status_' + providerId);
274
- statusEl.textContent = 'Testing…';
275
- try {
276
- const r = await postJSON('/api/providers/builtin/test', { providerId });
277
- statusEl.style.color = r.ok ? 'var(--green)' : 'var(--red)';
278
- statusEl.textContent = r.ok ? '✓ Connected — ' + (r.model || 'OK') : '✗ ' + (r.error || 'Failed');
279
- } catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
280
- }
281
-
282
- export async function fetchBuiltinModels(providerId, btn) {
283
- const statusEl = document.getElementById('bp_status_' + providerId);
284
- const orig = btn.textContent;
285
- btn.textContent = 'Fetching…';
286
- btn.disabled = true;
287
- statusEl.textContent = '';
288
- try {
289
- const r = await postJSON('/api/providers/fetch-models', { providerId });
290
- if (r.ok) {
291
- const tags = document.getElementById('bp_mtags_' + providerId);
292
- const count = document.getElementById('bp_mcount_' + providerId);
293
- const wrap = document.getElementById('bp_models_' + providerId);
294
- if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
295
- if (count) count.textContent = r.models.length;
296
- if (wrap) wrap.style.display = 'block';
297
- statusEl.style.color = 'var(--green)';
298
- statusEl.textContent = '✓ ' + r.models.length + ' models fetched' + (r.note ? ' — ' + r.note : '');
299
- _loadAgents();
300
- } else {
301
- statusEl.style.color = 'var(--red)';
302
- statusEl.textContent = '✗ ' + (r.error || 'Failed');
303
- }
304
- } catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
305
- finally { btn.textContent = orig; btn.disabled = false; }
306
- }
307
-
308
- // ── Legacy custom provider list (secondary view) ───────────────────────────────
309
-
310
- export async function loadProviders() {
311
- const list = document.getElementById('providersList');
312
- if (!list) return;
313
- showLoading(list, 'Loading providers...');
314
- try {
315
- const data = await getJSON('/api/providers');
316
- const providers = data.providers || [];
317
- if (!providers.length) { showEmpty(list, 'No providers found. Check ~/.crewswarm/crewswarm.json'); return; }
318
- list.innerHTML = '';
319
- providers.forEach(p => {
320
- const icon = PROVIDER_ICONS[p.id] || '🔌';
321
- const hasKey = p.hasKey;
322
- const badgeColor = hasKey ? '#10b981' : 'var(--red-hi)';
323
- const badgeText = hasKey ? '✓ key set' : '✗ no key';
324
- const card = document.createElement('div');
325
- card.className = 'provider-card';
326
- card.innerHTML = `
327
- <div class="provider-header" data-toggle-sibling="open">
328
- <span style="font-size:20px;">${icon}</span>
329
- <div style="flex:1;">
330
- <strong style="font-size:15px;">${p.id}</strong>
331
- <span class="meta" style="margin-left:10px;">${p.baseUrl}</span>
332
- </div>
333
- <span class="provider-badge" style="background:${badgeColor}20; color:${badgeColor}; border:1px solid ${badgeColor}40;">${badgeText}</span>
334
- <span class="meta" style="margin-left:12px;">${p.models.length} model${p.models.length !== 1 ? 's' : ''}</span>
335
- <span style="color:#64748b; margin-left:8px;">▼</span>
336
- </div>
337
- <div class="provider-body">
338
- <div class="key-row">
339
- <input class="key-input" type="password" autocomplete="new-password" id="key_${p.id}" value="${p.maskedKey || ''}" placeholder="Paste API key…" />
340
- <button data-action="toggleKeyVis" data-arg="key_${p.id}" data-self="1" style="background:#334155; padding:6px 10px; font-size:12px;">👁</button>
341
- <button data-action="saveKey" data-arg="${p.id}" style="background:#6366f1; padding:6px 14px; font-size:12px;">Save</button>
342
- <button data-action="testKey" data-arg="${p.id}" style="background:#334155; padding:6px 10px; font-size:12px;">Test</button>
343
- <button data-action="fetchModels" data-arg="${p.id}" data-self="1" style="background:#0f766e; padding:6px 10px; font-size:12px;">↻ Fetch models</button>
344
- <span id="test_${p.id}"></span>
345
- </div>
346
- <div style="margin-bottom:8px;"><span class="meta">Base URL: </span><code style="font-size:11px; color:#94a3b8;">${p.baseUrl}</code></div>
347
- <div><span class="meta" style="display:block; margin-bottom:6px;">Models (<span id="mcount_${p.id}">${p.models.length}</span>):</span><span id="mtags_${p.id}">${p.models.map(m => '<span class="model-tag">' + m.id + '</span>').join('')}</span></div>
348
- ${p.models.length === 0 ? `<div class="meta" style="margin-top:8px; color:var(--amber);" id="mnone_${p.id}">No models yet — click ↻ Fetch models</div>` : ''}
349
- </div>
350
- `;
351
- list.appendChild(card);
352
- });
353
- } catch(e) { showError(list, 'Error: ' + e.message); }
354
- }
355
-
356
- export function toggleKeyVis(inputId, btn) {
357
- const inp = document.getElementById(inputId);
358
- inp.type = inp.type === 'password' ? 'text' : 'password';
359
- btn.textContent = inp.type === 'password' ? '👁' : '🙈';
360
- }
361
-
362
- export async function saveKey(providerId) {
363
- const inp = document.getElementById('key_' + providerId);
364
- const key = inp.value.trim();
365
- if (!key) { showNotification('Key is empty', true); return; }
366
- try {
367
- await postJSON('/api/providers/save', { providerId, apiKey: key });
368
- showNotification('Saved key for ' + providerId);
369
- loadProviders();
370
- _loadAgents();
371
- } catch(e) { showNotification('Failed: ' + e.message, true); }
372
- }
373
-
374
- export async function testKey(providerId) {
375
- const statusEl = document.getElementById('test_' + providerId);
376
- statusEl.textContent = 'testing…';
377
- statusEl.className = 'meta';
378
- try {
379
- const r = await postJSON('/api/providers/test', { providerId });
380
- statusEl.textContent = r.ok ? '✓ ' + (r.model || 'ok') : '✗ ' + r.error;
381
- statusEl.className = r.ok ? 'test-ok' : 'test-err';
382
- } catch(e) { statusEl.textContent = '✗ ' + e.message; statusEl.className = 'test-err'; }
383
- }
384
-
385
- export async function fetchModels(providerId, btn) {
386
- const statusEl = document.getElementById('test_' + providerId);
387
- const origText = btn.textContent;
388
- btn.textContent = 'Fetching…';
389
- btn.disabled = true;
390
- if (statusEl) statusEl.textContent = '';
391
- try {
392
- const r = await postJSON('/api/providers/fetch-models', { providerId });
393
- if (r.ok) {
394
- const tags = document.getElementById('mtags_' + providerId);
395
- const count = document.getElementById('mcount_' + providerId);
396
- const none = document.getElementById('mnone_' + providerId);
397
- const wrap = document.getElementById('mwrap_' + providerId);
398
- if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
399
- if (count) count.textContent = r.models.length;
400
- if (none) none.style.display = 'none';
401
- if (wrap) wrap.style.display = 'block';
402
- if (statusEl) { statusEl.textContent = '✓ ' + r.models.length + ' models'; statusEl.className = 'test-ok'; }
403
- _loadAgents();
404
- } else {
405
- if (statusEl) { statusEl.textContent = '✗ ' + r.error; statusEl.className = 'test-err'; }
406
- }
407
- } catch(e) {
408
- if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.className = 'test-err'; }
409
- }
410
- finally { btn.textContent = origText; btn.disabled = false; }
411
- }
412
-
413
- // ── Add provider form wiring (called once on DOMContentLoaded) ─────────────────
414
-
415
- export function initAddProviderForm() {
416
- const addBtn = document.getElementById('addProviderBtn');
417
- if (addBtn) {
418
- addBtn.onclick = () => {
419
- const form = document.getElementById('addProviderForm');
420
- form.style.display = 'block';
421
- setTimeout(() => form.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
422
- const firstInput = form.querySelector('input');
423
- if (firstInput) setTimeout(() => firstInput.focus(), 150);
424
- };
425
- }
426
- const cancelBtn = document.getElementById('apCancelBtn');
427
- if (cancelBtn) {
428
- cancelBtn.onclick = () => {
429
- document.getElementById('addProviderForm').style.display = 'none';
430
- };
431
- }
432
- const saveBtn = document.getElementById('apSaveBtn');
433
- if (saveBtn) {
434
- saveBtn.onclick = async () => {
435
- const id = document.getElementById('apId').value.trim();
436
- const baseUrl = document.getElementById('apBaseUrl').value.trim();
437
- const apiKey = document.getElementById('apKey').value.trim();
438
- const api = document.getElementById('apApi').value;
439
- if (!id || !baseUrl) { showNotification('ID and Base URL are required', true); return; }
440
- try {
441
- await postJSON('/api/providers/add', { id, baseUrl, apiKey, api });
442
- showNotification('Provider added: ' + id);
443
- document.getElementById('addProviderForm').style.display = 'none';
444
- loadBuiltinProviders();
445
- } catch(e) { showNotification('Failed: ' + e.message, true); }
446
- };
447
- }
448
- const refreshBtn = document.getElementById('refreshProvidersBtn');
449
- if (refreshBtn) refreshBtn.onclick = loadBuiltinProviders;
450
- }
@@ -1,185 +0,0 @@
1
- /**
2
- * PM Loop controls tab — extracted from app.js
3
- * Deps: showNotification (core/dom), loadBuildProjectPicker, getBuildProjectById (projects-tab)
4
- */
5
- import { showNotification } from '../core/dom.js';
6
- import { loadBuildProjectPicker, getBuildProjectById } from './projects-tab.js';
7
-
8
- // ── PM Loop controls ──────────────────────────────────────────────────────
9
- let pmPoller = null;
10
-
11
- function getSelectedProjectId() {
12
- const sel = document.getElementById('buildProjectPicker');
13
- return sel ? sel.value : '';
14
- }
15
-
16
- export async function checkPmStatus() {
17
- try {
18
- const projectId = getSelectedProjectId();
19
- const qs = projectId ? '?projectId=' + encodeURIComponent(projectId) : '';
20
- const s = await fetch('/api/pm-loop/status' + qs).then(r => r.json());
21
- const badge = document.getElementById('pmLoopBadge');
22
- const startBtn = document.getElementById('pmStartBtn');
23
- const dryBtn = document.getElementById('pmDryRunBtn');
24
- const logBox = document.getElementById('pmLiveLog');
25
- if (s.running) {
26
- badge.textContent = 'running (pid ' + s.pid + ')';
27
- badge.classList.add('running');
28
- startBtn.disabled = true;
29
- dryBtn.disabled = true;
30
- logBox.style.display = 'block';
31
- if (!pmPoller) startPmLogPoller();
32
- } else {
33
- if (badge.textContent.startsWith('running')) {
34
- badge.textContent = 'idle';
35
- badge.classList.remove('running');
36
- startBtn.disabled = false;
37
- dryBtn.disabled = false;
38
- }
39
- }
40
- } catch(_) {}
41
- }
42
-
43
- function startPmLogPoller() {
44
- if (pmPoller) return;
45
- pmPoller = setInterval(async () => {
46
- try {
47
- const lg = await fetch('/api/pm-loop/log').then(r2 => r2.json());
48
- const logBox = document.getElementById('pmLiveLog');
49
- const badge = document.getElementById('pmLoopBadge');
50
- const startBtn = document.getElementById('pmStartBtn');
51
- const dryBtn = document.getElementById('pmDryRunBtn');
52
- if (lg.lines && lg.lines.length) {
53
- logBox.textContent = lg.lines.map(l => {
54
- if (l.event === 'finish') return `🏁 Done ✓${l.done} ✗${l.failed} ⏳${l.pending}`;
55
- if (l.event === 'stopped_by_file') return '⛔ Stopped by user';
56
- if (l.event === 'all_done') return `🏁 All ${l.total} items complete!`;
57
- const icon = l.status === 'done' ? '✅' : l.status === 'failed' ? '❌' : l.event ? '·' : '·';
58
- const txt = l.item ? `${l.item.substring(0, 60)}` : (l.event || '');
59
- return `${icon} ${txt}`;
60
- }).join('\n');
61
- logBox.scrollTop = logBox.scrollHeight;
62
- const last = lg.lines[lg.lines.length - 1];
63
- if (last && (last.event === 'finish' || last.event === 'all_done' || last.event === 'stopped_by_file')) {
64
- clearInterval(pmPoller); pmPoller = null;
65
- badge.textContent = last.event === 'all_done' ? '✓ complete' : 'idle';
66
- badge.classList.remove('running');
67
- startBtn.disabled = false; dryBtn.disabled = false;
68
- }
69
- }
70
- } catch(_){}
71
- }, 5000);
72
- }
73
-
74
- export async function startPmLoop(dryRun = false) {
75
- const projectId = getSelectedProjectId();
76
- const badge = document.getElementById('pmLoopBadge');
77
- const status = document.getElementById('pmStatus');
78
- const logBox = document.getElementById('pmLiveLog');
79
- const startBtn = document.getElementById('pmStartBtn');
80
- const dryBtn = document.getElementById('pmDryRunBtn');
81
- const proj = getBuildProjectById(projectId);
82
- if (!projectId) {
83
- showNotification('Select a project first from the Project picker above', true);
84
- return;
85
- }
86
- try {
87
- badge.textContent = dryRun ? 'dry run...' : 'starting...';
88
- badge.classList.add('running');
89
- startBtn.disabled = true;
90
- dryBtn.disabled = true;
91
- logBox.style.display = 'block';
92
- logBox.textContent = '⚙ Starting PM Loop for ' + (proj ? proj.name : projectId) + (dryRun ? ' (dry run)' : '') + '...\n';
93
- const resp = await fetch('/api/pm-loop/start', {
94
- method: 'POST',
95
- headers: {'content-type':'application/json'},
96
- body: JSON.stringify({
97
- dryRun, projectId,
98
- pmOptions: {
99
- useQA: document.getElementById('pmOptQA')?.checked ?? true,
100
- useSecurity: document.getElementById('pmOptSecurity')?.checked ?? true,
101
- useSpecialists: document.getElementById('pmOptSpecialists')?.checked ?? true,
102
- selfExtend: document.getElementById('pmOptSelfExtend')?.checked ?? true,
103
- maxItems: parseInt(document.getElementById('pmOptMaxItems')?.value || '200'),
104
- taskTimeoutMin: parseInt(document.getElementById('pmOptTimeout')?.value || '10'),
105
- extendEveryN: parseInt(document.getElementById('pmOptExtendN')?.value || '5'),
106
- pauseSec: parseInt(document.getElementById('pmOptPause')?.value || '5'),
107
- maxRetries: parseInt(document.getElementById('pmOptMaxRetries')?.value || '2'),
108
- coderAgent: document.getElementById('pmOptCoder')?.value.trim() || 'crew-coder',
109
- }
110
- })
111
- });
112
- const r = await resp.json();
113
- if (resp.status === 409 || r.alreadyRunning) {
114
- logBox.textContent = '⚠ Already running (pid ' + r.pid + '). Watch the log below.\n';
115
- badge.textContent = 'running (pid ' + r.pid + ')';
116
- showNotification('PM Loop already running for this project (pid ' + r.pid + ')', true);
117
- startPmLogPoller();
118
- return;
119
- }
120
- logBox.textContent += '✅ Spawned (pid ' + r.pid + '). PM is reading roadmap...\n';
121
- badge.textContent = 'running (pid ' + r.pid + ')';
122
- showNotification('PM Loop started' + (dryRun ? ' (dry run)' : '') + ' for ' + (proj ? proj.name : projectId));
123
- startPmLogPoller();
124
- } catch (e) {
125
- showNotification('PM Loop failed: ' + e.message, true);
126
- badge.textContent = 'idle';
127
- badge.classList.remove('running');
128
- startBtn.disabled = false;
129
- dryBtn.disabled = false;
130
- }
131
- }
132
-
133
- export async function stopPmLoop() {
134
- const projectId = getSelectedProjectId();
135
- try {
136
- await fetch('/api/pm-loop/stop', { method: 'POST', headers: {'content-type':'application/json'}, body: JSON.stringify({ projectId }) });
137
- showNotification('Stop signal sent — PM will finish current task then halt.');
138
- document.getElementById('pmLoopBadge').textContent = 'stopping...';
139
- } catch (e) { showNotification('Stop failed: ' + e.message, true); }
140
- }
141
-
142
- export async function toggleRoadmap() {
143
- const panel = document.getElementById('pmRoadmapPanel');
144
- if (panel.style.display !== 'none') { panel.style.display = 'none'; return; }
145
- try {
146
- // Ensure projects are loaded first
147
- await loadBuildProjectPicker();
148
-
149
- const projectId = getSelectedProjectId();
150
- const proj = getBuildProjectById(projectId);
151
- console.log('[toggleRoadmap] projectId:', projectId, 'proj:', proj);
152
-
153
- // If we have a project selected, fetch its roadmap file directly via file API
154
- let content = '';
155
- if (proj && proj.roadmapFile) {
156
- console.log('[toggleRoadmap] Fetching project roadmap:', proj.roadmapFile);
157
- const r = await fetch('/api/file-content?path=' + encodeURIComponent(proj.roadmapFile)).then(r2 => r2.json());
158
- content = r.content || '(empty)';
159
- } else {
160
- console.log('[toggleRoadmap] Using default roadmap (no project selected or no roadmapFile)');
161
- const r = await fetch('/api/pm-loop/roadmap').then(r2 => r2.json());
162
- content = r.content || '(empty)';
163
- }
164
- panel.textContent = content;
165
- panel.style.display = 'block';
166
- } catch (e) {
167
- console.error('[toggleRoadmap] Error:', e);
168
- panel.textContent = 'Could not load roadmap: ' + e.message;
169
- panel.style.display = 'block';
170
- }
171
- }
172
-
173
- export function initPmLoopTab() {
174
- document.getElementById('pmStartBtn').onclick = () => startPmLoop(false);
175
- document.getElementById('pmDryRunBtn').onclick = () => startPmLoop(true);
176
- document.getElementById('pmStopBtn').onclick = stopPmLoop;
177
- document.getElementById('pmRoadmapBtn').onclick = toggleRoadmap;
178
- // Check PM status after picker loads so we use the right projectId
179
- loadBuildProjectPicker().then(() => checkPmStatus());
180
- // Re-check status whenever the project picker changes
181
- document.getElementById('buildProjectPicker').addEventListener('change', () => {
182
- if (pmPoller) { clearInterval(pmPoller); pmPoller = null; }
183
- checkPmStatus();
184
- });
185
- }