@swarmclawai/swarmclaw 0.3.1 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -13
- package/bin/server-cmd.js +14 -7
- package/bin/swarmclaw.js +3 -1
- package/bin/update-cmd.js +120 -0
- package/next.config.ts +10 -0
- package/package.json +4 -1
- package/src/app/api/agents/[id]/route.ts +20 -18
- package/src/app/api/agents/[id]/thread/route.ts +4 -3
- package/src/app/api/agents/route.ts +8 -3
- package/src/app/api/auth/route.ts +3 -1
- package/src/app/api/claude-skills/route.ts +3 -1
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +14 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +12 -4
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +5 -3
- package/src/app/api/daemon/route.ts +6 -1
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- package/src/app/api/ip/route.ts +3 -1
- package/src/app/api/knowledge/[id]/route.ts +5 -4
- package/src/app/api/knowledge/upload/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/route.ts +11 -14
- package/src/app/api/mcp-servers/[id]/test/route.ts +2 -1
- package/src/app/api/mcp-servers/[id]/tools/route.ts +2 -1
- package/src/app/api/mcp-servers/route.ts +5 -3
- package/src/app/api/memory/[id]/route.ts +9 -8
- package/src/app/api/memory/route.ts +2 -2
- package/src/app/api/memory-images/[filename]/route.ts +2 -1
- package/src/app/api/openclaw/directory/route.ts +26 -0
- package/src/app/api/openclaw/discover/route.ts +61 -0
- package/src/app/api/openclaw/sync/route.ts +30 -0
- package/src/app/api/orchestrator/graph/route.ts +25 -0
- package/src/app/api/orchestrator/run/route.ts +2 -2
- package/src/app/api/plugins/marketplace/route.ts +3 -1
- package/src/app/api/plugins/route.ts +3 -1
- package/src/app/api/projects/[id]/route.ts +55 -0
- package/src/app/api/projects/route.ts +27 -0
- package/src/app/api/providers/[id]/models/route.ts +2 -1
- package/src/app/api/providers/[id]/route.ts +13 -12
- package/src/app/api/providers/configs/route.ts +3 -1
- package/src/app/api/providers/route.ts +7 -3
- package/src/app/api/schedules/[id]/route.ts +16 -15
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +8 -3
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +5 -3
- package/src/app/api/sessions/[id]/chat/route.ts +5 -2
- package/src/app/api/sessions/[id]/clear/route.ts +2 -1
- package/src/app/api/sessions/[id]/deploy/route.ts +2 -1
- package/src/app/api/sessions/[id]/devserver/route.ts +2 -1
- package/src/app/api/sessions/[id]/messages/route.ts +2 -1
- package/src/app/api/sessions/[id]/retry/route.ts +2 -1
- package/src/app/api/sessions/[id]/route.ts +2 -1
- package/src/app/api/sessions/route.ts +11 -4
- package/src/app/api/settings/route.ts +3 -1
- package/src/app/api/setup/doctor/route.ts +1 -0
- package/src/app/api/setup/openclaw-device/route.ts +3 -1
- package/src/app/api/skills/[id]/route.ts +23 -21
- package/src/app/api/skills/import/route.ts +2 -2
- package/src/app/api/skills/route.ts +5 -3
- package/src/app/api/tasks/[id]/approve/route.ts +74 -0
- package/src/app/api/tasks/[id]/route.ts +9 -5
- package/src/app/api/tasks/route.ts +5 -2
- package/src/app/api/tts/stream/route.ts +48 -0
- package/src/app/api/upload/route.ts +2 -2
- package/src/app/api/uploads/[filename]/route.ts +4 -1
- package/src/app/api/usage/route.ts +3 -1
- package/src/app/api/version/route.ts +3 -1
- package/src/app/api/webhooks/[id]/route.ts +31 -32
- package/src/app/api/webhooks/route.ts +5 -3
- package/src/app/icon.svg +58 -0
- package/src/app/page.tsx +11 -26
- package/src/cli/index.js +28 -9
- package/src/cli/index.ts +45 -2
- package/src/cli/spec.js +2 -8
- package/src/components/agents/agent-card.tsx +1 -1
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +166 -81
- package/src/components/chat/chat-area.tsx +71 -34
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/chat-tool-toggles.tsx +12 -53
- package/src/components/chat/message-bubble.tsx +110 -42
- package/src/components/chat/tool-call-bubble.tsx +50 -6
- package/src/components/chat/tool-request-banner.tsx +1 -9
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +9 -10
- package/src/components/connectors/connector-sheet.tsx +55 -36
- package/src/components/input/chat-input.tsx +72 -56
- package/src/components/knowledge/knowledge-list.tsx +27 -31
- package/src/components/layout/app-layout.tsx +133 -90
- package/src/components/layout/daemon-indicator.tsx +3 -5
- package/src/components/logs/log-list.tsx +5 -9
- package/src/components/mcp-servers/mcp-server-list.tsx +24 -2
- package/src/components/memory/memory-detail.tsx +1 -1
- package/src/components/plugins/plugin-list.tsx +227 -27
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/providers/provider-list.tsx +46 -13
- package/src/components/providers/provider-sheet.tsx +0 -45
- package/src/components/runs/run-list.tsx +6 -15
- package/src/components/schedules/schedule-card.tsx +54 -4
- package/src/components/schedules/schedule-list.tsx +9 -4
- package/src/components/schedules/schedule-sheet.tsx +0 -47
- package/src/components/secrets/secrets-list.tsx +20 -2
- package/src/components/sessions/new-session-sheet.tsx +14 -15
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/sessions/session-list.tsx +7 -7
- package/src/components/shared/connector-platform-icon.tsx +26 -20
- package/src/components/shared/model-combobox.tsx +148 -0
- package/src/components/shared/settings/section-heartbeat.tsx +8 -40
- package/src/components/shared/settings/section-orchestrator.tsx +9 -11
- package/src/components/shared/settings/section-web-search.tsx +56 -0
- package/src/components/shared/settings/settings-page.tsx +73 -0
- package/src/components/skills/skill-list.tsx +262 -35
- package/src/components/skills/skill-sheet.tsx +0 -45
- package/src/components/tasks/task-board.tsx +3 -6
- package/src/components/tasks/task-card.tsx +43 -1
- package/src/components/tasks/task-list.tsx +8 -7
- package/src/components/tasks/task-sheet.tsx +0 -44
- package/src/components/usage/usage-list.tsx +12 -4
- package/src/hooks/use-continuous-speech.ts +144 -0
- package/src/hooks/use-view-router.ts +52 -0
- package/src/hooks/use-voice-conversation.ts +80 -0
- package/src/hooks/use-ws.ts +66 -0
- package/src/instrumentation.ts +2 -0
- package/src/lib/chat.ts +14 -2
- package/src/lib/id.ts +6 -0
- package/src/lib/projects.ts +13 -0
- package/src/lib/provider-sets.ts +5 -0
- package/src/lib/providers/anthropic.ts +15 -2
- package/src/lib/providers/index.ts +8 -0
- package/src/lib/providers/ollama.ts +10 -2
- package/src/lib/providers/openai.ts +42 -13
- package/src/lib/providers/openclaw.ts +11 -0
- package/src/lib/server/api-routes.test.ts +5 -6
- package/src/lib/server/build-llm.ts +17 -4
- package/src/lib/server/chat-execution.ts +57 -8
- package/src/lib/server/collection-helpers.ts +54 -0
- package/src/lib/server/connectors/bluebubbles.test.ts +208 -0
- package/src/lib/server/connectors/bluebubbles.ts +357 -0
- package/src/lib/server/connectors/connector-routing.test.ts +1 -1
- package/src/lib/server/connectors/googlechat.ts +46 -7
- package/src/lib/server/connectors/manager.ts +401 -6
- package/src/lib/server/connectors/media.ts +2 -2
- package/src/lib/server/connectors/openclaw.ts +64 -0
- package/src/lib/server/connectors/pairing.test.ts +99 -0
- package/src/lib/server/connectors/pairing.ts +256 -0
- package/src/lib/server/connectors/signal.ts +1 -0
- package/src/lib/server/connectors/teams.ts +5 -5
- package/src/lib/server/connectors/types.ts +10 -0
- package/src/lib/server/context-manager.ts +1 -1
- package/src/lib/server/daemon-state.ts +3 -0
- package/src/lib/server/data-dir.ts +1 -0
- package/src/lib/server/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +67 -3
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/langgraph-checkpoint.ts +274 -0
- package/src/lib/server/main-agent-loop.ts +67 -8
- package/src/lib/server/memory-db.ts +6 -6
- package/src/lib/server/openclaw-approvals.ts +105 -0
- package/src/lib/server/openclaw-sync.ts +496 -0
- package/src/lib/server/orchestrator-lg.ts +422 -20
- package/src/lib/server/orchestrator.ts +29 -9
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +39 -13
- package/src/lib/server/scheduler.ts +2 -2
- package/src/lib/server/session-mailbox.ts +2 -2
- package/src/lib/server/session-run-manager.ts +8 -3
- package/src/lib/server/session-tools/connector.ts +51 -4
- package/src/lib/server/session-tools/crud.ts +3 -3
- package/src/lib/server/session-tools/delegate.ts +5 -5
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +4 -0
- package/src/lib/server/session-tools/memory.ts +2 -2
- package/src/lib/server/session-tools/openclaw-nodes.ts +112 -0
- package/src/lib/server/session-tools/sandbox.ts +197 -0
- package/src/lib/server/session-tools/search-providers.ts +270 -0
- package/src/lib/server/session-tools/session-info.ts +2 -2
- package/src/lib/server/session-tools/web.ts +47 -66
- package/src/lib/server/storage-mcp.test.ts +25 -2
- package/src/lib/server/storage.ts +36 -7
- package/src/lib/server/stream-agent-chat.ts +106 -22
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/server/task-validation.test.ts +23 -0
- package/src/lib/server/task-validation.ts +5 -3
- package/src/lib/server/ws-hub.ts +85 -0
- package/src/lib/tool-definitions.ts +44 -0
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/upload.ts +7 -1
- package/src/lib/view-routes.ts +28 -0
- package/src/lib/ws-client.ts +124 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +42 -14
- package/src/types/index.ts +34 -2
- package/src/app/api/agents/generate/route.ts +0 -42
- package/src/app/api/generate/info/route.ts +0 -12
- package/src/app/api/generate/route.ts +0 -106
- package/src/app/favicon.ico +0 -0
- package/src/components/shared/ai-gen-block.tsx +0 -77
|
@@ -1,40 +1,42 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadSkills, saveSkills, deleteSkill } from '@/lib/server/storage'
|
|
3
3
|
import { normalizeSkillPayload } from '@/lib/server/skills-normalize'
|
|
4
|
+
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
7
|
+
const ops: CollectionOps<any> = { load: loadSkills, save: saveSkills, deleteFn: deleteSkill }
|
|
4
8
|
|
|
5
9
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
6
10
|
const { id } = await params
|
|
7
11
|
const skills = loadSkills()
|
|
8
|
-
if (!skills[id]) return
|
|
12
|
+
if (!skills[id]) return notFound()
|
|
9
13
|
return NextResponse.json(skills[id])
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
13
17
|
const { id } = await params
|
|
14
18
|
const body = await req.json()
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return NextResponse.json(
|
|
19
|
+
const result = mutateItem(ops, id, (skill) => {
|
|
20
|
+
const normalized = normalizeSkillPayload({ ...skill, ...body })
|
|
21
|
+
return {
|
|
22
|
+
...skill,
|
|
23
|
+
...body,
|
|
24
|
+
name: normalized.name,
|
|
25
|
+
filename: normalized.filename,
|
|
26
|
+
description: normalized.description,
|
|
27
|
+
content: normalized.content,
|
|
28
|
+
sourceUrl: normalized.sourceUrl,
|
|
29
|
+
sourceFormat: normalized.sourceFormat,
|
|
30
|
+
id,
|
|
31
|
+
updatedAt: Date.now(),
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
if (!result) return notFound()
|
|
35
|
+
return NextResponse.json(result)
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
35
39
|
const { id } = await params
|
|
36
|
-
|
|
37
|
-
if (!skills[id]) return new NextResponse(null, { status: 404 })
|
|
38
|
-
deleteSkill(id)
|
|
40
|
+
if (!deleteItem(ops, id)) return notFound()
|
|
39
41
|
return NextResponse.json({ deleted: id })
|
|
40
42
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
3
|
import { loadSkills, saveSkills } from '@/lib/server/storage'
|
|
4
4
|
import { normalizeSkillPayload } from '@/lib/server/skills-normalize'
|
|
@@ -47,7 +47,7 @@ export async function POST(req: Request) {
|
|
|
47
47
|
})
|
|
48
48
|
|
|
49
49
|
const skills = loadSkills()
|
|
50
|
-
const id =
|
|
50
|
+
const id = genId()
|
|
51
51
|
skills[id] = {
|
|
52
52
|
id,
|
|
53
53
|
name: normalized.name,
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
3
|
import { loadSkills, saveSkills } from '@/lib/server/storage'
|
|
4
4
|
import { 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
|
|
|
10
12
|
export async function POST(req: Request) {
|
|
11
13
|
const body = await req.json()
|
|
12
14
|
const skills = loadSkills()
|
|
13
|
-
const id =
|
|
15
|
+
const id = genId()
|
|
14
16
|
const normalized = normalizeSkillPayload(body)
|
|
15
17
|
skills[id] = {
|
|
16
18
|
id,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { loadTasks, saveTasks, loadAgents } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
5
|
+
import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
6
|
+
|
|
7
|
+
export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
8
|
+
const { id } = await params
|
|
9
|
+
const body = await req.json()
|
|
10
|
+
const approved = body.approved === true
|
|
11
|
+
|
|
12
|
+
const tasks = loadTasks()
|
|
13
|
+
const task = tasks[id]
|
|
14
|
+
if (!task) return notFound()
|
|
15
|
+
if (!task.pendingApproval) {
|
|
16
|
+
return NextResponse.json({ error: 'No pending approval on this task' }, { status: 400 })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { threadId } = task.pendingApproval
|
|
20
|
+
|
|
21
|
+
if (!approved) {
|
|
22
|
+
// Reject: clear approval, delete checkpoint, fail the task
|
|
23
|
+
task.pendingApproval = null
|
|
24
|
+
task.status = 'failed'
|
|
25
|
+
task.error = 'Tool execution rejected by user'
|
|
26
|
+
task.updatedAt = Date.now()
|
|
27
|
+
saveTasks(tasks)
|
|
28
|
+
await getCheckpointSaver().deleteThread(threadId)
|
|
29
|
+
notify('tasks')
|
|
30
|
+
return NextResponse.json({ status: 'rejected' })
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Approve: clear pendingApproval, resume the graph
|
|
34
|
+
const agents = loadAgents()
|
|
35
|
+
const agent = agents[task.agentId]
|
|
36
|
+
if (!agent) {
|
|
37
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 400 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
task.pendingApproval = null
|
|
41
|
+
task.updatedAt = Date.now()
|
|
42
|
+
saveTasks(tasks)
|
|
43
|
+
notify('tasks')
|
|
44
|
+
|
|
45
|
+
// Resume in the background
|
|
46
|
+
const sessionId = task.sessionId || ''
|
|
47
|
+
setImmediate(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const { resumeLangGraphOrchestrator } = await import('@/lib/server/orchestrator-lg')
|
|
50
|
+
const result = await resumeLangGraphOrchestrator(agent, sessionId, threadId)
|
|
51
|
+
const t2 = loadTasks()
|
|
52
|
+
if (t2[id] && !t2[id].pendingApproval) {
|
|
53
|
+
// Only mark completed if not paused again
|
|
54
|
+
if (t2[id].status === 'running') {
|
|
55
|
+
t2[id].result = result
|
|
56
|
+
}
|
|
57
|
+
t2[id].updatedAt = Date.now()
|
|
58
|
+
saveTasks(t2)
|
|
59
|
+
notify('tasks')
|
|
60
|
+
}
|
|
61
|
+
} catch (err: any) {
|
|
62
|
+
console.error(`[approve] Resume failed for task ${id}:`, err.message)
|
|
63
|
+
const t2 = loadTasks()
|
|
64
|
+
if (t2[id]) {
|
|
65
|
+
t2[id].error = err.message || String(err)
|
|
66
|
+
t2[id].updatedAt = Date.now()
|
|
67
|
+
saveTasks(t2)
|
|
68
|
+
notify('tasks')
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
return NextResponse.json({ status: 'approved', resuming: true })
|
|
74
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
2
2
|
import { NextResponse } from 'next/server'
|
|
3
3
|
import { loadTasks, saveTasks } from '@/lib/server/storage'
|
|
4
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
5
|
import { disableSessionHeartbeat, enqueueTask, validateCompletedTasksQueue } from '@/lib/server/queue'
|
|
5
6
|
import { ensureTaskCompletionReport } from '@/lib/server/task-reports'
|
|
6
7
|
import { formatValidationFailure, validateTaskCompletion } from '@/lib/server/task-validation'
|
|
7
8
|
import { pushMainLoopEventToMainSessions } from '@/lib/server/main-agent-loop'
|
|
9
|
+
import { notify } from '@/lib/server/ws-hub'
|
|
8
10
|
|
|
9
11
|
export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
10
12
|
// Keep completed queue integrity even if daemon is not running.
|
|
@@ -12,7 +14,7 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
12
14
|
|
|
13
15
|
const { id } = await params
|
|
14
16
|
const tasks = loadTasks()
|
|
15
|
-
if (!tasks[id]) return
|
|
17
|
+
if (!tasks[id]) return notFound()
|
|
16
18
|
return NextResponse.json(tasks[id])
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -20,7 +22,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
20
22
|
const { id } = await params
|
|
21
23
|
const body = await req.json()
|
|
22
24
|
const tasks = loadTasks()
|
|
23
|
-
if (!tasks[id]) return
|
|
25
|
+
if (!tasks[id]) return notFound()
|
|
24
26
|
|
|
25
27
|
const prevStatus = tasks[id].status
|
|
26
28
|
|
|
@@ -54,7 +56,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
54
56
|
tasks[id].error = formatValidationFailure(validation.reasons).slice(0, 500)
|
|
55
57
|
if (!tasks[id].comments) tasks[id].comments = []
|
|
56
58
|
tasks[id].comments.push({
|
|
57
|
-
id:
|
|
59
|
+
id: genId(),
|
|
58
60
|
author: 'System',
|
|
59
61
|
text: `Completion validation failed.\n\n${validation.reasons.map((r) => `- ${r}`).join('\n')}`,
|
|
60
62
|
createdAt: Date.now(),
|
|
@@ -80,13 +82,14 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
80
82
|
enqueueTask(id)
|
|
81
83
|
}
|
|
82
84
|
|
|
85
|
+
notify('tasks')
|
|
83
86
|
return NextResponse.json(tasks[id])
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
87
90
|
const { id } = await params
|
|
88
91
|
const tasks = loadTasks()
|
|
89
|
-
if (!tasks[id]) return
|
|
92
|
+
if (!tasks[id]) return notFound()
|
|
90
93
|
|
|
91
94
|
// Soft delete: move to archived status instead of hard delete
|
|
92
95
|
tasks[id].status = 'archived'
|
|
@@ -98,5 +101,6 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|
|
98
101
|
text: `Task archived: "${tasks[id].title}" (${id}).`,
|
|
99
102
|
})
|
|
100
103
|
|
|
104
|
+
notify('tasks')
|
|
101
105
|
return NextResponse.json(tasks[id])
|
|
102
106
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import
|
|
2
|
+
import { genId } from '@/lib/id'
|
|
3
3
|
import { loadTasks, saveTasks, loadSettings } from '@/lib/server/storage'
|
|
4
4
|
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,12 +48,13 @@ 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
|
|
|
53
55
|
export async function POST(req: Request) {
|
|
54
56
|
const body = await req.json()
|
|
55
|
-
const id =
|
|
57
|
+
const id = genId()
|
|
56
58
|
const now = Date.now()
|
|
57
59
|
const tasks = loadTasks()
|
|
58
60
|
const settings = loadSettings()
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { loadSettings } from '@/lib/server/storage'
|
|
2
|
+
|
|
3
|
+
export async function POST(req: Request) {
|
|
4
|
+
const settings = loadSettings()
|
|
5
|
+
const ELEVENLABS_KEY = settings.elevenLabsApiKey || process.env.ELEVENLABS_API_KEY
|
|
6
|
+
const ELEVENLABS_VOICE = settings.elevenLabsVoiceId || process.env.ELEVENLABS_VOICE || 'JBFqnCBsd6RMkjVDRZzb'
|
|
7
|
+
|
|
8
|
+
if (!ELEVENLABS_KEY) {
|
|
9
|
+
return new Response('No ElevenLabs API key. Set one in Settings > Voice.', { status: 500 })
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { text } = await req.json()
|
|
13
|
+
if (!text?.trim()) {
|
|
14
|
+
return new Response('No text provided', { status: 400 })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const apiRes = await fetch(
|
|
18
|
+
`https://api.elevenlabs.io/v1/text-to-speech/${ELEVENLABS_VOICE}/stream`,
|
|
19
|
+
{
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'xi-api-key': ELEVENLABS_KEY,
|
|
23
|
+
'Content-Type': 'application/json',
|
|
24
|
+
'Accept': 'audio/mpeg',
|
|
25
|
+
},
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
text: text.slice(0, 2000),
|
|
28
|
+
model_id: 'eleven_multilingual_v2',
|
|
29
|
+
voice_settings: { stability: 0.5, similarity_boost: 0.75 },
|
|
30
|
+
output_format: 'mp3_22050_32',
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
if (!apiRes.ok) {
|
|
36
|
+
const err = await apiRes.text()
|
|
37
|
+
return new Response(err, { status: apiRes.status })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Pipe the streaming response directly
|
|
41
|
+
return new Response(apiRes.body, {
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'audio/mpeg',
|
|
44
|
+
'Transfer-Encoding': 'chunked',
|
|
45
|
+
'Cache-Control': 'no-cache',
|
|
46
|
+
},
|
|
47
|
+
})
|
|
48
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import fs from 'fs'
|
|
3
3
|
import path from 'path'
|
|
4
|
-
import
|
|
4
|
+
import { genId } from '@/lib/id'
|
|
5
5
|
import { UPLOAD_DIR } from '@/lib/server/storage'
|
|
6
6
|
|
|
7
7
|
export async function POST(req: Request) {
|
|
8
8
|
const filename = req.headers.get('x-filename') || 'image.png'
|
|
9
9
|
const buf = Buffer.from(await req.arrayBuffer())
|
|
10
|
-
const name =
|
|
10
|
+
const name = genId() + '-' + filename.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
11
11
|
const filePath = path.join(UPLOAD_DIR, name)
|
|
12
12
|
|
|
13
13
|
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true })
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
2
3
|
import fs from 'fs'
|
|
3
4
|
import path from 'path'
|
|
4
5
|
import { UPLOAD_DIR } from '@/lib/server/storage'
|
|
@@ -43,16 +44,18 @@ export async function GET(_req: Request, { params }: { params: Promise<{ filenam
|
|
|
43
44
|
const filePath = path.join(UPLOAD_DIR, safeName)
|
|
44
45
|
|
|
45
46
|
if (!fs.existsSync(filePath)) {
|
|
46
|
-
return
|
|
47
|
+
return notFound()
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const ext = path.extname(safeName).toLowerCase()
|
|
50
51
|
const contentType = MIME_TYPES[ext] || 'application/octet-stream'
|
|
51
52
|
const data = fs.readFileSync(filePath)
|
|
52
53
|
|
|
54
|
+
const inline = contentType.startsWith('image/') || contentType.startsWith('video/') || contentType.startsWith('text/') || contentType === 'application/pdf'
|
|
53
55
|
return new NextResponse(data, {
|
|
54
56
|
headers: {
|
|
55
57
|
'Content-Type': contentType,
|
|
58
|
+
'Content-Disposition': inline ? 'inline' : `attachment; filename="${path.basename(safeName)}"`,
|
|
56
59
|
'Cache-Control': 'public, max-age=86400',
|
|
57
60
|
},
|
|
58
61
|
})
|
|
@@ -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,7 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
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'
|
|
6
|
+
import { mutateItem, deleteItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
|
|
7
|
+
|
|
8
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
9
|
+
const ops: CollectionOps<any> = { load: loadWebhooks, save: saveWebhooks }
|
|
5
10
|
|
|
6
11
|
function normalizeEvents(value: unknown): string[] {
|
|
7
12
|
if (!Array.isArray(value)) return []
|
|
@@ -21,36 +26,30 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
21
26
|
const { id } = await params
|
|
22
27
|
const webhooks = loadWebhooks()
|
|
23
28
|
const webhook = webhooks[id]
|
|
24
|
-
if (!webhook) return
|
|
29
|
+
if (!webhook) return notFound('Webhook not found')
|
|
25
30
|
return NextResponse.json(webhook)
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
29
34
|
const { id } = await params
|
|
30
35
|
const body = await req.json().catch(() => ({}))
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
webhooks[id] = webhook
|
|
44
|
-
saveWebhooks(webhooks)
|
|
45
|
-
return NextResponse.json(webhook)
|
|
36
|
+
const result = mutateItem(ops, id, (webhook) => {
|
|
37
|
+
if (body.name !== undefined) webhook.name = body.name
|
|
38
|
+
if (body.source !== undefined) webhook.source = body.source
|
|
39
|
+
if (body.events !== undefined) webhook.events = normalizeEvents(body.events)
|
|
40
|
+
if (body.agentId !== undefined) webhook.agentId = body.agentId
|
|
41
|
+
if (body.secret !== undefined) webhook.secret = body.secret
|
|
42
|
+
if (body.isEnabled !== undefined) webhook.isEnabled = !!body.isEnabled
|
|
43
|
+
webhook.updatedAt = Date.now()
|
|
44
|
+
return webhook
|
|
45
|
+
})
|
|
46
|
+
if (!result) return notFound('Webhook not found')
|
|
47
|
+
return NextResponse.json(result)
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
49
51
|
const { id } = await params
|
|
50
|
-
|
|
51
|
-
if (!webhooks[id]) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
|
52
|
-
delete webhooks[id]
|
|
53
|
-
saveWebhooks(webhooks)
|
|
52
|
+
if (!deleteItem(ops, id)) return notFound('Webhook not found')
|
|
54
53
|
return NextResponse.json({ ok: true })
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -58,10 +57,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
58
57
|
const { id } = await params
|
|
59
58
|
const webhooks = loadWebhooks()
|
|
60
59
|
const webhook = webhooks[id]
|
|
61
|
-
if (!webhook) return
|
|
60
|
+
if (!webhook) return notFound('Webhook not found')
|
|
62
61
|
if (webhook.isEnabled === false) {
|
|
63
|
-
appendWebhookLog(
|
|
64
|
-
id:
|
|
62
|
+
appendWebhookLog(genId(8), {
|
|
63
|
+
id: genId(8), webhookId: id, event: 'unknown',
|
|
65
64
|
payload: '', status: 'error', error: 'Webhook is disabled', timestamp: Date.now(),
|
|
66
65
|
})
|
|
67
66
|
return NextResponse.json({ error: 'Webhook is disabled' }, { status: 409 })
|
|
@@ -72,8 +71,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
72
71
|
const url = new URL(req.url)
|
|
73
72
|
const provided = req.headers.get('x-webhook-secret') || url.searchParams.get('secret') || ''
|
|
74
73
|
if (provided !== secret) {
|
|
75
|
-
appendWebhookLog(
|
|
76
|
-
id:
|
|
74
|
+
appendWebhookLog(genId(8), {
|
|
75
|
+
id: genId(8), webhookId: id, event: 'unknown',
|
|
77
76
|
payload: '', status: 'error', error: 'Invalid webhook secret', timestamp: Date.now(),
|
|
78
77
|
})
|
|
79
78
|
return NextResponse.json({ error: 'Invalid webhook secret' }, { status: 401 })
|
|
@@ -121,8 +120,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
121
120
|
const agents = loadAgents()
|
|
122
121
|
const agent = webhook.agentId ? agents[webhook.agentId] : null
|
|
123
122
|
if (!agent) {
|
|
124
|
-
appendWebhookLog(
|
|
125
|
-
id:
|
|
123
|
+
appendWebhookLog(genId(8), {
|
|
124
|
+
id: genId(8), webhookId: id, event: incomingEvent,
|
|
126
125
|
payload: (rawBody || '').slice(0, 2000), status: 'error', error: 'Webhook agent is not configured or missing', timestamp: Date.now(),
|
|
127
126
|
})
|
|
128
127
|
return NextResponse.json({ error: 'Webhook agent is not configured or missing' }, { status: 400 })
|
|
@@ -132,12 +131,12 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
132
131
|
const sessionName = `webhook:${id}`
|
|
133
132
|
let session = Object.values(sessions).find((s: any) => s.name === sessionName && s.agentId === agent.id) as any
|
|
134
133
|
if (!session) {
|
|
135
|
-
const sessionId =
|
|
134
|
+
const sessionId = genId()
|
|
136
135
|
const now = Date.now()
|
|
137
136
|
session = {
|
|
138
137
|
id: sessionId,
|
|
139
138
|
name: sessionName,
|
|
140
|
-
cwd:
|
|
139
|
+
cwd: WORKSPACE_DIR,
|
|
141
140
|
user: 'system',
|
|
142
141
|
provider: agent.provider || 'claude-cli',
|
|
143
142
|
model: agent.model || '',
|
|
@@ -188,8 +187,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
188
187
|
mode: 'followup',
|
|
189
188
|
})
|
|
190
189
|
|
|
191
|
-
appendWebhookLog(
|
|
192
|
-
id:
|
|
190
|
+
appendWebhookLog(genId(8), {
|
|
191
|
+
id: genId(8), webhookId: id, event: incomingEvent,
|
|
193
192
|
payload: (rawBody || '').slice(0, 2000), status: 'success',
|
|
194
193
|
sessionId: session.id, runId: run.runId, timestamp: Date.now(),
|
|
195
194
|
})
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { genId } from '@/lib/id'
|
|
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,14 +12,14 @@ 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
|
|
|
17
19
|
export async function POST(req: Request) {
|
|
18
20
|
const body = await req.json().catch(() => ({}))
|
|
19
21
|
const webhooks = loadWebhooks()
|
|
20
|
-
const id =
|
|
22
|
+
const id = genId()
|
|
21
23
|
const now = Date.now()
|
|
22
24
|
|
|
23
25
|
webhooks[id] = {
|
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>
|