@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.
- package/README.md +37 -1
- package/package.json +10 -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-disabled.test.ts +14 -31
- package/src/lib/server/chat-execution/chat-execution-eval-history.test.ts +11 -34
- package/src/lib/server/chat-execution/chat-execution-grounding.test.ts +15 -34
- package/src/lib/server/chat-execution/chat-execution-session-sync.test.ts +35 -36
- package/src/lib/server/chat-execution/chat-execution.ts +12 -7
- package/src/lib/server/extensions.ts +11 -0
- package/src/lib/server/knowledge-sources.test.ts +46 -0
- package/src/lib/server/knowledge-sources.ts +34 -16
- 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/test-utils/run-with-temp-data-dir.ts +15 -2
- 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,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
|
+
"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.
|
|
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,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}
|