@swarmclawai/swarmclaw 1.3.6 → 1.4.2
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 +16 -52
- package/next.config.ts +9 -4
- package/package.json +18 -10
- package/scripts/build-bootstrap-env.mjs +24 -0
- package/scripts/run-next-build.mjs +74 -0
- package/scripts/run-next-typegen.mjs +61 -0
- package/src/app/api/.well-known/agent-card/route.ts +46 -0
- package/src/app/api/a2a/route.ts +56 -0
- package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
- package/src/app/api/approvals/route.test.ts +29 -3
- package/src/app/api/approvals/route.ts +13 -7
- package/src/app/api/chats/[id]/chat/route.test.ts +64 -0
- package/src/app/api/chats/[id]/chat/route.ts +24 -8
- package/src/app/api/chats/[id]/deploy/route.ts +2 -2
- package/src/app/api/chats/chat-route.test.ts +68 -0
- package/src/app/api/connectors/[id]/doctor/route.test.ts +97 -0
- package/src/app/api/connectors/[id]/doctor/route.ts +26 -1
- package/src/app/api/connectors/connector-doctor-route.test.ts +1 -0
- package/src/app/api/logs/route.test.ts +61 -0
- package/src/app/api/logs/route.ts +35 -0
- package/src/app/api/openclaw/sync/route.ts +1 -1
- package/src/app/api/swarmfeed/channels/route.ts +14 -0
- package/src/app/api/swarmfeed/posts/route.ts +60 -0
- package/src/app/api/swarmfeed/route.ts +37 -0
- package/src/app/api/tts/route.test.ts +82 -0
- package/src/app/api/tts/route.ts +13 -6
- package/src/app/api/tts/stream/route.ts +12 -5
- package/src/app/error.tsx +32 -0
- package/src/app/global-error.tsx +33 -0
- package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
- package/src/app/protocols/page.tsx +16 -7
- package/src/app/swarmfeed/page.tsx +7 -0
- package/src/cli/index.js +22 -0
- package/src/cli/spec.js +9 -0
- package/src/components/agents/agent-avatar.tsx +2 -5
- package/src/components/agents/agent-sheet.tsx +10 -0
- package/src/components/auth/access-key-gate.tsx +25 -0
- package/src/components/layout/error-boundary.tsx +12 -30
- package/src/components/layout/error-fallback.tsx +61 -0
- package/src/components/layout/sidebar-rail.tsx +52 -0
- package/src/components/protocols/builder/edge-editor.tsx +43 -0
- package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
- package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
- package/src/components/protocols/builder/edge-types/index.ts +3 -0
- package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
- package/src/components/protocols/builder/node-inspector.tsx +227 -0
- package/src/components/protocols/builder/node-palette.tsx +97 -0
- package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
- package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
- package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
- package/src/components/protocols/builder/node-types/index.ts +9 -0
- package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
- package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
- package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
- package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
- package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
- package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
- package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
- package/src/components/protocols/builder/run-overlay.tsx +29 -0
- package/src/components/protocols/builder/template-gallery.tsx +53 -0
- package/src/components/protocols/builder/validation-panel.tsx +57 -0
- package/src/components/skills/skills-workspace.tsx +1 -9
- package/src/features/protocols/builder/hooks/index.ts +2 -0
- package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
- package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
- package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
- package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
- package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
- package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
- package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
- package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
- package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
- package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
- package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
- package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
- package/src/features/swarmfeed/compose-post.tsx +139 -0
- package/src/features/swarmfeed/feed-page.tsx +136 -0
- package/src/features/swarmfeed/post-card.tsx +114 -0
- package/src/features/swarmfeed/queries.ts +28 -0
- package/src/lib/a2a/agent-card.ts +61 -0
- package/src/lib/a2a/auth.ts +54 -0
- package/src/lib/a2a/client.ts +133 -0
- package/src/lib/a2a/discovery.ts +116 -0
- package/src/lib/a2a/handlers.ts +176 -0
- package/src/lib/a2a/json-rpc-router.ts +38 -0
- package/src/lib/a2a/types.ts +95 -0
- package/src/lib/app/navigation.ts +1 -0
- package/src/lib/app/report-client-error.ts +52 -0
- package/src/lib/app/view-constants.ts +9 -1
- package/src/lib/providers/anthropic.ts +119 -107
- package/src/lib/providers/ollama.ts +34 -14
- package/src/lib/providers/openai.ts +154 -142
- package/src/lib/providers/openclaw.ts +3 -3
- package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
- package/src/lib/server/agents/main-agent-loop.ts +377 -41
- package/src/lib/server/chat-execution/chat-execution.ts +12 -7
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +19 -12
- package/src/lib/server/connectors/swarmdock.ts +1 -1
- package/src/lib/server/extensions.ts +11 -0
- package/src/lib/server/messages/message-repository.ts +31 -0
- package/src/lib/server/openclaw/sync.ts +4 -4
- package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
- package/src/lib/server/protocols/protocol-normalization.ts +1 -0
- package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
- package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
- package/src/lib/server/protocols/protocol-types.ts +1 -0
- package/src/lib/server/provider-health.ts +19 -3
- package/src/lib/server/safe-parse-body.test.ts +32 -0
- package/src/lib/server/safe-parse-body.ts +20 -3
- package/src/lib/server/session-tools/delegate.ts +151 -77
- package/src/lib/server/storage-auth.ts +10 -2
- package/src/lib/server/storage-normalization.ts +11 -0
- package/src/lib/server/storage.ts +113 -4
- package/src/lib/server/working-state/service.test.ts +2 -3
- package/src/lib/server/working-state/service.ts +37 -6
- package/src/lib/swarmfeed-client.ts +157 -0
- package/src/lib/validation/schemas.ts +1 -1
- package/src/stores/slices/data-slice.ts +3 -0
- package/src/stores/use-approval-store.ts +4 -1
- package/src/types/agent.ts +31 -1
- package/src/types/index.ts +1 -0
- package/src/types/protocol.ts +19 -0
- package/src/types/session.ts +1 -1
- package/src/types/swarmfeed.ts +30 -0
- package/tsconfig.json +1 -2
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import test from 'node:test'
|
|
3
|
+
|
|
4
|
+
import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
|
|
5
|
+
|
|
6
|
+
test('tts routes reject malformed JSON with a 400', () => {
|
|
7
|
+
const output = runWithTempDataDir<{
|
|
8
|
+
ttsStatus: number
|
|
9
|
+
ttsPayload: { error?: string }
|
|
10
|
+
streamStatus: number
|
|
11
|
+
streamPayload: { error?: string }
|
|
12
|
+
}>(`
|
|
13
|
+
const ttsRouteMod = await import('./src/app/api/tts/route')
|
|
14
|
+
const ttsStreamRouteMod = await import('./src/app/api/tts/stream/route')
|
|
15
|
+
const ttsRoute = ttsRouteMod.default || ttsRouteMod
|
|
16
|
+
const ttsStreamRoute = ttsStreamRouteMod.default || ttsStreamRouteMod
|
|
17
|
+
|
|
18
|
+
const ttsResponse = await ttsRoute.POST(new Request('http://local/api/tts', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { 'content-type': 'application/json' },
|
|
21
|
+
body: '{bad-json',
|
|
22
|
+
}))
|
|
23
|
+
|
|
24
|
+
const ttsStreamResponse = await ttsStreamRoute.POST(new Request('http://local/api/tts/stream', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'content-type': 'application/json' },
|
|
27
|
+
body: '{bad-json',
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
console.log(JSON.stringify({
|
|
31
|
+
ttsStatus: ttsResponse.status,
|
|
32
|
+
ttsPayload: await ttsResponse.json(),
|
|
33
|
+
streamStatus: ttsStreamResponse.status,
|
|
34
|
+
streamPayload: await ttsStreamResponse.json(),
|
|
35
|
+
}))
|
|
36
|
+
`, { prefix: 'swarmclaw-tts-route-' })
|
|
37
|
+
|
|
38
|
+
assert.equal(output.ttsStatus, 400)
|
|
39
|
+
assert.equal(output.ttsPayload.error, 'Invalid or missing request body')
|
|
40
|
+
assert.equal(output.streamStatus, 400)
|
|
41
|
+
assert.equal(output.streamPayload.error, 'Invalid or missing request body')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('tts routes reject empty text with a validation error', () => {
|
|
45
|
+
const output = runWithTempDataDir<{
|
|
46
|
+
ttsStatus: number
|
|
47
|
+
ttsPayload: { error?: string; issues?: Array<{ path: string; message: string }> }
|
|
48
|
+
streamStatus: number
|
|
49
|
+
streamPayload: { error?: string; issues?: Array<{ path: string; message: string }> }
|
|
50
|
+
}>(`
|
|
51
|
+
const ttsRouteMod = await import('./src/app/api/tts/route')
|
|
52
|
+
const ttsStreamRouteMod = await import('./src/app/api/tts/stream/route')
|
|
53
|
+
const ttsRoute = ttsRouteMod.default || ttsRouteMod
|
|
54
|
+
const ttsStreamRoute = ttsStreamRouteMod.default || ttsStreamRouteMod
|
|
55
|
+
|
|
56
|
+
const ttsResponse = await ttsRoute.POST(new Request('http://local/api/tts', {
|
|
57
|
+
method: 'POST',
|
|
58
|
+
headers: { 'content-type': 'application/json' },
|
|
59
|
+
body: JSON.stringify({ text: ' ' }),
|
|
60
|
+
}))
|
|
61
|
+
|
|
62
|
+
const ttsStreamResponse = await ttsStreamRoute.POST(new Request('http://local/api/tts/stream', {
|
|
63
|
+
method: 'POST',
|
|
64
|
+
headers: { 'content-type': 'application/json' },
|
|
65
|
+
body: JSON.stringify({ text: ' ' }),
|
|
66
|
+
}))
|
|
67
|
+
|
|
68
|
+
console.log(JSON.stringify({
|
|
69
|
+
ttsStatus: ttsResponse.status,
|
|
70
|
+
ttsPayload: await ttsResponse.json(),
|
|
71
|
+
streamStatus: ttsStreamResponse.status,
|
|
72
|
+
streamPayload: await ttsStreamResponse.json(),
|
|
73
|
+
}))
|
|
74
|
+
`, { prefix: 'swarmclaw-tts-route-' })
|
|
75
|
+
|
|
76
|
+
assert.equal(output.ttsStatus, 400)
|
|
77
|
+
assert.equal(output.ttsPayload.error, 'Validation failed')
|
|
78
|
+
assert.deepEqual(output.ttsPayload.issues, [{ path: 'text', message: 'No text provided' }])
|
|
79
|
+
assert.equal(output.streamStatus, 400)
|
|
80
|
+
assert.equal(output.streamPayload.error, 'Validation failed')
|
|
81
|
+
assert.deepEqual(output.streamPayload.issues, [{ path: 'text', message: 'No text provided' }])
|
|
82
|
+
})
|
package/src/app/api/tts/route.ts
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
2
4
|
import { explainElevenLabsError, resolveElevenLabsConfig, synthesizeElevenLabsMp3 } from '@/lib/server/elevenlabs'
|
|
5
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
6
|
+
|
|
7
|
+
const TtsRequestSchema = z.object({
|
|
8
|
+
text: z.string().trim().min(1, 'No text provided'),
|
|
9
|
+
voiceId: z.string().nullable().optional(),
|
|
10
|
+
})
|
|
3
11
|
|
|
4
12
|
export async function POST(req: Request) {
|
|
13
|
+
const { data: body, error } = await safeParseBody(req, TtsRequestSchema)
|
|
14
|
+
if (error) return error
|
|
15
|
+
|
|
5
16
|
try {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return new NextResponse('No text provided', { status: 400 })
|
|
9
|
-
}
|
|
10
|
-
resolveElevenLabsConfig(voiceId)
|
|
11
|
-
const audioBuffer = await synthesizeElevenLabsMp3({ text: String(text || ''), voiceId })
|
|
17
|
+
resolveElevenLabsConfig(body.voiceId)
|
|
18
|
+
const audioBuffer = await synthesizeElevenLabsMp3({ text: body.text, voiceId: body.voiceId })
|
|
12
19
|
return new NextResponse(new Uint8Array(audioBuffer), {
|
|
13
20
|
headers: {
|
|
14
21
|
'Content-Type': 'audio/mpeg',
|
|
@@ -1,12 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
1
3
|
import { explainElevenLabsError, requestElevenLabsMp3Stream } from '@/lib/server/elevenlabs'
|
|
4
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
|
+
|
|
6
|
+
const TtsStreamRequestSchema = z.object({
|
|
7
|
+
text: z.string().trim().min(1, 'No text provided'),
|
|
8
|
+
voiceId: z.string().nullable().optional(),
|
|
9
|
+
})
|
|
2
10
|
|
|
3
11
|
export async function POST(req: Request) {
|
|
12
|
+
const { data: body, error } = await safeParseBody(req, TtsStreamRequestSchema)
|
|
13
|
+
if (error) return error
|
|
14
|
+
|
|
4
15
|
try {
|
|
5
|
-
const { text, voiceId }
|
|
6
|
-
if (!String(text || '').trim()) {
|
|
7
|
-
return new Response('No text provided', { status: 400 })
|
|
8
|
-
}
|
|
9
|
-
const apiRes = await requestElevenLabsMp3Stream({ text: String(text || ''), voiceId })
|
|
16
|
+
const apiRes = await requestElevenLabsMp3Stream({ text: body.text, voiceId: body.voiceId })
|
|
10
17
|
return new Response(apiRes.body, {
|
|
11
18
|
headers: {
|
|
12
19
|
'Content-Type': 'audio/mpeg',
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
import { ErrorFallback } from '@/components/layout/error-fallback'
|
|
6
|
+
import { reportClientError } from '@/lib/app/report-client-error'
|
|
7
|
+
|
|
8
|
+
export default function AppError({
|
|
9
|
+
error,
|
|
10
|
+
reset,
|
|
11
|
+
}: {
|
|
12
|
+
error: Error & { digest?: string }
|
|
13
|
+
reset: () => void
|
|
14
|
+
}) {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
reportClientError({
|
|
17
|
+
source: 'app-error',
|
|
18
|
+
error,
|
|
19
|
+
digest: error.digest,
|
|
20
|
+
})
|
|
21
|
+
}, [error])
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<ErrorFallback
|
|
25
|
+
message="A route-level error interrupted the current view. Try the request again or reload the app."
|
|
26
|
+
primaryLabel="Try Again"
|
|
27
|
+
onPrimaryAction={() => reset()}
|
|
28
|
+
secondaryLabel="Reload"
|
|
29
|
+
onSecondaryAction={() => window.location.reload()}
|
|
30
|
+
/>
|
|
31
|
+
)
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
|
|
5
|
+
import './globals.css'
|
|
6
|
+
|
|
7
|
+
import { ErrorFallback } from '@/components/layout/error-fallback'
|
|
8
|
+
import { reportClientError } from '@/lib/app/report-client-error'
|
|
9
|
+
|
|
10
|
+
export default function GlobalError({
|
|
11
|
+
error,
|
|
12
|
+
}: {
|
|
13
|
+
error: Error & { digest?: string }
|
|
14
|
+
}) {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
reportClientError({
|
|
17
|
+
source: 'global-error',
|
|
18
|
+
error,
|
|
19
|
+
digest: error.digest,
|
|
20
|
+
})
|
|
21
|
+
}, [error])
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<html lang="en" className="dark">
|
|
25
|
+
<body className="antialiased">
|
|
26
|
+
<ErrorFallback
|
|
27
|
+
message="A fatal application error occurred before the normal shell could recover. Reload the app to continue."
|
|
28
|
+
onPrimaryAction={() => window.location.reload()}
|
|
29
|
+
/>
|
|
30
|
+
</body>
|
|
31
|
+
</html>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import { useParams, useRouter } from 'next/navigation'
|
|
5
|
+
import { useProtocolTemplatesQuery } from '@/features/protocols/queries'
|
|
6
|
+
import { useProtocolBuilderStore } from '@/features/protocols/builder/protocol-builder-store'
|
|
7
|
+
import { templateToNodes } from '@/features/protocols/builder/utils/template-to-nodes'
|
|
8
|
+
import { getNodeLayout } from '@/features/protocols/builder/utils/node-position-layout'
|
|
9
|
+
import { ProtocolBuilderCanvas } from '@/components/protocols/builder/protocol-builder-canvas'
|
|
10
|
+
import { useTemplateSync } from '@/features/protocols/builder/hooks/use-template-sync'
|
|
11
|
+
import { useCanvasValidation } from '@/features/protocols/builder/hooks/use-canvas-validation'
|
|
12
|
+
|
|
13
|
+
export default function ProtocolBuilderPage() {
|
|
14
|
+
const params = useParams()
|
|
15
|
+
const router = useRouter()
|
|
16
|
+
const templateId = params.templateId as string
|
|
17
|
+
|
|
18
|
+
const { data: templates, isLoading } = useProtocolTemplatesQuery()
|
|
19
|
+
const loadTemplate = useProtocolBuilderStore((s) => s.loadTemplate)
|
|
20
|
+
const reset = useProtocolBuilderStore((s) => s.reset)
|
|
21
|
+
|
|
22
|
+
// Auto-sync to server
|
|
23
|
+
useTemplateSync(2000)
|
|
24
|
+
|
|
25
|
+
// Validate on changes
|
|
26
|
+
useCanvasValidation()
|
|
27
|
+
|
|
28
|
+
// Load template on mount
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!templates) return
|
|
31
|
+
const template = templates.find((t) => t.id === templateId)
|
|
32
|
+
if (!template) return
|
|
33
|
+
|
|
34
|
+
const { nodes, edges } = templateToNodes(template)
|
|
35
|
+
const positioned = getNodeLayout(nodes, edges)
|
|
36
|
+
loadTemplate(template, positioned, edges)
|
|
37
|
+
}, [templateId, templates, loadTemplate])
|
|
38
|
+
|
|
39
|
+
// Cleanup on unmount
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
return () => reset()
|
|
42
|
+
}, [reset])
|
|
43
|
+
|
|
44
|
+
if (isLoading) {
|
|
45
|
+
return (
|
|
46
|
+
<div className="flex h-screen items-center justify-center">
|
|
47
|
+
<div className="text-sm text-muted-foreground">Loading builder...</div>
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const template = templates?.find((t) => t.id === templateId)
|
|
53
|
+
if (!template) {
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex h-screen flex-col items-center justify-center gap-3">
|
|
56
|
+
<div className="text-sm text-muted-foreground">Template not found</div>
|
|
57
|
+
<button
|
|
58
|
+
onClick={() => router.push('/protocols')}
|
|
59
|
+
className="text-sm text-blue-500 hover:underline"
|
|
60
|
+
>
|
|
61
|
+
Back to protocols
|
|
62
|
+
</button>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div className="flex h-full flex-col">
|
|
69
|
+
{/* Header */}
|
|
70
|
+
<div className="flex items-center justify-between border-b px-4 py-2">
|
|
71
|
+
<div className="flex items-center gap-3">
|
|
72
|
+
<button
|
|
73
|
+
onClick={() => router.push('/protocols')}
|
|
74
|
+
className="text-sm text-muted-foreground hover:text-foreground"
|
|
75
|
+
>
|
|
76
|
+
← Protocols
|
|
77
|
+
</button>
|
|
78
|
+
<span className="text-sm font-semibold">{template.name}</span>
|
|
79
|
+
{template.builtIn && (
|
|
80
|
+
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground">
|
|
81
|
+
Built-in
|
|
82
|
+
</span>
|
|
83
|
+
)}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
|
|
87
|
+
{/* Canvas */}
|
|
88
|
+
<div className="flex-1 p-3">
|
|
89
|
+
<ProtocolBuilderCanvas />
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -657,13 +657,22 @@ export default function ProtocolsPage() {
|
|
|
657
657
|
: editingTemplateId ? 'Save template' : 'Create template'}
|
|
658
658
|
</button>
|
|
659
659
|
{editingTemplateId && (
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
660
|
+
<>
|
|
661
|
+
<button
|
|
662
|
+
type="button"
|
|
663
|
+
onClick={() => router.push(`/protocols/builder/${editingTemplateId}`)}
|
|
664
|
+
className="rounded-[10px] border border-blue-500/20 bg-blue-500/10 px-3 py-2 text-[12px] font-700 text-blue-200 transition-all hover:bg-blue-500/14 cursor-pointer"
|
|
665
|
+
>
|
|
666
|
+
Visual Builder
|
|
667
|
+
</button>
|
|
668
|
+
<button
|
|
669
|
+
type="button"
|
|
670
|
+
onClick={() => void handleDeleteTemplate(editingTemplateId)}
|
|
671
|
+
className="rounded-[10px] border border-red-500/20 bg-red-500/10 px-3 py-2 text-[12px] font-700 text-red-200 transition-all hover:bg-red-500/14 cursor-pointer"
|
|
672
|
+
>
|
|
673
|
+
{templatePending === `delete:${editingTemplateId}` ? 'Deleting…' : 'Delete template'}
|
|
674
|
+
</button>
|
|
675
|
+
</>
|
|
667
676
|
)}
|
|
668
677
|
</div>
|
|
669
678
|
</div>
|
package/src/cli/index.js
CHANGED
|
@@ -220,6 +220,15 @@ const COMMAND_GROUPS = [
|
|
|
220
220
|
cmd('suite', 'POST', '/eval/suite', 'Run a full eval suite against an agent', { expectsJsonBody: true }),
|
|
221
221
|
],
|
|
222
222
|
},
|
|
223
|
+
{
|
|
224
|
+
name: 'a2a',
|
|
225
|
+
description: 'A2A Protocol gateway',
|
|
226
|
+
commands: [
|
|
227
|
+
cmd('send', 'POST', '/a2a', 'Send a JSON-RPC request to the A2A endpoint', { expectsJsonBody: true }),
|
|
228
|
+
cmd('agent-card', 'GET', '/.well-known/agent-card', 'Get agent card for a SwarmClaw agent'),
|
|
229
|
+
cmd('task-status', 'GET', '/a2a/tasks/:taskId/status', 'Check A2A task status'),
|
|
230
|
+
],
|
|
231
|
+
},
|
|
223
232
|
{
|
|
224
233
|
name: 'external-agents',
|
|
225
234
|
description: 'Manage external agent runtimes',
|
|
@@ -289,6 +298,9 @@ const COMMAND_GROUPS = [
|
|
|
289
298
|
commands: [
|
|
290
299
|
cmd('list', 'GET', '/logs', 'List logs (use --query lines=200, --query level=INFO,ERROR)'),
|
|
291
300
|
cmd('clear', 'DELETE', '/logs', 'Clear logs file'),
|
|
301
|
+
cmd('report', 'POST', '/logs', 'Write a client/browser error entry to the application log', {
|
|
302
|
+
expectsJsonBody: true,
|
|
303
|
+
}),
|
|
292
304
|
],
|
|
293
305
|
},
|
|
294
306
|
{
|
|
@@ -784,6 +796,16 @@ const COMMAND_GROUPS = [
|
|
|
784
796
|
cmd('delete', 'DELETE', '/goals/:id', 'Delete a goal'),
|
|
785
797
|
],
|
|
786
798
|
},
|
|
799
|
+
{
|
|
800
|
+
name: 'swarmfeed',
|
|
801
|
+
description: 'SwarmFeed social network',
|
|
802
|
+
commands: [
|
|
803
|
+
cmd('feed', 'GET', '/swarmfeed', 'Get SwarmFeed timeline'),
|
|
804
|
+
cmd('channels', 'GET', '/swarmfeed/channels', 'List SwarmFeed channels'),
|
|
805
|
+
cmd('posts', 'GET', '/swarmfeed/posts', 'Get recent posts'),
|
|
806
|
+
cmd('post', 'POST', '/swarmfeed/posts', 'Create a post', { expectsJsonBody: true }),
|
|
807
|
+
],
|
|
808
|
+
},
|
|
787
809
|
]
|
|
788
810
|
|
|
789
811
|
const GROUP_MAP = new Map(COMMAND_GROUPS.map((group) => [group.name, group]))
|
package/src/cli/spec.js
CHANGED
|
@@ -176,6 +176,14 @@ const COMMAND_GROUPS = {
|
|
|
176
176
|
heartbeat: { description: 'Record an external agent heartbeat', method: 'POST', path: '/external-agents/:id/heartbeat', params: ['id'] },
|
|
177
177
|
},
|
|
178
178
|
},
|
|
179
|
+
a2a: {
|
|
180
|
+
description: 'A2A Protocol gateway',
|
|
181
|
+
commands: {
|
|
182
|
+
send: { description: 'Send a JSON-RPC request to the A2A endpoint', method: 'POST', path: '/a2a' },
|
|
183
|
+
'agent-card': { description: 'Get agent card for a SwarmClaw agent', method: 'GET', path: '/.well-known/agent-card' },
|
|
184
|
+
'task-status': { description: 'Check A2A task status', method: 'GET', path: '/a2a/tasks/:taskId/status', params: ['taskId'] },
|
|
185
|
+
},
|
|
186
|
+
},
|
|
179
187
|
uploads: {
|
|
180
188
|
description: 'Manage uploaded artifacts',
|
|
181
189
|
commands: {
|
|
@@ -207,6 +215,7 @@ const COMMAND_GROUPS = {
|
|
|
207
215
|
commands: {
|
|
208
216
|
list: { description: 'Fetch logs (supports --query lines=200,level=INFO)', method: 'GET', path: '/logs' },
|
|
209
217
|
clear: { description: 'Clear log file', method: 'DELETE', path: '/logs' },
|
|
218
|
+
report: { description: 'Write a client/browser error entry to the application log', method: 'POST', path: '/logs' },
|
|
210
219
|
},
|
|
211
220
|
},
|
|
212
221
|
|
|
@@ -2,13 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { useMemo } from 'react'
|
|
4
4
|
import multiavatar from '@multiavatar/multiavatar'
|
|
5
|
+
import DOMPurify from 'isomorphic-dompurify'
|
|
5
6
|
|
|
6
|
-
/** Strip scripts/event handlers from SVG to prevent XSS */
|
|
7
7
|
function sanitizeSvg(svg: string): string {
|
|
8
|
-
return svg
|
|
9
|
-
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
10
|
-
.replace(/\bon\w+\s*=\s*"[^"]*"/gi, '')
|
|
11
|
-
.replace(/\bon\w+\s*=\s*'[^']*'/gi, '')
|
|
8
|
+
return DOMPurify.sanitize(svg, { USE_PROFILES: { svg: true, svgFilters: true } })
|
|
12
9
|
}
|
|
13
10
|
|
|
14
11
|
interface Props {
|
|
@@ -28,6 +28,7 @@ import { errorMessage } from '@/lib/shared-utils'
|
|
|
28
28
|
import { getDefaultAgentToolIds } from '@/lib/agent-default-tools'
|
|
29
29
|
import { getEnabledExtensionIds, getEnabledToolIds } from '@/lib/capability-selection'
|
|
30
30
|
import { buildAgentSelectableProviders, resolveAgentSelectableProviderCredentials } from '@/lib/agent-provider-options'
|
|
31
|
+
import { AgentSocialSettings } from '@/features/swarmfeed/agent-social-settings'
|
|
31
32
|
|
|
32
33
|
const HB_PRESETS = [1800, 3600, 7200, 21600, 43200] as const
|
|
33
34
|
const FALLBACK_ELEVENLABS_VOICE_ID = 'JBFqnCBsd6RMkjVDRZzb'
|
|
@@ -1973,6 +1974,15 @@ export function AgentSheet() {
|
|
|
1973
1974
|
</SectionCard>
|
|
1974
1975
|
)}
|
|
1975
1976
|
|
|
1977
|
+
{editing && (
|
|
1978
|
+
<SectionCard
|
|
1979
|
+
title="Social Network"
|
|
1980
|
+
description="SwarmFeed integration — let this agent post and engage on the social feed."
|
|
1981
|
+
>
|
|
1982
|
+
<AgentSocialSettings agent={editing} />
|
|
1983
|
+
</SectionCard>
|
|
1984
|
+
)}
|
|
1985
|
+
|
|
1976
1986
|
{!WORKER_ONLY_PROVIDER_IDS.has(provider) && (
|
|
1977
1987
|
<AdvancedSettingsSection
|
|
1978
1988
|
open={showAdvancedSettings}
|
|
@@ -9,6 +9,11 @@ interface AccessKeyGateProps {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const AUTH_CHECK_TIMEOUT_MS = 8_000
|
|
12
|
+
const NETWORK_LINKS = [
|
|
13
|
+
{ href: 'https://www.swarmdock.ai', label: 'SwarmDock' },
|
|
14
|
+
{ href: 'https://swarmrecall.ai', label: 'SwarmRecall' },
|
|
15
|
+
{ href: 'https://swarmrelay.ai', label: 'SwarmRelay' },
|
|
16
|
+
]
|
|
12
17
|
|
|
13
18
|
function isExpectedAuthCheckError(err: unknown): boolean {
|
|
14
19
|
return isAbortError(err) || isTimeoutError(err)
|
|
@@ -421,6 +426,26 @@ export function AccessKeyGate({ onAuthenticated }: AccessKeyGateProps) {
|
|
|
421
426
|
</form>
|
|
422
427
|
</>
|
|
423
428
|
)}
|
|
429
|
+
|
|
430
|
+
<div className="mt-10 border-t border-white/[0.06] pt-5" style={{ animation: 'fade-up 0.6s var(--ease-spring) 0.35s both' }}>
|
|
431
|
+
<p className="text-[10px] font-700 uppercase tracking-[0.18em] text-text-3/55">
|
|
432
|
+
Network
|
|
433
|
+
</p>
|
|
434
|
+
<div className="mt-3 flex flex-wrap items-center justify-center gap-2.5">
|
|
435
|
+
{NETWORK_LINKS.map((link) => (
|
|
436
|
+
<a
|
|
437
|
+
key={link.href}
|
|
438
|
+
href={link.href}
|
|
439
|
+
target="_blank"
|
|
440
|
+
rel="noopener noreferrer"
|
|
441
|
+
className="rounded-full border border-white/[0.08] bg-white/[0.03] px-3 py-1.5 text-[12px] text-text-3
|
|
442
|
+
no-underline transition-all duration-200 hover:border-white/[0.14] hover:bg-white/[0.06] hover:text-text"
|
|
443
|
+
>
|
|
444
|
+
{link.label}
|
|
445
|
+
</a>
|
|
446
|
+
))}
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
424
449
|
</div>
|
|
425
450
|
</div>
|
|
426
451
|
)
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
import { Component } from 'react'
|
|
4
4
|
import type { ReactNode, ErrorInfo } from 'react'
|
|
5
5
|
|
|
6
|
+
import { reportClientError } from '@/lib/app/report-client-error'
|
|
7
|
+
import { ErrorFallback } from '@/components/layout/error-fallback'
|
|
8
|
+
|
|
6
9
|
export class ErrorBoundary extends Component<{ children: ReactNode }, { hasError: boolean }> {
|
|
7
10
|
constructor(props: { children: ReactNode }) {
|
|
8
11
|
super(props)
|
|
@@ -15,41 +18,20 @@ export class ErrorBoundary extends Component<{ children: ReactNode }, { hasError
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
|
18
|
-
|
|
21
|
+
reportClientError({
|
|
22
|
+
source: 'error-boundary',
|
|
23
|
+
error,
|
|
24
|
+
componentStack: info.componentStack,
|
|
25
|
+
})
|
|
19
26
|
}
|
|
20
27
|
|
|
21
28
|
render() {
|
|
22
29
|
if (this.state.hasError) {
|
|
23
30
|
return (
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
<circle cx="12" cy="12" r="10" />
|
|
29
|
-
<line x1="12" y1="8" x2="12" y2="12" />
|
|
30
|
-
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
31
|
-
</svg>
|
|
32
|
-
</div>
|
|
33
|
-
<h2 className="font-display text-[22px] font-700 text-text mb-2 tracking-[-0.02em]">
|
|
34
|
-
Something went wrong
|
|
35
|
-
</h2>
|
|
36
|
-
<p className="text-[14px] text-text-3 mb-6">
|
|
37
|
-
An unexpected error occurred. Try reloading the page.
|
|
38
|
-
</p>
|
|
39
|
-
<button
|
|
40
|
-
onClick={() => window.location.reload()}
|
|
41
|
-
className="inline-flex items-center gap-2 px-6 py-3 rounded-[12px] border-none bg-accent-bright text-white text-[14px] font-600 cursor-pointer
|
|
42
|
-
hover:brightness-110 active:scale-[0.97] transition-all shadow-[0_4px_16px_rgba(99,102,241,0.2)]"
|
|
43
|
-
style={{ fontFamily: 'inherit' }}
|
|
44
|
-
>
|
|
45
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
46
|
-
<polyline points="23 4 23 10 17 10" />
|
|
47
|
-
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
48
|
-
</svg>
|
|
49
|
-
Reload
|
|
50
|
-
</button>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
31
|
+
<ErrorFallback
|
|
32
|
+
message="An unexpected dashboard error occurred. Reload the page to recover."
|
|
33
|
+
onPrimaryAction={() => window.location.reload()}
|
|
34
|
+
/>
|
|
53
35
|
)
|
|
54
36
|
}
|
|
55
37
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
type ErrorFallbackProps = {
|
|
4
|
+
title?: string
|
|
5
|
+
message?: string
|
|
6
|
+
primaryLabel?: string
|
|
7
|
+
onPrimaryAction?: () => void
|
|
8
|
+
secondaryLabel?: string
|
|
9
|
+
onSecondaryAction?: () => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ErrorFallback({
|
|
13
|
+
title = 'Something went wrong',
|
|
14
|
+
message = 'An unexpected error occurred. Try again or reload the page.',
|
|
15
|
+
primaryLabel = 'Reload',
|
|
16
|
+
onPrimaryAction,
|
|
17
|
+
secondaryLabel,
|
|
18
|
+
onSecondaryAction,
|
|
19
|
+
}: ErrorFallbackProps) {
|
|
20
|
+
return (
|
|
21
|
+
<div className="flex min-h-[50vh] flex-1 flex-col items-center justify-center bg-bg px-8">
|
|
22
|
+
<div className="max-w-[420px] text-center">
|
|
23
|
+
<div className="mx-auto mb-5 flex h-14 w-14 items-center justify-center rounded-[16px] border border-red-500/20 bg-red-500/10">
|
|
24
|
+
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-red-400">
|
|
25
|
+
<circle cx="12" cy="12" r="10" />
|
|
26
|
+
<line x1="12" y1="8" x2="12" y2="12" />
|
|
27
|
+
<line x1="12" y1="16" x2="12.01" y2="16" />
|
|
28
|
+
</svg>
|
|
29
|
+
</div>
|
|
30
|
+
<h2 className="mb-2 font-display text-[22px] font-700 tracking-[-0.02em] text-text">
|
|
31
|
+
{title}
|
|
32
|
+
</h2>
|
|
33
|
+
<p className="mb-6 text-[14px] text-text-3">
|
|
34
|
+
{message}
|
|
35
|
+
</p>
|
|
36
|
+
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
37
|
+
<button
|
|
38
|
+
onClick={onPrimaryAction}
|
|
39
|
+
className="inline-flex cursor-pointer items-center gap-2 rounded-[12px] border-none bg-accent-bright px-6 py-3 text-[14px] font-600 text-white shadow-[0_4px_16px_rgba(99,102,241,0.2)] transition-all hover:brightness-110 active:scale-[0.97]"
|
|
40
|
+
style={{ fontFamily: 'inherit' }}
|
|
41
|
+
>
|
|
42
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
43
|
+
<polyline points="23 4 23 10 17 10" />
|
|
44
|
+
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
|
|
45
|
+
</svg>
|
|
46
|
+
{primaryLabel}
|
|
47
|
+
</button>
|
|
48
|
+
{secondaryLabel && onSecondaryAction ? (
|
|
49
|
+
<button
|
|
50
|
+
onClick={onSecondaryAction}
|
|
51
|
+
className="inline-flex cursor-pointer items-center rounded-[12px] border border-border bg-transparent px-5 py-3 text-[14px] font-600 text-text transition-colors hover:bg-panel/60"
|
|
52
|
+
style={{ fontFamily: 'inherit' }}
|
|
53
|
+
>
|
|
54
|
+
{secondaryLabel}
|
|
55
|
+
</button>
|
|
56
|
+
) : null}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
)
|
|
61
|
+
}
|