@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
@@ -145,11 +145,12 @@ const slack: PlatformConnector = {
145
145
  }
146
146
  botUserId = auth.user_id as string
147
147
  log.info(TAG, `Authenticated as @${auth.user} in workspace "${auth.team}"`)
148
- } catch (err: any) {
149
- const hint = err.code === 'slack_webapi_platform_error'
148
+ } catch (err: unknown) {
149
+ const hint = (err instanceof Error && 'code' in err) ? (err as { code: string }).code : undefined
150
+ const suffix = hint === 'slack_webapi_platform_error'
150
151
  ? '. Check that your Bot Token (xoxb-...) is correct and the app is installed to the workspace.'
151
152
  : ''
152
- throw new Error(`Slack auth failed: ${err.message}${hint}`)
153
+ throw new Error(`Slack auth failed: ${errorMessage(err)}${suffix}`)
153
154
  }
154
155
 
155
156
  const app = new App({
@@ -215,8 +216,8 @@ const slack: PlatformConnector = {
215
216
  media.push(stored)
216
217
  continue
217
218
  }
218
- } catch (err: any) {
219
- log.warn(TAG, `Media download failed (${f?.name || 'file'}):`, err?.message || String(err))
219
+ } catch (err: unknown) {
220
+ log.warn(TAG, `Media download failed (${f?.name || 'file'}):`, errorMessage(err))
220
221
  }
221
222
  }
222
223
  media.push({
@@ -264,8 +265,8 @@ const slack: PlatformConnector = {
264
265
  return sent.ts || undefined
265
266
  },
266
267
  })
267
- } catch (err: any) {
268
- log.error(TAG, 'Error handling message:', err.message)
268
+ } catch (err: unknown) {
269
+ log.error(TAG, 'Error handling message:', errorMessage(err))
269
270
  try {
270
271
  await say('Sorry, I encountered an error processing your message.')
271
272
  } catch { /* ignore */ }
@@ -322,8 +323,8 @@ const slack: PlatformConnector = {
322
323
  return sent.ts || undefined
323
324
  },
324
325
  })
325
- } catch (err: any) {
326
- log.error(TAG, 'Error handling mention:', err.message)
326
+ } catch (err: unknown) {
327
+ log.error(TAG, 'Error handling mention:', errorMessage(err))
327
328
  }
328
329
  })
329
330
 
@@ -0,0 +1,74 @@
1
+ import { log } from '@/lib/server/logger'
2
+ import type { BidCreateInput } from '@swarmdock/shared'
3
+
4
+ const TAG = 'swarmdock-bid'
5
+
6
+ interface SwarmDockTask {
7
+ id: string
8
+ title: string
9
+ skillRequirements: string[]
10
+ budgetMax: string
11
+ }
12
+
13
+ interface SwarmDockConfig {
14
+ skills: string
15
+ maxBudget: string
16
+ autoDiscover: boolean
17
+ }
18
+
19
+ /**
20
+ * Determine if the agent should auto-bid on a discovered task.
21
+ * Checks skill overlap and budget limits.
22
+ */
23
+ export function shouldAutoBid(task: SwarmDockTask, config: SwarmDockConfig): boolean {
24
+ if (!config.autoDiscover) return false
25
+
26
+ // Check budget
27
+ const maxBudget = BigInt(config.maxBudget || '0')
28
+ if (maxBudget > BigInt(0)) {
29
+ const taskBudget = BigInt(task.budgetMax || '0')
30
+ if (taskBudget > maxBudget) {
31
+ log.debug(TAG, `Skipping "${task.title}" — budget ${task.budgetMax} exceeds max ${config.maxBudget}`)
32
+ return false
33
+ }
34
+ }
35
+
36
+ // Check skill overlap
37
+ const agentSkills = new Set(
38
+ config.skills.split(',').map((s) => s.trim().toLowerCase()).filter(Boolean),
39
+ )
40
+ if (agentSkills.size === 0) return false
41
+
42
+ const hasMatchingSkill = task.skillRequirements.some(
43
+ (req) => agentSkills.has(req.toLowerCase()),
44
+ )
45
+ if (!hasMatchingSkill) {
46
+ log.debug(TAG, `Skipping "${task.title}" — no matching skills`)
47
+ return false
48
+ }
49
+
50
+ return true
51
+ }
52
+
53
+ /**
54
+ * Submit an auto-bid on a SwarmDock task.
55
+ * Uses the task's max budget as the proposed price (simple strategy).
56
+ */
57
+ export async function submitAutoBid(
58
+ client: { tasks: { bid: (taskId: string, input: BidCreateInput) => Promise<unknown> } },
59
+ taskId: string,
60
+ config: SwarmDockConfig,
61
+ ): Promise<void> {
62
+ const agentSkills = config.skills.split(',').map((s) => s.trim()).filter(Boolean)
63
+
64
+ const bid: BidCreateInput = {
65
+ proposedPrice: config.maxBudget || '1000000',
66
+ confidenceScore: 0.8,
67
+ proposal: `SwarmClaw agent with skills: ${agentSkills.join(', ')}. Ready to start immediately.`,
68
+ portfolioRefs: [],
69
+ }
70
+
71
+ await client.tasks.bid(taskId, bid)
72
+
73
+ log.info(TAG, `Auto-bid submitted for task ${taskId}`)
74
+ }
@@ -0,0 +1,85 @@
1
+ import assert from 'node:assert/strict'
2
+ import test from 'node:test'
3
+
4
+ import { submitAutoBid } from '@/lib/server/connectors/swarmdock-bidding'
5
+ import { submitSwarmdockTaskResult } from '@/lib/server/connectors/swarmdock'
6
+
7
+ test('submitAutoBid includes empty portfolio refs for SDK compatibility', async () => {
8
+ const seen: {
9
+ taskId?: string
10
+ bid?: { proposedPrice: string; portfolioRefs: string[] }
11
+ } = {}
12
+
13
+ await submitAutoBid(
14
+ {
15
+ tasks: {
16
+ bid: async (taskId, input) => {
17
+ seen.taskId = taskId
18
+ seen.bid = {
19
+ proposedPrice: input.proposedPrice,
20
+ portfolioRefs: [...input.portfolioRefs],
21
+ }
22
+ },
23
+ },
24
+ },
25
+ 'task-123',
26
+ {
27
+ skills: 'typescript,automation',
28
+ maxBudget: '2500000',
29
+ autoDiscover: true,
30
+ },
31
+ )
32
+
33
+ assert.equal(seen.taskId, 'task-123')
34
+ assert.deepEqual(seen.bid, {
35
+ proposedPrice: '2500000',
36
+ portfolioRefs: [],
37
+ })
38
+ })
39
+
40
+ test('submitSwarmdockTaskResult includes empty files and propagates submit errors', async () => {
41
+ const seen: {
42
+ taskId?: string
43
+ payload?: { files: unknown[]; artifacts: Array<{ type: string; content: string }> }
44
+ } = {}
45
+
46
+ await submitSwarmdockTaskResult(
47
+ {
48
+ tasks: {
49
+ submit: async (taskId, input) => {
50
+ seen.taskId = taskId
51
+ seen.payload = {
52
+ files: [...input.files],
53
+ artifacts: input.artifacts.map((artifact) => ({
54
+ type: artifact.type,
55
+ content: String(artifact.content),
56
+ })),
57
+ }
58
+ },
59
+ },
60
+ },
61
+ 'task-456',
62
+ 'Result body',
63
+ )
64
+
65
+ assert.equal(seen.taskId, 'task-456')
66
+ assert.deepEqual(seen.payload, {
67
+ files: [],
68
+ artifacts: [{ type: 'text/markdown', content: 'Result body' }],
69
+ })
70
+
71
+ await assert.rejects(
72
+ submitSwarmdockTaskResult(
73
+ {
74
+ tasks: {
75
+ submit: async () => {
76
+ throw new Error('submit failed')
77
+ },
78
+ },
79
+ },
80
+ 'task-456',
81
+ 'Result body',
82
+ ),
83
+ /submit failed/,
84
+ )
85
+ })
@@ -0,0 +1,128 @@
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-swarmdock-secret-'))
12
+ try {
13
+ const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
14
+ cwd: repoRoot,
15
+ env: {
16
+ ...process.env,
17
+ CREDENTIAL_SECRET: 'test-credential-secret',
18
+ DATA_DIR: path.join(tempDir, 'data'),
19
+ WORKSPACE_DIR: path.join(tempDir, 'workspace'),
20
+ },
21
+ encoding: 'utf-8',
22
+ })
23
+ assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
24
+ const lines = (result.stdout || '')
25
+ .trim()
26
+ .split('\n')
27
+ .map((line) => line.trim())
28
+ .filter(Boolean)
29
+ const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
30
+ return JSON.parse(jsonLine || '{}')
31
+ } finally {
32
+ fs.rmSync(tempDir, { recursive: true, force: true })
33
+ }
34
+ }
35
+
36
+ test('createConnector stores SwarmDock private keys as credentials and redacts config output', () => {
37
+ const output = runWithTempDataDir(`
38
+ const storageMod = await import('./src/lib/server/storage')
39
+ const serviceMod = await import('./src/lib/server/connectors/connector-service')
40
+ const secretMod = await import('./src/lib/server/connectors/swarmdock-secret')
41
+ const storage = storageMod.default || storageMod
42
+ const service = serviceMod.default || serviceMod
43
+ const secret = secretMod.default || secretMod
44
+
45
+ const created = service.createConnector({
46
+ name: 'SwarmDock Worker',
47
+ platform: 'swarmdock',
48
+ config: {
49
+ apiUrl: 'https://api.swarmdock.example',
50
+ walletAddress: '0x000000000000000000000000000000000000dEaD',
51
+ privateKey: 'legacy-private-key',
52
+ },
53
+ })
54
+
55
+ const stored = storage.loadConnectors()[created.id]
56
+ const credentials = storage.loadCredentials()
57
+ const credential = stored?.credentialId ? credentials[stored.credentialId] : null
58
+ const redacted = secret.redactConnectorSecrets({
59
+ ...stored,
60
+ config: {
61
+ ...(stored?.config || {}),
62
+ privateKey: 'should-not-leak',
63
+ },
64
+ })
65
+
66
+ console.log(JSON.stringify({
67
+ credentialId: stored?.credentialId || null,
68
+ storedHasPrivateKey: Boolean(stored?.config && Object.prototype.hasOwnProperty.call(stored.config, 'privateKey')),
69
+ credentialProvider: credential?.provider || null,
70
+ credentialName: credential?.name || null,
71
+ redactedHasPrivateKey: Boolean(redacted?.config && Object.prototype.hasOwnProperty.call(redacted.config, 'privateKey')),
72
+ }))
73
+ `)
74
+
75
+ assert.match(String(output.credentialId || ''), /^cred_/)
76
+ assert.equal(output.storedHasPrivateKey, false)
77
+ assert.equal(output.credentialProvider, 'swarmdock')
78
+ assert.match(String(output.credentialName || ''), /SwarmDock Identity Key/)
79
+ assert.equal(output.redactedHasPrivateKey, false)
80
+ })
81
+
82
+ test('ensureSwarmdockConnectorCredential migrates stored legacy config keys into credentials', () => {
83
+ const output = runWithTempDataDir(`
84
+ const storageMod = await import('./src/lib/server/storage')
85
+ const repoMod = await import('./src/lib/server/connectors/connector-repository')
86
+ const secretMod = await import('./src/lib/server/connectors/swarmdock-secret')
87
+ const storage = storageMod.default || storageMod
88
+ const repo = repoMod.default || repoMod
89
+ const secret = secretMod.default || secretMod
90
+
91
+ storage.saveConnectors({
92
+ conn_legacy: {
93
+ id: 'conn_legacy',
94
+ name: 'Legacy SwarmDock',
95
+ platform: 'swarmdock',
96
+ agentId: null,
97
+ chatroomId: null,
98
+ credentialId: null,
99
+ config: {
100
+ walletAddress: '0x000000000000000000000000000000000000dEaD',
101
+ privateKey: 'legacy-private-key',
102
+ },
103
+ isEnabled: false,
104
+ status: 'stopped',
105
+ lastError: null,
106
+ createdAt: 1,
107
+ updatedAt: 1,
108
+ },
109
+ })
110
+
111
+ const prepared = secret.ensureSwarmdockConnectorCredential(repo.loadConnector('conn_legacy'))
112
+ const migrated = repo.loadConnector('conn_legacy')
113
+ const credentials = storage.loadCredentials()
114
+ const credential = migrated?.credentialId ? credentials[migrated.credentialId] : null
115
+
116
+ console.log(JSON.stringify({
117
+ fallbackPrivateKey: prepared.fallbackPrivateKey,
118
+ migratedCredentialId: migrated?.credentialId || null,
119
+ migratedHasPrivateKey: Boolean(migrated?.config && Object.prototype.hasOwnProperty.call(migrated.config, 'privateKey')),
120
+ credentialProvider: credential?.provider || null,
121
+ }))
122
+ `)
123
+
124
+ assert.equal(output.fallbackPrivateKey, null)
125
+ assert.match(String(output.migratedCredentialId || ''), /^cred_/)
126
+ assert.equal(output.migratedHasPrivateKey, false)
127
+ assert.equal(output.credentialProvider, 'swarmdock')
128
+ })
@@ -0,0 +1,152 @@
1
+ import type { Connector } from '@/types'
2
+ import { createCredentialRecord, resolveCredentialSecret } from '@/lib/server/credentials/credential-service'
3
+ import { upsertConnector } from './connector-repository'
4
+ import { notify } from '@/lib/server/ws-hub'
5
+
6
+ export const SWARMMDOCK_CREDENTIAL_PROVIDER = 'swarmdock'
7
+
8
+ function clean(value: unknown): string {
9
+ return typeof value === 'string' ? value.trim() : ''
10
+ }
11
+
12
+ function cloneConfig(config: Record<string, string> | null | undefined): Record<string, string> {
13
+ return config ? { ...config } : {}
14
+ }
15
+
16
+ function getLegacyPrivateKey(config: Record<string, string> | null | undefined): string {
17
+ return clean(config?.privateKey)
18
+ }
19
+
20
+ function stripLegacyPrivateKey(config: Record<string, string> | null | undefined): Record<string, string> {
21
+ const next = cloneConfig(config)
22
+ delete next.privateKey
23
+ return next
24
+ }
25
+
26
+ function buildCredentialName(connectorName: string): string {
27
+ const normalizedName = clean(connectorName)
28
+ return normalizedName ? `${normalizedName} SwarmDock Identity Key` : 'SwarmDock Identity Key'
29
+ }
30
+
31
+ function persistConnectorSecretMigration(
32
+ connector: Connector,
33
+ credentialId: string,
34
+ ): Connector {
35
+ const nextConfig = stripLegacyPrivateKey(connector.config)
36
+ const next: Connector = {
37
+ ...connector,
38
+ credentialId,
39
+ config: nextConfig,
40
+ updatedAt: Date.now(),
41
+ }
42
+ upsertConnector(next.id, next)
43
+ notify('connectors')
44
+ return next
45
+ }
46
+
47
+ export function redactConnectorSecrets<T extends Connector>(connector: T): T {
48
+ if (connector.platform !== 'swarmdock') {
49
+ return {
50
+ ...connector,
51
+ config: cloneConfig(connector.config),
52
+ }
53
+ }
54
+ return {
55
+ ...connector,
56
+ config: stripLegacyPrivateKey(connector.config),
57
+ }
58
+ }
59
+
60
+ export function prepareSwarmdockConnectorInput(params: {
61
+ platform: Connector['platform']
62
+ name: string
63
+ credentialId: string | null
64
+ config: Record<string, string> | null | undefined
65
+ }): {
66
+ credentialId: string | null
67
+ config: Record<string, string>
68
+ } {
69
+ const config = cloneConfig(params.config)
70
+ if (params.platform !== 'swarmdock') {
71
+ return {
72
+ credentialId: clean(params.credentialId) || null,
73
+ config,
74
+ }
75
+ }
76
+
77
+ const credentialId = clean(params.credentialId)
78
+ const legacyPrivateKey = getLegacyPrivateKey(config)
79
+ if (!legacyPrivateKey) {
80
+ return {
81
+ credentialId: credentialId || null,
82
+ config: stripLegacyPrivateKey(config),
83
+ }
84
+ }
85
+
86
+ if (credentialId) {
87
+ return {
88
+ credentialId,
89
+ config: stripLegacyPrivateKey(config),
90
+ }
91
+ }
92
+
93
+ const credential = createCredentialRecord({
94
+ provider: SWARMMDOCK_CREDENTIAL_PROVIDER,
95
+ name: buildCredentialName(params.name),
96
+ apiKey: legacyPrivateKey,
97
+ })
98
+
99
+ return {
100
+ credentialId: credential.id,
101
+ config: stripLegacyPrivateKey(config),
102
+ }
103
+ }
104
+
105
+ export function ensureSwarmdockConnectorCredential(
106
+ connector: Connector,
107
+ options?: { allowMigrationFailureFallback?: boolean },
108
+ ): {
109
+ connector: Connector
110
+ fallbackPrivateKey: string | null
111
+ } {
112
+ if (connector.platform !== 'swarmdock') {
113
+ return { connector, fallbackPrivateKey: null }
114
+ }
115
+
116
+ const legacyPrivateKey = getLegacyPrivateKey(connector.config)
117
+ if (!legacyPrivateKey) {
118
+ return { connector, fallbackPrivateKey: null }
119
+ }
120
+
121
+ const configuredCredentialId = clean(connector.credentialId)
122
+ if (configuredCredentialId) {
123
+ if (resolveCredentialSecret(configuredCredentialId)) {
124
+ return {
125
+ connector: persistConnectorSecretMigration(connector, configuredCredentialId),
126
+ fallbackPrivateKey: null,
127
+ }
128
+ }
129
+ return {
130
+ connector,
131
+ fallbackPrivateKey: legacyPrivateKey,
132
+ }
133
+ }
134
+
135
+ try {
136
+ const credential = createCredentialRecord({
137
+ provider: SWARMMDOCK_CREDENTIAL_PROVIDER,
138
+ name: buildCredentialName(connector.name),
139
+ apiKey: legacyPrivateKey,
140
+ })
141
+ return {
142
+ connector: persistConnectorSecretMigration(connector, credential.id),
143
+ fallbackPrivateKey: null,
144
+ }
145
+ } catch (error) {
146
+ if (!options?.allowMigrationFailureFallback) throw error
147
+ return {
148
+ connector,
149
+ fallbackPrivateKey: legacyPrivateKey,
150
+ }
151
+ }
152
+ }
@@ -0,0 +1,119 @@
1
+ import { genId } from '@/lib/id'
2
+ import { loadTasks, saveTasks } from '@/lib/server/tasks/task-repository'
3
+ import { logActivity } from '@/lib/server/activity/activity-log'
4
+ import type { BoardTask } from '@/types/task'
5
+
6
+ interface SwarmDockTask {
7
+ id: string
8
+ requesterId: string
9
+ title: string
10
+ description: string
11
+ skillRequirements: string[]
12
+ budgetMax: string
13
+ deadline: string | null
14
+ }
15
+
16
+ /**
17
+ * Create a SwarmClaw BoardTask from a SwarmDock task assignment.
18
+ * Uses `externalSource` to link back to the SwarmDock task (same pattern as GitHub issue import).
19
+ */
20
+ export async function createBoardTaskFromAssignment(
21
+ task: SwarmDockTask,
22
+ agentId: string,
23
+ connectorId: string,
24
+ apiUrl: string,
25
+ ): Promise<string> {
26
+ const tasks = loadTasks() as Record<string, BoardTask>
27
+ const id = genId()
28
+ const now = Date.now()
29
+
30
+ const boardTask: BoardTask = {
31
+ id,
32
+ title: task.title,
33
+ description: task.description,
34
+ status: 'running',
35
+ agentId,
36
+ createdAt: now,
37
+ updatedAt: now,
38
+ startedAt: now,
39
+ lastActivityAt: now,
40
+ sourceType: 'import',
41
+ externalSource: {
42
+ source: 'swarmdock',
43
+ id: task.id,
44
+ state: 'in_progress',
45
+ url: `${apiUrl}/tasks/${task.id}`,
46
+ },
47
+ tags: task.skillRequirements,
48
+ objective: task.description,
49
+ followupConnectorId: connectorId,
50
+ }
51
+
52
+ if (task.deadline) {
53
+ boardTask.dueAt = new Date(task.deadline).getTime()
54
+ }
55
+
56
+ tasks[id] = boardTask
57
+ saveTasks(tasks)
58
+
59
+ logActivity({
60
+ entityType: 'task',
61
+ entityId: id,
62
+ action: 'created',
63
+ actor: 'system',
64
+ summary: `SwarmDock task assigned: "${task.title}"`,
65
+ })
66
+
67
+ return id
68
+ }
69
+
70
+ /**
71
+ * Update a SwarmClaw BoardTask based on a SwarmDock SSE event.
72
+ */
73
+ export async function updateBoardTaskFromEvent(
74
+ swarmdockTaskId: string,
75
+ eventType: string,
76
+ ): Promise<void> {
77
+ const tasks = loadTasks() as Record<string, BoardTask>
78
+ const boardTask = Object.values(tasks).find(
79
+ (t) => t.externalSource?.source === 'swarmdock' && t.externalSource.id === swarmdockTaskId,
80
+ )
81
+ if (!boardTask) return
82
+
83
+ const now = Date.now()
84
+
85
+ switch (eventType) {
86
+ case 'task.completed':
87
+ boardTask.status = 'completed'
88
+ boardTask.completedAt = now
89
+ boardTask.checkoutRunId = null
90
+ break
91
+ case 'task.submitted':
92
+ // Results submitted, waiting for approval on SwarmDock
93
+ if (boardTask.externalSource) boardTask.externalSource.state = 'review'
94
+ break
95
+ case 'task.cancelled':
96
+ boardTask.status = 'cancelled'
97
+ boardTask.checkoutRunId = null
98
+ break
99
+ case 'task.failed':
100
+ boardTask.status = 'failed'
101
+ boardTask.checkoutRunId = null
102
+ break
103
+ }
104
+
105
+ boardTask.updatedAt = now
106
+ boardTask.lastActivityAt = now
107
+ saveTasks(tasks)
108
+ }
109
+
110
+ /**
111
+ * Find a SwarmClaw BoardTask ID by its SwarmDock task ID.
112
+ */
113
+ export function findBoardTaskBySwarmdockId(swarmdockTaskId: string): string | null {
114
+ const tasks = loadTasks() as Record<string, BoardTask>
115
+ const task = Object.values(tasks).find(
116
+ (t) => t.externalSource?.source === 'swarmdock' && t.externalSource.id === swarmdockTaskId,
117
+ )
118
+ return task?.id || null
119
+ }