@swarmclawai/swarmclaw 0.6.6 → 0.6.8

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 (126) hide show
  1. package/README.md +81 -33
  2. package/package.json +6 -1
  3. package/src/app/api/agents/[id]/clone/route.ts +40 -0
  4. package/src/app/api/agents/route.ts +40 -14
  5. package/src/app/api/chatrooms/[id]/chat/route.ts +21 -1
  6. package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
  7. package/src/app/api/chatrooms/[id]/route.ts +19 -1
  8. package/src/app/api/chatrooms/route.ts +12 -2
  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/eval/run/route.ts +37 -0
  12. package/src/app/api/eval/scenarios/route.ts +24 -0
  13. package/src/app/api/eval/suite/route.ts +29 -0
  14. package/src/app/api/knowledge/route.ts +6 -1
  15. package/src/app/api/memory/graph/route.ts +46 -0
  16. package/src/app/api/openclaw/doctor/route.ts +17 -0
  17. package/src/app/api/sessions/[id]/chat/route.ts +5 -1
  18. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  19. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  20. package/src/app/api/sessions/route.ts +11 -2
  21. package/src/app/api/souls/[id]/route.ts +65 -0
  22. package/src/app/api/souls/route.ts +70 -0
  23. package/src/app/api/tasks/[id]/route.ts +23 -13
  24. package/src/app/api/tasks/route.ts +22 -1
  25. package/src/app/api/usage/route.ts +25 -9
  26. package/src/cli/index.js +29 -0
  27. package/src/cli/index.ts +223 -39
  28. package/src/components/agents/agent-card.tsx +37 -6
  29. package/src/components/agents/agent-chat-list.tsx +78 -2
  30. package/src/components/agents/agent-sheet.tsx +104 -4
  31. package/src/components/agents/soul-library-picker.tsx +84 -13
  32. package/src/components/auth/setup-wizard.tsx +268 -353
  33. package/src/components/chat/activity-moment.tsx +2 -0
  34. package/src/components/chat/chat-area.tsx +22 -7
  35. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  36. package/src/components/chat/message-bubble.tsx +14 -14
  37. package/src/components/chat/message-list.tsx +20 -4
  38. package/src/components/chat/session-debug-panel.tsx +106 -84
  39. package/src/components/chat/task-approval-card.tsx +78 -0
  40. package/src/components/chat/tool-call-bubble.tsx +3 -0
  41. package/src/components/chatrooms/chatroom-message.tsx +164 -22
  42. package/src/components/chatrooms/chatroom-sheet.tsx +288 -3
  43. package/src/components/chatrooms/chatroom-view.tsx +62 -17
  44. package/src/components/connectors/connector-health.tsx +120 -0
  45. package/src/components/connectors/connector-sheet.tsx +17 -1
  46. package/src/components/home/home-view.tsx +55 -10
  47. package/src/components/input/chat-input.tsx +8 -1
  48. package/src/components/layout/app-layout.tsx +35 -3
  49. package/src/components/memory/memory-browser.tsx +73 -45
  50. package/src/components/memory/memory-graph-view.tsx +203 -0
  51. package/src/components/plugins/plugin-list.tsx +1 -1
  52. package/src/components/schedules/schedule-list.tsx +55 -9
  53. package/src/components/schedules/schedule-sheet.tsx +143 -25
  54. package/src/components/shared/command-palette.tsx +237 -0
  55. package/src/components/shared/connector-platform-icon.tsx +1 -0
  56. package/src/components/shared/hint-tip.tsx +31 -0
  57. package/src/components/shared/settings/section-runtime-loop.tsx +5 -4
  58. package/src/components/tasks/approvals-panel.tsx +120 -0
  59. package/src/components/tasks/task-card.tsx +22 -2
  60. package/src/components/tasks/task-sheet.tsx +91 -16
  61. package/src/components/usage/metrics-dashboard.tsx +38 -28
  62. package/src/hooks/use-swipe.ts +49 -0
  63. package/src/lib/providers/anthropic.ts +16 -2
  64. package/src/lib/providers/claude-cli.ts +7 -1
  65. package/src/lib/providers/index.ts +7 -0
  66. package/src/lib/providers/ollama.ts +16 -2
  67. package/src/lib/providers/openai.ts +7 -2
  68. package/src/lib/providers/openclaw.ts +6 -1
  69. package/src/lib/providers/provider-defaults.ts +7 -0
  70. package/src/lib/schedule-templates.ts +115 -0
  71. package/src/lib/server/alert-dispatch.ts +64 -0
  72. package/src/lib/server/chat-execution.ts +137 -13
  73. package/src/lib/server/chatroom-helpers.ts +85 -6
  74. package/src/lib/server/chatroom-orchestration.ts +74 -0
  75. package/src/lib/server/chatroom-routing.ts +65 -0
  76. package/src/lib/server/connectors/discord.ts +3 -0
  77. package/src/lib/server/connectors/email.ts +267 -0
  78. package/src/lib/server/connectors/manager.ts +159 -3
  79. package/src/lib/server/connectors/openclaw.ts +3 -0
  80. package/src/lib/server/connectors/slack.ts +6 -0
  81. package/src/lib/server/connectors/telegram.ts +18 -0
  82. package/src/lib/server/connectors/types.ts +2 -0
  83. package/src/lib/server/connectors/whatsapp.ts +9 -0
  84. package/src/lib/server/context-manager.ts +132 -50
  85. package/src/lib/server/cost.ts +70 -0
  86. package/src/lib/server/create-notification.ts +2 -0
  87. package/src/lib/server/daemon-state.ts +194 -1
  88. package/src/lib/server/dag-validation.ts +115 -0
  89. package/src/lib/server/eval/runner.ts +126 -0
  90. package/src/lib/server/eval/scenarios.ts +218 -0
  91. package/src/lib/server/eval/scorer.ts +96 -0
  92. package/src/lib/server/eval/store.ts +37 -0
  93. package/src/lib/server/eval/types.ts +48 -0
  94. package/src/lib/server/execution-log.ts +12 -8
  95. package/src/lib/server/guardian.ts +34 -0
  96. package/src/lib/server/heartbeat-service.ts +53 -1
  97. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  98. package/src/lib/server/link-understanding.ts +55 -0
  99. package/src/lib/server/main-agent-loop.ts +114 -15
  100. package/src/lib/server/memory-db.ts +30 -14
  101. package/src/lib/server/mmr.ts +73 -0
  102. package/src/lib/server/openclaw-doctor.ts +48 -0
  103. package/src/lib/server/orchestrator-lg.ts +3 -0
  104. package/src/lib/server/plugins.ts +44 -22
  105. package/src/lib/server/query-expansion.ts +57 -0
  106. package/src/lib/server/queue.ts +39 -0
  107. package/src/lib/server/session-run-manager.ts +43 -2
  108. package/src/lib/server/session-tools/http.ts +19 -9
  109. package/src/lib/server/session-tools/index.ts +36 -0
  110. package/src/lib/server/session-tools/memory.ts +61 -14
  111. package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
  112. package/src/lib/server/session-tools/schedule.ts +43 -0
  113. package/src/lib/server/session-tools/web.ts +35 -11
  114. package/src/lib/server/storage.ts +132 -6
  115. package/src/lib/server/stream-agent-chat.ts +57 -8
  116. package/src/lib/server/tool-capability-policy.ts +1 -0
  117. package/src/lib/server/tool-retry.ts +62 -0
  118. package/src/lib/server/transcript-repair.ts +72 -0
  119. package/src/lib/setup-defaults.ts +278 -0
  120. package/src/lib/tool-definitions.ts +1 -0
  121. package/src/lib/validation/schemas.ts +70 -0
  122. package/src/lib/view-routes.ts +1 -0
  123. package/src/stores/use-app-store.ts +7 -3
  124. package/src/stores/use-chatroom-store.ts +52 -2
  125. package/src/types/index.ts +72 -4
  126. 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,28 +137,28 @@ 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
116
159
  - **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, sub-agent spawning, canvas presentation, direct HTTP requests, git operations, persistent memory, and sandboxed code execution (JS/TS via Deno, Python)
117
160
  - **Platform Tools** — Agents can manage other agents, tasks, schedules, skills, connectors, sessions, and encrypted secrets via built-in platform tools
118
- - **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
161
+ - **Orchestration** — Multi-agent workflows powered by LangGraph with automatic sub-agent routing, checkpointed execution, checkpoint timeline with time-travel restore, 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
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
@@ -135,15 +178,21 @@ Notes:
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
138
- - **Memory** — Per-agent and per-session memory with hybrid FTS5 + vector embeddings search, relevance-based memory recall injected into runs, and periodic auto-journaling for durable execution context
139
- - **Cost Tracking** — Per-message token counting and cost estimation displayed in the chat header
140
- - **Provider Health Metrics** — Usage dashboard surfaces provider request volume, success rates, models used, and last-used timestamps
181
+ - **Memory** — Per-agent and per-session memory with hybrid FTS5 + vector embeddings search, query expansion (LLM-generated semantic variants), MMR diversity ranking, cross-agent search (`scope: all`), pinned memories (always preloaded), memory sharing between agents, linked memory graph with interactive visualization, image attachments, and periodic auto-journaling for durable execution context
182
+ - **Knowledge Base** — Shared knowledge store (`knowledge_store` / `knowledge_search` actions) with tags, source tracking, and provenance URLs separate from per-agent memories
183
+ - **Memory Graph Visualization** — Interactive force-directed graph view of linked memories with node details and relationship exploration
184
+ - **Cost Tracking** — Per-message token counting and cost estimation displayed in the chat header, with per-agent monthly budget caps (`warn` or `block` enforcement)
185
+ - **Provider Health Metrics** — Usage dashboard surfaces provider request volume, success rates, average latency, models used, and last-used timestamps
141
186
  - **Model Failover** — Automatic key rotation on rate limits and auth errors with configurable fallback credentials
142
- - **Plugin System** — Extend agent behavior with JS plugins (hooks: beforeAgentStart, afterAgentComplete, beforeToolExec, afterToolExec, onMessage)
187
+ - **Plugin System** — Extend agent behavior with JS plugins (hooks: beforeAgentStart, afterAgentComplete, beforeToolExec, afterToolExec, onMessage, onTaskComplete, onAgentDelegation). Plugins can also define custom tools that agents can use
143
188
  - **Secrets Vault** — Encrypted storage for API keys and service tokens
144
189
  - **Custom Providers** — Add any OpenAI-compatible API as a provider
145
190
  - **MCP Servers** — Connect agents to any Model Context Protocol server. Per-agent server selection with tool discovery and per-tool disable toggles
146
191
  - **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
192
+ - **Eval Framework** — Built-in agent evaluation with scenario-based testing across categories (coding, research, companionship, multi-step, memory, planning, tool-usage), weighted scoring criteria, and LLM judge support
193
+ - **Guardian Auto-Recovery** — Automatic workspace recovery when agents fail critically, rolling back to the last known good state via git reset
194
+ - **Soul Library** — Browse and apply pre-built personality templates (archetypes) to agents, or create custom souls for reuse across your swarm
195
+ - **Context Degradation Warnings** — Proactive alerts when context usage exceeds 85%, with strategy recommendations (save to memory, summarize, checkpoint)
147
196
  - **Real-Time Sync** — WebSocket push notifications for instant UI updates across tabs and devices (fallback to polling when WS is unavailable)
148
197
  - **Mobile-First UI** — Responsive glass-themed dark interface, works on phone and desktop
149
198
 
@@ -222,22 +271,6 @@ src/
222
271
  | OpenClaw | Per-Agent Gateway | Toggle in agent editor connects to any OpenClaw gateway via the bundled CLI. |
223
272
  | Custom | API | Any OpenAI-compatible endpoint. Add via Providers sidebar. |
224
273
 
225
- ### OpenClaw
226
-
227
- [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.
228
-
229
- To connect an agent to an OpenClaw gateway:
230
-
231
- 1. Create or edit an agent
232
- 2. Toggle **OpenClaw Gateway** ON
233
- 3. Enter the gateway URL (e.g. `http://192.168.1.50:18789` or `https://my-vps:18789`)
234
- 4. Add a gateway token if authentication is enabled on the remote gateway
235
- 5. Click **Connect** — approve the device in your gateway's dashboard if prompted, then **Retry Connection**
236
-
237
- 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.
238
-
239
- URLs without a protocol are auto-prefixed with `http://`. For remote gateways with TLS, use `https://` explicitly.
240
-
241
274
  ## Chat Connectors
242
275
 
243
276
  Bridge any agent to a chat platform:
@@ -302,6 +335,7 @@ Agents with platform tools enabled can manage the SwarmClaw instance:
302
335
  | Manage Agents | List, create, update, delete agents |
303
336
  | Manage Tasks | Create and manage task board items with agent assignment |
304
337
  | Manage Schedules | Create cron, interval, or one-time scheduled jobs |
338
+ | Reminders | Schedule a conversational wake event in the current chat (`schedule_wake`) |
305
339
  | Manage Skills | List, create, update reusable skill definitions |
306
340
  | Manage Documents | Upload/search/get/delete indexed docs for lightweight RAG workflows |
307
341
  | Manage Webhooks | Register external webhook endpoints that trigger agent sessions |
@@ -432,10 +466,21 @@ module.exports = {
432
466
  hooks: {
433
467
  beforeAgentStart: async ({ session, message }) => { /* ... */ },
434
468
  afterAgentComplete: async ({ session, response }) => { /* ... */ },
435
- beforeToolExec: async ({ toolName, input }) => { /* ... */ },
469
+ beforeToolExec: async ({ toolName, input }) => { /* return { abort: true } to cancel */ },
436
470
  afterToolExec: async ({ toolName, input, output }) => { /* ... */ },
437
471
  onMessage: async ({ session, message }) => { /* ... */ },
472
+ onTaskComplete: async ({ taskId, result }) => { /* ... */ },
473
+ onAgentDelegation: async ({ sourceAgentId, targetAgentId, task }) => { /* ... */ },
438
474
  },
475
+ // Plugins can also define custom tools that agents can use
476
+ tools: [
477
+ {
478
+ name: 'my_custom_tool',
479
+ description: 'Does something amazing',
480
+ parameters: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
481
+ execute: async (args, ctx) => 'Result: ' + args.query,
482
+ },
483
+ ],
439
484
  }
440
485
  ```
441
486
 
@@ -626,7 +671,10 @@ swarmclaw tasks create --title "Fix flaky CI test" --description "Stabilize retr
626
671
  # run setup diagnostics
627
672
  swarmclaw setup doctor
628
673
 
629
- # complete setup from CLI (example: OpenAI)
674
+ # interactive setup (walks through provider selection, supports multiple providers)
675
+ swarmclaw setup init
676
+
677
+ # or non-interactive setup with flags
630
678
  swarmclaw setup init --provider openai --api-key "$OPENAI_API_KEY"
631
679
 
632
680
  # run memory maintenance analysis
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
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": {
@@ -78,9 +78,12 @@
78
78
  "exceljs": "^4.4.0",
79
79
  "grammy": "^1.40.0",
80
80
  "highlight.js": "^11.11.1",
81
+ "imapflow": "^1.2.11",
81
82
  "lucide-react": "^0.574.0",
83
+ "mailparser": "^3.9.3",
82
84
  "next": "16.1.6",
83
85
  "next-themes": "^0.4.6",
86
+ "nodemailer": "^8.0.1",
84
87
  "openclaw": "^2026.2.26",
85
88
  "pdf-parse": "^2.4.5",
86
89
  "qrcode": "^1.5.4",
@@ -103,7 +106,9 @@
103
106
  "devDependencies": {
104
107
  "@tailwindcss/postcss": "^4",
105
108
  "@types/better-sqlite3": "^7.6.13",
109
+ "@types/mailparser": "^3.4.6",
106
110
  "@types/node": "^20",
111
+ "@types/nodemailer": "^7.0.11",
107
112
  "@types/qrcode": "^1.5.6",
108
113
  "@types/react": "^19",
109
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,58 @@ 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
+ autoRecovery: body.autoRecovery || false,
57
+ soul: body.soul || undefined,
32
58
  createdAt: now,
33
59
  updatedAt: now,
34
60
  }
@@ -13,9 +13,12 @@ import {
13
13
  buildSyntheticSession,
14
14
  buildAgentSystemPromptForChatroom,
15
15
  buildHistoryForAgent,
16
+ isMuted,
16
17
  } from '@/lib/server/chatroom-helpers'
17
18
  import { filterHealthyChatroomAgents } from '@/lib/server/chatroom-health'
19
+ import { evaluateRoutingRules } from '@/lib/server/chatroom-routing'
18
20
  import { markProviderFailure, markProviderSuccess } from '@/lib/server/provider-health'
21
+ import { applyAgentReactionsFromText } from '@/lib/server/chatroom-orchestration'
19
22
  import type { Chatroom, ChatroomMessage, Agent } from '@/types'
20
23
 
21
24
  export const dynamic = 'force-dynamic'
@@ -48,7 +51,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
48
51
  // Persist incoming message
49
52
  const senderName = senderId === 'user' ? 'You' : (agents[senderId]?.name || senderId)
50
53
  let mentions = parseMentions(text, agents, chatroom.agentIds)
51
- // Auto-address: if enabled and no explicit mentions, address all agents
54
+ // Routing rules: if no explicit mentions, evaluate keyword/capability rules
55
+ if (mentions.length === 0 && chatroom.routingRules?.length) {
56
+ const agentList = chatroom.agentIds.map((aid) => agents[aid]).filter(Boolean)
57
+ mentions = evaluateRoutingRules(text, chatroom.routingRules, agentList)
58
+ }
59
+ // Auto-address: if enabled and still no mentions, address all agents
52
60
  if (chatroom.autoAddress && mentions.length === 0) {
53
61
  mentions = [...chatroom.agentIds]
54
62
  }
@@ -129,6 +137,15 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
129
137
  const agent = agents[item.agentId]
130
138
  if (!agent) return []
131
139
 
140
+ // Skip muted agents
141
+ const freshForMuteCheck = loadChatrooms()[id] as Chatroom | undefined
142
+ if (freshForMuteCheck && isMuted(freshForMuteCheck, item.agentId)) {
143
+ writeEvent({ t: 'cr_agent_start', agentId: agent.id, agentName: agent.name })
144
+ writeEvent({ t: 'err', text: `${agent.name} is muted`, agentId: agent.id, agentName: agent.name })
145
+ writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
146
+ return []
147
+ }
148
+
132
149
  // Pre-flight: check if the agent's provider is usable before attempting to stream
133
150
  const providerInfo = getProvider(agent.provider)
134
151
  const apiKey = resolveApiKey(agent.credentialId)
@@ -234,6 +251,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
234
251
  saveChatrooms(latestChatrooms)
235
252
  notify(`chatroom:${id}`)
236
253
 
254
+ // Extract and apply reactions (e.g. [REACTION]{"emoji":"👍","to":"..."})
255
+ applyAgentReactionsFromText(responseText, id, agent.id)
256
+
237
257
  markProviderSuccess(agent.provider)
238
258
  writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
239
259
 
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadChatrooms, saveChatrooms, loadAgents } from '@/lib/server/storage'
2
+ import { loadChatrooms, saveChatrooms, loadAgents, loadConnectors, saveConnectors } from '@/lib/server/storage'
3
3
  import { notify } from '@/lib/server/ws-hub'
4
4
  import { notFound } from '@/lib/server/collection-helpers'
5
5
  import { genId } from '@/lib/id'
@@ -27,6 +27,9 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
27
27
  if (body.autoAddress !== undefined) {
28
28
  chatroom.autoAddress = Boolean(body.autoAddress)
29
29
  }
30
+ if (body.routingRules !== undefined) {
31
+ chatroom.routingRules = Array.isArray(body.routingRules) ? body.routingRules : undefined
32
+ }
30
33
 
31
34
  // Diff agentIds and inject join/leave system messages
32
35
  if (Array.isArray(body.agentIds)) {
@@ -91,6 +94,21 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
91
94
  const chatrooms = loadChatrooms()
92
95
  if (!chatrooms[id]) return notFound()
93
96
 
97
+ // Cascade: null out chatroomId on any connectors that reference this chatroom
98
+ const connectors = loadConnectors()
99
+ let connectorsDirty = false
100
+ for (const connector of Object.values(connectors)) {
101
+ if (connector.chatroomId === id) {
102
+ connector.chatroomId = null
103
+ connector.updatedAt = Date.now()
104
+ connectorsDirty = true
105
+ }
106
+ }
107
+ if (connectorsDirty) {
108
+ saveConnectors(connectors)
109
+ notify('connectors')
110
+ }
111
+
94
112
  delete chatrooms[id]
95
113
  saveChatrooms(chatrooms)
96
114
  notify('chatrooms')
@@ -2,6 +2,8 @@ import { NextResponse } from 'next/server'
2
2
  import { genId } from '@/lib/id'
3
3
  import { loadChatrooms, saveChatrooms, loadAgents } from '@/lib/server/storage'
4
4
  import { notify } from '@/lib/server/ws-hub'
5
+ import { ChatroomCreateSchema, formatZodError } from '@/lib/validation/schemas'
6
+ import { z } from 'zod'
5
7
  import type { Chatroom, ChatroomMessage } from '@/types'
6
8
 
7
9
  export const dynamic = 'force-dynamic'
@@ -12,11 +14,16 @@ export async function GET() {
12
14
  }
13
15
 
14
16
  export async function POST(req: Request) {
15
- const body = await req.json()
17
+ const raw = await req.json()
18
+ const parsed = ChatroomCreateSchema.safeParse(raw)
19
+ if (!parsed.success) {
20
+ return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
21
+ }
22
+ const body = parsed.data
16
23
  const chatrooms = loadChatrooms()
17
24
  const id = genId()
18
25
 
19
- const requestedAgentIds: string[] = Array.isArray(body.agentIds) ? body.agentIds : []
26
+ const requestedAgentIds: string[] = body.agentIds
20
27
  const knownAgents = loadAgents()
21
28
  const invalidAgentIds = requestedAgentIds.filter((agentId) => !knownAgents[agentId])
22
29
  if (invalidAgentIds.length > 0) {
@@ -51,6 +58,9 @@ export async function POST(req: Request) {
51
58
  messages: joinMessages,
52
59
  chatMode,
53
60
  autoAddress,
61
+ ...(Array.isArray(body.routingRules) && body.routingRules.length > 0
62
+ ? { routingRules: body.routingRules }
63
+ : {}),
54
64
  createdAt: now,
55
65
  updatedAt: now,
56
66
  }