@swarmclawai/swarmclaw 0.3.1 → 0.4.5

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 (203) hide show
  1. package/README.md +33 -13
  2. package/bin/server-cmd.js +14 -7
  3. package/bin/swarmclaw.js +3 -1
  4. package/bin/update-cmd.js +120 -0
  5. package/next.config.ts +10 -0
  6. package/package.json +4 -1
  7. package/src/app/api/agents/[id]/route.ts +20 -18
  8. package/src/app/api/agents/[id]/thread/route.ts +4 -3
  9. package/src/app/api/agents/route.ts +8 -3
  10. package/src/app/api/auth/route.ts +3 -1
  11. package/src/app/api/claude-skills/route.ts +3 -1
  12. package/src/app/api/clawhub/install/route.ts +2 -2
  13. package/src/app/api/connectors/[id]/route.ts +14 -3
  14. package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
  15. package/src/app/api/connectors/route.ts +12 -4
  16. package/src/app/api/credentials/[id]/route.ts +2 -1
  17. package/src/app/api/credentials/route.ts +5 -3
  18. package/src/app/api/daemon/route.ts +6 -1
  19. package/src/app/api/documents/route.ts +2 -2
  20. package/src/app/api/files/serve/route.ts +8 -0
  21. package/src/app/api/ip/route.ts +3 -1
  22. package/src/app/api/knowledge/[id]/route.ts +5 -4
  23. package/src/app/api/knowledge/upload/route.ts +2 -2
  24. package/src/app/api/mcp-servers/[id]/route.ts +11 -14
  25. package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
  26. package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
  27. package/src/app/api/mcp-servers/route.ts +5 -3
  28. package/src/app/api/memory/[id]/route.ts +9 -8
  29. package/src/app/api/memory/route.ts +2 -2
  30. package/src/app/api/memory-images/[filename]/route.ts +2 -1
  31. package/src/app/api/openclaw/directory/route.ts +26 -0
  32. package/src/app/api/openclaw/discover/route.ts +61 -0
  33. package/src/app/api/openclaw/sync/route.ts +30 -0
  34. package/src/app/api/orchestrator/graph/route.ts +25 -0
  35. package/src/app/api/orchestrator/run/route.ts +2 -2
  36. package/src/app/api/plugins/marketplace/route.ts +3 -1
  37. package/src/app/api/plugins/route.ts +3 -1
  38. package/src/app/api/projects/[id]/route.ts +55 -0
  39. package/src/app/api/projects/route.ts +27 -0
  40. package/src/app/api/providers/[id]/models/route.ts +2 -1
  41. package/src/app/api/providers/[id]/route.ts +13 -12
  42. package/src/app/api/providers/configs/route.ts +3 -1
  43. package/src/app/api/providers/route.ts +7 -3
  44. package/src/app/api/schedules/[id]/route.ts +16 -15
  45. package/src/app/api/schedules/[id]/run/route.ts +4 -3
  46. package/src/app/api/schedules/route.ts +8 -3
  47. package/src/app/api/secrets/[id]/route.ts +16 -17
  48. package/src/app/api/secrets/route.ts +5 -3
  49. package/src/app/api/sessions/[id]/chat/route.ts +5 -2
  50. package/src/app/api/sessions/[id]/clear/route.ts +2 -1
  51. package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
  52. package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
  53. package/src/app/api/sessions/[id]/messages/route.ts +2 -1
  54. package/src/app/api/sessions/[id]/retry/route.ts +2 -1
  55. package/src/app/api/sessions/[id]/route.ts +2 -1
  56. package/src/app/api/sessions/route.ts +11 -4
  57. package/src/app/api/settings/route.ts +3 -1
  58. package/src/app/api/setup/doctor/route.ts +1 -0
  59. package/src/app/api/setup/openclaw-device/route.ts +3 -1
  60. package/src/app/api/skills/[id]/route.ts +23 -21
  61. package/src/app/api/skills/import/route.ts +2 -2
  62. package/src/app/api/skills/route.ts +5 -3
  63. package/src/app/api/tasks/[id]/approve/route.ts +74 -0
  64. package/src/app/api/tasks/[id]/route.ts +9 -5
  65. package/src/app/api/tasks/route.ts +5 -2
  66. package/src/app/api/tts/stream/route.ts +48 -0
  67. package/src/app/api/upload/route.ts +2 -2
  68. package/src/app/api/uploads/[filename]/route.ts +4 -1
  69. package/src/app/api/usage/route.ts +3 -1
  70. package/src/app/api/version/route.ts +3 -1
  71. package/src/app/api/webhooks/[id]/route.ts +31 -32
  72. package/src/app/api/webhooks/route.ts +5 -3
  73. package/src/app/icon.svg +58 -0
  74. package/src/app/page.tsx +11 -26
  75. package/src/cli/index.js +28 -9
  76. package/src/cli/index.ts +45 -2
  77. package/src/cli/spec.js +2 -8
  78. package/src/components/agents/agent-card.tsx +1 -1
  79. package/src/components/agents/agent-list.tsx +3 -1
  80. package/src/components/agents/agent-sheet.tsx +166 -81
  81. package/src/components/chat/chat-area.tsx +71 -34
  82. package/src/components/chat/chat-header.tsx +141 -29
  83. package/src/components/chat/chat-tool-toggles.tsx +12 -53
  84. package/src/components/chat/message-bubble.tsx +110 -42
  85. package/src/components/chat/tool-call-bubble.tsx +50 -6
  86. package/src/components/chat/tool-request-banner.tsx +1 -9
  87. package/src/components/chat/voice-overlay.tsx +80 -0
  88. package/src/components/connectors/connector-list.tsx +9 -10
  89. package/src/components/connectors/connector-sheet.tsx +55 -36
  90. package/src/components/input/chat-input.tsx +72 -56
  91. package/src/components/knowledge/knowledge-list.tsx +27 -31
  92. package/src/components/layout/app-layout.tsx +133 -90
  93. package/src/components/layout/daemon-indicator.tsx +3 -5
  94. package/src/components/logs/log-list.tsx +5 -9
  95. package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
  96. package/src/components/memory/memory-detail.tsx +1 -1
  97. package/src/components/plugins/plugin-list.tsx +227 -27
  98. package/src/components/projects/project-list.tsx +122 -0
  99. package/src/components/projects/project-sheet.tsx +135 -0
  100. package/src/components/providers/provider-list.tsx +46 -13
  101. package/src/components/providers/provider-sheet.tsx +0 -45
  102. package/src/components/runs/run-list.tsx +6 -15
  103. package/src/components/schedules/schedule-card.tsx +54 -4
  104. package/src/components/schedules/schedule-list.tsx +9 -4
  105. package/src/components/schedules/schedule-sheet.tsx +0 -47
  106. package/src/components/secrets/secrets-list.tsx +20 -2
  107. package/src/components/sessions/new-session-sheet.tsx +14 -15
  108. package/src/components/sessions/session-card.tsx +1 -1
  109. package/src/components/sessions/session-list.tsx +7 -7
  110. package/src/components/shared/connector-platform-icon.tsx +26 -20
  111. package/src/components/shared/model-combobox.tsx +148 -0
  112. package/src/components/shared/settings/section-heartbeat.tsx +8 -40
  113. package/src/components/shared/settings/section-orchestrator.tsx +9 -11
  114. package/src/components/shared/settings/section-web-search.tsx +56 -0
  115. package/src/components/shared/settings/settings-page.tsx +73 -0
  116. package/src/components/skills/skill-list.tsx +262 -35
  117. package/src/components/skills/skill-sheet.tsx +0 -45
  118. package/src/components/tasks/task-board.tsx +3 -6
  119. package/src/components/tasks/task-card.tsx +43 -1
  120. package/src/components/tasks/task-list.tsx +8 -7
  121. package/src/components/tasks/task-sheet.tsx +0 -44
  122. package/src/components/usage/usage-list.tsx +12 -4
  123. package/src/hooks/use-continuous-speech.ts +144 -0
  124. package/src/hooks/use-view-router.ts +52 -0
  125. package/src/hooks/use-voice-conversation.ts +80 -0
  126. package/src/hooks/use-ws.ts +66 -0
  127. package/src/instrumentation.ts +2 -0
  128. package/src/lib/chat.ts +14 -2
  129. package/src/lib/id.ts +6 -0
  130. package/src/lib/projects.ts +13 -0
  131. package/src/lib/provider-sets.ts +5 -0
  132. package/src/lib/providers/anthropic.ts +15 -2
  133. package/src/lib/providers/index.ts +8 -0
  134. package/src/lib/providers/ollama.ts +10 -2
  135. package/src/lib/providers/openai.ts +42 -13
  136. package/src/lib/providers/openclaw.ts +11 -0
  137. package/src/lib/server/api-routes.test.ts +5 -6
  138. package/src/lib/server/build-llm.ts +17 -4
  139. package/src/lib/server/chat-execution.ts +57 -8
  140. package/src/lib/server/collection-helpers.ts +54 -0
  141. package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
  142. package/src/lib/server/connectors/bluebubbles.ts +357 -0
  143. package/src/lib/server/connectors/connector-routing.test.ts +1 -1
  144. package/src/lib/server/connectors/googlechat.ts +46 -7
  145. package/src/lib/server/connectors/manager.ts +401 -6
  146. package/src/lib/server/connectors/media.ts +2 -2
  147. package/src/lib/server/connectors/openclaw.ts +64 -0
  148. package/src/lib/server/connectors/pairing.test.ts +99 -0
  149. package/src/lib/server/connectors/pairing.ts +256 -0
  150. package/src/lib/server/connectors/signal.ts +1 -0
  151. package/src/lib/server/connectors/teams.ts +5 -5
  152. package/src/lib/server/connectors/types.ts +10 -0
  153. package/src/lib/server/context-manager.ts +1 -1
  154. package/src/lib/server/daemon-state.ts +3 -0
  155. package/src/lib/server/data-dir.ts +1 -0
  156. package/src/lib/server/execution-log.ts +3 -3
  157. package/src/lib/server/heartbeat-service.ts +67 -3
  158. package/src/lib/server/knowledge-db.test.ts +2 -33
  159. package/src/lib/server/langgraph-checkpoint.ts +274 -0
  160. package/src/lib/server/main-agent-loop.ts +67 -8
  161. package/src/lib/server/memory-db.ts +6 -6
  162. package/src/lib/server/openclaw-approvals.ts +105 -0
  163. package/src/lib/server/openclaw-sync.ts +496 -0
  164. package/src/lib/server/orchestrator-lg.ts +422 -20
  165. package/src/lib/server/orchestrator.ts +29 -9
  166. package/src/lib/server/process-manager.ts +2 -2
  167. package/src/lib/server/queue.ts +39 -13
  168. package/src/lib/server/scheduler.ts +2 -2
  169. package/src/lib/server/session-mailbox.ts +2 -2
  170. package/src/lib/server/session-run-manager.ts +8 -3
  171. package/src/lib/server/session-tools/connector.ts +51 -4
  172. package/src/lib/server/session-tools/crud.ts +3 -3
  173. package/src/lib/server/session-tools/delegate.ts +5 -5
  174. package/src/lib/server/session-tools/file.ts +176 -3
  175. package/src/lib/server/session-tools/index.ts +4 -0
  176. package/src/lib/server/session-tools/memory.ts +2 -2
  177. package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
  178. package/src/lib/server/session-tools/sandbox.ts +197 -0
  179. package/src/lib/server/session-tools/search-providers.ts +270 -0
  180. package/src/lib/server/session-tools/session-info.ts +2 -2
  181. package/src/lib/server/session-tools/web.ts +47 -66
  182. package/src/lib/server/storage-mcp.test.ts +25 -2
  183. package/src/lib/server/storage.ts +36 -7
  184. package/src/lib/server/stream-agent-chat.ts +106 -22
  185. package/src/lib/server/task-result.test.ts +44 -0
  186. package/src/lib/server/task-result.ts +14 -0
  187. package/src/lib/server/task-validation.test.ts +23 -0
  188. package/src/lib/server/task-validation.ts +5 -3
  189. package/src/lib/server/ws-hub.ts +85 -0
  190. package/src/lib/tool-definitions.ts +44 -0
  191. package/src/lib/tts-stream.ts +130 -0
  192. package/src/lib/upload.ts +7 -1
  193. package/src/lib/view-routes.ts +28 -0
  194. package/src/lib/ws-client.ts +124 -0
  195. package/src/proxy.ts +3 -0
  196. package/src/stores/use-app-store.ts +28 -1
  197. package/src/stores/use-chat-store.ts +42 -14
  198. package/src/types/index.ts +34 -2
  199. package/src/app/api/agents/generate/route.ts +0 -42
  200. package/src/app/api/generate/info/route.ts +0 -12
  201. package/src/app/api/generate/route.ts +0 -106
  202. package/src/app/favicon.ico +0 -0
  203. package/src/components/shared/ai-gen-block.tsx +0 -77
package/README.md CHANGED
@@ -13,16 +13,10 @@ Inspired by [OpenClaw](https://github.com/openclaw).
13
13
 
14
14
  **[Documentation](https://swarmclaw.ai/docs)** | **[Website](https://swarmclaw.ai)**
15
15
 
16
- Org avatar files: `public/branding/swarmclaw-org-avatar.png` (upload-ready), `public/branding/swarmclaw-org-avatar.svg` (source)
17
-
18
16
  ![Dashboard](public/screenshots/dashboard.png)
19
17
  ![Agent Builder](public/screenshots/agents.png)
20
18
  ![Task Board](public/screenshots/tasks.png)
21
19
 
22
- ## Security Warning
23
-
24
- SwarmClaw can spawn **Claude Code CLI** processes with full shell access on your machine. This means agents can read, write, and execute anything your user account can. **Do not run this on a shared server or expose it to the public internet without understanding the risks.**
25
-
26
20
  - Always use the access key authentication (generated on first run)
27
21
  - Never expose port 3456 without a reverse proxy + TLS
28
22
  - Review agent system prompts before giving them shell or browser tools
@@ -32,17 +26,17 @@ SwarmClaw can spawn **Claude Code CLI** processes with full shell access on your
32
26
  - **15 Built-in Providers** — Claude Code CLI, OpenAI Codex CLI, OpenCode CLI, Anthropic, OpenAI, Google Gemini, DeepSeek, Groq, Together AI, Mistral AI, xAI (Grok), Fireworks AI, Ollama, plus custom OpenAI-compatible endpoints
33
27
  - **OpenClaw Gateway** — Per-agent toggle to connect any agent to a local or remote OpenClaw gateway. Each agent gets its own gateway URL and token — run a swarm of OpenClaws from one dashboard. The `openclaw` CLI ships as a bundled dependency (no separate install needed)
34
28
  - **Agent Builder** — Create agents with custom personalities (soul), system prompts, tools, and skills. AI-powered generation from a description
35
- - **Agent Tools** — Shell, process control for long-running commands, files, edit file, send file, web search, web fetch, CLI delegation (Claude/Codex/OpenCode), Playwright browser automation, and persistent memory
29
+ - **Agent Tools** — Shell, process control for long-running commands, files, edit file, send file, web search, web fetch, CLI delegation (Claude/Codex/OpenCode), Playwright browser automation, persistent memory, and sandboxed code execution (JS/TS via Deno, Python)
36
30
  - **Platform Tools** — Agents can manage other agents, tasks, schedules, skills, connectors, sessions, and encrypted secrets via built-in platform tools
37
- - **Orchestration** — Multi-agent workflows powered by LangGraph with automatic sub-agent routing
31
+ - **Orchestration** — Multi-agent workflows powered by LangGraph with automatic sub-agent routing, checkpointed execution, and rich delegation cards that link to sub-agent chat threads
38
32
  - **Agentic Execution Policy** — Tool-first autonomous action loop with progress updates, evidence-driven answers, and better use of platform tools for long-lived work
39
- - **Task Board** — Queue and track agent tasks with status, comments, results, and archiving (soft delete with show/hide toggle)
33
+ - **Task Board** — Queue and track agent tasks with status, comments, results, and archiving. Strict capability policy pauses tasks for human approval before tool execution
40
34
  - **Background Daemon** — Auto-processes queued tasks and scheduled jobs with a 30s heartbeat plus recurring health monitoring
41
35
  - **Scheduling** — Cron-based agent scheduling with human-friendly presets
42
36
  - **Loop Runtime Controls** — Switch between bounded and ongoing loops with configurable step caps, runtime guards, heartbeat cadence, and timeout budgets
43
37
  - **Session Run Queue** — Per-session queued runs with followup/steer/collect modes, collect coalescing for bursty inputs, and run-state APIs
44
38
  - **Voice Settings** — Per-instance ElevenLabs API key + voice ID for TTS replies, plus configurable speech recognition language for chat input
45
- - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, and WhatsApp with media-aware inbound handling
39
+ - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, WhatsApp, BlueBubbles (iMessage), Signal, Microsoft Teams, Google Chat, Matrix, and OpenClaw with media-aware inbound handling
46
40
  - **Skills System** — Discover local skills, import skills from URL, and load OpenClaw `SKILL.md` files (frontmatter-compatible)
47
41
  - **Execution Logging** — Structured audit trail for triggers, tool calls, file ops, commits, and errors in a dedicated `logs.db`
48
42
  - **Context Management** — Auto-compaction of conversation history when approaching context limits, with manual `context_status` and `context_summarize` tools for agents
@@ -52,6 +46,9 @@ SwarmClaw can spawn **Claude Code CLI** processes with full shell access on your
52
46
  - **Plugin System** — Extend agent behavior with JS plugins (hooks: beforeAgentStart, afterAgentComplete, beforeToolExec, afterToolExec, onMessage)
53
47
  - **Secrets Vault** — Encrypted storage for API keys and service tokens
54
48
  - **Custom Providers** — Add any OpenAI-compatible API as a provider
49
+ - **MCP Servers** — Connect agents to any Model Context Protocol server. Per-agent server selection with tool discovery and per-tool disable toggles
50
+ - **Sandboxed Code Execution** — Agents can write and run JS/TS (Deno) or Python scripts in an isolated sandbox with network access, scoped filesystem, and artifact output
51
+ - **Real-Time Sync** — WebSocket push notifications for instant UI updates across tabs and devices (fallback to polling when WS is unavailable)
55
52
  - **Mobile-First UI** — Responsive glass-themed dark interface, works on phone and desktop
56
53
 
57
54
  ## Requirements
@@ -64,6 +61,15 @@ SwarmClaw can spawn **Claude Code CLI** processes with full shell access on your
64
61
 
65
62
  ## Quick Start
66
63
 
64
+ ### npm (recommended)
65
+
66
+ ```bash
67
+ npm i -g @swarmclawai/swarmclaw
68
+ swarmclaw
69
+ ```
70
+
71
+ ### Install script
72
+
67
73
  ```bash
68
74
  curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.sh | bash
69
75
  ```
@@ -134,7 +140,9 @@ ACCESS_KEY=<your-access-key> # Auth key for the dashboard
134
140
  CREDENTIAL_SECRET=<auto-generated> # AES-256 encryption key for stored credentials
135
141
  ```
136
142
 
137
- Data is stored in `data/swarmclaw.db` (SQLite with WAL mode), `data/memory.db` (agent memory with FTS5 + vector embeddings), and `data/logs.db` (execution audit trail). Back the `data/` directory up if you care about your sessions, agents, and credentials. Existing JSON file data is auto-migrated to SQLite on first run.
143
+ Data is stored in `data/swarmclaw.db` (SQLite with WAL mode), `data/memory.db` (agent memory with FTS5 + vector embeddings), `data/logs.db` (execution audit trail), and `data/langgraph-checkpoints.db` (orchestrator checkpoints). Back the `data/` directory up if you care about your sessions, agents, and credentials. Existing JSON file data is auto-migrated to SQLite on first run.
144
+
145
+ The app listens on two ports: `PORT` (default 3456) for the HTTP/SSE API, and `PORT + 1` (default 3457) for WebSocket push notifications. The WS port can be customized with `--ws-port`.
138
146
 
139
147
  ## Architecture
140
148
 
@@ -151,7 +159,6 @@ src/
151
159
  │ ├── layout/ # App shell, sidebar, mobile header
152
160
  │ ├── providers/ # Provider management
153
161
  │ ├── schedules/ # Cron scheduler
154
- │ ├── sessions/ # Session list + new session
155
162
  │ ├── skills/ # Skills manager
156
163
  │ ├── tasks/ # Task board
157
164
  │ └── shared/ # Reusable UI (BottomSheet, IconButton, etc.)
@@ -222,6 +229,12 @@ Bridge any agent to a chat platform:
222
229
  | Slack | @slack/bolt | Bot token + app token (Socket Mode) |
223
230
  | Telegram | grammy | Bot token from @BotFather |
224
231
  | WhatsApp | baileys | QR code pairing (shown in browser) |
232
+ | BlueBubbles | Custom webhook bridge | Server URL + password/webhook secret |
233
+ | Signal | signal-cli | `signal-cli` binary + linked phone |
234
+ | Microsoft Teams | botbuilder | Bot Framework credentials + webhook ingress |
235
+ | Google Chat | googleapis | Service account + webhook ingress |
236
+ | Matrix | matrix-bot-sdk | Homeserver URL + access token |
237
+ | OpenClaw | gateway protocol | OpenClaw connector credentials |
225
238
 
226
239
  Connector sessions preserve attachment visibility in chat context:
227
240
  - WhatsApp media is decoded and persisted to `/api/uploads/...` when possible
@@ -230,7 +243,12 @@ Connector sessions preserve attachment visibility in chat context:
230
243
 
231
244
  Agents automatically suppress replies to simple acknowledgments ("ok", "thanks", thumbs-up, etc.) via a `NO_MESSAGE` response — conversations feel natural without a forced reply to every message. This is handled at the connector layer, so agents can return `NO_MESSAGE` as their response content and the platform won't deliver anything to the channel.
232
245
 
233
- For proactive outreach, `connector_message_tool` supports text plus optional `imageUrl` / `fileUrl` / `mediaPath` (local file path) payloads. All four platforms (WhatsApp, Discord, Slack, Telegram) support local file sending via `mediaPath` with auto-detected MIME types.
246
+ For proactive outreach, `connector_message_tool` supports text plus optional `imageUrl` / `fileUrl` / `mediaPath` (local file path) payloads. WhatsApp, Discord, Slack, and Telegram support local file sending via `mediaPath` with auto-detected MIME types.
247
+
248
+ Connector ingress now also supports optional pairing/allowlist policy:
249
+ - `dmPolicy: allowlist` blocks unknown senders until approved
250
+ - `/pair` flow lets approved admins generate and approve pairing codes
251
+ - `/think` command can set connector thread thinking level (`low`, `medium`, `high`)
234
252
 
235
253
  ## Agent Tools
236
254
 
@@ -248,6 +266,8 @@ Agents can use the following tools when enabled:
248
266
  | CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, or OpenCode CLI |
249
267
  | Browser | Playwright-powered web browsing via MCP (navigate, click, type, screenshot, PDF) |
250
268
  | Memory | Store and retrieve long-term memories with FTS5 + vector search, file references, image attachments, and linked memory graph traversal |
269
+ | Sandbox | Run JS/TS (Deno) or Python code in an isolated sandbox. Created files are returned as downloadable artifacts |
270
+ | MCP Servers | Connect to external Model Context Protocol servers. Tools from MCP servers are injected as first-class agent tools |
251
271
 
252
272
  ### Platform Tools
253
273
 
package/bin/server-cmd.js CHANGED
@@ -177,16 +177,18 @@ function startServer(opts) {
177
177
  }
178
178
 
179
179
  const port = opts.port || '3456'
180
+ const wsPort = opts.wsPort || String(Number(port) + 1)
180
181
  const host = opts.host || '0.0.0.0'
181
182
 
182
183
  const env = {
183
184
  ...process.env,
184
185
  PORT: port,
186
+ WS_PORT: wsPort,
185
187
  HOSTNAME: host,
186
188
  DATA_DIR,
187
189
  }
188
190
 
189
- log(`Starting server on ${host}:${port}...`)
191
+ log(`Starting server on ${host}:${port} (WebSocket: ${wsPort})...`)
190
192
  log(`Data directory: ${DATA_DIR}`)
191
193
 
192
194
  if (opts.detach) {
@@ -268,6 +270,7 @@ function showStatus() {
268
270
 
269
271
  log(`Home: ${SWARMCLAW_HOME}`)
270
272
  log(`Data: ${DATA_DIR}`)
273
+ log(`WebSocket port: ${process.env.WS_PORT || '(PORT + 1)'}`)
271
274
 
272
275
  if (fs.existsSync(BUILT_MARKER)) {
273
276
  try {
@@ -295,11 +298,12 @@ Commands:
295
298
  status Show server status
296
299
 
297
300
  Options:
298
- --build Force rebuild before starting
299
- -d, --detach Start server in background
300
- --port <port> Server port (default: 3456)
301
- --host <host> Server host (default: 0.0.0.0)
302
- -h, --help Show this help message
301
+ --build Force rebuild before starting
302
+ -d, --detach Start server in background
303
+ --port <port> Server port (default: 3456)
304
+ --ws-port <port> WebSocket port (default: PORT + 1)
305
+ --host <host> Server host (default: 0.0.0.0)
306
+ -h, --help Show this help message
303
307
  `.trim()
304
308
  console.log(help)
305
309
  }
@@ -310,6 +314,7 @@ function main() {
310
314
  let forceBuild = false
311
315
  let detach = false
312
316
  let port = null
317
+ let wsPort = null
313
318
  let host = null
314
319
 
315
320
  for (let i = 0; i < args.length; i++) {
@@ -326,6 +331,8 @@ function main() {
326
331
  detach = true
327
332
  } else if (arg === '--port' && i + 1 < args.length) {
328
333
  port = args[++i]
334
+ } else if (arg === '--ws-port' && i + 1 < args.length) {
335
+ wsPort = args[++i]
329
336
  } else if (arg === '--host' && i + 1 < args.length) {
330
337
  host = args[++i]
331
338
  } else if (arg === '-h' || arg === '--help') {
@@ -353,7 +360,7 @@ function main() {
353
360
  runBuild()
354
361
  }
355
362
 
356
- startServer({ port, host, detach })
363
+ startServer({ port, wsPort, host, detach })
357
364
  }
358
365
 
359
366
  main()
package/bin/swarmclaw.js CHANGED
@@ -4,9 +4,11 @@
4
4
  const path = require('node:path')
5
5
  const { spawnSync } = require('node:child_process')
6
6
 
7
- // Route 'server' subcommand to the CJS server script (no TS dependency)
7
+ // Route 'server' and 'update' subcommands to CJS scripts (no TS dependency)
8
8
  if (process.argv[2] === 'server') {
9
9
  require('./server-cmd.js')
10
+ } else if (process.argv[2] === 'update') {
11
+ require('./update-cmd.js')
10
12
  } else {
11
13
  const cliPath = path.join(__dirname, '..', 'src', 'cli', 'index.ts')
12
14
 
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const { execSync } = require('node:child_process')
5
+ const path = require('node:path')
6
+
7
+ const PKG_ROOT = path.resolve(__dirname, '..')
8
+ const RELEASE_TAG_RE = /^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/
9
+
10
+ function run(cmd) {
11
+ return execSync(cmd, { encoding: 'utf-8', cwd: PKG_ROOT, timeout: 60_000 }).trim()
12
+ }
13
+
14
+ function log(msg) {
15
+ process.stdout.write(`[swarmclaw] ${msg}\n`)
16
+ }
17
+
18
+ function logError(msg) {
19
+ process.stderr.write(`[swarmclaw] ${msg}\n`)
20
+ }
21
+
22
+ function getLatestStableTag() {
23
+ const tags = run("git tag --list 'v*' --sort=-v:refname")
24
+ .split('\n')
25
+ .map((l) => l.trim())
26
+ .filter(Boolean)
27
+ return tags.find((t) => RELEASE_TAG_RE.test(t)) || null
28
+ }
29
+
30
+ function main() {
31
+ const args = process.argv.slice(3)
32
+ if (args.includes('-h') || args.includes('--help')) {
33
+ console.log(`
34
+ Usage: swarmclaw update
35
+
36
+ Pull the latest SwarmClaw release via git.
37
+ Prefers stable release tags (v*); falls back to origin/main.
38
+ Runs npm install if package files changed.
39
+ `.trim())
40
+ process.exit(0)
41
+ }
42
+
43
+ // Verify we're in a git repo
44
+ try {
45
+ run('git rev-parse --git-dir')
46
+ } catch {
47
+ logError('Not a git repository. Cannot update.')
48
+ process.exit(1)
49
+ }
50
+
51
+ const beforeRef = run('git rev-parse HEAD')
52
+ const beforeSha = run('git rev-parse --short HEAD')
53
+
54
+ log('Fetching latest releases...')
55
+ try {
56
+ run('git fetch --tags origin --quiet')
57
+ } catch (err) {
58
+ logError(`Fetch failed: ${err.message}`)
59
+ process.exit(1)
60
+ }
61
+
62
+ const latestTag = getLatestStableTag()
63
+ let channel = 'main'
64
+ let pullOutput = ''
65
+
66
+ if (latestTag) {
67
+ channel = 'stable'
68
+ const targetSha = run(`git rev-parse ${latestTag}^{commit}`)
69
+ if (targetSha === beforeRef) {
70
+ log(`Already up to date (${latestTag}, ${beforeSha}).`)
71
+ process.exit(0)
72
+ }
73
+
74
+ // Check for uncommitted changes
75
+ const dirty = run('git status --porcelain')
76
+ if (dirty) {
77
+ logError('Local changes detected. Commit or stash them first, then retry.')
78
+ process.exit(1)
79
+ }
80
+
81
+ log(`Updating to ${latestTag}...`)
82
+ run(`git checkout -B stable ${latestTag}^{commit}`)
83
+ pullOutput = `Updated to stable release ${latestTag}.`
84
+ } else {
85
+ // Fallback: pull from origin/main
86
+ const behindCount = parseInt(run('git rev-list HEAD..origin/main --count'), 10) || 0
87
+ if (behindCount === 0) {
88
+ log(`Already up to date (${beforeSha}).`)
89
+ process.exit(0)
90
+ }
91
+
92
+ const dirty = run('git status --porcelain')
93
+ if (dirty) {
94
+ logError('Local changes detected. Commit or stash them first, then retry.')
95
+ process.exit(1)
96
+ }
97
+
98
+ log(`Pulling ${behindCount} commit(s) from origin/main...`)
99
+ pullOutput = run('git pull --ff-only origin main')
100
+ }
101
+
102
+ const newSha = run('git rev-parse --short HEAD')
103
+ log(pullOutput)
104
+
105
+ // Install deps if package files changed
106
+ try {
107
+ const diff = run(`git diff --name-only ${beforeSha}..HEAD`)
108
+ if (diff.includes('package-lock.json') || diff.includes('package.json')) {
109
+ log('Package files changed — running npm install...')
110
+ execSync('npm install --omit=dev', { cwd: PKG_ROOT, stdio: 'inherit', timeout: 120_000 })
111
+ }
112
+ } catch {
113
+ // If diff fails, skip install check
114
+ }
115
+
116
+ log(`Done (${beforeSha} → ${newSha}, channel: ${channel}).`)
117
+ log('Restart the server to apply changes: swarmclaw server stop && swarmclaw server start')
118
+ }
119
+
120
+ main()
package/next.config.ts CHANGED
@@ -13,9 +13,11 @@ const nextConfig: NextConfig = {
13
13
  output: 'standalone',
14
14
  env: {
15
15
  NEXT_PUBLIC_GIT_SHA: getGitSha(),
16
+ NEXT_PUBLIC_WS_PORT: String((Number(process.env.PORT) || 3456) + 1),
16
17
  },
17
18
  // Allow external network access
18
19
  serverExternalPackages: [
20
+ 'ws',
19
21
  'highlight.js', 'better-sqlite3',
20
22
  'discord.js', '@discordjs/ws', '@discordjs/rest',
21
23
  'grammy',
@@ -28,6 +30,14 @@ const nextConfig: NextConfig = {
28
30
  '127.0.0.1',
29
31
  '0.0.0.0',
30
32
  ],
33
+ async rewrites() {
34
+ return [
35
+ {
36
+ source: '/:view(agents|schedules|memory|tasks|secrets|providers|skills|connectors|webhooks|mcp-servers|knowledge|plugins|usage|runs|logs|settings|projects)',
37
+ destination: '/',
38
+ },
39
+ ]
40
+ },
31
41
  };
32
42
 
33
43
  export default nextConfig;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "0.3.1",
3
+ "version": "0.4.5",
4
4
  "description": "Self-hosted AI agent orchestration dashboard — manage LLM providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -69,16 +69,19 @@
69
69
  "cron-parser": "^5.5.0",
70
70
  "cronstrue": "^3.12.0",
71
71
  "discord.js": "^14.25.1",
72
+ "exceljs": "^4.4.0",
72
73
  "grammy": "^1.40.0",
73
74
  "highlight.js": "^11.11.1",
74
75
  "lucide-react": "^0.574.0",
75
76
  "next": "16.1.6",
76
77
  "next-themes": "^0.4.6",
77
78
  "openclaw": "^2026.2.26",
79
+ "pdf-parse": "^2.4.5",
78
80
  "qrcode": "^1.5.4",
79
81
  "radix-ui": "^1.4.3",
80
82
  "react": "19.2.3",
81
83
  "react-dom": "19.2.3",
84
+ "react-icons": "^5.5.0",
82
85
  "react-markdown": "^10.1.0",
83
86
  "rehype-highlight": "^7.0.2",
84
87
  "remark-gfm": "^4.0.1",
@@ -1,30 +1,32 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { loadAgents, saveAgents, deleteAgent } from '@/lib/server/storage'
3
3
  import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
4
+ import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
5
+
6
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
+ const ops: CollectionOps<any> = { load: loadAgents, save: saveAgents, deleteFn: deleteAgent, topic: 'agents' }
4
8
 
5
9
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
10
  const { id } = await params
7
11
  const body = await req.json()
8
- const agents = loadAgents()
9
- if (!agents[id]) return new NextResponse(null, { status: 404 })
10
-
11
- Object.assign(agents[id], body, { updatedAt: Date.now() })
12
- if (body.apiEndpoint !== undefined) {
13
- agents[id].apiEndpoint = normalizeProviderEndpoint(
14
- body.provider || agents[id].provider,
15
- body.apiEndpoint,
16
- )
17
- }
18
- delete (agents[id] as Record<string, unknown>).id // prevent id overwrite
19
- agents[id].id = id
20
- saveAgents(agents)
21
- return NextResponse.json(agents[id])
12
+ const result = mutateItem(ops, id, (agent) => {
13
+ Object.assign(agent, body, { updatedAt: Date.now() })
14
+ if (body.apiEndpoint !== undefined) {
15
+ agent.apiEndpoint = normalizeProviderEndpoint(
16
+ body.provider || agent.provider,
17
+ body.apiEndpoint,
18
+ )
19
+ }
20
+ delete (agent as Record<string, unknown>).id
21
+ agent.id = id
22
+ return agent
23
+ })
24
+ if (!result) return notFound()
25
+ return NextResponse.json(result)
22
26
  }
23
27
 
24
28
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
25
29
  const { id } = await params
26
- const agents = loadAgents()
27
- if (!agents[id]) return new NextResponse(null, { status: 404 })
28
- deleteAgent(id)
29
- return NextResponse.json('ok')
30
+ if (!deleteItem(ops, id)) return notFound()
31
+ return NextResponse.json({ ok: true })
30
32
  }
@@ -1,6 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
- import crypto from 'crypto'
2
+ import { genId } from '@/lib/id'
3
3
  import { loadAgents, saveAgents, loadSessions, saveSessions } from '@/lib/server/storage'
4
+ import { WORKSPACE_DIR } from '@/lib/server/data-dir'
4
5
 
5
6
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
7
  const { id: agentId } = await params
@@ -31,12 +32,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
31
32
  }
32
33
 
33
34
  // Create a new thread session
34
- const sessionId = `agent-thread-${agentId}-${crypto.randomBytes(4).toString('hex')}`
35
+ const sessionId = `agent-thread-${agentId}-${genId()}`
35
36
  const now = Date.now()
36
37
  const session = {
37
38
  id: sessionId,
38
39
  name: `agent-thread:${agentId}`,
39
- cwd: process.cwd(),
40
+ cwd: WORKSPACE_DIR,
40
41
  user: user,
41
42
  provider: agent.provider,
42
43
  model: agent.model,
@@ -1,15 +1,18 @@
1
1
  import { NextResponse } from 'next/server'
2
- import crypto from 'crypto'
2
+ import { genId } from '@/lib/id'
3
3
  import { loadAgents, saveAgents } from '@/lib/server/storage'
4
4
  import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
5
+ import { notify } from '@/lib/server/ws-hub'
6
+ export const dynamic = 'force-dynamic'
5
7
 
6
- export async function GET() {
8
+
9
+ export async function GET(_req: Request) {
7
10
  return NextResponse.json(loadAgents())
8
11
  }
9
12
 
10
13
  export async function POST(req: Request) {
11
14
  const body = await req.json()
12
- const id = crypto.randomBytes(4).toString('hex')
15
+ const id = genId()
13
16
  const now = Date.now()
14
17
  const agents = loadAgents()
15
18
  agents[id] = {
@@ -25,9 +28,11 @@ export async function POST(req: Request) {
25
28
  subAgentIds: body.subAgentIds || [],
26
29
  tools: body.tools || [],
27
30
  capabilities: body.capabilities || [],
31
+ thinkingLevel: body.thinkingLevel || undefined,
28
32
  createdAt: now,
29
33
  updatedAt: now,
30
34
  }
31
35
  saveAgents(agents)
36
+ notify('agents')
32
37
  return NextResponse.json(agents[id])
33
38
  }
@@ -1,9 +1,11 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { validateAccessKey, getAccessKey, isFirstTimeSetup, markSetupComplete } from '@/lib/server/storage'
3
3
  import { ensureDaemonStarted } from '@/lib/server/daemon-state'
4
+ export const dynamic = 'force-dynamic'
5
+
4
6
 
5
7
  /** GET /api/auth — check if this is a first-time setup (returns key for initial display) */
6
- export async function GET() {
8
+ export async function GET(_req: Request) {
7
9
  if (isFirstTimeSetup()) {
8
10
  return NextResponse.json({ firstTime: true, key: getAccessKey() })
9
11
  }
@@ -2,9 +2,11 @@ import { NextResponse } from 'next/server'
2
2
  import fs from 'fs'
3
3
  import path from 'path'
4
4
  import os from 'os'
5
+ export const dynamic = 'force-dynamic'
6
+
5
7
 
6
8
  /** GET /api/claude-skills — discover skills from ~/.claude/skills/ */
7
- export async function GET() {
9
+ export async function GET(_req: Request) {
8
10
  const skillsDir = path.join(os.homedir(), '.claude', 'skills')
9
11
  const skills: { id: string; name: string; description: string }[] = []
10
12
 
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from 'next/server'
2
- import crypto from 'crypto'
2
+ import { genId } from '@/lib/id'
3
3
  import { loadSkills, saveSkills } from '@/lib/server/storage'
4
4
  import { fetchSkillContent } from '@/lib/server/clawhub-client'
5
5
 
@@ -20,7 +20,7 @@ export async function POST(req: Request) {
20
20
  }
21
21
 
22
22
  const skills = loadSkills()
23
- const id = crypto.randomBytes(4).toString('hex')
23
+ const id = genId()
24
24
  skills[id] = {
25
25
  id,
26
26
  name,
@@ -1,11 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { loadConnectors, saveConnectors } from '@/lib/server/storage'
3
+ import { notify } from '@/lib/server/ws-hub'
4
+ import { notFound } from '@/lib/server/collection-helpers'
3
5
 
4
6
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
7
  const { id } = await params
6
8
  const connectors = loadConnectors()
7
9
  const connector = connectors[id]
8
- if (!connector) return NextResponse.json({ error: 'Not found' }, { status: 404 })
10
+ if (!connector) return notFound()
9
11
 
10
12
  // Merge runtime status and QR code
11
13
  try {
@@ -25,7 +27,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
25
27
  const body = await req.json()
26
28
  const connectors = loadConnectors()
27
29
  const connector = connectors[id]
28
- if (!connector) return NextResponse.json({ error: 'Not found' }, { status: 404 })
30
+ if (!connector) return notFound()
29
31
 
30
32
  // Handle start/stop/repair actions — these modify connector state internally,
31
33
  // so re-read from storage after to avoid overwriting with stale data
@@ -46,6 +48,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
46
48
  }
47
49
  // Re-read the connector after manager modified it
48
50
  const fresh = loadConnectors()
51
+ notify('connectors')
49
52
  return NextResponse.json(fresh[id])
50
53
  }
51
54
 
@@ -59,13 +62,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
59
62
 
60
63
  connectors[id] = connector
61
64
  saveConnectors(connectors)
65
+ notify('connectors')
62
66
  return NextResponse.json(connector)
63
67
  }
64
68
 
65
69
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
66
70
  const { id } = await params
67
71
  const connectors = loadConnectors()
68
- if (!connectors[id]) return NextResponse.json({ error: 'Not found' }, { status: 404 })
72
+ if (!connectors[id]) return notFound()
69
73
 
70
74
  // Stop if running
71
75
  try {
@@ -73,7 +77,14 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
73
77
  await stopConnector(id)
74
78
  } catch { /* ignore */ }
75
79
 
80
+ // Clear persisted pairing state when connector is deleted.
81
+ try {
82
+ const { clearConnectorPairingState } = await import('@/lib/server/connectors/pairing')
83
+ clearConnectorPairingState(id)
84
+ } catch { /* ignore */ }
85
+
76
86
  delete connectors[id]
77
87
  saveConnectors(connectors)
88
+ notify('connectors')
78
89
  return NextResponse.json({ ok: true })
79
90
  }