@swarmclawai/swarmclaw 0.3.1 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +5 -3
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
package/README.md
CHANGED
|
@@ -13,16 +13,10 @@ Inspired by [OpenClaw](https://github.com/openclaw).
|
|
|
13
13
|
|
|
14
14
|
**[Documentation](https://swarmclaw.ai/docs)** | **[Website](https://swarmclaw.ai)**
|
|
15
15
|
|
|
16
|
-
Org avatar files: `public/branding/swarmclaw-org-avatar.png` (upload-ready), `public/branding/swarmclaw-org-avatar.svg` (source)
|
|
17
|
-
|
|
18
16
|

|
|
19
17
|

|
|
20
18
|

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