@swarmclawai/swarmclaw 1.2.6 → 1.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -17
- package/next.config.ts +1 -0
- package/package.json +3 -2
- package/scripts/easy-setup.mjs +1 -1
- package/scripts/postinstall.mjs +1 -1
- package/skills/swarmclaw.md +115 -0
- package/skills/tools/browser.md +131 -0
- package/skills/tools/execute.md +98 -0
- package/skills/tools/files.md +98 -0
- package/skills/tools/memory.md +104 -0
- package/skills/tools/platform.md +144 -0
- package/skills/tools/skills.md +83 -0
- package/src/app/api/chats/[id]/messages/route.ts +23 -19
- package/src/app/api/chats/messages-route.test.ts +105 -51
- package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
- package/src/app/api/openclaw/deploy/route.ts +2 -0
- package/src/app/api/setup/doctor/route.ts +4 -4
- package/src/components/agents/agent-chat-list.tsx +23 -1
- package/src/components/agents/inspector-panel.tsx +165 -48
- package/src/components/chat/chat-area.tsx +38 -9
- package/src/components/chat/message-list.tsx +33 -19
- package/src/components/gateways/gateway-sheet.tsx +5 -2
- package/src/lib/agent-execute-defaults.test.ts +24 -0
- package/src/lib/agent-execute-defaults.ts +62 -0
- package/src/lib/chat/queued-message-queue.test.ts +134 -1
- package/src/lib/chat/queued-message-queue.ts +77 -2
- package/src/lib/server/agents/agent-service.ts +5 -0
- package/src/lib/server/builtin-extensions.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +1 -0
- package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -2
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +79 -42
- package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
- package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
- package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
- package/src/lib/server/chat-execution/message-classifier.ts +11 -1
- package/src/lib/server/chat-execution/prompt-builder.test.ts +28 -0
- package/src/lib/server/chat-execution/prompt-builder.ts +14 -1
- package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
- package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +6 -4
- package/src/lib/server/chat-execution/stream-agent-chat.ts +45 -16
- package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
- package/src/lib/server/connectors/discord.ts +2 -2
- package/src/lib/server/connectors/matrix.ts +3 -2
- package/src/lib/server/connectors/signal.ts +5 -4
- package/src/lib/server/connectors/slack.ts +10 -9
- package/src/lib/server/connectors/teams.ts +3 -2
- package/src/lib/server/connectors/telegram.ts +4 -4
- package/src/lib/server/connectors/whatsapp.ts +2 -2
- package/src/lib/server/daemon/controller.ts +7 -0
- package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
- package/src/lib/server/messages/message-repository.test.ts +70 -0
- package/src/lib/server/messages/message-repository.ts +11 -6
- package/src/lib/server/openclaw/deploy.ts +32 -2
- package/src/lib/server/plugins-advanced.test.ts +1 -2
- package/src/lib/server/provider-health.ts +1 -1
- package/src/lib/server/runtime/process-manager.ts +13 -9
- package/src/lib/server/runtime/session-run-manager/queries.ts +15 -0
- package/src/lib/server/runtime/session-run-manager.test.ts +58 -0
- package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
- package/src/lib/server/sandbox/session-runtime.ts +40 -28
- package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
- package/src/lib/server/session-tools/context.ts +1 -1
- package/src/lib/server/session-tools/credential-env.ts +109 -0
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/edit_file.ts +3 -2
- package/src/lib/server/session-tools/execute.test.ts +58 -0
- package/src/lib/server/session-tools/execute.ts +334 -0
- package/src/lib/server/session-tools/files-tool.ts +635 -0
- package/src/lib/server/session-tools/index.ts +14 -4
- package/src/lib/server/session-tools/memory-tool.ts +242 -0
- package/src/lib/server/session-tools/memory.ts +1 -1
- package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
- package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
- package/src/lib/server/session-tools/platform-tool.ts +617 -0
- package/src/lib/server/session-tools/session-info.ts +3 -2
- package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
- package/src/lib/server/session-tools/shell.ts +7 -122
- package/src/lib/server/session-tools/skills-tool.ts +396 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/storage-normalization.ts +2 -0
- package/src/lib/server/tool-aliases.ts +2 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +9 -2
- package/src/lib/server/tool-capability-policy.test.ts +2 -1
- package/src/lib/server/tool-capability-policy.ts +60 -33
- package/src/lib/server/tool-planning.ts +11 -0
- package/src/lib/setup-defaults.ts +5 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.test.ts +16 -0
- package/src/lib/validation/schemas.ts +16 -0
- package/src/stores/use-chat-store.test.ts +231 -0
- package/src/stores/use-chat-store.ts +62 -13
- package/src/types/agent.ts +348 -0
- package/src/types/app-settings.ts +175 -0
- package/src/types/approval.ts +27 -0
- package/src/types/connector.ts +187 -0
- package/src/types/extension.ts +386 -0
- package/src/types/index.ts +16 -3555
- package/src/types/message.ts +57 -0
- package/src/types/misc.ts +739 -0
- package/src/types/mission.ts +185 -0
- package/src/types/protocol.ts +422 -0
- package/src/types/provider.ts +52 -0
- package/src/types/run.ts +183 -0
- package/src/types/schedule.ts +59 -0
- package/src/types/session.ts +265 -0
- package/src/types/skill.ts +157 -0
- package/src/types/task.ts +140 -0
- package/src/types/working-state.ts +211 -0
- package/src/views/settings/section-heartbeat.tsx +2 -2
- package/src/lib/server/session-tools/sandbox.ts +0 -281
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Platform Tool
|
|
2
|
+
|
|
3
|
+
Interact with the SwarmClaw platform: manage tasks, communicate with humans and other agents, access projects, and participate in chatrooms.
|
|
4
|
+
|
|
5
|
+
## Action Groups
|
|
6
|
+
|
|
7
|
+
### Tasks
|
|
8
|
+
|
|
9
|
+
| Action | Description | Key Parameters |
|
|
10
|
+
|--------|-------------|----------------|
|
|
11
|
+
| `tasks.create` | Create a new task | `title`, `description`, `priority` |
|
|
12
|
+
| `tasks.update` | Update task fields | `id`, fields to update |
|
|
13
|
+
| `tasks.list` | List tasks with filters | `status`, `assignee`, `priority` |
|
|
14
|
+
| `tasks.get` | Get task details | `id` |
|
|
15
|
+
| `tasks.complete` | Mark task as done | `id`, `result` (optional summary) |
|
|
16
|
+
|
|
17
|
+
#### Create a task
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"action": "tasks.create",
|
|
22
|
+
"title": "Review PR #55",
|
|
23
|
+
"description": "Check for type safety issues and test coverage",
|
|
24
|
+
"priority": "high"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
#### List open tasks
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{ "action": "tasks.list", "status": "open" }
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Communication
|
|
35
|
+
|
|
36
|
+
| Action | Description | Key Parameters |
|
|
37
|
+
|--------|-------------|----------------|
|
|
38
|
+
| `communicate.ask_human` | Block and wait for human input | `question`, `context` |
|
|
39
|
+
| `communicate.send_message` | Send to a connector channel | `connector`, `channel`, `message` |
|
|
40
|
+
| `communicate.delegate` | Route work to another agent | `agentId`, `message` |
|
|
41
|
+
| `communicate.spawn` | Create a subagent for parallel work | `agentId`, `message`, `mode` |
|
|
42
|
+
|
|
43
|
+
#### Ask human (blocks execution)
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"action": "communicate.ask_human",
|
|
48
|
+
"question": "Should I proceed with the database migration?",
|
|
49
|
+
"context": "This will add 3 new columns to the users table and backfill existing rows."
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Important:** `ask_human` blocks the agent loop until the human responds. Use it when you genuinely need input before continuing. Do not use it for status updates (use `send_message` instead).
|
|
54
|
+
|
|
55
|
+
#### Send a message to Discord/Slack/Telegram
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"action": "communicate.send_message",
|
|
60
|
+
"connector": "discord",
|
|
61
|
+
"channel": "#general",
|
|
62
|
+
"message": "Deployment complete. All tests passing."
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Delegate to another agent
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"action": "communicate.delegate",
|
|
71
|
+
"agentId": "agent_research",
|
|
72
|
+
"message": "Find the top 5 competitors in the AI coding assistant space and summarize their pricing."
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Spawn a subagent
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"action": "communicate.spawn",
|
|
81
|
+
"agentId": "agent_coder",
|
|
82
|
+
"message": "Implement the dark mode toggle component",
|
|
83
|
+
"mode": "run"
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Modes:
|
|
88
|
+
- `run` -- fire and forget, subagent runs independently
|
|
89
|
+
- `session` -- creates a persistent session you can check on later
|
|
90
|
+
|
|
91
|
+
### Projects
|
|
92
|
+
|
|
93
|
+
| Action | Description | Key Parameters |
|
|
94
|
+
|--------|-------------|----------------|
|
|
95
|
+
| `projects.list` | List all projects | (none) |
|
|
96
|
+
| `projects.get` | Get project details | `id` |
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{ "action": "projects.list" }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Chatrooms
|
|
103
|
+
|
|
104
|
+
| Action | Description | Key Parameters |
|
|
105
|
+
|--------|-------------|----------------|
|
|
106
|
+
| `chatrooms.send` | Send a message to a chatroom | `chatroomId`, `message` |
|
|
107
|
+
| `chatrooms.list` | List available chatrooms | (none) |
|
|
108
|
+
| `chatrooms.history` | Get recent messages | `chatroomId`, `limit` |
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"action": "chatrooms.send",
|
|
113
|
+
"chatroomId": "room_design",
|
|
114
|
+
"message": "The mockups are ready for review."
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Agents
|
|
119
|
+
|
|
120
|
+
| Action | Description | Key Parameters |
|
|
121
|
+
|--------|-------------|----------------|
|
|
122
|
+
| `agents.list` | List all agents | (none) |
|
|
123
|
+
| `agents.get` | Get agent details | `id` |
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{ "action": "agents.list" }
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Communication Decision Guide
|
|
130
|
+
|
|
131
|
+
| Situation | Action |
|
|
132
|
+
|-----------|--------|
|
|
133
|
+
| Need human approval before proceeding | `communicate.ask_human` |
|
|
134
|
+
| Sharing a status update with the team | `communicate.send_message` |
|
|
135
|
+
| Task is outside your expertise | `communicate.delegate` |
|
|
136
|
+
| Task can run in parallel with your work | `communicate.spawn` |
|
|
137
|
+
| Collaborating with agents in a shared space | `chatrooms.send` |
|
|
138
|
+
|
|
139
|
+
## Tips
|
|
140
|
+
|
|
141
|
+
- Use `ask_human` sparingly. Only block when you truly cannot proceed without input.
|
|
142
|
+
- When delegating, provide enough context that the target agent can work independently.
|
|
143
|
+
- Check `tasks.list` before creating duplicates.
|
|
144
|
+
- Use `agents.list` to discover available agents and their capabilities before delegating.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# Skills Tool
|
|
2
|
+
|
|
3
|
+
Discover and load skill files that teach you how to use tools, APIs, and workflows.
|
|
4
|
+
|
|
5
|
+
## Actions
|
|
6
|
+
|
|
7
|
+
| Action | Description | Key Parameters |
|
|
8
|
+
|--------|-------------|----------------|
|
|
9
|
+
| `list` | Browse all available skills | (none) |
|
|
10
|
+
| `read` | Load a skill by name | `name` |
|
|
11
|
+
| `search` | Find skills by keyword | `query` |
|
|
12
|
+
|
|
13
|
+
## List Available Skills
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{ "action": "list" }
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Returns all discoverable skill files with names and short descriptions.
|
|
20
|
+
|
|
21
|
+
## Read a Skill
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{ "action": "read", "name": "tools/files" }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Loads the full content of a skill file. Skill names use path-style notation:
|
|
28
|
+
|
|
29
|
+
- `tools/files` -- the files tool documentation
|
|
30
|
+
- `tools/memory` -- the memory tool documentation
|
|
31
|
+
- `swarmclaw` -- the platform overview skill
|
|
32
|
+
- `github` -- GitHub CLI operations
|
|
33
|
+
|
|
34
|
+
Name matching is flexible: partial matches work if unambiguous.
|
|
35
|
+
|
|
36
|
+
## Search Skills
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{ "action": "search", "query": "browser screenshot" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Searches skill file names, descriptions, and content for keyword matches. Returns ranked results.
|
|
43
|
+
|
|
44
|
+
## Skill File Locations
|
|
45
|
+
|
|
46
|
+
| Directory | Source | Description |
|
|
47
|
+
|-----------|--------|-------------|
|
|
48
|
+
| `skills/` | Built-in | Shipped with SwarmClaw, checked into the repo |
|
|
49
|
+
| `data/skills/` | User-created | Added at runtime, not version-controlled |
|
|
50
|
+
|
|
51
|
+
## Skill File Format
|
|
52
|
+
|
|
53
|
+
Skills are markdown files (`.md`). They can be:
|
|
54
|
+
|
|
55
|
+
- **Flat files**: `skills/swarmclaw.md`
|
|
56
|
+
- **Directory-based**: `skills/github/SKILL.md`
|
|
57
|
+
- **Nested**: `skills/tools/files.md`
|
|
58
|
+
|
|
59
|
+
Optional YAML frontmatter can declare metadata:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
---
|
|
63
|
+
name: my-skill
|
|
64
|
+
description: What this skill teaches
|
|
65
|
+
metadata:
|
|
66
|
+
openclaw:
|
|
67
|
+
requires:
|
|
68
|
+
bins: ["gh"]
|
|
69
|
+
---
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## When to Use Skills
|
|
73
|
+
|
|
74
|
+
- **Before using an unfamiliar tool**: Load its skill to understand parameters, patterns, and best practices.
|
|
75
|
+
- **When stuck on a task**: Search for skills related to what you're trying to accomplish.
|
|
76
|
+
- **At the start of a session**: List skills to understand what documentation is available.
|
|
77
|
+
|
|
78
|
+
## Tips
|
|
79
|
+
|
|
80
|
+
- Skills are read-only reference material. They don't execute anything.
|
|
81
|
+
- Load the relevant tool skill before attempting complex operations with that tool.
|
|
82
|
+
- If a skill doesn't exist for what you need, you can still use the tool directly -- skills are guides, not gates.
|
|
83
|
+
- User-created skills in `data/skills/` take the same format as built-in ones.
|
|
@@ -3,7 +3,13 @@ import { notFound } from '@/lib/server/collection-helpers'
|
|
|
3
3
|
import { materializeStreamingAssistantArtifacts } from '@/lib/chat/chat-streaming-state'
|
|
4
4
|
import { appendSessionNote } from '@/lib/server/session-note'
|
|
5
5
|
import { getSessionRunState } from '@/lib/server/runtime/session-run-manager'
|
|
6
|
-
import { getSession
|
|
6
|
+
import { getSession } from '@/lib/server/sessions/session-repository'
|
|
7
|
+
import {
|
|
8
|
+
appendMessage,
|
|
9
|
+
getMessages,
|
|
10
|
+
replaceAllMessages,
|
|
11
|
+
replaceMessageAt,
|
|
12
|
+
} from '@/lib/server/messages/message-repository'
|
|
7
13
|
import type { Message } from '@/types'
|
|
8
14
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
9
15
|
|
|
@@ -11,22 +17,21 @@ export async function GET(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
11
17
|
const { id } = await params
|
|
12
18
|
const session = getSession(id)
|
|
13
19
|
if (!session) return notFound()
|
|
14
|
-
session.messages = Array.isArray(session.messages) ? session.messages : []
|
|
15
20
|
|
|
16
21
|
// Use persisted fields plus the run ledger. Process-local execution state is
|
|
17
22
|
// intentionally excluded here so stale registry entries do not block cleanup.
|
|
18
23
|
const sessionClaimsActive = session.active === true
|
|
19
24
|
|| (typeof session.currentRunId === 'string' && session.currentRunId.trim().length > 0)
|
|
20
25
|
|| !!getSessionRunState(id).runningRunId
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
const allMessages = getMessages(id)
|
|
27
|
+
if (!sessionClaimsActive && materializeStreamingAssistantArtifacts(allMessages)) {
|
|
28
|
+
replaceAllMessages(id, allMessages)
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
const url = new URL(req.url)
|
|
26
32
|
const limitParam = url.searchParams.get('limit')
|
|
27
33
|
const beforeParam = url.searchParams.get('before')
|
|
28
34
|
|
|
29
|
-
const allMessages = Array.isArray(session.messages) ? session.messages : []
|
|
30
35
|
const total = allMessages.length
|
|
31
36
|
|
|
32
37
|
// If no limit param, return all messages (backward compatible)
|
|
@@ -64,13 +69,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
64
69
|
const session = getSession(id)
|
|
65
70
|
if (!session) return notFound()
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
appendMessage(id, {
|
|
68
73
|
role: 'user',
|
|
69
74
|
text: '',
|
|
70
75
|
kind: 'context-clear',
|
|
71
76
|
time: Date.now(),
|
|
72
77
|
})
|
|
73
|
-
saveSession(id, session)
|
|
74
78
|
return NextResponse.json({ ok: true })
|
|
75
79
|
}
|
|
76
80
|
|
|
@@ -96,37 +100,37 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
96
100
|
const { id } = await params
|
|
97
101
|
const { data: body, error } = await safeParseBody<{ messageIndex: number; bookmarked: boolean }>(req)
|
|
98
102
|
if (error) return error
|
|
99
|
-
|
|
100
|
-
if (!session) return notFound()
|
|
103
|
+
if (!getSession(id)) return notFound()
|
|
101
104
|
|
|
102
105
|
const { messageIndex, bookmarked } = body
|
|
103
|
-
|
|
106
|
+
const messages = getMessages(id)
|
|
107
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= messages.length) {
|
|
104
108
|
return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return NextResponse.json(
|
|
111
|
+
const updated = { ...messages[messageIndex], bookmarked }
|
|
112
|
+
replaceMessageAt(id, messageIndex, updated)
|
|
113
|
+
return NextResponse.json(updated)
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
113
117
|
const { id } = await params
|
|
114
118
|
const { data: body, error } = await safeParseBody<{ messageIndex: number }>(req)
|
|
115
119
|
if (error) return error
|
|
116
|
-
|
|
117
|
-
if (!session) return notFound()
|
|
120
|
+
if (!getSession(id)) return notFound()
|
|
118
121
|
|
|
119
122
|
const { messageIndex } = body
|
|
120
|
-
|
|
123
|
+
const messages = getMessages(id)
|
|
124
|
+
if (typeof messageIndex !== 'number' || messageIndex < 0 || messageIndex >= messages.length) {
|
|
121
125
|
return NextResponse.json({ error: 'Invalid message index' }, { status: 400 })
|
|
122
126
|
}
|
|
123
127
|
|
|
124
128
|
// Only allow deleting context-clear markers (safety guard)
|
|
125
|
-
if (
|
|
129
|
+
if (messages[messageIndex].kind !== 'context-clear') {
|
|
126
130
|
return NextResponse.json({ error: 'Only context-clear markers can be removed' }, { status: 400 })
|
|
127
131
|
}
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
messages.splice(messageIndex, 1)
|
|
134
|
+
replaceAllMessages(id, messages)
|
|
131
135
|
return NextResponse.json({ ok: true })
|
|
132
136
|
}
|
|
@@ -3,65 +3,119 @@ import test from 'node:test'
|
|
|
3
3
|
|
|
4
4
|
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
5
|
|
|
6
|
-
test('
|
|
6
|
+
test('messages route serves and mutates repo-backed transcript history', () => {
|
|
7
7
|
const output = runWithTempDataDir<{
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
fullCount: number
|
|
9
|
+
paginatedTexts: string[]
|
|
10
|
+
paginatedStartIndex: number
|
|
11
|
+
paginatedTotal: number
|
|
12
|
+
bookmarkPersisted: boolean
|
|
13
|
+
contextClearCountAfterPost: number
|
|
14
|
+
finalKinds: Array<string | null>
|
|
15
|
+
finalBookmarked: boolean
|
|
16
|
+
blobMessageCount: number
|
|
17
|
+
}>(`
|
|
18
|
+
const storageMod = await import('./src/lib/server/storage')
|
|
19
|
+
const repoMod = await import('@/lib/server/messages/message-repository')
|
|
20
|
+
const routeMod = await import('./src/app/api/chats/[id]/messages/route')
|
|
21
|
+
const storage = storageMod.default || storageMod
|
|
22
|
+
const repo = repoMod.default || repoMod
|
|
23
|
+
const route = routeMod.default || routeMod
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
},
|
|
39
|
-
],
|
|
25
|
+
const now = Date.now()
|
|
26
|
+
storage.saveSessions({
|
|
27
|
+
sess_1: {
|
|
28
|
+
id: 'sess_1',
|
|
29
|
+
name: 'Repo-backed session',
|
|
30
|
+
cwd: process.env.WORKSPACE_DIR,
|
|
31
|
+
user: 'tester',
|
|
32
|
+
provider: 'openai',
|
|
33
|
+
model: 'gpt-5',
|
|
34
|
+
claudeSessionId: null,
|
|
35
|
+
codexThreadId: null,
|
|
36
|
+
opencodeSessionId: null,
|
|
37
|
+
delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
|
|
38
|
+
messages: [],
|
|
39
|
+
createdAt: now,
|
|
40
|
+
lastActiveAt: now,
|
|
41
|
+
},
|
|
40
42
|
})
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
repo.appendMessage('sess_1', { role: 'user', text: 'hello', time: now })
|
|
45
|
+
repo.appendMessage('sess_1', { role: 'user', text: '', kind: 'context-clear', time: now + 1 })
|
|
46
|
+
repo.appendMessage('sess_1', { role: 'assistant', text: 'welcome back', time: now + 2 })
|
|
47
|
+
storage.patchSession('sess_1', (current) => {
|
|
48
|
+
if (!current) return null
|
|
49
|
+
current.messages = [{ role: 'assistant', text: 'stale blob only', time: now - 10 }]
|
|
50
|
+
return current
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const fullResponse = await route.GET(
|
|
54
|
+
new Request('http://local/api/chats/sess_1/messages'),
|
|
55
|
+
{ params: Promise.resolve({ id: 'sess_1' }) },
|
|
56
|
+
)
|
|
57
|
+
const fullMessages = await fullResponse.json()
|
|
43
58
|
|
|
44
|
-
const
|
|
45
|
-
new Request('http://local/api/chats/
|
|
46
|
-
{ params: Promise.resolve({ id: '
|
|
59
|
+
const paginatedResponse = await route.GET(
|
|
60
|
+
new Request('http://local/api/chats/sess_1/messages?limit=2'),
|
|
61
|
+
{ params: Promise.resolve({ id: 'sess_1' }) },
|
|
47
62
|
)
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
63
|
+
const paginated = await paginatedResponse.json()
|
|
64
|
+
|
|
65
|
+
const bookmarkResponse = await route.PUT(
|
|
66
|
+
new Request('http://local/api/chats/sess_1/messages', {
|
|
67
|
+
method: 'PUT',
|
|
68
|
+
headers: { 'content-type': 'application/json' },
|
|
69
|
+
body: JSON.stringify({ messageIndex: 2, bookmarked: true }),
|
|
70
|
+
}),
|
|
71
|
+
{ params: Promise.resolve({ id: 'sess_1' }) },
|
|
72
|
+
)
|
|
73
|
+
const bookmarked = await bookmarkResponse.json()
|
|
74
|
+
|
|
75
|
+
await route.POST(
|
|
76
|
+
new Request('http://local/api/chats/sess_1/messages', {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'content-type': 'application/json' },
|
|
79
|
+
body: JSON.stringify({ kind: 'context-clear' }),
|
|
80
|
+
}),
|
|
81
|
+
{ params: Promise.resolve({ id: 'sess_1' }) },
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const afterPost = repo.getMessages('sess_1')
|
|
85
|
+
const contextClearCountAfterPost = afterPost.filter((message) => message.kind === 'context-clear').length
|
|
86
|
+
|
|
87
|
+
await route.DELETE(
|
|
88
|
+
new Request('http://local/api/chats/sess_1/messages', {
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
headers: { 'content-type': 'application/json' },
|
|
91
|
+
body: JSON.stringify({ messageIndex: 1 }),
|
|
92
|
+
}),
|
|
93
|
+
{ params: Promise.resolve({ id: 'sess_1' }) },
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
const finalMessages = repo.getMessages('sess_1')
|
|
97
|
+
const sessions = storage.loadSessions()
|
|
52
98
|
|
|
53
99
|
console.log(JSON.stringify({
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
100
|
+
fullCount: fullMessages.length,
|
|
101
|
+
paginatedTexts: paginated.messages.map((message) => message.text),
|
|
102
|
+
paginatedStartIndex: paginated.startIndex,
|
|
103
|
+
paginatedTotal: paginated.total,
|
|
104
|
+
bookmarkPersisted: bookmarked.bookmarked === true,
|
|
105
|
+
contextClearCountAfterPost,
|
|
106
|
+
finalKinds: finalMessages.map((message) => message.kind || null),
|
|
107
|
+
finalBookmarked: finalMessages[1]?.bookmarked === true,
|
|
108
|
+
blobMessageCount: Array.isArray(sessions.sess_1.messages) ? sessions.sess_1.messages.length : -1,
|
|
59
109
|
}))
|
|
60
|
-
`, { prefix: 'swarmclaw-
|
|
110
|
+
`, { prefix: 'swarmclaw-messages-route-' })
|
|
61
111
|
|
|
62
|
-
assert.equal(output.
|
|
63
|
-
assert.
|
|
64
|
-
assert.equal(output.
|
|
65
|
-
assert.equal(output.
|
|
66
|
-
assert.equal(output.
|
|
112
|
+
assert.equal(output.fullCount, 3)
|
|
113
|
+
assert.deepEqual(output.paginatedTexts, ['', 'welcome back'])
|
|
114
|
+
assert.equal(output.paginatedStartIndex, 1)
|
|
115
|
+
assert.equal(output.paginatedTotal, 3)
|
|
116
|
+
assert.equal(output.bookmarkPersisted, true)
|
|
117
|
+
assert.equal(output.contextClearCountAfterPost, 2)
|
|
118
|
+
assert.deepEqual(output.finalKinds, [null, null, 'context-clear'])
|
|
119
|
+
assert.equal(output.finalBookmarked, true)
|
|
120
|
+
assert.equal(output.blobMessageCount, 1)
|
|
67
121
|
})
|
|
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import { loadMcpServers } from '@/lib/server/storage'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { connectMcpServer, mcpToolsToLangChain, disconnectMcpServer } from '@/lib/server/mcp-client'
|
|
5
|
+
import { errorMessage } from '@/lib/shared-utils'
|
|
5
6
|
|
|
6
7
|
export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
8
|
const { id } = await params
|
|
@@ -15,9 +16,9 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
|
|
|
15
16
|
const toolNames = tools.map((t: any) => t.name)
|
|
16
17
|
await disconnectMcpServer(client, transport)
|
|
17
18
|
return NextResponse.json({ ok: true, tools: toolNames })
|
|
18
|
-
} catch (err:
|
|
19
|
+
} catch (err: unknown) {
|
|
19
20
|
return NextResponse.json(
|
|
20
|
-
{ ok: false, error: err
|
|
21
|
+
{ ok: false, error: errorMessage(err) || 'Connection failed' },
|
|
21
22
|
{ status: 500 }
|
|
22
23
|
)
|
|
23
24
|
}
|
|
@@ -129,6 +129,7 @@ export async function POST(req: Request) {
|
|
|
129
129
|
locals: result.locals,
|
|
130
130
|
localPrimaryId: result.locals.find((item) => item.isPrimary)?.id || result.local.id,
|
|
131
131
|
token: result.token,
|
|
132
|
+
gatewayProfileId: result.gatewayProfileId,
|
|
132
133
|
})
|
|
133
134
|
}
|
|
134
135
|
|
|
@@ -156,6 +157,7 @@ export async function POST(req: Request) {
|
|
|
156
157
|
locals: result.locals,
|
|
157
158
|
localPrimaryId: result.locals.find((item) => item.isPrimary)?.id || result.local.id,
|
|
158
159
|
token: result.token,
|
|
160
|
+
gatewayProfileId: result.gatewayProfileId,
|
|
159
161
|
})
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -217,14 +217,14 @@ export async function GET(req: Request) {
|
|
|
217
217
|
pushCheck(
|
|
218
218
|
checks,
|
|
219
219
|
'docker',
|
|
220
|
-
'Docker (sandbox runtime)',
|
|
220
|
+
'Docker (browser sandbox runtime)',
|
|
221
221
|
docker.available ? 'pass' : 'warn',
|
|
222
222
|
docker.available
|
|
223
|
-
? `Docker ${docker.version || ''} is available for
|
|
224
|
-
: 'Docker is not available. SwarmClaw will
|
|
223
|
+
? `Docker ${docker.version || ''} is available for sandbox browser execution.`.trim()
|
|
224
|
+
: 'Docker is not available. SwarmClaw will use the host Playwright runtime unless Docker Desktop is installed.',
|
|
225
225
|
)
|
|
226
226
|
if (!docker.available) {
|
|
227
|
-
actions.push('Install Docker Desktop if you want
|
|
227
|
+
actions.push('Install Docker Desktop if you want Playwright browser sessions to use the sandbox browser runtime.')
|
|
228
228
|
}
|
|
229
229
|
|
|
230
230
|
const gitRootCheck = run('git', ['rev-parse', '--is-inside-work-tree'], 4_000)
|
|
@@ -193,9 +193,11 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
193
193
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
194
194
|
}, [filteredAgents.map((a) => a.id).join(',')])
|
|
195
195
|
|
|
196
|
+
const [enableAgentTarget, setEnableAgentTarget] = useState<Agent | null>(null)
|
|
197
|
+
|
|
196
198
|
const handleSelect = async (agent: Agent) => {
|
|
197
199
|
if (agent.disabled === true && !agent.threadSessionId) {
|
|
198
|
-
|
|
200
|
+
setEnableAgentTarget(agent)
|
|
199
201
|
return
|
|
200
202
|
}
|
|
201
203
|
navigateTo('agents', agent.id)
|
|
@@ -547,6 +549,26 @@ export function AgentChatList({ inSidebar, onSelect }: Props) {
|
|
|
547
549
|
onConfirm={() => { setConfirmBulkDelete(false); handleBulkDelete() }}
|
|
548
550
|
onCancel={() => setConfirmBulkDelete(false)}
|
|
549
551
|
/>
|
|
552
|
+
<ConfirmDialog
|
|
553
|
+
open={!!enableAgentTarget}
|
|
554
|
+
title={`Enable ${enableAgentTarget?.name ?? 'Agent'}?`}
|
|
555
|
+
message={`${enableAgentTarget?.name ?? 'This agent'} is currently disabled. Enable it to start a new chat.`}
|
|
556
|
+
confirmLabel="Enable"
|
|
557
|
+
onConfirm={async () => {
|
|
558
|
+
if (!enableAgentTarget) return
|
|
559
|
+
try {
|
|
560
|
+
await api('PUT', `/agents/${enableAgentTarget.id}`, { disabled: false })
|
|
561
|
+
await loadAgents()
|
|
562
|
+
const agent = enableAgentTarget
|
|
563
|
+
setEnableAgentTarget(null)
|
|
564
|
+
handleSelect(agent)
|
|
565
|
+
} catch {
|
|
566
|
+
toast.error('Failed to enable agent')
|
|
567
|
+
setEnableAgentTarget(null)
|
|
568
|
+
}
|
|
569
|
+
}}
|
|
570
|
+
onCancel={() => setEnableAgentTarget(null)}
|
|
571
|
+
/>
|
|
550
572
|
</div>
|
|
551
573
|
)
|
|
552
574
|
}
|