mcp-swarm 1.1.5 → 1.1.6
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/CHANGELOG.md +32 -0
- package/CHANGELOG.ru.md +32 -0
- package/README.md +2 -2
- package/README.ru.md +2 -2
- package/dist/dashboard.js +249 -18
- package/dist/dashboard.js.map +1 -1
- package/dist/workflows/agentBooster.js +94 -2
- package/dist/workflows/agentBooster.js.map +1 -1
- package/package.json +1 -1
- package/dist/smartTools.legacy.js +0 -3513
- package/dist/smartTools.legacy.js.map +0 -1
- package/dist/tests/newModules.test.js +0 -585
- package/dist/tests/newModules.test.js.map +0 -1
- package/dist/tests/projectId.test.js +0 -125
- package/dist/tests/projectId.test.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,38 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [1.1.6] - 2026-02-10
|
|
13
|
+
|
|
14
|
+
### What's New
|
|
15
|
+
|
|
16
|
+
#### 🏗️ Hub Architecture Refactoring
|
|
17
|
+
- **Modular services** — Hub refactored from 846-line monolith into clean modules: `types.ts`, `services/events.ts`, `services/tasks.ts`, `services/agents.ts`
|
|
18
|
+
- **Thin entrypoint** — `index.ts` now delegates to services, making the codebase maintainable
|
|
19
|
+
- **Legacy cleanup** — Removed `smartTools.legacy.ts` (144KB dead code)
|
|
20
|
+
|
|
21
|
+
#### 📊 Dashboard 2.0
|
|
22
|
+
- **Chart.js graphs** — Bar chart for tasks over 24h, doughnut chart for agent activity
|
|
23
|
+
- **Pulse Timeline** — Live heartbeat visualization of all connected agents
|
|
24
|
+
- **WebSocket updates** — Replaced `meta http-equiv="refresh"` with WebSocket for real-time updates
|
|
25
|
+
- **Global Swarm Control** — Stop/Resume entire swarm directly from dashboard via Hub API
|
|
26
|
+
|
|
27
|
+
#### 🔒 API Security
|
|
28
|
+
- **X-Swarm-Secret middleware** — All `/api/*` endpoints validate `X-Swarm-Secret` header when `SWARM_AUTH_TOKEN` is set
|
|
29
|
+
- **Rate Limiting** — Built-in 100 requests/IP/minute limiter with `429 Too Many Requests` response
|
|
30
|
+
|
|
31
|
+
#### 🧪 E2E Testing
|
|
32
|
+
- **Full lifecycle test** — Hub → Task → Claim → Release → Lock → Unlock → Stop → Resume
|
|
33
|
+
- **Rate limit test** — Validates the 429 protection works correctly
|
|
34
|
+
- **Vitest-based** — Consistent with existing test suite
|
|
35
|
+
|
|
36
|
+
#### 🦙 Optional Ollama Integration
|
|
37
|
+
- **Local LLM support** — `swarm_booster` now supports `ollama_generate` task type for complex operations
|
|
38
|
+
- **Cost savings** — Use local Ollama models (codellama:7b) instead of expensive API calls
|
|
39
|
+
- **Fully optional** — Without `ollamaUrl` in config, everything works exactly as before
|
|
40
|
+
- **Smart detection** — `can_boost` detects refactoring/optimization tasks when Ollama is available
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
12
44
|
## [1.1.5] - 2026-02-09
|
|
13
45
|
|
|
14
46
|
### What's New
|
package/CHANGELOG.ru.md
CHANGED
|
@@ -9,6 +9,38 @@
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
+
## [1.1.6] - 2026-02-10
|
|
13
|
+
|
|
14
|
+
### Что нового
|
|
15
|
+
|
|
16
|
+
#### 🏗️ Рефакторинг архитектуры Hub
|
|
17
|
+
- **Модульные сервисы** — Hub рефакторнут из 846-строчного монолита в чистые модули: `types.ts`, `services/events.ts`, `services/tasks.ts`, `services/agents.ts`
|
|
18
|
+
- **Тонкий entrypoint** — `index.ts` теперь делегирует сервисам, код стал поддерживаемым
|
|
19
|
+
- **Удаление legacy** — Удалён `smartTools.legacy.ts` (144KB мёртвого кода)
|
|
20
|
+
|
|
21
|
+
#### 📊 Dashboard 2.0
|
|
22
|
+
- **Chart.js графики** — Bar chart задач за 24ч, doughnut chart активности агентов
|
|
23
|
+
- **Pulse Timeline** — Визуализация heartbeat всех подключённых агентов в реальном времени
|
|
24
|
+
- **WebSocket обновления** — Заменён `meta http-equiv="refresh"` на WebSocket для live-обновлений
|
|
25
|
+
- **Глобальный Swarm Control** — Stop/Resume всего роя прямо из дашборда через Hub API
|
|
26
|
+
|
|
27
|
+
#### 🔒 Безопасность API
|
|
28
|
+
- **X-Swarm-Secret middleware** — Все `/api/*` эндпоинты проверяют заголовок `X-Swarm-Secret` при установке `SWARM_AUTH_TOKEN`
|
|
29
|
+
- **Rate Limiting** — Встроенный лимитер 100 запросов/IP/минуту с ответом `429 Too Many Requests`
|
|
30
|
+
|
|
31
|
+
#### 🧪 E2E Тестирование
|
|
32
|
+
- **Полный lifecycle тест** — Hub → Task → Claim → Release → Lock → Unlock → Stop → Resume
|
|
33
|
+
- **Тест rate limiting** — Проверка корректности защиты 429
|
|
34
|
+
- **На базе Vitest** — Консистентно с существующим тест-сьютом
|
|
35
|
+
|
|
36
|
+
#### 🦙 Опциональная интеграция Ollama
|
|
37
|
+
- **Поддержка локальных LLM** — `swarm_booster` теперь поддерживает тип задач `ollama_generate` для сложных операций
|
|
38
|
+
- **Экономия** — Используйте локальные модели Ollama (codellama:7b) вместо дорогих API-вызовов
|
|
39
|
+
- **Полностью опционально** — Без `ollamaUrl` в конфиге всё работает как раньше
|
|
40
|
+
- **Умное определение** — `can_boost` определяет задачи рефакторинга/оптимизации при доступном Ollama
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
12
44
|
## [1.1.5] - 2026-02-09
|
|
13
45
|
|
|
14
46
|
### Что нового
|
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
<img src="./assets/banner.png" alt="MCP Swarm Banner" width="800" />
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
# 🐝 MCP Swarm v1.1.
|
|
15
|
+
# 🐝 MCP Swarm v1.1.6 — Universal AI Agent Coordination Platform
|
|
16
16
|
|
|
17
|
-
> 🐝 **v1.1.
|
|
17
|
+
> 🐝 **v1.1.6 — Architecture & Security:** Hub refactored into modular services, Dashboard 2.0 with Chart.js graphs and WebSocket live updates, API security (X-Swarm-Secret + Rate Limiting), E2E tests, optional Ollama integration for swarm_booster. Update: `npm install -g mcp-swarm@latest`
|
|
18
18
|
|
|
19
19
|
**MCP Swarm** is a global "nervous system" for your AI assistants. It turns separate agents (Claude, Cursor, Windsurf, OpenCode) into a coordinated team that can work on massive projects without conflicts or context loss.
|
|
20
20
|
|
package/README.ru.md
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
<img src="./assets/banner.png" alt="MCP Swarm Banner" width="800" />
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
# 🐝 MCP Swarm v1.1.
|
|
15
|
+
# 🐝 MCP Swarm v1.1.6 — Универсальная Платформа Координации AI-Агентов
|
|
16
16
|
|
|
17
|
-
> 🐝 **v1.1.
|
|
17
|
+
> 🐝 **v1.1.6 — Архитектура и Безопасность:** Hub рефакторнут в модульные сервисы, Dashboard 2.0 с Chart.js графиками и WebSocket обновлениями, API безопасность (X-Swarm-Secret + Rate Limiting), E2E тесты, опциональная интеграция Ollama для swarm_booster. Обновитесь: `npm install -g mcp-swarm@latest`
|
|
18
18
|
|
|
19
19
|
**MCP Swarm** — это глобальная «нервная система» для ваших AI-помощников. Она превращает разрозненных агентов (Claude, Cursor, Windsurf, OpenCode) в слаженную команду, способную работать над огромными проектами без конфликтов и потери контекста.
|
|
20
20
|
|
package/dist/dashboard.js
CHANGED
|
@@ -1,25 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP Swarm — Dashboard HTML Generator
|
|
2
|
+
* MCP Swarm — Dashboard 2.0 HTML Generator
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Enhanced dashboard with:
|
|
5
|
+
* - Chart.js graphs (tasks 24h, cost per agent)
|
|
6
|
+
* - Pulse timeline (agent heartbeats)
|
|
7
|
+
* - Global Stop/Resume (via Hub API)
|
|
8
|
+
* - WebSocket live updates (replaces meta-refresh)
|
|
6
9
|
*/
|
|
7
10
|
export function renderDashboard(data) {
|
|
8
|
-
const { agentName, role, isOrchestrator, paused, stop, bridgeConnected, projectId, uptimeStr, pid, logFilePath } = data;
|
|
11
|
+
const { agentName, role, isOrchestrator, paused, stop, bridgeConnected, projectId, uptimeStr, pid, logFilePath, hubUrl } = data;
|
|
9
12
|
return `<!DOCTYPE html>
|
|
10
13
|
<html lang="en">
|
|
11
14
|
<head>
|
|
12
15
|
<meta charset="UTF-8">
|
|
13
16
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
14
|
-
<
|
|
15
|
-
<
|
|
17
|
+
<title>🐝 MCP Swarm — Dashboard 2.0</title>
|
|
18
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
|
|
16
19
|
<style>
|
|
17
20
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
18
21
|
body { font-family: 'Segoe UI', system-ui, sans-serif; background: #0d1117; color: #e6edf3; min-height: 100vh; padding: 2rem; }
|
|
19
|
-
.container { max-width:
|
|
22
|
+
.container { max-width: 1100px; margin: 0 auto; }
|
|
20
23
|
h1 { font-size: 2rem; margin-bottom: 0.5rem; }
|
|
21
24
|
h1 span { color: #58a6ff; }
|
|
22
25
|
.subtitle { color: #8b949e; margin-bottom: 2rem; font-size: 0.95rem; }
|
|
26
|
+
.live-dot { display: inline-block; width: 8px; height: 8px; background: #3fb950; border-radius: 50%; margin-left: 8px; animation: pulse-dot 2s infinite; }
|
|
27
|
+
@keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
28
|
+
|
|
29
|
+
/* Cards */
|
|
23
30
|
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
|
24
31
|
.card { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.2rem; transition: border-color 0.2s; }
|
|
25
32
|
.card:hover { border-color: #58a6ff44; }
|
|
@@ -34,6 +41,8 @@ export function renderDashboard(data) {
|
|
|
34
41
|
.badge.running { background: #23863633; color: #3fb950; }
|
|
35
42
|
.badge.paused { background: #d2992233; color: #d29922; }
|
|
36
43
|
.badge.stopped { background: #f8514933; color: #f85149; }
|
|
44
|
+
|
|
45
|
+
/* Controls */
|
|
37
46
|
.controls { display: flex; gap: 0.6rem; margin-bottom: 2rem; flex-wrap: wrap; }
|
|
38
47
|
.btn { padding: 0.5rem 1.2rem; border: 1px solid #30363d; border-radius: 8px; background: #161b22; color: #e6edf3; cursor: pointer; font-size: 0.85rem; font-weight: 500; transition: all 0.15s; }
|
|
39
48
|
.btn:hover { background: #1f2937; border-color: #58a6ff; transform: translateY(-1px); }
|
|
@@ -44,8 +53,26 @@ export function renderDashboard(data) {
|
|
|
44
53
|
.btn.warn:hover { background: #d2992222; }
|
|
45
54
|
.btn.ok { border-color: #3fb950; color: #3fb950; }
|
|
46
55
|
.btn.ok:hover { background: #3fb95022; }
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
|
|
57
|
+
/* Charts */
|
|
58
|
+
.charts { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 2rem; }
|
|
59
|
+
.chart-box { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.2rem; }
|
|
60
|
+
.chart-box h3 { color: #c9d1d9; font-size: 0.9rem; margin-bottom: 0.8rem; }
|
|
61
|
+
|
|
62
|
+
/* Pulse Timeline */
|
|
63
|
+
.timeline { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 1.2rem; margin-bottom: 2rem; }
|
|
64
|
+
.timeline h3 { color: #c9d1d9; font-size: 0.9rem; margin-bottom: 0.8rem; }
|
|
65
|
+
.timeline-items { display: flex; flex-direction: column; gap: 0.4rem; }
|
|
66
|
+
.timeline-item { display: flex; align-items: center; gap: 0.8rem; padding: 0.4rem 0.6rem; border-radius: 6px; background: #0d1117; }
|
|
67
|
+
.timeline-item .dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
|
|
68
|
+
.timeline-item .dot.active { background: #3fb950; box-shadow: 0 0 6px #3fb95066; }
|
|
69
|
+
.timeline-item .dot.idle { background: #d29922; }
|
|
70
|
+
.timeline-item .dot.offline { background: #484f58; }
|
|
71
|
+
.timeline-item .agent-name { font-weight: 600; font-size: 0.85rem; min-width: 120px; }
|
|
72
|
+
.timeline-item .agent-info { color: #8b949e; font-size: 0.8rem; }
|
|
73
|
+
.timeline-item .agent-time { color: #484f58; font-size: 0.75rem; margin-left: auto; }
|
|
74
|
+
|
|
75
|
+
/* Endpoints */
|
|
49
76
|
.endpoints { margin-top: 1rem; }
|
|
50
77
|
.endpoints h2 { font-size: 1.1rem; margin-bottom: 0.8rem; color: #c9d1d9; }
|
|
51
78
|
.ep-list { list-style: none; }
|
|
@@ -53,30 +80,39 @@ export function renderDashboard(data) {
|
|
|
53
80
|
.ep-list .method { color: #3fb950; min-width: 3.5rem; font-weight: 600; }
|
|
54
81
|
.ep-list .path { color: #58a6ff; }
|
|
55
82
|
.ep-list .desc { color: #8b949e; margin-left: auto; }
|
|
83
|
+
|
|
84
|
+
.toast { position: fixed; bottom: 2rem; right: 2rem; background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 0.8rem 1.2rem; color: #e6edf3; font-size: 0.85rem; opacity: 0; transition: opacity 0.3s; z-index: 99; pointer-events: none; }
|
|
85
|
+
.toast.show { opacity: 1; }
|
|
56
86
|
.footer { margin-top: 2rem; color: #484f58; font-size: 0.8rem; text-align: center; }
|
|
57
87
|
.footer a { color: #58a6ff; text-decoration: none; }
|
|
88
|
+
|
|
89
|
+
@media (max-width: 768px) {
|
|
90
|
+
.charts { grid-template-columns: 1fr; }
|
|
91
|
+
.cards { grid-template-columns: repeat(2, 1fr); }
|
|
92
|
+
}
|
|
58
93
|
</style>
|
|
59
94
|
</head>
|
|
60
95
|
<body>
|
|
61
96
|
<div class="container">
|
|
62
|
-
<h1>🐝 MCP <span>Swarm</span></h1>
|
|
63
|
-
<p class="subtitle">
|
|
97
|
+
<h1>🐝 MCP <span>Swarm</span> <span class="live-dot" id="live-dot" title="Live updates"></span></h1>
|
|
98
|
+
<p class="subtitle">Dashboard 2.0 — Live WebSocket Updates</p>
|
|
99
|
+
|
|
64
100
|
<div class="cards">
|
|
65
101
|
<div class="card">
|
|
66
102
|
<h3>Agent</h3>
|
|
67
|
-
<div class="value blue">${agentName}</div>
|
|
103
|
+
<div class="value blue" id="agent-name">${agentName}</div>
|
|
68
104
|
</div>
|
|
69
105
|
<div class="card">
|
|
70
106
|
<h3>Role</h3>
|
|
71
|
-
<div class="value"><span class="badge ${isOrchestrator ? 'orch' : 'exec'}">${role.toUpperCase()}</span></div>
|
|
107
|
+
<div class="value"><span class="badge ${isOrchestrator ? 'orch' : 'exec'}" id="agent-role">${role.toUpperCase()}</span></div>
|
|
72
108
|
</div>
|
|
73
109
|
<div class="card">
|
|
74
110
|
<h3>Status</h3>
|
|
75
|
-
<div class="value"><span class="badge ${stop ? 'stopped' : paused ? 'paused' : 'running'}">${stop ? '⏹ STOPPED' : paused ? '⏸ PAUSED' : '▶ RUNNING'}</span></div>
|
|
111
|
+
<div class="value"><span class="badge ${stop ? 'stopped' : paused ? 'paused' : 'running'}" id="agent-status">${stop ? '⏹ STOPPED' : paused ? '⏸ PAUSED' : '▶ RUNNING'}</span></div>
|
|
76
112
|
</div>
|
|
77
113
|
<div class="card">
|
|
78
114
|
<h3>Bridge</h3>
|
|
79
|
-
<div class="value ${bridgeConnected ? 'green' : 'yellow'}">${bridgeConnected ? '🌉 Connected' : '⚠ Off'}</div>
|
|
115
|
+
<div class="value ${bridgeConnected ? 'green' : 'yellow'}" id="bridge-status">${bridgeConnected ? '🌉 Connected' : '⚠ Off'}</div>
|
|
80
116
|
</div>
|
|
81
117
|
<div class="card">
|
|
82
118
|
<h3>Project</h3>
|
|
@@ -84,7 +120,7 @@ export function renderDashboard(data) {
|
|
|
84
120
|
</div>
|
|
85
121
|
<div class="card">
|
|
86
122
|
<h3>Uptime</h3>
|
|
87
|
-
<div class="value green">${uptimeStr}</div>
|
|
123
|
+
<div class="value green" id="uptime">${uptimeStr}</div>
|
|
88
124
|
</div>
|
|
89
125
|
<div class="card">
|
|
90
126
|
<h3>PID</h3>
|
|
@@ -95,11 +131,41 @@ export function renderDashboard(data) {
|
|
|
95
131
|
<div class="value" style="font-size:0.7rem;word-break:break-all;color:#8b949e;">${logFilePath}</div>
|
|
96
132
|
</div>
|
|
97
133
|
</div>
|
|
134
|
+
|
|
135
|
+
<!-- Controls -->
|
|
98
136
|
<div class="controls">
|
|
99
|
-
<button class="btn ${paused ? 'ok' : 'warn'}" onclick="action('${paused ? 'resume' : 'pause'}')">${paused ? '▶ Resume' : '⏸ Pause'}</button>
|
|
137
|
+
<button class="btn ${paused ? 'ok' : 'warn'}" onclick="action('${paused ? 'resume' : 'pause'}')" id="btn-pause">${paused ? '▶ Resume' : '⏸ Pause'}</button>
|
|
100
138
|
<button class="btn danger" onclick="if(confirm('Shutdown companion?')) action('stop')">⏹ Shutdown</button>
|
|
139
|
+
<button class="btn ok" id="btn-swarm-resume" onclick="swarmControl('resume')" style="display:${stop ? 'inline-block' : 'none'}">🐝 Resume Swarm</button>
|
|
140
|
+
<button class="btn danger" id="btn-swarm-stop" onclick="if(confirm('Stop entire swarm?')) swarmControl('stop')" style="display:${stop ? 'none' : 'inline-block'}">🐝 Stop Swarm</button>
|
|
101
141
|
<button class="btn" onclick="copyId()">📋 Copy Project ID</button>
|
|
102
142
|
</div>
|
|
143
|
+
|
|
144
|
+
<!-- Charts -->
|
|
145
|
+
<div class="charts">
|
|
146
|
+
<div class="chart-box">
|
|
147
|
+
<h3>📊 Tasks Last 24h</h3>
|
|
148
|
+
<canvas id="tasksChart" height="200"></canvas>
|
|
149
|
+
</div>
|
|
150
|
+
<div class="chart-box">
|
|
151
|
+
<h3>🤖 Agents Activity</h3>
|
|
152
|
+
<canvas id="agentsChart" height="200"></canvas>
|
|
153
|
+
</div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Pulse Timeline -->
|
|
157
|
+
<div class="timeline">
|
|
158
|
+
<h3>💓 Agent Pulse Timeline</h3>
|
|
159
|
+
<div class="timeline-items" id="pulse-timeline">
|
|
160
|
+
<div class="timeline-item">
|
|
161
|
+
<div class="dot active"></div>
|
|
162
|
+
<span class="agent-name">${agentName}</span>
|
|
163
|
+
<span class="agent-info">${role} • ${bridgeConnected ? 'bridge connected' : 'local only'}</span>
|
|
164
|
+
<span class="agent-time">now</span>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
103
169
|
<div class="endpoints">
|
|
104
170
|
<h2>📡 API Endpoints</h2>
|
|
105
171
|
<ul class="ep-list">
|
|
@@ -113,17 +179,20 @@ export function renderDashboard(data) {
|
|
|
113
179
|
</ul>
|
|
114
180
|
</div>
|
|
115
181
|
<div class="footer">
|
|
116
|
-
MCP Swarm v1.1 • <a href="https://github.com/AbdrAbdr/MCP-Swarm" target="_blank">GitHub</a> • <a href="https://www.npmjs.com/package/mcp-swarm" target="_blank">npm</a>
|
|
182
|
+
MCP Swarm v1.1.6 • <a href="https://github.com/AbdrAbdr/MCP-Swarm" target="_blank">GitHub</a> • <a href="https://www.npmjs.com/package/mcp-swarm" target="_blank">npm</a>
|
|
117
183
|
</div>
|
|
118
184
|
</div>
|
|
119
185
|
<div class="toast" id="toast"></div>
|
|
120
186
|
<script>
|
|
187
|
+
// ==================== Toast ====================
|
|
121
188
|
function toast(msg) {
|
|
122
189
|
const t = document.getElementById('toast');
|
|
123
190
|
t.textContent = msg;
|
|
124
191
|
t.classList.add('show');
|
|
125
192
|
setTimeout(() => t.classList.remove('show'), 2500);
|
|
126
193
|
}
|
|
194
|
+
|
|
195
|
+
// ==================== Local Companion Actions ====================
|
|
127
196
|
async function action(act) {
|
|
128
197
|
try {
|
|
129
198
|
const r = await fetch('/' + act, { method: 'POST' });
|
|
@@ -132,10 +201,172 @@ export function renderDashboard(data) {
|
|
|
132
201
|
if (act !== 'stop') setTimeout(() => location.reload(), 500);
|
|
133
202
|
} catch(e) { toast('Error: ' + e.message); }
|
|
134
203
|
}
|
|
204
|
+
|
|
205
|
+
// ==================== Global Swarm Control via Hub ====================
|
|
206
|
+
async function swarmControl(act) {
|
|
207
|
+
try {
|
|
208
|
+
const r = await fetch('/hub/' + act, { method: 'POST' });
|
|
209
|
+
const j = await r.json();
|
|
210
|
+
toast(j.ok ? 'Swarm ' + act + ' OK ✅' : 'Error: ' + (j.message || 'unknown'));
|
|
211
|
+
setTimeout(() => location.reload(), 500);
|
|
212
|
+
} catch(e) { toast('Error: ' + e.message); }
|
|
213
|
+
}
|
|
214
|
+
|
|
135
215
|
function copyId() {
|
|
136
216
|
const id = document.getElementById('project-id').textContent;
|
|
137
217
|
navigator.clipboard.writeText(id).then(() => toast('Copied: ' + id)).catch(() => toast('Copy failed'));
|
|
138
218
|
}
|
|
219
|
+
|
|
220
|
+
// ==================== Charts ====================
|
|
221
|
+
const chartDefaults = {
|
|
222
|
+
responsive: true,
|
|
223
|
+
maintainAspectRatio: false,
|
|
224
|
+
plugins: { legend: { labels: { color: '#8b949e', font: { size: 11 } } } },
|
|
225
|
+
scales: {
|
|
226
|
+
x: { ticks: { color: '#484f58' }, grid: { color: '#21262d' } },
|
|
227
|
+
y: { ticks: { color: '#484f58' }, grid: { color: '#21262d' }, beginAtZero: true }
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
// Tasks Chart (placeholder data — will be filled by WebSocket)
|
|
232
|
+
const tasksCtx = document.getElementById('tasksChart').getContext('2d');
|
|
233
|
+
const tasksChart = new Chart(tasksCtx, {
|
|
234
|
+
type: 'bar',
|
|
235
|
+
data: {
|
|
236
|
+
labels: ['6h ago', '5h', '4h', '3h', '2h', '1h', 'Now'],
|
|
237
|
+
datasets: [{
|
|
238
|
+
label: 'Created',
|
|
239
|
+
data: [0, 0, 0, 0, 0, 0, 0],
|
|
240
|
+
backgroundColor: '#58a6ff66',
|
|
241
|
+
borderColor: '#58a6ff',
|
|
242
|
+
borderWidth: 1,
|
|
243
|
+
borderRadius: 4,
|
|
244
|
+
}, {
|
|
245
|
+
label: 'Completed',
|
|
246
|
+
data: [0, 0, 0, 0, 0, 0, 0],
|
|
247
|
+
backgroundColor: '#3fb95066',
|
|
248
|
+
borderColor: '#3fb950',
|
|
249
|
+
borderWidth: 1,
|
|
250
|
+
borderRadius: 4,
|
|
251
|
+
}]
|
|
252
|
+
},
|
|
253
|
+
options: chartDefaults
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Agents Activity (pie/doughnut)
|
|
257
|
+
const agentsCtx = document.getElementById('agentsChart').getContext('2d');
|
|
258
|
+
const agentsChart = new Chart(agentsCtx, {
|
|
259
|
+
type: 'doughnut',
|
|
260
|
+
data: {
|
|
261
|
+
labels: ['Active', 'Idle', 'Offline'],
|
|
262
|
+
datasets: [{
|
|
263
|
+
data: [1, 0, 0],
|
|
264
|
+
backgroundColor: ['#3fb950', '#d29922', '#484f58'],
|
|
265
|
+
borderColor: '#161b22',
|
|
266
|
+
borderWidth: 3,
|
|
267
|
+
}]
|
|
268
|
+
},
|
|
269
|
+
options: {
|
|
270
|
+
responsive: true,
|
|
271
|
+
maintainAspectRatio: false,
|
|
272
|
+
plugins: {
|
|
273
|
+
legend: { position: 'bottom', labels: { color: '#8b949e', font: { size: 11 }, padding: 12 } }
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ==================== WebSocket Live Updates ====================
|
|
279
|
+
let ws = null;
|
|
280
|
+
function connectWS() {
|
|
281
|
+
try {
|
|
282
|
+
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
283
|
+
ws = new WebSocket(proto + '//' + location.host + '/ws');
|
|
284
|
+
ws.onmessage = (evt) => {
|
|
285
|
+
try {
|
|
286
|
+
const msg = JSON.parse(evt.data);
|
|
287
|
+
handleWSMessage(msg);
|
|
288
|
+
} catch {}
|
|
289
|
+
};
|
|
290
|
+
ws.onclose = () => { setTimeout(connectWS, 3000); };
|
|
291
|
+
ws.onerror = () => { ws.close(); };
|
|
292
|
+
document.getElementById('live-dot').style.background = '#3fb950';
|
|
293
|
+
} catch {
|
|
294
|
+
// Fallback to polling if WebSocket not available
|
|
295
|
+
setInterval(async () => {
|
|
296
|
+
try {
|
|
297
|
+
const r = await fetch('/status');
|
|
298
|
+
const data = await r.json();
|
|
299
|
+
updateDashboard(data);
|
|
300
|
+
} catch {}
|
|
301
|
+
}, 5000);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function handleWSMessage(msg) {
|
|
306
|
+
if (msg.kind === 'status_update') {
|
|
307
|
+
updateDashboard(msg);
|
|
308
|
+
}
|
|
309
|
+
if (msg.kind === 'pulse_update') {
|
|
310
|
+
updateTimeline(msg);
|
|
311
|
+
}
|
|
312
|
+
if (msg.kind === 'task_created' || msg.kind === 'task_completed') {
|
|
313
|
+
toast('📋 ' + msg.kind.replace('_', ' ') + ': ' + (msg.title || msg.taskId));
|
|
314
|
+
}
|
|
315
|
+
if (msg.kind === 'swarm_stopped') {
|
|
316
|
+
toast('⏹ Swarm Stopped');
|
|
317
|
+
document.getElementById('btn-swarm-stop').style.display = 'none';
|
|
318
|
+
document.getElementById('btn-swarm-resume').style.display = 'inline-block';
|
|
319
|
+
}
|
|
320
|
+
if (msg.kind === 'swarm_resumed') {
|
|
321
|
+
toast('▶ Swarm Resumed');
|
|
322
|
+
document.getElementById('btn-swarm-stop').style.display = 'inline-block';
|
|
323
|
+
document.getElementById('btn-swarm-resume').style.display = 'none';
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function updateDashboard(data) {
|
|
328
|
+
if (data.agentName) document.getElementById('agent-name').textContent = data.agentName;
|
|
329
|
+
if (data.uptimeStr) document.getElementById('uptime').textContent = data.uptimeStr;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function updateTimeline(pulse) {
|
|
333
|
+
const timeline = document.getElementById('pulse-timeline');
|
|
334
|
+
const existingItem = timeline.querySelector('[data-agent="' + pulse.agent + '"]');
|
|
335
|
+
const statusClass = pulse.status === 'active' ? 'active' : pulse.status === 'idle' ? 'idle' : 'offline';
|
|
336
|
+
const ago = Math.round((Date.now() - (pulse.lastUpdate || Date.now())) / 1000);
|
|
337
|
+
const timeStr = ago < 60 ? ago + 's ago' : Math.round(ago / 60) + 'm ago';
|
|
338
|
+
|
|
339
|
+
const html = '<div class="dot ' + statusClass + '"></div>' +
|
|
340
|
+
'<span class="agent-name">' + pulse.agent + '</span>' +
|
|
341
|
+
'<span class="agent-info">' + (pulse.platform || '') + ' • ' + (pulse.currentTask || 'idle') + '</span>' +
|
|
342
|
+
'<span class="agent-time">' + timeStr + '</span>';
|
|
343
|
+
|
|
344
|
+
if (existingItem) {
|
|
345
|
+
existingItem.innerHTML = html;
|
|
346
|
+
} else {
|
|
347
|
+
const item = document.createElement('div');
|
|
348
|
+
item.className = 'timeline-item';
|
|
349
|
+
item.setAttribute('data-agent', pulse.agent);
|
|
350
|
+
item.innerHTML = html;
|
|
351
|
+
timeline.appendChild(item);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Initialize WebSocket
|
|
356
|
+
connectWS();
|
|
357
|
+
|
|
358
|
+
// Fetch initial chart data
|
|
359
|
+
fetch('/status').then(r => r.json()).then(data => {
|
|
360
|
+
if (data.chartData) {
|
|
361
|
+
tasksChart.data.datasets[0].data = data.chartData.created || [0,0,0,0,0,0,0];
|
|
362
|
+
tasksChart.data.datasets[1].data = data.chartData.completed || [0,0,0,0,0,0,0];
|
|
363
|
+
tasksChart.update();
|
|
364
|
+
}
|
|
365
|
+
if (data.agentStats) {
|
|
366
|
+
agentsChart.data.datasets[0].data = [data.agentStats.active || 1, data.agentStats.idle || 0, data.agentStats.offline || 0];
|
|
367
|
+
agentsChart.update();
|
|
368
|
+
}
|
|
369
|
+
}).catch(() => {});
|
|
139
370
|
</script>
|
|
140
371
|
</body>
|
|
141
372
|
</html>`;
|
package/dist/dashboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,MAAM,EACJ,SAAS,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,EAC7C,eAAe,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,WAAW,EACvD,MAAM,EACP,GAAG,IAAI,CAAC;IAET,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kDA2FyC,SAAS;;;;gDAIX,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,qBAAqB,IAAI,CAAC,WAAW,EAAE;;;;gDAIvE,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,uBAAuB,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,WAAW;;;;4BAIjJ,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,wBAAwB,eAAe,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO;;;;4FAItC,SAAS;;;;+CAItD,SAAS;;;;qDAIH,GAAG;;;;0FAIkC,WAAW;;;;;;2BAM1E,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,sBAAsB,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,sBAAsB,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;;qGAElD,IAAI,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM;uIACI,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc;;;;;;;;;;;;;;;;;;;;;;qCAsBhI,SAAS;qCACT,IAAI,MAAM,eAAe,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAiN1F,CAAC;AACT,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent Booster — Fast Local Execution for Simple Tasks
|
|
3
3
|
*
|
|
4
|
-
* MCP Swarm
|
|
4
|
+
* MCP Swarm v1.1.6
|
|
5
5
|
*
|
|
6
6
|
* Executes trivial tasks locally without calling LLM APIs:
|
|
7
7
|
* - Rename variables/functions
|
|
@@ -36,6 +36,8 @@ const DEFAULT_CONFIG = {
|
|
|
36
36
|
backupBeforeChange: true,
|
|
37
37
|
dryRun: false,
|
|
38
38
|
estimatedLLMCostPerTask: 0.01, // $0.01 per simple task
|
|
39
|
+
ollamaUrl: undefined, // Not set by default — fully optional
|
|
40
|
+
ollamaModel: "codellama:7b",
|
|
39
41
|
};
|
|
40
42
|
// Estimated LLM time for comparison (ms)
|
|
41
43
|
const ESTIMATED_LLM_TIME_MS = 3000;
|
|
@@ -534,6 +536,71 @@ async function executeExtractConstant(content, task) {
|
|
|
534
536
|
lines: affectedLines,
|
|
535
537
|
};
|
|
536
538
|
}
|
|
539
|
+
// ============ OLLAMA INTEGRATION (OPTIONAL) ============
|
|
540
|
+
/**
|
|
541
|
+
* Execute a complex task via local Ollama LLM
|
|
542
|
+
* Only works if ollamaUrl is set in booster config
|
|
543
|
+
*/
|
|
544
|
+
async function executeOllamaGenerate(content, task, config) {
|
|
545
|
+
if (!config.ollamaUrl) {
|
|
546
|
+
throw new Error("Ollama is not configured. Set ollamaUrl in booster config:\n" +
|
|
547
|
+
" swarm_booster({ action: 'set_config', config: { ollamaUrl: 'http://localhost:11434' } })");
|
|
548
|
+
}
|
|
549
|
+
const prompt = task.prompt || `Modify this code:\n\n${content}`;
|
|
550
|
+
const model = task.model || config.ollamaModel || "codellama:7b";
|
|
551
|
+
try {
|
|
552
|
+
const response = await fetch(`${config.ollamaUrl}/api/generate`, {
|
|
553
|
+
method: "POST",
|
|
554
|
+
headers: { "Content-Type": "application/json" },
|
|
555
|
+
body: JSON.stringify({
|
|
556
|
+
model,
|
|
557
|
+
prompt: `You are a code assistant. Given the following code, apply the requested change and return ONLY the modified code without explanation.\n\nCode:\n\`\`\`\n${content}\n\`\`\`\n\nRequest: ${prompt}\n\nModified code:`,
|
|
558
|
+
stream: false,
|
|
559
|
+
options: {
|
|
560
|
+
temperature: 0.1,
|
|
561
|
+
num_predict: 4096,
|
|
562
|
+
},
|
|
563
|
+
}),
|
|
564
|
+
signal: AbortSignal.timeout(60_000),
|
|
565
|
+
});
|
|
566
|
+
if (!response.ok) {
|
|
567
|
+
const err = await response.text();
|
|
568
|
+
throw new Error(`Ollama error (${response.status}): ${err}`);
|
|
569
|
+
}
|
|
570
|
+
const data = await response.json();
|
|
571
|
+
let newContent = data.response || "";
|
|
572
|
+
// Clean up ollama response — extract code from markdown blocks if present
|
|
573
|
+
const codeBlockMatch = newContent.match(/```(?:\w+)?\n([\s\S]*?)```/);
|
|
574
|
+
if (codeBlockMatch) {
|
|
575
|
+
newContent = codeBlockMatch[1].trim();
|
|
576
|
+
}
|
|
577
|
+
if (!newContent || newContent.trim() === content.trim()) {
|
|
578
|
+
return { content, changes: 0, lines: [] };
|
|
579
|
+
}
|
|
580
|
+
// Simple diff: count changed lines
|
|
581
|
+
const oldLines = content.split("\n");
|
|
582
|
+
const newLines = newContent.split("\n");
|
|
583
|
+
const changedLines = [];
|
|
584
|
+
const maxLen = Math.max(oldLines.length, newLines.length);
|
|
585
|
+
for (let i = 0; i < maxLen; i++) {
|
|
586
|
+
if ((oldLines[i] || "") !== (newLines[i] || "")) {
|
|
587
|
+
changedLines.push(i + 1);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return {
|
|
591
|
+
content: newContent,
|
|
592
|
+
changes: changedLines.length,
|
|
593
|
+
lines: changedLines,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
catch (err) {
|
|
597
|
+
if (err instanceof Error && err.message.includes("Ollama error")) {
|
|
598
|
+
throw err;
|
|
599
|
+
}
|
|
600
|
+
throw new Error(`Cannot connect to Ollama at ${config.ollamaUrl}. ` +
|
|
601
|
+
`Make sure Ollama is running: ollama serve`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
537
604
|
// ============ MAIN EXECUTOR ============
|
|
538
605
|
/**
|
|
539
606
|
* Execute a booster task
|
|
@@ -618,6 +685,9 @@ export async function executeTask(input) {
|
|
|
618
685
|
case "extract_constant":
|
|
619
686
|
result = await executeExtractConstant(originalContent, input.task);
|
|
620
687
|
break;
|
|
688
|
+
case "ollama_generate":
|
|
689
|
+
result = await executeOllamaGenerate(originalContent, input.task, config);
|
|
690
|
+
break;
|
|
621
691
|
default:
|
|
622
692
|
throw new Error(`Unsupported task type: ${input.task.type}`);
|
|
623
693
|
}
|
|
@@ -824,12 +894,33 @@ export async function canBoost(input) {
|
|
|
824
894
|
};
|
|
825
895
|
}
|
|
826
896
|
}
|
|
897
|
+
// Check if Ollama is available for more complex tasks
|
|
898
|
+
const repoRoot = await getRepoRoot(input.repoPath);
|
|
899
|
+
const config = await loadConfig(repoRoot);
|
|
900
|
+
if (config.ollamaUrl) {
|
|
901
|
+
// With Ollama, we can handle more complex tasks
|
|
902
|
+
const complexPatterns = [
|
|
903
|
+
/refactor/i, /optimize/i, /simplify/i, /generate/i,
|
|
904
|
+
/explain/i, /document/i, /review/i, /suggest/i,
|
|
905
|
+
];
|
|
906
|
+
if (complexPatterns.some(p => p.test(text))) {
|
|
907
|
+
return {
|
|
908
|
+
canBoost: true,
|
|
909
|
+
taskType: "ollama_generate",
|
|
910
|
+
confidence: 0.7,
|
|
911
|
+
suggestedParams: { prompt: input.description },
|
|
912
|
+
reason: `Ollama available — can handle complex task via local LLM (${config.ollamaModel || 'codellama:7b'})`,
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
}
|
|
827
916
|
return {
|
|
828
917
|
canBoost: false,
|
|
829
918
|
taskType: null,
|
|
830
919
|
confidence: 0,
|
|
831
920
|
suggestedParams: {},
|
|
832
|
-
reason:
|
|
921
|
+
reason: config.ollamaUrl
|
|
922
|
+
? "Task does not match any boostable pattern (Ollama available for complex tasks)"
|
|
923
|
+
: "Task does not match any boostable pattern (tip: set ollamaUrl in config for complex tasks)",
|
|
833
924
|
};
|
|
834
925
|
}
|
|
835
926
|
// ============ API ============
|
|
@@ -892,6 +983,7 @@ export function getSupportedTypes() {
|
|
|
892
983
|
{ type: "sort_imports", description: "Sort imports alphabetically", requiredParams: [] },
|
|
893
984
|
{ type: "add_export", description: "Add export to declaration", requiredParams: ["variableName"] },
|
|
894
985
|
{ type: "extract_constant", description: "Extract value to constant", requiredParams: ["searchText", "variableName"] },
|
|
986
|
+
{ type: "ollama_generate", description: "[Optional] Use local Ollama LLM for complex tasks (requires OLLAMA_URL)", requiredParams: ["prompt"] },
|
|
895
987
|
];
|
|
896
988
|
}
|
|
897
989
|
export async function handleBoosterTool(input) {
|