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.
- package/README.md +22 -9
- package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +34 -4
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +55 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +15 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +5 -2
- package/lib/runtime/paths.mjs +12 -8
- package/package.json +35 -15
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +74 -0
- package/scripts/dashboard.mjs +560 -70
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- 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, '<') + '</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, '"') + '">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
|
-
}
|
|
Binary file
|
|
@@ -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
|
-
}
|
|
Binary file
|