@swarmclawai/swarmclaw 1.3.5 → 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.
Files changed (103) hide show
  1. package/README.md +37 -1
  2. package/package.json +10 -3
  3. package/src/.env.local +4 -0
  4. package/src/app/api/.well-known/agent-card/route.ts +46 -0
  5. package/src/app/api/a2a/route.ts +56 -0
  6. package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
  7. package/src/app/api/chats/[id]/deploy/route.ts +2 -2
  8. package/src/app/api/openclaw/sync/route.ts +1 -1
  9. package/src/app/api/swarmfeed/channels/route.ts +14 -0
  10. package/src/app/api/swarmfeed/posts/route.ts +60 -0
  11. package/src/app/api/swarmfeed/route.ts +37 -0
  12. package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
  13. package/src/app/protocols/page.tsx +16 -7
  14. package/src/app/swarmfeed/page.tsx +7 -0
  15. package/src/cli/index.js +19 -0
  16. package/src/cli/spec.js +8 -0
  17. package/src/components/agents/agent-avatar.tsx +2 -5
  18. package/src/components/agents/agent-sheet.tsx +10 -0
  19. package/src/components/auth/access-key-gate.tsx +25 -0
  20. package/src/components/layout/sidebar-rail.tsx +52 -0
  21. package/src/components/protocols/builder/edge-editor.tsx +43 -0
  22. package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
  23. package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
  24. package/src/components/protocols/builder/edge-types/index.ts +3 -0
  25. package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
  26. package/src/components/protocols/builder/node-inspector.tsx +227 -0
  27. package/src/components/protocols/builder/node-palette.tsx +97 -0
  28. package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
  29. package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
  30. package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
  31. package/src/components/protocols/builder/node-types/index.ts +9 -0
  32. package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
  33. package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
  34. package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
  35. package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
  36. package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
  37. package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
  38. package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
  39. package/src/components/protocols/builder/run-overlay.tsx +29 -0
  40. package/src/components/protocols/builder/template-gallery.tsx +53 -0
  41. package/src/components/protocols/builder/validation-panel.tsx +57 -0
  42. package/src/components/skills/skills-workspace.tsx +1 -9
  43. package/src/features/protocols/builder/hooks/index.ts +2 -0
  44. package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
  45. package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
  46. package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
  47. package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
  48. package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
  49. package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
  50. package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
  51. package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
  52. package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
  53. package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
  54. package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
  55. package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
  56. package/src/features/swarmfeed/compose-post.tsx +139 -0
  57. package/src/features/swarmfeed/feed-page.tsx +136 -0
  58. package/src/features/swarmfeed/post-card.tsx +114 -0
  59. package/src/features/swarmfeed/queries.ts +28 -0
  60. package/src/lib/a2a/agent-card.ts +61 -0
  61. package/src/lib/a2a/auth.ts +54 -0
  62. package/src/lib/a2a/client.ts +133 -0
  63. package/src/lib/a2a/discovery.ts +116 -0
  64. package/src/lib/a2a/handlers.ts +176 -0
  65. package/src/lib/a2a/json-rpc-router.ts +38 -0
  66. package/src/lib/a2a/types.ts +95 -0
  67. package/src/lib/app/navigation.ts +1 -0
  68. package/src/lib/app/view-constants.ts +9 -1
  69. package/src/lib/providers/anthropic.ts +111 -107
  70. package/src/lib/providers/openai.ts +146 -142
  71. package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
  72. package/src/lib/server/agents/main-agent-loop.ts +377 -41
  73. package/src/lib/server/chat-execution/chat-execution-disabled.test.ts +14 -31
  74. package/src/lib/server/chat-execution/chat-execution-eval-history.test.ts +11 -34
  75. package/src/lib/server/chat-execution/chat-execution-grounding.test.ts +15 -34
  76. package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +35 -36
  77. package/src/lib/server/chat-execution/chat-execution.ts +12 -7
  78. package/src/lib/server/extensions.ts +11 -0
  79. package/src/lib/server/knowledge-sources.test.ts +46 -0
  80. package/src/lib/server/knowledge-sources.ts +34 -16
  81. package/src/lib/server/openclaw/sync.ts +4 -4
  82. package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
  83. package/src/lib/server/protocols/protocol-normalization.ts +1 -0
  84. package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
  85. package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
  86. package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
  87. package/src/lib/server/protocols/protocol-types.ts +1 -0
  88. package/src/lib/server/session-tools/delegate.ts +151 -77
  89. package/src/lib/server/storage-auth.ts +10 -2
  90. package/src/lib/server/storage-normalization.ts +11 -0
  91. package/src/lib/server/storage.ts +100 -0
  92. package/src/lib/server/test-utils/run-with-temp-data-dir.ts +15 -2
  93. package/src/lib/server/working-state/service.test.ts +2 -3
  94. package/src/lib/server/working-state/service.ts +37 -6
  95. package/src/lib/swarmfeed-client.ts +157 -0
  96. package/src/lib/validation/schemas.ts +1 -1
  97. package/src/stores/slices/data-slice.ts +3 -0
  98. package/src/stores/use-approval-store.ts +4 -1
  99. package/src/types/agent.ts +31 -1
  100. package/src/types/index.ts +1 -0
  101. package/src/types/protocol.ts +19 -0
  102. package/src/types/session.ts +1 -1
  103. package/src/types/swarmfeed.ts +30 -0
package/README.md CHANGED
@@ -200,10 +200,44 @@ 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
+
236
+ ### v1.3.6 Highlights
237
+
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.
239
+ - **Release gate hardening**: the default test matrix now includes the 1.3.5 grounding/knowledge/runtime suites, and both CI and tag releases run `npm test`, `npm run type-check`, and `npm run build:ci` before publishing.
240
+
207
241
  ### v1.3.5 Highlights
208
242
 
209
243
  - **Knowledge grounding & citations**: agent responses are now grounded against knowledge sources at retrieval time. Citations — with scores, snippets, and match rationale — are persisted on chat messages, protocol events, and run records for full auditability.
@@ -342,7 +376,7 @@ Then open `http://localhost:3456`.
342
376
  - **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, and operator controls.
343
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.
344
378
  - **Wallets**: linked Base wallet generation, address management, approval-oriented limits, and agent payout identity.
345
- - **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, and more.
379
+ - **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, SwarmFeed, and more.
346
380
  - **Extensions**: external tool extensions, UI modules, hooks, and install/update flows.
347
381
 
348
382
  ## Requirements
@@ -367,5 +401,7 @@ Then open `http://localhost:3456`.
367
401
  - Connectors: https://swarmclaw.ai/docs/connectors
368
402
  - SwarmDock: https://swarmclaw.ai/docs/swarmdock
369
403
  - SwarmDock marketplace: https://swarmdock.ai
404
+ - SwarmFeed: https://swarmclaw.ai/docs/swarmfeed
405
+ - SwarmFeed platform: https://swarmfeed.ai
370
406
  - Extensions: https://swarmclaw.ai/docs/extensions
371
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.5",
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",
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",
@@ -72,6 +72,8 @@
72
72
  "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs",
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
+ "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",
75
77
  "test:e2e": "tsx .workbench/browser-e2e/run.ts",
76
78
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
77
79
  "prepack": "npm run build:ci",
@@ -87,7 +89,7 @@
87
89
  "@multiavatar/multiavatar": "^1.0.7",
88
90
  "@playwright/mcp": "^0.0.68",
89
91
  "@slack/bolt": "^4.6.0",
90
- "@swarmdock/sdk": "^0.2.3",
92
+ "@swarmdock/sdk": "^0.4.1",
91
93
  "@tailwindcss/postcss": "^4",
92
94
  "@tanstack/react-query": "^5.91.0",
93
95
  "@types/better-sqlite3": "^7.6.13",
@@ -99,6 +101,8 @@
99
101
  "@types/react-dom": "^19",
100
102
  "@types/ws": "^8.18.1",
101
103
  "@whiskeysockets/baileys": "^7.0.0-rc.9",
104
+ "@xyflow/react": "^12.10.2",
105
+ "@xyflow/system": "^0.0.76",
102
106
  "better-sqlite3": "^12.6.2",
103
107
  "bs58": "^5.0.0",
104
108
  "cheerio": "^1.2.0",
@@ -107,12 +111,14 @@
107
111
  "commander": "^13.1.0",
108
112
  "cron-parser": "^5.5.0",
109
113
  "cronstrue": "^3.12.0",
114
+ "dagre": "^0.8.5",
110
115
  "discord.js": "^14.25.1",
111
116
  "ethers": "^6.16.0",
112
117
  "exceljs": "^4.4.0",
113
118
  "grammy": "^1.40.0",
114
119
  "highlight.js": "^11.11.1",
115
120
  "imapflow": "^1.2.11",
121
+ "isomorphic-dompurify": "^3.7.1",
116
122
  "just-bash": "^2.14.0",
117
123
  "langchain": "^1.2.30",
118
124
  "lucide-react": "^0.574.0",
@@ -145,6 +151,7 @@
145
151
  "zustand": "^5.0.11"
146
152
  },
147
153
  "devDependencies": {
154
+ "@types/dagre": "^0.7.54",
148
155
  "eslint": "^9",
149
156
  "eslint-config-next": "16.1.7"
150
157
  },
package/src/.env.local ADDED
@@ -0,0 +1,4 @@
1
+
2
+ CREDENTIAL_SECRET=903abc19d0ffc461795f39d90f55b68cdc2c35e930f1115374f11a4ff7ddd293
3
+
4
+ ACCESS_KEY=9cf202df60e837ff327ae4e16b257d1f
@@ -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
- execSync(`git commit -m "${msg.replace(/"/g, '\\"')}"`, opts)
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', 'plugins']
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
+ &larr; 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
- <button
661
- type="button"
662
- onClick={() => void handleDeleteTemplate(editingTemplateId)}
663
- 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"
664
- >
665
- {templatePending === `delete:${editingTemplateId}` ? 'Deleting…' : 'Delete template'}
666
- </button>
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>
@@ -0,0 +1,7 @@
1
+ 'use client'
2
+
3
+ import { FeedPage } from '@/features/swarmfeed/feed-page'
4
+
5
+ export default function SwarmFeedPage() {
6
+ return <FeedPage />
7
+ }
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}