@swarmclawai/swarmclaw 0.4.0 → 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 +13 -2
- package/next.config.ts +8 -0
- package/package.json +2 -1
- package/src/app/api/agents/[id]/route.ts +20 -21
- package/src/app/api/agents/[id]/thread/route.ts +2 -2
- package/src/app/api/agents/route.ts +3 -2
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/connectors/[id]/route.ts +10 -3
- package/src/app/api/connectors/[id]/webhook/route.ts +99 -0
- package/src/app/api/connectors/route.ts +6 -3
- package/src/app/api/credentials/[id]/route.ts +2 -1
- package/src/app/api/credentials/route.ts +2 -2
- package/src/app/api/documents/route.ts +2 -2
- package/src/app/api/files/serve/route.ts +8 -0
- 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 +2 -2
- 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/run/route.ts +2 -2
- 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 -15
- package/src/app/api/providers/route.ts +2 -2
- package/src/app/api/schedules/[id]/route.ts +16 -18
- package/src/app/api/schedules/[id]/run/route.ts +4 -3
- package/src/app/api/schedules/route.ts +2 -2
- package/src/app/api/secrets/[id]/route.ts +16 -17
- package/src/app/api/secrets/route.ts +2 -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 +2 -2
- 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 +2 -2
- package/src/app/api/tasks/[id]/approve/route.ts +2 -1
- package/src/app/api/tasks/[id]/route.ts +6 -5
- package/src/app/api/tasks/route.ts +2 -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/webhooks/[id]/route.ts +29 -31
- package/src/app/api/webhooks/route.ts +2 -2
- package/src/app/page.tsx +3 -24
- package/src/cli/index.js +28 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/spec.js +2 -0
- package/src/components/agents/agent-list.tsx +3 -1
- package/src/components/agents/agent-sheet.tsx +116 -14
- package/src/components/chat/chat-area.tsx +27 -4
- package/src/components/chat/chat-header.tsx +141 -29
- package/src/components/chat/tool-call-bubble.tsx +9 -3
- package/src/components/chat/voice-overlay.tsx +80 -0
- package/src/components/connectors/connector-list.tsx +6 -2
- package/src/components/connectors/connector-sheet.tsx +31 -7
- package/src/components/layout/app-layout.tsx +47 -25
- package/src/components/projects/project-list.tsx +122 -0
- package/src/components/projects/project-sheet.tsx +135 -0
- package/src/components/schedules/schedule-list.tsx +3 -1
- package/src/components/sessions/new-session-sheet.tsx +6 -6
- 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 +4 -0
- package/src/components/shared/settings/section-heartbeat.tsx +1 -1
- package/src/components/shared/settings/section-orchestrator.tsx +1 -2
- 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 +2 -1
- package/src/components/tasks/task-list.tsx +5 -2
- 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/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 +14 -1
- package/src/lib/providers/index.ts +6 -0
- package/src/lib/providers/ollama.ts +9 -1
- package/src/lib/providers/openai.ts +9 -1
- 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 +38 -4
- 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 +392 -3
- 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/execution-log.ts +3 -3
- package/src/lib/server/heartbeat-service.ts +1 -1
- package/src/lib/server/knowledge-db.test.ts +2 -33
- package/src/lib/server/main-agent-loop.ts +6 -6
- 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 +30 -9
- package/src/lib/server/orchestrator.ts +4 -4
- package/src/lib/server/process-manager.ts +2 -2
- package/src/lib/server/queue.ts +22 -10
- 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 +2 -2
- 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 +3 -3
- package/src/lib/server/session-tools/file.ts +176 -3
- package/src/lib/server/session-tools/index.ts +2 -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 +33 -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.ts +12 -0
- package/src/lib/server/stream-agent-chat.ts +29 -0
- package/src/lib/server/task-result.test.ts +44 -0
- package/src/lib/server/task-result.ts +14 -0
- package/src/lib/tool-definitions.ts +5 -3
- package/src/lib/tts-stream.ts +130 -0
- package/src/lib/view-routes.ts +28 -0
- package/src/proxy.ts +3 -0
- package/src/stores/use-app-store.ts +28 -1
- package/src/stores/use-chat-store.ts +9 -1
- package/src/types/index.ts +27 -2
|
@@ -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,5 +1,5 @@
|
|
|
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
5
|
export const dynamic = 'force-dynamic'
|
|
@@ -12,7 +12,7 @@ export async function GET(_req: Request) {
|
|
|
12
12
|
export async function POST(req: Request) {
|
|
13
13
|
const body = await req.json()
|
|
14
14
|
const skills = loadSkills()
|
|
15
|
-
const id =
|
|
15
|
+
const id = genId()
|
|
16
16
|
const normalized = normalizeSkillPayload(body)
|
|
17
17
|
skills[id] = {
|
|
18
18
|
id,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
2
|
import { loadTasks, saveTasks, loadAgents } from '@/lib/server/storage'
|
|
3
|
+
import { notFound } from '@/lib/server/collection-helpers'
|
|
3
4
|
import { notify } from '@/lib/server/ws-hub'
|
|
4
5
|
import { getCheckpointSaver } from '@/lib/server/langgraph-checkpoint'
|
|
5
6
|
|
|
@@ -10,7 +11,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
10
11
|
|
|
11
12
|
const tasks = loadTasks()
|
|
12
13
|
const task = tasks[id]
|
|
13
|
-
if (!task) return
|
|
14
|
+
if (!task) return notFound()
|
|
14
15
|
if (!task.pendingApproval) {
|
|
15
16
|
return NextResponse.json({ error: 'No pending approval on this task' }, { status: 400 })
|
|
16
17
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
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'
|
|
@@ -13,7 +14,7 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
13
14
|
|
|
14
15
|
const { id } = await params
|
|
15
16
|
const tasks = loadTasks()
|
|
16
|
-
if (!tasks[id]) return
|
|
17
|
+
if (!tasks[id]) return notFound()
|
|
17
18
|
return NextResponse.json(tasks[id])
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -21,7 +22,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
21
22
|
const { id } = await params
|
|
22
23
|
const body = await req.json()
|
|
23
24
|
const tasks = loadTasks()
|
|
24
|
-
if (!tasks[id]) return
|
|
25
|
+
if (!tasks[id]) return notFound()
|
|
25
26
|
|
|
26
27
|
const prevStatus = tasks[id].status
|
|
27
28
|
|
|
@@ -55,7 +56,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
55
56
|
tasks[id].error = formatValidationFailure(validation.reasons).slice(0, 500)
|
|
56
57
|
if (!tasks[id].comments) tasks[id].comments = []
|
|
57
58
|
tasks[id].comments.push({
|
|
58
|
-
id:
|
|
59
|
+
id: genId(),
|
|
59
60
|
author: 'System',
|
|
60
61
|
text: `Completion validation failed.\n\n${validation.reasons.map((r) => `- ${r}`).join('\n')}`,
|
|
61
62
|
createdAt: Date.now(),
|
|
@@ -88,7 +89,7 @@ export async function PUT(req: Request, { params }: { params: Promise<{ id: stri
|
|
|
88
89
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
89
90
|
const { id } = await params
|
|
90
91
|
const tasks = loadTasks()
|
|
91
|
-
if (!tasks[id]) return
|
|
92
|
+
if (!tasks[id]) return notFound()
|
|
92
93
|
|
|
93
94
|
// Soft delete: move to archived status instead of hard delete
|
|
94
95
|
tasks[id].status = 'archived'
|
|
@@ -1,5 +1,5 @@
|
|
|
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'
|
|
@@ -54,7 +54,7 @@ export async function DELETE(req: Request) {
|
|
|
54
54
|
|
|
55
55
|
export async function POST(req: Request) {
|
|
56
56
|
const body = await req.json()
|
|
57
|
-
const id =
|
|
57
|
+
const id = genId()
|
|
58
58
|
const now = Date.now()
|
|
59
59
|
const tasks = loadTasks()
|
|
60
60
|
const settings = loadSettings()
|
|
@@ -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,8 +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
4
|
import { WORKSPACE_DIR } from '@/lib/server/data-dir'
|
|
5
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 }
|
|
6
10
|
|
|
7
11
|
function normalizeEvents(value: unknown): string[] {
|
|
8
12
|
if (!Array.isArray(value)) return []
|
|
@@ -22,36 +26,30 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
|
|
|
22
26
|
const { id } = await params
|
|
23
27
|
const webhooks = loadWebhooks()
|
|
24
28
|
const webhook = webhooks[id]
|
|
25
|
-
if (!webhook) return
|
|
29
|
+
if (!webhook) return notFound('Webhook not found')
|
|
26
30
|
return NextResponse.json(webhook)
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
30
34
|
const { id } = await params
|
|
31
35
|
const body = await req.json().catch(() => ({}))
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
webhooks[id] = webhook
|
|
45
|
-
saveWebhooks(webhooks)
|
|
46
|
-
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)
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
|
|
50
51
|
const { id } = await params
|
|
51
|
-
|
|
52
|
-
if (!webhooks[id]) return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
|
53
|
-
delete webhooks[id]
|
|
54
|
-
saveWebhooks(webhooks)
|
|
52
|
+
if (!deleteItem(ops, id)) return notFound('Webhook not found')
|
|
55
53
|
return NextResponse.json({ ok: true })
|
|
56
54
|
}
|
|
57
55
|
|
|
@@ -59,10 +57,10 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
59
57
|
const { id } = await params
|
|
60
58
|
const webhooks = loadWebhooks()
|
|
61
59
|
const webhook = webhooks[id]
|
|
62
|
-
if (!webhook) return
|
|
60
|
+
if (!webhook) return notFound('Webhook not found')
|
|
63
61
|
if (webhook.isEnabled === false) {
|
|
64
|
-
appendWebhookLog(
|
|
65
|
-
id:
|
|
62
|
+
appendWebhookLog(genId(8), {
|
|
63
|
+
id: genId(8), webhookId: id, event: 'unknown',
|
|
66
64
|
payload: '', status: 'error', error: 'Webhook is disabled', timestamp: Date.now(),
|
|
67
65
|
})
|
|
68
66
|
return NextResponse.json({ error: 'Webhook is disabled' }, { status: 409 })
|
|
@@ -73,8 +71,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
73
71
|
const url = new URL(req.url)
|
|
74
72
|
const provided = req.headers.get('x-webhook-secret') || url.searchParams.get('secret') || ''
|
|
75
73
|
if (provided !== secret) {
|
|
76
|
-
appendWebhookLog(
|
|
77
|
-
id:
|
|
74
|
+
appendWebhookLog(genId(8), {
|
|
75
|
+
id: genId(8), webhookId: id, event: 'unknown',
|
|
78
76
|
payload: '', status: 'error', error: 'Invalid webhook secret', timestamp: Date.now(),
|
|
79
77
|
})
|
|
80
78
|
return NextResponse.json({ error: 'Invalid webhook secret' }, { status: 401 })
|
|
@@ -122,8 +120,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
122
120
|
const agents = loadAgents()
|
|
123
121
|
const agent = webhook.agentId ? agents[webhook.agentId] : null
|
|
124
122
|
if (!agent) {
|
|
125
|
-
appendWebhookLog(
|
|
126
|
-
id:
|
|
123
|
+
appendWebhookLog(genId(8), {
|
|
124
|
+
id: genId(8), webhookId: id, event: incomingEvent,
|
|
127
125
|
payload: (rawBody || '').slice(0, 2000), status: 'error', error: 'Webhook agent is not configured or missing', timestamp: Date.now(),
|
|
128
126
|
})
|
|
129
127
|
return NextResponse.json({ error: 'Webhook agent is not configured or missing' }, { status: 400 })
|
|
@@ -133,7 +131,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
133
131
|
const sessionName = `webhook:${id}`
|
|
134
132
|
let session = Object.values(sessions).find((s: any) => s.name === sessionName && s.agentId === agent.id) as any
|
|
135
133
|
if (!session) {
|
|
136
|
-
const sessionId =
|
|
134
|
+
const sessionId = genId()
|
|
137
135
|
const now = Date.now()
|
|
138
136
|
session = {
|
|
139
137
|
id: sessionId,
|
|
@@ -189,8 +187,8 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
189
187
|
mode: 'followup',
|
|
190
188
|
})
|
|
191
189
|
|
|
192
|
-
appendWebhookLog(
|
|
193
|
-
id:
|
|
190
|
+
appendWebhookLog(genId(8), {
|
|
191
|
+
id: genId(8), webhookId: id, event: incomingEvent,
|
|
194
192
|
payload: (rawBody || '').slice(0, 2000), status: 'success',
|
|
195
193
|
sessionId: session.id, runId: run.runId, timestamp: Date.now(),
|
|
196
194
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
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
4
|
export const dynamic = 'force-dynamic'
|
|
@@ -19,7 +19,7 @@ export async function GET(_req: Request) {
|
|
|
19
19
|
export async function POST(req: Request) {
|
|
20
20
|
const body = await req.json().catch(() => ({}))
|
|
21
21
|
const webhooks = loadWebhooks()
|
|
22
|
-
const id =
|
|
22
|
+
const id = genId()
|
|
23
23
|
const now = Date.now()
|
|
24
24
|
|
|
25
25
|
webhooks[id] = {
|
package/src/app/page.tsx
CHANGED
|
@@ -10,6 +10,7 @@ import { AccessKeyGate } from '@/components/auth/access-key-gate'
|
|
|
10
10
|
import { UserPicker } from '@/components/auth/user-picker'
|
|
11
11
|
import { SetupWizard } from '@/components/auth/setup-wizard'
|
|
12
12
|
import { AppLayout } from '@/components/layout/app-layout'
|
|
13
|
+
import { useViewRouter } from '@/hooks/use-view-router'
|
|
13
14
|
|
|
14
15
|
export default function Home() {
|
|
15
16
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
@@ -17,7 +18,6 @@ export default function Home() {
|
|
|
17
18
|
const hydrated = useAppStore((s) => s._hydrated)
|
|
18
19
|
const hydrate = useAppStore((s) => s.hydrate)
|
|
19
20
|
const loadNetworkInfo = useAppStore((s) => s.loadNetworkInfo)
|
|
20
|
-
const sessions = useAppStore((s) => s.sessions)
|
|
21
21
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
22
22
|
const loadSettings = useAppStore((s) => s.loadSettings)
|
|
23
23
|
|
|
@@ -107,29 +107,6 @@ export default function Home() {
|
|
|
107
107
|
return () => { cancelled = true }
|
|
108
108
|
}, [authenticated, currentUser])
|
|
109
109
|
|
|
110
|
-
// Keep __main__ session for backward compat — create if missing
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (!authenticated || !currentUser) return
|
|
113
|
-
const sessionList = Object.values(sessions)
|
|
114
|
-
const mainSession = sessionList.find((s: any) => s.name === '__main__' && s.user === currentUser)
|
|
115
|
-
if (mainSession) return
|
|
116
|
-
let cancelled = false
|
|
117
|
-
;(async () => {
|
|
118
|
-
try {
|
|
119
|
-
const mainId = `main-${currentUser}`
|
|
120
|
-
await api<any>('POST', '/sessions', {
|
|
121
|
-
id: mainId,
|
|
122
|
-
name: '__main__',
|
|
123
|
-
user: currentUser,
|
|
124
|
-
agentId: 'default',
|
|
125
|
-
heartbeatEnabled: true,
|
|
126
|
-
})
|
|
127
|
-
if (!cancelled) await loadSessions()
|
|
128
|
-
} catch { /* ignore */ }
|
|
129
|
-
})()
|
|
130
|
-
return () => { cancelled = true }
|
|
131
|
-
}, [authenticated, currentUser, sessions, loadSessions])
|
|
132
|
-
|
|
133
110
|
// Check if first-run setup is needed
|
|
134
111
|
useEffect(() => {
|
|
135
112
|
if (!authenticated || !currentUser) return
|
|
@@ -169,6 +146,8 @@ export default function Home() {
|
|
|
169
146
|
return () => window.removeEventListener('sc_auth_required', handler)
|
|
170
147
|
}, [])
|
|
171
148
|
|
|
149
|
+
useViewRouter()
|
|
150
|
+
|
|
172
151
|
if (!hydrated || !authChecked) return null
|
|
173
152
|
if (!authenticated) return <AccessKeyGate onAuthenticated={() => setAuthenticated(true)} />
|
|
174
153
|
if (!currentUser) return <UserPicker />
|
package/src/cli/index.js
CHANGED
|
@@ -55,6 +55,7 @@ const COMMAND_GROUPS = [
|
|
|
55
55
|
cmd('create', 'POST', '/connectors', 'Create connector', { expectsJsonBody: true }),
|
|
56
56
|
cmd('update', 'PUT', '/connectors/:id', 'Update connector', { expectsJsonBody: true }),
|
|
57
57
|
cmd('delete', 'DELETE', '/connectors/:id', 'Delete connector'),
|
|
58
|
+
cmd('webhook', 'POST', '/connectors/:id/webhook', 'Trigger connector webhook ingress', { expectsJsonBody: true }),
|
|
58
59
|
cmd('start', 'PUT', '/connectors/:id', 'Start connector', {
|
|
59
60
|
expectsJsonBody: true,
|
|
60
61
|
defaultBody: { action: 'start' },
|
|
@@ -199,6 +200,16 @@ const COMMAND_GROUPS = [
|
|
|
199
200
|
expectsJsonBody: true,
|
|
200
201
|
waitEntityFrom: 'taskId',
|
|
201
202
|
}),
|
|
203
|
+
cmd('graph', 'GET', '/orchestrator/graph', 'Get orchestrator graph structure'),
|
|
204
|
+
],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'openclaw',
|
|
208
|
+
description: 'OpenClaw discovery and sync',
|
|
209
|
+
commands: [
|
|
210
|
+
cmd('discover', 'GET', '/openclaw/discover', 'Discover OpenClaw gateways'),
|
|
211
|
+
cmd('directory', 'GET', '/openclaw/directory', 'List directory entries from running OpenClaw connectors'),
|
|
212
|
+
cmd('sync', 'POST', '/openclaw/sync', 'Run OpenClaw sync action', { expectsJsonBody: true }),
|
|
202
213
|
],
|
|
203
214
|
},
|
|
204
215
|
{
|
|
@@ -208,6 +219,17 @@ const COMMAND_GROUPS = [
|
|
|
208
219
|
cmd('manage', 'POST', '/preview-server', 'Start/stop/status/detect preview server', { expectsJsonBody: true }),
|
|
209
220
|
],
|
|
210
221
|
},
|
|
222
|
+
{
|
|
223
|
+
name: 'projects',
|
|
224
|
+
description: 'Manage projects',
|
|
225
|
+
commands: [
|
|
226
|
+
cmd('list', 'GET', '/projects', 'List projects'),
|
|
227
|
+
cmd('get', 'GET', '/projects/:id', 'Get project by id'),
|
|
228
|
+
cmd('create', 'POST', '/projects', 'Create project', { expectsJsonBody: true }),
|
|
229
|
+
cmd('update', 'PUT', '/projects/:id', 'Update project', { expectsJsonBody: true }),
|
|
230
|
+
cmd('delete', 'DELETE', '/projects/:id', 'Delete project'),
|
|
231
|
+
],
|
|
232
|
+
},
|
|
211
233
|
{
|
|
212
234
|
name: 'plugins',
|
|
213
235
|
description: 'Manage plugins and marketplace',
|
|
@@ -351,6 +373,7 @@ const COMMAND_GROUPS = [
|
|
|
351
373
|
cmd('update', 'PUT', '/tasks/:id', 'Update task', { expectsJsonBody: true }),
|
|
352
374
|
cmd('delete', 'DELETE', '/tasks/:id', 'Delete task'),
|
|
353
375
|
cmd('purge', 'DELETE', '/tasks', 'Bulk delete tasks', { expectsJsonBody: true }),
|
|
376
|
+
cmd('approve', 'POST', '/tasks/:id/approve', 'Approve or reject a pending tool execution', { expectsJsonBody: true }),
|
|
354
377
|
],
|
|
355
378
|
},
|
|
356
379
|
{
|
|
@@ -362,6 +385,11 @@ const COMMAND_GROUPS = [
|
|
|
362
385
|
responseType: 'binary',
|
|
363
386
|
bodyFlagMap: { text: 'text' },
|
|
364
387
|
}),
|
|
388
|
+
cmd('stream', 'POST', '/tts/stream', 'Generate streaming TTS audio', {
|
|
389
|
+
expectsJsonBody: true,
|
|
390
|
+
responseType: 'binary',
|
|
391
|
+
bodyFlagMap: { text: 'text' },
|
|
392
|
+
}),
|
|
365
393
|
],
|
|
366
394
|
},
|
|
367
395
|
{
|
package/src/cli/index.ts
CHANGED
|
@@ -928,7 +928,7 @@ export function buildProgram(): Command {
|
|
|
928
928
|
connectors
|
|
929
929
|
.command('create')
|
|
930
930
|
.description('Create connector')
|
|
931
|
-
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw)')
|
|
931
|
+
.requiredOption('--platform <platform>', 'Connector platform (discord|telegram|slack|whatsapp|openclaw|bluebubbles|signal|teams|googlechat|matrix)')
|
|
932
932
|
.requiredOption('--agent-id <agentId>', 'Agent id')
|
|
933
933
|
.option('--name <name>', 'Connector name')
|
|
934
934
|
.option('--credential-id <credentialId>', 'Credential id')
|
package/src/cli/spec.js
CHANGED
|
@@ -127,6 +127,7 @@ const COMMAND_GROUPS = {
|
|
|
127
127
|
run: { description: 'Run orchestrator task now', method: 'POST', path: '/orchestrator/run', waitable: true },
|
|
128
128
|
runs: { description: 'List queued/running/completed runs', method: 'GET', path: '/runs' },
|
|
129
129
|
'run-get': { description: 'Get run by id', method: 'GET', path: '/runs/:id', params: ['id'] },
|
|
130
|
+
graph: { description: 'Get orchestrator graph structure', method: 'GET', path: '/orchestrator/graph' },
|
|
130
131
|
},
|
|
131
132
|
},
|
|
132
133
|
plugins: {
|
|
@@ -243,6 +244,7 @@ const COMMAND_GROUPS = {
|
|
|
243
244
|
update: { description: 'Update task', method: 'PUT', path: '/tasks/:id', params: ['id'] },
|
|
244
245
|
delete: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
245
246
|
archive: { description: 'Archive task', method: 'DELETE', path: '/tasks/:id', params: ['id'] },
|
|
247
|
+
approve: { description: 'Approve or reject a pending tool execution', method: 'POST', path: '/tasks/:id/approve', params: ['id'] },
|
|
246
248
|
},
|
|
247
249
|
},
|
|
248
250
|
webhooks: {
|
|
@@ -16,6 +16,7 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
16
16
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
17
17
|
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
18
18
|
const setAgentSheetOpen = useAppStore((s) => s.setAgentSheetOpen)
|
|
19
|
+
const activeProjectFilter = useAppStore((s) => s.activeProjectFilter)
|
|
19
20
|
const [search, setSearch] = useState('')
|
|
20
21
|
const [filter, setFilter] = useState<'all' | 'orchestrator' | 'agent'>('all')
|
|
21
22
|
|
|
@@ -41,10 +42,11 @@ export function AgentList({ inSidebar }: Props) {
|
|
|
41
42
|
if (search && !p.name.toLowerCase().includes(search.toLowerCase())) return false
|
|
42
43
|
if (filter === 'orchestrator' && !p.isOrchestrator) return false
|
|
43
44
|
if (filter === 'agent' && p.isOrchestrator) return false
|
|
45
|
+
if (activeProjectFilter && p.projectId !== activeProjectFilter) return false
|
|
44
46
|
return true
|
|
45
47
|
})
|
|
46
48
|
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
47
|
-
}, [agents, search, filter])
|
|
49
|
+
}, [agents, search, filter, activeProjectFilter])
|
|
48
50
|
|
|
49
51
|
if (!filtered.length && !search) {
|
|
50
52
|
return (
|