@swarmclawai/swarmclaw 1.2.6 → 1.2.9

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 (269) hide show
  1. package/README.md +54 -23
  2. package/next.config.ts +1 -0
  3. package/package.json +4 -3
  4. package/scripts/easy-setup.mjs +1 -1
  5. package/scripts/postinstall.mjs +1 -1
  6. package/skills/swarmclaw.md +115 -0
  7. package/skills/tools/browser.md +131 -0
  8. package/skills/tools/execute.md +98 -0
  9. package/skills/tools/files.md +98 -0
  10. package/skills/tools/memory.md +104 -0
  11. package/skills/tools/platform.md +144 -0
  12. package/skills/tools/skills.md +83 -0
  13. package/src/app/agents/[id]/page.tsx +1 -18
  14. package/src/app/api/agents/thread-route.test.ts +0 -1
  15. package/src/app/api/approvals/route.test.ts +6 -22
  16. package/src/app/api/chats/[id]/messages/route.ts +23 -19
  17. package/src/app/api/chats/messages-route.test.ts +105 -51
  18. package/src/app/api/connectors/route.ts +2 -2
  19. package/src/app/api/mcp-servers/[id]/test/route.ts +3 -2
  20. package/src/app/api/openclaw/deploy/route.ts +2 -0
  21. package/src/app/api/portability/export/route.ts +8 -0
  22. package/src/app/api/portability/import/route.test.ts +80 -0
  23. package/src/app/api/portability/import/route.ts +28 -0
  24. package/src/app/api/settings/route.ts +0 -2
  25. package/src/app/api/setup/doctor/route.ts +4 -4
  26. package/src/app/api/wallets/[id]/route.ts +15 -157
  27. package/src/app/api/wallets/generate/route.ts +22 -0
  28. package/src/app/api/wallets/route.test.ts +147 -0
  29. package/src/app/api/wallets/route.ts +13 -95
  30. package/src/app/autonomy/page.tsx +2 -57
  31. package/src/app/protocols/page.tsx +2 -21
  32. package/src/app/settings/page.tsx +0 -9
  33. package/src/app/wallets/page.tsx +105 -5
  34. package/src/cli/index.js +21 -33
  35. package/src/cli/spec.js +19 -30
  36. package/src/components/agents/agent-chat-list.tsx +23 -1
  37. package/src/components/agents/agent-sheet.tsx +2 -40
  38. package/src/components/agents/inspector-panel.tsx +165 -131
  39. package/src/components/chat/chat-area.tsx +38 -9
  40. package/src/components/chat/chat-card.tsx +0 -31
  41. package/src/components/chat/message-bubble.tsx +1 -108
  42. package/src/components/chat/message-list.tsx +33 -19
  43. package/src/components/connectors/connector-sheet.tsx +25 -1
  44. package/src/components/gateways/gateway-sheet.tsx +5 -2
  45. package/src/components/layout/sidebar-rail.tsx +6 -10
  46. package/src/components/projects/project-detail.tsx +3 -35
  47. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  48. package/src/components/projects/tabs/work-tab.tsx +7 -77
  49. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  50. package/src/components/shared/connector-platform-icon.tsx +1 -0
  51. package/src/components/tasks/task-card.tsx +4 -34
  52. package/src/components/tasks/task-sheet.tsx +6 -36
  53. package/src/components/wallets/wallet-list.tsx +150 -0
  54. package/src/lib/agent-execute-defaults.test.ts +24 -0
  55. package/src/lib/agent-execute-defaults.ts +62 -0
  56. package/src/lib/app/navigation.test.ts +0 -13
  57. package/src/lib/app/navigation.ts +2 -7
  58. package/src/lib/app/view-constants.ts +14 -19
  59. package/src/lib/chat/queued-message-queue.test.ts +134 -1
  60. package/src/lib/chat/queued-message-queue.ts +77 -2
  61. package/src/lib/server/agents/agent-service.ts +5 -0
  62. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  63. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  64. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  65. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  66. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  67. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  68. package/src/lib/server/approval-match.ts +0 -85
  69. package/src/lib/server/approvals.test.ts +6 -6
  70. package/src/lib/server/approvals.ts +0 -6
  71. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  72. package/src/lib/server/builtin-extensions.ts +1 -2
  73. package/src/lib/server/capability-router.test.ts +0 -2
  74. package/src/lib/server/chat-execution/chat-execution-advanced.test.ts +1 -1
  75. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +15 -14
  76. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  77. package/src/lib/server/chat-execution/chat-execution-utils.ts +2 -4
  78. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  79. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  80. package/src/lib/server/chat-execution/chat-turn-preparation.ts +81 -64
  81. package/src/lib/server/chat-execution/chat-turn-stream-execution.ts +4 -0
  82. package/src/lib/server/chat-execution/continuation-evaluator.ts +8 -0
  83. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  84. package/src/lib/server/chat-execution/memory-mutation-tools.ts +1 -1
  85. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  86. package/src/lib/server/chat-execution/message-classifier.ts +11 -16
  87. package/src/lib/server/chat-execution/prompt-builder.test.ts +27 -0
  88. package/src/lib/server/chat-execution/prompt-builder.ts +14 -31
  89. package/src/lib/server/chat-execution/prompt-mode.test.ts +24 -0
  90. package/src/lib/server/chat-execution/prompt-mode.ts +5 -1
  91. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  92. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  93. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  94. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +13 -126
  95. package/src/lib/server/chat-execution/stream-agent-chat.ts +46 -21
  96. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  97. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  98. package/src/lib/server/chatrooms/chatroom-routing.test.ts +4 -0
  99. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  100. package/src/lib/server/chats/chat-session-service.ts +3 -5
  101. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  102. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  103. package/src/lib/server/connectors/connector-service.ts +39 -9
  104. package/src/lib/server/connectors/discord.ts +2 -2
  105. package/src/lib/server/connectors/matrix.ts +3 -2
  106. package/src/lib/server/connectors/signal.ts +5 -4
  107. package/src/lib/server/connectors/slack.ts +10 -9
  108. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  109. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  110. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  111. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  112. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  113. package/src/lib/server/connectors/swarmdock.ts +255 -0
  114. package/src/lib/server/connectors/teams.ts +3 -2
  115. package/src/lib/server/connectors/telegram.ts +4 -4
  116. package/src/lib/server/connectors/whatsapp.ts +2 -2
  117. package/src/lib/server/daemon/controller.ts +7 -0
  118. package/src/lib/server/execution-brief.test.ts +2 -25
  119. package/src/lib/server/execution-brief.ts +12 -35
  120. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  121. package/src/lib/server/gateways/gateway-profile-service.ts +19 -1
  122. package/src/lib/server/messages/message-repository.test.ts +70 -0
  123. package/src/lib/server/messages/message-repository.ts +11 -6
  124. package/src/lib/server/openclaw/deploy.ts +32 -2
  125. package/src/lib/server/persistence/storage-context.ts +0 -5
  126. package/src/lib/server/plugins-advanced.test.ts +1 -2
  127. package/src/lib/server/portability/export.ts +109 -0
  128. package/src/lib/server/portability/import.ts +159 -0
  129. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  130. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  131. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  132. package/src/lib/server/protocols/protocol-service.ts +0 -1
  133. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  134. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  135. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  136. package/src/lib/server/protocols/protocol-types.ts +0 -2
  137. package/src/lib/server/provider-health.ts +1 -10
  138. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  139. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  140. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  141. package/src/lib/server/runtime/process-manager.ts +13 -9
  142. package/src/lib/server/runtime/queue/core.ts +11 -33
  143. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  144. package/src/lib/server/runtime/scheduler.ts +0 -13
  145. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  146. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  147. package/src/lib/server/runtime/session-run-manager/queries.ts +15 -1
  148. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  149. package/src/lib/server/runtime/session-run-manager.test.ts +58 -28
  150. package/src/lib/server/sandbox/session-runtime.test.ts +18 -1
  151. package/src/lib/server/sandbox/session-runtime.ts +40 -28
  152. package/src/lib/server/session-tools/autonomy-tools.test.ts +7 -9
  153. package/src/lib/server/session-tools/context.ts +1 -1
  154. package/src/lib/server/session-tools/credential-env.ts +109 -0
  155. package/src/lib/server/session-tools/crud.ts +3 -17
  156. package/src/lib/server/session-tools/delegate.ts +0 -4
  157. package/src/lib/server/session-tools/edit_file.ts +3 -2
  158. package/src/lib/server/session-tools/execute.test.ts +58 -0
  159. package/src/lib/server/session-tools/execute.ts +334 -0
  160. package/src/lib/server/session-tools/files-tool.ts +635 -0
  161. package/src/lib/server/session-tools/index.ts +14 -8
  162. package/src/lib/server/session-tools/memory-tool.ts +242 -0
  163. package/src/lib/server/session-tools/memory.ts +1 -1
  164. package/src/lib/server/session-tools/openclaw-nodes.ts +3 -2
  165. package/src/lib/server/session-tools/openclaw-workspace.ts +3 -2
  166. package/src/lib/server/session-tools/platform-tool.ts +617 -0
  167. package/src/lib/server/session-tools/session-info.ts +3 -2
  168. package/src/lib/server/session-tools/session-tools-wiring.test.ts +3 -4
  169. package/src/lib/server/session-tools/shell.ts +7 -122
  170. package/src/lib/server/session-tools/skills-tool.ts +396 -0
  171. package/src/lib/server/session-tools/team-context.ts +0 -3
  172. package/src/lib/server/session-tools/web.ts +2 -2
  173. package/src/lib/server/storage-normalization.ts +10 -0
  174. package/src/lib/server/storage.ts +18 -45
  175. package/src/lib/server/tasks/task-checkout.ts +59 -0
  176. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  177. package/src/lib/server/tasks/task-route-service.ts +4 -26
  178. package/src/lib/server/tasks/task-service.ts +0 -7
  179. package/src/lib/server/tool-aliases.ts +2 -2
  180. package/src/lib/server/tool-capability-policy-advanced.test.ts +13 -6
  181. package/src/lib/server/tool-capability-policy.test.ts +2 -1
  182. package/src/lib/server/tool-capability-policy.ts +60 -35
  183. package/src/lib/server/tool-planning.ts +11 -12
  184. package/src/lib/server/universal-tool-access.ts +0 -1
  185. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  186. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  187. package/src/lib/server/wallets/wallet-service.ts +119 -0
  188. package/src/lib/server/working-state/extraction.ts +8 -42
  189. package/src/lib/server/working-state/normalization.ts +10 -103
  190. package/src/lib/server/working-state/service.ts +12 -21
  191. package/src/lib/setup-defaults.ts +5 -0
  192. package/src/lib/strip-internal-metadata.test.ts +1 -1
  193. package/src/lib/strip-internal-metadata.ts +1 -1
  194. package/src/lib/tool-definitions.ts +1 -1
  195. package/src/lib/validation/schemas.test.ts +16 -0
  196. package/src/lib/validation/schemas.ts +49 -2
  197. package/src/stores/slices/data-slice.ts +5 -1
  198. package/src/stores/slices/ui-slice.ts +0 -4
  199. package/src/stores/use-chat-store.test.ts +231 -0
  200. package/src/stores/use-chat-store.ts +62 -13
  201. package/src/types/agent.ts +264 -0
  202. package/src/types/app-settings.ts +173 -0
  203. package/src/types/approval.ts +25 -0
  204. package/src/types/connector.ts +188 -0
  205. package/src/types/extension.ts +386 -0
  206. package/src/types/index.ts +16 -3555
  207. package/src/types/message.ts +56 -0
  208. package/src/types/misc.ts +737 -0
  209. package/src/types/protocol.ts +420 -0
  210. package/src/types/provider.ts +52 -0
  211. package/src/types/run.ts +180 -0
  212. package/src/types/schedule.ts +59 -0
  213. package/src/types/session.ts +215 -0
  214. package/src/types/skill.ts +157 -0
  215. package/src/types/swarmdock.ts +29 -0
  216. package/src/types/task.ts +144 -0
  217. package/src/types/working-state.ts +204 -0
  218. package/src/views/settings/section-heartbeat.tsx +2 -2
  219. package/src/views/settings/section-runtime-loop.tsx +0 -14
  220. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  221. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  222. package/src/app/api/missions/[id]/events/route.ts +0 -14
  223. package/src/app/api/missions/[id]/route.ts +0 -10
  224. package/src/app/api/missions/route.test.ts +0 -244
  225. package/src/app/api/missions/route.ts +0 -57
  226. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  227. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  228. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  229. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  230. package/src/app/missions/[id]/page.tsx +0 -3
  231. package/src/app/missions/page.tsx +0 -685
  232. package/src/components/canvas/canvas-panel.tsx +0 -267
  233. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  234. package/src/components/wallets/wallet-panel.tsx +0 -1010
  235. package/src/components/wallets/wallet-section.tsx +0 -260
  236. package/src/features/missions/queries.ts +0 -23
  237. package/src/lib/canvas-content.test.ts +0 -360
  238. package/src/lib/canvas-content.ts +0 -198
  239. package/src/lib/server/canvas-content.test.ts +0 -32
  240. package/src/lib/server/canvas-content.ts +0 -6
  241. package/src/lib/server/ethereum.ts +0 -591
  242. package/src/lib/server/evm-swap.ts +0 -476
  243. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  244. package/src/lib/server/missions/mission-intent.ts +0 -569
  245. package/src/lib/server/missions/mission-repository.ts +0 -74
  246. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  247. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  248. package/src/lib/server/missions/mission-service/context.ts +0 -4
  249. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  250. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  251. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  252. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  253. package/src/lib/server/missions/mission-service.test.ts +0 -888
  254. package/src/lib/server/missions/mission-service.ts +0 -6
  255. package/src/lib/server/session-tools/canvas.ts +0 -105
  256. package/src/lib/server/session-tools/sandbox.ts +0 -281
  257. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  258. package/src/lib/server/session-tools/wallet.ts +0 -1287
  259. package/src/lib/server/solana.ts +0 -327
  260. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  261. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  262. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  263. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  264. package/src/lib/server/wallet/wallet-service.ts +0 -225
  265. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  266. package/src/lib/wallet/wallet-transactions.ts +0 -43
  267. package/src/lib/wallet/wallet.test.ts +0 -333
  268. package/src/lib/wallet/wallet.ts +0 -183
  269. package/src/views/settings/section-wallets.tsx +0 -35
@@ -3,65 +3,119 @@ import test from 'node:test'
3
3
 
4
4
  import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
5
5
 
6
- test('chat messages route materializes stale streaming artifacts even if runtime memory is stale', () => {
6
+ test('messages route serves and mutates repo-backed transcript history', () => {
7
7
  const output = runWithTempDataDir<{
8
- status: number
9
- returnedStreaming: boolean | null
10
- returnedText: string | null
11
- persistedStreaming: boolean | null
12
- persistedText: string | null
13
- }>(`
14
- const storageMod = await import('./src/lib/server/storage')
15
- const routeMod = await import('./src/app/api/chats/[id]/messages/route')
16
- const runtimeStateMod = await import('./src/lib/server/runtime/runtime-state')
17
- const storage = storageMod.default || storageMod
18
- const route = routeMod.default || routeMod
19
- const runtimeState = runtimeStateMod.default || runtimeStateMod
8
+ fullCount: number
9
+ paginatedTexts: string[]
10
+ paginatedStartIndex: number
11
+ paginatedTotal: number
12
+ bookmarkPersisted: boolean
13
+ contextClearCountAfterPost: number
14
+ finalKinds: Array<string | null>
15
+ finalBookmarked: boolean
16
+ blobMessageCount: number
17
+ }>(`
18
+ const storageMod = await import('./src/lib/server/storage')
19
+ const repoMod = await import('@/lib/server/messages/message-repository')
20
+ const routeMod = await import('./src/app/api/chats/[id]/messages/route')
21
+ const storage = storageMod.default || storageMod
22
+ const repo = repoMod.default || repoMod
23
+ const route = routeMod.default || routeMod
20
24
 
21
- storage.upsertStoredItem('sessions', 'session-stale', {
22
- id: 'session-stale',
23
- name: 'Stale session',
24
- provider: 'ollama',
25
- model: 'test-model',
26
- createdAt: 1,
27
- updatedAt: 1,
28
- active: false,
29
- currentRunId: null,
30
- messages: [
31
- { role: 'user', text: 'hello', time: 1 },
32
- {
33
- role: 'assistant',
34
- text: 'partial reply',
35
- time: 2,
36
- streaming: true,
37
- toolEvents: [{ name: 'http_request', input: '{}', output: '{"ok":true}' }],
38
- },
39
- ],
25
+ const now = Date.now()
26
+ storage.saveSessions({
27
+ sess_1: {
28
+ id: 'sess_1',
29
+ name: 'Repo-backed session',
30
+ cwd: process.env.WORKSPACE_DIR,
31
+ user: 'tester',
32
+ provider: 'openai',
33
+ model: 'gpt-5',
34
+ claudeSessionId: null,
35
+ codexThreadId: null,
36
+ opencodeSessionId: null,
37
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
38
+ messages: [],
39
+ createdAt: now,
40
+ lastActiveAt: now,
41
+ },
40
42
  })
41
43
 
42
- runtimeState.registerActiveSessionProcess('session-stale', { kill() {} })
44
+ repo.appendMessage('sess_1', { role: 'user', text: 'hello', time: now })
45
+ repo.appendMessage('sess_1', { role: 'user', text: '', kind: 'context-clear', time: now + 1 })
46
+ repo.appendMessage('sess_1', { role: 'assistant', text: 'welcome back', time: now + 2 })
47
+ storage.patchSession('sess_1', (current) => {
48
+ if (!current) return null
49
+ current.messages = [{ role: 'assistant', text: 'stale blob only', time: now - 10 }]
50
+ return current
51
+ })
52
+
53
+ const fullResponse = await route.GET(
54
+ new Request('http://local/api/chats/sess_1/messages'),
55
+ { params: Promise.resolve({ id: 'sess_1' }) },
56
+ )
57
+ const fullMessages = await fullResponse.json()
43
58
 
44
- const response = await route.GET(
45
- new Request('http://local/api/chats/session-stale/messages'),
46
- { params: Promise.resolve({ id: 'session-stale' }) },
59
+ const paginatedResponse = await route.GET(
60
+ new Request('http://local/api/chats/sess_1/messages?limit=2'),
61
+ { params: Promise.resolve({ id: 'sess_1' }) },
47
62
  )
48
- const payload = await response.json()
49
- const persisted = storage.loadSession('session-stale')
50
- const returned = Array.isArray(payload) ? payload[payload.length - 1] : null
51
- const saved = Array.isArray(persisted?.messages) ? persisted.messages[persisted.messages.length - 1] : null
63
+ const paginated = await paginatedResponse.json()
64
+
65
+ const bookmarkResponse = await route.PUT(
66
+ new Request('http://local/api/chats/sess_1/messages', {
67
+ method: 'PUT',
68
+ headers: { 'content-type': 'application/json' },
69
+ body: JSON.stringify({ messageIndex: 2, bookmarked: true }),
70
+ }),
71
+ { params: Promise.resolve({ id: 'sess_1' }) },
72
+ )
73
+ const bookmarked = await bookmarkResponse.json()
74
+
75
+ await route.POST(
76
+ new Request('http://local/api/chats/sess_1/messages', {
77
+ method: 'POST',
78
+ headers: { 'content-type': 'application/json' },
79
+ body: JSON.stringify({ kind: 'context-clear' }),
80
+ }),
81
+ { params: Promise.resolve({ id: 'sess_1' }) },
82
+ )
83
+
84
+ const afterPost = repo.getMessages('sess_1')
85
+ const contextClearCountAfterPost = afterPost.filter((message) => message.kind === 'context-clear').length
86
+
87
+ await route.DELETE(
88
+ new Request('http://local/api/chats/sess_1/messages', {
89
+ method: 'DELETE',
90
+ headers: { 'content-type': 'application/json' },
91
+ body: JSON.stringify({ messageIndex: 1 }),
92
+ }),
93
+ { params: Promise.resolve({ id: 'sess_1' }) },
94
+ )
95
+
96
+ const finalMessages = repo.getMessages('sess_1')
97
+ const sessions = storage.loadSessions()
52
98
 
53
99
  console.log(JSON.stringify({
54
- status: response.status,
55
- returnedStreaming: returned?.streaming === true,
56
- returnedText: returned?.text || null,
57
- persistedStreaming: saved?.streaming === true,
58
- persistedText: saved?.text || null,
100
+ fullCount: fullMessages.length,
101
+ paginatedTexts: paginated.messages.map((message) => message.text),
102
+ paginatedStartIndex: paginated.startIndex,
103
+ paginatedTotal: paginated.total,
104
+ bookmarkPersisted: bookmarked.bookmarked === true,
105
+ contextClearCountAfterPost,
106
+ finalKinds: finalMessages.map((message) => message.kind || null),
107
+ finalBookmarked: finalMessages[1]?.bookmarked === true,
108
+ blobMessageCount: Array.isArray(sessions.sess_1.messages) ? sessions.sess_1.messages.length : -1,
59
109
  }))
60
- `, { prefix: 'swarmclaw-chat-messages-route-' })
110
+ `, { prefix: 'swarmclaw-messages-route-' })
61
111
 
62
- assert.equal(output.status, 200)
63
- assert.equal(output.returnedStreaming, false)
64
- assert.equal(output.persistedStreaming, false)
65
- assert.equal(output.returnedText, 'partial reply')
66
- assert.equal(output.persistedText, 'partial reply')
112
+ assert.equal(output.fullCount, 3)
113
+ assert.deepEqual(output.paginatedTexts, ['', 'welcome back'])
114
+ assert.equal(output.paginatedStartIndex, 1)
115
+ assert.equal(output.paginatedTotal, 3)
116
+ assert.equal(output.bookmarkPersisted, true)
117
+ assert.equal(output.contextClearCountAfterPost, 2)
118
+ assert.deepEqual(output.finalKinds, [null, null, 'context-clear'])
119
+ assert.equal(output.finalBookmarked, true)
120
+ assert.equal(output.blobMessageCount, 1)
67
121
  })
@@ -6,10 +6,10 @@ import { z } from 'zod'
6
6
  import {
7
7
  autoStartConnectorIfNeeded,
8
8
  createConnector,
9
+ getConnectorWithRuntime,
9
10
  listConnectorsWithRuntime,
10
11
  } from '@/lib/server/connectors/connector-service'
11
12
  import { ensureDaemonProcessRunning } from '@/lib/server/daemon/controller'
12
- import { loadConnector } from '@/lib/server/connectors/connector-repository'
13
13
  export const dynamic = 'force-dynamic'
14
14
 
15
15
  export async function GET() {
@@ -29,5 +29,5 @@ export async function POST(req: Request) {
29
29
  }
30
30
  const connector = createConnector(parsed.data as unknown as Record<string, unknown>)
31
31
  await autoStartConnectorIfNeeded(connector, parsed.data as unknown as Record<string, unknown>)
32
- return NextResponse.json(loadConnector(connector.id) || connector)
32
+ return NextResponse.json(await getConnectorWithRuntime(connector.id) || connector)
33
33
  }
@@ -2,6 +2,7 @@ import { NextResponse } from 'next/server'
2
2
  import { loadMcpServers } from '@/lib/server/storage'
3
3
  import { notFound } from '@/lib/server/collection-helpers'
4
4
  import { connectMcpServer, mcpToolsToLangChain, disconnectMcpServer } from '@/lib/server/mcp-client'
5
+ import { errorMessage } from '@/lib/shared-utils'
5
6
 
6
7
  export async function POST(_req: Request, { params }: { params: Promise<{ id: string }> }) {
7
8
  const { id } = await params
@@ -15,9 +16,9 @@ export async function POST(_req: Request, { params }: { params: Promise<{ id: st
15
16
  const toolNames = tools.map((t: any) => t.name)
16
17
  await disconnectMcpServer(client, transport)
17
18
  return NextResponse.json({ ok: true, tools: toolNames })
18
- } catch (err: any) {
19
+ } catch (err: unknown) {
19
20
  return NextResponse.json(
20
- { ok: false, error: err.message || 'Connection failed' },
21
+ { ok: false, error: errorMessage(err) || 'Connection failed' },
21
22
  { status: 500 }
22
23
  )
23
24
  }
@@ -129,6 +129,7 @@ export async function POST(req: Request) {
129
129
  locals: result.locals,
130
130
  localPrimaryId: result.locals.find((item) => item.isPrimary)?.id || result.local.id,
131
131
  token: result.token,
132
+ gatewayProfileId: result.gatewayProfileId,
132
133
  })
133
134
  }
134
135
 
@@ -156,6 +157,7 @@ export async function POST(req: Request) {
156
157
  locals: result.locals,
157
158
  localPrimaryId: result.locals.find((item) => item.isPrimary)?.id || result.local.id,
158
159
  token: result.token,
160
+ gatewayProfileId: result.gatewayProfileId,
159
161
  })
160
162
  }
161
163
 
@@ -0,0 +1,8 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { exportConfig } from '@/lib/server/portability/export'
3
+ export const dynamic = 'force-dynamic'
4
+
5
+ export async function GET() {
6
+ const manifest = exportConfig()
7
+ return NextResponse.json(manifest)
8
+ }
@@ -0,0 +1,80 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import test from 'node:test'
7
+
8
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../../..')
9
+
10
+ function runWithTempDataDir(script: string) {
11
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-portability-import-'))
12
+ try {
13
+ const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
14
+ cwd: repoRoot,
15
+ env: {
16
+ ...process.env,
17
+ DATA_DIR: path.join(tempDir, 'data'),
18
+ WORKSPACE_DIR: path.join(tempDir, 'workspace'),
19
+ },
20
+ encoding: 'utf-8',
21
+ })
22
+ assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
23
+ const lines = (result.stdout || '')
24
+ .trim()
25
+ .split('\n')
26
+ .map((line) => line.trim())
27
+ .filter(Boolean)
28
+ const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
29
+ return JSON.parse(jsonLine || '{}')
30
+ } finally {
31
+ fs.rmSync(tempDir, { recursive: true, force: true })
32
+ }
33
+ }
34
+
35
+ test('POST /api/portability/import validates manifest arrays before importing', () => {
36
+ const output = runWithTempDataDir(`
37
+ const routeMod = await import('./src/app/api/portability/import/route')
38
+ const route = routeMod.default || routeMod
39
+
40
+ const invalidResponse = await route.POST(new Request('http://local/api/portability/import', {
41
+ method: 'POST',
42
+ headers: { 'content-type': 'application/json' },
43
+ body: JSON.stringify({ formatVersion: 1, agents: [] }),
44
+ }))
45
+ const invalidPayload = await invalidResponse.json()
46
+
47
+ const validResponse = await route.POST(new Request('http://local/api/portability/import', {
48
+ method: 'POST',
49
+ headers: { 'content-type': 'application/json' },
50
+ body: JSON.stringify({
51
+ formatVersion: 1,
52
+ exportedAt: '2026-03-29T00:00:00.000Z',
53
+ agents: [],
54
+ skills: [],
55
+ schedules: [],
56
+ }),
57
+ }))
58
+ const validPayload = await validResponse.json()
59
+
60
+ console.log(JSON.stringify({
61
+ invalidStatus: invalidResponse.status,
62
+ invalidError: invalidPayload?.error || null,
63
+ invalidPaths: Array.isArray(invalidPayload?.issues)
64
+ ? invalidPayload.issues.map((issue) => issue.path).sort()
65
+ : [],
66
+ validStatus: validResponse.status,
67
+ validAgentsCreated: validPayload?.agents?.created ?? null,
68
+ validSkillsCreated: validPayload?.skills?.created ?? null,
69
+ validSchedulesCreated: validPayload?.schedules?.created ?? null,
70
+ }))
71
+ `)
72
+
73
+ assert.equal(output.invalidStatus, 400)
74
+ assert.equal(output.invalidError, 'Validation failed')
75
+ assert.deepEqual(output.invalidPaths, ['schedules', 'skills'])
76
+ assert.equal(output.validStatus, 200)
77
+ assert.equal(output.validAgentsCreated, 0)
78
+ assert.equal(output.validSkillsCreated, 0)
79
+ assert.equal(output.validSchedulesCreated, 0)
80
+ })
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { safeParseBody } from '@/lib/server/safe-parse-body'
3
+ import { importConfig } from '@/lib/server/portability/import'
4
+ import type { PortableManifest } from '@/lib/server/portability/export'
5
+ import { PortableManifestSchema, formatZodError } from '@/lib/validation/schemas'
6
+ import { z } from 'zod'
7
+ export const dynamic = 'force-dynamic'
8
+
9
+ export async function POST(req: Request) {
10
+ const { data: raw, error } = await safeParseBody(req)
11
+ if (error) return error
12
+
13
+ const parsed = PortableManifestSchema.safeParse(raw)
14
+ if (!parsed.success) {
15
+ return NextResponse.json(formatZodError(parsed.error as z.ZodError), { status: 400 })
16
+ }
17
+
18
+ try {
19
+ const result = importConfig(parsed.data as PortableManifest)
20
+ return NextResponse.json(result)
21
+ } catch (err) {
22
+ const message = err instanceof Error ? err.message : 'Failed to import manifest'
23
+ if (/^Unsupported format version /i.test(message)) {
24
+ return NextResponse.json({ error: message }, { status: 400 })
25
+ }
26
+ return NextResponse.json({ error: message }, { status: 500 })
27
+ }
28
+ }
@@ -141,11 +141,9 @@ export async function PUT(req: Request) {
141
141
  settings.taskQualityGateRequireReport = parseBoolSetting(settings.taskQualityGateRequireReport, false)
142
142
  settings.taskManagementEnabled = parseBoolSetting(settings.taskManagementEnabled, true)
143
143
  settings.projectManagementEnabled = parseBoolSetting(settings.projectManagementEnabled, true)
144
- settings.walletApprovalsEnabled = parseBoolSetting(settings.walletApprovalsEnabled, true)
145
144
  settings.integrityMonitorEnabled = parseBoolSetting(settings.integrityMonitorEnabled, true)
146
145
  settings.daemonAutostartEnabled = parseBoolSetting(settings.daemonAutostartEnabled, true)
147
146
  settings.autonomyResumeApprovalsEnabled = parseBoolSetting(settings.autonomyResumeApprovalsEnabled, false)
148
- settings.missionHumanLoopEnabled = parseBoolSetting(settings.missionHumanLoopEnabled, false)
149
147
  settings.untrustedContentGuardMode = parseGuardMode(settings.untrustedContentGuardMode)
150
148
  settings.sessionResetMode = settings.sessionResetMode === 'daily' ? 'daily' : settings.sessionResetMode === 'idle' ? 'idle' : null
151
149
  settings.whatsappApprovedContacts = normalizeWhatsAppApprovedContacts(settings.whatsappApprovedContacts)
@@ -217,14 +217,14 @@ export async function GET(req: Request) {
217
217
  pushCheck(
218
218
  checks,
219
219
  'docker',
220
- 'Docker (sandbox runtime)',
220
+ 'Docker (browser sandbox runtime)',
221
221
  docker.available ? 'pass' : 'warn',
222
222
  docker.available
223
- ? `Docker ${docker.version || ''} is available for container sandbox execution.`.trim()
224
- : 'Docker is not available. SwarmClaw will fall back to host execution until Docker Desktop is installed.',
223
+ ? `Docker ${docker.version || ''} is available for sandbox browser execution.`.trim()
224
+ : 'Docker is not available. SwarmClaw will use the host Playwright runtime unless Docker Desktop is installed.',
225
225
  )
226
226
  if (!docker.available) {
227
- actions.push('Install Docker Desktop if you want shell, browser, and code execution to stay inside containers by default.')
227
+ actions.push('Install Docker Desktop if you want Playwright browser sessions to use the sandbox browser runtime.')
228
228
  }
229
229
 
230
230
  const gitRootCheck = run('git', ['rev-parse', '--is-inside-work-tree'], 4_000)
@@ -1,174 +1,32 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadWallets, upsertWallet, deleteWallet as deleteWalletFromStore, loadAgent, loadAgents, upsertAgent } from '@/lib/server/storage'
4
- import { notify } from '@/lib/server/ws-hub'
5
- import { getWalletLimitAtomic, normalizeAtomicString } from '@/lib/wallet/wallet'
6
- import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary } from '@/types'
7
- import { buildEmptyWalletPortfolio, getCachedWalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
8
- import {
9
- getAgentActiveWalletId,
10
- getWalletPortfolioSnapshot,
11
- linkWalletToAgent,
12
- setAgentActiveWallet,
13
- stripWalletPrivateKey,
14
- unlinkWalletFromAgent,
15
- } from '@/lib/server/wallet/wallet-service'
3
+ import { getWalletSafe, removeWallet, updateWallet } from '@/lib/server/wallets/wallet-service'
16
4
  export const dynamic = 'force-dynamic'
17
- const WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS = 2500
18
5
 
19
- function withPortfolio(
20
- wallet: AgentWallet,
21
- portfolio: {
22
- balanceAtomic: string
23
- balanceFormatted: string
24
- balanceSymbol: string
25
- balanceDisplay: string
26
- balanceLamports?: number
27
- balanceSol?: number
28
- assets: WalletAssetBalance[]
29
- summary: WalletPortfolioSummary
30
- },
31
- isActive: boolean,
32
- ) {
33
- return {
34
- ...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
35
- balanceAtomic: portfolio.balanceAtomic,
36
- balanceFormatted: portfolio.balanceFormatted,
37
- balanceSymbol: portfolio.balanceSymbol,
38
- balanceDisplay: portfolio.balanceDisplay,
39
- balanceLamports: portfolio.balanceLamports,
40
- balanceSol: portfolio.balanceSol,
41
- assets: portfolio.assets,
42
- portfolioSummary: portfolio.summary,
43
- isActive,
44
- }
45
- }
46
-
47
- export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
48
7
  const { id } = await params
49
- const wallets = loadWallets() as Record<string, AgentWallet>
50
- const wallet = wallets[id]
8
+ const wallet = getWalletSafe(id)
51
9
  if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
52
-
53
- const url = new URL(req.url)
54
- const cachedOnly = url.searchParams.get('cached') === '1'
55
- const agents = loadAgents()
56
- const isActive = getAgentActiveWalletId(agents[wallet.agentId]) === wallet.id
57
-
58
- if (cachedOnly) {
59
- const cached = getCachedWalletPortfolio(wallet)
60
- if (!cached) {
61
- return NextResponse.json({
62
- ...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
63
- isActive,
64
- })
65
- }
66
- return NextResponse.json(withPortfolio(wallet, cached, isActive))
67
- }
68
-
69
- let portfolio = buildEmptyWalletPortfolio(wallet)
70
- try {
71
- portfolio = await getWalletPortfolioSnapshot(wallet, {
72
- timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
73
- allowStale: true,
74
- })
75
- } catch {
76
- // RPC failure — return 0
77
- }
78
-
79
- return NextResponse.json(withPortfolio(wallet, portfolio, isActive))
10
+ return NextResponse.json(wallet)
80
11
  }
81
12
 
82
13
  export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
83
14
  const { id } = await params
84
- const wallets = loadWallets() as Record<string, AgentWallet>
85
- const wallet = wallets[id]
86
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
87
-
88
15
  const { data: body, error } = await safeParseBody(req)
89
16
  if (error) return error
90
- const shouldMakeActive = body.makeActive === true
91
-
92
- // Reassign wallet to a different agent
93
- if (typeof body.agentId === 'string' && body.agentId !== wallet.agentId) {
94
- const newAgent = loadAgent(body.agentId)
95
- if (!newAgent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
96
-
97
- // Only one wallet per chain per agent.
98
- const allWallets = loadWallets() as Record<string, AgentWallet>
99
- const conflict = Object.values(allWallets).find((w) => w.agentId === body.agentId && w.id !== id && w.chain === wallet.chain)
100
- if (conflict) return NextResponse.json({ error: `Target agent already has a ${wallet.chain} wallet` }, { status: 409 })
101
-
102
- const oldAgent = loadAgent(wallet.agentId)
103
- if (oldAgent) {
104
- unlinkWalletFromAgent(oldAgent as any, id)
105
- oldAgent.updatedAt = Date.now()
106
- upsertAgent(wallet.agentId, oldAgent)
107
- }
108
-
109
- linkWalletToAgent(newAgent as any, id, shouldMakeActive || getAgentActiveWalletId(newAgent as any) == null)
110
- newAgent.updatedAt = Date.now()
111
- upsertAgent(body.agentId, newAgent)
112
- notify('agents')
113
-
114
- wallet.agentId = body.agentId
115
- } else if (shouldMakeActive) {
116
- const agent = loadAgent(wallet.agentId)
117
- if (agent) {
118
- setAgentActiveWallet(agent as any, id)
119
- agent.updatedAt = Date.now()
120
- upsertAgent(wallet.agentId, agent)
121
- notify('agents')
122
- }
123
- }
124
-
125
- if (body.label !== undefined) wallet.label = body.label as string | undefined
126
- if (body.spendingLimitAtomic !== undefined || body.spendingLimitLamports !== undefined) {
127
- wallet.spendingLimitAtomic = normalizeAtomicString(body.spendingLimitAtomic ?? body.spendingLimitLamports, getWalletLimitAtomic(wallet, 'perTx'))
128
- }
129
- if (body.dailyLimitAtomic !== undefined || body.dailyLimitLamports !== undefined) {
130
- wallet.dailyLimitAtomic = normalizeAtomicString(body.dailyLimitAtomic ?? body.dailyLimitLamports, getWalletLimitAtomic(wallet, 'daily'))
131
- }
132
- if (typeof body.requireApproval === 'boolean') wallet.requireApproval = body.requireApproval
133
- wallet.updatedAt = Date.now()
134
-
135
- upsertWallet(id, wallet)
136
- notify('wallets')
137
-
138
- return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
17
+ const patch: Record<string, unknown> = {}
18
+ if (typeof body.label === 'string') patch.label = body.label
19
+ if (typeof body.spendingLimitUsdc === 'string' || body.spendingLimitUsdc === null) patch.spendingLimitUsdc = body.spendingLimitUsdc
20
+ if (typeof body.dailyLimitUsdc === 'string' || body.dailyLimitUsdc === null) patch.dailyLimitUsdc = body.dailyLimitUsdc
21
+ if (typeof body.requireApproval === 'boolean') patch.requireApproval = body.requireApproval
22
+ const updated = updateWallet(id, patch)
23
+ if (!updated) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
24
+ return NextResponse.json(updated)
139
25
  }
140
26
 
141
27
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
142
28
  const { id } = await params
143
- const wallets = loadWallets() as Record<string, AgentWallet>
144
- const wallet = wallets[id]
145
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
146
-
147
- // Check if balance > 0 and warn
148
- let portfolio = buildEmptyWalletPortfolio(wallet)
149
- try {
150
- portfolio = await getWalletPortfolioSnapshot(wallet, {
151
- timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
152
- allowStale: true,
153
- })
154
- } catch { /* ignore */ }
155
-
156
- // Unlink from agent
157
- const agent = loadAgent(wallet.agentId)
158
- if (agent) {
159
- unlinkWalletFromAgent(agent as any, id)
160
- agent.updatedAt = Date.now()
161
- upsertAgent(wallet.agentId, agent)
162
- notify('agents')
163
- }
164
-
165
- deleteWalletFromStore(id)
166
- notify('wallets')
167
-
168
- return NextResponse.json({
169
- ok: true,
170
- warning: portfolio.summary.nonZeroAssets > 0
171
- ? `Wallet still had ${portfolio.summary.nonZeroAssets} asset${portfolio.summary.nonZeroAssets === 1 ? '' : 's'} remaining, including ${portfolio.balanceDisplay}`
172
- : undefined,
173
- })
29
+ const deleted = removeWallet(id)
30
+ if (!deleted) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
31
+ return NextResponse.json({ ok: true })
174
32
  }
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { safeParseBody } from '@/lib/server/safe-parse-body'
3
+ import { generateWallet, WalletServiceError } from '@/lib/server/wallets/wallet-service'
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function POST(req: Request) {
7
+ const { data: body, error } = await safeParseBody(req)
8
+ if (error) return error
9
+ try {
10
+ const wallet = await generateWallet({
11
+ agentId: typeof body.agentId === 'string' ? body.agentId : '',
12
+ label: typeof body.label === 'string' ? body.label : undefined,
13
+ })
14
+ return NextResponse.json(wallet, { status: 201 })
15
+ } catch (err) {
16
+ if (err instanceof WalletServiceError) {
17
+ return NextResponse.json({ error: err.message }, { status: err.status })
18
+ }
19
+ const message = err instanceof Error ? err.message : 'Failed to generate wallet'
20
+ return NextResponse.json({ error: message }, { status: 500 })
21
+ }
22
+ }