@swarmclawai/swarmclaw 0.6.4 → 0.6.7

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 (143) hide show
  1. package/README.md +62 -30
  2. package/package.json +10 -1
  3. package/src/app/api/agents/[id]/clone/route.ts +40 -0
  4. package/src/app/api/agents/route.ts +39 -14
  5. package/src/app/api/chatrooms/[id]/chat/route.ts +58 -3
  6. package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
  7. package/src/app/api/chatrooms/[id]/route.ts +34 -2
  8. package/src/app/api/chatrooms/route.ts +26 -3
  9. package/src/app/api/connectors/[id]/health/route.ts +64 -0
  10. package/src/app/api/connectors/route.ts +17 -2
  11. package/src/app/api/knowledge/route.ts +6 -1
  12. package/src/app/api/openclaw/doctor/route.ts +17 -0
  13. package/src/app/api/schedules/[id]/run/route.ts +3 -0
  14. package/src/app/api/sessions/[id]/chat/route.ts +5 -1
  15. package/src/app/api/sessions/route.ts +11 -2
  16. package/src/app/api/tasks/[id]/route.ts +18 -13
  17. package/src/app/api/tasks/route.ts +44 -1
  18. package/src/app/api/usage/route.ts +16 -7
  19. package/src/app/api/wallets/[id]/approve/route.ts +62 -0
  20. package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
  21. package/src/app/api/wallets/[id]/route.ts +118 -0
  22. package/src/app/api/wallets/[id]/send/route.ts +118 -0
  23. package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
  24. package/src/app/api/wallets/route.ts +74 -0
  25. package/src/app/globals.css +8 -0
  26. package/src/cli/index.js +20 -0
  27. package/src/cli/index.ts +223 -39
  28. package/src/cli/spec.js +14 -0
  29. package/src/components/agents/agent-avatar.tsx +15 -1
  30. package/src/components/agents/agent-card.tsx +38 -6
  31. package/src/components/agents/agent-chat-list.tsx +79 -3
  32. package/src/components/agents/agent-sheet.tsx +191 -26
  33. package/src/components/auth/setup-wizard.tsx +268 -353
  34. package/src/components/chat/chat-area.tsx +24 -9
  35. package/src/components/chat/chat-header.tsx +48 -19
  36. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  37. package/src/components/chat/delegation-banner.test.ts +27 -0
  38. package/src/components/chat/delegation-banner.tsx +109 -23
  39. package/src/components/chat/message-bubble.tsx +17 -16
  40. package/src/components/chat/message-list.tsx +6 -5
  41. package/src/components/chat/streaming-bubble.tsx +3 -2
  42. package/src/components/chat/thinking-indicator.tsx +3 -2
  43. package/src/components/chat/transfer-agent-picker.tsx +1 -1
  44. package/src/components/chatrooms/agent-hover-card.tsx +1 -1
  45. package/src/components/chatrooms/chatroom-input.tsx +1 -1
  46. package/src/components/chatrooms/chatroom-message.tsx +165 -23
  47. package/src/components/chatrooms/chatroom-sheet.tsx +289 -4
  48. package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
  49. package/src/components/chatrooms/chatroom-view.tsx +62 -17
  50. package/src/components/connectors/connector-health.tsx +120 -0
  51. package/src/components/connectors/connector-list.tsx +1 -1
  52. package/src/components/connectors/connector-sheet.tsx +9 -0
  53. package/src/components/home/home-view.tsx +25 -3
  54. package/src/components/input/chat-input.tsx +8 -1
  55. package/src/components/knowledge/knowledge-list.tsx +1 -1
  56. package/src/components/knowledge/knowledge-sheet.tsx +1 -1
  57. package/src/components/layout/app-layout.tsx +35 -4
  58. package/src/components/memory/memory-agent-list.tsx +1 -1
  59. package/src/components/memory/memory-browser.tsx +1 -0
  60. package/src/components/memory/memory-card.tsx +3 -2
  61. package/src/components/memory/memory-detail.tsx +3 -3
  62. package/src/components/memory/memory-sheet.tsx +2 -2
  63. package/src/components/projects/project-detail.tsx +4 -4
  64. package/src/components/schedules/schedule-list.tsx +55 -9
  65. package/src/components/schedules/schedule-sheet.tsx +134 -23
  66. package/src/components/secrets/secret-sheet.tsx +1 -1
  67. package/src/components/secrets/secrets-list.tsx +1 -1
  68. package/src/components/sessions/session-card.tsx +1 -1
  69. package/src/components/shared/agent-picker-list.tsx +1 -1
  70. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  71. package/src/components/shared/command-palette.tsx +237 -0
  72. package/src/components/shared/connector-platform-icon.tsx +1 -0
  73. package/src/components/shared/settings/section-user-preferences.tsx +4 -4
  74. package/src/components/skills/skill-list.tsx +1 -1
  75. package/src/components/skills/skill-sheet.tsx +1 -1
  76. package/src/components/tasks/task-board.tsx +3 -3
  77. package/src/components/tasks/task-card.tsx +22 -2
  78. package/src/components/tasks/task-sheet.tsx +112 -17
  79. package/src/components/usage/metrics-dashboard.tsx +13 -25
  80. package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
  81. package/src/components/wallets/wallet-panel.tsx +616 -0
  82. package/src/components/wallets/wallet-section.tsx +100 -0
  83. package/src/hooks/use-swipe.ts +49 -0
  84. package/src/lib/providers/anthropic.ts +16 -2
  85. package/src/lib/providers/claude-cli.ts +7 -1
  86. package/src/lib/providers/index.ts +7 -0
  87. package/src/lib/providers/ollama.ts +16 -2
  88. package/src/lib/providers/openai.ts +7 -2
  89. package/src/lib/providers/openclaw.ts +6 -1
  90. package/src/lib/providers/provider-defaults.ts +7 -0
  91. package/src/lib/schedule-templates.ts +115 -0
  92. package/src/lib/server/agent-registry.ts +2 -2
  93. package/src/lib/server/alert-dispatch.ts +64 -0
  94. package/src/lib/server/chat-execution.ts +76 -4
  95. package/src/lib/server/chatroom-health.ts +60 -0
  96. package/src/lib/server/chatroom-helpers.test.ts +94 -0
  97. package/src/lib/server/chatroom-helpers.ts +86 -12
  98. package/src/lib/server/chatroom-routing.ts +65 -0
  99. package/src/lib/server/connectors/discord.ts +3 -0
  100. package/src/lib/server/connectors/email.ts +267 -0
  101. package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
  102. package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
  103. package/src/lib/server/connectors/manager.ts +239 -5
  104. package/src/lib/server/connectors/openclaw.ts +3 -0
  105. package/src/lib/server/connectors/slack.ts +6 -0
  106. package/src/lib/server/connectors/telegram.ts +18 -0
  107. package/src/lib/server/connectors/types.ts +2 -0
  108. package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
  109. package/src/lib/server/connectors/whatsapp-text.ts +26 -0
  110. package/src/lib/server/connectors/whatsapp.ts +17 -5
  111. package/src/lib/server/cost.ts +70 -0
  112. package/src/lib/server/create-notification.ts +2 -0
  113. package/src/lib/server/daemon-state.ts +124 -0
  114. package/src/lib/server/dag-validation.ts +115 -0
  115. package/src/lib/server/memory-db.ts +12 -7
  116. package/src/lib/server/openclaw-doctor.ts +48 -0
  117. package/src/lib/server/orchestrator-lg.ts +12 -2
  118. package/src/lib/server/orchestrator.ts +6 -1
  119. package/src/lib/server/queue-followups.test.ts +224 -0
  120. package/src/lib/server/queue.ts +238 -24
  121. package/src/lib/server/scheduler.ts +3 -0
  122. package/src/lib/server/session-run-manager.ts +22 -1
  123. package/src/lib/server/session-tools/chatroom.ts +11 -2
  124. package/src/lib/server/session-tools/context-mgmt.ts +2 -2
  125. package/src/lib/server/session-tools/index.ts +8 -2
  126. package/src/lib/server/session-tools/memory.ts +23 -4
  127. package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
  128. package/src/lib/server/session-tools/shell.ts +1 -1
  129. package/src/lib/server/session-tools/wallet.ts +124 -0
  130. package/src/lib/server/session-tools/web.ts +2 -2
  131. package/src/lib/server/solana.ts +122 -0
  132. package/src/lib/server/storage.ts +158 -6
  133. package/src/lib/server/stream-agent-chat.ts +126 -63
  134. package/src/lib/server/task-mention.test.ts +41 -0
  135. package/src/lib/server/task-mention.ts +3 -2
  136. package/src/lib/setup-defaults.ts +277 -0
  137. package/src/lib/tool-definitions.ts +1 -0
  138. package/src/lib/validation/schemas.ts +69 -0
  139. package/src/lib/view-routes.ts +1 -0
  140. package/src/stores/use-app-store.ts +15 -3
  141. package/src/stores/use-chatroom-store.ts +52 -2
  142. package/src/types/index.ts +98 -2
  143. package/tsconfig.json +2 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
  <img src="https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/public/branding/swarmclaw-org-avatar.png" alt="SwarmClaw lobster logo" width="120" />
9
9
  </p>
10
10
 
11
- Self-hosted AI agent orchestration dashboard. Manage multiple AI providers, orchestrate agent swarms, schedule tasks, and bridge agents to chat platforms — all from a single mobile-friendly interface.
11
+ The orchestration dashboard for OpenClaw. Manage a swarm of OpenClaws + 14 other AI providers, orchestrate LangGraph workflows, schedule tasks, and bridge agents to 10+ chat platforms — all from one self-hosted UI.
12
12
 
13
13
  Inspired by [OpenClaw](https://github.com/openclaw).
14
14
 
@@ -18,6 +18,44 @@ Inspired by [OpenClaw](https://github.com/openclaw).
18
18
  ![Agent Builder](public/screenshots/agents.png)
19
19
  ![Task Board](public/screenshots/tasks.png)
20
20
 
21
+ ## OpenClaw Integration
22
+
23
+ SwarmClaw was built for OpenClaw users who outgrew a single agent. Connect each SwarmClaw agent to a different OpenClaw gateway (one local, several remote) and manage the whole swarm from one UI.
24
+
25
+ SwarmClaw includes the `openclaw` CLI as a bundled dependency, so there is no separate OpenClaw CLI install step.
26
+
27
+ The OpenClaw Control Plane in SwarmClaw adds:
28
+ - Reload mode switching (`hot`, `hybrid`, `full`)
29
+ - Config issue detection and guided repair
30
+ - Remote history sync
31
+ - Live execution approval handling
32
+
33
+ The Agent Inspector Panel lets you edit OpenClaw files (`SOUL.md`, `IDENTITY.md`, `USER.md`), tune personality/system behavior, and manage OpenClaw-compatible skills. SwarmClaw also supports importing OpenClaw `SKILL.md` files from URL.
34
+
35
+ To connect an agent to an OpenClaw gateway:
36
+
37
+ 1. Create or edit an agent
38
+ 2. Toggle **OpenClaw Gateway** ON
39
+ 3. Enter the gateway URL (e.g. `http://192.168.1.50:18789` or `https://my-vps:18789`)
40
+ 4. Add a gateway token if authentication is enabled on the remote gateway
41
+ 5. Click **Connect** — approve the device in your gateway's dashboard if prompted, then **Retry Connection**
42
+
43
+ Each agent can point to a **different** OpenClaw gateway — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
44
+
45
+ URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
46
+
47
+ ## SwarmClaw ClawHub Skill
48
+
49
+ Use the `swarmclaw` ClawHub skill when you want an OpenClaw agent to operate your SwarmClaw control plane directly from chat: list agents, dispatch tasks, check sessions, run diagnostics, and coordinate multi-agent work.
50
+
51
+ Install it from ClawHub:
52
+
53
+ ```bash
54
+ clawhub install swarmclaw
55
+ ```
56
+
57
+ Skill source and runbook: [`swarmclaw/SKILL.md`](swarmclaw/SKILL.md).
58
+
21
59
  - Always use the access key authentication (generated on first run)
22
60
  - Never expose port 3456 without a reverse proxy + TLS
23
61
  - Review agent system prompts before giving them shell or browser tools
@@ -47,7 +85,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
47
85
  ```
48
86
 
49
87
  The installer resolves the latest stable release tag and installs that version by default.
50
- To pin a version: `SWARMCLAW_VERSION=v0.6.2 curl ... | bash`
88
+ To pin a version: `SWARMCLAW_VERSION=v0.6.6 curl ... | bash`
51
89
 
52
90
  Or run locally from the repo (friendly for non-technical users):
53
91
 
@@ -80,11 +118,16 @@ You can complete first-time setup from terminal:
80
118
  # Start the app (if not already running)
81
119
  npm run dev
82
120
 
83
- # In another terminal, run setup with your provider
121
+ # In another terminal, run interactive setup (walks you through provider selection)
122
+ node ./bin/swarmclaw.js setup init
123
+
124
+ # Or pass flags directly for non-interactive setup
84
125
  node ./bin/swarmclaw.js setup init --provider openai --api-key "$OPENAI_API_KEY"
85
126
  ```
86
127
 
87
128
  Notes:
129
+ - When run with no flags in a TTY, `setup init` enters interactive mode — pick providers, enter keys, name agents, and add multiple providers in one session.
130
+ - Use `--no-interactive` to force flag-only mode.
88
131
  - On a fresh instance, `setup init` can auto-discover and claim the first-run access key from `/api/auth`.
89
132
  - For existing installs, pass `--key <ACCESS_KEY>` (or set `SWARMCLAW_ACCESS_KEY`).
90
133
  - `setup init` performs provider validation, stores credentials, creates a starter agent, and marks setup complete.
@@ -94,22 +137,22 @@ Notes:
94
137
 
95
138
  After login, SwarmClaw opens a guided wizard designed for non-technical users:
96
139
 
97
- 1. Choose a provider: **OpenAI**, **Anthropic**, or **Ollama**
98
- 2. Add only required fields (API key and/or endpoint)
99
- 3. Click **Check Connection** for live validation before continuing
100
- 4. (Optional) click **Run System Check** for setup diagnostics
101
- 5. Create a starter assistant (advanced settings are optional)
140
+ 1. **Choose a provider** — Pick from all 11 supported providers (OpenAI, Anthropic, Google Gemini, DeepSeek, Groq, Together AI, Mistral, xAI, Fireworks, OpenClaw, Ollama)
141
+ 2. **Connect provider** — Enter only required fields (API key and/or endpoint), then click **Check Connection** for live validation
142
+ 3. **Create your agent** — Each provider gets a unique default name (e.g. Atlas for OpenAI, Claude for Anthropic, Bolt for Groq). Choose **Create & Add Another** to set up multiple providers, or **Create & Finish** to continue
143
+ 4. **Summary** Review all created agents and discover connectors (Discord, Slack, Telegram, WhatsApp)
102
144
 
103
145
  Notes:
146
+ - You can add multiple providers in a single wizard session — configured providers are dimmed and shown as chips.
104
147
  - Ollama checks can auto-suggest a model from the connected endpoint.
105
- - OpenClaw is configured per-agent via the **OpenClaw Gateway** toggle (not in the setup wizard).
106
- - You can skip setup and configure everything later in the sidebar.
148
+ - You can skip setup at any step and configure everything later in the sidebar.
107
149
 
108
150
  ## Features
109
151
 
110
152
  - **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
111
153
  - **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)
112
154
  - **OpenClaw Control Plane** — Built-in gateway connection controls, reload mode switching (hot/hybrid/full), config issue detection/repair, remote history sync, and live execution approval handling
155
+ - **Gateway Watchdog** — Proactive gateway health monitoring with auto-repair via `openclaw doctor`, outbound ops alerts to Discord/Slack/custom webhooks, workspace backup/rollback/history tools for agents, and connector liveness detection
113
156
  - **Agent Builder** — Create agents with custom personalities (soul), system prompts, tools, and skills. AI-powered generation from a description
114
157
  - **Agent Inspector Panel** — Per-agent side panel for OpenClaw file editing (`SOUL.md`, `IDENTITY.md`, `USER.md`, etc.), guided personality editing, skill install/enable/remove, permission presets, sandbox env allowlist, and cron automations
115
158
  - **Agent Fleet Management** — Avatar seeds with generated avatars, running/approval fleet filters, soft-delete agent trash with restore/permanent delete, and approval counters in agent cards
@@ -118,20 +161,20 @@ Notes:
118
161
  - **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
119
162
  - **Agentic Execution Policy** — Tool-first autonomous action loop with progress updates, evidence-driven answers, and better use of platform tools for long-lived work
120
163
  - **Runtime Date/Time Grounding** — Session, orchestrator, chatroom, and connector prompts include authoritative current timestamp context to reduce stale-date behavior
121
- - **Task Board** — Queue and track agent tasks with status, comments, results, and archiving. Strict capability policy pauses tasks for human approval before tool execution
164
+ - **Task Board** — Queue and track agent tasks with status, comments, structured result artifacts (`outputFiles`, uploads), completion reports, and archiving. Strict capability policy pauses tasks for human approval before tool execution
122
165
  - **Task Metrics API** — Built-in analytics endpoint for WIP, cycle times, throughput velocity, completion/failure by agent, and priority distribution
123
166
  - **Background Daemon** — Auto-processes queued tasks and scheduled jobs with a 30s heartbeat plus recurring health monitoring
124
167
  - **Scheduling** — Cron-based agent scheduling with human-friendly presets
125
168
  - **Loop Runtime Controls** — Switch between bounded and ongoing loops with configurable step caps, runtime guards, heartbeat cadence, and timeout budgets
126
169
  - **Session Run Queue** — Per-session queued runs with followup/steer/collect modes, collect coalescing for bursty inputs, and run-state APIs
127
170
  - **Chat Iteration Workflow** — Edit-and-resend user turns, fork a new session from any message, bookmark key messages, use contextual follow-up suggestion chips, and auto-continue after tool access grants
128
- - **Agent Chatrooms** — Multi-agent room conversations with `@mention` routing, chained agent replies, reactions, and file/image-aware chat context
171
+ - **Agent Chatrooms** — Multi-agent room conversations with `@mention` routing, chained agent replies, reactions, file/image-aware context, health-aware member filtering, and persistent context compaction for long-lived rooms
129
172
  - **Live Chat Telemetry** — Thinking/tool/responding stream phases, live main-loop status badges, connector activity presence, tone indicator, and optional sound notifications
130
173
  - **Global Search Palette** — `Cmd/Ctrl+K` search across agents, tasks, sessions, schedules, webhooks, and skills from anywhere in the app
131
174
  - **Notification Center** — Real-time in-app notifications for task/schedule/daemon events with unread tracking, mark-all/clear-read controls, and optional action links
132
175
  - **Preview-Rich Chat UI** — Side preview panel for tool outputs (image/browser/html/code), inline code/PDF previews for attachments, and image lightbox support
133
176
  - **Voice Settings** — Per-instance ElevenLabs API key + voice ID for TTS replies, plus configurable speech recognition language for chat input
134
- - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, WhatsApp, BlueBubbles (iMessage), Signal, Microsoft Teams, Google Chat, Matrix, and OpenClaw with media-aware inbound handling
177
+ - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, WhatsApp, BlueBubbles (iMessage), Signal, Microsoft Teams, Google Chat, Matrix, and OpenClaw with media-aware inbound handling, inbound voice-note transcription (ElevenLabs/OpenAI fallback), and WhatsApp-friendly plain-text formatting
135
178
  - **Skills System** — Discover local skills, import skills from URL, and load OpenClaw `SKILL.md` files (frontmatter-compatible)
136
179
  - **Execution Logging** — Structured audit trail for triggers, tool calls, file ops, commits, and errors in a dedicated `logs.db`
137
180
  - **Context Management** — Auto-compaction of conversation history when approaching context limits, with manual `context_status` and `context_summarize` tools for agents
@@ -157,6 +200,7 @@ CREDENTIAL_SECRET=<auto-generated> # AES-256 encryption key for stored credentia
157
200
  ```
158
201
 
159
202
  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.
203
+ Agent wallet private keys are stored encrypted (AES-256 via `CREDENTIAL_SECRET`) in `data/swarmclaw.db` and are never returned by wallet API responses; keep `data/` out of version control.
160
204
 
161
205
  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`.
162
206
 
@@ -221,22 +265,6 @@ src/
221
265
  | OpenClaw | Per-Agent Gateway | Toggle in agent editor connects to any OpenClaw gateway via the bundled CLI. |
222
266
  | Custom | API | Any OpenAI-compatible endpoint. Add via Providers sidebar. |
223
267
 
224
- ### OpenClaw
225
-
226
- [OpenClaw](https://github.com/openclaw/openclaw) is an open-source autonomous AI agent that runs on your own devices. SwarmClaw includes the `openclaw` CLI as a bundled dependency — no separate install needed.
227
-
228
- To connect an agent to an OpenClaw gateway:
229
-
230
- 1. Create or edit an agent
231
- 2. Toggle **OpenClaw Gateway** ON
232
- 3. Enter the gateway URL (e.g. `http://192.168.1.50:18789` or `https://my-vps:18789`)
233
- 4. Add a gateway token if authentication is enabled on the remote gateway
234
- 5. Click **Connect** — approve the device in your gateway's dashboard if prompted, then **Retry Connection**
235
-
236
- Each agent can point to a **different** OpenClaw gateway — one local, several remote. This is how you manage a **swarm of OpenClaws** from a single dashboard.
237
-
238
- URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
239
-
240
268
  ## Chat Connectors
241
269
 
242
270
  Bridge any agent to a chat platform:
@@ -288,6 +316,7 @@ Agents can use the following tools when enabled:
288
316
  | HTTP Request | Make direct API calls with method, headers, body, redirect control, and timeout |
289
317
  | Git | Run structured git subcommands (`status`, `diff`, `log`, `add`, `commit`, `push`, etc.) with repo safety checks |
290
318
  | Memory | Store and retrieve long-term memories with FTS5 + vector search, file references, image attachments, and linked memory graph traversal |
319
+ | Wallet | Manage an agent-linked Solana wallet (`wallet_tool`) to check balance/address, send SOL (limits + approval), and review transaction history |
291
320
  | Sandbox | Run JS/TS (Deno) or Python code in an isolated sandbox. Created files are returned as downloadable artifacts |
292
321
  | MCP Servers | Connect to external Model Context Protocol servers. Tools from MCP servers are injected as first-class agent tools |
293
322
 
@@ -624,7 +653,10 @@ swarmclaw tasks create --title "Fix flaky CI test" --description "Stabilize retr
624
653
  # run setup diagnostics
625
654
  swarmclaw setup doctor
626
655
 
627
- # complete setup from CLI (example: OpenAI)
656
+ # interactive setup (walks through provider selection, supports multiple providers)
657
+ swarmclaw setup init
658
+
659
+ # or non-interactive setup with flags
628
660
  swarmclaw setup init --provider openai --api-key "$OPENAI_API_KEY"
629
661
 
630
662
  # run memory maintenance analysis
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "0.6.4",
3
+ "version": "0.6.7",
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": {
@@ -45,6 +45,7 @@
45
45
  "build:ci": "NEXT_DISABLE_ESLINT=1 next build",
46
46
  "start": "next start",
47
47
  "start:standalone": "node .next/standalone/server.js",
48
+ "benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
48
49
  "lint": "eslint",
49
50
  "lint:fix": "eslint --fix",
50
51
  "lint:baseline": "node ./scripts/lint-baseline.mjs check",
@@ -63,8 +64,10 @@
63
64
  "@multiavatar/multiavatar": "^1.0.7",
64
65
  "@playwright/mcp": "^0.0.68",
65
66
  "@slack/bolt": "^4.6.0",
67
+ "@solana/web3.js": "^1.98.4",
66
68
  "@whiskeysockets/baileys": "^7.0.0-rc.9",
67
69
  "better-sqlite3": "^12.6.2",
70
+ "bs58": "^5.0.0",
68
71
  "cheerio": "^1.2.0",
69
72
  "class-variance-authority": "^0.7.1",
70
73
  "clsx": "^2.1.1",
@@ -75,9 +78,12 @@
75
78
  "exceljs": "^4.4.0",
76
79
  "grammy": "^1.40.0",
77
80
  "highlight.js": "^11.11.1",
81
+ "imapflow": "^1.2.11",
78
82
  "lucide-react": "^0.574.0",
83
+ "mailparser": "^3.9.3",
79
84
  "next": "16.1.6",
80
85
  "next-themes": "^0.4.6",
86
+ "nodemailer": "^8.0.1",
81
87
  "openclaw": "^2026.2.26",
82
88
  "pdf-parse": "^2.4.5",
83
89
  "qrcode": "^1.5.4",
@@ -90,6 +96,7 @@
90
96
  "recharts": "^3.7.0",
91
97
  "rehype-highlight": "^7.0.2",
92
98
  "remark-gfm": "^4.0.1",
99
+ "remove-markdown": "^0.6.3",
93
100
  "sonner": "^2.0.7",
94
101
  "tailwind-merge": "^3.4.1",
95
102
  "ws": "^8.19.0",
@@ -99,7 +106,9 @@
99
106
  "devDependencies": {
100
107
  "@tailwindcss/postcss": "^4",
101
108
  "@types/better-sqlite3": "^7.6.13",
109
+ "@types/mailparser": "^3.4.6",
102
110
  "@types/node": "^20",
111
+ "@types/nodemailer": "^7.0.11",
103
112
  "@types/qrcode": "^1.5.6",
104
113
  "@types/react": "^19",
105
114
  "@types/react-dom": "^19",
@@ -0,0 +1,40 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadAgents, saveAgents, logActivity } from '@/lib/server/storage'
3
+ import { notify } from '@/lib/server/ws-hub'
4
+
5
+ export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ const { id } = await params
7
+ const agents = loadAgents({ includeTrashed: true })
8
+ const source = agents[id]
9
+ if (!source) {
10
+ return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
11
+ }
12
+
13
+ const newId = crypto.randomUUID()
14
+ const now = Date.now()
15
+
16
+ // Deep-copy the source agent, then override clone-specific fields
17
+ const cloned = JSON.parse(JSON.stringify(source)) as Record<string, unknown>
18
+ cloned.id = newId
19
+ cloned.name = `${source.name} (Copy)`
20
+ cloned.createdAt = now
21
+ cloned.updatedAt = now
22
+ cloned.totalCost = 0
23
+ cloned.lastUsedAt = undefined
24
+ cloned.threadSessionId = null
25
+ cloned.pinned = false
26
+ cloned.trashedAt = undefined
27
+
28
+ agents[newId] = cloned
29
+ saveAgents(agents)
30
+ logActivity({
31
+ entityType: 'agent',
32
+ entityId: newId,
33
+ action: 'created',
34
+ actor: 'user',
35
+ summary: `Agent cloned from "${source.name}": "${cloned.name}"`,
36
+ })
37
+ notify('agents')
38
+
39
+ return NextResponse.json(cloned)
40
+ }
@@ -3,32 +3,57 @@ import { genId } from '@/lib/id'
3
3
  import { loadAgents, saveAgents, logActivity } from '@/lib/server/storage'
4
4
  import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
5
5
  import { notify } from '@/lib/server/ws-hub'
6
+ import { getAgentMonthlySpend } from '@/lib/server/cost'
7
+ import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
8
+ import { z } from 'zod'
6
9
  export const dynamic = 'force-dynamic'
7
10
 
8
11
 
9
- export async function GET(_req: Request) {
10
- return NextResponse.json(loadAgents())
12
+ export async function GET(req: Request) {
13
+ const agents = loadAgents()
14
+ // Enrich agents that have a monthly budget with current spend
15
+ for (const agent of Object.values(agents)) {
16
+ if (typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0) {
17
+ agent.monthlySpend = getAgentMonthlySpend(agent.id)
18
+ }
19
+ }
20
+
21
+ const { searchParams } = new URL(req.url)
22
+ const limitParam = searchParams.get('limit')
23
+ if (!limitParam) return NextResponse.json(agents)
24
+
25
+ const limit = Math.max(1, Number(limitParam) || 50)
26
+ const offset = Math.max(0, Number(searchParams.get('offset')) || 0)
27
+ const all = Object.values(agents).sort((a, b) => b.updatedAt - a.updatedAt)
28
+ const items = all.slice(offset, offset + limit)
29
+ return NextResponse.json({ items, total: all.length, hasMore: offset + limit < all.length })
11
30
  }
12
31
 
13
32
  export async function POST(req: Request) {
14
- const body = await req.json()
33
+ const raw = await req.json()
34
+ const parsed = AgentCreateSchema.safeParse(raw)
35
+ if (!parsed.success) {
36
+ return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
37
+ }
38
+ const body = parsed.data
15
39
  const id = genId()
16
40
  const now = Date.now()
17
41
  const agents = loadAgents()
18
42
  agents[id] = {
19
43
  id,
20
- name: body.name || 'Unnamed Agent',
21
- description: body.description || '',
22
- systemPrompt: body.systemPrompt || '',
23
- provider: body.provider || 'claude-cli',
24
- model: body.model || '',
25
- credentialId: body.credentialId || null,
26
- apiEndpoint: normalizeProviderEndpoint(body.provider || 'claude-cli', body.apiEndpoint || null),
27
- isOrchestrator: body.isOrchestrator || false,
28
- subAgentIds: body.subAgentIds || [],
29
- tools: body.tools || [],
30
- capabilities: body.capabilities || [],
44
+ name: body.name,
45
+ description: body.description,
46
+ systemPrompt: body.systemPrompt,
47
+ provider: body.provider,
48
+ model: body.model,
49
+ credentialId: body.credentialId,
50
+ apiEndpoint: normalizeProviderEndpoint(body.provider, body.apiEndpoint || null),
51
+ isOrchestrator: body.isOrchestrator,
52
+ subAgentIds: body.subAgentIds,
53
+ tools: body.tools,
54
+ capabilities: body.capabilities,
31
55
  thinkingLevel: body.thinkingLevel || undefined,
56
+ soul: body.soul || undefined,
32
57
  createdAt: now,
33
58
  updatedAt: now,
34
59
  }
@@ -8,11 +8,16 @@ import { getProvider } from '@/lib/providers'
8
8
  import {
9
9
  resolveApiKey,
10
10
  parseMentions,
11
+ compactChatroomMessages,
11
12
  buildChatroomSystemPrompt,
12
13
  buildSyntheticSession,
13
14
  buildAgentSystemPromptForChatroom,
14
15
  buildHistoryForAgent,
16
+ isMuted,
15
17
  } from '@/lib/server/chatroom-helpers'
18
+ import { filterHealthyChatroomAgents } from '@/lib/server/chatroom-health'
19
+ import { evaluateRoutingRules } from '@/lib/server/chatroom-routing'
20
+ import { markProviderFailure, markProviderSuccess } from '@/lib/server/provider-health'
16
21
  import type { Chatroom, ChatroomMessage, Agent } from '@/types'
17
22
 
18
23
  export const dynamic = 'force-dynamic'
@@ -45,10 +50,17 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
45
50
  // Persist incoming message
46
51
  const senderName = senderId === 'user' ? 'You' : (agents[senderId]?.name || senderId)
47
52
  let mentions = parseMentions(text, agents, chatroom.agentIds)
48
- // Auto-address: if enabled and no explicit mentions, address all agents
53
+ // Routing rules: if no explicit mentions, evaluate keyword/capability rules
54
+ if (mentions.length === 0 && chatroom.routingRules?.length) {
55
+ const agentList = chatroom.agentIds.map((aid) => agents[aid]).filter(Boolean)
56
+ mentions = evaluateRoutingRules(text, chatroom.routingRules, agentList)
57
+ }
58
+ // Auto-address: if enabled and still no mentions, address all agents
49
59
  if (chatroom.autoAddress && mentions.length === 0) {
50
60
  mentions = [...chatroom.agentIds]
51
61
  }
62
+ const mentionHealth = filterHealthyChatroomAgents(mentions, agents)
63
+ mentions = mentionHealth.healthyAgentIds
52
64
  const userMessage: ChatroomMessage = {
53
65
  id: genId(),
54
66
  senderId,
@@ -63,6 +75,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
63
75
  ...(replyToId ? { replyToId } : {}),
64
76
  }
65
77
  chatroom.messages.push(userMessage)
78
+ compactChatroomMessages(chatroom)
66
79
  chatroom.updatedAt = Date.now()
67
80
  chatrooms[id] = chatroom
68
81
  saveChatrooms(chatrooms)
@@ -94,6 +107,22 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
94
107
  }
95
108
 
96
109
  const processAgents = async () => {
110
+ if (mentionHealth.skipped.length > 0) {
111
+ const detail = mentionHealth.skipped
112
+ .map((row) => `${agents[row.agentId]?.name || row.agentId}: ${row.reason}`)
113
+ .join(', ')
114
+ writeEvent({ t: 'err', text: `Skipped agents: ${detail}` })
115
+ }
116
+ if (mentions.length === 0) {
117
+ writeEvent({ t: 'err', text: 'No healthy agents available in this chatroom. Check provider credentials/endpoints and retry.' })
118
+ writeEvent({ t: 'done' })
119
+ if (!closed) {
120
+ try { controller.close() } catch { /* already closed */ }
121
+ closed = true
122
+ }
123
+ return
124
+ }
125
+
97
126
  // Build agent queue: start with mentioned agents, then chain
98
127
  const initialQueue: Array<{ agentId: string; depth: number; contextMessage?: string }> = mentions.map((aid) => ({ agentId: aid, depth: 0 }))
99
128
  const processed = new Set<string>()
@@ -107,6 +136,15 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
107
136
  const agent = agents[item.agentId]
108
137
  if (!agent) return []
109
138
 
139
+ // Skip muted agents
140
+ const freshForMuteCheck = loadChatrooms()[id] as Chatroom | undefined
141
+ if (freshForMuteCheck && isMuted(freshForMuteCheck, item.agentId)) {
142
+ writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
143
+ writeEvent({ t: 'err', text: `${agent.name} is muted`, agentId: agent.id, agentName: agent.name })
144
+ writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
145
+ return []
146
+ }
147
+
110
148
  // Pre-flight: check if the agent's provider is usable before attempting to stream
111
149
  const providerInfo = getProvider(agent.provider)
112
150
  const apiKey = resolveApiKey(agent.credentialId)
@@ -128,6 +166,11 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
128
166
  try {
129
167
  const freshChatrooms = loadChatrooms()
130
168
  const freshChatroom = freshChatrooms[id] as Chatroom
169
+ if (compactChatroomMessages(freshChatroom)) {
170
+ freshChatrooms[id] = freshChatroom
171
+ saveChatrooms(freshChatrooms)
172
+ notify(`chatroom:${id}`)
173
+ }
131
174
 
132
175
  const syntheticSession = buildSyntheticSession(agent, id)
133
176
  const agentSystemPrompt = buildAgentSystemPromptForChatroom(agent)
@@ -170,16 +213,25 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
170
213
  history,
171
214
  })
172
215
 
173
- const responseText = result.fullText || fullText
216
+ const responseText = result.finalResponse || result.fullText || fullText
174
217
 
175
218
  // Don't persist empty or error-only messages — they pollute chat history
176
219
  if (!responseText.trim() && agentError) {
220
+ markProviderFailure(agent.provider, agentError)
177
221
  writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
178
222
  return []
179
223
  }
180
224
 
181
225
  if (responseText.trim()) {
182
- const newMentions = parseMentions(responseText, agents, freshChatroom.agentIds)
226
+ const parsedMentions = parseMentions(responseText, agents, freshChatroom.agentIds)
227
+ const chainedHealth = filterHealthyChatroomAgents(parsedMentions, agents)
228
+ const newMentions = chainedHealth.healthyAgentIds
229
+ if (chainedHealth.skipped.length > 0) {
230
+ const detail = chainedHealth.skipped
231
+ .map((row) => `${agents[row.agentId]?.name || row.agentId}: ${row.reason}`)
232
+ .join(', ')
233
+ writeEvent({ t: 'err', text: `Mentioned agents skipped: ${detail}`, agentId: agent.id, agentName: agent.name })
234
+ }
183
235
  const agentMessage: ChatroomMessage = {
184
236
  id: genId(),
185
237
  senderId: agent.id,
@@ -198,16 +250,19 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
198
250
  saveChatrooms(latestChatrooms)
199
251
  notify(`chatroom:${id}`)
200
252
 
253
+ markProviderSuccess(agent.provider)
201
254
  writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
202
255
 
203
256
  // Return chained agent IDs — enriched context is built below when queuing
204
257
  return newMentions.filter((mid) => !processed.has(mid) && freshChatroom.agentIds.includes(mid))
205
258
  }
206
259
 
260
+ markProviderSuccess(agent.provider)
207
261
  writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
208
262
  return []
209
263
  } catch (err: unknown) {
210
264
  const msg = err instanceof Error ? err.message : String(err)
265
+ markProviderFailure(agent.provider, msg)
211
266
  writeEvent({ t: 'err', text: `Agent ${agent.name} error: ${msg}`, agentId: agent.id })
212
267
  writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
213
268
  return []
@@ -0,0 +1,150 @@
1
+ import { NextResponse } from 'next/server'
2
+ import crypto from 'crypto'
3
+ import { loadChatrooms, saveChatrooms, appendModerationLog } from '@/lib/server/storage'
4
+ import { notify } from '@/lib/server/ws-hub'
5
+ import { notFound } from '@/lib/server/collection-helpers'
6
+ import { getMembers } from '@/lib/server/chatroom-helpers'
7
+ import type { Chatroom, ChatroomMember } from '@/types'
8
+
9
+ export const dynamic = 'force-dynamic'
10
+
11
+ interface ModerationBody {
12
+ action: 'delete-message' | 'mute' | 'unmute' | 'set-role'
13
+ targetAgentId: string
14
+ messageId?: string
15
+ role?: 'admin' | 'moderator' | 'member'
16
+ muteDurationMinutes?: number
17
+ }
18
+
19
+ function isValidAction(action: unknown): action is ModerationBody['action'] {
20
+ return typeof action === 'string' && ['delete-message', 'mute', 'unmute', 'set-role'].includes(action)
21
+ }
22
+
23
+ function isValidRole(role: unknown): role is ChatroomMember['role'] {
24
+ return typeof role === 'string' && ['admin', 'moderator', 'member'].includes(role)
25
+ }
26
+
27
+ export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
28
+ const { id } = await params
29
+ const body = await req.json() as Record<string, unknown>
30
+
31
+ const chatrooms = loadChatrooms()
32
+ const chatroom = chatrooms[id] as Chatroom | undefined
33
+ if (!chatroom) return notFound()
34
+
35
+ const action = body.action
36
+ const targetAgentId = typeof body.targetAgentId === 'string' ? body.targetAgentId : ''
37
+
38
+ if (!isValidAction(action)) {
39
+ return NextResponse.json({ error: 'Invalid action. Must be: delete-message, mute, unmute, or set-role' }, { status: 400 })
40
+ }
41
+ if (!targetAgentId) {
42
+ return NextResponse.json({ error: 'targetAgentId is required' }, { status: 400 })
43
+ }
44
+ if (!chatroom.agentIds.includes(targetAgentId)) {
45
+ return NextResponse.json({ error: 'Agent is not a member of this chatroom' }, { status: 400 })
46
+ }
47
+
48
+ // Ensure members array exists (backward compat)
49
+ if (!chatroom.members) {
50
+ chatroom.members = getMembers(chatroom)
51
+ }
52
+
53
+ const logId = crypto.randomBytes(8).toString('hex')
54
+
55
+ switch (action) {
56
+ case 'delete-message': {
57
+ const messageId = typeof body.messageId === 'string' ? body.messageId : ''
58
+ if (!messageId) {
59
+ return NextResponse.json({ error: 'messageId is required for delete-message' }, { status: 400 })
60
+ }
61
+ const msgIndex = chatroom.messages.findIndex((m) => m.id === messageId)
62
+ if (msgIndex === -1) {
63
+ return NextResponse.json({ error: 'Message not found' }, { status: 404 })
64
+ }
65
+ const deleted = chatroom.messages.splice(msgIndex, 1)[0]
66
+ // Also remove from pinned if it was pinned
67
+ if (chatroom.pinnedMessageIds) {
68
+ chatroom.pinnedMessageIds = chatroom.pinnedMessageIds.filter((pid) => pid !== messageId)
69
+ }
70
+ appendModerationLog(logId, {
71
+ id: logId,
72
+ chatroomId: id,
73
+ action: 'delete-message',
74
+ targetAgentId,
75
+ messageId,
76
+ messagePreview: deleted.text.slice(0, 100),
77
+ timestamp: Date.now(),
78
+ })
79
+ break
80
+ }
81
+
82
+ case 'mute': {
83
+ const minutes = typeof body.muteDurationMinutes === 'number' && body.muteDurationMinutes > 0
84
+ ? body.muteDurationMinutes
85
+ : 30
86
+ const mutedUntil = new Date(Date.now() + minutes * 60 * 1000).toISOString()
87
+ const memberIdx = chatroom.members.findIndex((m) => m.agentId === targetAgentId)
88
+ if (memberIdx >= 0) {
89
+ chatroom.members[memberIdx].mutedUntil = mutedUntil
90
+ } else {
91
+ chatroom.members.push({ agentId: targetAgentId, role: 'member', mutedUntil })
92
+ }
93
+ appendModerationLog(logId, {
94
+ id: logId,
95
+ chatroomId: id,
96
+ action: 'mute',
97
+ targetAgentId,
98
+ muteDurationMinutes: minutes,
99
+ mutedUntil,
100
+ timestamp: Date.now(),
101
+ })
102
+ break
103
+ }
104
+
105
+ case 'unmute': {
106
+ const memberIdx = chatroom.members.findIndex((m) => m.agentId === targetAgentId)
107
+ if (memberIdx >= 0) {
108
+ delete chatroom.members[memberIdx].mutedUntil
109
+ }
110
+ appendModerationLog(logId, {
111
+ id: logId,
112
+ chatroomId: id,
113
+ action: 'unmute',
114
+ targetAgentId,
115
+ timestamp: Date.now(),
116
+ })
117
+ break
118
+ }
119
+
120
+ case 'set-role': {
121
+ const role = body.role
122
+ if (!isValidRole(role)) {
123
+ return NextResponse.json({ error: 'role must be: admin, moderator, or member' }, { status: 400 })
124
+ }
125
+ const memberIdx = chatroom.members.findIndex((m) => m.agentId === targetAgentId)
126
+ if (memberIdx >= 0) {
127
+ chatroom.members[memberIdx].role = role
128
+ } else {
129
+ chatroom.members.push({ agentId: targetAgentId, role })
130
+ }
131
+ appendModerationLog(logId, {
132
+ id: logId,
133
+ chatroomId: id,
134
+ action: 'set-role',
135
+ targetAgentId,
136
+ role,
137
+ timestamp: Date.now(),
138
+ })
139
+ break
140
+ }
141
+ }
142
+
143
+ chatroom.updatedAt = Date.now()
144
+ chatrooms[id] = chatroom
145
+ saveChatrooms(chatrooms)
146
+ notify('chatrooms')
147
+ notify(`chatroom:${id}`)
148
+
149
+ return NextResponse.json(chatroom)
150
+ }