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,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
- }