@swarmclawai/swarmclaw 0.6.7 → 0.7.0
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 +82 -39
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -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/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/graph/route.ts +46 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- 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/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- 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]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/checkpoint-timeline.tsx +112 -0
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +37 -7
- package/src/components/home/home-view.tsx +54 -24
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +87 -19
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +28 -9
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/hint-tip.tsx +31 -0
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +224 -0
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +72 -48
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- 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/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +115 -16
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +32 -2
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +78 -0
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ The orchestration dashboard for OpenClaw. Manage a swarm of OpenClaws + 14 other
|
|
|
12
12
|
|
|
13
13
|
Inspired by [OpenClaw](https://github.com/openclaw).
|
|
14
14
|
|
|
15
|
-
**[Documentation](https://swarmclaw.ai/docs)** | **[Website](https://swarmclaw.ai)**
|
|
15
|
+
**[Documentation](https://swarmclaw.ai/docs)** | **[Plugin Tutorial](https://swarmclaw.ai/docs/plugin-tutorial)** | **[Website](https://swarmclaw.ai)**
|
|
16
16
|
|
|
17
17
|

|
|
18
18
|

|
|
@@ -85,7 +85,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
|
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
The installer resolves the latest stable release tag and installs that version by default.
|
|
88
|
-
To pin a version: `SWARMCLAW_VERSION=v0.
|
|
88
|
+
To pin a version: `SWARMCLAW_VERSION=v0.7.0 curl ... | bash`
|
|
89
89
|
|
|
90
90
|
Or run locally from the repo (friendly for non-technical users):
|
|
91
91
|
|
|
@@ -158,7 +158,7 @@ Notes:
|
|
|
158
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
|
|
159
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)
|
|
160
160
|
- **Platform Tools** — Agents can manage other agents, tasks, schedules, skills, connectors, sessions, and encrypted secrets via built-in platform tools
|
|
161
|
-
- **Orchestration** — Multi-agent workflows powered by LangGraph with automatic sub-agent routing, checkpointed execution, and rich delegation cards that link to sub-agent chat threads
|
|
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
|
|
162
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
|
|
163
163
|
- **Runtime Date/Time Grounding** — Session, orchestrator, chatroom, and connector prompts include authoritative current timestamp context to reduce stale-date behavior
|
|
164
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
|
|
@@ -178,15 +178,21 @@ Notes:
|
|
|
178
178
|
- **Skills System** — Discover local skills, import skills from URL, and load OpenClaw `SKILL.md` files (frontmatter-compatible)
|
|
179
179
|
- **Execution Logging** — Structured audit trail for triggers, tool calls, file ops, commits, and errors in a dedicated `logs.db`
|
|
180
180
|
- **Context Management** — Auto-compaction of conversation history when approaching context limits, with manual `context_status` and `context_summarize` tools for agents
|
|
181
|
-
- **Memory** — Per-agent and per-session memory with hybrid FTS5 + vector embeddings search,
|
|
182
|
-
- **
|
|
183
|
-
- **
|
|
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
|
|
184
186
|
- **Model Failover** — Automatic key rotation on rate limits and auth errors with configurable fallback credentials
|
|
185
|
-
- **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
|
|
186
188
|
- **Secrets Vault** — Encrypted storage for API keys and service tokens
|
|
187
189
|
- **Custom Providers** — Add any OpenAI-compatible API as a provider
|
|
188
190
|
- **MCP Servers** — Connect agents to any Model Context Protocol server. Per-agent server selection with tool discovery and per-tool disable toggles
|
|
189
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)
|
|
190
196
|
- **Real-Time Sync** — WebSocket push notifications for instant UI updates across tabs and devices (fallback to polling when WS is unavailable)
|
|
191
197
|
- **Mobile-First UI** — Responsive glass-themed dark interface, works on phone and desktop
|
|
192
198
|
|
|
@@ -195,8 +201,9 @@ Notes:
|
|
|
195
201
|
All config lives in `.env.local` (auto-generated):
|
|
196
202
|
|
|
197
203
|
```
|
|
198
|
-
ACCESS_KEY=<your-access-key>
|
|
199
|
-
CREDENTIAL_SECRET=<auto-generated>
|
|
204
|
+
ACCESS_KEY=<your-access-key> # Auth key for the dashboard
|
|
205
|
+
CREDENTIAL_SECRET=<auto-generated> # AES-256 encryption key for stored credentials
|
|
206
|
+
SWARMCLAW_PLUGIN_FAILURE_THRESHOLD=3 # Consecutive failures before auto-disabling a plugin
|
|
200
207
|
```
|
|
201
208
|
|
|
202
209
|
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.
|
|
@@ -329,6 +336,7 @@ Agents with platform tools enabled can manage the SwarmClaw instance:
|
|
|
329
336
|
| Manage Agents | List, create, update, delete agents |
|
|
330
337
|
| Manage Tasks | Create and manage task board items with agent assignment |
|
|
331
338
|
| Manage Schedules | Create cron, interval, or one-time scheduled jobs |
|
|
339
|
+
| Reminders | Schedule a conversational wake event in the current chat (`schedule_wake`) |
|
|
332
340
|
| Manage Skills | List, create, update reusable skill definitions |
|
|
333
341
|
| Manage Documents | Upload/search/get/delete indexed docs for lightweight RAG workflows |
|
|
334
342
|
| Manage Webhooks | Register external webhook endpoints that trigger agent sessions |
|
|
@@ -442,43 +450,68 @@ When enabled, new memories get vector embeddings. Search uses both FTS5 keyword
|
|
|
442
450
|
|
|
443
451
|
Agents and sessions can have **fallback credentials**. If the primary API key gets a 401, 429, or 500 error, SwarmClaw automatically retries with the next credential. Configure fallback keys in the agent builder UI.
|
|
444
452
|
|
|
445
|
-
##
|
|
453
|
+
## Plugin System
|
|
446
454
|
|
|
447
|
-
|
|
455
|
+
SwarmClaw features a powerful, modular plugin system designed for both agent enhancement and application extensibility. It is fully compatible with the **OpenClaw** plugin format.
|
|
448
456
|
|
|
449
|
-
|
|
450
|
-
2. **URL** — Install from any HTTPS URL via Settings → Plugins → Install from URL
|
|
451
|
-
3. **Manual** — Drop `.js` files into `data/plugins/`
|
|
457
|
+
Plugins can be managed in **Settings → Plugins** and installed via the Marketplace, URL, or by dropping `.js` files into `data/plugins/`.
|
|
452
458
|
|
|
453
|
-
|
|
459
|
+
Docs:
|
|
460
|
+
- Full docs: https://swarmclaw.ai/docs
|
|
461
|
+
- Plugin tutorial: https://swarmclaw.ai/docs/plugin-tutorial
|
|
462
|
+
|
|
463
|
+
### Extension Points
|
|
464
|
+
|
|
465
|
+
Unlike standard tool systems, SwarmClaw plugins can modify the application itself:
|
|
466
|
+
|
|
467
|
+
- **Agent Tools**: Define custom tools that agents can autonomously discover and use.
|
|
468
|
+
- **Lifecycle Hooks**: Intercept events like `beforeAgentStart`, `afterToolExec`, and `onMessage`.
|
|
469
|
+
- **UI Extensions**:
|
|
470
|
+
- `sidebarItems`: Inject new navigation links into the main sidebar.
|
|
471
|
+
- `headerWidgets`: Add status badges or indicators to the chat header (e.g., Wallet Balance).
|
|
472
|
+
- `chatInputActions`: Add custom action buttons next to the chat input (e.g., "Quick Scan").
|
|
473
|
+
- `plugin-ui` Messages: Render rich, interactive React cards in the chat stream.
|
|
474
|
+
- **Deep Chat Hooks**:
|
|
475
|
+
- `transformInboundMessage`: Modify user messages before they reach the agent.
|
|
476
|
+
- `transformOutboundMessage`: Modify agent responses before they are saved or displayed.
|
|
477
|
+
- **Custom Providers**: Add new LLM backends (e.g., a specialized local model or a new API).
|
|
478
|
+
- **Custom Connectors**: Build new chat platform bridges (e.g., a proprietary internal messenger).
|
|
479
|
+
|
|
480
|
+
### Autonomous Capability Discovery
|
|
481
|
+
|
|
482
|
+
Agents in SwarmClaw are "aware" of the plugin system. If an agent lacks a tool needed for a task, it can:
|
|
483
|
+
1. **Discover**: Scan the system for all installed plugins.
|
|
484
|
+
2. **Search Marketplace**: Autonomously search **ClawHub** and the **SwarmClaw Registry** for new capabilities.
|
|
485
|
+
3. **Request Access**: Prompt the user in-chat to enable a specific installed plugin.
|
|
486
|
+
4. **Install Request**: Suggest installing a new plugin from a marketplace URL to fill a capability gap (requires user approval).
|
|
487
|
+
|
|
488
|
+
### Example Plugin (SwarmClaw Format)
|
|
454
489
|
|
|
455
490
|
```js
|
|
456
491
|
module.exports = {
|
|
457
|
-
name: 'my-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
afterAgentComplete: async ({ session, response }) => { /* ... */ },
|
|
462
|
-
beforeToolExec: async ({ toolName, input }) => { /* ... */ },
|
|
463
|
-
afterToolExec: async ({ toolName, input, output }) => { /* ... */ },
|
|
464
|
-
onMessage: async ({ session, message }) => { /* ... */ },
|
|
492
|
+
name: 'my-custom-extension',
|
|
493
|
+
ui: {
|
|
494
|
+
sidebarItems: [{ id: 'dashboard', label: 'My View', href: '/custom-view' }],
|
|
495
|
+
headerWidgets: [{ id: 'status', label: '🟢 Active' }]
|
|
465
496
|
},
|
|
466
|
-
|
|
497
|
+
tools: [{
|
|
498
|
+
name: 'custom_action',
|
|
499
|
+
description: 'Perform a specialized task',
|
|
500
|
+
parameters: { type: 'object', properties: { input: { type: 'string' } } },
|
|
501
|
+
execute: async (args) => {
|
|
502
|
+
// Logic here
|
|
503
|
+
return { kind: 'plugin-ui', text: 'Rich result card content' };
|
|
504
|
+
}
|
|
505
|
+
}]
|
|
506
|
+
};
|
|
467
507
|
```
|
|
468
508
|
|
|
469
|
-
###
|
|
509
|
+
### Lifecycle Management
|
|
470
510
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
| `onAgentStart` | `beforeAgentStart` |
|
|
476
|
-
| `onAgentComplete` | `afterAgentComplete` |
|
|
477
|
-
| `onToolCall` | `beforeToolExec` |
|
|
478
|
-
| `onToolResult` | `afterToolExec` |
|
|
479
|
-
| `onMessage` | `onMessage` |
|
|
480
|
-
|
|
481
|
-
Plugin API: `GET /api/plugins`, `POST /api/plugins`, `GET /api/plugins/marketplace`, `POST /api/plugins/install`.
|
|
511
|
+
- **Versioning**: All plugins support semantic versioning (e.g., `v1.2.3`).
|
|
512
|
+
- **Updates**: Plugins can be updated individually or in bulk via the Plugins manager.
|
|
513
|
+
- **Hot-Reload**: The system automatically reloads plugin logic when a file is updated or a new plugin is installed.
|
|
514
|
+
- **Stability Guardrails**: Consecutive plugin failures are tracked in `data/plugin-failures.json`; failing plugins are auto-disabled, a warning notification is emitted in-app, and users can re-enable manually from the Plugins manager.
|
|
482
515
|
|
|
483
516
|
## Deploy to a VPS
|
|
484
517
|
|
|
@@ -568,7 +601,7 @@ npm run dev:webpack
|
|
|
568
601
|
### First-Run Helpers
|
|
569
602
|
|
|
570
603
|
```bash
|
|
571
|
-
npm run setup:easy # setup only (does not start server)
|
|
604
|
+
npm run setup:easy # setup only (installs Deno if missing; does not start server)
|
|
572
605
|
npm run quickstart # setup + start dev server
|
|
573
606
|
npm run quickstart:prod # setup + build + start production server
|
|
574
607
|
npm run update:easy # safe update helper for local installs
|
|
@@ -579,8 +612,8 @@ npm run update:easy # safe update helper for local installs
|
|
|
579
612
|
SwarmClaw uses tag-based releases (`vX.Y.Z`) as the stable channel.
|
|
580
613
|
|
|
581
614
|
```bash
|
|
582
|
-
# example
|
|
583
|
-
npm version
|
|
615
|
+
# example minor release (v0.7.0 style)
|
|
616
|
+
npm version minor
|
|
584
617
|
git push origin main --follow-tags
|
|
585
618
|
```
|
|
586
619
|
|
|
@@ -589,6 +622,16 @@ On `v*` tags, GitHub Actions will:
|
|
|
589
622
|
2. Create a GitHub Release
|
|
590
623
|
3. Build and publish Docker images to `ghcr.io/swarmclawai/swarmclaw` (`:vX.Y.Z`, `:latest`, `:sha-*`)
|
|
591
624
|
|
|
625
|
+
#### v0.7.0 Release Readiness Notes
|
|
626
|
+
|
|
627
|
+
Before shipping `v0.7.0`, confirm the following user-facing changes are reflected in docs:
|
|
628
|
+
|
|
629
|
+
1. Plugins UI now shows richer installed plugin details (description fallback, source/status, capability badges, and clearer detail sheet metadata).
|
|
630
|
+
2. Marketplace plugin installs normalize legacy URLs from `swarmclawai/plugins` to `swarmclawai/swarmforge` to avoid 404 installs.
|
|
631
|
+
3. Hydration fixes removed nested `<button>` structures in list-card UIs (Plugins, Providers, Secrets, MCP Servers).
|
|
632
|
+
4. Clipboard actions now use a browser-safe fallback when `navigator.clipboard` is unavailable.
|
|
633
|
+
5. Site docs/release notes are updated in `swarmclaw-site` (especially `content/docs/plugins.md`, `content/docs/release-notes.md`, and `public/registry/plugins.json`).
|
|
634
|
+
|
|
592
635
|
## CLI
|
|
593
636
|
|
|
594
637
|
SwarmClaw ships a built-in CLI for core operational workflows:
|
package/next.config.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
import { execSync } from "child_process";
|
|
3
|
+
import { networkInterfaces } from "os";
|
|
4
|
+
import { DIRECT_NAV_SEGMENTS } from "./view-route-paths";
|
|
3
5
|
|
|
4
6
|
function getGitSha(): string {
|
|
5
7
|
try {
|
|
@@ -9,6 +11,33 @@ function getGitSha(): string {
|
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
function getAllowedDevOrigins(): string[] {
|
|
15
|
+
const allowed = new Set<string>([
|
|
16
|
+
'localhost',
|
|
17
|
+
'127.0.0.1',
|
|
18
|
+
'0.0.0.0',
|
|
19
|
+
])
|
|
20
|
+
|
|
21
|
+
// Include all active local IPv4 interfaces so LAN devices can load /_next assets in dev.
|
|
22
|
+
for (const interfaces of Object.values(networkInterfaces())) {
|
|
23
|
+
for (const iface of interfaces ?? []) {
|
|
24
|
+
if ((iface.family === 'IPv4' || (iface.family as string | number) === 4) && !iface.internal) {
|
|
25
|
+
allowed.add(iface.address)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Optional override for custom origins/hosts, e.g. `NEXT_ALLOWED_DEV_ORIGINS=host1,host2`.
|
|
31
|
+
const extra = (process.env.NEXT_ALLOWED_DEV_ORIGINS ?? '')
|
|
32
|
+
.split(',')
|
|
33
|
+
.map((v) => v.trim())
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.map((v) => v.replace(/^https?:\/\//, '').replace(/\/$/, ''))
|
|
36
|
+
for (const host of extra) allowed.add(host)
|
|
37
|
+
|
|
38
|
+
return [...allowed]
|
|
39
|
+
}
|
|
40
|
+
|
|
12
41
|
const nextConfig: NextConfig = {
|
|
13
42
|
output: 'standalone',
|
|
14
43
|
turbopack: {
|
|
@@ -35,13 +64,9 @@ const nextConfig: NextConfig = {
|
|
|
35
64
|
'@whiskeysockets/baileys',
|
|
36
65
|
'qrcode',
|
|
37
66
|
],
|
|
38
|
-
allowedDevOrigins:
|
|
39
|
-
'localhost',
|
|
40
|
-
'127.0.0.1',
|
|
41
|
-
'0.0.0.0',
|
|
42
|
-
],
|
|
67
|
+
allowedDevOrigins: getAllowedDevOrigins(),
|
|
43
68
|
async rewrites() {
|
|
44
|
-
const views = '
|
|
69
|
+
const views = DIRECT_NAV_SEGMENTS.join('|')
|
|
45
70
|
return [
|
|
46
71
|
{
|
|
47
72
|
source: `/:view(${views})`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
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": {
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"lint:baseline:update": "node ./scripts/lint-baseline.mjs update",
|
|
53
53
|
"cli": "node ./bin/swarmclaw.js",
|
|
54
54
|
"test:cli": "node --test src/cli/index.test.js",
|
|
55
|
-
"test:openclaw": "tsx --test src/lib/server/connectors/openclaw.test.ts src/lib/openclaw-endpoint.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/tool-capability-policy.test.ts",
|
|
55
|
+
"test:openclaw": "tsx --test src/lib/server/connectors/openclaw.test.ts src/lib/openclaw-endpoint.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/server/task-validation.test.ts src/lib/server/task-quality-gate.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts",
|
|
56
|
+
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
56
57
|
"postinstall": "node ./scripts/postinstall.mjs"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { genId } from '@/lib/id'
|
|
3
|
-
import { loadAgents, saveAgents, logActivity } from '@/lib/server/storage'
|
|
3
|
+
import { loadAgents, loadSessions, loadUsage, 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 {
|
|
6
|
+
import { getAgentSpendWindows } from '@/lib/server/cost'
|
|
7
7
|
import { AgentCreateSchema, formatZodError } from '@/lib/validation/schemas'
|
|
8
8
|
import { z } from 'zod'
|
|
9
9
|
export const dynamic = 'force-dynamic'
|
|
@@ -11,10 +11,19 @@ export const dynamic = 'force-dynamic'
|
|
|
11
11
|
|
|
12
12
|
export async function GET(req: Request) {
|
|
13
13
|
const agents = loadAgents()
|
|
14
|
-
|
|
14
|
+
const sessions = loadSessions()
|
|
15
|
+
const usage = loadUsage()
|
|
16
|
+
// Enrich agents that have spend limits with current spend windows
|
|
15
17
|
for (const agent of Object.values(agents)) {
|
|
16
|
-
if (
|
|
17
|
-
agent.
|
|
18
|
+
if (
|
|
19
|
+
(typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0)
|
|
20
|
+
|| (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0)
|
|
21
|
+
|| (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0)
|
|
22
|
+
) {
|
|
23
|
+
const spend = getAgentSpendWindows(agent.id, Date.now(), { sessions, usage })
|
|
24
|
+
if (typeof agent.monthlyBudget === 'number' && agent.monthlyBudget > 0) agent.monthlySpend = spend.monthly
|
|
25
|
+
if (typeof agent.dailyBudget === 'number' && agent.dailyBudget > 0) agent.dailySpend = spend.daily
|
|
26
|
+
if (typeof agent.hourlyBudget === 'number' && agent.hourlyBudget > 0) agent.hourlySpend = spend.hourly
|
|
18
27
|
}
|
|
19
28
|
}
|
|
20
29
|
|
|
@@ -53,6 +62,11 @@ export async function POST(req: Request) {
|
|
|
53
62
|
tools: body.tools,
|
|
54
63
|
capabilities: body.capabilities,
|
|
55
64
|
thinkingLevel: body.thinkingLevel || undefined,
|
|
65
|
+
autoRecovery: body.autoRecovery || false,
|
|
66
|
+
monthlyBudget: body.monthlyBudget ?? null,
|
|
67
|
+
dailyBudget: body.dailyBudget ?? null,
|
|
68
|
+
hourlyBudget: body.hourlyBudget ?? null,
|
|
69
|
+
budgetAction: body.budgetAction || 'warn',
|
|
56
70
|
soul: body.soul || undefined,
|
|
57
71
|
createdAt: now,
|
|
58
72
|
updatedAt: now,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { listPendingApprovals, submitDecision } from '@/lib/server/approvals'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
return NextResponse.json(listPendingApprovals())
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request) {
|
|
11
|
+
try {
|
|
12
|
+
const body = await req.json()
|
|
13
|
+
const { id, approved } = body
|
|
14
|
+
if (!id || typeof approved !== 'boolean') {
|
|
15
|
+
return NextResponse.json({ error: 'id and approved required' }, { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
await submitDecision(id, approved)
|
|
18
|
+
return NextResponse.json({ ok: true })
|
|
19
|
+
} catch (err: unknown) {
|
|
20
|
+
return NextResponse.json({ error: err instanceof Error ? err.message : String(err) }, { status: 500 })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { filterHealthyChatroomAgents } from '@/lib/server/chatroom-health'
|
|
19
19
|
import { evaluateRoutingRules } from '@/lib/server/chatroom-routing'
|
|
20
20
|
import { markProviderFailure, markProviderSuccess } from '@/lib/server/provider-health'
|
|
21
|
+
import { applyAgentReactionsFromText } from '@/lib/server/chatroom-orchestration'
|
|
21
22
|
import type { Chatroom, ChatroomMessage, Agent } from '@/types'
|
|
22
23
|
|
|
23
24
|
export const dynamic = 'force-dynamic'
|
|
@@ -250,6 +251,9 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
250
251
|
saveChatrooms(latestChatrooms)
|
|
251
252
|
notify(`chatroom:${id}`)
|
|
252
253
|
|
|
254
|
+
// Extract and apply reactions (e.g. [REACTION]{"emoji":"👍","to":"..."})
|
|
255
|
+
applyAgentReactionsFromText(responseText, id, agent.id)
|
|
256
|
+
|
|
253
257
|
markProviderSuccess(agent.provider)
|
|
254
258
|
writeEvent({ t: 'cr_agent_done', agentId: agent.id, agentName: agent.name })
|
|
255
259
|
|
|
@@ -11,9 +11,9 @@ export async function POST(req: Request) {
|
|
|
11
11
|
if (!content) {
|
|
12
12
|
try {
|
|
13
13
|
content = await fetchSkillContent(url)
|
|
14
|
-
} catch (err:
|
|
14
|
+
} catch (err: unknown) {
|
|
15
15
|
return NextResponse.json(
|
|
16
|
-
{ error: err.message
|
|
16
|
+
{ error: err instanceof Error ? err.message : 'Failed to fetch skill content' },
|
|
17
17
|
{ status: 502 }
|
|
18
18
|
)
|
|
19
19
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { runEvalScenario } from '@/lib/server/eval/runner'
|
|
4
|
+
import { listEvalRuns } from '@/lib/server/eval/store'
|
|
5
|
+
|
|
6
|
+
const RunSchema = z.object({
|
|
7
|
+
scenarioId: z.string().min(1),
|
|
8
|
+
agentId: z.string().min(1),
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export async function POST(req: Request) {
|
|
12
|
+
try {
|
|
13
|
+
const body: unknown = await req.json()
|
|
14
|
+
const parsed = RunSchema.safeParse(body)
|
|
15
|
+
if (!parsed.success) {
|
|
16
|
+
return NextResponse.json(
|
|
17
|
+
{ error: parsed.error.issues.map((i) => i.message).join(', ') },
|
|
18
|
+
{ status: 400 },
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const result = await runEvalScenario(parsed.data.scenarioId, parsed.data.agentId)
|
|
23
|
+
return NextResponse.json(result)
|
|
24
|
+
} catch (err: unknown) {
|
|
25
|
+
return NextResponse.json(
|
|
26
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
27
|
+
{ status: 500 },
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function GET(req: Request) {
|
|
33
|
+
const { searchParams } = new URL(req.url)
|
|
34
|
+
const limit = Math.min(parseInt(searchParams.get('limit') || '50', 10), 200)
|
|
35
|
+
const runs = listEvalRuns(limit)
|
|
36
|
+
return NextResponse.json(runs)
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { EVAL_SCENARIOS } from '@/lib/server/eval/scenarios'
|
|
3
|
+
|
|
4
|
+
export async function GET(req: Request) {
|
|
5
|
+
const { searchParams } = new URL(req.url)
|
|
6
|
+
const category = searchParams.get('category')
|
|
7
|
+
|
|
8
|
+
const scenarios = category
|
|
9
|
+
? EVAL_SCENARIOS.filter((s) => s.category === category)
|
|
10
|
+
: EVAL_SCENARIOS
|
|
11
|
+
|
|
12
|
+
return NextResponse.json(
|
|
13
|
+
scenarios.map((s) => ({
|
|
14
|
+
id: s.id,
|
|
15
|
+
name: s.name,
|
|
16
|
+
category: s.category,
|
|
17
|
+
description: s.description,
|
|
18
|
+
tools: s.tools,
|
|
19
|
+
timeoutMs: s.timeoutMs,
|
|
20
|
+
criteriaCount: s.scoringCriteria.length,
|
|
21
|
+
maxScore: s.scoringCriteria.reduce((sum, c) => sum + c.weight, 0),
|
|
22
|
+
})),
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import { runEvalSuite } from '@/lib/server/eval/runner'
|
|
4
|
+
|
|
5
|
+
const SuiteSchema = z.object({
|
|
6
|
+
agentId: z.string().min(1),
|
|
7
|
+
categories: z.array(z.string()).optional(),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
export async function POST(req: Request) {
|
|
11
|
+
try {
|
|
12
|
+
const body: unknown = await req.json()
|
|
13
|
+
const parsed = SuiteSchema.safeParse(body)
|
|
14
|
+
if (!parsed.success) {
|
|
15
|
+
return NextResponse.json(
|
|
16
|
+
{ error: parsed.error.issues.map((i) => i.message).join(', ') },
|
|
17
|
+
{ status: 400 },
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const result = await runEvalSuite(parsed.data.agentId, parsed.data.categories)
|
|
22
|
+
return NextResponse.json(result)
|
|
23
|
+
} catch (err: unknown) {
|
|
24
|
+
return NextResponse.json(
|
|
25
|
+
{ error: err instanceof Error ? err.message : String(err) },
|
|
26
|
+
{ status: 500 },
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadMcpServers } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { runMcpConformanceCheck } from '@/lib/server/mcp-conformance'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
const { id } = await params
|
|
8
|
+
const servers = loadMcpServers()
|
|
9
|
+
const server = servers[id]
|
|
10
|
+
if (!server) return notFound()
|
|
11
|
+
|
|
12
|
+
const body = await req.json().catch(() => ({}))
|
|
13
|
+
const timeoutMs = typeof body?.timeoutMs === 'number' ? body.timeoutMs : undefined
|
|
14
|
+
const smokeToolName = typeof body?.smokeToolName === 'string' ? body.smokeToolName : undefined
|
|
15
|
+
const smokeToolArgs = body?.smokeToolArgs && typeof body.smokeToolArgs === 'object' && !Array.isArray(body.smokeToolArgs)
|
|
16
|
+
? body.smokeToolArgs
|
|
17
|
+
: undefined
|
|
18
|
+
|
|
19
|
+
const result = await runMcpConformanceCheck(server, {
|
|
20
|
+
timeoutMs,
|
|
21
|
+
smokeToolName,
|
|
22
|
+
smokeToolArgs,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return NextResponse.json(result, { status: result.ok ? 200 : 502 })
|
|
26
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadMcpServers } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { connectMcpServer, disconnectMcpServer } from '@/lib/server/mcp-client'
|
|
5
|
+
|
|
6
|
+
function parseArgs(value: unknown): Record<string, unknown> {
|
|
7
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
8
|
+
return value as Record<string, unknown>
|
|
9
|
+
}
|
|
10
|
+
if (typeof value === 'string' && value.trim()) {
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(value)
|
|
13
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
14
|
+
return parsed as Record<string, unknown>
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
// Handled by caller via fallback validation error.
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
24
|
+
const { id } = await params
|
|
25
|
+
const servers = loadMcpServers()
|
|
26
|
+
const server = servers[id]
|
|
27
|
+
if (!server) return notFound()
|
|
28
|
+
|
|
29
|
+
const body = await req.json().catch(() => null)
|
|
30
|
+
const toolName = typeof body?.toolName === 'string' ? body.toolName.trim() : ''
|
|
31
|
+
if (!toolName) {
|
|
32
|
+
return NextResponse.json({ error: 'toolName is required' }, { status: 400 })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const argsRaw = body?.args
|
|
36
|
+
if (
|
|
37
|
+
argsRaw !== undefined
|
|
38
|
+
&& typeof argsRaw !== 'string'
|
|
39
|
+
&& (typeof argsRaw !== 'object' || Array.isArray(argsRaw))
|
|
40
|
+
) {
|
|
41
|
+
return NextResponse.json({ error: 'args must be an object or JSON string' }, { status: 400 })
|
|
42
|
+
}
|
|
43
|
+
const args = parseArgs(argsRaw)
|
|
44
|
+
|
|
45
|
+
let client: unknown
|
|
46
|
+
let transport: unknown
|
|
47
|
+
try {
|
|
48
|
+
const conn = await connectMcpServer(server)
|
|
49
|
+
client = conn.client
|
|
50
|
+
transport = conn.transport
|
|
51
|
+
const result = await (client as { callTool: (opts: { name: string; arguments: Record<string, unknown> }) => Promise<Record<string, unknown>> }).callTool({
|
|
52
|
+
name: toolName,
|
|
53
|
+
arguments: args,
|
|
54
|
+
})
|
|
55
|
+
const textParts = Array.isArray(result?.content)
|
|
56
|
+
? (result.content as Array<Record<string, unknown>>)
|
|
57
|
+
.filter((part) => part?.type === 'text' && typeof part?.text === 'string')
|
|
58
|
+
.map((part) => part.text as string)
|
|
59
|
+
: []
|
|
60
|
+
const text = textParts.join('\n').trim() || '(no text output)'
|
|
61
|
+
|
|
62
|
+
return NextResponse.json({
|
|
63
|
+
ok: true,
|
|
64
|
+
toolName,
|
|
65
|
+
args,
|
|
66
|
+
text,
|
|
67
|
+
result,
|
|
68
|
+
isError: result?.isError === true,
|
|
69
|
+
})
|
|
70
|
+
} catch (err: unknown) {
|
|
71
|
+
return NextResponse.json(
|
|
72
|
+
{ ok: false, error: err instanceof Error ? err.message : 'MCP tool invocation failed' },
|
|
73
|
+
{ status: 500 },
|
|
74
|
+
)
|
|
75
|
+
} finally {
|
|
76
|
+
if (client && transport) {
|
|
77
|
+
await disconnectMcpServer(client, transport)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getMemoryDb } from '@/lib/server/memory-db'
|
|
3
|
+
import type { MemoryEntry } from '@/types'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
/** GET /api/memory/graph — returns a node-link structure of the memory graph */
|
|
8
|
+
export async function GET(req: Request) {
|
|
9
|
+
const { searchParams } = new URL(req.url)
|
|
10
|
+
const agentId = searchParams.get('agentId')
|
|
11
|
+
const limit = Math.min(1000, Math.max(1, Number(searchParams.get('limit')) || 200))
|
|
12
|
+
|
|
13
|
+
const db = getMemoryDb()
|
|
14
|
+
const entries: MemoryEntry[] = db.list(agentId || undefined, limit)
|
|
15
|
+
|
|
16
|
+
const nodes = entries.map(e => ({
|
|
17
|
+
id: e.id,
|
|
18
|
+
title: e.title,
|
|
19
|
+
category: e.category,
|
|
20
|
+
agentId: e.agentId,
|
|
21
|
+
contentPreview: e.content.slice(0, 100) + (e.content.length > 100 ? '...' : ''),
|
|
22
|
+
createdAt: e.createdAt,
|
|
23
|
+
updatedAt: e.updatedAt,
|
|
24
|
+
pinned: e.pinned
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
const links: Array<{ source: string; target: string; type: string }> = []
|
|
28
|
+
const entryIds = new Set(entries.map(e => e.id))
|
|
29
|
+
|
|
30
|
+
for (const entry of entries) {
|
|
31
|
+
if (entry.linkedMemoryIds && Array.isArray(entry.linkedMemoryIds)) {
|
|
32
|
+
for (const targetId of entry.linkedMemoryIds) {
|
|
33
|
+
// Only include links where both nodes are in the current set (or could fetch more if needed)
|
|
34
|
+
if (entryIds.has(targetId)) {
|
|
35
|
+
links.push({
|
|
36
|
+
source: entry.id,
|
|
37
|
+
target: targetId,
|
|
38
|
+
type: 'linked'
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return NextResponse.json({ nodes, links })
|
|
46
|
+
}
|