@swarmclawai/swarmclaw 0.3.0 → 0.4.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 +20 -11
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +2 -0
- package/package.json +3 -1
- package/src/app/api/agents/[id]/route.ts +3 -0
- package/src/app/api/agents/[id]/thread/route.ts +2 -1
- package/src/app/api/agents/route.ts +5 -1
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/connectors/[id]/route.ts +4 -0
- package/src/app/api/connectors/route.ts +6 -1
- package/src/app/api/credentials/route.ts +3 -1
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/mcp-servers/route.ts +3 -1
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/providers/[id]/route.ts +3 -0
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +5 -1
- package/src/app/api/schedules/[id]/route.ts +3 -0
- package/src/app/api/schedules/route.ts +6 -1
- package/src/app/api/secrets/route.ts +3 -1
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/route.ts +9 -2
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/route.ts +3 -1
- package/src/app/api/tasks/[id]/approve/route.ts +73 -0
- package/src/app/api/tasks/[id]/route.ts +3 -0
- package/src/app/api/tasks/route.ts +3 -0
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +2 -1
- package/src/app/api/webhooks/route.ts +3 -1
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +8 -2
- package/src/cli/index.js +1 -9
- package/src/cli/index.ts +51 -1
- package/src/cli/spec.js +0 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-sheet.tsx +63 -80
- package/src/components/chat/chat-area.tsx +44 -30
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +41 -3
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/connectors/connector-list.tsx +3 -8
- package/src/components/connectors/connector-sheet.tsx +24 -29
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +92 -71
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +6 -3
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +8 -9
- package/src/components/shared/connector-platform-icon.tsx +22 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +7 -39
- package/src/components/shared/settings/section-orchestrator.tsx +8 -9
- package/src/components/skills/skill-list.tsx +260 -34
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +3 -5
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/providers/anthropic.ts +1 -1
- package/src/lib/providers/index.ts +2 -0
- package/src/lib/providers/ollama.ts +1 -1
- package/src/lib/providers/openai.ts +33 -12
- package/src/lib/server/chat-execution.ts +19 -4
- package/src/lib/server/connectors/manager.ts +9 -3
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +61 -2
- package/src/lib/server/orchestrator-lg.ts +394 -13
- package/src/lib/server/orchestrator.ts +25 -5
- package/src/lib/server/queue.ts +17 -3
- package/src/lib/server/session-run-manager.ts +6 -1
- package/src/lib/server/session-tools/delegate.ts +2 -2
- package/src/lib/server/session-tools/index.ts +2 -0
- package/src/lib/server/session-tools/sandbox.ts +164 -0
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +24 -7
- package/src/lib/server/stream-agent-chat.ts +77 -22
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +42 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/ws-client.ts +124 -0
- package/src/stores/use-chat-store.ts +33 -13
- package/src/types/index.ts +8 -1
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSchedules, saveSchedules, deleteSchedule } from '@/lib/server/storage'
|
|
3
3
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
4
5
|
|
|
5
6
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
7
|
const { id } = await params
|
|
@@ -16,6 +17,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
16
17
|
taskPrompt: schedules[id].taskPrompt,
|
|
17
18
|
})
|
|
18
19
|
saveSchedules(schedules)
|
|
20
|
+
notify('schedules')
|
|
19
21
|
return NextResponse.json(schedules[id])
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -24,5 +26,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
24
26
|
const schedules = loadSchedules()
|
|
25
27
|
if (!schedules[id]) return new NextResponse(null, { status: 404 })
|
|
26
28
|
deleteSchedule(id)
|
|
29
|
+
notify('schedules')
|
|
27
30
|
return NextResponse.json('ok')
|
|
28
31
|
}
|
|
@@ -3,8 +3,11 @@ import crypto from 'crypto'
|
|
|
3
3
|
import { loadSchedules, saveSchedules } from '@/lib/server/storage'
|
|
4
4
|
import { resolveScheduleName } from '@/lib/schedule-name'
|
|
5
5
|
import { findDuplicateSchedule } from '@/lib/schedule-dedupe'
|
|
6
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
|
|
10
|
+
export async function GET(_req: Request) {
|
|
8
11
|
return NextResponse.json(loadSchedules())
|
|
9
12
|
}
|
|
10
13
|
|
|
@@ -43,6 +46,7 @@ export async function POST(req: Request) {
|
|
|
43
46
|
mutableDuplicate.updatedAt = now
|
|
44
47
|
if (duplicateId) schedules[duplicateId] = duplicate
|
|
45
48
|
saveSchedules(schedules)
|
|
49
|
+
notify('schedules')
|
|
46
50
|
}
|
|
47
51
|
return NextResponse.json(duplicate)
|
|
48
52
|
}
|
|
@@ -74,5 +78,6 @@ export async function POST(req: Request) {
|
|
|
74
78
|
createdAt: now,
|
|
75
79
|
}
|
|
76
80
|
saveSchedules(schedules)
|
|
81
|
+
notify('schedules')
|
|
77
82
|
return NextResponse.json(schedules[id])
|
|
78
83
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import crypto from 'crypto'
|
|
3
3
|
import { loadSecrets, saveSecrets, encryptKey } from '@/lib/server/storage'
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
export async function GET(_req: Request) {
|
|
6
8
|
// Return secrets WITHOUT the encrypted values (just metadata)
|
|
7
9
|
const secrets = loadSecrets()
|
|
8
10
|
const safe = Object.fromEntries(
|
|
@@ -14,11 +14,13 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
14
14
|
const message = typeof body.message === 'string' ? body.message : ''
|
|
15
15
|
const imagePath = typeof body.imagePath === 'string' ? body.imagePath : undefined
|
|
16
16
|
const imageUrl = typeof body.imageUrl === 'string' ? body.imageUrl : undefined
|
|
17
|
+
const attachedFiles = Array.isArray(body.attachedFiles) ? body.attachedFiles.filter((f: unknown) => typeof f === 'string') as string[] : undefined
|
|
17
18
|
const internal = body.internal === true
|
|
18
19
|
const queueMode = normalizeQueueMode(body.queueMode, internal)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const hasFiles = !!(imagePath || imageUrl || (attachedFiles && attachedFiles.length > 0))
|
|
22
|
+
if (!message.trim() && !hasFiles) {
|
|
23
|
+
return NextResponse.json({ error: 'message or file is required' }, { status: 400 })
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
const encoder = new TextEncoder()
|
|
@@ -39,6 +41,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
39
41
|
message,
|
|
40
42
|
imagePath,
|
|
41
43
|
imageUrl,
|
|
44
|
+
attachedFiles,
|
|
42
45
|
internal,
|
|
43
46
|
source: internal ? 'heartbeat' : 'chat',
|
|
44
47
|
mode: queueMode,
|
|
@@ -3,10 +3,14 @@ import crypto from 'crypto'
|
|
|
3
3
|
import os from 'os'
|
|
4
4
|
import path from 'path'
|
|
5
5
|
import { loadSessions, saveSessions, deleteSession, active, loadAgents } from '@/lib/server/storage'
|
|
6
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
7
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
6
8
|
import { getSessionRunState } from '@/lib/server/session-run-manager'
|
|
7
9
|
import { normalizeProviderEndpoint } from '@/lib/openclaw-endpoint'
|
|
10
|
+
export const dynamic = 'force-dynamic'
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
|
|
13
|
+
export async function GET(_req: Request) {
|
|
10
14
|
const sessions = loadSessions()
|
|
11
15
|
for (const id of Object.keys(sessions)) {
|
|
12
16
|
const run = getSessionRunState(id)
|
|
@@ -31,6 +35,7 @@ export async function DELETE(req: Request) {
|
|
|
31
35
|
}
|
|
32
36
|
deleteSession(id)
|
|
33
37
|
}
|
|
38
|
+
notify('sessions')
|
|
34
39
|
return NextResponse.json({ deleted: ids.length })
|
|
35
40
|
}
|
|
36
41
|
|
|
@@ -38,7 +43,8 @@ export async function POST(req: Request) {
|
|
|
38
43
|
const body = await req.json()
|
|
39
44
|
let cwd = (body.cwd || '').trim()
|
|
40
45
|
if (cwd.startsWith('~/')) cwd = path.join(os.homedir(), cwd.slice(2))
|
|
41
|
-
else if (cwd === '~'
|
|
46
|
+
else if (cwd === '~') cwd = os.homedir()
|
|
47
|
+
else if (!cwd) cwd = WORKSPACE_DIR
|
|
42
48
|
|
|
43
49
|
const id = body.id || crypto.randomBytes(4).toString('hex')
|
|
44
50
|
const sessions = loadSessions()
|
|
@@ -81,5 +87,6 @@ export async function POST(req: Request) {
|
|
|
81
87
|
heartbeatIntervalSec: body.heartbeatIntervalSec ?? null,
|
|
82
88
|
}
|
|
83
89
|
saveSessions(sessions)
|
|
90
|
+
notify('sessions')
|
|
84
91
|
return NextResponse.json(sessions[id])
|
|
85
92
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSettings, saveSettings } from '@/lib/server/storage'
|
|
3
|
+
export const dynamic = 'force-dynamic'
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
const MEMORY_DEPTH_MIN = 0
|
|
5
7
|
const MEMORY_DEPTH_MAX = 12
|
|
@@ -18,7 +20,7 @@ function parseIntSetting(value: unknown, fallback: number, min: number, max: num
|
|
|
18
20
|
return Math.max(min, Math.min(max, Math.trunc(parsed)))
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
export async function GET() {
|
|
23
|
+
export async function GET(_req: Request) {
|
|
22
24
|
return NextResponse.json(loadSettings())
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -165,6 +165,7 @@ export async function GET(req: Request) {
|
|
|
165
165
|
{ id: 'claude-cli', label: 'Claude Code CLI', command: 'claude' },
|
|
166
166
|
{ id: 'codex-cli', label: 'OpenAI Codex CLI', command: 'codex' },
|
|
167
167
|
{ id: 'opencode-cli', label: 'OpenCode CLI', command: 'opencode' },
|
|
168
|
+
{ id: 'deno', label: 'Deno (sandbox runtime)', command: 'deno' },
|
|
168
169
|
]
|
|
169
170
|
|
|
170
171
|
for (const binary of optionalBinaries) {
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { getDeviceId } from '@/lib/providers/openclaw'
|
|
3
|
+
export const dynamic = 'force-dynamic'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
export async function GET(_req: Request) {
|
|
5
7
|
try {
|
|
6
8
|
const deviceId = getDeviceId()
|
|
7
9
|
return NextResponse.json({ deviceId })
|
|
@@ -2,8 +2,10 @@ import { NextResponse } from 'next/server'
|
|
|
2
2
|
import crypto from 'crypto'
|
|
3
3
|
import { loadSkills, saveSkills } from '@/lib/server/storage'
|
|
4
4
|
import { normalizeSkillPayload } from '@/lib/server/skills-normalize'
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
export async function GET(_req: Request) {
|
|
7
9
|
return NextResponse.json(loadSkills())
|
|
8
10
|
}
|
|
9
11
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadTasks, saveTasks, loadAgents } from '@/lib/server/storage'
|
|
3
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
4
|
+
import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
5
|
+
|
|
6
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
7
|
+
const { id } = await params
|
|
8
|
+
const body = await req.json()
|
|
9
|
+
const approved = body.approved === true
|
|
10
|
+
|
|
11
|
+
const tasks = loadTasks()
|
|
12
|
+
const task = tasks[id]
|
|
13
|
+
if (!task) return new NextResponse(null, { status: 404 })
|
|
14
|
+
if (!task.pendingApproval) {
|
|
15
|
+
return NextResponse.json({ error: 'No pending approval on this task' }, { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const { threadId } = task.pendingApproval
|
|
19
|
+
|
|
20
|
+
if (!approved) {
|
|
21
|
+
// Reject: clear approval, delete checkpoint, fail the task
|
|
22
|
+
task.pendingApproval = null
|
|
23
|
+
task.status = 'failed'
|
|
24
|
+
task.error = 'Tool execution rejected by user'
|
|
25
|
+
task.updatedAt = Date.now()
|
|
26
|
+
saveTasks(tasks)
|
|
27
|
+
await getCheckpointSaver().deleteThread(threadId)
|
|
28
|
+
notify('tasks')
|
|
29
|
+
return NextResponse.json({ status: 'rejected' })
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Approve: clear pendingApproval, resume the graph
|
|
33
|
+
const agents = loadAgents()
|
|
34
|
+
const agent = agents[task.agentId]
|
|
35
|
+
if (!agent) {
|
|
36
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 400 })
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
task.pendingApproval = null
|
|
40
|
+
task.updatedAt = Date.now()
|
|
41
|
+
saveTasks(tasks)
|
|
42
|
+
notify('tasks')
|
|
43
|
+
|
|
44
|
+
// Resume in the background
|
|
45
|
+
const sessionId = task.sessionId || ''
|
|
46
|
+
setImmediate(async () => {
|
|
47
|
+
try {
|
|
48
|
+
const { resumeLangGraphOrchestrator } = await import('@/lib/server/orchestrator-lg')
|
|
49
|
+
const result = await resumeLangGraphOrchestrator(agent, sessionId, threadId)
|
|
50
|
+
const t2 = loadTasks()
|
|
51
|
+
if (t2[id] && !t2[id].pendingApproval) {
|
|
52
|
+
// Only mark completed if not paused again
|
|
53
|
+
if (t2[id].status === 'running') {
|
|
54
|
+
t2[id].result = result
|
|
55
|
+
}
|
|
56
|
+
t2[id].updatedAt = Date.now()
|
|
57
|
+
saveTasks(t2)
|
|
58
|
+
notify('tasks')
|
|
59
|
+
}
|
|
60
|
+
} catch (err: any) {
|
|
61
|
+
console.error(`[approve] Resume failed for task ${id}:`, err.message)
|
|
62
|
+
const t2 = loadTasks()
|
|
63
|
+
if (t2[id]) {
|
|
64
|
+
t2[id].error = err.message || String(err)
|
|
65
|
+
t2[id].updatedAt = Date.now()
|
|
66
|
+
saveTasks(t2)
|
|
67
|
+
notify('tasks')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
return NextResponse.json({ status: 'approved', resuming: true })
|
|
73
|
+
}
|
|
@@ -5,6 +5,7 @@ import { disableSessionHeartbeat, enqueueTask, validateCompletedTasksQueue } fro
|
|
|
5
5
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
6
6
|
import { formatValidationFailure, validateTaskCompletion } from '@/lib/server/task-validation'
|
|
7
7
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
8
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
8
9
|
|
|
9
10
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
11
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -80,6 +81,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
80
81
|
enqueueTask(id)
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
notify('tasks')
|
|
83
85
|
return NextResponse.json(tasks[id])
|
|
84
86
|
}
|
|
85
87
|
|
|
@@ -98,5 +100,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
98
100
|
text: `Task archived: "${tasks[id].title}" (${id}).`,
|
|
99
101
|
})
|
|
100
102
|
|
|
103
|
+
notify('tasks')
|
|
101
104
|
return NextResponse.json(tasks[id])
|
|
102
105
|
}
|
|
@@ -5,6 +5,7 @@ import { enqueueTask, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
|
5
5
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
6
6
|
import { formatValidationFailure, validateTaskCompletion } from '@/lib/server/task-validation'
|
|
7
7
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
8
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
8
9
|
|
|
9
10
|
export async function GET(req: Request) {
|
|
10
11
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -47,6 +48,7 @@ export async function DELETE(req: Request) {
|
|
|
47
48
|
removed++
|
|
48
49
|
}
|
|
49
50
|
}
|
|
51
|
+
notify('tasks')
|
|
50
52
|
return NextResponse.json({ removed, remaining: Object.keys(tasks).length - removed })
|
|
51
53
|
}
|
|
52
54
|
|
|
@@ -111,5 +113,6 @@ export async function POST(req: Request) {
|
|
|
111
113
|
if (tasks[id].status === 'queued') {
|
|
112
114
|
enqueueTask(id)
|
|
113
115
|
}
|
|
116
|
+
notify('tasks')
|
|
114
117
|
return NextResponse.json(tasks[id])
|
|
115
118
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadUsage } from '@/lib/server/storage'
|
|
3
|
+
export const dynamic = 'force-dynamic'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
export async function GET(_req: Request) {
|
|
5
7
|
const usage = loadUsage()
|
|
6
8
|
// Compute summary
|
|
7
9
|
let totalTokens = 0
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { execSync } from 'child_process'
|
|
3
|
+
export const dynamic = 'force-dynamic'
|
|
4
|
+
|
|
3
5
|
|
|
4
6
|
let cachedRemote: {
|
|
5
7
|
sha: string
|
|
@@ -31,7 +33,7 @@ function getHeadStableTag(): string | null {
|
|
|
31
33
|
return tags.find((tag) => RELEASE_TAG_RE.test(tag)) || null
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
export async function GET() {
|
|
36
|
+
export async function GET(_req: Request) {
|
|
35
37
|
try {
|
|
36
38
|
const localSha = run('git rev-parse --short HEAD')
|
|
37
39
|
const localTag = getHeadStableTag()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
3
|
import { loadAgents, loadSessions, loadWebhooks, saveSessions, saveWebhooks, appendWebhookLog } from '@/lib/server/storage'
|
|
4
|
+
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
4
5
|
import { enqueueSessionRun } from '@/lib/server/session-run-manager'
|
|
5
6
|
|
|
6
7
|
function normalizeEvents(value: unknown): string[] {
|
|
@@ -137,7 +138,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
137
138
|
session = {
|
|
138
139
|
id: sessionId,
|
|
139
140
|
name: sessionName,
|
|
140
|
-
cwd:
|
|
141
|
+
cwd: WORKSPACE_DIR,
|
|
141
142
|
user: 'system',
|
|
142
143
|
provider: agent.provider || 'claude-cli',
|
|
143
144
|
model: agent.model || '',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import crypto from 'crypto'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
3
|
import { loadWebhooks, saveWebhooks } from '@/lib/server/storage'
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
4
6
|
|
|
5
7
|
function normalizeEvents(value: unknown): string[] {
|
|
6
8
|
if (!Array.isArray(value)) return []
|
|
@@ -10,7 +12,7 @@ function normalizeEvents(value: unknown): string[] {
|
|
|
10
12
|
.filter(Boolean)
|
|
11
13
|
}
|
|
12
14
|
|
|
13
|
-
export async function GET() {
|
|
15
|
+
export async function GET(_req: Request) {
|
|
14
16
|
return NextResponse.json(loadWebhooks())
|
|
15
17
|
}
|
|
16
18
|
|
package/src/app/icon.svg
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">SwarmClaw Lobster Avatar</title>
|
|
3
|
+
<desc id="desc">SwarmClaw org avatar using an OpenClaw-inspired lobster mark with swarm accents.</desc>
|
|
4
|
+
|
|
5
|
+
<defs>
|
|
6
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
7
|
+
<stop offset="0%" stop-color="#050B18"/>
|
|
8
|
+
<stop offset="100%" stop-color="#111827"/>
|
|
9
|
+
</linearGradient>
|
|
10
|
+
<radialGradient id="glow" cx="50%" cy="38%" r="62%">
|
|
11
|
+
<stop offset="0%" stop-color="#22d3ee" stop-opacity="0.22"/>
|
|
12
|
+
<stop offset="100%" stop-color="#22d3ee" stop-opacity="0"/>
|
|
13
|
+
</radialGradient>
|
|
14
|
+
<linearGradient id="lobster-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
15
|
+
<stop offset="0%" stop-color="#ff6a5f"/>
|
|
16
|
+
<stop offset="100%" stop-color="#a41318"/>
|
|
17
|
+
</linearGradient>
|
|
18
|
+
<filter id="soft-shadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
19
|
+
<feDropShadow dx="0" dy="16" stdDeviation="18" flood-color="#020617" flood-opacity="0.55"/>
|
|
20
|
+
</filter>
|
|
21
|
+
</defs>
|
|
22
|
+
|
|
23
|
+
<rect x="40" y="40" width="944" height="944" rx="216" fill="url(#bg)"/>
|
|
24
|
+
<rect x="40" y="40" width="944" height="944" rx="216" fill="url(#glow)"/>
|
|
25
|
+
<rect x="56" y="56" width="912" height="912" rx="200" fill="none" stroke="#334155" stroke-width="6"/>
|
|
26
|
+
|
|
27
|
+
<!-- swarm accents -->
|
|
28
|
+
<g stroke="#22d3ee" stroke-opacity="0.8" stroke-width="10" fill="none" stroke-linecap="round">
|
|
29
|
+
<path d="M182 286 C232 236, 314 224, 378 252"/>
|
|
30
|
+
<path d="M842 286 C792 236, 710 224, 646 252"/>
|
|
31
|
+
<path d="M202 760 C270 814, 350 826, 420 806"/>
|
|
32
|
+
<path d="M822 760 C754 814, 674 826, 604 806"/>
|
|
33
|
+
</g>
|
|
34
|
+
<g fill="#67e8f9">
|
|
35
|
+
<circle cx="172" cy="282" r="14"/>
|
|
36
|
+
<circle cx="852" cy="282" r="14"/>
|
|
37
|
+
<circle cx="198" cy="760" r="12"/>
|
|
38
|
+
<circle cx="826" cy="760" r="12"/>
|
|
39
|
+
</g>
|
|
40
|
+
|
|
41
|
+
<!-- OpenClaw-inspired lobster mark -->
|
|
42
|
+
<g transform="translate(152 156) scale(6)" filter="url(#soft-shadow)">
|
|
43
|
+
<!-- Body -->
|
|
44
|
+
<path d="M60 10 C30 10 15 35 15 55 C15 75 30 95 45 100 L45 110 L55 110 L55 100 C55 100 60 102 65 100 L65 110 L75 110 L75 100 C90 95 105 75 105 55 C105 35 90 10 60 10Z" fill="url(#lobster-gradient)"/>
|
|
45
|
+
<!-- Left Claw -->
|
|
46
|
+
<path d="M20 45 C5 40 0 50 5 60 C10 70 20 65 25 55 C28 48 25 45 20 45Z" fill="url(#lobster-gradient)"/>
|
|
47
|
+
<!-- Right Claw -->
|
|
48
|
+
<path d="M100 45 C115 40 120 50 115 60 C110 70 100 65 95 55 C92 48 95 45 100 45Z" fill="url(#lobster-gradient)"/>
|
|
49
|
+
<!-- Antenna -->
|
|
50
|
+
<path d="M45 15 Q35 5 30 8" stroke="#ff8b84" stroke-width="3" stroke-linecap="round"/>
|
|
51
|
+
<path d="M75 15 Q85 5 90 8" stroke="#ff8b84" stroke-width="3" stroke-linecap="round"/>
|
|
52
|
+
<!-- Eyes -->
|
|
53
|
+
<circle cx="45" cy="35" r="6" fill="#030712"/>
|
|
54
|
+
<circle cx="75" cy="35" r="6" fill="#030712"/>
|
|
55
|
+
<circle cx="46" cy="34" r="2.5" fill="#22d3ee"/>
|
|
56
|
+
<circle cx="76" cy="34" r="2.5" fill="#22d3ee"/>
|
|
57
|
+
</g>
|
|
58
|
+
</svg>
|
package/src/app/page.tsx
CHANGED
|
@@ -4,6 +4,8 @@ import { useEffect, useState, useCallback } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { initAudioContext } from '@/lib/tts'
|
|
6
6
|
import { getStoredAccessKey, clearStoredAccessKey, api } from '@/lib/api-client'
|
|
7
|
+
import { connectWs, disconnectWs } from '@/lib/ws-client'
|
|
8
|
+
import { useWs } from '@/hooks/use-ws'
|
|
7
9
|
import { AccessKeyGate } from '@/components/auth/access-key-gate'
|
|
8
10
|
import { UserPicker } from '@/components/auth/user-picker'
|
|
9
11
|
import { SetupWizard } from '@/components/auth/setup-wizard'
|
|
@@ -70,14 +72,17 @@ export default function Home() {
|
|
|
70
72
|
|
|
71
73
|
useEffect(() => {
|
|
72
74
|
if (!authenticated) return
|
|
75
|
+
const key = getStoredAccessKey()
|
|
76
|
+
if (key) connectWs(key)
|
|
73
77
|
syncUserFromServer()
|
|
74
78
|
loadNetworkInfo()
|
|
75
79
|
loadSettings()
|
|
76
80
|
loadSessions()
|
|
77
|
-
|
|
78
|
-
return () => clearInterval(interval)
|
|
81
|
+
return () => { disconnectWs() }
|
|
79
82
|
}, [authenticated])
|
|
80
83
|
|
|
84
|
+
useWs('sessions', loadSessions, 5000)
|
|
85
|
+
|
|
81
86
|
// Auto-select default agent's thread on load
|
|
82
87
|
useEffect(() => {
|
|
83
88
|
if (!authenticated || !currentUser) return
|
|
@@ -156,6 +161,7 @@ export default function Home() {
|
|
|
156
161
|
|
|
157
162
|
useEffect(() => {
|
|
158
163
|
const handler = () => {
|
|
164
|
+
disconnectWs()
|
|
159
165
|
setAuthenticated(false)
|
|
160
166
|
setAuthChecked(true)
|
|
161
167
|
}
|
package/src/cli/index.js
CHANGED
|
@@ -17,7 +17,6 @@ const COMMAND_GROUPS = [
|
|
|
17
17
|
cmd('create', 'POST', '/agents', 'Create an agent', { expectsJsonBody: true }),
|
|
18
18
|
cmd('update', 'PUT', '/agents/:id', 'Update an agent', { expectsJsonBody: true }),
|
|
19
19
|
cmd('delete', 'DELETE', '/agents/:id', 'Delete an agent'),
|
|
20
|
-
cmd('generate', 'POST', '/agents/generate', 'Generate agent definition from prompt', { expectsJsonBody: true }),
|
|
21
20
|
cmd('thread', 'POST', '/agents/:id/thread', 'Get or create agent thread session'),
|
|
22
21
|
],
|
|
23
22
|
},
|
|
@@ -123,14 +122,6 @@ const COMMAND_GROUPS = [
|
|
|
123
122
|
cmd('serve', 'GET', '/files/serve', 'Serve a local file (use --query path=/abs/path)'),
|
|
124
123
|
],
|
|
125
124
|
},
|
|
126
|
-
{
|
|
127
|
-
name: 'generate',
|
|
128
|
-
description: 'AI generation endpoints',
|
|
129
|
-
commands: [
|
|
130
|
-
cmd('run', 'POST', '/generate', 'Generate schedule/task/skill/provider payload', { expectsJsonBody: true }),
|
|
131
|
-
cmd('info', 'GET', '/generate/info', 'Get generation provider/model info'),
|
|
132
|
-
],
|
|
133
|
-
},
|
|
134
125
|
{
|
|
135
126
|
name: 'ip',
|
|
136
127
|
description: 'Get local IP/port metadata',
|
|
@@ -335,6 +326,7 @@ const COMMAND_GROUPS = [
|
|
|
335
326
|
commands: [
|
|
336
327
|
cmd('check-provider', 'POST', '/setup/check-provider', 'Validate provider credentials/endpoint', { expectsJsonBody: true }),
|
|
337
328
|
cmd('doctor', 'GET', '/setup/doctor', 'Run local setup diagnostics'),
|
|
329
|
+
cmd('openclaw-device', 'GET', '/setup/openclaw-device', 'Show the local OpenClaw device ID'),
|
|
338
330
|
],
|
|
339
331
|
},
|
|
340
332
|
{
|
package/src/cli/index.ts
CHANGED
|
@@ -901,6 +901,13 @@ export function buildProgram(): Command {
|
|
|
901
901
|
})
|
|
902
902
|
})
|
|
903
903
|
|
|
904
|
+
setup
|
|
905
|
+
.command('openclaw-device')
|
|
906
|
+
.description('Show the local OpenClaw device ID')
|
|
907
|
+
.action(async function () {
|
|
908
|
+
await runWithHandler(this as Command, (ctx) => apiRequest(ctx, 'GET', '/setup/openclaw-device'))
|
|
909
|
+
})
|
|
910
|
+
|
|
904
911
|
const connectors = program.command('connectors').description('Manage connectors')
|
|
905
912
|
|
|
906
913
|
connectors
|
|
@@ -1133,14 +1140,57 @@ export function buildProgram(): Command {
|
|
|
1133
1140
|
console.log('Run: swarmclaw server --help')
|
|
1134
1141
|
})
|
|
1135
1142
|
|
|
1143
|
+
program
|
|
1144
|
+
.command('update')
|
|
1145
|
+
.description('Pull the latest SwarmClaw release via git')
|
|
1146
|
+
.action(() => {
|
|
1147
|
+
console.log('The update command is handled directly by the swarmclaw binary.')
|
|
1148
|
+
console.log('Run: swarmclaw update --help')
|
|
1149
|
+
})
|
|
1150
|
+
|
|
1136
1151
|
return program
|
|
1137
1152
|
}
|
|
1138
1153
|
|
|
1154
|
+
async function checkForUpdate(baseUrl: string, accessKey: string): Promise<void> {
|
|
1155
|
+
try {
|
|
1156
|
+
const url = `${baseUrl}/api/version`
|
|
1157
|
+
const headers: Record<string, string> = {}
|
|
1158
|
+
if (accessKey) headers['X-Access-Key'] = accessKey
|
|
1159
|
+
const controller = new AbortController()
|
|
1160
|
+
const timeout = setTimeout(() => controller.abort(), 2000)
|
|
1161
|
+
const res = await fetch(url, { headers, signal: controller.signal })
|
|
1162
|
+
clearTimeout(timeout)
|
|
1163
|
+
if (!res.ok) return
|
|
1164
|
+
const data = (await res.json()) as { updateAvailable?: boolean; behindBy?: number }
|
|
1165
|
+
if (data.updateAvailable && data.behindBy) {
|
|
1166
|
+
process.stderr.write(`\n Update available (${data.behindBy} behind). Run: swarmclaw update\n`)
|
|
1167
|
+
}
|
|
1168
|
+
} catch {
|
|
1169
|
+
// Server unreachable or timed out — silently skip
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1139
1173
|
export async function runCli(argv: string[] = process.argv.slice(2)): Promise<number> {
|
|
1140
1174
|
const program = buildProgram()
|
|
1141
1175
|
try {
|
|
1176
|
+
// Skip update hint for commands that don't talk to the server
|
|
1177
|
+
const skipHint = !argv.length || ['update', 'server', '--help', '-h'].includes(argv[0])
|
|
1178
|
+
const hintPromise = skipHint
|
|
1179
|
+
? null
|
|
1180
|
+
: checkForUpdate(
|
|
1181
|
+
normalizeBaseUrl(process.env.SWARMCLAW_URL || process.env.SWARMCLAW_BASE_URL || 'http://localhost:3456'),
|
|
1182
|
+
(process.env.SWARMCLAW_ACCESS_KEY || '').trim(),
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1142
1185
|
await program.parseAsync(['node', 'swarmclaw', ...argv])
|
|
1143
|
-
|
|
1186
|
+
const code = (process.exitCode as number | undefined) ?? 0
|
|
1187
|
+
|
|
1188
|
+
// Wait briefly for the hint if the command succeeded
|
|
1189
|
+
if (hintPromise && code === 0) {
|
|
1190
|
+
await Promise.race([hintPromise, new Promise((r) => setTimeout(r, 2000))])
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return code
|
|
1144
1194
|
} catch (err) {
|
|
1145
1195
|
const msg = err instanceof Error ? err.message : String(err)
|
|
1146
1196
|
console.error(msg)
|
package/src/cli/spec.js
CHANGED
|
@@ -7,7 +7,6 @@ const COMMAND_GROUPS = {
|
|
|
7
7
|
create: { description: 'Create an agent', method: 'POST', path: '/agents' },
|
|
8
8
|
update: { description: 'Update an agent', method: 'PUT', path: '/agents/:id', params: ['id'] },
|
|
9
9
|
delete: { description: 'Delete an agent', method: 'DELETE', path: '/agents/:id', params: ['id'] },
|
|
10
|
-
generate: { description: 'Generate an agent definition', method: 'POST', path: '/agents/generate' },
|
|
11
10
|
},
|
|
12
11
|
},
|
|
13
12
|
auth: {
|
|
@@ -97,13 +96,6 @@ const COMMAND_GROUPS = {
|
|
|
97
96
|
},
|
|
98
97
|
},
|
|
99
98
|
},
|
|
100
|
-
generate: {
|
|
101
|
-
description: 'Structured AI generation helpers',
|
|
102
|
-
commands: {
|
|
103
|
-
create: { description: 'Generate object from prompt/type', method: 'POST', path: '/generate' },
|
|
104
|
-
info: { description: 'Get active generator provider/model', method: 'GET', path: '/generate/info' },
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
99
|
logs: {
|
|
108
100
|
description: 'Application logs',
|
|
109
101
|
commands: {
|
|
@@ -56,7 +56,7 @@ export function AgentCard({ agent, isDefault, onSetDefault }: Props) {
|
|
|
56
56
|
await loadSessions()
|
|
57
57
|
setMessages([])
|
|
58
58
|
setCurrentSession(result.sessionId)
|
|
59
|
-
setActiveView('
|
|
59
|
+
setActiveView('agents')
|
|
60
60
|
}
|
|
61
61
|
} catch (err) {
|
|
62
62
|
console.error('Orchestrator run failed:', err)
|