@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.
- package/README.md +81 -33
- package/package.json +6 -1
- package/src/app/api/agents/[id]/clone/route.ts +40 -0
- package/src/app/api/agents/route.ts +40 -14
- package/src/app/api/chatrooms/[id]/chat/route.ts +21 -1
- package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
- package/src/app/api/chatrooms/[id]/route.ts +19 -1
- package/src/app/api/chatrooms/route.ts +12 -2
- package/src/app/api/connectors/[id]/health/route.ts +64 -0
- package/src/app/api/connectors/route.ts +17 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- package/src/app/api/knowledge/route.ts +6 -1
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/openclaw/doctor/route.ts +17 -0
- package/src/app/api/sessions/[id]/chat/route.ts +5 -1
- package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/sessions/route.ts +11 -2
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/route.ts +23 -13
- package/src/app/api/tasks/route.ts +22 -1
- package/src/app/api/usage/route.ts +25 -9
- package/src/cli/index.js +29 -0
- package/src/cli/index.ts +223 -39
- package/src/components/agents/agent-card.tsx +37 -6
- package/src/components/agents/agent-chat-list.tsx +78 -2
- package/src/components/agents/agent-sheet.tsx +104 -4
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/setup-wizard.tsx +268 -353
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +22 -7
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/message-bubble.tsx +14 -14
- package/src/components/chat/message-list.tsx +20 -4
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chatrooms/chatroom-message.tsx +164 -22
- package/src/components/chatrooms/chatroom-sheet.tsx +288 -3
- package/src/components/chatrooms/chatroom-view.tsx +62 -17
- package/src/components/connectors/connector-health.tsx +120 -0
- package/src/components/connectors/connector-sheet.tsx +17 -1
- package/src/components/home/home-view.tsx +55 -10
- package/src/components/input/chat-input.tsx +8 -1
- package/src/components/layout/app-layout.tsx +35 -3
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/plugins/plugin-list.tsx +1 -1
- package/src/components/schedules/schedule-list.tsx +55 -9
- package/src/components/schedules/schedule-sheet.tsx +143 -25
- package/src/components/shared/command-palette.tsx +237 -0
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/settings/section-runtime-loop.tsx +5 -4
- package/src/components/tasks/approvals-panel.tsx +120 -0
- package/src/components/tasks/task-card.tsx +22 -2
- package/src/components/tasks/task-sheet.tsx +91 -16
- package/src/components/usage/metrics-dashboard.tsx +38 -28
- package/src/hooks/use-swipe.ts +49 -0
- package/src/lib/providers/anthropic.ts +16 -2
- package/src/lib/providers/claude-cli.ts +7 -1
- package/src/lib/providers/index.ts +7 -0
- package/src/lib/providers/ollama.ts +16 -2
- package/src/lib/providers/openai.ts +7 -2
- package/src/lib/providers/openclaw.ts +6 -1
- package/src/lib/providers/provider-defaults.ts +7 -0
- package/src/lib/schedule-templates.ts +115 -0
- package/src/lib/server/alert-dispatch.ts +64 -0
- package/src/lib/server/chat-execution.ts +137 -13
- package/src/lib/server/chatroom-helpers.ts +85 -6
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/chatroom-routing.ts +65 -0
- package/src/lib/server/connectors/discord.ts +3 -0
- package/src/lib/server/connectors/email.ts +267 -0
- package/src/lib/server/connectors/manager.ts +159 -3
- package/src/lib/server/connectors/openclaw.ts +3 -0
- package/src/lib/server/connectors/slack.ts +6 -0
- package/src/lib/server/connectors/telegram.ts +18 -0
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp.ts +9 -0
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.ts +70 -0
- package/src/lib/server/create-notification.ts +2 -0
- package/src/lib/server/daemon-state.ts +194 -1
- package/src/lib/server/dag-validation.ts +115 -0
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/main-agent-loop.ts +114 -15
- package/src/lib/server/memory-db.ts +30 -14
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/openclaw-doctor.ts +48 -0
- package/src/lib/server/orchestrator-lg.ts +3 -0
- package/src/lib/server/plugins.ts +44 -22
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +39 -0
- package/src/lib/server/session-run-manager.ts +43 -2
- package/src/lib/server/session-tools/http.ts +19 -9
- package/src/lib/server/session-tools/index.ts +36 -0
- package/src/lib/server/session-tools/memory.ts +61 -14
- package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
- package/src/lib/server/session-tools/schedule.ts +43 -0
- package/src/lib/server/session-tools/web.ts +35 -11
- package/src/lib/server/storage.ts +132 -6
- package/src/lib/server/stream-agent-chat.ts +57 -8
- package/src/lib/server/tool-capability-policy.ts +1 -0
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +278 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.ts +70 -0
- package/src/lib/view-routes.ts +1 -0
- package/src/stores/use-app-store.ts +7 -3
- package/src/stores/use-chatroom-store.ts +52 -2
- package/src/types/index.ts +72 -4
- 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
|
-
|
|
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
|

|
|
19
19
|

|
|
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.
|
|
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
|
|
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
|
|
98
|
-
2.
|
|
99
|
-
3.
|
|
100
|
-
4.
|
|
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
|
-
-
|
|
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,
|
|
139
|
-
- **
|
|
140
|
-
- **
|
|
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
|
-
#
|
|
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.
|
|
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(
|
|
10
|
-
|
|
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
|
|
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
|
|
21
|
-
description: body.description
|
|
22
|
-
systemPrompt: body.systemPrompt
|
|
23
|
-
provider: body.provider
|
|
24
|
-
model: body.model
|
|
25
|
-
credentialId: body.credentialId
|
|
26
|
-
apiEndpoint: normalizeProviderEndpoint(body.provider
|
|
27
|
-
isOrchestrator: body.isOrchestrator
|
|
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
|
-
//
|
|
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
|
|
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[] =
|
|
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
|
}
|