@swarmclawai/swarmclaw 1.2.4 → 1.2.6

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 (262) hide show
  1. package/README.md +14 -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]/route.test.ts +49 -0
  49. package/src/app/api/providers/ollama/route.ts +6 -5
  50. package/src/app/api/schedules/[id]/route.ts +14 -108
  51. package/src/app/api/schedules/[id]/run/route.ts +6 -67
  52. package/src/app/api/schedules/route.ts +9 -51
  53. package/src/app/api/settings/route.ts +4 -3
  54. package/src/app/api/setup/check-provider/route.ts +23 -1
  55. package/src/app/api/setup/openclaw-device/route.ts +2 -2
  56. package/src/app/api/system/status/route.ts +2 -2
  57. package/src/app/api/tasks/[id]/route.ts +16 -202
  58. package/src/app/api/tasks/bulk/route.ts +5 -86
  59. package/src/app/api/tasks/metrics/route.ts +2 -1
  60. package/src/app/api/tasks/route.ts +11 -171
  61. package/src/app/api/upload/route.ts +1 -1
  62. package/src/app/api/uploads/[filename]/route.ts +1 -1
  63. package/src/app/api/uploads/route.ts +1 -1
  64. package/src/app/api/webhooks/[id]/history/route.ts +2 -2
  65. package/src/app/layout.tsx +9 -6
  66. package/src/app/protocols/page.tsx +71 -89
  67. package/src/app/tasks/page.tsx +32 -32
  68. package/src/cli/index.js +1 -0
  69. package/src/cli/spec.js +1 -0
  70. package/src/components/agents/agent-sheet.tsx +5 -5
  71. package/src/components/auth/setup-wizard/index.tsx +4 -4
  72. package/src/components/auth/setup-wizard/step-agents.tsx +1 -1
  73. package/src/components/auth/setup-wizard/step-connect.tsx +1 -1
  74. package/src/components/auth/setup-wizard/utils.ts +1 -1
  75. package/src/components/chatrooms/chatroom-sheet.tsx +16 -276
  76. package/src/components/connectors/connector-list.tsx +26 -40
  77. package/src/components/connectors/connector-sheet.tsx +95 -149
  78. package/src/components/gateways/gateway-sheet.tsx +61 -110
  79. package/src/components/layout/live-query-sync.tsx +121 -0
  80. package/src/components/protocols/structured-session-launcher.tsx +24 -45
  81. package/src/components/providers/app-query-provider.tsx +17 -0
  82. package/src/components/providers/provider-list.tsx +60 -61
  83. package/src/components/providers/provider-sheet.tsx +74 -56
  84. package/src/components/skills/skill-list.tsx +5 -18
  85. package/src/components/skills/skill-sheet.tsx +21 -20
  86. package/src/components/skills/skills-workspace.tsx +48 -87
  87. package/src/components/tasks/task-card.tsx +20 -13
  88. package/src/components/tasks/task-column.tsx +22 -7
  89. package/src/components/tasks/task-list.tsx +8 -11
  90. package/src/components/tasks/task-sheet.tsx +111 -103
  91. package/src/features/agents/queries.ts +20 -0
  92. package/src/features/chatrooms/queries.ts +20 -0
  93. package/src/features/chats/queries.ts +27 -0
  94. package/src/features/connectors/queries.ts +145 -0
  95. package/src/features/credentials/queries.ts +37 -0
  96. package/src/features/extensions/queries.ts +26 -0
  97. package/src/features/external-agents/queries.ts +36 -0
  98. package/src/features/gateways/queries.ts +274 -0
  99. package/src/features/missions/queries.ts +23 -0
  100. package/src/features/projects/queries.ts +20 -0
  101. package/src/features/protocols/queries.ts +149 -0
  102. package/src/features/providers/queries.ts +142 -0
  103. package/src/features/settings/queries.ts +20 -0
  104. package/src/features/skills/queries.ts +182 -0
  105. package/src/features/tasks/queries.ts +189 -0
  106. package/src/hooks/use-ws.ts +3 -2
  107. package/src/lib/app/api-client.ts +2 -2
  108. package/src/lib/providers/index.test.ts +108 -0
  109. package/src/lib/providers/index.ts +38 -15
  110. package/src/lib/query/client.ts +17 -0
  111. package/src/lib/server/agents/agent-runtime-config.ts +1 -1
  112. package/src/lib/server/agents/agent-service.ts +429 -0
  113. package/src/lib/server/agents/agent-thread-session.ts +6 -5
  114. package/src/lib/server/agents/autonomy-contract.ts +1 -4
  115. package/src/lib/server/agents/delegation-advisory.test.ts +206 -0
  116. package/src/lib/server/agents/delegation-advisory.ts +251 -0
  117. package/src/lib/server/agents/main-agent-loop.ts +98 -40
  118. package/src/lib/server/agents/subagent-runtime.ts +12 -0
  119. package/src/lib/server/autonomy/supervisor-reflection.test.ts +20 -1
  120. package/src/lib/server/autonomy/supervisor-reflection.ts +39 -19
  121. package/src/lib/server/build-llm.ts +7 -15
  122. package/src/lib/server/capability-router.test.ts +70 -1
  123. package/src/lib/server/capability-router.ts +24 -99
  124. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -15
  125. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -4
  126. package/src/lib/server/chat-execution/chat-turn-finalization.ts +77 -12
  127. package/src/lib/server/chat-execution/chat-turn-partial-persistence.ts +4 -4
  128. package/src/lib/server/chat-execution/chat-turn-preflight.ts +2 -2
  129. package/src/lib/server/chat-execution/chat-turn-preparation.ts +41 -17
  130. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -2
  131. package/src/lib/server/chat-execution/chat-turn-tool-routing.test.ts +45 -0
  132. package/src/lib/server/chat-execution/chat-turn-tool-routing.ts +48 -17
  133. package/src/lib/server/chat-execution/continuation-evaluator.ts +4 -1
  134. package/src/lib/server/chat-execution/direct-memory-intent.test.ts +9 -0
  135. package/src/lib/server/chat-execution/direct-memory-intent.ts +12 -2
  136. package/src/lib/server/chat-execution/message-classifier.test.ts +35 -23
  137. package/src/lib/server/chat-execution/message-classifier.ts +74 -32
  138. package/src/lib/server/chat-execution/prompt-builder.test.ts +29 -0
  139. package/src/lib/server/chat-execution/prompt-builder.ts +37 -2
  140. package/src/lib/server/chat-execution/prompt-sections.test.ts +56 -0
  141. package/src/lib/server/chat-execution/prompt-sections.ts +193 -0
  142. package/src/lib/server/chat-execution/stream-agent-chat.ts +63 -7
  143. package/src/lib/server/chat-execution/stream-continuation.test.ts +36 -0
  144. package/src/lib/server/chat-execution/stream-continuation.ts +28 -13
  145. package/src/lib/server/chatrooms/chatroom-agent-signals.ts +26 -18
  146. package/src/lib/server/chatrooms/chatroom-helpers.ts +19 -18
  147. package/src/lib/server/chatrooms/chatroom-repository.ts +16 -0
  148. package/src/lib/server/chatrooms/chatroom-routing.test.ts +96 -0
  149. package/src/lib/server/chatrooms/chatroom-routing.ts +207 -53
  150. package/src/lib/server/chatrooms/mailbox-utils.ts +4 -2
  151. package/src/lib/server/chatrooms/session-mailbox.ts +50 -40
  152. package/src/lib/server/chats/chat-session-service.ts +410 -0
  153. package/src/lib/server/connectors/access.ts +1 -1
  154. package/src/lib/server/connectors/commands.ts +7 -6
  155. package/src/lib/server/connectors/connector-inbound.ts +14 -7
  156. package/src/lib/server/connectors/connector-outbound.ts +16 -11
  157. package/src/lib/server/connectors/connector-service.ts +453 -0
  158. package/src/lib/server/connectors/delivery.ts +17 -12
  159. package/src/lib/server/connectors/inbound-audio-transcription.ts +5 -14
  160. package/src/lib/server/connectors/media.ts +1 -1
  161. package/src/lib/server/connectors/response-media.ts +1 -1
  162. package/src/lib/server/connectors/session-consolidation.ts +11 -7
  163. package/src/lib/server/connectors/session.ts +9 -7
  164. package/src/lib/server/connectors/voice-note.ts +2 -1
  165. package/src/lib/server/context-manager.ts +20 -1
  166. package/src/lib/server/cost.ts +2 -3
  167. package/src/lib/server/credentials/credential-repository.ts +43 -4
  168. package/src/lib/server/credentials/credential-service.ts +112 -0
  169. package/src/lib/server/daemon/admin-metadata.ts +64 -0
  170. package/src/lib/server/daemon/controller.ts +577 -0
  171. package/src/lib/server/daemon/daemon-runtime.ts +352 -0
  172. package/src/lib/server/daemon/daemon-status-repository.ts +63 -0
  173. package/src/lib/server/daemon/types.ts +101 -0
  174. package/src/lib/server/embeddings.ts +3 -9
  175. package/src/lib/server/eval/agent-regression.ts +3 -2
  176. package/src/lib/server/eval/runner.ts +2 -2
  177. package/src/lib/server/execution-brief.test.ts +167 -0
  178. package/src/lib/server/execution-brief.ts +295 -0
  179. package/src/lib/server/execution-engine/chat-turn.ts +9 -0
  180. package/src/lib/server/execution-engine/import-boundary.test.ts +44 -0
  181. package/src/lib/server/execution-engine/index.ts +35 -0
  182. package/src/lib/server/execution-engine/task-attempt.ts +303 -0
  183. package/src/lib/server/execution-engine/types.ts +33 -0
  184. package/src/lib/server/gateways/gateway-profile-repository.ts +47 -3
  185. package/src/lib/server/gateways/gateway-profile-service.ts +200 -0
  186. package/src/lib/server/memory/session-archive-memory.ts +12 -10
  187. package/src/lib/server/messages/message-repository.ts +330 -0
  188. package/src/lib/server/missions/mission-service/core.ts +8 -6
  189. package/src/lib/server/openclaw/agent-resolver.ts +2 -3
  190. package/src/lib/server/openclaw/doctor.ts +1 -1
  191. package/src/lib/server/openclaw/gateway.test.ts +10 -1
  192. package/src/lib/server/openclaw/gateway.ts +5 -14
  193. package/src/lib/server/openclaw/health.ts +3 -11
  194. package/src/lib/server/openclaw/sync.ts +8 -6
  195. package/src/lib/server/persistence/storage-context.ts +3 -0
  196. package/src/lib/server/protocols/protocol-agent-turn.ts +25 -17
  197. package/src/lib/server/protocols/protocol-normalization.ts +1 -1
  198. package/src/lib/server/protocols/protocol-queries.ts +13 -7
  199. package/src/lib/server/protocols/protocol-run-lifecycle.ts +16 -20
  200. package/src/lib/server/protocols/protocol-run-repository.ts +81 -0
  201. package/src/lib/server/protocols/protocol-step-processors.ts +23 -31
  202. package/src/lib/server/protocols/protocol-swarm.ts +8 -8
  203. package/src/lib/server/protocols/protocol-template-repository.ts +42 -0
  204. package/src/lib/server/protocols/protocol-templates.ts +4 -2
  205. package/src/lib/server/protocols/protocol-types.ts +10 -7
  206. package/src/lib/server/provider-endpoint.ts +7 -12
  207. package/src/lib/server/provider-model-discovery.ts +2 -11
  208. package/src/lib/server/query-expansion.ts +5 -6
  209. package/src/lib/server/run-context.test.ts +365 -0
  210. package/src/lib/server/run-context.ts +367 -0
  211. package/src/lib/server/runtime/heartbeat-service.ts +7 -5
  212. package/src/lib/server/runtime/queue/core.ts +61 -190
  213. package/src/lib/server/runtime/run-ledger.ts +8 -0
  214. package/src/lib/server/runtime/session-run-manager/drain.ts +2 -2
  215. package/src/lib/server/runtime/session-run-manager/enqueue.ts +6 -0
  216. package/src/lib/server/runtime/session-run-manager/state.ts +4 -0
  217. package/src/lib/server/schedules/schedule-route-service.ts +230 -0
  218. package/src/lib/server/service-result.ts +16 -0
  219. package/src/lib/server/session-note.ts +2 -3
  220. package/src/lib/server/session-reset-policy.ts +4 -3
  221. package/src/lib/server/session-tools/connector.ts +9 -6
  222. package/src/lib/server/session-tools/context-mgmt.ts +58 -9
  223. package/src/lib/server/session-tools/crud.ts +162 -10
  224. package/src/lib/server/session-tools/delegate.ts +1 -1
  225. package/src/lib/server/session-tools/manage-tasks.test.ts +152 -0
  226. package/src/lib/server/session-tools/memory.ts +6 -4
  227. package/src/lib/server/session-tools/session-info.test.ts +56 -0
  228. package/src/lib/server/session-tools/session-info.ts +119 -12
  229. package/src/lib/server/session-tools/skill-runtime.ts +3 -1
  230. package/src/lib/server/session-tools/skills.ts +15 -15
  231. package/src/lib/server/session-tools/subagent.test.ts +115 -1
  232. package/src/lib/server/session-tools/subagent.ts +125 -7
  233. package/src/lib/server/session-tools/team-context.ts +4 -3
  234. package/src/lib/server/session-tools/wallet.ts +0 -58
  235. package/src/lib/server/sessions/session-lineage.ts +55 -0
  236. package/src/lib/server/sessions/session-repository.ts +2 -2
  237. package/src/lib/server/skills/learned-skills.ts +24 -23
  238. package/src/lib/server/skills/runtime-skill-resolver.ts +2 -1
  239. package/src/lib/server/skills/skill-repository.ts +136 -13
  240. package/src/lib/server/skills/skill-suggestions.ts +25 -28
  241. package/src/lib/server/storage-normalization.test.ts +44 -267
  242. package/src/lib/server/storage-normalization.ts +75 -0
  243. package/src/lib/server/storage.ts +19 -0
  244. package/src/lib/server/structured-extract.ts +3 -14
  245. package/src/lib/server/tasks/task-followups.ts +16 -11
  246. package/src/lib/server/tasks/task-result.test.ts +25 -29
  247. package/src/lib/server/tasks/task-result.ts +5 -9
  248. package/src/lib/server/tasks/task-route-service.ts +449 -0
  249. package/src/lib/server/text-normalization.ts +41 -0
  250. package/src/lib/server/tool-planning.ts +6 -42
  251. package/src/lib/server/upload-path.ts +5 -0
  252. package/src/lib/server/working-state/extraction.ts +614 -0
  253. package/src/lib/server/working-state/normalization.ts +866 -0
  254. package/src/lib/server/working-state/prompt.ts +60 -0
  255. package/src/lib/server/working-state/repository.ts +38 -0
  256. package/src/lib/server/working-state/service.test.ts +253 -0
  257. package/src/lib/server/working-state/service.ts +293 -0
  258. package/src/lib/validation/schemas.ts +1 -0
  259. package/src/lib/ws-client.ts +3 -3
  260. package/src/stores/slices/task-slice.ts +1 -4
  261. package/src/stores/use-chatroom-store.ts +2 -2
  262. package/src/types/index.ts +277 -12
@@ -1,135 +1,20 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
3
- import { loadAgents, loadGatewayProfiles, saveGatewayProfiles, upsertAgent } from '@/lib/server/storage'
4
- import { mutateItem, notFound, type CollectionOps } from '@/lib/server/collection-helpers'
5
- import type { Agent, AgentRoutingTarget, GatewayProfile, OpenClawDeploymentConfig, OpenClawGatewayStats } from '@/types'
6
-
7
- const ops: CollectionOps<GatewayProfile> = {
8
- load: loadGatewayProfiles,
9
- save: saveGatewayProfiles,
10
- topic: 'gateways',
11
- }
12
-
13
- function normalizeTags(value: unknown): string[] {
14
- if (!Array.isArray(value)) return []
15
- return value
16
- .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
17
- .filter(Boolean)
18
- }
19
-
20
- function normalizeText(value: unknown): string | null {
21
- return typeof value === 'string' && value.trim() ? value.trim() : null
22
- }
23
-
24
- function normalizeNullableNumber(value: unknown): number | null {
25
- return typeof value === 'number' && Number.isFinite(value) ? value : null
26
- }
27
-
28
- function normalizeDeployment(value: unknown): OpenClawDeploymentConfig | null {
29
- if (!value || typeof value !== 'object') return null
30
- const deployment = value as Record<string, unknown>
31
- return {
32
- method: normalizeText(deployment.method) as OpenClawDeploymentConfig['method'],
33
- provider: normalizeText(deployment.provider) as OpenClawDeploymentConfig['provider'],
34
- remoteTarget: normalizeText(deployment.remoteTarget) as OpenClawDeploymentConfig['remoteTarget'],
35
- useCase: normalizeText(deployment.useCase) as OpenClawDeploymentConfig['useCase'],
36
- exposure: normalizeText(deployment.exposure) as OpenClawDeploymentConfig['exposure'],
37
- managedBy: normalizeText(deployment.managedBy) as OpenClawDeploymentConfig['managedBy'],
38
- localInstanceId: normalizeText(deployment.localInstanceId),
39
- localPort: normalizeNullableNumber(deployment.localPort),
40
- targetHost: normalizeText(deployment.targetHost),
41
- sshHost: normalizeText(deployment.sshHost),
42
- sshUser: normalizeText(deployment.sshUser),
43
- sshPort: normalizeNullableNumber(deployment.sshPort),
44
- sshKeyPath: normalizeText(deployment.sshKeyPath),
45
- sshTargetDir: normalizeText(deployment.sshTargetDir),
46
- image: normalizeText(deployment.image),
47
- version: normalizeText(deployment.version),
48
- lastDeployAt: normalizeNullableNumber(deployment.lastDeployAt),
49
- lastDeployAction: normalizeText(deployment.lastDeployAction),
50
- lastDeployProcessId: normalizeText(deployment.lastDeployProcessId),
51
- lastDeploySummary: normalizeText(deployment.lastDeploySummary),
52
- lastVerifiedAt: normalizeNullableNumber(deployment.lastVerifiedAt),
53
- lastVerifiedOk: typeof deployment.lastVerifiedOk === 'boolean' ? deployment.lastVerifiedOk : null,
54
- lastVerifiedMessage: normalizeText(deployment.lastVerifiedMessage),
55
- lastBackupPath: normalizeText(deployment.lastBackupPath),
56
- }
57
- }
58
-
59
- function normalizeStats(value: unknown): OpenClawGatewayStats | null {
60
- if (!value || typeof value !== 'object') return null
61
- const stats = value as Record<string, unknown>
62
- return {
63
- nodeCount: normalizeNullableNumber(stats.nodeCount) ?? undefined,
64
- connectedNodeCount: normalizeNullableNumber(stats.connectedNodeCount) ?? undefined,
65
- pendingNodePairings: normalizeNullableNumber(stats.pendingNodePairings) ?? undefined,
66
- pairedDeviceCount: normalizeNullableNumber(stats.pairedDeviceCount) ?? undefined,
67
- pendingDevicePairings: normalizeNullableNumber(stats.pendingDevicePairings) ?? undefined,
68
- externalRuntimeCount: normalizeNullableNumber(stats.externalRuntimeCount) ?? undefined,
69
- }
70
- }
2
+ import { notFound } from '@/lib/server/collection-helpers'
3
+ import {
4
+ deleteGatewayProfileAndDetachAgents,
5
+ updateGatewayProfile,
6
+ } from '@/lib/server/gateways/gateway-profile-service'
71
7
 
72
8
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
73
9
  const { id } = await params
74
10
  const body = await req.json().catch(() => ({}))
75
- const result = mutateItem(ops, id, (gateway, all) => {
76
- if (body.isDefault === true) {
77
- for (const [candidateId, candidate] of Object.entries(all)) {
78
- if (candidateId === id || !candidate || typeof candidate !== 'object') continue
79
- candidate.isDefault = false
80
- }
81
- }
82
- if (body.name !== undefined) gateway.name = String(body.name || '').trim() || gateway.name
83
- if (body.endpoint !== undefined) gateway.endpoint = normalizeOpenClawEndpoint(body.endpoint || undefined)
84
- if (body.wsUrl !== undefined) gateway.wsUrl = body.wsUrl || null
85
- if (body.credentialId !== undefined) gateway.credentialId = body.credentialId || null
86
- if (body.status !== undefined) gateway.status = body.status || 'unknown'
87
- if (body.notes !== undefined) gateway.notes = body.notes || null
88
- if (body.tags !== undefined) gateway.tags = normalizeTags(body.tags)
89
- if (body.lastError !== undefined) gateway.lastError = body.lastError || null
90
- if (body.lastCheckedAt !== undefined) gateway.lastCheckedAt = body.lastCheckedAt || null
91
- if (body.lastModelCount !== undefined) gateway.lastModelCount = body.lastModelCount || null
92
- if (body.discoveredHost !== undefined) gateway.discoveredHost = body.discoveredHost || null
93
- if (body.discoveredPort !== undefined) gateway.discoveredPort = body.discoveredPort || null
94
- if (body.deployment !== undefined) gateway.deployment = { ...(gateway.deployment || {}), ...(normalizeDeployment(body.deployment) || {}) }
95
- if (body.stats !== undefined) gateway.stats = { ...(gateway.stats || {}), ...(normalizeStats(body.stats) || {}) }
96
- if (body.isDefault !== undefined) gateway.isDefault = body.isDefault === true
97
- gateway.updatedAt = Date.now()
98
- return gateway
99
- })
11
+ const result = updateGatewayProfile(id, body as Record<string, unknown>)
100
12
  if (!result) return notFound()
101
13
  return NextResponse.json(result)
102
14
  }
103
15
 
104
16
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
105
17
  const { id } = await params
106
- const gateways = loadGatewayProfiles()
107
- if (!gateways[id]) return notFound()
108
- delete gateways[id]
109
- saveGatewayProfiles(gateways)
110
-
111
- const agents = loadAgents({ includeTrashed: true })
112
- const changedAgents: Agent[] = []
113
- for (const agent of Object.values(agents) as Agent[]) {
114
- let changed = false
115
- if (agent.gatewayProfileId === id) {
116
- agent.gatewayProfileId = null
117
- changed = true
118
- }
119
- if (Array.isArray(agent.routingTargets)) {
120
- const nextTargets = agent.routingTargets.map((target: AgentRoutingTarget) => (
121
- target.gatewayProfileId === id
122
- ? { ...target, gatewayProfileId: null }
123
- : target
124
- ))
125
- if (JSON.stringify(nextTargets) !== JSON.stringify(agent.routingTargets)) {
126
- agent.routingTargets = nextTargets
127
- changed = true
128
- }
129
- }
130
- if (changed) changedAgents.push(agent)
131
- }
132
- for (const agent of changedAgents) upsertAgent(agent.id, agent)
133
-
18
+ if (!deleteGatewayProfileAndDetachAgents(id)) return notFound()
134
19
  return NextResponse.json({ ok: true })
135
20
  }
@@ -1,112 +1,12 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { genId } from '@/lib/id'
3
- import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
4
- import { getGatewayProfiles } from '@/lib/server/agents/agent-runtime-config'
5
- import { loadGatewayProfiles, saveGatewayProfiles } from '@/lib/server/storage'
6
- import { notify } from '@/lib/server/ws-hub'
7
- import type { OpenClawDeploymentConfig, OpenClawGatewayStats } from '@/types'
2
+ import { createGatewayProfile, listOpenClawGatewayProfiles } from '@/lib/server/gateways/gateway-profile-service'
8
3
  export const dynamic = 'force-dynamic'
9
4
 
10
- function normalizeTags(value: unknown): string[] {
11
- if (!Array.isArray(value)) return []
12
- return value
13
- .map((entry) => (typeof entry === 'string' ? entry.trim() : ''))
14
- .filter(Boolean)
15
- }
16
-
17
- function normalizeText(value: unknown): string | null {
18
- return typeof value === 'string' && value.trim() ? value.trim() : null
19
- }
20
-
21
- function normalizeNullableNumber(value: unknown): number | null {
22
- return typeof value === 'number' && Number.isFinite(value) ? value : null
23
- }
24
-
25
- function normalizeDeployment(value: unknown): OpenClawDeploymentConfig | null {
26
- if (!value || typeof value !== 'object') return null
27
- const deployment = value as Record<string, unknown>
28
- return {
29
- method: normalizeText(deployment.method) as OpenClawDeploymentConfig['method'],
30
- provider: normalizeText(deployment.provider) as OpenClawDeploymentConfig['provider'],
31
- remoteTarget: normalizeText(deployment.remoteTarget) as OpenClawDeploymentConfig['remoteTarget'],
32
- useCase: normalizeText(deployment.useCase) as OpenClawDeploymentConfig['useCase'],
33
- exposure: normalizeText(deployment.exposure) as OpenClawDeploymentConfig['exposure'],
34
- managedBy: normalizeText(deployment.managedBy) as OpenClawDeploymentConfig['managedBy'],
35
- localInstanceId: normalizeText(deployment.localInstanceId),
36
- localPort: normalizeNullableNumber(deployment.localPort),
37
- targetHost: normalizeText(deployment.targetHost),
38
- sshHost: normalizeText(deployment.sshHost),
39
- sshUser: normalizeText(deployment.sshUser),
40
- sshPort: normalizeNullableNumber(deployment.sshPort),
41
- sshKeyPath: normalizeText(deployment.sshKeyPath),
42
- sshTargetDir: normalizeText(deployment.sshTargetDir),
43
- image: normalizeText(deployment.image),
44
- version: normalizeText(deployment.version),
45
- lastDeployAt: normalizeNullableNumber(deployment.lastDeployAt),
46
- lastDeployAction: normalizeText(deployment.lastDeployAction),
47
- lastDeployProcessId: normalizeText(deployment.lastDeployProcessId),
48
- lastDeploySummary: normalizeText(deployment.lastDeploySummary),
49
- lastVerifiedAt: normalizeNullableNumber(deployment.lastVerifiedAt),
50
- lastVerifiedOk: typeof deployment.lastVerifiedOk === 'boolean' ? deployment.lastVerifiedOk : null,
51
- lastVerifiedMessage: normalizeText(deployment.lastVerifiedMessage),
52
- lastBackupPath: normalizeText(deployment.lastBackupPath),
53
- }
54
- }
55
-
56
- function normalizeStats(value: unknown): OpenClawGatewayStats | null {
57
- if (!value || typeof value !== 'object') return null
58
- const stats = value as Record<string, unknown>
59
- return {
60
- nodeCount: normalizeNullableNumber(stats.nodeCount) ?? undefined,
61
- connectedNodeCount: normalizeNullableNumber(stats.connectedNodeCount) ?? undefined,
62
- pendingNodePairings: normalizeNullableNumber(stats.pendingNodePairings) ?? undefined,
63
- pairedDeviceCount: normalizeNullableNumber(stats.pairedDeviceCount) ?? undefined,
64
- pendingDevicePairings: normalizeNullableNumber(stats.pendingDevicePairings) ?? undefined,
65
- externalRuntimeCount: normalizeNullableNumber(stats.externalRuntimeCount) ?? undefined,
66
- }
67
- }
68
-
69
5
  export async function GET() {
70
- return NextResponse.json(getGatewayProfiles('openclaw'))
6
+ return NextResponse.json(listOpenClawGatewayProfiles())
71
7
  }
72
8
 
73
9
  export async function POST(req: Request) {
74
10
  const body = await req.json().catch(() => ({}))
75
- const endpoint = normalizeOpenClawEndpoint(body.endpoint || undefined)
76
- const now = Date.now()
77
- const gateways = loadGatewayProfiles()
78
- const id = body.id || `gateway-${genId()}`
79
- const isDefault = body.isDefault === true
80
-
81
- if (isDefault) {
82
- for (const gateway of Object.values(gateways) as any[]) {
83
- gateway.isDefault = false
84
- }
85
- }
86
-
87
- gateways[id] = {
88
- id,
89
- name: typeof body.name === 'string' && body.name.trim() ? body.name.trim() : 'OpenClaw Gateway',
90
- provider: 'openclaw',
91
- endpoint,
92
- wsUrl: body.wsUrl || null,
93
- credentialId: body.credentialId || null,
94
- status: body.status || 'unknown',
95
- notes: typeof body.notes === 'string' ? body.notes : null,
96
- tags: normalizeTags(body.tags),
97
- lastError: null,
98
- lastCheckedAt: null,
99
- lastModelCount: null,
100
- discoveredHost: typeof body.discoveredHost === 'string' ? body.discoveredHost : null,
101
- discoveredPort: typeof body.discoveredPort === 'number' ? body.discoveredPort : null,
102
- deployment: normalizeDeployment(body.deployment),
103
- stats: normalizeStats(body.stats),
104
- isDefault,
105
- createdAt: now,
106
- updatedAt: now,
107
- }
108
-
109
- saveGatewayProfiles(gateways)
110
- notify('gateways')
111
- return NextResponse.json(gateways[id])
11
+ return NextResponse.json(createGatewayProfile(body as Record<string, unknown>))
112
12
  }
@@ -9,22 +9,22 @@ export async function GET(_req: Request, { params }: { params: Promise<{ id: str
9
9
  const config = servers[id]
10
10
  if (!config) return notFound()
11
11
 
12
- let client: any
13
- let transport: any
12
+ let client: Awaited<ReturnType<typeof connectMcpServer>>['client'] | null = null
13
+ let transport: Awaited<ReturnType<typeof connectMcpServer>>['transport'] | null = null
14
14
  try {
15
15
  const conn = await connectMcpServer(config)
16
16
  client = conn.client
17
17
  transport = conn.transport
18
18
  const { tools } = await client.listTools()
19
19
  return NextResponse.json(
20
- tools.map((t: any) => ({
20
+ tools.map((t: { name: string; description?: string; inputSchema?: unknown }) => ({
21
21
  name: t.name,
22
22
  description: t.description ?? '',
23
23
  inputSchema: t.inputSchema ?? {},
24
24
  }))
25
25
  )
26
- } catch (err: any) {
27
- return NextResponse.json({ error: err.message }, { status: 502 })
26
+ } catch (err: unknown) {
27
+ return NextResponse.json({ error: err instanceof Error ? err.message : 'MCP connection failed' }, { status: 502 })
28
28
  } finally {
29
29
  if (client && transport) {
30
30
  await disconnectMcpServer(client, transport)
@@ -1,5 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents, loadGatewayProfiles, loadCredentials, decryptKey } from '@/lib/server/storage'
2
+ import { getAgent } from '@/lib/server/agents/agent-repository'
3
+ import { getGatewayProfile } from '@/lib/server/agents/agent-runtime-config'
4
+ import { resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
3
5
 
4
6
  /** GET ?agentId=X — resolve the tokenized dashboard URL for an OpenClaw agent's gateway */
5
7
  export async function GET(req: Request) {
@@ -9,8 +11,7 @@ export async function GET(req: Request) {
9
11
  return NextResponse.json({ error: 'Missing agentId' }, { status: 400 })
10
12
  }
11
13
 
12
- const agents = loadAgents()
13
- const agent = agents[agentId]
14
+ const agent = getAgent(agentId)
14
15
  if (!agent) {
15
16
  return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
16
17
  }
@@ -24,8 +25,7 @@ export async function GET(req: Request) {
24
25
 
25
26
  // If agent has a gatewayProfileId, prefer its endpoint and credential
26
27
  if (agent.gatewayProfileId) {
27
- const gateways = loadGatewayProfiles()
28
- const gw = gateways[agent.gatewayProfileId]
28
+ const gw = getGatewayProfile(agent.gatewayProfileId)
29
29
  if (gw) {
30
30
  endpoint = gw.endpoint || endpoint
31
31
  credentialId = gw.credentialId || credentialId
@@ -50,17 +50,9 @@ export async function GET(req: Request) {
50
50
 
51
51
  // Decrypt the token if we have a credential
52
52
  if (credentialId) {
53
- try {
54
- const creds = loadCredentials()
55
- const cred = creds[credentialId]
56
- if (cred?.encryptedKey) {
57
- const token = decryptKey(cred.encryptedKey)
58
- if (token) {
59
- dashboardUrl = `${dashboardUrl}?token=${encodeURIComponent(token)}`
60
- }
61
- }
62
- } catch {
63
- // If decryption fails, return the URL without token
53
+ const token = resolveCredentialSecret(credentialId)
54
+ if (token) {
55
+ dashboardUrl = `${dashboardUrl}?token=${encodeURIComponent(token)}`
64
56
  }
65
57
  }
66
58
 
@@ -1,10 +1,10 @@
1
1
  import { NextResponse } from 'next/server'
2
+ import { listDaemonRunningConnectors } from '@/lib/server/daemon/controller'
2
3
  export const dynamic = 'force-dynamic'
3
4
 
4
5
  export async function GET() {
5
6
  try {
6
- const { listRunningConnectors } = await import('@/lib/server/connectors/manager')
7
- const openclawConnectors = listRunningConnectors('openclaw')
7
+ const openclawConnectors = await listDaemonRunningConnectors('openclaw')
8
8
 
9
9
  if (!openclawConnectors.length) {
10
10
  return NextResponse.json({ devices: [], note: 'No running OpenClaw connector.' })
@@ -1,7 +1,7 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { ensureGatewayConnected } from '@/lib/server/openclaw/gateway'
3
3
  import { mergeHistoryMessages, isValidSessionKey } from '@/lib/server/openclaw/history-merge'
4
- import { loadSessions, saveSessions } from '@/lib/server/storage'
4
+ import { getSession, saveSession } from '@/lib/server/sessions/session-repository'
5
5
  import { notify } from '@/lib/server/ws-hub'
6
6
  import type { GatewaySessionPreview } from '@/types'
7
7
  import { errorMessage } from '@/lib/shared-utils'
@@ -90,8 +90,7 @@ export async function POST(req: Request) {
90
90
  return NextResponse.json({ ok: true, merged: 0 })
91
91
  }
92
92
 
93
- const sessions = loadSessions()
94
- const session = sessions[localSessionId]
93
+ const session = getSession(localSessionId)
95
94
  if (!session) {
96
95
  return NextResponse.json({ error: 'Local session not found' }, { status: 404 })
97
96
  }
@@ -100,8 +99,7 @@ export async function POST(req: Request) {
100
99
  const newCount = merged.length - session.messages.length
101
100
  session.messages = merged
102
101
  session.lastActiveAt = Date.now()
103
- sessions[localSessionId] = session
104
- saveSessions(sessions)
102
+ saveSession(localSessionId, session)
105
103
  notify('sessions')
106
104
 
107
105
  return NextResponse.json({ ok: true, merged: newCount })
@@ -0,0 +1,49 @@
1
+ import assert from 'node:assert/strict'
2
+ import test from 'node:test'
3
+
4
+ import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
5
+
6
+ test('provider route upserts builtin override records for enablement changes', () => {
7
+ const output = runWithTempDataDir<{
8
+ providerConfig: {
9
+ id: string
10
+ type: string
11
+ name: string
12
+ isEnabled: boolean
13
+ requiresApiKey: boolean
14
+ }
15
+ responsePayload: {
16
+ id: string
17
+ type: string
18
+ isEnabled: boolean
19
+ }
20
+ }>(`
21
+ const storageMod = await import('./src/lib/server/storage')
22
+ const routeMod = await import('./src/app/api/providers/[id]/route')
23
+ const storage = storageMod.default || storageMod
24
+ const route = routeMod.default || routeMod
25
+
26
+ const response = await route.PUT(
27
+ new Request('http://local/api/providers/openai', {
28
+ method: 'PUT',
29
+ headers: { 'content-type': 'application/json' },
30
+ body: JSON.stringify({ isEnabled: false }),
31
+ }),
32
+ { params: Promise.resolve({ id: 'openai' }) },
33
+ )
34
+
35
+ console.log(JSON.stringify({
36
+ providerConfig: storage.loadProviderConfigs().openai,
37
+ responsePayload: await response.json(),
38
+ }))
39
+ `, { prefix: 'swarmclaw-provider-route-test-' })
40
+
41
+ assert.equal(output.providerConfig.id, 'openai')
42
+ assert.equal(output.providerConfig.type, 'builtin')
43
+ assert.equal(output.providerConfig.name, 'OpenAI')
44
+ assert.equal(output.providerConfig.isEnabled, false)
45
+ assert.equal(output.providerConfig.requiresApiKey, true)
46
+ assert.equal(output.responsePayload.id, 'openai')
47
+ assert.equal(output.responsePayload.type, 'builtin')
48
+ assert.equal(output.responsePayload.isEnabled, false)
49
+ })
@@ -13,18 +13,19 @@ export async function GET(req: Request) {
13
13
  return NextResponse.json({ models: [], error: `Ollama returned ${res.status}` })
14
14
  }
15
15
  const data = await res.json()
16
- const models = (data.models || []).map((m: any) => ({
17
- name: m.name?.replace(/:latest$/, '') || m.name,
16
+ const models = (data.models || []).map((m: Record<string, unknown>) => ({
17
+ name: typeof m.name === 'string' ? m.name.replace(/:latest$/, '') : m.name,
18
18
  size: m.size,
19
19
  modified: m.modified_at,
20
20
  }))
21
21
  return NextResponse.json({ models })
22
- } catch (err: any) {
22
+ } catch (err: unknown) {
23
+ const e = err as Record<string, unknown>
23
24
  return NextResponse.json({
24
25
  models: [],
25
- error: err.code === 'ECONNREFUSED'
26
+ error: e.code === 'ECONNREFUSED'
26
27
  ? 'Ollama is not running'
27
- : err.message || 'Failed to connect',
28
+ : (err instanceof Error ? err.message : 'Failed to connect'),
28
29
  })
29
30
  }
30
31
  }
@@ -1,123 +1,29 @@
1
1
  import { NextResponse } from 'next/server'
2
- import { loadAgents, loadSchedules, loadSessions, logActivity, upsertSchedules } from '@/lib/server/storage'
3
- import { WORKSPACE_DIR } from '@/lib/server/data-dir'
4
2
  import { notFound } from '@/lib/server/collection-helpers'
5
- import { prepareScheduleUpdate } from '@/lib/server/schedules/schedule-service'
6
- import {
7
- archiveScheduleCluster,
8
- purgeArchivedScheduleCluster,
9
- restoreArchivedScheduleCluster,
10
- } from '@/lib/server/schedules/schedule-lifecycle'
11
- import { errorMessage } from '@/lib/shared-utils'
12
- import { notify } from '@/lib/server/ws-hub'
13
3
  import { safeParseBody } from '@/lib/server/safe-parse-body'
4
+ import {
5
+ deleteScheduleFromRoute,
6
+ updateScheduleFromRoute,
7
+ } from '@/lib/server/schedules/schedule-route-service'
14
8
 
15
9
  export async function PUT(req: Request, { params }: { params: Promise<{ id: string }> }) {
16
10
  const { id } = await params
17
11
  const { data: body, error } = await safeParseBody(req)
18
12
  if (error) return error
19
- const schedules = loadSchedules()
20
- const current = schedules[id]
21
- if (!current) return notFound()
22
-
23
- if (body?.restore === true) {
24
- const restored = restoreArchivedScheduleCluster(id, {
25
- actor: { actor: 'user' },
26
- })
27
- if (!restored.ok || !restored.schedule) {
28
- return NextResponse.json({ error: 'Schedule is not archived.' }, { status: 409 })
29
- }
30
- return NextResponse.json({
31
- ...restored.schedule,
32
- restoredIds: restored.restoredIds,
33
- })
34
- }
35
-
36
- if (body?.status === 'archived') {
37
- const archived = archiveScheduleCluster(id, {
38
- actor: { actor: 'user' },
39
- })
40
- if (!archived.ok || !archived.schedule) {
41
- return NextResponse.json({ error: 'Failed to archive schedule.' }, { status: 500 })
42
- }
43
- return NextResponse.json({
44
- ...archived.schedule,
45
- archivedIds: archived.archivedIds,
46
- cancelledTaskIds: archived.cancelledTaskIds,
47
- abortedRunSessionIds: archived.abortedRunSessionIds,
48
- })
49
- }
50
-
51
- const sessions = loadSessions()
52
- const agents = loadAgents()
53
- const sessionCwd = typeof current.createdInSessionId === 'string'
54
- ? sessions[current.createdInSessionId]?.cwd
55
- : null
56
- const prepared = prepareScheduleUpdate({
57
- id,
58
- current,
59
- patch: body as Record<string, unknown>,
60
- schedules,
61
- now: Date.now(),
62
- cwd: sessionCwd || WORKSPACE_DIR,
63
- agentExists: (agentId) => Boolean(agents[agentId]),
64
- propagateEquivalentStatuses: true,
65
- propagationSource: current as unknown as Record<string, unknown>,
66
- })
67
- if (!prepared.ok) {
68
- const message = errorMessage(prepared.error)
69
- return NextResponse.json({ error: message }, { status: 400 })
70
- }
71
- upsertSchedules(prepared.entries)
72
- logActivity({
73
- entityType: 'schedule',
74
- entityId: id,
75
- action: 'updated',
76
- actor: 'user',
77
- summary: `Schedule updated: "${prepared.schedule.name}"`,
78
- detail: prepared.affectedScheduleIds.length > 1 ? { affectedScheduleIds: prepared.affectedScheduleIds } : undefined,
79
- })
80
- notify('schedules')
81
- return NextResponse.json(
82
- prepared.affectedScheduleIds.length > 1
83
- ? { ...prepared.schedule, affectedScheduleIds: prepared.affectedScheduleIds }
84
- : prepared.schedule,
85
- )
13
+ const result = updateScheduleFromRoute(id, (body || {}) as Record<string, unknown>)
14
+ if (!result.ok && result.status === 404) return notFound()
15
+ return result.ok
16
+ ? NextResponse.json(result.payload)
17
+ : NextResponse.json(result.payload, { status: result.status })
86
18
  }
87
19
 
88
20
  export async function DELETE(req: Request, { params }: { params: Promise<{ id: string }> }) {
89
21
  const { id } = await params
90
- const schedules = loadSchedules()
91
- const current = schedules[id]
92
- if (!current) return notFound()
93
-
94
22
  const { searchParams } = new URL(req.url)
95
23
  const purge = searchParams.get('purge') === 'true'
96
- if (purge) {
97
- const purged = purgeArchivedScheduleCluster(id, {
98
- actor: { actor: 'user' },
99
- })
100
- if (!purged.ok) {
101
- return NextResponse.json({ error: 'Only archived schedules can be purged.' }, { status: 409 })
102
- }
103
- return NextResponse.json({
104
- ok: true,
105
- purgedIds: purged.purgedIds,
106
- })
107
- }
108
-
109
- const archived = archiveScheduleCluster(id, {
110
- actor: { actor: 'user' },
111
- })
112
- if (!archived.ok || !archived.schedule) {
113
- return NextResponse.json({ error: 'Failed to archive schedule.' }, { status: 500 })
114
- }
115
- return NextResponse.json({
116
- ok: true,
117
- archivedIds: archived.archivedIds,
118
- cancelledTaskIds: archived.cancelledTaskIds,
119
- removedQueuedTaskIds: archived.removedQueuedTaskIds,
120
- abortedRunSessionIds: archived.abortedRunSessionIds,
121
- schedule: archived.schedule,
122
- })
24
+ const result = deleteScheduleFromRoute(id, purge)
25
+ if (!result.ok && result.status === 404) return notFound()
26
+ return result.ok
27
+ ? NextResponse.json(result.payload)
28
+ : NextResponse.json(result.payload, { status: result.status })
123
29
  }