@swarmclawai/swarmclaw 0.6.0 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +56 -42
  2. package/bin/server-cmd.js +1 -0
  3. package/package.json +2 -1
  4. package/src/app/api/canvas/[sessionId]/route.ts +31 -0
  5. package/src/app/api/chatrooms/[id]/chat/route.ts +10 -136
  6. package/src/app/api/connectors/[id]/route.ts +1 -0
  7. package/src/app/api/connectors/route.ts +2 -1
  8. package/src/app/api/files/open/route.ts +43 -0
  9. package/src/app/api/search/route.ts +9 -7
  10. package/src/app/api/sessions/[id]/messages/route.ts +70 -2
  11. package/src/app/api/sessions/[id]/route.ts +4 -0
  12. package/src/app/api/tasks/metrics/route.ts +101 -0
  13. package/src/app/api/tasks/route.ts +17 -2
  14. package/src/app/api/tts/route.ts +16 -35
  15. package/src/app/api/tts/stream/route.ts +14 -42
  16. package/src/app/api/uploads/[filename]/route.ts +19 -34
  17. package/src/app/api/uploads/route.ts +94 -0
  18. package/src/app/globals.css +5 -0
  19. package/src/cli/index.js +16 -1
  20. package/src/cli/spec.js +26 -0
  21. package/src/components/agents/agent-card.tsx +3 -3
  22. package/src/components/agents/agent-chat-list.tsx +29 -6
  23. package/src/components/agents/agent-sheet.tsx +66 -4
  24. package/src/components/agents/inspector-panel.tsx +81 -6
  25. package/src/components/agents/openclaw-skills-panel.tsx +32 -3
  26. package/src/components/agents/personality-builder.tsx +42 -14
  27. package/src/components/agents/soul-library-picker.tsx +89 -0
  28. package/src/components/canvas/canvas-panel.tsx +96 -0
  29. package/src/components/chat/activity-moment.tsx +8 -4
  30. package/src/components/chat/chat-area.tsx +76 -24
  31. package/src/components/chat/chat-header.tsx +522 -286
  32. package/src/components/chat/chat-preview-panel.tsx +1 -2
  33. package/src/components/chat/delegation-banner.tsx +371 -0
  34. package/src/components/chat/file-path-chip.tsx +23 -2
  35. package/src/components/chat/heartbeat-history-panel.tsx +269 -0
  36. package/src/components/chat/message-bubble.tsx +315 -25
  37. package/src/components/chat/message-list.tsx +113 -8
  38. package/src/components/chat/streaming-bubble.tsx +68 -1
  39. package/src/components/chat/tool-call-bubble.tsx +45 -3
  40. package/src/components/chat/transfer-agent-picker.tsx +1 -1
  41. package/src/components/chatrooms/chatroom-list.tsx +8 -1
  42. package/src/components/chatrooms/chatroom-message.tsx +8 -3
  43. package/src/components/chatrooms/chatroom-view.tsx +3 -3
  44. package/src/components/connectors/connector-list.tsx +168 -90
  45. package/src/components/connectors/connector-sheet.tsx +84 -17
  46. package/src/components/home/home-view.tsx +1 -1
  47. package/src/components/input/chat-input.tsx +28 -2
  48. package/src/components/layout/app-layout.tsx +19 -2
  49. package/src/components/projects/project-detail.tsx +1 -1
  50. package/src/components/schedules/schedule-sheet.tsx +260 -127
  51. package/src/components/settings/gateway-disconnect-overlay.tsx +80 -0
  52. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  53. package/src/components/shared/chatroom-picker-list.tsx +61 -0
  54. package/src/components/shared/connector-platform-icon.tsx +51 -4
  55. package/src/components/shared/icon-button.tsx +16 -2
  56. package/src/components/shared/keyboard-shortcuts-dialog.tsx +1 -1
  57. package/src/components/shared/search-dialog.tsx +17 -10
  58. package/src/components/shared/settings/section-embedding.tsx +48 -13
  59. package/src/components/shared/settings/section-orchestrator.tsx +46 -15
  60. package/src/components/shared/settings/section-storage.tsx +206 -0
  61. package/src/components/shared/settings/section-user-preferences.tsx +18 -0
  62. package/src/components/shared/settings/section-voice.tsx +42 -21
  63. package/src/components/shared/settings/section-web-search.tsx +30 -6
  64. package/src/components/shared/settings/settings-page.tsx +3 -1
  65. package/src/components/shared/settings/storage-browser.tsx +259 -0
  66. package/src/components/tasks/task-card.tsx +14 -1
  67. package/src/components/tasks/task-sheet.tsx +328 -3
  68. package/src/components/usage/metrics-dashboard.tsx +90 -6
  69. package/src/hooks/use-continuous-speech.ts +10 -4
  70. package/src/hooks/use-voice-conversation.ts +53 -10
  71. package/src/hooks/use-ws.ts +4 -2
  72. package/src/lib/providers/anthropic.ts +13 -7
  73. package/src/lib/providers/index.ts +1 -0
  74. package/src/lib/providers/openai.ts +13 -7
  75. package/src/lib/server/chat-execution.ts +125 -14
  76. package/src/lib/server/chatroom-helpers.ts +146 -0
  77. package/src/lib/server/connectors/connector-routing.test.ts +118 -1
  78. package/src/lib/server/connectors/discord.ts +31 -8
  79. package/src/lib/server/connectors/manager.ts +594 -16
  80. package/src/lib/server/connectors/media.ts +5 -0
  81. package/src/lib/server/connectors/telegram.ts +12 -2
  82. package/src/lib/server/connectors/types.ts +2 -0
  83. package/src/lib/server/connectors/whatsapp.ts +28 -2
  84. package/src/lib/server/elevenlabs.test.ts +60 -0
  85. package/src/lib/server/elevenlabs.ts +103 -0
  86. package/src/lib/server/heartbeat-service.ts +8 -1
  87. package/src/lib/server/main-agent-loop.ts +1 -1
  88. package/src/lib/server/memory-consolidation.ts +15 -2
  89. package/src/lib/server/memory-db.ts +134 -6
  90. package/src/lib/server/mime.ts +51 -0
  91. package/src/lib/server/openclaw-gateway.ts +2 -2
  92. package/src/lib/server/orchestrator-lg.ts +2 -0
  93. package/src/lib/server/orchestrator.ts +5 -2
  94. package/src/lib/server/playwright-proxy.mjs +2 -3
  95. package/src/lib/server/prompt-runtime-context.ts +53 -0
  96. package/src/lib/server/queue.ts +182 -8
  97. package/src/lib/server/session-tools/canvas.ts +67 -0
  98. package/src/lib/server/session-tools/connector.ts +583 -63
  99. package/src/lib/server/session-tools/crud.ts +21 -0
  100. package/src/lib/server/session-tools/delegate.ts +68 -4
  101. package/src/lib/server/session-tools/file.ts +26 -7
  102. package/src/lib/server/session-tools/git.ts +71 -0
  103. package/src/lib/server/session-tools/http.ts +57 -0
  104. package/src/lib/server/session-tools/index.ts +8 -0
  105. package/src/lib/server/session-tools/memory.ts +1 -0
  106. package/src/lib/server/session-tools/search-providers.ts +16 -8
  107. package/src/lib/server/session-tools/subagent.ts +106 -0
  108. package/src/lib/server/session-tools/web.ts +118 -8
  109. package/src/lib/server/stream-agent-chat.ts +39 -10
  110. package/src/lib/server/task-mention.ts +41 -0
  111. package/src/lib/sessions.ts +10 -0
  112. package/src/lib/soul-library.ts +103 -0
  113. package/src/lib/task-dedupe.ts +26 -0
  114. package/src/lib/tool-definitions.ts +2 -0
  115. package/src/lib/tts.ts +2 -2
  116. package/src/stores/use-app-store.ts +5 -1
  117. package/src/stores/use-chat-store.ts +65 -2
  118. package/src/types/index.ts +32 -2
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # SwarmClaw
2
2
 
3
3
  [![CI](https://github.com/swarmclawai/swarmclaw/actions/workflows/ci.yml/badge.svg)](https://github.com/swarmclawai/swarmclaw/actions/workflows/ci.yml)
4
- [![Release](https://img.shields.io/github/v/release/swarmclawai/swarmclaw?sort=semver)](https://github.com/swarmclawai/swarmclaw/releases)
4
+ [![Release](https://img.shields.io/github/v/tag/swarmclawai/swarmclaw)](https://github.com/swarmclawai/swarmclaw/releases)
5
+ [![npm](https://img.shields.io/npm/v/%40swarmclawai%2Fswarmclaw?label=npm)](https://www.npmjs.com/package/@swarmclawai/swarmclaw)
5
6
 
6
7
  <p align="center">
7
8
  <img src="https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/public/branding/swarmclaw-org-avatar.png" alt="SwarmClaw lobster logo" width="120" />
@@ -22,46 +23,6 @@ Inspired by [OpenClaw](https://github.com/openclaw).
22
23
  - Review agent system prompts before giving them shell or browser tools
23
24
  - Repeated failed access key attempts are rate-limited to slow brute-force attacks
24
25
 
25
- ## Features
26
-
27
- - **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
28
- - **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)
29
- - **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
30
- - **Agent Builder** — Create agents with custom personalities (soul), system prompts, tools, and skills. AI-powered generation from a description
31
- - **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
32
- - **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
33
- - **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)
34
- - **Platform Tools** — Agents can manage other agents, tasks, schedules, skills, connectors, sessions, and encrypted secrets via built-in platform tools
35
- - **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
36
- - **Agentic Execution Policy** — Tool-first autonomous action loop with progress updates, evidence-driven answers, and better use of platform tools for long-lived work
37
- - **Task Board** — Queue and track agent tasks with status, comments, results, and archiving. Strict capability policy pauses tasks for human approval before tool execution
38
- - **Background Daemon** — Auto-processes queued tasks and scheduled jobs with a 30s heartbeat plus recurring health monitoring
39
- - **Scheduling** — Cron-based agent scheduling with human-friendly presets
40
- - **Loop Runtime Controls** — Switch between bounded and ongoing loops with configurable step caps, runtime guards, heartbeat cadence, and timeout budgets
41
- - **Session Run Queue** — Per-session queued runs with followup/steer/collect modes, collect coalescing for bursty inputs, and run-state APIs
42
- - **Chat Iteration Workflow** — Edit-and-resend user turns, fork a new session from any message, bookmark key messages, use contextual follow-up suggestion chips, and auto-continue after tool access grants
43
- - **Agent Chatrooms** — Multi-agent room conversations with `@mention` routing, chained agent replies, reactions, and file/image-aware chat context
44
- - **Live Chat Telemetry** — Thinking/tool/responding stream phases, live main-loop status badges, connector activity presence, tone indicator, and optional sound notifications
45
- - **Global Search Palette** — `Cmd/Ctrl+K` search across agents, tasks, sessions, schedules, webhooks, and skills from anywhere in the app
46
- - **Notification Center** — Real-time in-app notifications for task/schedule/daemon events with unread tracking, mark-all/clear-read controls, and optional action links
47
- - **Preview-Rich Chat UI** — Side preview panel for tool outputs (image/browser/html/code), inline code/PDF previews for attachments, and image lightbox support
48
- - **Voice Settings** — Per-instance ElevenLabs API key + voice ID for TTS replies, plus configurable speech recognition language for chat input
49
- - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, WhatsApp, BlueBubbles (iMessage), Signal, Microsoft Teams, Google Chat, Matrix, and OpenClaw with media-aware inbound handling
50
- - **Skills System** — Discover local skills, import skills from URL, and load OpenClaw `SKILL.md` files (frontmatter-compatible)
51
- - **Execution Logging** — Structured audit trail for triggers, tool calls, file ops, commits, and errors in a dedicated `logs.db`
52
- - **Context Management** — Auto-compaction of conversation history when approaching context limits, with manual `context_status` and `context_summarize` tools for agents
53
- - **Memory** — Per-agent and per-session memory with hybrid FTS5 + vector embeddings search, relevance-based memory recall injected into runs, and periodic auto-journaling for durable execution context
54
- - **Cost Tracking** — Per-message token counting and cost estimation displayed in the chat header
55
- - **Provider Health Metrics** — Usage dashboard surfaces provider request volume, success rates, models used, and last-used timestamps
56
- - **Model Failover** — Automatic key rotation on rate limits and auth errors with configurable fallback credentials
57
- - **Plugin System** — Extend agent behavior with JS plugins (hooks: beforeAgentStart, afterAgentComplete, beforeToolExec, afterToolExec, onMessage)
58
- - **Secrets Vault** — Encrypted storage for API keys and service tokens
59
- - **Custom Providers** — Add any OpenAI-compatible API as a provider
60
- - **MCP Servers** — Connect agents to any Model Context Protocol server. Per-agent server selection with tool discovery and per-tool disable toggles
61
- - **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
62
- - **Real-Time Sync** — WebSocket push notifications for instant UI updates across tabs and devices (fallback to polling when WS is unavailable)
63
- - **Mobile-First UI** — Responsive glass-themed dark interface, works on phone and desktop
64
-
65
26
  ## Requirements
66
27
 
67
28
  - **Node.js** 22.6+
@@ -86,7 +47,7 @@ curl -fsSL https://raw.githubusercontent.com/swarmclawai/swarmclaw/main/install.
86
47
  ```
87
48
 
88
49
  The installer resolves the latest stable release tag and installs that version by default.
89
- To pin a version: `SWARMCLAW_VERSION=v0.6.0 curl ... | bash`
50
+ To pin a version: `SWARMCLAW_VERSION=v0.6.2 curl ... | bash`
90
51
 
91
52
  Or run locally from the repo (friendly for non-technical users):
92
53
 
@@ -144,6 +105,48 @@ Notes:
144
105
  - OpenClaw is configured per-agent via the **OpenClaw Gateway** toggle (not in the setup wizard).
145
106
  - You can skip setup and configure everything later in the sidebar.
146
107
 
108
+ ## Features
109
+
110
+ - **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
+ - **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
+ - **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
113
+ - **Agent Builder** — Create agents with custom personalities (soul), system prompts, tools, and skills. AI-powered generation from a description
114
+ - **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
+ - **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
+ - **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
+ - **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
119
+ - **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
+ - **Runtime Date/Time Grounding** — Session, orchestrator, chatroom, and connector prompts include authoritative current timestamp context to reduce stale-date behavior
121
+ - **Task Board** — Queue and track agent tasks with status, comments, results, and archiving. Strict capability policy pauses tasks for human approval before tool execution
122
+ - **Task Metrics API** — Built-in analytics endpoint for WIP, cycle times, throughput velocity, completion/failure by agent, and priority distribution
123
+ - **Background Daemon** — Auto-processes queued tasks and scheduled jobs with a 30s heartbeat plus recurring health monitoring
124
+ - **Scheduling** — Cron-based agent scheduling with human-friendly presets
125
+ - **Loop Runtime Controls** — Switch between bounded and ongoing loops with configurable step caps, runtime guards, heartbeat cadence, and timeout budgets
126
+ - **Session Run Queue** — Per-session queued runs with followup/steer/collect modes, collect coalescing for bursty inputs, and run-state APIs
127
+ - **Chat Iteration Workflow** — Edit-and-resend user turns, fork a new session from any message, bookmark key messages, use contextual follow-up suggestion chips, and auto-continue after tool access grants
128
+ - **Agent Chatrooms** — Multi-agent room conversations with `@mention` routing, chained agent replies, reactions, and file/image-aware chat context
129
+ - **Live Chat Telemetry** — Thinking/tool/responding stream phases, live main-loop status badges, connector activity presence, tone indicator, and optional sound notifications
130
+ - **Global Search Palette** — `Cmd/Ctrl+K` search across agents, tasks, sessions, schedules, webhooks, and skills from anywhere in the app
131
+ - **Notification Center** — Real-time in-app notifications for task/schedule/daemon events with unread tracking, mark-all/clear-read controls, and optional action links
132
+ - **Preview-Rich Chat UI** — Side preview panel for tool outputs (image/browser/html/code), inline code/PDF previews for attachments, and image lightbox support
133
+ - **Voice Settings** — Per-instance ElevenLabs API key + voice ID for TTS replies, plus configurable speech recognition language for chat input
134
+ - **Chat Connectors** — Bridge agents to Discord, Slack, Telegram, WhatsApp, BlueBubbles (iMessage), Signal, Microsoft Teams, Google Chat, Matrix, and OpenClaw with media-aware inbound handling
135
+ - **Skills System** — Discover local skills, import skills from URL, and load OpenClaw `SKILL.md` files (frontmatter-compatible)
136
+ - **Execution Logging** — Structured audit trail for triggers, tool calls, file ops, commits, and errors in a dedicated `logs.db`
137
+ - **Context Management** — Auto-compaction of conversation history when approaching context limits, with manual `context_status` and `context_summarize` tools for agents
138
+ - **Memory** — Per-agent and per-session memory with hybrid FTS5 + vector embeddings search, relevance-based memory recall injected into runs, and periodic auto-journaling for durable execution context
139
+ - **Cost Tracking** — Per-message token counting and cost estimation displayed in the chat header
140
+ - **Provider Health Metrics** — Usage dashboard surfaces provider request volume, success rates, models used, and last-used timestamps
141
+ - **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)
143
+ - **Secrets Vault** — Encrypted storage for API keys and service tokens
144
+ - **Custom Providers** — Add any OpenAI-compatible API as a provider
145
+ - **MCP Servers** — Connect agents to any Model Context Protocol server. Per-agent server selection with tool discovery and per-tool disable toggles
146
+ - **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
147
+ - **Real-Time Sync** — WebSocket push notifications for instant UI updates across tabs and devices (fallback to polling when WS is unavailable)
148
+ - **Mobile-First UI** — Responsive glass-themed dark interface, works on phone and desktop
149
+
147
150
  ## Configuration
148
151
 
149
152
  All config lives in `.env.local` (auto-generated):
@@ -279,7 +282,11 @@ Agents can use the following tools when enabled:
279
282
  | Web Search | Search the web via DuckDuckGo HTML scraping |
280
283
  | Web Fetch | Fetch and extract text content from URLs (uses cheerio) |
281
284
  | CLI Delegation | Delegate complex tasks to Claude Code, Codex CLI, or OpenCode CLI |
285
+ | Spawn Subagent | Delegate a sub-task to another agent and capture its response in the current run |
282
286
  | Browser | Playwright-powered web browsing via MCP (navigate, click, type, screenshot, PDF) |
287
+ | Canvas | Present/hide/snapshot live HTML content in a session canvas panel |
288
+ | HTTP Request | Make direct API calls with method, headers, body, redirect control, and timeout |
289
+ | Git | Run structured git subcommands (`status`, `diff`, `log`, `add`, `commit`, `push`, etc.) with repo safety checks |
283
290
  | Memory | Store and retrieve long-term memories with FTS5 + vector search, file references, image attachments, and linked memory graph traversal |
284
291
  | Sandbox | Run JS/TS (Deno) or Python code in an isolated sandbox. Created files are returned as downloadable artifacts |
285
292
  | MCP Servers | Connect to external Model Context Protocol servers. Tools from MCP servers are injected as first-class agent tools |
@@ -319,6 +326,13 @@ Token usage and estimated costs are tracked per message for API-based providers
319
326
  - **Data:** Stored in `data/swarmclaw.db` (usage table)
320
327
  - Cost estimates use published model pricing (updated manually in `src/lib/server/cost.ts`)
321
328
 
329
+ ## Task Metrics
330
+
331
+ Task analytics are available via API for dashboarding and release-readiness checks:
332
+
333
+ - **API endpoint:** `GET /api/tasks/metrics?range=24h|7d|30d`
334
+ - **Returns:** status totals, WIP count, completion velocity buckets, avg/p50/p90 cycle time, completion/failure by agent, and priority counts
335
+
322
336
  ## Background Daemon
323
337
 
324
338
  The daemon auto-processes queued tasks from the scheduler on a 30-second interval. It also runs recurring health checks that detect stale heartbeat sessions and can send proactive WhatsApp alerts when issues are detected. Toggle the daemon from the sidebar indicator or via API.
package/bin/server-cmd.js CHANGED
@@ -21,6 +21,7 @@ const DATA_DIR = path.join(SWARMCLAW_HOME, 'data')
21
21
  const BUILD_COPY_ENTRIES = [
22
22
  'src',
23
23
  'public',
24
+ 'scripts',
24
25
  'next.config.ts',
25
26
  'tsconfig.json',
26
27
  'postcss.config.mjs',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
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": {
@@ -84,6 +84,7 @@
84
84
  "radix-ui": "^1.4.3",
85
85
  "react": "19.2.3",
86
86
  "react-dom": "19.2.3",
87
+ "react-hot-toast": "^2.6.0",
87
88
  "react-icons": "^5.5.0",
88
89
  "react-markdown": "^10.1.0",
89
90
  "recharts": "^3.7.0",
@@ -0,0 +1,31 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { loadSessions, saveSessions } from '@/lib/server/storage'
3
+ import { notify } from '@/lib/server/ws-hub'
4
+
5
+ export async function GET(_req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
6
+ const { sessionId } = await params
7
+ const sessions = loadSessions()
8
+ const session = sessions[sessionId]
9
+ if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
10
+
11
+ return NextResponse.json({
12
+ sessionId,
13
+ content: (session as Record<string, unknown>).canvasContent || null,
14
+ })
15
+ }
16
+
17
+ export async function POST(req: Request, { params }: { params: Promise<{ sessionId: string }> }) {
18
+ const { sessionId } = await params
19
+ const body = await req.json()
20
+ const sessions = loadSessions()
21
+ const session = sessions[sessionId]
22
+ if (!session) return NextResponse.json({ error: 'Session not found' }, { status: 404 })
23
+
24
+ ;(session as Record<string, unknown>).canvasContent = body.content || null
25
+ session.lastActiveAt = Date.now()
26
+ sessions[sessionId] = session
27
+ saveSessions(sessions)
28
+
29
+ notify(`canvas:${sessionId}`)
30
+ return NextResponse.json({ ok: true, sessionId })
31
+ }
@@ -1,151 +1,25 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { genId } from '@/lib/id'
3
- import { loadChatrooms, saveChatrooms, loadAgents, loadSettings, loadSkills, loadCredentials, decryptKey } from '@/lib/server/storage'
3
+ import { loadChatrooms, saveChatrooms, loadAgents } from '@/lib/server/storage'
4
4
  import { notify } from '@/lib/server/ws-hub'
5
5
  import { notFound } from '@/lib/server/collection-helpers'
6
6
  import { streamAgentChat } from '@/lib/server/stream-agent-chat'
7
7
  import { getProvider } from '@/lib/providers'
8
- import type { Chatroom, ChatroomMessage, Agent, Session, Message } from '@/types'
8
+ import {
9
+ resolveApiKey,
10
+ parseMentions,
11
+ buildChatroomSystemPrompt,
12
+ buildSyntheticSession,
13
+ buildAgentSystemPromptForChatroom,
14
+ buildHistoryForAgent,
15
+ } from '@/lib/server/chatroom-helpers'
16
+ import type { Chatroom, ChatroomMessage, Agent } from '@/types'
9
17
 
10
18
  export const dynamic = 'force-dynamic'
11
19
  export const maxDuration = 300
12
20
 
13
21
  const MAX_CHAIN_DEPTH = 5
14
22
 
15
- /** Resolve API key from an agent's credentialId */
16
- function resolveApiKey(credentialId: string | null | undefined): string | null {
17
- if (!credentialId) return null
18
- const creds = loadCredentials()
19
- const cred = creds[credentialId]
20
- if (!cred?.encryptedKey) return null
21
- try { return decryptKey(cred.encryptedKey) } catch { return null }
22
- }
23
-
24
- /** Parse @mentions from message text, returns matching agentIds */
25
- function parseMentions(text: string, agents: Record<string, Agent>, memberIds: string[]): string[] {
26
- if (/@all\b/i.test(text)) return [...memberIds]
27
- const mentionPattern = /@(\S+)/g
28
- const mentioned: string[] = []
29
- let match: RegExpExecArray | null
30
- while ((match = mentionPattern.exec(text)) !== null) {
31
- const name = match[1].toLowerCase()
32
- for (const id of memberIds) {
33
- const agent = agents[id]
34
- if (agent && agent.name.toLowerCase().replace(/\s+/g, '') === name) {
35
- if (!mentioned.includes(id)) mentioned.push(id)
36
- }
37
- }
38
- }
39
- return mentioned
40
- }
41
-
42
- /** Build chatroom context as a system prompt addendum with agent profiles and collaboration guidelines */
43
- function buildChatroomSystemPrompt(chatroom: Chatroom, agents: Record<string, Agent>, agentId: string): string {
44
- const selfAgent = agents[agentId]
45
- const selfName = selfAgent?.name || agentId
46
-
47
- // Build team profiles with capabilities
48
- const teamProfiles = chatroom.agentIds
49
- .filter((id) => id !== agentId)
50
- .map((id) => {
51
- const a = agents[id]
52
- if (!a) return null
53
- const tools = a.tools?.length ? `Tools: ${a.tools.join(', ')}` : 'No specialized tools'
54
- const desc = a.description || a.soul || 'No description'
55
- return `- **${a.name}**: ${desc}\n ${tools}`
56
- })
57
- .filter(Boolean)
58
- .join('\n')
59
-
60
- const recentMessages = chatroom.messages.slice(-30).map((m) => {
61
- return `[${m.senderName}]: ${m.text}`
62
- }).join('\n')
63
-
64
- return [
65
- `## Chatroom Context`,
66
- `You are **${selfName}** in chatroom "${chatroom.name}".`,
67
- selfAgent?.description ? `Your role: ${selfAgent.description}` : '',
68
- selfAgent?.tools?.length ? `Your tools: ${selfAgent.tools.join(', ')}` : '',
69
- '',
70
- '## Team Members',
71
- teamProfiles || '(no other agents)',
72
- '',
73
- '## Collaboration Guidelines',
74
- '- Before executing complex tasks, briefly discuss your approach with the team.',
75
- '- When delegating to another agent, explain what you need, why they are best suited, and what output you expect. Example: "@DataBot I need a summary of recent API errors from the logs — you have the shell tool to grep through them."',
76
- '- If someone mentions a task you are well-suited for, proactively offer to help.',
77
- '- Do not just @mention mechanically — explain your reasoning when involving others.',
78
- '- If you can handle a request entirely yourself, just do it. Only delegate what you cannot do.',
79
- '',
80
- '## Recent Messages',
81
- recentMessages || '(no messages yet)',
82
- ].filter((line) => line !== undefined).join('\n')
83
- }
84
-
85
- /** Build a synthetic session object for an agent in a chatroom */
86
- function buildSyntheticSession(agent: Agent, chatroomId: string): Session {
87
- return {
88
- id: `chatroom-${chatroomId}-${agent.id}`,
89
- name: `Chatroom session for ${agent.name}`,
90
- cwd: process.cwd(),
91
- user: 'chatroom',
92
- provider: agent.provider,
93
- model: agent.model,
94
- credentialId: agent.credentialId ?? null,
95
- fallbackCredentialIds: agent.fallbackCredentialIds,
96
- apiEndpoint: agent.apiEndpoint ?? null,
97
- claudeSessionId: null,
98
- messages: [],
99
- createdAt: Date.now(),
100
- lastActiveAt: Date.now(),
101
- tools: agent.tools || [],
102
- agentId: agent.id,
103
- }
104
- }
105
-
106
- /** Build agent's system prompt including skills */
107
- function buildAgentSystemPromptForChatroom(agent: Agent): string {
108
- const settings = loadSettings()
109
- const parts: string[] = []
110
- if (settings.userPrompt) parts.push(settings.userPrompt)
111
- if (agent.soul) parts.push(agent.soul)
112
- if (agent.systemPrompt) parts.push(agent.systemPrompt)
113
- if (agent.skillIds?.length) {
114
- const allSkills = loadSkills()
115
- for (const skillId of agent.skillIds) {
116
- const skill = allSkills[skillId]
117
- if (skill?.content) parts.push(`## Skill: ${skill.name}\n${skill.content}`)
118
- }
119
- }
120
- return parts.join('\n\n')
121
- }
122
-
123
- /** Convert chatroom messages to Message history format for LLM */
124
- function buildHistoryForAgent(chatroom: Chatroom, agentId: string, imagePath?: string, attachedFiles?: string[]): Message[] {
125
- const history = chatroom.messages.slice(-50).map((m) => {
126
- let msgText = `[${m.senderName}]: ${m.text}`
127
- // Include attachment info in history
128
- if (m.attachedFiles?.length) {
129
- const names = m.attachedFiles.map((f) => f.split('/').pop()).join(', ')
130
- msgText += `\n[Attached: ${names}]`
131
- }
132
- return {
133
- role: m.senderId === agentId ? 'assistant' as const : 'user' as const,
134
- text: msgText,
135
- time: m.time,
136
- ...(m.imagePath ? { imagePath: m.imagePath } : {}),
137
- ...(m.attachedFiles ? { attachedFiles: m.attachedFiles } : {}),
138
- }
139
- })
140
- // Pass through imagePath/attachedFiles from the current message to the last history entry
141
- if (history.length > 0 && (imagePath || attachedFiles)) {
142
- const last = history[history.length - 1]
143
- if (imagePath && !last.imagePath) last.imagePath = imagePath
144
- if (attachedFiles && !last.attachedFiles) last.attachedFiles = attachedFiles
145
- }
146
- return history
147
- }
148
-
149
23
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
150
24
  const { id } = await params
151
25
  const body = await req.json()
@@ -61,6 +61,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
61
61
  // Regular update
62
62
  if (body.name !== undefined) connector.name = body.name
63
63
  if (body.agentId !== undefined) connector.agentId = body.agentId
64
+ if (body.chatroomId !== undefined) connector.chatroomId = body.chatroomId
64
65
  if (body.credentialId !== undefined) connector.credentialId = body.credentialId
65
66
  if (body.config !== undefined) connector.config = body.config
66
67
  if (body.isEnabled !== undefined) connector.isEnabled = body.isEnabled
@@ -33,7 +33,8 @@ export async function POST(req: Request) {
33
33
  id,
34
34
  name: body.name || `${body.platform} Connector`,
35
35
  platform: body.platform,
36
- agentId: body.agentId,
36
+ agentId: body.agentId || null,
37
+ chatroomId: body.chatroomId || null,
37
38
  credentialId: body.credentialId || null,
38
39
  config: body.config || {},
39
40
  isEnabled: false,
@@ -0,0 +1,43 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { exec } from 'child_process'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+
6
+ export async function POST(req: Request) {
7
+ const { path: targetPath } = await req.json() as { path?: string }
8
+ if (!targetPath || typeof targetPath !== 'string') {
9
+ return NextResponse.json({ error: 'path is required' }, { status: 400 })
10
+ }
11
+
12
+ const resolved = path.resolve(targetPath)
13
+
14
+ // Verify the path exists
15
+ if (!fs.existsSync(resolved)) {
16
+ return NextResponse.json({ error: 'Path does not exist' }, { status: 404 })
17
+ }
18
+
19
+ const isDir = fs.statSync(resolved).isDirectory()
20
+ const platform = process.platform
21
+
22
+ // Determine the command to reveal in the OS file manager
23
+ let cmd: string
24
+ if (platform === 'darwin') {
25
+ // macOS: -R reveals in Finder (selects the item), for dirs just open the dir
26
+ cmd = isDir ? `open "${resolved}"` : `open -R "${resolved}"`
27
+ } else if (platform === 'win32') {
28
+ cmd = isDir ? `explorer "${resolved}"` : `explorer /select,"${resolved}"`
29
+ } else {
30
+ // Linux: xdg-open on the directory containing the file
31
+ cmd = `xdg-open "${isDir ? resolved : path.dirname(resolved)}"`
32
+ }
33
+
34
+ return new Promise<NextResponse>((resolve) => {
35
+ exec(cmd, (err) => {
36
+ if (err) {
37
+ resolve(NextResponse.json({ error: err.message }, { status: 500 }))
38
+ } else {
39
+ resolve(NextResponse.json({ ok: true }))
40
+ }
41
+ })
42
+ })
43
+ }
@@ -59,22 +59,24 @@ function searchMessages(
59
59
  const MAX_MSG_RESULTS = 10
60
60
  for (const [sessionId, session] of Object.entries(sessions)) {
61
61
  if (results.length >= MAX_MSG_RESULTS) break
62
- const messages = session.messages as Array<{ role: string; text: string; time?: number }> | undefined
63
- if (!messages?.length) continue
62
+ if (!Array.isArray(session.messages) || !session.messages.length) continue
63
+ const messages = session.messages as Array<Record<string, unknown>>
64
64
  const agentId = session.agentId as string | undefined
65
65
  const agentName = agentId && agents[agentId] ? (agents[agentId].name as string) : undefined
66
66
  const sessionName = (session.name as string) || 'Untitled'
67
67
  for (let i = 0; i < messages.length; i++) {
68
68
  if (results.length >= MAX_MSG_RESULTS) break
69
69
  const msg = messages[i]
70
- if (!msg?.text) continue
71
- const idx = msg.text.toLowerCase().indexOf(needle)
70
+ const text = typeof msg?.text === 'string' ? msg.text : ''
71
+ if (!text) continue
72
+ const idx = text.toLowerCase().indexOf(needle)
72
73
  if (idx === -1) continue
73
74
  // Build snippet with context around match
74
75
  const start = Math.max(0, idx - 30)
75
- const end = Math.min(msg.text.length, idx + needle.length + 50)
76
- const snippet = (start > 0 ? '...' : '') + msg.text.slice(start, end).replace(/\n/g, ' ') + (end < msg.text.length ? '...' : '')
77
- const timeAgo = msg.time ? formatTimeAgo(msg.time) : ''
76
+ const end = Math.min(text.length, idx + needle.length + 50)
77
+ const snippet = (start > 0 ? '...' : '') + text.slice(start, end).replace(/\n/g, ' ') + (end < text.length ? '...' : '')
78
+ const msgTime = typeof msg.time === 'number' ? msg.time : 0
79
+ const timeAgo = msgTime ? formatTimeAgo(msgTime) : ''
78
80
  results.push({
79
81
  type: 'message',
80
82
  id: sessionId,
@@ -2,11 +2,57 @@ import { NextResponse } from 'next/server'
2
2
  import { loadSessions, saveSessions } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
 
5
- export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
5
+ export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
6
  const { id } = await params
7
7
  const sessions = loadSessions()
8
8
  if (!sessions[id]) return notFound()
9
- return NextResponse.json(sessions[id].messages)
9
+
10
+ const url = new URL(req.url)
11
+ const limitParam = url.searchParams.get('limit')
12
+ const beforeParam = url.searchParams.get('before')
13
+
14
+ const allMessages = sessions[id].messages
15
+ const total = allMessages.length
16
+
17
+ // If no limit param, return all messages (backward compatible)
18
+ if (!limitParam) {
19
+ return NextResponse.json(allMessages)
20
+ }
21
+
22
+ const limit = Math.max(1, Math.min(500, parseInt(limitParam) || 100))
23
+ const before = beforeParam !== null ? parseInt(beforeParam) : total
24
+
25
+ // Return `limit` messages ending just before `before` index
26
+ const start = Math.max(0, before - limit)
27
+ const end = Math.max(0, before)
28
+ const messages = allMessages.slice(start, end)
29
+
30
+ return NextResponse.json({
31
+ messages,
32
+ total,
33
+ hasMore: start > 0,
34
+ startIndex: start,
35
+ })
36
+ }
37
+
38
+ export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
39
+ const { id } = await params
40
+ const body = await req.json() as { kind?: string }
41
+ if (body.kind !== 'context-clear') {
42
+ return NextResponse.json({ error: 'Only context-clear kind is supported' }, { status: 400 })
43
+ }
44
+ const sessions = loadSessions()
45
+ const session = sessions[id]
46
+ if (!session) return notFound()
47
+
48
+ session.messages.push({
49
+ role: 'user',
50
+ text: '',
51
+ kind: 'context-clear',
52
+ time: Date.now(),
53
+ })
54
+ saveSessions(sessions)
55
+ return NextResponse.json({ ok: true })
10
56
  }
11
57
 
12
58
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
@@ -25,3 +71,25 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
25
71
  saveSessions(sessions)
26
72
  return NextResponse.json(session.messages[messageIndex])
27
73
  }
74
+
75
+ export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
76
+ const { id } = await params
77
+ const body = await req.json() as { messageIndex: number }
78
+ const sessions = loadSessions()
79
+ const session = sessions[id]
80
+ if (!session) return notFound()
81
+
82
+ const { messageIndex } = body
83
+ if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= session.messages.length) {
84
+ return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
85
+ }
86
+
87
+ // Only allow deleting context-clear markers (safety guard)
88
+ if (session.messages[messageIndex].kind !== 'context-clear') {
89
+ return NextResponse.json({ error: 'Only context-clear markers can be removed' }, { status: 400 })
90
+ }
91
+
92
+ session.messages.splice(messageIndex, 1)
93
+ saveSessions(sessions)
94
+ return NextResponse.json({ ok: true })
95
+ }
@@ -69,6 +69,10 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
69
69
  if (updates.heartbeatEnabled !== undefined) sessions[id].heartbeatEnabled = updates.heartbeatEnabled
70
70
  if (updates.heartbeatIntervalSec !== undefined) sessions[id].heartbeatIntervalSec = updates.heartbeatIntervalSec
71
71
  if (updates.pinned !== undefined) sessions[id].pinned = !!updates.pinned
72
+ if (updates.claudeSessionId !== undefined) sessions[id].claudeSessionId = updates.claudeSessionId
73
+ if (updates.codexThreadId !== undefined) sessions[id].codexThreadId = updates.codexThreadId
74
+ if (updates.opencodeSessionId !== undefined) sessions[id].opencodeSessionId = updates.opencodeSessionId
75
+ if (updates.delegateResumeIds !== undefined) sessions[id].delegateResumeIds = updates.delegateResumeIds
72
76
  if (!Array.isArray(sessions[id].messages)) sessions[id].messages = []
73
77
  ensureMainSessionFlag(sessions[id])
74
78