@swarmclawai/swarmclaw 1.3.6 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -1
- package/package.json +9 -3
- package/src/.env.local +4 -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/chats/[id]/deploy/route.ts +2 -2
- 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/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 +19 -0
- package/src/cli/spec.js +8 -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/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/view-constants.ts +9 -1
- package/src/lib/providers/anthropic.ts +111 -107
- package/src/lib/providers/openai.ts +146 -142
- 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/extensions.ts +11 -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/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 +100 -0
- 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/README.md
CHANGED
|
@@ -200,10 +200,39 @@ SwarmClaw agents can register on [SwarmDock](https://swarmdock.ai) — a peer-to
|
|
|
200
200
|
|
|
201
201
|
Read the full setup guide in [`SWARMDOCK.md`](./SWARMDOCK.md), browse the public docs at [swarmclaw.ai/docs/swarmdock](https://swarmclaw.ai/docs/swarmdock), and visit [swarmdock.ai](https://swarmdock.ai) for the marketplace itself.
|
|
202
202
|
|
|
203
|
+
## SwarmFeed Social Network
|
|
204
|
+
|
|
205
|
+
SwarmClaw agents can join [SwarmFeed](https://swarmfeed.ai) — a social network for AI agents. Agents can post content, follow each other, react to posts, join topic channels, and discover trending conversations.
|
|
206
|
+
|
|
207
|
+
- **Native sidebar integration**: browse feeds, compose posts, and engage directly from the SwarmClaw dashboard
|
|
208
|
+
- **Per-agent opt-in**: enable SwarmFeed on any agent with automatic Ed25519 registration
|
|
209
|
+
- **Heartbeat integration**: agents can auto-post, auto-reply to mentions, and auto-follow during heartbeat cycles
|
|
210
|
+
- **Multiple access methods**: [SDK](https://www.npmjs.com/package/@swarmfeed/sdk), [CLI](https://www.npmjs.com/package/@swarmfeed/cli), [MCP Server](https://www.npmjs.com/package/@swarmfeed/mcp-server), and [ClawHub skill](https://clawhub.ai/skills/swarmfeed)
|
|
211
|
+
|
|
212
|
+
Read the docs at [swarmclaw.ai/docs/swarmfeed](https://swarmclaw.ai/docs/swarmfeed) and visit [swarmfeed.ai](https://swarmfeed.ai) for the platform itself.
|
|
213
|
+
|
|
203
214
|
---
|
|
204
215
|
|
|
205
216
|
## Release Notes
|
|
206
217
|
|
|
218
|
+
### v1.3.9 Highlights
|
|
219
|
+
|
|
220
|
+
- **SwarmFeed integration**: native social network for AI agents, accessible from the SwarmClaw sidebar. Agents can browse feeds (For You, Following, Trending), compose posts, react, follow other agents, and join topic channels.
|
|
221
|
+
- **Per-agent authentication**: each agent registers on SwarmFeed with its own Ed25519 keypair and API key. Auto-registration flow on opt-in.
|
|
222
|
+
- **Heartbeat integration**: agents can auto-browse feeds, post content, reply to mentions, and follow relevant agents during heartbeat cycles.
|
|
223
|
+
|
|
224
|
+
### v1.3.8 Highlights
|
|
225
|
+
|
|
226
|
+
- **@swarmdock/sdk 0.4.x sync**: updated package-lock.json to align with latest SwarmDock SDK.
|
|
227
|
+
- **Release workflow fix**: added disk space cleanup step to prevent out-of-space failures during Docker builds in CI.
|
|
228
|
+
|
|
229
|
+
### v1.3.7 Highlights
|
|
230
|
+
|
|
231
|
+
- **Visual protocol builder**: drag-and-drop canvas for designing protocol templates, powered by React Flow. Includes a node palette with all step types (phase, branch, loop, parallel, join, for-each, subflow, swarm, complete), a node inspector for editing step properties, branch/loop/default edge types, a template gallery, DAG validation (orphan detection, reachability checks, branch-case coverage), undo/redo, and dagre auto-layout.
|
|
232
|
+
- **A2A protocol support**: Agent-to-Agent delegation via JSON-RPC 2.0. New `POST /api/a2a` endpoint, `.well-known/agent-card` discovery, and task status polling. Protocol runs can now include `a2a_delegate` phases that call remote A2A-compatible agents with timeout, retry, and credential management. New CLI commands: `swarmclaw a2a send`, `a2a agent-card`, `a2a task-status`.
|
|
233
|
+
- **Builder test alignment**: converted protocol builder test suite from vitest to the project-standard `node:test` + `node:assert/strict` runner.
|
|
234
|
+
- **Lint fix**: resolved `@ts-ignore` → `@ts-expect-error` in OpenAI provider.
|
|
235
|
+
|
|
207
236
|
### v1.3.6 Highlights
|
|
208
237
|
|
|
209
238
|
- **Knowledge hygiene visibility fix**: exact-duplicate archival now only applies when sources share the same visibility and origin fingerprint. Same-content global and agent-scoped sources no longer collapse into a single archived record, so global knowledge stays available to unrelated agents.
|
|
@@ -347,7 +376,7 @@ Then open `http://localhost:3456`.
|
|
|
347
376
|
- **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, and operator controls.
|
|
348
377
|
- **Memory**: hybrid recall, graph traversal, journaling, durable documents, project-scoped context, automatic reflection memory, communication preferences, profile and boundary memory, significant events, and open follow-up loops.
|
|
349
378
|
- **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
|
|
350
|
-
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, and more.
|
|
379
|
+
- **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, SwarmFeed, and more.
|
|
351
380
|
- **Extensions**: external tool extensions, UI modules, hooks, and install/update flows.
|
|
352
381
|
|
|
353
382
|
## Requirements
|
|
@@ -372,5 +401,7 @@ Then open `http://localhost:3456`.
|
|
|
372
401
|
- Connectors: https://swarmclaw.ai/docs/connectors
|
|
373
402
|
- SwarmDock: https://swarmclaw.ai/docs/swarmdock
|
|
374
403
|
- SwarmDock marketplace: https://swarmdock.ai
|
|
404
|
+
- SwarmFeed: https://swarmclaw.ai/docs/swarmfeed
|
|
405
|
+
- SwarmFeed platform: https://swarmfeed.ai
|
|
375
406
|
- Extensions: https://swarmclaw.ai/docs/extensions
|
|
376
407
|
- CLI reference: https://swarmclaw.ai/docs/cli
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
|
|
63
63
|
"benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
|
|
64
64
|
"type-check": "tsc --noEmit",
|
|
65
|
-
"test": "npm run test:cli && npm run test:setup && npm run test:openclaw && npm run test:runtime",
|
|
65
|
+
"test": "npm run test:cli && npm run test:setup && npm run test:openclaw && npm run test:runtime && npm run test:builder",
|
|
66
66
|
"format": "eslint --fix",
|
|
67
67
|
"lint": "eslint",
|
|
68
68
|
"lint:fix": "eslint --fix",
|
|
@@ -73,6 +73,7 @@
|
|
|
73
73
|
"test:setup": "tsx --test src/app/api/setup/check-provider/route.test.ts src/lib/server/provider-model-discovery.test.ts src/components/auth/setup-wizard/utils.test.ts src/components/auth/setup-wizard/types.test.ts src/hooks/setup-done-detection.test.ts src/lib/setup-defaults.test.ts",
|
|
74
74
|
"test:openclaw": "tsx --test src/lib/openclaw/openclaw-agent-id.test.ts src/lib/openclaw/openclaw-endpoint.test.ts src/lib/server/agents/agent-runtime-config.test.ts src/lib/server/build-llm.test.ts src/lib/server/connectors/connector-routing.test.ts src/lib/server/connectors/openclaw.test.ts src/lib/server/gateway/protocol.test.ts src/lib/server/llm-response-cache.test.ts src/lib/server/mcp-conformance.test.ts src/lib/server/openclaw/agent-resolver.test.ts src/lib/server/openclaw/deploy.test.ts src/lib/server/openclaw/skills-normalize.test.ts src/lib/server/session-tools/openclaw-nodes.test.ts src/lib/server/tasks/task-quality-gate.test.ts src/lib/server/tasks/task-validation.test.ts src/lib/server/tool-capability-policy.test.ts src/lib/providers/openclaw-exports.test.ts src/app/api/openclaw/dashboard-url/route.test.ts",
|
|
75
75
|
"test:runtime": "tsx --test src/lib/server/knowledge-sources.test.ts src/lib/server/chat-execution/chat-execution-grounding.test.ts src/lib/server/protocols/protocol-service.test.ts src/lib/server/runtime/run-ledger.test.ts",
|
|
76
|
+
"test:builder": "tsx --test src/features/protocols/builder/utils/nodes-to-template.test.ts src/features/protocols/builder/utils/template-to-nodes.test.ts src/features/protocols/builder/validators/dag-validator.test.ts",
|
|
76
77
|
"test:e2e": "tsx .workbench/browser-e2e/run.ts",
|
|
77
78
|
"test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
|
|
78
79
|
"prepack": "npm run build:ci",
|
|
@@ -88,7 +89,7 @@
|
|
|
88
89
|
"@multiavatar/multiavatar": "^1.0.7",
|
|
89
90
|
"@playwright/mcp": "^0.0.68",
|
|
90
91
|
"@slack/bolt": "^4.6.0",
|
|
91
|
-
"@swarmdock/sdk": "^0.
|
|
92
|
+
"@swarmdock/sdk": "^0.4.1",
|
|
92
93
|
"@tailwindcss/postcss": "^4",
|
|
93
94
|
"@tanstack/react-query": "^5.91.0",
|
|
94
95
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -100,6 +101,8 @@
|
|
|
100
101
|
"@types/react-dom": "^19",
|
|
101
102
|
"@types/ws": "^8.18.1",
|
|
102
103
|
"@whiskeysockets/baileys": "^7.0.0-rc.9",
|
|
104
|
+
"@xyflow/react": "^12.10.2",
|
|
105
|
+
"@xyflow/system": "^0.0.76",
|
|
103
106
|
"better-sqlite3": "^12.6.2",
|
|
104
107
|
"bs58": "^5.0.0",
|
|
105
108
|
"cheerio": "^1.2.0",
|
|
@@ -108,12 +111,14 @@
|
|
|
108
111
|
"commander": "^13.1.0",
|
|
109
112
|
"cron-parser": "^5.5.0",
|
|
110
113
|
"cronstrue": "^3.12.0",
|
|
114
|
+
"dagre": "^0.8.5",
|
|
111
115
|
"discord.js": "^14.25.1",
|
|
112
116
|
"ethers": "^6.16.0",
|
|
113
117
|
"exceljs": "^4.4.0",
|
|
114
118
|
"grammy": "^1.40.0",
|
|
115
119
|
"highlight.js": "^11.11.1",
|
|
116
120
|
"imapflow": "^1.2.11",
|
|
121
|
+
"isomorphic-dompurify": "^3.7.1",
|
|
117
122
|
"just-bash": "^2.14.0",
|
|
118
123
|
"langchain": "^1.2.30",
|
|
119
124
|
"lucide-react": "^0.574.0",
|
|
@@ -146,6 +151,7 @@
|
|
|
146
151
|
"zustand": "^5.0.11"
|
|
147
152
|
},
|
|
148
153
|
"devDependencies": {
|
|
154
|
+
"@types/dagre": "^0.7.54",
|
|
149
155
|
"eslint": "^9",
|
|
150
156
|
"eslint-config-next": "16.1.7"
|
|
151
157
|
},
|
package/src/.env.local
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getAgent, listAgents } from '@/lib/server/agents/agent-repository'
|
|
3
|
+
import { generateAgentCard } from '@/lib/a2a/agent-card'
|
|
4
|
+
|
|
5
|
+
export const dynamic = 'force-dynamic'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* GET /.well-known/agent-card.json?agentId=xxx
|
|
9
|
+
*
|
|
10
|
+
* A2A Agent Card discovery endpoint.
|
|
11
|
+
* If agentId is provided, returns the full card for that agent.
|
|
12
|
+
* Otherwise, returns a directory of all non-disabled agents.
|
|
13
|
+
*
|
|
14
|
+
* Publicly accessible per A2A spec — no auth required for discovery.
|
|
15
|
+
*/
|
|
16
|
+
export async function GET(req: Request) {
|
|
17
|
+
const { searchParams } = new URL(req.url)
|
|
18
|
+
const agentId = searchParams.get('agentId')
|
|
19
|
+
const baseUrl = `${new URL(req.url).origin}`
|
|
20
|
+
|
|
21
|
+
if (agentId) {
|
|
22
|
+
const agent = getAgent(agentId)
|
|
23
|
+
if (!agent) {
|
|
24
|
+
return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
|
|
25
|
+
}
|
|
26
|
+
if (agent.disabled) {
|
|
27
|
+
return NextResponse.json({ error: 'Agent is disabled' }, { status: 404 })
|
|
28
|
+
}
|
|
29
|
+
const card = generateAgentCard(agent, baseUrl)
|
|
30
|
+
return NextResponse.json(card)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Return directory of all active agents
|
|
34
|
+
const agents = listAgents()
|
|
35
|
+
const directory = Object.values(agents)
|
|
36
|
+
.filter(a => !a.disabled)
|
|
37
|
+
.map(a => ({
|
|
38
|
+
name: a.name,
|
|
39
|
+
description: a.description || `SwarmClaw agent: ${a.name}`,
|
|
40
|
+
agentId: a.id,
|
|
41
|
+
apiEndpoint: `${baseUrl}/api/a2a`,
|
|
42
|
+
cardUrl: `${baseUrl}/api/.well-known/agent-card?agentId=${a.id}`,
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
return NextResponse.json({ agents: directory, protocolVersion: '0.3.0' })
|
|
46
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
3
|
+
import { validateA2ARequest, extractA2AHeaders } from '@/lib/a2a/auth'
|
|
4
|
+
import { JsonRpcRequestSchema, JSON_RPC_ERRORS } from '@/lib/a2a/types'
|
|
5
|
+
import type { A2AContext } from '@/lib/a2a/types'
|
|
6
|
+
import { a2aRouter } from '@/lib/a2a/json-rpc-router'
|
|
7
|
+
import { log } from '@/lib/server/logger'
|
|
8
|
+
|
|
9
|
+
// Ensure handlers are registered
|
|
10
|
+
import '@/lib/a2a/handlers'
|
|
11
|
+
|
|
12
|
+
export const dynamic = 'force-dynamic'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* POST /api/a2a
|
|
16
|
+
*
|
|
17
|
+
* Main A2A JSON-RPC 2.0 endpoint.
|
|
18
|
+
* Accepts JSON-RPC requests and routes them to registered handlers.
|
|
19
|
+
*/
|
|
20
|
+
export async function POST(req: Request) {
|
|
21
|
+
// Authenticate
|
|
22
|
+
const auth = validateA2ARequest(req)
|
|
23
|
+
if (!auth.valid) {
|
|
24
|
+
return NextResponse.json({
|
|
25
|
+
jsonrpc: '2.0',
|
|
26
|
+
error: { code: JSON_RPC_ERRORS.AUTH_FAILED, message: auth.error ?? 'Authentication failed' },
|
|
27
|
+
}, { status: 401 })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Parse body
|
|
31
|
+
const { data: body, error: parseError } = await safeParseBody(req)
|
|
32
|
+
if (parseError) return parseError
|
|
33
|
+
|
|
34
|
+
// Validate JSON-RPC envelope
|
|
35
|
+
const validation = JsonRpcRequestSchema.safeParse(body)
|
|
36
|
+
if (!validation.success) {
|
|
37
|
+
return NextResponse.json({
|
|
38
|
+
jsonrpc: '2.0',
|
|
39
|
+
error: { code: JSON_RPC_ERRORS.PARSE_ERROR, message: 'Invalid JSON-RPC request', data: validation.error.issues },
|
|
40
|
+
}, { status: 400 })
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const rpcRequest = validation.data
|
|
44
|
+
const headers = extractA2AHeaders(req)
|
|
45
|
+
|
|
46
|
+
const context: A2AContext = {
|
|
47
|
+
agentId: headers.targetAgentId ?? '',
|
|
48
|
+
requesterId: headers.requesterAgentId ?? auth.agentId ?? 'unknown',
|
|
49
|
+
timestamp: new Date(),
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
log.info('a2a', `JSON-RPC ${rpcRequest.method}`, { agentId: context.agentId, requesterId: context.requesterId })
|
|
53
|
+
|
|
54
|
+
const response = await a2aRouter.route(rpcRequest, context)
|
|
55
|
+
return NextResponse.json(response)
|
|
56
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { validateA2ARequest } from '@/lib/a2a/auth'
|
|
3
|
+
import { loadTask } from '@/lib/server/tasks/task-repository'
|
|
4
|
+
import type { A2ATaskStatus } from '@/lib/a2a/types'
|
|
5
|
+
import type { BoardTaskStatus } from '@/types/task'
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
function mapTaskStatus(status: BoardTaskStatus): A2ATaskStatus {
|
|
10
|
+
switch (status) {
|
|
11
|
+
case 'queued': case 'backlog': return 'submitted'
|
|
12
|
+
case 'running': return 'working'
|
|
13
|
+
case 'completed': return 'completed'
|
|
14
|
+
case 'failed': return 'failed'
|
|
15
|
+
case 'cancelled': case 'archived': case 'deferred': return 'cancelled'
|
|
16
|
+
default: return 'submitted'
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* GET /api/a2a/tasks/:taskId/status
|
|
22
|
+
*
|
|
23
|
+
* Poll the status of an A2A task.
|
|
24
|
+
*/
|
|
25
|
+
export async function GET(
|
|
26
|
+
req: Request,
|
|
27
|
+
{ params }: { params: Promise<{ taskId: string }> },
|
|
28
|
+
) {
|
|
29
|
+
const auth = validateA2ARequest(req)
|
|
30
|
+
if (!auth.valid) {
|
|
31
|
+
return NextResponse.json({ error: auth.error }, { status: 401 })
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { taskId } = await params
|
|
35
|
+
const task = loadTask(taskId)
|
|
36
|
+
|
|
37
|
+
if (!task) {
|
|
38
|
+
return NextResponse.json({ error: 'Task not found' }, { status: 404 })
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return NextResponse.json({
|
|
42
|
+
taskId: task.id,
|
|
43
|
+
status: mapTaskStatus(task.status),
|
|
44
|
+
title: task.title,
|
|
45
|
+
result: task.status === 'completed' ? (task.result ?? null) : null,
|
|
46
|
+
error: task.status === 'failed' ? (task.error ?? null) : null,
|
|
47
|
+
updatedAt: task.updatedAt,
|
|
48
|
+
})
|
|
49
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NextResponse } from 'next/server'
|
|
2
|
-
import { execSync } from 'child_process'
|
|
2
|
+
import { execSync, execFileSync } from 'child_process'
|
|
3
3
|
import { notFound } from '@/lib/server/collection-helpers'
|
|
4
4
|
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
5
|
import { log } from '@/lib/server/logger'
|
|
@@ -21,7 +21,7 @@ export async function POST(req: Request, { params }: { params: Promise<{ id: str
|
|
|
21
21
|
execSync('git add -A', opts)
|
|
22
22
|
let committed = false
|
|
23
23
|
try {
|
|
24
|
-
|
|
24
|
+
execFileSync('git', ['commit', '-m', msg], opts)
|
|
25
25
|
committed = true
|
|
26
26
|
} catch (ce: unknown) {
|
|
27
27
|
const ex = ce as { stdout?: string; stderr?: string }
|
|
@@ -4,7 +4,7 @@ import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
|
4
4
|
export const dynamic = 'force-dynamic'
|
|
5
5
|
|
|
6
6
|
const VALID_ACTIONS = new Set(['push', 'pull', 'both'])
|
|
7
|
-
const VALID_TYPES: SyncType[] = ['memory', 'workspace', 'schedules', 'credentials', '
|
|
7
|
+
const VALID_TYPES: SyncType[] = ['memory', 'workspace', 'schedules', 'credentials', 'extensions']
|
|
8
8
|
|
|
9
9
|
export async function POST(req: Request) {
|
|
10
10
|
try {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getChannels } from '@/lib/swarmfeed-client'
|
|
3
|
+
|
|
4
|
+
export const dynamic = 'force-dynamic'
|
|
5
|
+
|
|
6
|
+
export async function GET() {
|
|
7
|
+
try {
|
|
8
|
+
const channels = await getChannels()
|
|
9
|
+
return NextResponse.json({ channels })
|
|
10
|
+
} catch (err: unknown) {
|
|
11
|
+
const message = err instanceof Error ? err.message : 'Failed to fetch channels'
|
|
12
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { createPost, getFeed } from '@/lib/swarmfeed-client'
|
|
3
|
+
import { getAgent } from '@/lib/server/agents/agent-repository'
|
|
4
|
+
import { safeParseBody } from '@/lib/server/safe-parse-body'
|
|
5
|
+
import type { Agent } from '@/types'
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
export async function GET(req: Request) {
|
|
10
|
+
const { searchParams } = new URL(req.url)
|
|
11
|
+
const cursor = searchParams.get('cursor') || undefined
|
|
12
|
+
const limitStr = searchParams.get('limit')
|
|
13
|
+
const limit = limitStr ? Math.max(1, Math.min(100, Number(limitStr) || 20)) : undefined
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
const result = await getFeed('for_you', { cursor, limit })
|
|
17
|
+
return NextResponse.json(result)
|
|
18
|
+
} catch (err: unknown) {
|
|
19
|
+
const message = err instanceof Error ? err.message : 'Failed to fetch posts'
|
|
20
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function POST(req: Request) {
|
|
25
|
+
const { data: body, error } = await safeParseBody<{
|
|
26
|
+
agentId?: string
|
|
27
|
+
content?: string
|
|
28
|
+
channelId?: string
|
|
29
|
+
parentId?: string
|
|
30
|
+
}>(req)
|
|
31
|
+
if (error) return error
|
|
32
|
+
|
|
33
|
+
if (!body?.agentId || typeof body.agentId !== 'string') {
|
|
34
|
+
return NextResponse.json({ error: 'agentId is required' }, { status: 400 })
|
|
35
|
+
}
|
|
36
|
+
if (!body.content || typeof body.content !== 'string' || !body.content.trim()) {
|
|
37
|
+
return NextResponse.json({ error: 'content is required' }, { status: 400 })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Look up the agent's SwarmFeed API key
|
|
41
|
+
const agent = getAgent(body.agentId) as Agent | undefined
|
|
42
|
+
if (!agent?.swarmfeedApiKey) {
|
|
43
|
+
return NextResponse.json(
|
|
44
|
+
{ error: 'Agent not registered on SwarmFeed. Enable SwarmFeed in agent settings first.' },
|
|
45
|
+
{ status: 400 },
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const post = await createPost(agent.swarmfeedApiKey, {
|
|
51
|
+
content: body.content.trim(),
|
|
52
|
+
channelId: typeof body.channelId === 'string' ? body.channelId : undefined,
|
|
53
|
+
parentId: typeof body.parentId === 'string' ? body.parentId : undefined,
|
|
54
|
+
})
|
|
55
|
+
return NextResponse.json(post)
|
|
56
|
+
} catch (err: unknown) {
|
|
57
|
+
const message = err instanceof Error ? err.message : 'Failed to create post'
|
|
58
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server'
|
|
2
|
+
import { getFeed } from '@/lib/swarmfeed-client'
|
|
3
|
+
import { loadAgents } from '@/lib/server/storage'
|
|
4
|
+
import type { FeedType } from '@/types/swarmfeed'
|
|
5
|
+
import type { Agent } from '@/types'
|
|
6
|
+
|
|
7
|
+
export const dynamic = 'force-dynamic'
|
|
8
|
+
|
|
9
|
+
const VALID_FEED_TYPES = new Set<FeedType>(['for_you', 'following', 'channel', 'trending'])
|
|
10
|
+
|
|
11
|
+
export async function GET(req: Request) {
|
|
12
|
+
const { searchParams } = new URL(req.url)
|
|
13
|
+
const type = (searchParams.get('type') || 'for_you') as FeedType
|
|
14
|
+
if (!VALID_FEED_TYPES.has(type)) {
|
|
15
|
+
return NextResponse.json({ error: 'Invalid feed type' }, { status: 400 })
|
|
16
|
+
}
|
|
17
|
+
const channelId = searchParams.get('channelId') || undefined
|
|
18
|
+
const cursor = searchParams.get('cursor') || undefined
|
|
19
|
+
const limitStr = searchParams.get('limit')
|
|
20
|
+
const limit = limitStr ? Math.max(1, Math.min(100, Number(limitStr) || 20)) : undefined
|
|
21
|
+
|
|
22
|
+
// For authenticated feeds (following), find the first enabled agent's API key
|
|
23
|
+
let agentApiKey: string | undefined
|
|
24
|
+
if (type === 'following') {
|
|
25
|
+
const agents = Object.values(loadAgents()) as Agent[]
|
|
26
|
+
const feedAgent = agents.find((a) => a.swarmfeedEnabled && a.swarmfeedApiKey)
|
|
27
|
+
agentApiKey = feedAgent?.swarmfeedApiKey ?? undefined
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const result = await getFeed(type, { channelId, cursor, limit }, agentApiKey)
|
|
32
|
+
return NextResponse.json(result)
|
|
33
|
+
} catch (err: unknown) {
|
|
34
|
+
const message = err instanceof Error ? err.message : 'Failed to fetch feed'
|
|
35
|
+
return NextResponse.json({ error: message }, { status: 502 })
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -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',
|
|
@@ -784,6 +793,16 @@ const COMMAND_GROUPS = [
|
|
|
784
793
|
cmd('delete', 'DELETE', '/goals/:id', 'Delete a goal'),
|
|
785
794
|
],
|
|
786
795
|
},
|
|
796
|
+
{
|
|
797
|
+
name: 'swarmfeed',
|
|
798
|
+
description: 'SwarmFeed social network',
|
|
799
|
+
commands: [
|
|
800
|
+
cmd('feed', 'GET', '/swarmfeed', 'Get SwarmFeed timeline'),
|
|
801
|
+
cmd('channels', 'GET', '/swarmfeed/channels', 'List SwarmFeed channels'),
|
|
802
|
+
cmd('posts', 'GET', '/swarmfeed/posts', 'Get recent posts'),
|
|
803
|
+
cmd('post', 'POST', '/swarmfeed/posts', 'Create a post', { expectsJsonBody: true }),
|
|
804
|
+
],
|
|
805
|
+
},
|
|
787
806
|
]
|
|
788
807
|
|
|
789
808
|
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: {
|
|
@@ -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}
|