@swarmclawai/swarmclaw 1.2.3 → 1.2.5

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 (273) hide show
  1. package/README.md +20 -0
  2. package/bin/daemon-cmd.js +169 -0
  3. package/bin/server-cmd.js +3 -0
  4. package/bin/swarmclaw.js +11 -0
  5. package/package.json +17 -16
  6. package/src/app/api/agents/[id]/clone/route.ts +3 -32
  7. package/src/app/api/agents/[id]/route.ts +6 -158
  8. package/src/app/api/agents/[id]/status/route.ts +2 -3
  9. package/src/app/api/agents/[id]/thread/route.ts +4 -17
  10. package/src/app/api/agents/bulk/route.ts +5 -47
  11. package/src/app/api/agents/route.ts +5 -119
  12. package/src/app/api/agents/trash/route.ts +13 -24
  13. package/src/app/api/auth/route.ts +3 -9
  14. package/src/app/api/autonomy/estop/route.ts +5 -5
  15. package/src/app/api/chatrooms/[id]/chat/route.ts +11 -5
  16. package/src/app/api/chatrooms/[id]/route.ts +23 -2
  17. package/src/app/api/chatrooms/route.ts +13 -2
  18. package/src/app/api/chats/[id]/clear/route.ts +2 -13
  19. package/src/app/api/chats/[id]/deploy/route.ts +2 -3
  20. package/src/app/api/chats/[id]/edit-resend/route.ts +7 -13
  21. package/src/app/api/chats/[id]/mailbox/route.ts +6 -8
  22. package/src/app/api/chats/[id]/queue/route.ts +17 -64
  23. package/src/app/api/chats/[id]/retry/route.ts +4 -22
  24. package/src/app/api/chats/[id]/route.ts +10 -138
  25. package/src/app/api/chats/heartbeat/route.ts +2 -1
  26. package/src/app/api/chats/migrate-messages/route.ts +7 -0
  27. package/src/app/api/chats/route.ts +13 -134
  28. package/src/app/api/connectors/[id]/access/route.ts +12 -229
  29. package/src/app/api/connectors/[id]/doctor/route.ts +1 -1
  30. package/src/app/api/connectors/[id]/health/route.ts +12 -39
  31. package/src/app/api/connectors/[id]/route.ts +14 -122
  32. package/src/app/api/connectors/[id]/webhook/route.ts +1 -1
  33. package/src/app/api/connectors/doctor/route.ts +1 -1
  34. package/src/app/api/connectors/route.ts +12 -70
  35. package/src/app/api/credentials/[id]/route.ts +2 -4
  36. package/src/app/api/credentials/route.ts +10 -19
  37. package/src/app/api/daemon/health-check/route.ts +3 -4
  38. package/src/app/api/daemon/route.ts +10 -8
  39. package/src/app/api/documents/route.ts +11 -10
  40. package/src/app/api/external-agents/route.ts +3 -3
  41. package/src/app/api/gateways/[id]/health/route.ts +2 -3
  42. package/src/app/api/gateways/[id]/route.ts +7 -122
  43. package/src/app/api/gateways/route.ts +3 -103
  44. package/src/app/api/mcp-servers/[id]/tools/route.ts +5 -5
  45. package/src/app/api/openclaw/dashboard-url/route.ts +8 -16
  46. package/src/app/api/openclaw/directory/route.ts +2 -2
  47. package/src/app/api/openclaw/history/route.ts +3 -5
  48. package/src/app/api/providers/[id]/models/route.test.ts +60 -0
  49. package/src/app/api/providers/[id]/models/route.ts +33 -1
  50. package/src/app/api/providers/[id]/route.test.ts +49 -0
  51. package/src/app/api/providers/[id]/route.ts +30 -1
  52. package/src/app/api/providers/ollama/route.ts +6 -5
  53. package/src/app/api/schedules/[id]/route.ts +14 -108
  54. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  55. package/src/app/api/schedules/route.ts +9 -51
  56. package/src/app/api/settings/route.ts +4 -3
  57. package/src/app/api/setup/check-provider/route.ts +15 -1
  58. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  59. package/src/app/api/system/status/route.ts +2 -2
  60. package/src/app/api/tasks/[id]/route.ts +16 -202
  61. package/src/app/api/tasks/bulk/route.ts +5 -86
  62. package/src/app/api/tasks/metrics/route.ts +2 -1
  63. package/src/app/api/tasks/route.ts +11 -171
  64. package/src/app/api/upload/route.ts +1 -1
  65. package/src/app/api/uploads/[filename]/route.ts +1 -1
  66. package/src/app/api/uploads/route.ts +1 -1
  67. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  68. package/src/app/layout.tsx +9 -6
  69. package/src/app/protocols/page.tsx +71 -89
  70. package/src/app/tasks/page.tsx +32 -32
  71. package/src/cli/index.js +1 -0
  72. package/src/cli/spec.js +1 -0
  73. package/src/components/agents/agent-sheet.tsx +51 -25
  74. package/src/components/agents/inspector-panel.tsx +15 -4
  75. package/src/components/auth/setup-wizard/index.tsx +27 -18
  76. package/src/components/auth/setup-wizard/shared.tsx +2 -2
  77. package/src/components/auth/setup-wizard/step-agents.tsx +51 -38
  78. package/src/components/auth/setup-wizard/step-connect.tsx +48 -17
  79. package/src/components/auth/setup-wizard/types.ts +6 -4
  80. package/src/components/auth/setup-wizard/utils.test.ts +38 -8
  81. package/src/components/auth/setup-wizard/utils.ts +14 -8
  82. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  83. package/src/components/connectors/connector-list.tsx +26 -40
  84. package/src/components/connectors/connector-sheet.tsx +95 -149
  85. package/src/components/gateways/gateway-sheet.tsx +61 -110
  86. package/src/components/layout/live-query-sync.tsx +121 -0
  87. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  88. package/src/components/providers/app-query-provider.tsx +17 -0
  89. package/src/components/providers/provider-list.tsx +150 -77
  90. package/src/components/providers/provider-sheet.tsx +102 -77
  91. package/src/components/shared/model-combobox.tsx +5 -4
  92. package/src/components/skills/skill-list.tsx +5 -18
  93. package/src/components/skills/skill-sheet.tsx +21 -20
  94. package/src/components/skills/skills-workspace.tsx +48 -87
  95. package/src/components/tasks/task-card.tsx +20 -13
  96. package/src/components/tasks/task-column.tsx +22 -7
  97. package/src/components/tasks/task-list.tsx +8 -11
  98. package/src/components/tasks/task-sheet.tsx +111 -103
  99. package/src/features/agents/queries.ts +20 -0
  100. package/src/features/chatrooms/queries.ts +20 -0
  101. package/src/features/chats/queries.ts +27 -0
  102. package/src/features/connectors/queries.ts +145 -0
  103. package/src/features/credentials/queries.ts +37 -0
  104. package/src/features/extensions/queries.ts +26 -0
  105. package/src/features/external-agents/queries.ts +36 -0
  106. package/src/features/gateways/queries.ts +274 -0
  107. package/src/features/missions/queries.ts +23 -0
  108. package/src/features/projects/queries.ts +20 -0
  109. package/src/features/protocols/queries.ts +149 -0
  110. package/src/features/providers/queries.ts +142 -0
  111. package/src/features/settings/queries.ts +20 -0
  112. package/src/features/skills/queries.ts +182 -0
  113. package/src/features/tasks/queries.ts +189 -0
  114. package/src/hooks/use-ws.ts +3 -2
  115. package/src/lib/agent-provider-options.test.ts +152 -0
  116. package/src/lib/agent-provider-options.ts +84 -0
  117. package/src/lib/app/api-client.ts +2 -2
  118. package/src/lib/providers/index.test.ts +78 -0
  119. package/src/lib/providers/index.ts +13 -10
  120. package/src/lib/query/client.ts +17 -0
  121. package/src/lib/server/agents/agent-runtime-config.ts +6 -6
  122. package/src/lib/server/agents/agent-service.ts +429 -0
  123. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  124. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  125. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  126. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  127. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  128. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  129. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  130. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  131. package/src/lib/server/build-llm.ts +7 -15
  132. package/src/lib/server/capability-router.test.ts +70 -1
  133. package/src/lib/server/capability-router.ts +24 -99
  134. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  135. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  136. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  137. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  138. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  139. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  140. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  141. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  142. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  143. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  144. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  145. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  146. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  147. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  148. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  149. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  150. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  151. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  152. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  153. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  154. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  155. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  156. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  157. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  158. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  159. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  160. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  161. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  162. package/src/lib/server/chats/chat-session-service.ts +410 -0
  163. package/src/lib/server/connectors/access.ts +1 -1
  164. package/src/lib/server/connectors/commands.ts +7 -6
  165. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  166. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  167. package/src/lib/server/connectors/connector-service.ts +453 -0
  168. package/src/lib/server/connectors/delivery.ts +17 -12
  169. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  170. package/src/lib/server/connectors/media.ts +1 -1
  171. package/src/lib/server/connectors/response-media.ts +1 -1
  172. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  173. package/src/lib/server/connectors/session.ts +9 -7
  174. package/src/lib/server/connectors/voice-note.ts +2 -1
  175. package/src/lib/server/context-manager.ts +20 -1
  176. package/src/lib/server/cost.ts +2 -3
  177. package/src/lib/server/credentials/credential-repository.ts +43 -4
  178. package/src/lib/server/credentials/credential-service.ts +112 -0
  179. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  180. package/src/lib/server/daemon/controller.ts +577 -0
  181. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  182. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  183. package/src/lib/server/daemon/types.ts +101 -0
  184. package/src/lib/server/embeddings.ts +3 -9
  185. package/src/lib/server/eval/agent-regression.ts +3 -2
  186. package/src/lib/server/eval/runner.ts +2 -2
  187. package/src/lib/server/execution-brief.test.ts +167 -0
  188. package/src/lib/server/execution-brief.ts +295 -0
  189. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  190. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  191. package/src/lib/server/execution-engine/index.ts +35 -0
  192. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  193. package/src/lib/server/execution-engine/types.ts +33 -0
  194. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  195. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  196. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  197. package/src/lib/server/messages/message-repository.ts +330 -0
  198. package/src/lib/server/missions/mission-service/core.ts +8 -6
  199. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  200. package/src/lib/server/openclaw/doctor.ts +1 -1
  201. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  202. package/src/lib/server/openclaw/gateway.ts +5 -14
  203. package/src/lib/server/openclaw/health.ts +3 -11
  204. package/src/lib/server/openclaw/sync.ts +8 -6
  205. package/src/lib/server/persistence/storage-context.ts +3 -0
  206. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  207. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  208. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  209. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  210. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  211. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  212. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  213. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  214. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  215. package/src/lib/server/protocols/protocol-types.ts +10 -7
  216. package/src/lib/server/provider-endpoint.ts +7 -12
  217. package/src/lib/server/provider-model-discovery.ts +2 -11
  218. package/src/lib/server/query-expansion.ts +5 -6
  219. package/src/lib/server/run-context.test.ts +365 -0
  220. package/src/lib/server/run-context.ts +367 -0
  221. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  222. package/src/lib/server/runtime/queue/core.ts +61 -190
  223. package/src/lib/server/runtime/run-ledger.ts +8 -0
  224. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  225. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  226. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  227. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  228. package/src/lib/server/service-result.ts +16 -0
  229. package/src/lib/server/session-note.ts +2 -3
  230. package/src/lib/server/session-reset-policy.ts +4 -3
  231. package/src/lib/server/session-tools/connector.ts +9 -6
  232. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  233. package/src/lib/server/session-tools/crud.ts +162 -10
  234. package/src/lib/server/session-tools/delegate.ts +1 -1
  235. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  236. package/src/lib/server/session-tools/memory.ts +6 -4
  237. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  238. package/src/lib/server/session-tools/session-info.ts +119 -12
  239. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  240. package/src/lib/server/session-tools/skills.ts +15 -15
  241. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  242. package/src/lib/server/session-tools/subagent.ts +125 -7
  243. package/src/lib/server/session-tools/team-context.ts +4 -3
  244. package/src/lib/server/session-tools/wallet.ts +0 -58
  245. package/src/lib/server/sessions/session-lineage.ts +55 -0
  246. package/src/lib/server/sessions/session-repository.ts +2 -2
  247. package/src/lib/server/skills/learned-skills.ts +24 -23
  248. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  249. package/src/lib/server/skills/skill-repository.ts +136 -13
  250. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  251. package/src/lib/server/storage-normalization.test.ts +42 -215
  252. package/src/lib/server/storage-normalization.ts +98 -0
  253. package/src/lib/server/storage.ts +19 -0
  254. package/src/lib/server/structured-extract.ts +3 -14
  255. package/src/lib/server/tasks/task-followups.ts +16 -11
  256. package/src/lib/server/tasks/task-result.test.ts +25 -29
  257. package/src/lib/server/tasks/task-result.ts +5 -9
  258. package/src/lib/server/tasks/task-route-service.ts +449 -0
  259. package/src/lib/server/text-normalization.ts +41 -0
  260. package/src/lib/server/tool-planning.ts +6 -42
  261. package/src/lib/server/upload-path.ts +5 -0
  262. package/src/lib/server/working-state/extraction.ts +614 -0
  263. package/src/lib/server/working-state/normalization.ts +866 -0
  264. package/src/lib/server/working-state/prompt.ts +60 -0
  265. package/src/lib/server/working-state/repository.ts +38 -0
  266. package/src/lib/server/working-state/service.test.ts +253 -0
  267. package/src/lib/server/working-state/service.ts +293 -0
  268. package/src/lib/validation/schemas.ts +1 -0
  269. package/src/lib/ws-client.ts +3 -3
  270. package/src/stores/slices/task-slice.ts +1 -4
  271. package/src/stores/use-chatroom-store.ts +2 -2
  272. package/src/types/index.ts +288 -22
  273. package/src/views/settings/section-providers.tsx +2 -2
package/README.md CHANGED
@@ -190,6 +190,26 @@ The building blocks are the same: **agents, tools, memory, delegation, schedules
190
190
 
191
191
  ## Release Notes
192
192
 
193
+ ### v1.2.5 Highlights
194
+
195
+ - **Working memory hierarchy**: agents maintain structured working state (facts, plans, decisions, blockers, evidence) that persists across turns and survives context compaction.
196
+ - **Execution brief**: each chat turn receives a concise briefing document synthesized from working state for faster, more focused agent reasoning.
197
+ - **RunContext**: persistent structured context tracks session lineage, delegation chain, and accumulated working memory across run lifecycle.
198
+ - **Unified message flow**: consolidated message handling and execution routing into a single coherent pipeline.
199
+ - **Delegation advisory**: new advisory layer for structured capability analysis during delegation decisions.
200
+ - **Real-time session sync**: session create, update, and delete events now push over WebSocket, replacing poll-only refresh for the chat list.
201
+ - **HMR resilience**: module-level state in the WebSocket client, fallback polling, and API request dedup now survives Next.js hot-module reloads.
202
+ - **Type safety sweep**: eliminated 16 `any` types across 7 API routes with proper narrowing and error guards.
203
+ - **Bug fix — setup wizard crash**: replaced client-side `crypto.randomUUID()` with browser-safe alternative, fixing fresh-install failures with Ollama and other providers.
204
+ - **Bug fix — custom provider validation**: connection-test endpoint now recognizes custom providers from storage instead of rejecting them as "Unsupported provider."
205
+ - **Bug fix — session cwd normalization**: `updateChatSession` now expands `~` paths consistently with session creation.
206
+
207
+ ### v1.2.4 Highlights
208
+
209
+ - **Custom providers in agent config**: agent setup and inline model switching now merge saved custom provider configs into the selectable provider list, so custom providers show up reliably even when the built-in provider feed is stale or incomplete.
210
+ - **Custom provider save-only flow**: the Providers screen no longer forces connection tests or live model discovery for custom providers; operators can save the endpoint, linked key, and manual model list directly.
211
+ - **Custom provider runtime routing**: saved custom-provider model lists and linked credentials now flow through the agent UI and runtime resolution paths consistently, including legacy `provider_configs` records normalized on load.
212
+
193
213
  ### v1.2.3 Highlights
194
214
 
195
215
  - **Standalone asset staging repair**: `swarmclaw server` now copies `.next/static` and `public/` into the Next.js standalone runtime after the first build, preventing blank UI loads and 503s for CSS, JS, and image assets.
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+ /* eslint-disable @typescript-eslint/no-require-imports */
4
+
5
+ const crypto = require('node:crypto')
6
+ const fs = require('node:fs')
7
+ const net = require('node:net')
8
+ const path = require('node:path')
9
+ const { spawn } = require('node:child_process')
10
+
11
+ const {
12
+ BROWSER_PROFILES_DIR,
13
+ DATA_DIR,
14
+ PKG_ROOT,
15
+ SWARMCLAW_HOME,
16
+ WORKSPACE_DIR,
17
+ resolvePackageBuildRoot,
18
+ } = require('./server-cmd.js')
19
+
20
+ function printHelp() {
21
+ const help = `
22
+ Usage: swarmclaw daemon run [options]
23
+
24
+ Run the detached SwarmClaw runtime daemon outside the public web process.
25
+
26
+ Options:
27
+ -d, --detach Start daemon in background
28
+ --port <port> Admin port to bind on localhost (default: random)
29
+ --token <token> Admin bearer token (default: random)
30
+ -h, --help Show this help message
31
+
32
+ Other daemon controls remain available through the API-backed CLI:
33
+ swarmclaw daemon status
34
+ swarmclaw daemon start
35
+ swarmclaw daemon stop
36
+ swarmclaw daemon health-check
37
+ `.trim()
38
+ console.log(help)
39
+ }
40
+
41
+ function resolveRoot() {
42
+ const buildRoot = process.env.SWARMCLAW_BUILD_ROOT || resolvePackageBuildRoot(PKG_ROOT)
43
+ if (fs.existsSync(path.join(buildRoot, 'src', 'lib', 'server', 'daemon', 'daemon-runtime.ts'))) return buildRoot
44
+ return PKG_ROOT
45
+ }
46
+
47
+ function resolveEntry(root) {
48
+ const entry = path.join(root, 'src', 'lib', 'server', 'daemon', 'daemon-runtime.ts')
49
+ if (!fs.existsSync(entry)) {
50
+ throw new Error(`Daemon runtime entry not found at ${entry}`)
51
+ }
52
+ return entry
53
+ }
54
+
55
+ function reservePort() {
56
+ return new Promise((resolve, reject) => {
57
+ const server = net.createServer()
58
+ server.once('error', reject)
59
+ server.listen(0, '127.0.0.1', () => {
60
+ const address = server.address()
61
+ if (!address || typeof address === 'string') {
62
+ server.close(() => reject(new Error('Failed to reserve daemon port.')))
63
+ return
64
+ }
65
+ const port = address.port
66
+ server.close((err) => {
67
+ if (err) reject(err)
68
+ else resolve(port)
69
+ })
70
+ })
71
+ })
72
+ }
73
+
74
+ function buildEnv(root, port, token) {
75
+ return {
76
+ ...process.env,
77
+ SWARMCLAW_HOME,
78
+ DATA_DIR,
79
+ WORKSPACE_DIR,
80
+ BROWSER_PROFILES_DIR,
81
+ SWARMCLAW_PACKAGE_ROOT: PKG_ROOT,
82
+ SWARMCLAW_BUILD_ROOT: root,
83
+ SWARMCLAW_RUNTIME_ROLE: 'daemon',
84
+ SWARMCLAW_DAEMON_BACKGROUND_SERVICES: '1',
85
+ SWARMCLAW_DAEMON_ADMIN_PORT: String(port),
86
+ SWARMCLAW_DAEMON_ADMIN_TOKEN: token,
87
+ }
88
+ }
89
+
90
+ async function runDaemon(options) {
91
+ const root = resolveRoot()
92
+ const entry = resolveEntry(root)
93
+ const port = options.port || await reservePort()
94
+ const token = options.token || crypto.randomBytes(24).toString('hex')
95
+ const env = buildEnv(root, port, token)
96
+ const args = ['--no-warnings', '--import', 'tsx', entry, '--port', String(port), '--token', token]
97
+
98
+ if (options.detach) {
99
+ const logPath = path.join(SWARMCLAW_HOME, 'daemon.log')
100
+ fs.mkdirSync(path.dirname(logPath), { recursive: true })
101
+ const logStream = fs.openSync(logPath, 'a')
102
+ const child = spawn(process.execPath, args, {
103
+ cwd: root,
104
+ detached: true,
105
+ env,
106
+ stdio: ['ignore', logStream, logStream],
107
+ })
108
+ child.unref()
109
+ console.log(`[swarmclaw] Daemon started in background (PID: ${child.pid})`)
110
+ console.log(`[swarmclaw] Admin port: ${port}`)
111
+ console.log(`[swarmclaw] Logs: ${logPath}`)
112
+ return
113
+ }
114
+
115
+ console.log(`[swarmclaw] Starting daemon runtime on 127.0.0.1:${port}`)
116
+ const child = spawn(process.execPath, args, {
117
+ cwd: root,
118
+ env,
119
+ stdio: 'inherit',
120
+ })
121
+ child.on('exit', (code) => {
122
+ process.exit(code || 0)
123
+ })
124
+ for (const signal of ['SIGINT', 'SIGTERM']) {
125
+ process.on(signal, () => child.kill(signal))
126
+ }
127
+ }
128
+
129
+ async function main(args = process.argv.slice(3)) {
130
+ let detach = false
131
+ let port = null
132
+ let token = ''
133
+ let command = 'run'
134
+
135
+ for (let index = 0; index < args.length; index += 1) {
136
+ const arg = args[index]
137
+ if (arg === 'run') {
138
+ command = 'run'
139
+ } else if (arg === '-d' || arg === '--detach') {
140
+ detach = true
141
+ } else if (arg === '--port' && index + 1 < args.length) {
142
+ port = Number.parseInt(args[index + 1], 10)
143
+ index += 1
144
+ } else if (arg === '--token' && index + 1 < args.length) {
145
+ token = args[index + 1] || ''
146
+ index += 1
147
+ } else if (arg === '-h' || arg === '--help' || arg === 'help') {
148
+ printHelp()
149
+ return
150
+ } else {
151
+ throw new Error(`Unknown daemon argument: ${arg}`)
152
+ }
153
+ }
154
+
155
+ if (command !== 'run') {
156
+ throw new Error(`Unsupported daemon command: ${command}`)
157
+ }
158
+
159
+ await runDaemon({ detach, port, token })
160
+ }
161
+
162
+ if (require.main === module) {
163
+ void main().catch((err) => {
164
+ console.error(`[swarmclaw] ${err?.message || String(err)}`)
165
+ process.exit(1)
166
+ })
167
+ }
168
+
169
+ module.exports = { main }
package/bin/server-cmd.js CHANGED
@@ -381,6 +381,9 @@ async function startServer(opts, { pkgRoot = PKG_ROOT } = {}) {
381
381
  DATA_DIR,
382
382
  WORKSPACE_DIR,
383
383
  BROWSER_PROFILES_DIR,
384
+ SWARMCLAW_PACKAGE_ROOT: pkgRoot,
385
+ SWARMCLAW_BUILD_ROOT: buildRoot,
386
+ SWARMCLAW_RUNTIME_ROLE: 'web',
384
387
  HOSTNAME: host,
385
388
  PORT: port,
386
389
  WS_PORT: wsPort,
package/bin/swarmclaw.js CHANGED
@@ -131,6 +131,10 @@ async function runHelp(argv) {
131
131
  await require('./server-cmd.js').main(['--help'])
132
132
  return
133
133
  }
134
+ if (target === 'daemon') {
135
+ await require('./daemon-cmd.js').main(['--help'])
136
+ return
137
+ }
134
138
  if (target === 'worker') {
135
139
  require('./worker-cmd.js').main(['--help'])
136
140
  return
@@ -188,6 +192,13 @@ async function main() {
188
192
  await require('./server-cmd.js').main(argv.slice(1))
189
193
  return
190
194
  }
195
+ if (top === 'daemon') {
196
+ const subcommand = argv[1]
197
+ if (!subcommand || subcommand === 'run' || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
198
+ await require('./daemon-cmd.js').main(argv.slice(1))
199
+ return
200
+ }
201
+ }
191
202
  if (top === 'run' || top === 'start') {
192
203
  await require('./server-cmd.js').main(argv.slice(1))
193
204
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swarmclawai/swarmclaw",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
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": {
@@ -81,14 +81,23 @@
81
81
  "@huggingface/transformers": "^3.8.1",
82
82
  "@langchain/anthropic": "^1.3.18",
83
83
  "@langchain/core": "^1.1.31",
84
- "@modelcontextprotocol/sdk": "^1.27.1",
85
- "@tailwindcss/postcss": "^4",
86
84
  "@langchain/langgraph": "^1.2.2",
87
85
  "@langchain/openai": "^1.2.8",
86
+ "@modelcontextprotocol/sdk": "^1.27.1",
88
87
  "@multiavatar/multiavatar": "^1.0.7",
89
88
  "@playwright/mcp": "^0.0.68",
90
89
  "@slack/bolt": "^4.6.0",
91
90
  "@solana/web3.js": "^1.98.4",
91
+ "@tailwindcss/postcss": "^4",
92
+ "@tanstack/react-query": "^5.91.0",
93
+ "@types/better-sqlite3": "^7.6.13",
94
+ "@types/mailparser": "^3.4.6",
95
+ "@types/node": "^20",
96
+ "@types/nodemailer": "^7.0.11",
97
+ "@types/qrcode": "^1.5.6",
98
+ "@types/react": "^19",
99
+ "@types/react-dom": "^19",
100
+ "@types/ws": "^8.18.1",
92
101
  "@whiskeysockets/baileys": "^7.0.0-rc.9",
93
102
  "better-sqlite3": "^12.6.2",
94
103
  "bs58": "^5.0.0",
@@ -125,26 +134,18 @@
125
134
  "remove-markdown": "^0.6.3",
126
135
  "shadcn": "^3.8.5",
127
136
  "sonner": "^2.0.7",
128
- "tailwindcss": "^4",
129
137
  "tailwind-merge": "^3.4.1",
130
- "typescript": "^5",
138
+ "tailwindcss": "^4",
131
139
  "tw-animate-css": "^1.4.0",
140
+ "tsx": "^4.20.6",
141
+ "typescript": "^5",
132
142
  "ws": "^8.19.0",
133
143
  "zod": "^4.3.6",
134
- "zustand": "^5.0.11",
135
- "@types/better-sqlite3": "^7.6.13",
136
- "@types/mailparser": "^3.4.6",
137
- "@types/node": "^20",
138
- "@types/nodemailer": "^7.0.11",
139
- "@types/qrcode": "^1.5.6",
140
- "@types/react": "^19",
141
- "@types/react-dom": "^19",
142
- "@types/ws": "^8.18.1"
144
+ "zustand": "^5.0.11"
143
145
  },
144
146
  "devDependencies": {
145
147
  "eslint": "^9",
146
- "eslint-config-next": "16.1.7",
147
- "tsx": "^4.20.6"
148
+ "eslint-config-next": "16.1.7"
148
149
  },
149
150
  "optionalDependencies": {
150
151
  "botbuilder": "^4.23.3",
@@ -1,40 +1,11 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents, saveAgents, logActivity } from '@/lib/server/storage'
3
- import { notify } from '@/lib/server/ws-hub'
2
+ import { cloneAgent } from '@/lib/server/agents/agent-service'
4
3
 
5
4
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
6
5
  const { id } = await params
7
- const agents = loadAgents({ includeTrashed: true })
8
- const source = agents[id]
9
- if (!source) {
6
+ const cloned = cloneAgent(id)
7
+ if (!cloned) {
10
8
  return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
11
9
  }
12
-
13
- const newId = crypto.randomUUID()
14
- const now = Date.now()
15
-
16
- // Deep-copy the source agent, then override clone-specific fields
17
- const cloned = JSON.parse(JSON.stringify(source)) as typeof source
18
- cloned.id = newId
19
- cloned.name = `${source.name} (Copy)`
20
- cloned.createdAt = now
21
- cloned.updatedAt = now
22
- cloned.totalCost = 0
23
- cloned.lastUsedAt = undefined
24
- cloned.threadSessionId = null
25
- cloned.pinned = false
26
- cloned.trashedAt = undefined
27
-
28
- agents[newId] = cloned
29
- saveAgents(agents)
30
- logActivity({
31
- entityType: 'agent',
32
- entityId: newId,
33
- action: 'created',
34
- actor: 'user',
35
- summary: `Agent cloned from "${source.name}": "${cloned.name}"`,
36
- })
37
- notify('agents')
38
-
39
10
  return NextResponse.json(cloned)
40
11
  }
@@ -1,175 +1,23 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents, saveAgents, loadSessions, logActivity, upsertStoredItem, upsertStoredItems } from '@/lib/server/storage'
3
- import { normalizeProviderEndpoint } from '@/lib/openclaw/openclaw-endpoint'
4
- import { mutateItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
5
- import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
6
- import { suspendAgentReferences } from '@/lib/server/agents/agent-cascade'
2
+ import { notFound } from '@/lib/server/collection-helpers'
3
+ import { trashAgent, updateAgent } from '@/lib/server/agents/agent-service'
7
4
  import { notify } from '@/lib/server/ws-hub'
8
- import { normalizeAgentSandboxConfig } from '@/lib/agent-sandbox-defaults'
9
- import { normalizeCapabilitySelection } from '@/lib/capability-selection'
10
- import { normalizeOrchestratorConfig } from '@/lib/orchestrator-config'
11
5
  import { safeParseBody } from '@/lib/server/safe-parse-body'
12
6
 
13
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
- const ops: CollectionOps<any> = { load: () => loadAgents({ includeTrashed: true }), save: saveAgents, topic: 'agents', table: 'agents' }
15
-
16
7
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
17
8
  const { id } = await params
18
9
  const { data: body, error } = await safeParseBody(req)
19
10
  if (error) return error
20
- const result = mutateItem(ops, id, (agent) => {
21
- Object.assign(agent, body, { updatedAt: Date.now() })
22
- if (body.tools !== undefined || body.extensions !== undefined) {
23
- const nextSelection = normalizeCapabilitySelection({
24
- tools: Array.isArray(body.tools) ? body.tools : agent.tools,
25
- extensions: Array.isArray(body.extensions) ? body.extensions : agent.extensions,
26
- })
27
- agent.tools = nextSelection.tools
28
- agent.extensions = nextSelection.extensions
29
- }
30
- if (body.delegationEnabled !== undefined) {
31
- agent.delegationEnabled = body.delegationEnabled === true
32
- }
33
- if (body.delegationTargetMode === 'all' || body.delegationTargetMode === 'selected') {
34
- agent.delegationTargetMode = body.delegationTargetMode
35
- }
36
- if (body.delegationTargetAgentIds !== undefined) {
37
- agent.delegationTargetAgentIds = Array.isArray(body.delegationTargetAgentIds)
38
- ? body.delegationTargetAgentIds.filter((entry: unknown): entry is string => typeof entry === 'string' && entry.trim().length > 0)
39
- : []
40
- }
41
- if (agent.delegationTargetMode !== 'selected') {
42
- agent.delegationTargetAgentIds = []
43
- }
44
- if (body.apiEndpoint !== undefined) {
45
- agent.apiEndpoint = normalizeProviderEndpoint(
46
- body.provider || agent.provider,
47
- body.apiEndpoint as string | null | undefined,
48
- )
49
- }
50
- if (body.provider !== undefined && body.provider !== 'ollama' && body.ollamaMode === undefined) {
51
- agent.ollamaMode = null
52
- }
53
- if (body.sandboxConfig !== undefined) {
54
- agent.sandboxConfig = normalizeAgentSandboxConfig(body.sandboxConfig)
55
- }
56
- if (
57
- body.provider !== undefined
58
- || body.orchestratorEnabled !== undefined
59
- || body.orchestratorMission !== undefined
60
- || body.orchestratorWakeInterval !== undefined
61
- || body.orchestratorGovernance !== undefined
62
- || body.orchestratorMaxCyclesPerDay !== undefined
63
- ) {
64
- const orchestratorConfig = normalizeOrchestratorConfig({
65
- provider: typeof body.provider === 'string' ? body.provider : agent.provider,
66
- orchestratorEnabled: body.orchestratorEnabled ?? agent.orchestratorEnabled,
67
- orchestratorMission: body.orchestratorMission ?? agent.orchestratorMission,
68
- orchestratorWakeInterval: body.orchestratorWakeInterval ?? agent.orchestratorWakeInterval,
69
- orchestratorGovernance: body.orchestratorGovernance ?? agent.orchestratorGovernance,
70
- orchestratorMaxCyclesPerDay: body.orchestratorMaxCyclesPerDay ?? agent.orchestratorMaxCyclesPerDay,
71
- })
72
- agent.orchestratorEnabled = orchestratorConfig.orchestratorEnabled
73
- agent.orchestratorMission = orchestratorConfig.orchestratorMission
74
- agent.orchestratorWakeInterval = orchestratorConfig.orchestratorWakeInterval
75
- agent.orchestratorGovernance = orchestratorConfig.orchestratorGovernance
76
- agent.orchestratorMaxCyclesPerDay = orchestratorConfig.orchestratorMaxCyclesPerDay
77
- }
78
- if (body.preferredGatewayTags !== undefined) {
79
- agent.preferredGatewayTags = Array.isArray(body.preferredGatewayTags)
80
- ? body.preferredGatewayTags.filter((tag: unknown): tag is string => typeof tag === 'string' && tag.trim().length > 0)
81
- : []
82
- }
83
- if (body.preferredGatewayUseCase !== undefined) {
84
- agent.preferredGatewayUseCase = typeof body.preferredGatewayUseCase === 'string' && body.preferredGatewayUseCase.trim()
85
- ? body.preferredGatewayUseCase.trim()
86
- : null
87
- }
88
- if (body.routingTargets !== undefined && Array.isArray(body.routingTargets)) {
89
- agent.routingTargets = body.routingTargets.map((target: Record<string, unknown>, index: number) => ({
90
- id: typeof target.id === 'string' && target.id.trim() ? target.id.trim() : `route-${index + 1}`,
91
- label: typeof target.label === 'string' ? target.label : undefined,
92
- role: target.role,
93
- provider: (typeof target.provider === 'string' && target.provider.trim() ? target.provider : agent.provider),
94
- model: typeof target.model === 'string' ? target.model : '',
95
- ollamaMode: (typeof target.provider === 'string' ? target.provider : agent.provider) === 'ollama'
96
- ? (target.ollamaMode === 'cloud' ? 'cloud' : 'local')
97
- : null,
98
- credentialId: target.credentialId ?? null,
99
- fallbackCredentialIds: Array.isArray(target.fallbackCredentialIds) ? target.fallbackCredentialIds : [],
100
- apiEndpoint: normalizeProviderEndpoint(
101
- typeof target.provider === 'string' ? target.provider : agent.provider,
102
- typeof target.apiEndpoint === 'string' ? target.apiEndpoint : null,
103
- ),
104
- gatewayProfileId: target.gatewayProfileId ?? null,
105
- preferredGatewayTags: Array.isArray(target.preferredGatewayTags)
106
- ? target.preferredGatewayTags.filter((tag: unknown): tag is string => typeof tag === 'string' && tag.trim().length > 0)
107
- : [],
108
- preferredGatewayUseCase: typeof target.preferredGatewayUseCase === 'string' && target.preferredGatewayUseCase.trim()
109
- ? target.preferredGatewayUseCase.trim()
110
- : null,
111
- priority: typeof target.priority === 'number' ? target.priority : index + 1,
112
- }))
113
- }
114
- delete (agent as Record<string, unknown>).platformAssignScope
115
- delete (agent as Record<string, unknown>).subAgentIds
116
- delete (agent as Record<string, unknown>).id
117
- agent.id = id
118
- return agent
119
- })
11
+ const result = updateAgent(id, body as Record<string, unknown>)
120
12
  if (!result) return notFound()
121
-
122
- if (result.threadSessionId) {
123
- ensureAgentThreadSession(id)
124
- }
125
-
126
- if (result.threadSessionId) {
127
- const sessions = loadSessions()
128
- const shortcut = sessions[result.threadSessionId]
129
- if (shortcut) {
130
- let changed = false
131
- if (shortcut.name !== result.name) {
132
- shortcut.name = result.name
133
- changed = true
134
- }
135
- if (shortcut.shortcutForAgentId !== id) {
136
- shortcut.shortcutForAgentId = id
137
- changed = true
138
- }
139
- if (changed) upsertStoredItem('sessions', shortcut.id, shortcut)
140
- }
141
- }
142
-
143
- logActivity({ entityType: 'agent', entityId: id, action: 'updated', actor: 'user', summary: `Agent updated: "${result.name}"` })
144
13
  return NextResponse.json(result)
145
14
  }
146
15
 
147
16
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
148
17
  const { id } = await params
149
- // Soft delete — set trashedAt instead of removing the record
150
- const result = mutateItem(ops, id, (agent) => {
151
- agent.trashedAt = Date.now()
152
- return agent
153
- })
154
- if (!result) return notFound()
155
- logActivity({ entityType: 'agent', entityId: id, action: 'deleted', actor: 'user', summary: `Agent trashed: "${result.name}"` })
156
-
157
- // Detach sessions from the trashed agent
158
- const sessions = loadSessions()
159
- const detached: Array<[string, unknown]> = []
160
- for (const session of Object.values(sessions)) {
161
- if (!session || session.agentId !== id) continue
162
- session.agentId = null
163
- session.heartbeatEnabled = false
164
- detached.push([session.id, session])
165
- }
166
- if (detached.length > 0) {
167
- upsertStoredItems('sessions', detached)
168
- }
169
- const detachedSessions = detached.length
170
-
171
- // Cascade: suspend tasks, schedules, watch jobs, connectors, webhooks, chatrooms
172
- const cascade = suspendAgentReferences(id)
18
+ const result = trashAgent(id)
19
+ if (!result.ok) return notFound()
20
+ const { detachedSessions, cascade } = result
173
21
  if (cascade.tasks) notify('tasks')
174
22
  if (cascade.schedules) notify('schedules')
175
23
  if (cascade.connectors) notify('connectors')
@@ -1,13 +1,12 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents } from '@/lib/server/storage'
2
+ import { getAgentStatus } from '@/lib/server/agents/agent-service'
3
3
  import { getMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
4
4
 
5
5
  export const dynamic = 'force-dynamic'
6
6
 
7
7
  export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
8
8
  const { id } = await params
9
- const agents = loadAgents()
10
- const agent = agents[id]
9
+ const agent = getAgentStatus(id)
11
10
  if (!agent) return NextResponse.json(null, { status: 404 })
12
11
 
13
12
  const sessionId = agent.threadSessionId
@@ -1,23 +1,10 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/agent-availability'
3
- import { ensureAgentThreadSession } from '@/lib/server/agents/agent-thread-session'
4
- import type { Agent } from '@/types'
5
- import { loadAgents } from '@/lib/server/storage'
2
+ import { getAgentThreadSession } from '@/lib/server/agents/agent-service'
6
3
 
7
4
  export async function POST(req: Request, { params }: { params: Promise<{ id: string }> }) {
8
5
  const { id: agentId } = await params
9
6
  const body = await req.json().catch(() => ({}))
10
- const user = body.user || 'default'
11
- const agent = loadAgents()[agentId]
12
- if (!agent) {
13
- return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
14
- }
15
- const session = ensureAgentThreadSession(agentId, user, agent as Agent)
16
- if (!session) {
17
- if (isAgentDisabled(agent)) {
18
- return NextResponse.json({ error: buildAgentDisabledMessage(agent, 'start new chats') }, { status: 409 })
19
- }
20
- return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
21
- }
22
- return NextResponse.json(session)
7
+ const result = getAgentThreadSession(agentId, typeof body.user === 'string' ? body.user : 'default')
8
+ if (!result.ok) return NextResponse.json(result.payload, { status: result.status })
9
+ return NextResponse.json(result.payload)
23
10
  }
@@ -1,55 +1,13 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { patchAgent } from '@/lib/server/storage'
4
- import { logActivity } from '@/lib/server/storage'
5
- import { notify } from '@/lib/server/ws-hub'
3
+ import { bulkPatchAgents } from '@/lib/server/agents/agent-service'
6
4
 
7
5
  export async function PATCH(req: Request) {
8
6
  const { data: body, error } = await safeParseBody<Record<string, unknown>>(req)
9
7
  if (error) return error
10
-
11
- const patches = body.patches
12
- if (!Array.isArray(patches) || patches.length === 0) {
13
- return NextResponse.json({ error: 'patches must be a non-empty array' }, { status: 400 })
8
+ const result = bulkPatchAgents(body.patches)
9
+ if (result.updated === 0 && result.errors.length === 1 && result.errors[0] === 'patches must be a non-empty array') {
10
+ return NextResponse.json({ error: result.errors[0] }, { status: 400 })
14
11
  }
15
-
16
- let updated = 0
17
- const errors: string[] = []
18
-
19
- for (const entry of patches) {
20
- if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
21
- errors.push('Invalid patch entry (not an object)')
22
- continue
23
- }
24
- const { id, patch } = entry as { id?: unknown; patch?: unknown }
25
- if (typeof id !== 'string' || !id.trim()) {
26
- errors.push('Patch entry missing valid id')
27
- continue
28
- }
29
- if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
30
- errors.push(`Patch for ${id} is not a valid object`)
31
- continue
32
- }
33
-
34
- const result = patchAgent(id, (current) => {
35
- if (!current) return null
36
- return { ...current, ...(patch as Record<string, unknown>), updatedAt: Date.now() }
37
- })
38
-
39
- if (result) {
40
- updated++
41
- logActivity({
42
- entityType: 'agent',
43
- entityId: id,
44
- action: 'updated',
45
- actor: 'user',
46
- summary: `Bulk patch: updated agent "${result.name || id}"`,
47
- })
48
- } else {
49
- errors.push(`Agent ${id} not found`)
50
- }
51
- }
52
-
53
- if (updated > 0) notify('agents')
54
- return NextResponse.json({ updated, errors })
12
+ return NextResponse.json(result)
55
13
  }