crewswarm 0.9.2 → 0.9.3

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 (207) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
  3. package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
  4. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  5. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  6. package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
  7. package/apps/dashboard/dist/index.html +82 -11
  8. package/apps/vibe/README.md +2 -2
  9. package/apps/vibe/package.json +1 -1
  10. package/apps/vibe/server.mjs +3 -3
  11. package/crew-lead.mjs +34 -4
  12. package/lib/bridges/gateway-ws.mjs +4 -0
  13. package/lib/crew-lead/chat-handler.mjs +34 -0
  14. package/lib/crew-lead/http-server.mjs +55 -14
  15. package/lib/crew-lead/llm-caller.mjs +24 -8
  16. package/lib/crew-lead/prompts.mjs +7 -0
  17. package/lib/crew-lead/wave-dispatcher.mjs +15 -3
  18. package/lib/crew-lead/ws-router.mjs +219 -27
  19. package/lib/engines/engine-registry.mjs +9 -0
  20. package/lib/engines/rt-envelope.mjs +1 -0
  21. package/lib/engines/runners.mjs +5 -2
  22. package/lib/runtime/paths.mjs +12 -8
  23. package/package.json +35 -15
  24. package/scripts/capture-build-flow.mjs +118 -0
  25. package/scripts/coverage-report.mjs +209 -0
  26. package/scripts/coverage-summary.mjs +47 -0
  27. package/scripts/dashboard-validation.mjs +74 -0
  28. package/scripts/dashboard.mjs +560 -70
  29. package/scripts/live-bridge-matrix.mjs +79 -0
  30. package/scripts/live-cli-matrix.mjs +166 -0
  31. package/scripts/live-crewchat-check.mjs +42 -0
  32. package/scripts/live-engine-matrix.mjs +50 -0
  33. package/scripts/live-provider-failover-matrix.mjs +107 -0
  34. package/scripts/live-provider-matrix.mjs +228 -0
  35. package/scripts/restart-all-from-repo.sh +4 -4
  36. package/scripts/smoke-dispatch.mjs +4 -1
  37. package/scripts/test-blast-radius.mjs +204 -0
  38. package/scripts/test-report-summary.mjs +88 -0
  39. package/scripts/test-reporter.mjs +651 -0
  40. package/scripts/test-rerun.mjs +136 -0
  41. package/scripts/tmux-bridge +130 -0
  42. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  43. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  44. package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
  45. package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
  46. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  47. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  48. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  49. package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
  50. package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
  51. package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
  52. package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
  53. package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
  54. package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
  55. package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
  56. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  57. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  58. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  59. package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
  60. package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
  61. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  62. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  63. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  64. package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
  65. package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
  66. package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
  67. package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
  68. package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
  69. package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
  70. package/apps/dashboard/dist/index.html.br +0 -0
  71. package/apps/dashboard/dist/index.html.gz +0 -0
  72. package/apps/dashboard/index.html +0 -6529
  73. package/apps/dashboard/package.json +0 -15
  74. package/apps/dashboard/src/app.js +0 -2828
  75. package/apps/dashboard/src/app.js.br +0 -0
  76. package/apps/dashboard/src/app.js.gz +0 -0
  77. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  78. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  79. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  80. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  81. package/apps/dashboard/src/cli-process.js +0 -208
  82. package/apps/dashboard/src/cli-process.js.br +0 -0
  83. package/apps/dashboard/src/cli-process.js.gz +0 -0
  84. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  85. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  86. package/apps/dashboard/src/core/api.js +0 -18
  87. package/apps/dashboard/src/core/api.js.br +0 -0
  88. package/apps/dashboard/src/core/dom.js +0 -228
  89. package/apps/dashboard/src/core/dom.js.br +0 -0
  90. package/apps/dashboard/src/core/state.js +0 -91
  91. package/apps/dashboard/src/core/state.js.br +0 -0
  92. package/apps/dashboard/src/core/task-manager.js +0 -134
  93. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  94. package/apps/dashboard/src/orchestration-status.js +0 -127
  95. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  96. package/apps/dashboard/src/setup-wizard.js +0 -562
  97. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  98. package/apps/dashboard/src/styles.css +0 -2085
  99. package/apps/dashboard/src/styles.css.br +0 -0
  100. package/apps/dashboard/src/styles.css.gz +0 -0
  101. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  102. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  103. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  104. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  105. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  106. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  107. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  108. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  109. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  110. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  111. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  112. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  113. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  114. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  115. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  116. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  117. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  118. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  119. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  120. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  121. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  122. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  123. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  125. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  127. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  129. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  131. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  133. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  135. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  137. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  139. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  140. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  141. package/apps/vibe/.crew/cost.json +0 -17
  142. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  143. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  144. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  145. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  146. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  147. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  148. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  149. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  150. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  151. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  152. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  153. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  154. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  155. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  156. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  157. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  158. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  159. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  160. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  161. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  162. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  163. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  164. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  172. package/apps/vibe/.crew/sandbox.json +0 -7
  173. package/apps/vibe/.crew/session.json +0 -330
  174. package/apps/vibe/.crew/training-data.jsonl +0 -0
  175. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  176. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  177. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  178. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  179. package/apps/vibe/ARCHITECTURE.md +0 -3393
  180. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  181. package/apps/vibe/ROADMAP.md +0 -41
  182. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  183. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  184. package/apps/vibe/capture-demo.mjs +0 -160
  185. package/apps/vibe/capture-full-demo.mjs +0 -255
  186. package/apps/vibe/capture-quickstart.mjs +0 -256
  187. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  188. package/apps/vibe/capture-vibe-video.mjs +0 -260
  189. package/apps/vibe/check-buttons.js +0 -41
  190. package/apps/vibe/diagnose.html +0 -106
  191. package/apps/vibe/fix-buttons.js +0 -103
  192. package/apps/vibe/index.html +0 -3404
  193. package/apps/vibe/package-lock.json +0 -920
  194. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  195. package/apps/vibe/src/main.js +0 -2940
  196. package/apps/vibe/src/register-all-languages.js +0 -98
  197. package/apps/vibe/start-studio.sh +0 -11
  198. package/apps/vibe/test/accessibility-tests.js +0 -77
  199. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  200. package/apps/vibe/test/performance-tests.js +0 -120
  201. package/apps/vibe/test/security-tests.js +0 -213
  202. package/apps/vibe/tests/e2e.local.mjs +0 -54
  203. package/apps/vibe/tests/server.smoke.mjs +0 -106
  204. package/apps/vibe/update_website.mjs +0 -74
  205. package/apps/vibe/vite.config.js +0 -19
  206. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  207. 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
- }