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,390 +0,0 @@
1
- /**
2
- * Token usage widget and tool matrix — extracted from app.js
3
- * Deps: getJSON (core/api), escHtml, showLoading, showEmpty, showError (core/dom)
4
- */
5
- import { getJSON } from '../core/api.js';
6
- import { showLoading, showEmpty, showError, showNotification } from '../core/dom.js';
7
-
8
- // ── Token usage widget ────────────────────────────────────────────────────────
9
-
10
- // Approximate cost per 1M tokens by model prefix (input / output)
11
- // Keys matched via .includes() — more specific keys must come before general ones
12
- const MODEL_COST_PER_M = {
13
- // ── xAI Grok (2026 pricing) ───────────────────────────────────────────────
14
- 'grok-4-1-fast': [0.20, 0.50], // grok-4.1-fast + non-reasoning variant
15
- 'grok-4-fast': [0.20, 0.50],
16
- 'grok-4': [3.00, 15.00],
17
- 'grok-3-mini': [0.30, 0.50],
18
- 'grok-3': [3.00, 15.00],
19
- 'grok-code-fast': [0.20, 1.50],
20
- 'grok-beta': [5.00, 15.00], // legacy
21
- // ── OpenAI gpt-5.x (via openai or openai-local proxy) ───────────────────
22
- 'gpt-5.3-codex': [2.50, 20.00], // estimate — newer than 5.2
23
- 'gpt-5.2-codex': [1.75, 14.00],
24
- 'gpt-5.2': [1.75, 14.00],
25
- 'gpt-5.1-codex-max': [2.50, 20.00], // estimate — max tier
26
- 'gpt-5.1-codex-mini': [0.25, 2.00],
27
- 'gpt-5.1-codex': [1.25, 10.00],
28
- 'gpt-5.1': [1.25, 10.00],
29
- 'gpt-5-codex': [1.25, 10.00],
30
- 'gpt-5-nano': [0.15, 0.60], // estimate
31
- 'gpt-5': [1.25, 10.00],
32
- 'codex-mini': [0.25, 2.00],
33
- // ── OpenAI legacy ────────────────────────────────────────────────────────
34
- 'gpt-oss-120b': [0.90, 0.90], // Groq-hosted OSS model, estimate
35
- 'gpt-oss-20b': [0.20, 0.20], // estimate
36
- 'gpt-4o-mini': [0.15, 0.60],
37
- 'gpt-4o': [2.50, 10.00],
38
- 'gpt-4': [30.0, 60.00],
39
- // ── DeepSeek ─────────────────────────────────────────────────────────────
40
- 'deepseek-reasoner': [0.70, 2.50], // R1
41
- 'deepseek-chat': [0.27, 1.10],
42
- // ── Mistral ──────────────────────────────────────────────────────────────
43
- 'mistral-large': [0.50, 1.50], // mistral-large-latest = Large 3 2512 (Dec 2025)
44
- 'mistral-small': [0.10, 0.30],
45
- // ── Google Gemini 3 (preview, 2026) ──────────────────────────────────────
46
- 'gemini-3.1-pro': [2.50, 15.00], // 3.1 Pro preview
47
- 'gemini-3.1-flash': [0.075, 0.30], // 3.1 Flash preview
48
- 'gemini-3-pro': [2.50, 15.00], // Gemini 3 Pro preview
49
- 'gemini-3-flash': [0.075, 0.30], // Gemini 3 Flash preview
50
- // ── Google Gemini 2.5 ────────────────────────────────────────────────────
51
- 'gemini-2.5-pro': [1.25, 10.00],
52
- 'gemini-2.5-flash-lite': [0.04, 0.15], // Flash Lite (lower cost)
53
- 'gemini-2.5-flash': [0.075, 0.30],
54
- 'gemini-2.0-flash-lite': [0.075, 0.30],
55
- 'gemini-2.0-flash': [0.10, 0.40],
56
- // ── Anthropic Claude ─────────────────────────────────────────────────────
57
- 'claude-opus-4': [15.0, 75.00],
58
- 'claude-sonnet-4': [3.00, 15.00],
59
- 'claude-haiku-4': [0.80, 4.00],
60
- 'claude-3-5-haiku': [0.80, 4.00],
61
- 'claude-3-haiku': [0.25, 1.25],
62
- 'claude-3-5-sonnet': [3.00, 15.00],
63
- 'claude-3-7-sonnet': [3.00, 15.00],
64
- // ── Groq-hosted (inference pricing) ──────────────────────────────────────
65
- 'kimi-k2-instruct': [1.00, 3.00],
66
- 'kimi-k2': [0.60, 2.50],
67
- 'llama-4-maverick': [0.50, 0.77],
68
- 'llama-4-scout': [0.11, 0.34],
69
- 'llama-3.3-70b': [0.59, 0.79],
70
- 'llama-3.1-70b': [0.59, 0.79],
71
- 'llama3.1-70b': [0.59, 0.79],
72
- 'llama-3.1-8b': [0.05, 0.08],
73
- 'llama3.1-8b': [0.10, 0.10], // Cerebras pricing
74
- 'qwen3-32b': [0.29, 0.39],
75
- 'llama-guard': [0.20, 0.20],
76
- // ── Perplexity ───────────────────────────────────────────────────────────
77
- 'sonar-pro': [3.00, 15.00],
78
- 'sonar': [1.00, 1.00],
79
- // ── OpenCode free models ──────────────────────────────────────────────────
80
- 'big-pickle': [0.00, 0.00], // free
81
- 'trinity-large-preview': [0.00, 0.00], // free
82
- 'minimax-m2.5-free': [0.00, 0.00], // free
83
- 'glm-': [0.10, 0.10], // estimate
84
- 'minimax': [0.30, 1.00], // estimate
85
- // ── Default fallback ─────────────────────────────────────────────────────
86
- 'default': [1.00, 3.00],
87
- };
88
-
89
- export function estimateCost(byModel) {
90
- let total = 0;
91
- for (const [model, stats] of Object.entries(byModel || {})) {
92
- const rateKey = Object.keys(MODEL_COST_PER_M).find(k => model.toLowerCase().includes(k)) || 'default';
93
- const [inputRate, outputRate] = MODEL_COST_PER_M[rateKey];
94
- total += (stats.prompt / 1e6) * inputRate + (stats.completion / 1e6) * outputRate;
95
- }
96
- return total;
97
- }
98
-
99
- export async function loadTokenUsage() {
100
- const box = document.getElementById('tokenUsageWidget');
101
- if (!box) return;
102
- const u = await getJSON('/api/token-usage').catch(() => ({}));
103
- const totalTokens = (u.prompt || 0) + (u.completion || 0);
104
- const cost = estimateCost(u.byModel);
105
-
106
- // ── Totals row ────────────────────────────────────────────────────────────
107
- let html =
108
- '<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:12px;">' +
109
- '<div style="text-align:center;">' +
110
- '<div style="font-size:20px;font-weight:700;color:var(--accent);">' + (u.calls||0).toLocaleString() + '</div>' +
111
- '<div style="font-size:11px;color:var(--text-3);margin-top:2px;">LLM calls</div>' +
112
- '</div>' +
113
- '<div style="text-align:center;">' +
114
- '<div style="font-size:20px;font-weight:700;color:var(--green);">' + (totalTokens/1000).toFixed(1) + 'k</div>' +
115
- '<div style="font-size:11px;color:var(--text-3);margin-top:2px;">total tokens</div>' +
116
- '</div>' +
117
- '<div style="text-align:center;">' +
118
- '<div style="font-size:20px;font-weight:700;color:var(--yellow);">$' + cost.toFixed(4) + '</div>' +
119
- '<div style="font-size:11px;color:var(--text-3);margin-top:2px;">est. cost (all-time)</div>' +
120
- '</div>' +
121
- '</div>';
122
-
123
- // ── Daily history ─────────────────────────────────────────────────────────
124
- const byDay = u.byDay || {};
125
- const days = Object.keys(byDay).sort().reverse().slice(0, 14);
126
- if (days.length) {
127
- const maxCost = Math.max(...days.map(function(d){ return estimateCost(byDay[d].byModel || {}); }), 0.0001);
128
- html += '<div style="font-size:11px;font-weight:600;color:var(--text-2);margin:12px 0 6px;">Daily cost (last ' + days.length + ' days)</div>';
129
- html += '<div style="display:flex;flex-direction:column;gap:3px;">';
130
- days.forEach(function(day) {
131
- const ds = byDay[day];
132
- const dc = estimateCost(ds.byModel || {});
133
- const pct = Math.max((dc / maxCost) * 100, 2);
134
- const tok = ((ds.prompt||0) + (ds.completion||0)) / 1000;
135
- const isToday = day === new Date().toISOString().slice(0, 10);
136
- html += '<div style="display:flex;align-items:center;gap:8px;font-size:11px;">' +
137
- '<span style="width:70px;color:var(--text-3);flex-shrink:0;">' + (isToday ? 'today' : day.slice(5)) + '</span>' +
138
- '<div style="flex:1;background:var(--bg-1);border-radius:3px;height:14px;overflow:hidden;">' +
139
- '<div style="width:' + pct.toFixed(1) + '%;height:100%;background:' + (isToday ? 'var(--accent)' : 'var(--green)') + ';border-radius:3px;"></div>' +
140
- '</div>' +
141
- '<span style="width:52px;text-align:right;color:var(--yellow);font-weight:600;">$' + dc.toFixed(4) + '</span>' +
142
- '<span style="width:44px;text-align:right;color:var(--text-3);">' + tok.toFixed(1) + 'k</span>' +
143
- '</div>';
144
- });
145
- html += '</div>';
146
- } else {
147
- html += '<div style="font-size:11px;color:var(--text-3);margin-top:8px;">No daily history yet — data accumulates with next LLM call after restart.</div>';
148
- }
149
-
150
- // ── By model (all-time) ───────────────────────────────────────────────────
151
- if (Object.keys(u.byModel||{}).length) {
152
- html += '<div style="font-size:11px;color:var(--text-3);margin:12px 0 6px;">By model (all-time)</div>';
153
- Object.entries(u.byModel||{})
154
- .sort((a,b) => (b[1].prompt+b[1].completion) - (a[1].prompt+a[1].completion))
155
- .forEach(function(entry) {
156
- const model = entry[0], s = entry[1];
157
- const rateKey = Object.keys(MODEL_COST_PER_M).find(function(k){ return model.toLowerCase().includes(k); }) || 'default';
158
- const rates = MODEL_COST_PER_M[rateKey];
159
- const mc = (s.prompt/1e6)*rates[0] + (s.completion/1e6)*rates[1];
160
- html += '<div style="display:flex;justify-content:space-between;font-size:11px;padding:3px 0;border-bottom:1px solid var(--border);">' +
161
- '<code style="color:var(--accent);">' + model + '</code>' +
162
- '<span style="color:var(--text-2);">' + ((s.prompt+s.completion)/1000).toFixed(1) + 'k tok · $' + mc.toFixed(4) + '</span>' +
163
- '</div>';
164
- });
165
- }
166
- box.innerHTML = html;
167
- }
168
-
169
- export async function loadOcStats(reportOcCost) {
170
- const box = document.getElementById('ocStatsWidget');
171
- if (!box) return;
172
- const days = document.getElementById('ocStatsDays')?.value || '14';
173
- if (reportOcCost) reportOcCost(null);
174
- showLoading(box);
175
- try {
176
- const d = await getJSON('/api/opencode-stats?days=' + days);
177
- if (!d.ok || !Object.keys(d.byDay||{}).length) {
178
- showEmpty(box, d.error || 'No OpenCode data found');
179
- return;
180
- }
181
- const byDay = d.byDay;
182
- const sortedDays = Object.keys(byDay).sort().reverse();
183
- const totalCost = sortedDays.reduce(function(s,day){ return s + byDay[day].cost; }, 0);
184
- const totalIn = sortedDays.reduce(function(s,day){ return s + byDay[day].input_tok; }, 0);
185
- const totalOut = sortedDays.reduce(function(s,day){ return s + byDay[day].output_tok; }, 0);
186
- const totalCalls= sortedDays.reduce(function(s,day){ return s + byDay[day].calls; }, 0);
187
- const maxCost = Math.max(...sortedDays.map(function(d){ return byDay[d].cost; }), 0.0001);
188
-
189
- let html = '<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:16px;">' +
190
- '<div style="text-align:center;"><div style="font-size:18px;font-weight:700;color:var(--yellow);">$' + totalCost.toFixed(4) + '</div><div style="font-size:11px;color:var(--text-3);">total cost</div></div>' +
191
- '<div style="text-align:center;"><div style="font-size:18px;font-weight:700;color:var(--accent);">' + totalCalls.toLocaleString() + '</div><div style="font-size:11px;color:var(--text-3);">messages</div></div>' +
192
- '<div style="text-align:center;"><div style="font-size:18px;font-weight:700;color:var(--green);">' + (totalIn/1e6).toFixed(1) + 'M</div><div style="font-size:11px;color:var(--text-3);">input tokens</div></div>' +
193
- '<div style="text-align:center;"><div style="font-size:18px;font-weight:700;color:var(--green);">' + (totalOut/1e6).toFixed(2) + 'M</div><div style="font-size:11px;color:var(--text-3);">output tokens</div></div>' +
194
- '</div>';
195
-
196
- // Daily bars
197
- html += '<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:16px;">';
198
- const today = new Date().toISOString().slice(0,10);
199
- sortedDays.forEach(function(day) {
200
- const ds = byDay[day];
201
- const pct = Math.max((ds.cost / maxCost) * 100, ds.cost > 0 ? 2 : 0);
202
- const isToday = day === today;
203
- const tok = (ds.input_tok + ds.output_tok) / 1e6;
204
- html += '<div style="display:flex;align-items:center;gap:8px;font-size:11px;">' +
205
- '<span style="width:70px;color:var(--text-3);flex-shrink:0;">' + (isToday ? 'today' : day.slice(5)) + '</span>' +
206
- '<div style="flex:1;background:var(--bg-1);border-radius:3px;height:16px;overflow:hidden;">' +
207
- '<div style="width:' + pct.toFixed(1) + '%;height:100%;background:' + (isToday ? 'var(--accent)' : 'var(--green)') + ';border-radius:3px;opacity:0.85;"></div>' +
208
- '</div>' +
209
- '<span style="width:60px;text-align:right;color:var(--yellow);font-weight:600;">$' + ds.cost.toFixed(4) + '</span>' +
210
- '<span style="width:50px;text-align:right;color:var(--text-3);">' + tok.toFixed(2) + 'M</span>' +
211
- '<span style="width:36px;text-align:right;color:var(--text-3);">' + ds.calls + '</span>' +
212
- '</div>';
213
- });
214
- html += '</div>';
215
-
216
- // All models across period
217
- const allModels = {};
218
- sortedDays.forEach(function(day) {
219
- Object.entries(byDay[day].byModel||{}).forEach(function(e) {
220
- const m = e[0], s = e[1];
221
- if (!allModels[m]) allModels[m] = { cost:0, input_tok:0, output_tok:0, calls:0 };
222
- allModels[m].cost += s.cost;
223
- allModels[m].input_tok += s.input_tok;
224
- allModels[m].output_tok += s.output_tok;
225
- allModels[m].calls += s.calls;
226
- });
227
- });
228
- const sortedModels = Object.entries(allModels).sort(function(a,b){ return b[1].cost - a[1].cost; });
229
- if (sortedModels.length) {
230
- html += '<div style="font-size:11px;color:var(--text-3);margin-bottom:6px;">By model</div>';
231
- sortedModels.forEach(function(e) {
232
- const m = e[0], s = e[1];
233
- const tok = (s.input_tok + s.output_tok) / 1e6;
234
- html += '<div style="display:flex;justify-content:space-between;align-items:center;font-size:11px;padding:3px 0;border-bottom:1px solid var(--border);">' +
235
- '<code style="color:var(--accent);">' + m + '</code>' +
236
- '<span style="color:var(--text-2);">' + tok.toFixed(2) + 'M tok · ' + s.calls + ' calls · ' +
237
- '<span style="color:var(--yellow);font-weight:600;">$' + s.cost.toFixed(4) + '</span>' +
238
- '</span>' +
239
- '</div>';
240
- });
241
- }
242
- if (reportOcCost) reportOcCost(totalCost);
243
- box.innerHTML = html;
244
- } catch(e) {
245
- showError(box, 'Error: ' + e.message);
246
- }
247
- }
248
-
249
- export async function checkCrewLeadStatus() {
250
- try {
251
- const d = await getJSON('/api/crew-lead/status');
252
- const badge = document.getElementById('crewLeadBadge');
253
- if (d.online) {
254
- badge.textContent = '● online'; badge.className = 'status-badge status-running';
255
- } else {
256
- badge.textContent = '● offline'; badge.className = 'status-badge status-stopped';
257
- }
258
- } catch {}
259
- }
260
-
261
- // ── Task lifecycle (telemetry schema 1.1) ────────────────────────────────────────
262
- export function renderTaskLifecycle(events) {
263
- const el = document.getElementById('taskLifecycleContainer');
264
- if (!el) return;
265
- events = events || [];
266
- if (!events.length) {
267
- el.innerHTML = '<div class="card" style="padding:12px;"><div class="meta" style="font-size:12px;">Recent task lifecycle (dispatched → completed/failed/cancelled). Dispatch a task to see events.</div></div>';
268
- return;
269
- }
270
- const rows = events.slice().reverse().slice(0, 15).map(ev => {
271
- const d = ev.data || {};
272
- const phase = d.phase || '';
273
- const color = phase === 'completed' ? 'var(--green)' : phase === 'failed' || phase === 'cancelled' ? 'var(--red)' : 'var(--accent)';
274
- const time = (ev.occurredAt || '').replace('T', ' ').slice(0, 19);
275
- return '<tr style="border-bottom:1px solid var(--border);"><td style="padding:6px 10px;font-size:11px;color:var(--text-3);">' + time + '</td><td style="padding:6px 10px;font-size:12px;"><span style="color:' + color + ';">' + phase + '</span></td><td style="padding:6px 10px;font-size:12px;">' + (d.agentId || '') + '</td><td style="padding:6px 10px;font-size:11px;color:var(--text-3);">' + (d.taskId || '').slice(0, 20) + '</td></tr>';
276
- }).join('');
277
- el.innerHTML = '<div class="card" style="overflow:auto;"><div style="font-size:12px;font-weight:600;padding:8px 12px;border-bottom:1px solid var(--border);">Task lifecycle (schema 1.1)</div><table style="width:100%;border-collapse:collapse;font-size:12px;"><thead><tr style="border-bottom:1px solid var(--border);"><th style="text-align:left;padding:6px 10px;">Time</th><th style="text-align:left;padding:6px 10px;">Phase</th><th style="text-align:left;padding:6px 10px;">Agent</th><th style="text-align:left;padding:6px 10px;">Task ID</th></tr></thead><tbody>' + rows + '</tbody></table></div>';
278
- }
279
-
280
- // ── Tool Matrix (agents × tools from health + restart) ───────────────────────────
281
- const TOOL_LABELS = { read_file: 'read', write_file: 'write', mkdir: 'mkdir', run_cmd: 'run', dispatch: 'dispatch', skill: 'skill', define_skill: 'define_skill', git: 'git', telegram: 'tg', whatsapp: 'wa' };
282
-
283
- export async function loadToolMatrix(){
284
- const el = document.getElementById('toolMatrixContainer');
285
- if (!el) return;
286
- try {
287
- const res = await fetch('/api/health');
288
- const d = await res.json().catch(() => ({}));
289
- if (!res.ok || !d.ok) {
290
- const msg = d.error || (res.status === 401 ? 'Unauthorized' : res.statusText || 'Request failed');
291
- el.innerHTML = '<div class="card" style="padding:16px;"><div style="color:var(--yellow);font-size:13px;font-weight:600;">Health check failed</div>' +
292
- '<div style="color:var(--text-2);font-size:12px;margin-top:8px;">' + (res.status === 401 ? 'RT token missing or invalid. Set it in Settings → System (RT token) or in ~/.crewswarm/crewswarm.json (rt.authToken).' : msg) + '</div>' +
293
- '<div style="color:var(--text-3);font-size:11px;margin-top:8px;">Ensure crew-lead is running on :5010 (Services tab).</div></div>';
294
- return;
295
- }
296
- window._telemetryEvents = d.telemetry || [];
297
- renderTaskLifecycle(d.telemetry || []);
298
- const bridgeAgents = (d.agents || []).filter(a => (a.id || '').toLowerCase() !== 'crew-lead');
299
- const crewLeadInfo = window._crewLeadInfo || { name: 'Crew Lead', emoji: '🧠' };
300
- const crewLeadRow = { id: 'crew-lead', name: crewLeadInfo.name, emoji: crewLeadInfo.emoji, tools: ['read_file', 'write_file', 'mkdir', 'run_cmd', 'web_search', 'web_fetch', 'skill', 'define_skill', 'dispatch', 'telegram', 'whatsapp'] };
301
- const agents = [crewLeadRow, ...bridgeAgents];
302
- const toolKeys = [...new Set(['define_skill', 'skill', ...agents.flatMap(a => Array.isArray(a.tools) ? a.tools : Object.keys(a.tools || {}))])].sort();
303
- const labels = toolKeys.map(t => TOOL_LABELS[t] || t);
304
- if (!agents.length) {
305
- el.innerHTML = '<div class="card" style="padding:16px;"><div style="color:var(--text-2);font-size:13px;">No agents in roster.</div>' +
306
- '<div style="color:var(--text-3);font-size:12px;margin-top:6px;">Add agents in Settings → Agents (or ~/.crewswarm/crewswarm.json), then start bridges from Services.</div></div>';
307
- return;
308
- }
309
- let html = '<div class="card" style="overflow:auto;"><table style="width:100%;border-collapse:collapse;font-size:12px;">'
310
- + '<thead><tr style="border-bottom:1px solid var(--border);">'
311
- + '<th style="text-align:left;padding:8px 12px;">Agent</th>';
312
- toolKeys.forEach((t, i) => { html += '<th style="text-align:center;padding:8px 8px;" title="' + (t || '') + '">' + (labels[i] || t) + '</th>'; });
313
- html += '<th style="text-align:right;padding:8px 12px;">Quick action</th></tr></thead><tbody>';
314
- agents.forEach(a => {
315
- const tools = Array.isArray(a.tools) ? a.tools : (a.tools ? Object.keys(a.tools).filter(k => a.tools[k]) : []);
316
- const name = (a.emoji || '') + ' ' + (a.name || a.id || '');
317
- html += '<tr style="border-bottom:1px solid var(--border);">';
318
- html += '<td style="padding:8px 12px;"><strong>' + (name || a.id).replace(/</g, '&lt;') + '</strong></td>';
319
- toolKeys.forEach(t => {
320
- const has = tools.includes(t);
321
- html += '<td style="text-align:center;padding:6px 8px;">' + (has ? '<span style="color:var(--green);" title="' + t + '">✓</span>' : '<span style="color:var(--text-3);">—</span>') + '</td>';
322
- });
323
- html += '<td style="text-align:right;padding:8px 12px;"><button class="btn-ghost" style="font-size:11px;" data-action="restartAgentFromUI" data-arg="' + (a.id || '').replace(/"/g, '&quot;') + '">Restart</button></td></tr>';
324
- });
325
- html += '</tbody></table></div>';
326
- el.innerHTML = html;
327
- } catch (e) {
328
- showError(el, 'Error loading health: ' + (e.message || ''));
329
- }
330
- }
331
-
332
- export async function restartAgentFromUI(agentId){
333
- if (!agentId) return;
334
-
335
- // Find and disable the button immediately
336
- const btn = document.querySelector(`button[data-action="restartAgentFromUI"][data-arg="${agentId}"]`);
337
- if (btn) {
338
- btn.disabled = true;
339
- btn.style.opacity = '0.5';
340
- btn.style.cursor = 'not-allowed';
341
- }
342
-
343
- // Debounce: prevent rapid double-clicks
344
- const now = Date.now();
345
- const lastRestart = window._lastAgentRestart || {};
346
- if (lastRestart[agentId] && (now - lastRestart[agentId]) < 3000) {
347
- showNotification('⏳ Restart already in progress...', 'warning');
348
- if (btn) {
349
- btn.disabled = false;
350
- btn.style.opacity = '';
351
- btn.style.cursor = '';
352
- }
353
- return;
354
- }
355
- if (!window._lastAgentRestart) window._lastAgentRestart = {};
356
- window._lastAgentRestart[agentId] = now;
357
-
358
- try {
359
- const r = await fetch('/api/agents/' + encodeURIComponent(agentId) + '/restart', { method: 'POST', headers: { 'Content-Type': 'application/json' } });
360
- const data = await r.json();
361
- if (data.ok) {
362
- showNotification('Restarting ' + agentId + '…');
363
- // Reload after delay to show new status
364
- setTimeout(() => {
365
- const currentTab = window.appState?.activeTab;
366
- if (currentTab === 'usage') {
367
- const healthSection = document.getElementById('healthAgentList');
368
- if (healthSection && healthSection.offsetParent !== null) {
369
- // Health tab is visible - reload it
370
- if (window.loadAgentHealth) window.loadAgentHealth();
371
- }
372
- }
373
- }, 3000);
374
- } else {
375
- showNotification(data.error || 'Restart failed', 'error');
376
- if (btn) {
377
- btn.disabled = false;
378
- btn.style.opacity = '';
379
- btn.style.cursor = '';
380
- }
381
- }
382
- } catch (e) {
383
- showNotification(e.message || 'Request failed', 'error');
384
- if (btn) {
385
- btn.disabled = false;
386
- btn.style.opacity = '';
387
- btn.style.cursor = '';
388
- }
389
- }
390
- }
@@ -1,238 +0,0 @@
1
- // Waves Tab - Visual wave pipeline editor
2
-
3
- export function initWavesTab() {
4
- const wavesTab = document.getElementById('waves-tab');
5
- if (!wavesTab) return;
6
-
7
- let wavesConfig = null;
8
-
9
- async function loadWavesConfig() {
10
- try {
11
- const res = await fetch('/api/waves/config');
12
- wavesConfig = await res.json();
13
- renderWaves();
14
- } catch (e) {
15
- showError('Failed to load waves config: ' + e.message);
16
- }
17
- }
18
-
19
- function renderWaves() {
20
- if (!wavesConfig) return;
21
-
22
- const html = `
23
- <div class="waves-header" style="margin-bottom: 24px;">
24
- <h2 style="margin: 0 0 8px 0;">Planning Pipeline Waves</h2>
25
- <p style="margin: 0; color: var(--text-2); font-size: 14px;">
26
- Configure the 3-wave planning pipeline that runs when you say "build me X"
27
- </p>
28
- </div>
29
-
30
- <div class="wave-templates" style="margin-bottom: 32px;">
31
- <div style="font-weight: 600; margin-bottom: 12px;">Templates:</div>
32
- <div style="display: flex; gap: 12px; flex-wrap: wrap;">
33
- ${Object.entries(wavesConfig.templates || {}).map(([id, tmpl]) => `
34
- <button class="template-btn" data-template="${id}" style="padding: 12px 16px; border-radius: 8px; border: 1px solid var(--border); background: var(--surface-2); cursor: pointer;">
35
- <div style="font-weight: 600; margin-bottom: 4px;">${tmpl.name}</div>
36
- <div style="font-size: 12px; color: var(--text-3);">${tmpl.description}</div>
37
- </button>
38
- `).join('')}
39
- </div>
40
- </div>
41
-
42
- <div class="waves-list">
43
- ${wavesConfig.waves.map(wave => renderWave(wave)).join('')}
44
- </div>
45
-
46
- <div style="margin-top: 24px; display: flex; gap: 12px;">
47
- <button id="saveWavesBtn" style="padding: 12px 24px; background: var(--accent); color: white; border: none; border-radius: 8px; font-weight: 600; cursor: pointer;">
48
- 💾 Save Configuration
49
- </button>
50
- <button id="resetWavesBtn" style="padding: 12px 24px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; font-weight: 600; cursor: pointer;">
51
- ↺ Reset to Default
52
- </button>
53
- </div>
54
- `;
55
-
56
- wavesTab.innerHTML = html;
57
- attachWaveHandlers();
58
- }
59
-
60
- function renderWave(wave) {
61
- return `
62
- <div class="wave-card" data-wave-id="${wave.id}" style="margin-bottom: 24px; padding: 20px; background: var(--surface-1); border: 1px solid var(--border); border-radius: 12px;">
63
- <div class="wave-header" style="margin-bottom: 16px;">
64
- <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
65
- <div style="font-size: 20px; font-weight: 700;">Wave ${wave.id}</div>
66
- <div style="font-size: 14px; font-weight: 600; color: var(--text-1);">${wave.name}</div>
67
- </div>
68
- <div style="font-size: 13px; color: var(--text-3);">${wave.description}</div>
69
- </div>
70
-
71
- <div class="wave-agents" style="display: flex; flex-direction: column; gap: 12px;">
72
- ${wave.agents.map((agent, idx) => renderAgent(wave.id, agent, idx)).join('')}
73
- </div>
74
-
75
- <button class="add-agent-btn" data-wave-id="${wave.id}" style="margin-top: 12px; padding: 8px 16px; background: var(--surface-2); border: 1px dashed var(--border); border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--text-2);">
76
- + Add Agent to Wave ${wave.id}
77
- </button>
78
- </div>
79
- `;
80
- }
81
-
82
- function renderAgent(waveId, agent, idx) {
83
- return `
84
- <div class="agent-slot" data-wave-id="${waveId}" data-agent-idx="${idx}" style="padding: 12px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px;">
85
- <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 8px;">
86
- <select class="agent-select" data-wave-id="${waveId}" data-agent-idx="${idx}" style="padding: 6px 12px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-2); font-size: 13px; font-weight: 600;">
87
- <option value="${agent.id}" selected>${agent.id}</option>
88
- <option value="crew-researcher">crew-researcher</option>
89
- <option value="crew-copywriter">crew-copywriter</option>
90
- <option value="crew-pm">crew-pm</option>
91
- <option value="crew-architect">crew-architect</option>
92
- <option value="crew-coder-front">crew-coder-front</option>
93
- <option value="crew-frontend">crew-frontend</option>
94
- <option value="crew-qa">crew-qa</option>
95
- <option value="crew-security">crew-security</option>
96
- <option value="crew-main">crew-main</option>
97
- </select>
98
- <button class="remove-agent-btn" data-wave-id="${waveId}" data-agent-idx="${idx}" style="padding: 6px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; cursor: pointer; font-size: 12px; color: var(--text-3);">
99
- ✕ Remove
100
- </button>
101
- </div>
102
- <textarea class="agent-task" data-wave-id="${waveId}" data-agent-idx="${idx}" rows="3" style="width: 100%; padding: 8px; border-radius: 6px; border: 1px solid var(--border); background: var(--surface-2); font-size: 12px; font-family: 'SF Mono', monospace; resize: vertical;">${agent.task}</textarea>
103
- </div>
104
- `;
105
- }
106
-
107
- function attachWaveHandlers() {
108
- // Agent select dropdown
109
- document.querySelectorAll('.agent-select').forEach(select => {
110
- select.addEventListener('change', (e) => {
111
- const waveId = parseInt(e.target.dataset.waveId);
112
- const idx = parseInt(e.target.dataset.agentIdx);
113
- const wave = wavesConfig.waves.find(w => w.id === waveId);
114
- if (wave && wave.agents[idx]) {
115
- wave.agents[idx].id = e.target.value;
116
- }
117
- });
118
- });
119
-
120
- // Agent task textarea
121
- document.querySelectorAll('.agent-task').forEach(textarea => {
122
- textarea.addEventListener('change', (e) => {
123
- const waveId = parseInt(e.target.dataset.waveId);
124
- const idx = parseInt(e.target.dataset.agentIdx);
125
- const wave = wavesConfig.waves.find(w => w.id === waveId);
126
- if (wave && wave.agents[idx]) {
127
- wave.agents[idx].task = e.target.value;
128
- }
129
- });
130
- });
131
-
132
- // Remove agent button
133
- document.querySelectorAll('.remove-agent-btn').forEach(btn => {
134
- btn.addEventListener('click', (e) => {
135
- const waveId = parseInt(e.target.dataset.waveId);
136
- const idx = parseInt(e.target.dataset.agentIdx);
137
- const wave = wavesConfig.waves.find(w => w.id === waveId);
138
- if (wave) {
139
- wave.agents.splice(idx, 1);
140
- renderWaves();
141
- }
142
- });
143
- });
144
-
145
- // Add agent button
146
- document.querySelectorAll('.add-agent-btn').forEach(btn => {
147
- btn.addEventListener('click', (e) => {
148
- const waveId = parseInt(e.target.dataset.waveId);
149
- const wave = wavesConfig.waves.find(w => w.id === waveId);
150
- if (wave) {
151
- wave.agents.push({
152
- id: 'crew-main',
153
- task: '[TASK] Describe what this agent should do...'
154
- });
155
- renderWaves();
156
- }
157
- });
158
- });
159
-
160
- // Template buttons
161
- document.querySelectorAll('.template-btn').forEach(btn => {
162
- btn.addEventListener('click', (e) => {
163
- const templateId = e.target.closest('.template-btn').dataset.template;
164
- applyTemplate(templateId);
165
- });
166
- });
167
-
168
- // Save button
169
- document.getElementById('saveWavesBtn')?.addEventListener('click', saveWavesConfig);
170
-
171
- // Reset button
172
- document.getElementById('resetWavesBtn')?.addEventListener('click', resetWavesConfig);
173
- }
174
-
175
- function applyTemplate(templateId) {
176
- const template = wavesConfig.templates[templateId];
177
- if (!template) return;
178
-
179
- if (template.wave_overrides) {
180
- Object.entries(template.wave_overrides).forEach(([waveIdStr, overrides]) => {
181
- const waveId = parseInt(waveIdStr);
182
- const wave = wavesConfig.waves.find(w => w.id === waveId);
183
- if (wave && overrides.agents) {
184
- wave.agents = overrides.agents;
185
- }
186
- });
187
- }
188
-
189
- renderWaves();
190
- showSuccess(`Applied template: ${template.name}`);
191
- }
192
-
193
- async function saveWavesConfig() {
194
- try {
195
- const res = await fetch('/api/waves/config', {
196
- method: 'POST',
197
- headers: {'Content-Type': 'application/json'},
198
- body: JSON.stringify(wavesConfig)
199
- });
200
- if (!res.ok) throw new Error(await res.text());
201
- showSuccess('Waves configuration saved');
202
- } catch (e) {
203
- showError('Failed to save: ' + e.message);
204
- }
205
- }
206
-
207
- async function resetWavesConfig() {
208
- if (!confirm('Reset waves to default configuration?')) return;
209
- try {
210
- const res = await fetch('/api/waves/config/reset', {method: 'POST'});
211
- if (!res.ok) throw new Error(await res.text());
212
- await loadWavesConfig();
213
- showSuccess('Reset to default configuration');
214
- } catch (e) {
215
- showError('Failed to reset: ' + e.message);
216
- }
217
- }
218
-
219
- function showSuccess(msg) {
220
- // Reuse existing notification system
221
- const notif = document.createElement('div');
222
- notif.textContent = '✅ ' + msg;
223
- notif.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--success); color: white; padding: 12px 20px; border-radius: 8px; z-index: 10000; font-weight: 600;';
224
- document.body.appendChild(notif);
225
- setTimeout(() => notif.remove(), 3000);
226
- }
227
-
228
- function showError(msg) {
229
- const notif = document.createElement('div');
230
- notif.textContent = '❌ ' + msg;
231
- notif.style.cssText = 'position: fixed; top: 20px; right: 20px; background: var(--error); color: white; padding: 12px 20px; border-radius: 8px; z-index: 10000; font-weight: 600;';
232
- document.body.appendChild(notif);
233
- setTimeout(() => notif.remove(), 5000);
234
- }
235
-
236
- // Initialize
237
- loadWavesConfig();
238
- }