@swarmclawai/swarmclaw 1.3.6 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +16 -52
  2. package/next.config.ts +9 -4
  3. package/package.json +18 -10
  4. package/scripts/build-bootstrap-env.mjs +24 -0
  5. package/scripts/run-next-build.mjs +74 -0
  6. package/scripts/run-next-typegen.mjs +61 -0
  7. package/src/app/api/.well-known/agent-card/route.ts +46 -0
  8. package/src/app/api/a2a/route.ts +56 -0
  9. package/src/app/api/a2a/tasks/[taskId]/status/route.ts +49 -0
  10. package/src/app/api/approvals/route.test.ts +29 -3
  11. package/src/app/api/approvals/route.ts +13 -7
  12. package/src/app/api/chats/[id]/chat/route.test.ts +64 -0
  13. package/src/app/api/chats/[id]/chat/route.ts +24 -8
  14. package/src/app/api/chats/[id]/deploy/route.ts +2 -2
  15. package/src/app/api/chats/chat-route.test.ts +68 -0
  16. package/src/app/api/connectors/[id]/doctor/route.test.ts +97 -0
  17. package/src/app/api/connectors/[id]/doctor/route.ts +26 -1
  18. package/src/app/api/connectors/connector-doctor-route.test.ts +1 -0
  19. package/src/app/api/logs/route.test.ts +61 -0
  20. package/src/app/api/logs/route.ts +35 -0
  21. package/src/app/api/openclaw/sync/route.ts +1 -1
  22. package/src/app/api/swarmfeed/channels/route.ts +14 -0
  23. package/src/app/api/swarmfeed/posts/route.ts +60 -0
  24. package/src/app/api/swarmfeed/route.ts +37 -0
  25. package/src/app/api/tts/route.test.ts +82 -0
  26. package/src/app/api/tts/route.ts +13 -6
  27. package/src/app/api/tts/stream/route.ts +12 -5
  28. package/src/app/error.tsx +32 -0
  29. package/src/app/global-error.tsx +33 -0
  30. package/src/app/protocols/builder/[templateId]/page.tsx +93 -0
  31. package/src/app/protocols/page.tsx +16 -7
  32. package/src/app/swarmfeed/page.tsx +7 -0
  33. package/src/cli/index.js +22 -0
  34. package/src/cli/spec.js +9 -0
  35. package/src/components/agents/agent-avatar.tsx +2 -5
  36. package/src/components/agents/agent-sheet.tsx +10 -0
  37. package/src/components/auth/access-key-gate.tsx +25 -0
  38. package/src/components/layout/error-boundary.tsx +12 -30
  39. package/src/components/layout/error-fallback.tsx +61 -0
  40. package/src/components/layout/sidebar-rail.tsx +52 -0
  41. package/src/components/protocols/builder/edge-editor.tsx +43 -0
  42. package/src/components/protocols/builder/edge-types/branch-edge.tsx +33 -0
  43. package/src/components/protocols/builder/edge-types/default-edge.tsx +18 -0
  44. package/src/components/protocols/builder/edge-types/index.ts +3 -0
  45. package/src/components/protocols/builder/edge-types/loop-edge.tsx +19 -0
  46. package/src/components/protocols/builder/node-inspector.tsx +227 -0
  47. package/src/components/protocols/builder/node-palette.tsx +97 -0
  48. package/src/components/protocols/builder/node-types/branch-node.tsx +34 -0
  49. package/src/components/protocols/builder/node-types/complete-node.tsx +17 -0
  50. package/src/components/protocols/builder/node-types/for-each-node.tsx +21 -0
  51. package/src/components/protocols/builder/node-types/index.ts +9 -0
  52. package/src/components/protocols/builder/node-types/join-node.tsx +18 -0
  53. package/src/components/protocols/builder/node-types/loop-node.tsx +22 -0
  54. package/src/components/protocols/builder/node-types/parallel-node.tsx +31 -0
  55. package/src/components/protocols/builder/node-types/phase-node.tsx +52 -0
  56. package/src/components/protocols/builder/node-types/subflow-node.tsx +23 -0
  57. package/src/components/protocols/builder/node-types/swarm-node.tsx +26 -0
  58. package/src/components/protocols/builder/protocol-builder-canvas.tsx +184 -0
  59. package/src/components/protocols/builder/run-overlay.tsx +29 -0
  60. package/src/components/protocols/builder/template-gallery.tsx +53 -0
  61. package/src/components/protocols/builder/validation-panel.tsx +57 -0
  62. package/src/components/skills/skills-workspace.tsx +1 -9
  63. package/src/features/protocols/builder/hooks/index.ts +2 -0
  64. package/src/features/protocols/builder/hooks/use-canvas-validation.ts +14 -0
  65. package/src/features/protocols/builder/hooks/use-run-overlay.ts +39 -0
  66. package/src/features/protocols/builder/hooks/use-template-sync.ts +45 -0
  67. package/src/features/protocols/builder/protocol-builder-store.ts +233 -0
  68. package/src/features/protocols/builder/utils/node-position-layout.ts +41 -0
  69. package/src/features/protocols/builder/utils/nodes-to-template.test.ts +179 -0
  70. package/src/features/protocols/builder/utils/nodes-to-template.ts +49 -0
  71. package/src/features/protocols/builder/utils/template-to-nodes.test.ts +314 -0
  72. package/src/features/protocols/builder/utils/template-to-nodes.ts +169 -0
  73. package/src/features/protocols/builder/validators/dag-validator.test.ts +150 -0
  74. package/src/features/protocols/builder/validators/dag-validator.ts +119 -0
  75. package/src/features/swarmfeed/agent-social-settings.tsx +277 -0
  76. package/src/features/swarmfeed/compose-post.tsx +139 -0
  77. package/src/features/swarmfeed/feed-page.tsx +136 -0
  78. package/src/features/swarmfeed/post-card.tsx +114 -0
  79. package/src/features/swarmfeed/queries.ts +28 -0
  80. package/src/lib/a2a/agent-card.ts +61 -0
  81. package/src/lib/a2a/auth.ts +54 -0
  82. package/src/lib/a2a/client.ts +133 -0
  83. package/src/lib/a2a/discovery.ts +116 -0
  84. package/src/lib/a2a/handlers.ts +176 -0
  85. package/src/lib/a2a/json-rpc-router.ts +38 -0
  86. package/src/lib/a2a/types.ts +95 -0
  87. package/src/lib/app/navigation.ts +1 -0
  88. package/src/lib/app/report-client-error.ts +52 -0
  89. package/src/lib/app/view-constants.ts +9 -1
  90. package/src/lib/providers/anthropic.ts +119 -107
  91. package/src/lib/providers/ollama.ts +34 -14
  92. package/src/lib/providers/openai.ts +154 -142
  93. package/src/lib/providers/openclaw.ts +3 -3
  94. package/src/lib/server/agents/main-agent-loop.test.ts +94 -0
  95. package/src/lib/server/agents/main-agent-loop.ts +377 -41
  96. package/src/lib/server/chat-execution/chat-execution.ts +12 -7
  97. package/src/lib/server/chat-execution/chat-turn-preparation.ts +19 -12
  98. package/src/lib/server/connectors/swarmdock.ts +1 -1
  99. package/src/lib/server/extensions.ts +11 -0
  100. package/src/lib/server/messages/message-repository.ts +31 -0
  101. package/src/lib/server/openclaw/sync.ts +4 -4
  102. package/src/lib/server/protocols/protocol-a2a-delegate.ts +135 -0
  103. package/src/lib/server/protocols/protocol-normalization.ts +1 -0
  104. package/src/lib/server/protocols/protocol-step-helpers.test.ts +1 -1
  105. package/src/lib/server/protocols/protocol-step-helpers.ts +1 -0
  106. package/src/lib/server/protocols/protocol-step-processors.ts +2 -0
  107. package/src/lib/server/protocols/protocol-types.ts +1 -0
  108. package/src/lib/server/provider-health.ts +19 -3
  109. package/src/lib/server/safe-parse-body.test.ts +32 -0
  110. package/src/lib/server/safe-parse-body.ts +20 -3
  111. package/src/lib/server/session-tools/delegate.ts +151 -77
  112. package/src/lib/server/storage-auth.ts +10 -2
  113. package/src/lib/server/storage-normalization.ts +11 -0
  114. package/src/lib/server/storage.ts +113 -4
  115. package/src/lib/server/working-state/service.test.ts +2 -3
  116. package/src/lib/server/working-state/service.ts +37 -6
  117. package/src/lib/swarmfeed-client.ts +157 -0
  118. package/src/lib/validation/schemas.ts +1 -1
  119. package/src/stores/slices/data-slice.ts +3 -0
  120. package/src/stores/use-approval-store.ts +4 -1
  121. package/src/types/agent.ts +31 -1
  122. package/src/types/index.ts +1 -0
  123. package/src/types/protocol.ts +19 -0
  124. package/src/types/session.ts +1 -1
  125. package/src/types/swarmfeed.ts +30 -0
  126. package/tsconfig.json +1 -2
package/README.md CHANGED
@@ -200,61 +200,22 @@ 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
- ---
204
-
205
- ## Release Notes
206
-
207
- ### v1.3.6 Highlights
208
-
209
- - **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.
210
- - **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.
211
-
212
- ### v1.3.5 Highlights
213
-
214
- - **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.
215
- - **Knowledge source lifecycle**: new source management system with create, sync, archive, restore, supersede, and delete operations. Sources can be manual text, files (30+ formats including code, markup, PDF), or URLs (HTML auto-parsed).
216
- - **Hygiene automation**: background scanner detects stale, duplicate, overlapping, and broken knowledge sources. Auto-syncs stale file/URL sources and archives exact duplicates on idle.
217
- - **Redesigned Knowledge page**: detail-focused layout with sidebar list, full source inspector (metadata, chunks, sync status), and inline actions. Search/browse toggle, tag filtering, and archive visibility controls.
218
- - **Grounding panel**: new reusable citation display component shown on chat messages, protocol artifacts, and run results — surfaces retrieval query, hit scores, snippets, and source links.
219
- - **7 new API endpoints**: `/knowledge/hygiene` (GET/POST), `/knowledge/sources/:id/archive`, `/restore`, `/supersede`, `/sync` for full source lifecycle management via CLI and API.
220
- - **Protocol citation propagation**: structured protocol runs now capture and persist citations on participant responses and emitted artifacts.
221
- - **Dreaming (idle-time memory consolidation)**: agents now consolidate and optimize memories during idle periods. Two-tier system: server-side deterministic operations (decay, prune, promote, dedup) plus agent-driven LLM reflection that surfaces patterns and produces consolidated insights.
222
- - **Per-agent dream configuration**: dreaming is opt-in per agent with configurable cooldown, decay age, prune threshold, and Tier 2 reflection controls.
223
- - **Dream cycle audit trail**: every dream cycle is tracked with status, trigger, duration, and detailed results. Viewable in the memory UI and via CLI.
224
- - **3 new API endpoints**: `/memory/dream` (GET/POST), `/memory/dream/:id` for dream cycle management.
225
-
226
- ### v1.3.4 Highlights
227
-
228
- - **Bug fix — custom provider loading under Turbopack (#32)**: converted all CommonJS `require()` calls across the codebase to ES module imports, fixing "Unknown provider: custom-\<id\>" errors and other potential Turbopack compatibility issues. Affected modules: providers, provider health, subagent swarm, prompt builder, chat finalization, CLI utils, and OpenClaw connectors. Thanks to @psywolf85 for the initial fix.
229
-
230
- ### v1.3.3 Highlights
231
-
232
- - **Bug fix — stale connector status after auto-restart (#31)**: connectors that auto-restart via the daemon health monitor now show "Starting" instead of a stale "Stopped" or "Error" status in the UI until the daemon reports runtime state. Added `starting` to the `ConnectorStatus` type and updated both the connector list and detail views.
233
- - **Bug fix — stale credentialId after credential rotation (#30)**: when a provider credential is deleted and re-created, connector sessions now fall back to resolving any valid credential for the same provider instead of failing with "Missing credentials."
234
-
235
- ### v1.3.2 Highlights
236
-
237
- - **Custom provider fix for standalone builds**: fixed `require('@/lib/server/storage')` path alias resolution failure that caused custom providers to silently break in standalone/npm-global installs with "a is not a function" errors. All dynamic requires now use relative paths that resolve correctly at runtime.
238
- - **GitHub Copilot CLI provider**: new CLI provider wrapping the `copilot` binary with JSONL streaming, session continuity, system prompt injection, and multi-model support (Claude, GPT, Gemini via GitHub Copilot subscription).
203
+ ## SwarmFeed Social Network
239
204
 
240
- ### v1.3.1 Highlights
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.
241
206
 
242
- - **SwarmDock SDK v0.2.3**: upgraded marketplace integration with typed error handling, escrow state tracking, task invitation support for private tasks, and required example prompts for skill registration.
243
- - **SDK error resilience**: registration now gracefully handles already-registered agents by falling back to authentication; heartbeat catches expired tokens and re-authenticates automatically.
244
- - **Escrow event tracking**: new `escrow.releasing`, `escrow.refunding`, `escrow.release_failed`, and `escrow.refund_failed` SSE events are logged as activity entries, with failure events surfaced as incidents.
245
- - **Private task invitations**: when a SwarmDock task invites this agent directly, auto-discovery now evaluates it alongside public `task.created` events.
246
- - **SDK type imports**: replaced inlined SwarmDock type stubs with proper imports from `@swarmdock/shared`, eliminating type drift.
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)
247
211
 
248
- ### v1.3.0 Highlights
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.
249
213
 
250
- - **SwarmDock SDK v0.2.0**: upgraded marketplace integration to handle the new task lifecycle — `review` and `disputed` states are now tracked on board tasks, skill registration supports `inputModes`/`outputModes`, task submission accepts `notes`, and connector config supports `paymentPrivateKey` for on-chain payment signing.
251
- - **Comprehensive audit logging**: activity log now covers approval decisions, settings changes, budget modifications, and credential operations, with SQL-indexed paginated queries replacing the in-memory full-collection scan.
252
- - **Push-based cost rollups**: agent spend fields (`spentHourlyCents`, `spentDailyCents`, `spentMonthlyCents`) update atomically on every usage event, with automatic budget warning/exceeded activity entries and window reset detection — replacing the pull-based full-scan approach.
253
- - **Goal hierarchy**: new goals system with organization → team → project → agent → task levels, parent-child chains, and automatic injection of the "why chain" into agent execution briefs. Full CRUD API and CLI support.
254
- - **Extended approval workflows**: new `agent_create`, `budget_change`, and `delegation_enable` approval categories with configurable policies in settings. When enabled, agent creation returns a pending approval instead of creating the agent directly.
255
- - **Shared validation schemas**: Zod schemas in `src/lib/validation/schemas.ts` are now safe for client-side import (server-only DAG validation moved to `server-schemas.ts`), enabling form-level pre-validation.
214
+ ## Releases
256
215
 
257
- *For older release notes (v1.2.x and earlier), see [swarmclaw.ai/docs/release-notes](https://swarmclaw.ai/docs/release-notes).*
216
+ - GitHub releases: https://github.com/swarmclawai/swarmclaw/releases
217
+ - npm package: https://www.npmjs.com/package/@swarmclawai/swarmclaw
218
+ - Historical release notes: https://swarmclaw.ai/docs/release-notes
258
219
 
259
220
 
260
221
  ## What SwarmClaw Focuses On
@@ -310,6 +271,7 @@ Running `swarmclaw` starts the server on `http://localhost:3456`.
310
271
  ```bash
311
272
  git clone https://github.com/swarmclawai/swarmclaw.git
312
273
  cd swarmclaw
274
+ nvm use
313
275
  npm run quickstart
314
276
  ```
315
277
 
@@ -347,12 +309,12 @@ Then open `http://localhost:3456`.
347
309
  - **Structured Sessions**: reusable bounded runs with templates, facilitators, participants, hidden live rooms, chatroom `/breakout`, durable transcripts, outputs, and operator controls.
348
310
  - **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
311
  - **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.
312
+ - **Connectors**: Discord, Slack, Telegram, WhatsApp, Teams, Matrix, OpenClaw, SwarmDock, SwarmFeed, and more.
351
313
  - **Extensions**: external tool extensions, UI modules, hooks, and install/update flows.
352
314
 
353
315
  ## Requirements
354
316
 
355
- - Node.js 22.6+
317
+ - Node.js 22.6+ (`nvm use` will pick up the repo's `.nvmrc`, which matches CI)
356
318
  - npm 10+ or another supported package manager
357
319
  - Docker Desktop is recommended for sandbox browser execution
358
320
  - Optional provider CLIs if you want delegated CLI backends such as Claude Code, Codex, OpenCode, or Gemini
@@ -372,5 +334,7 @@ Then open `http://localhost:3456`.
372
334
  - Connectors: https://swarmclaw.ai/docs/connectors
373
335
  - SwarmDock: https://swarmclaw.ai/docs/swarmdock
374
336
  - SwarmDock marketplace: https://swarmdock.ai
337
+ - SwarmFeed: https://swarmclaw.ai/docs/swarmfeed
338
+ - SwarmFeed platform: https://swarmfeed.ai
375
339
  - Extensions: https://swarmclaw.ai/docs/extensions
376
340
  - CLI reference: https://swarmclaw.ai/docs/cli
package/next.config.ts CHANGED
@@ -6,6 +6,10 @@ import path from "path";
6
6
  import { fileURLToPath } from "url";
7
7
 
8
8
  const PROJECT_ROOT = path.dirname(fileURLToPath(import.meta.url))
9
+ const RUNTIME_STATE_GLOBS = [
10
+ 'data/**/*',
11
+ '.tmp-swarmclaw-build/**/*',
12
+ ]
9
13
 
10
14
  function getGitSha(): string {
11
15
  try {
@@ -46,10 +50,11 @@ function getAllowedDevOrigins(): string[] {
46
50
  const nextConfig: NextConfig = {
47
51
  output: 'standalone',
48
52
  outputFileTracingExcludes: {
49
- '/api/**': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
50
- instrumentation: ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
51
- '/instrumentation': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
52
- 'next-server': ['data/browser-profiles/**/*', 'data/browser-profiles-regression/**/*'],
53
+ '/*': RUNTIME_STATE_GLOBS,
54
+ '/api/**': RUNTIME_STATE_GLOBS,
55
+ instrumentation: RUNTIME_STATE_GLOBS,
56
+ '/instrumentation': RUNTIME_STATE_GLOBS,
57
+ 'next-server': RUNTIME_STATE_GLOBS,
53
58
  },
54
59
  turbopack: {
55
60
  // Pin workspace root to the project directory so a stale lockfile
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.3.6",
3
+ "version": "1.4.2",
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": {
@@ -38,6 +38,9 @@
38
38
  "scripts/easy-update.mjs",
39
39
  "scripts/ensure-sandbox-browser-image.mjs",
40
40
  "scripts/postinstall.mjs",
41
+ "scripts/build-bootstrap-env.mjs",
42
+ "scripts/run-next-build.mjs",
43
+ "scripts/run-next-typegen.mjs",
41
44
  "scripts/sandbox-browser-entrypoint.sh",
42
45
  "next.config.ts",
43
46
  "tsconfig.json",
@@ -52,27 +55,27 @@
52
55
  "dev": "next dev --turbopack --hostname 0.0.0.0 -p 3456",
53
56
  "dev:webpack": "next dev --webpack --hostname 0.0.0.0 -p 3456",
54
57
  "dev:clean": "rm -rf .next && next dev --turbopack --hostname 0.0.0.0 -p 3456",
55
- "build": "next build --webpack",
56
- "build:ci": "NEXT_DISABLE_ESLINT=1 next build --webpack",
58
+ "typegen": "node ./scripts/run-next-typegen.mjs",
59
+ "build": "node ./scripts/run-next-build.mjs",
60
+ "build:ci": "NEXT_DISABLE_ESLINT=1 node ./scripts/run-next-build.mjs",
57
61
  "start": "node .next/standalone/server.js",
58
62
  "start:standalone": "node .next/standalone/server.js",
59
- "smoke:browser": "node ./scripts/browser-route-smoke.mjs",
60
- "smoke:browser:workbench": "node ./scripts/browser-workbench-smoke.mjs",
61
63
  "sandbox:build:browser": "docker build -f Dockerfile.sandbox-browser -t swarmclaw-sandbox-browser:bookworm-slim .",
62
64
  "benchmark:autonomy": "node ./scripts/benchmark-autonomy-harness.mjs",
63
65
  "benchmark:agent-regression": "node --import tsx ./scripts/run-agent-regression-suite.ts",
64
- "type-check": "tsc --noEmit",
65
- "test": "npm run test:cli && npm run test:setup && npm run test:openclaw && npm run test:runtime",
66
+ "type-check": "npm run typegen && tsc --noEmit --incremental false",
67
+ "test": "npm run test:cli && npm run test:setup && npm run test:openclaw && npm run test:runtime && npm run test:builder",
66
68
  "format": "eslint --fix",
67
69
  "lint": "eslint",
68
70
  "lint:fix": "eslint --fix",
69
71
  "lint:baseline": "node ./scripts/lint-baseline.mjs check",
70
72
  "lint:baseline:update": "node ./scripts/lint-baseline.mjs update",
71
73
  "cli": "node ./bin/swarmclaw.js",
72
- "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs",
74
+ "test:cli": "node --test src/cli/*.test.js bin/*.test.js scripts/postinstall.test.mjs scripts/run-next-build.test.mjs scripts/run-next-typegen.test.mjs",
73
75
  "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
76
  "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",
77
+ "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 src/lib/server/safe-parse-body.test.ts src/app/api/approvals/route.test.ts src/app/api/chats/chat-route.test.ts src/app/api/connectors/connector-doctor-route.test.ts src/app/api/logs/route.test.ts src/app/api/tts/route.test.ts",
78
+ "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
79
  "test:e2e": "tsx .workbench/browser-e2e/run.ts",
77
80
  "test:mcp:conformance": "node --import tsx ./scripts/mcp-conformance-check.ts",
78
81
  "prepack": "npm run build:ci",
@@ -88,7 +91,7 @@
88
91
  "@multiavatar/multiavatar": "^1.0.7",
89
92
  "@playwright/mcp": "^0.0.68",
90
93
  "@slack/bolt": "^4.6.0",
91
- "@swarmdock/sdk": "^0.2.3",
94
+ "@swarmdock/sdk": "^0.4.1",
92
95
  "@tailwindcss/postcss": "^4",
93
96
  "@tanstack/react-query": "^5.91.0",
94
97
  "@types/better-sqlite3": "^7.6.13",
@@ -100,6 +103,8 @@
100
103
  "@types/react-dom": "^19",
101
104
  "@types/ws": "^8.18.1",
102
105
  "@whiskeysockets/baileys": "^7.0.0-rc.9",
106
+ "@xyflow/react": "^12.10.2",
107
+ "@xyflow/system": "^0.0.76",
103
108
  "better-sqlite3": "^12.6.2",
104
109
  "bs58": "^5.0.0",
105
110
  "cheerio": "^1.2.0",
@@ -108,12 +113,14 @@
108
113
  "commander": "^13.1.0",
109
114
  "cron-parser": "^5.5.0",
110
115
  "cronstrue": "^3.12.0",
116
+ "dagre": "^0.8.5",
111
117
  "discord.js": "^14.25.1",
112
118
  "ethers": "^6.16.0",
113
119
  "exceljs": "^4.4.0",
114
120
  "grammy": "^1.40.0",
115
121
  "highlight.js": "^11.11.1",
116
122
  "imapflow": "^1.2.11",
123
+ "isomorphic-dompurify": "^3.7.1",
117
124
  "just-bash": "^2.14.0",
118
125
  "langchain": "^1.2.30",
119
126
  "lucide-react": "^0.574.0",
@@ -146,6 +153,7 @@
146
153
  "zustand": "^5.0.11"
147
154
  },
148
155
  "devDependencies": {
156
+ "@types/dagre": "^0.7.54",
149
157
  "eslint": "^9",
150
158
  "eslint-config-next": "16.1.7"
151
159
  },
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+
6
+ export const BUILD_BOOTSTRAP_ROOT_NAME = '.tmp-swarmclaw-build'
7
+
8
+ export function resolveBuildBootstrapPaths(cwd = process.cwd()) {
9
+ const rootDir = path.join(cwd, BUILD_BOOTSTRAP_ROOT_NAME)
10
+ return {
11
+ rootDir,
12
+ dataDir: path.join(rootDir, 'data'),
13
+ workspaceDir: path.join(rootDir, 'workspace'),
14
+ browserProfilesDir: path.join(rootDir, 'browser-profiles'),
15
+ }
16
+ }
17
+
18
+ export function ensureBuildBootstrapPaths(cwd = process.cwd()) {
19
+ const paths = resolveBuildBootstrapPaths(cwd)
20
+ for (const dir of [paths.rootDir, paths.dataDir, paths.workspaceDir, paths.browserProfilesDir]) {
21
+ fs.mkdirSync(dir, { recursive: true })
22
+ }
23
+ return paths
24
+ }
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process'
4
+ import { createRequire } from 'node:module'
5
+ import { pathToFileURL } from 'node:url'
6
+
7
+ import { ensureBuildBootstrapPaths } from './build-bootstrap-env.mjs'
8
+
9
+ const require = createRequire(import.meta.url)
10
+
11
+ export const DEFAULT_MAX_OLD_SPACE_SIZE_MB = '8192'
12
+ export const TRACE_COPY_WARNING = 'Failed to copy traced files'
13
+
14
+ export function mergeNodeOptions(nodeOptions = '', maxOldSpaceSizeMb = DEFAULT_MAX_OLD_SPACE_SIZE_MB) {
15
+ const trimmed = nodeOptions.trim()
16
+ if (/(^|\s)--max-old-space-size(?:=|\s|$)/.test(trimmed)) return trimmed
17
+ return trimmed
18
+ ? `${trimmed} --max-old-space-size=${maxOldSpaceSizeMb}`
19
+ : `--max-old-space-size=${maxOldSpaceSizeMb}`
20
+ }
21
+
22
+ export function buildNextBuildEnv(
23
+ env = process.env,
24
+ maxOldSpaceSizeMb = DEFAULT_MAX_OLD_SPACE_SIZE_MB,
25
+ cwd = process.cwd(),
26
+ ) {
27
+ const bootstrapPaths = ensureBuildBootstrapPaths(cwd)
28
+ return {
29
+ ...env,
30
+ DATA_DIR: bootstrapPaths.dataDir,
31
+ WORKSPACE_DIR: bootstrapPaths.workspaceDir,
32
+ BROWSER_PROFILES_DIR: bootstrapPaths.browserProfilesDir,
33
+ NODE_OPTIONS: mergeNodeOptions(env.NODE_OPTIONS || '', maxOldSpaceSizeMb),
34
+ SWARMCLAW_BUILD_MODE: env.SWARMCLAW_BUILD_MODE || '1',
35
+ }
36
+ }
37
+
38
+ export function hasTraceCopyWarning(output = '') {
39
+ return output.includes(TRACE_COPY_WARNING)
40
+ }
41
+
42
+ export function runNextBuild(args = process.argv.slice(2), env = process.env, cwd = process.cwd()) {
43
+ const nextBin = require.resolve('next/dist/bin/next')
44
+ return spawnSync(process.execPath, [nextBin, 'build', '--webpack', ...args], {
45
+ stdio: 'pipe',
46
+ encoding: 'utf-8',
47
+ env: buildNextBuildEnv(env, DEFAULT_MAX_OLD_SPACE_SIZE_MB, cwd),
48
+ cwd,
49
+ })
50
+ }
51
+
52
+ function main() {
53
+ const result = runNextBuild()
54
+ if (result.stdout) process.stdout.write(result.stdout)
55
+ if (result.stderr) process.stderr.write(result.stderr)
56
+ if (result.error) throw result.error
57
+ const combinedOutput = `${result.stdout || ''}\n${result.stderr || ''}`
58
+ if ((result.status ?? 1) === 0 && hasTraceCopyWarning(combinedOutput)) {
59
+ console.error('Build emitted standalone trace copy warnings; failing to keep CI deterministic.')
60
+ process.exit(1)
61
+ }
62
+ if (typeof result.status === 'number') {
63
+ process.exit(result.status)
64
+ }
65
+ if (result.signal) {
66
+ process.kill(process.pid, result.signal)
67
+ return
68
+ }
69
+ process.exit(1)
70
+ }
71
+
72
+ if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
73
+ main()
74
+ }
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'node:fs'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import { createRequire } from 'node:module'
7
+ import { pathToFileURL } from 'node:url'
8
+
9
+ import { ensureBuildBootstrapPaths } from './build-bootstrap-env.mjs'
10
+
11
+ const require = createRequire(import.meta.url)
12
+
13
+ export const TYPEGEN_ARTIFACT_PATHS = [
14
+ '.next/types',
15
+ '.next/dev/types',
16
+ 'tsconfig.tsbuildinfo',
17
+ ]
18
+
19
+ export function cleanupTypegenArtifacts(cwd = process.cwd()) {
20
+ for (const relativePath of TYPEGEN_ARTIFACT_PATHS) {
21
+ fs.rmSync(path.join(cwd, relativePath), { recursive: true, force: true })
22
+ }
23
+ }
24
+
25
+ export function buildNextTypegenEnv(env = process.env, cwd = process.cwd()) {
26
+ const bootstrapPaths = ensureBuildBootstrapPaths(cwd)
27
+ return {
28
+ ...env,
29
+ DATA_DIR: bootstrapPaths.dataDir,
30
+ WORKSPACE_DIR: bootstrapPaths.workspaceDir,
31
+ BROWSER_PROFILES_DIR: bootstrapPaths.browserProfilesDir,
32
+ SWARMCLAW_BUILD_MODE: env.SWARMCLAW_BUILD_MODE || '1',
33
+ }
34
+ }
35
+
36
+ export function runNextTypegen(args = process.argv.slice(2), env = process.env, cwd = process.cwd()) {
37
+ cleanupTypegenArtifacts(cwd)
38
+ const nextBin = require.resolve('next/dist/bin/next')
39
+ return spawnSync(process.execPath, [nextBin, 'typegen', ...args], {
40
+ stdio: 'inherit',
41
+ env: buildNextTypegenEnv(env, cwd),
42
+ cwd,
43
+ })
44
+ }
45
+
46
+ function main() {
47
+ const result = runNextTypegen()
48
+ if (result.error) throw result.error
49
+ if (typeof result.status === 'number') {
50
+ process.exit(result.status)
51
+ }
52
+ if (result.signal) {
53
+ process.kill(process.pid, result.signal)
54
+ return
55
+ }
56
+ process.exit(1)
57
+ }
58
+
59
+ if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) {
60
+ main()
61
+ }
@@ -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
+ }
@@ -47,7 +47,7 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
47
47
  data: { question: 'Deploy now?' },
48
48
  })
49
49
 
50
- const pendingBefore = await route.GET()
50
+ const pendingBefore = await route.GET(new Request('http://local/api/approvals'))
51
51
  const pendingBeforeJson = await pendingBefore.json()
52
52
 
53
53
  const approveResponse = await route.POST(new Request('http://local/api/approvals', {
@@ -57,7 +57,7 @@ test('GET and POST /api/approvals handle human-loop approvals only', () => {
57
57
  }))
58
58
  const approvePayload = await approveResponse.json()
59
59
 
60
- const pendingAfter = await route.GET()
60
+ const pendingAfter = await route.GET(new Request('http://local/api/approvals'))
61
61
  const pendingAfterJson = await pendingAfter.json()
62
62
 
63
63
  const storedApproval = storage.loadApprovals()[humanApproval.id]
@@ -102,9 +102,35 @@ test('POST /api/approvals rejects invalid payloads', () => {
102
102
  console.log(JSON.stringify({
103
103
  invalidStatus: invalidResponse.status,
104
104
  invalidError: invalidPayload?.error || null,
105
+ invalidIssues: invalidPayload?.issues || null,
105
106
  }))
106
107
  `)
107
108
 
108
109
  assert.equal(output.invalidStatus, 400)
109
- assert.match(String(output.invalidError || ''), /id and approved required/i)
110
+ assert.equal(output.invalidError, 'Validation failed')
111
+ assert.deepEqual(output.invalidIssues, [
112
+ { path: 'approved', message: 'Invalid input: expected boolean, received undefined' },
113
+ ])
114
+ })
115
+
116
+ test('POST /api/approvals rejects malformed JSON with a 400', () => {
117
+ const output = runWithTempDataDir(`
118
+ const routeMod = await import('./src/app/api/approvals/route')
119
+ const route = routeMod.default || routeMod
120
+
121
+ const invalidResponse = await route.POST(new Request('http://local/api/approvals', {
122
+ method: 'POST',
123
+ headers: { 'content-type': 'application/json' },
124
+ body: '{bad-json',
125
+ }))
126
+ const invalidPayload = await invalidResponse.json()
127
+
128
+ console.log(JSON.stringify({
129
+ invalidStatus: invalidResponse.status,
130
+ invalidError: invalidPayload?.error || null,
131
+ }))
132
+ `)
133
+
134
+ assert.equal(output.invalidStatus, 400)
135
+ assert.equal(output.invalidError, 'Invalid or missing request body')
110
136
  })
@@ -1,5 +1,8 @@
1
1
  import { NextResponse } from 'next/server'
2
+ import { z } from 'zod'
3
+
2
4
  import { listPendingApprovals, submitDecision } from '@/lib/server/approvals'
5
+ import { safeParseBody } from '@/lib/server/safe-parse-body'
3
6
  import { loadApprovals } from '@/lib/server/storage'
4
7
  import { errorMessage } from '@/lib/shared-utils'
5
8
  import type { ApprovalCategory } from '@/types'
@@ -11,6 +14,11 @@ const ALLOWED_CATEGORIES: ApprovalCategory[] = [
11
14
  'task_tool', 'connector_sender', 'agent_create', 'budget_change', 'delegation_enable',
12
15
  ]
13
16
 
17
+ const ApprovalDecisionSchema = z.object({
18
+ id: z.string().min(1, 'id is required'),
19
+ approved: z.boolean(),
20
+ })
21
+
14
22
  export async function GET(req: Request) {
15
23
  const { searchParams } = new URL(req.url)
16
24
  const categoryParam = searchParams.get('category') as ApprovalCategory | null
@@ -21,17 +29,15 @@ export async function GET(req: Request) {
21
29
  }
22
30
 
23
31
  export async function POST(req: Request) {
32
+ const { data: body, error } = await safeParseBody(req, ApprovalDecisionSchema)
33
+ if (error) return error
34
+
24
35
  try {
25
- const body = await req.json()
26
- const { id, approved } = body
27
- if (!id || typeof approved !== 'boolean') {
28
- return NextResponse.json({ error: 'id and approved required' }, { status: 400 })
29
- }
30
- const approval = loadApprovals()[id]
36
+ const approval = loadApprovals()[body.id]
31
37
  if (!approval) {
32
38
  return NextResponse.json({ error: 'approval not found' }, { status: 404 })
33
39
  }
34
- await submitDecision(id, approved)
40
+ await submitDecision(body.id, body.approved)
35
41
  return NextResponse.json({ ok: true })
36
42
  } catch (err: unknown) {
37
43
  return NextResponse.json({ error: errorMessage(err) }, { status: 500 })