@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
@@ -0,0 +1,255 @@
1
+ import { log } from '@/lib/server/logger'
2
+ import { hmrSingleton } from '@/lib/shared-utils'
3
+ import { logActivity } from '@/lib/server/activity/activity-log'
4
+ import type { Connector, InboundMessage } from '@/types/connector'
5
+ import type { PlatformConnector, ConnectorInstance } from '@/lib/server/connectors/types'
6
+ import { createBoardTaskFromAssignment, updateBoardTaskFromEvent, findBoardTaskBySwarmdockId } from './swarmdock-tasks'
7
+ import { shouldAutoBid, submitAutoBid } from './swarmdock-bidding'
8
+ import type { TaskSubmitInput } from '@swarmdock/shared'
9
+
10
+ const TAG = 'swarmdock'
11
+
12
+ // SDK types inlined until @swarmdock/sdk is built and linked
13
+ interface SwarmDockTask {
14
+ id: string
15
+ requesterId: string
16
+ assigneeId: string | null
17
+ title: string
18
+ description: string
19
+ skillRequirements: string[]
20
+ budgetMax: string
21
+ status: string
22
+ deadline: string | null
23
+ }
24
+
25
+ interface SwarmDockSSEEvent {
26
+ type: string
27
+ data: Record<string, unknown>
28
+ timestamp: string
29
+ }
30
+
31
+ interface SwarmDockConfig {
32
+ apiUrl: string
33
+ walletAddress: string
34
+ agentDescription: string
35
+ skills: string
36
+ autoDiscover: boolean
37
+ maxBudget: string
38
+ }
39
+
40
+ function parseConfig(connector: Connector): SwarmDockConfig {
41
+ const c = connector.config || {}
42
+ return {
43
+ apiUrl: c.apiUrl || 'https://api.swarmdock.ai',
44
+ walletAddress: c.walletAddress || '',
45
+ agentDescription: c.agentDescription || connector.name || '',
46
+ skills: c.skills || '',
47
+ autoDiscover: c.autoDiscover === 'true',
48
+ maxBudget: c.maxBudget || '0',
49
+ }
50
+ }
51
+
52
+ function buildTaskPrompt(task: SwarmDockTask): string {
53
+ const lines: string[] = [
54
+ `# SwarmDock Task: ${task.title}`,
55
+ '',
56
+ task.description,
57
+ '',
58
+ `**Required Skills:** ${task.skillRequirements.join(', ')}`,
59
+ `**Budget:** ${formatUsdc(task.budgetMax)}`,
60
+ ]
61
+ if (task.deadline) lines.push(`**Deadline:** ${task.deadline}`)
62
+ lines.push('', 'Complete this task and provide your deliverables. Your response will be submitted as the task result on the SwarmDock marketplace.')
63
+ return lines.join('\n')
64
+ }
65
+
66
+ function formatUsdc(microUnits: string): string {
67
+ const cents = BigInt(microUnits)
68
+ const dollars = Number(cents) / 1_000_000
69
+ return `$${dollars.toFixed(2)} USDC`
70
+ }
71
+
72
+ export async function submitSwarmdockTaskResult(
73
+ client: { tasks: { submit: (taskId: string, input: TaskSubmitInput) => Promise<unknown> } },
74
+ swarmdockTaskId: string,
75
+ text: string,
76
+ ): Promise<void> {
77
+ const payload: TaskSubmitInput = {
78
+ artifacts: [{ type: 'text/markdown', content: text }],
79
+ files: [],
80
+ }
81
+ await client.tasks.submit(swarmdockTaskId, payload)
82
+ }
83
+
84
+ /** Runtime state: maps SwarmDock task IDs → SwarmClaw BoardTask IDs */
85
+ const taskIdMap = hmrSingleton('__swarmclaw_swarmdock_task_map__', () => new Map<string, string>())
86
+
87
+ const swarmdock: PlatformConnector = {
88
+ async start(connector, _botToken, onMessage): Promise<ConnectorInstance> {
89
+ const config = parseConfig(connector)
90
+ const connectorId = connector.id
91
+ const agentId = connector.agentId || ''
92
+ const privateKey = _botToken || ''
93
+
94
+ if (!privateKey) throw new Error('SwarmDock connector requires an Ed25519 private key credential')
95
+ if (!config.walletAddress) throw new Error('SwarmDock connector requires a Base L2 wallet address in config')
96
+
97
+ // Dynamic import of the SDK (must be built and linked first)
98
+ let SwarmDockClient: typeof import('@swarmdock/sdk').SwarmDockClient
99
+ try {
100
+ const sdk = await import('@swarmdock/sdk')
101
+ SwarmDockClient = sdk.SwarmDockClient
102
+ } catch {
103
+ throw new Error('SwarmDock SDK (@swarmdock/sdk) is not installed. Run: npm install @swarmdock/sdk')
104
+ }
105
+
106
+ const client = new SwarmDockClient({
107
+ baseUrl: config.apiUrl,
108
+ privateKey,
109
+ })
110
+
111
+ // Register agent on SwarmDock (Ed25519 challenge-response)
112
+ const skillList = config.skills
113
+ .split(',')
114
+ .map((s) => s.trim())
115
+ .filter(Boolean)
116
+ .map((skillId) => ({
117
+ skillId,
118
+ skillName: skillId.replace(/-/g, ' '),
119
+ description: `${skillId} capability`,
120
+ category: skillId,
121
+ basePrice: '1000000', // $1.00 default
122
+ }))
123
+
124
+ log.info(TAG, `Registering agent "${connector.name}" on SwarmDock at ${config.apiUrl}`)
125
+ const registration = await client.register({
126
+ displayName: connector.name,
127
+ description: config.agentDescription,
128
+ framework: 'swarmclaw',
129
+ walletAddress: config.walletAddress,
130
+ skills: skillList,
131
+ })
132
+ log.info(TAG, `Registered as ${registration.agent.did} (trust level ${registration.agent.trustLevel})`)
133
+
134
+ logActivity({
135
+ entityType: 'connector',
136
+ entityId: connectorId,
137
+ action: 'swarmdock-registered',
138
+ actor: 'system',
139
+ summary: `Agent "${connector.name}" registered on SwarmDock as ${registration.agent.did}`,
140
+ })
141
+
142
+ // Set up SSE event stream
143
+ let alive = true
144
+
145
+ const handleSSEEvent = async (event: SwarmDockSSEEvent) => {
146
+ if (!alive) return
147
+ try {
148
+ switch (event.type) {
149
+ case 'task.created': {
150
+ if (!config.autoDiscover) break
151
+ const task = event.data as unknown as SwarmDockTask
152
+ if (shouldAutoBid(task, config)) {
153
+ await submitAutoBid(client, task.id, config)
154
+ logActivity({
155
+ entityType: 'connector',
156
+ entityId: connectorId,
157
+ action: 'swarmdock-bid',
158
+ actor: 'system',
159
+ summary: `Auto-bid on SwarmDock task: "${task.title}"`,
160
+ })
161
+ }
162
+ break
163
+ }
164
+
165
+ case 'task.assigned': {
166
+ const task = event.data as unknown as SwarmDockTask
167
+ if (!task.assigneeId) break
168
+
169
+ // Signal work started on SwarmDock
170
+ try { await client.tasks.start(task.id) } catch {}
171
+
172
+ // Create a BoardTask in SwarmClaw
173
+ const boardTaskId = await createBoardTaskFromAssignment(task, agentId, connectorId, config.apiUrl)
174
+ taskIdMap.set(task.id, boardTaskId)
175
+
176
+ // Dispatch as inbound message to the assigned agent
177
+ const inbound: InboundMessage = {
178
+ platform: 'swarmdock',
179
+ channelId: `swarmdock-task:${task.id}`,
180
+ channelName: `SwarmDock: ${task.title}`,
181
+ senderId: task.requesterId,
182
+ senderName: `SwarmDock Requester`,
183
+ text: buildTaskPrompt(task),
184
+ messageId: task.id,
185
+ }
186
+ await onMessage(inbound)
187
+ break
188
+ }
189
+
190
+ case 'task.completed':
191
+ case 'task.cancelled':
192
+ case 'task.failed': {
193
+ const taskId = (event.data as Record<string, string>).taskId
194
+ if (taskId) await updateBoardTaskFromEvent(taskId, event.type)
195
+ break
196
+ }
197
+
198
+ case 'payment.released': {
199
+ const data = event.data as Record<string, string>
200
+ logActivity({
201
+ entityType: 'connector',
202
+ entityId: connectorId,
203
+ action: 'swarmdock-payment',
204
+ actor: 'system',
205
+ summary: `Payment received: ${formatUsdc(data.amount || '0')} for task ${data.taskId}`,
206
+ })
207
+ break
208
+ }
209
+ }
210
+ } catch (err) {
211
+ log.error(TAG, `Error handling SSE event ${event.type}: ${err instanceof Error ? err.message : String(err)}`)
212
+ }
213
+ }
214
+
215
+ client.events.subscribe(handleSSEEvent as (event: unknown) => void)
216
+
217
+ // Token refresh heartbeat (23h, token lasts 24h)
218
+ const heartbeatInterval = setInterval(async () => {
219
+ try {
220
+ await client.heartbeat()
221
+ log.debug(TAG, 'SwarmDock token refreshed')
222
+ } catch (err) {
223
+ log.error(TAG, `SwarmDock heartbeat failed: ${err instanceof Error ? err.message : String(err)}`)
224
+ }
225
+ }, 23 * 60 * 60 * 1000)
226
+
227
+ return {
228
+ connector,
229
+
230
+ stop: async () => {
231
+ alive = false
232
+ clearInterval(heartbeatInterval)
233
+ client.events.unsubscribe()
234
+ log.info(TAG, 'SwarmDock connector stopped')
235
+ },
236
+
237
+ sendMessage: async (channelId: string, text: string) => {
238
+ // channelId format: "swarmdock-task:{taskId}"
239
+ const swarmdockTaskId = channelId.replace('swarmdock-task:', '')
240
+ if (!swarmdockTaskId) return
241
+
242
+ await submitSwarmdockTaskResult(client, swarmdockTaskId, text)
243
+ log.info(TAG, `Submitted results for SwarmDock task ${swarmdockTaskId}`)
244
+
245
+ if (findBoardTaskBySwarmdockId(swarmdockTaskId)) {
246
+ await updateBoardTaskFromEvent(swarmdockTaskId, 'task.submitted')
247
+ }
248
+
249
+ return { messageId: swarmdockTaskId }
250
+ },
251
+ }
252
+ },
253
+ }
254
+
255
+ export default swarmdock
@@ -1,6 +1,7 @@
1
1
  import { log } from '@/lib/server/logger'
2
2
  import type { PlatformConnector, ConnectorInstance, InboundMessage } from './types'
3
3
  import { resolveConnectorIngressReply } from './ingress-delivery'
4
+ import { errorMessage } from '@/lib/shared-utils'
4
5
 
5
6
  const TAG = 'teams'
6
7
 
@@ -51,8 +52,8 @@ const teams: PlatformConnector = {
51
52
  const reply = await resolveConnectorIngressReply(onMessage, inbound)
52
53
  if (!reply) return
53
54
  await context.sendActivity(reply.visibleText)
54
- } catch (err: any) {
55
- log.error(TAG, 'Error handling message:', err.message)
55
+ } catch (err: unknown) {
56
+ log.error(TAG, 'Error handling message:', errorMessage(err))
56
57
  try {
57
58
  await context.sendActivity('Sorry, I encountered an error processing your message.')
58
59
  } catch { /* ignore */ }
@@ -127,8 +127,8 @@ const telegram: PlatformConnector = {
127
127
  mimeType: m.mimeType,
128
128
  })
129
129
  if (stored) media.push(stored)
130
- } catch (err: any) {
131
- log.warn(TAG, `Failed to fetch media ${m.fileId}:`, err?.message || String(err))
130
+ } catch (err: unknown) {
131
+ log.warn(TAG, `Failed to fetch media ${m.fileId}:`, errorMessage(err))
132
132
  media.push({
133
133
  type: m.type,
134
134
  fileName: m.fileName,
@@ -177,8 +177,8 @@ const telegram: PlatformConnector = {
177
177
  return String(sent.message_id)
178
178
  },
179
179
  })
180
- } catch (err: any) {
181
- log.error(TAG, 'Error handling message:', err.message)
180
+ } catch (err: unknown) {
181
+ log.error(TAG, 'Error handling message:', errorMessage(err))
182
182
  try {
183
183
  await ctx.reply('Sorry, I encountered an error processing your message.')
184
184
  } catch { /* ignore */ }
@@ -714,8 +714,8 @@ const whatsapp: PlatformConnector = {
714
714
  fileName: mediaCandidate.payload?.fileName || undefined,
715
715
  })
716
716
  media.push(saved)
717
- } catch (err: any) {
718
- log.error(TAG, `Failed to decode media: ${err?.message || String(err)}`)
717
+ } catch (err: unknown) {
718
+ log.error(TAG, `Failed to decode media: ${errorMessage(err)}`)
719
719
  media.push({
720
720
  type: mediaCandidate.kind,
721
721
  fileName: mediaCandidate.payload?.fileName || undefined,
@@ -25,6 +25,7 @@ import type {
25
25
  } from '@/lib/server/daemon/types'
26
26
  import { DATA_DIR } from '@/lib/server/data-dir'
27
27
  import { loadEstopState } from '@/lib/server/runtime/estop'
28
+ import { getDaemonStatus } from '@/lib/server/runtime/daemon-state/core'
28
29
  import { daemonAutostartEnvEnabled } from '@/lib/server/runtime/daemon-policy'
29
30
  import {
30
31
  releaseRuntimeLock,
@@ -344,6 +345,12 @@ export async function ensureDaemonProcessRunning(
344
345
  source: string,
345
346
  opts?: { manualStart?: boolean },
346
347
  ): Promise<boolean> {
348
+ // In dev mode, the daemon may already be running in-process (same Next.js server)
349
+ // without a daemon-admin.json file. Check in-process state first to avoid spawning
350
+ // a subprocess that fails to acquire the already-held lease.
351
+ const inProcessStatus = getDaemonStatus()
352
+ if (inProcessStatus.running) return false
353
+
347
354
  const manualStart = opts?.manualStart === true
348
355
  const record = loadDaemonStatusRecord()
349
356
  if (loadEstopState().level !== 'none') return false
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
3
 
4
4
  import { buildExecutionBrief, buildExecutionBriefContextBlock, serializeExecutionBriefForDelegation } from './execution-brief'
5
- import type { Mission, Session, SessionWorkingState } from '@/types'
5
+ import type { Session, SessionWorkingState } from '@/types'
6
6
 
7
7
  test('buildExecutionBrief prefers working state and folds in mission and run-context fallback data', () => {
8
8
  const session = {
@@ -31,25 +31,8 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
31
31
  },
32
32
  } satisfies Partial<Session> as Session
33
33
 
34
- const mission = {
35
- id: 'm1',
36
- source: 'chat',
37
- objective: 'Ship the release fix',
38
- status: 'active',
39
- phase: 'executing',
40
- currentStep: 'Verify staging auth',
41
- successCriteria: ['Restore staging deploys'],
42
- waitState: {
43
- kind: 'approval',
44
- reason: 'Waiting for deploy approval.',
45
- },
46
- createdAt: 1,
47
- updatedAt: 1,
48
- } satisfies Partial<Mission> as Mission
49
-
50
34
  const workingState = {
51
35
  sessionId: 's1',
52
- missionId: 'm1',
53
36
  objective: 'Ship the release fix safely',
54
37
  summary: 'Auth mismatch isolated to staging.',
55
38
  constraints: ['Do not change the API'],
@@ -79,7 +62,7 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
79
62
  updatedAt: 1,
80
63
  } satisfies SessionWorkingState
81
64
 
82
- const brief = buildExecutionBrief({ session, mission, workingState })
65
+ const brief = buildExecutionBrief({ session, workingState })
83
66
 
84
67
  assert.equal(brief.objective, 'Ship the release fix safely')
85
68
  assert.equal(brief.summary, 'Auth mismatch isolated to staging.')
@@ -87,11 +70,9 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
87
70
  assert.equal(brief.nextAction, 'Request deploy approval')
88
71
  assert.equal(brief.plan[0]?.text, 'Request deploy approval')
89
72
  assert.equal(brief.blockers[0], 'Deploy approval is pending. | next: Request deploy approval')
90
- assert.ok(brief.blockers.some((entry) => /waiting for deploy approval/i.test(entry)))
91
73
  assert.ok(brief.facts.some((entry) => /auth mismatch isolated to staging/i.test(entry)))
92
74
  assert.ok(brief.artifacts.some((entry) => /deploy\.log/i.test(entry)))
93
75
  assert.equal(brief.parentContext, 'Parent asked for a contained fix.')
94
- assert.equal(brief.missionPhase, 'executing')
95
76
  })
96
77
 
97
78
  test('buildExecutionBriefContextBlock renders a single canonical state block', () => {
@@ -139,7 +120,6 @@ test('buildExecutionBriefContextBlock renders a single canonical state block', (
139
120
  test('serializeExecutionBriefForDelegation creates a bounded handoff summary', () => {
140
121
  const text = serializeExecutionBriefForDelegation({
141
122
  sessionId: 's3',
142
- missionId: 'm3',
143
123
  objective: 'Repair the deployment pipeline',
144
124
  summary: 'The regression is isolated to the release job.',
145
125
  status: 'blocked',
@@ -153,9 +133,6 @@ test('serializeExecutionBriefForDelegation creates a bounded handoff summary', (
153
133
  artifacts: ['/tmp/project/release.log'],
154
134
  constraints: ['Keep the current release shape.'],
155
135
  successCriteria: ['Production deploy completes'],
156
- missionStatus: 'active',
157
- missionPhase: 'executing',
158
- waitState: null,
159
136
  evidenceRefs: [],
160
137
  parentContext: 'Parent is waiting on a concise status update.',
161
138
  })
@@ -2,7 +2,6 @@ import type {
2
2
  EvidenceRef,
3
3
  ExecutionBrief,
4
4
  ExecutionBriefPlanStep,
5
- Mission,
6
5
  Session,
7
6
  SessionWorkingState,
8
7
  WorkingPlanStep,
@@ -75,14 +74,8 @@ function dedupePlan(steps: ExecutionBriefPlanStep[]): ExecutionBriefPlanStep[] {
75
74
  return out
76
75
  }
77
76
 
78
- function inferStatus(mission: Mission | null | undefined, workingState: SessionWorkingState | null): WorkingStateStatus {
77
+ function inferStatus(workingState: SessionWorkingState | null): WorkingStateStatus {
79
78
  if (workingState?.status) return workingState.status
80
- if (!mission) return 'idle'
81
- if (mission.status === 'completed') return 'completed'
82
- if (mission.status === 'waiting') return 'waiting'
83
- if (mission.status === 'failed' || mission.phase === 'failed') return 'blocked'
84
- if (mission.waitState) return 'waiting'
85
- if (mission.phase === 'executing' || mission.phase === 'dispatching' || mission.phase === 'verifying') return 'progress'
86
79
  return 'idle'
87
80
  }
88
81
 
@@ -121,15 +114,14 @@ function buildFacts(workingState: SessionWorkingState | null, session: Session |
121
114
  return uniqueStrings([...activeFacts, ...runContextFacts], MAX_FACTS, 240)
122
115
  }
123
116
 
124
- function buildBlockers(workingState: SessionWorkingState | null, mission: Mission | null | undefined, session: Session | null): string[] {
117
+ function buildBlockers(workingState: SessionWorkingState | null, session: Session | null): string[] {
125
118
  const activeBlockers = workingState
126
119
  ? workingState.blockers
127
120
  .filter((blocker) => blocker.status === 'active')
128
121
  .map((blocker) => blocker.nextAction ? `${blocker.summary} | next: ${blocker.nextAction}` : blocker.summary)
129
122
  : []
130
- const missionBlockers = mission?.waitState?.reason ? [cleanText(mission.waitState.reason, 280)] : []
131
123
  const runContextBlockers = session?.runContext ? ensureRunContext(session.runContext).blockers : []
132
- return uniqueStrings([...activeBlockers, ...missionBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
124
+ return uniqueStrings([...activeBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
133
125
  }
134
126
 
135
127
  function buildArtifacts(workingState: SessionWorkingState | null): string[] {
@@ -147,8 +139,8 @@ function buildConstraints(workingState: SessionWorkingState | null, session: Ses
147
139
  return uniqueStrings([...(workingConstraints || []), ...(runContext?.constraints || [])], 10, 220)
148
140
  }
149
141
 
150
- function buildSuccessCriteria(workingState: SessionWorkingState | null, mission: Mission | null | undefined): string[] {
151
- return uniqueStrings([...(workingState?.successCriteria || []), ...(mission?.successCriteria || [])], 10, 220)
142
+ function buildSuccessCriteria(workingState: SessionWorkingState | null): string[] {
143
+ return uniqueStrings([...(workingState?.successCriteria || [])], 10, 220)
152
144
  }
153
145
 
154
146
  function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRef[] {
@@ -161,49 +153,40 @@ function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRe
161
153
  export function buildExecutionBrief(params: {
162
154
  sessionId?: string | null
163
155
  session?: Session | null
164
- mission?: Mission | null
156
+ mission?: null
165
157
  workingState?: SessionWorkingState | null
166
158
  }): ExecutionBrief {
167
159
  const session = params.session
168
160
  || (params.sessionId ? getSession(params.sessionId) || null : null)
169
- const mission = params.mission || null
170
161
  const workingState = params.workingState
171
- || (session?.id ? loadSessionWorkingState(session.id, { mission }) : null)
162
+ || (session?.id ? loadSessionWorkingState(session.id) : null)
172
163
  const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
173
164
  const plan = buildPlan(workingState, session)
174
165
  const nextAction = cleanText(
175
166
  workingState?.nextAction
176
- || mission?.currentStep
177
167
  || plan.find((step) => step.status === 'active')?.text,
178
168
  240,
179
169
  ) || null
180
170
 
181
171
  return {
182
172
  sessionId: session?.id || params.sessionId || null,
183
- missionId: mission?.id || workingState?.missionId || null,
184
173
  objective: cleanMultiline(
185
174
  workingState?.objective
186
- || mission?.objective
187
175
  || runContext?.objective,
188
176
  900,
189
177
  ) || null,
190
178
  summary: cleanMultiline(
191
- workingState?.summary
192
- || mission?.verifierSummary
193
- || mission?.plannerSummary,
179
+ workingState?.summary,
194
180
  700,
195
181
  ) || null,
196
- status: inferStatus(mission, workingState),
182
+ status: inferStatus(workingState),
197
183
  nextAction,
198
184
  plan,
199
- blockers: buildBlockers(workingState, mission, session),
185
+ blockers: buildBlockers(workingState, session),
200
186
  facts: buildFacts(workingState, session),
201
187
  artifacts: buildArtifacts(workingState),
202
188
  constraints: buildConstraints(workingState, session),
203
- successCriteria: buildSuccessCriteria(workingState, mission),
204
- missionStatus: mission?.status || null,
205
- missionPhase: mission?.phase || null,
206
- waitState: mission?.waitState || null,
189
+ successCriteria: buildSuccessCriteria(workingState),
207
190
  evidenceRefs: buildEvidenceRefs(workingState),
208
191
  parentContext: cleanMultiline(runContext?.parentContext, 900) || null,
209
192
  }
@@ -238,10 +221,7 @@ export function buildExecutionBriefContextBlock(
238
221
  || brief.artifacts.length > 0
239
222
  || brief.constraints.length > 0
240
223
  || brief.successCriteria.length > 0
241
- || brief.evidenceRefs.length > 0
242
- || brief.missionStatus
243
- || brief.missionPhase
244
- || brief.waitState?.reason,
224
+ || brief.evidenceRefs.length > 0,
245
225
  )
246
226
  if (!hasContent && brief.status === 'idle') return ''
247
227
  const sections = [
@@ -250,9 +230,6 @@ export function buildExecutionBriefContextBlock(
250
230
  brief.objective ? `Objective: ${brief.objective}` : '',
251
231
  brief.summary ? `Summary: ${brief.summary}` : '',
252
232
  `Status: ${brief.status}`,
253
- brief.missionStatus ? `Mission status: ${brief.missionStatus}` : '',
254
- brief.missionPhase ? `Mission phase: ${brief.missionPhase}` : '',
255
- brief.waitState?.reason ? `Waiting reason: ${cleanText(brief.waitState.reason, 280)}` : '',
256
233
  brief.nextAction ? `Next action: ${brief.nextAction}` : '',
257
234
  brief.successCriteria.length > 0 ? `Success criteria: ${brief.successCriteria.join(' | ')}` : '',
258
235
  brief.constraints.length > 0 ? `Constraints: ${brief.constraints.join(' | ')}` : '',
@@ -238,7 +238,6 @@ export function enqueueTaskAttemptExecution(
238
238
  const run: SessionRunRecord = {
239
239
  id: executionId,
240
240
  sessionId: input.sessionId,
241
- missionId: input.task.missionId || null,
242
241
  kind: 'task_attempt',
243
242
  ownerType: 'task',
244
243
  ownerId: input.task.id,
@@ -4,6 +4,7 @@ import { genId } from '@/lib/id'
4
4
  import { normalizeOpenClawEndpoint } from '@/lib/openclaw/openclaw-endpoint'
5
5
  import { listAgents, saveAgentMany } from '@/lib/server/agents/agent-repository'
6
6
  import { getGatewayProfiles } from '@/lib/server/agents/agent-runtime-config'
7
+ import { deleteCredentialRecord } from '@/lib/server/credentials/credential-service'
7
8
  import {
8
9
  loadGatewayProfile,
9
10
  loadGatewayProfiles,
@@ -161,7 +162,9 @@ export function updateGatewayProfile(id: string, input: Record<string, unknown>)
161
162
 
162
163
  export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
163
164
  const gateways = loadGatewayProfiles()
164
- if (!gateways[id]) return false
165
+ const deleted = gateways[id]
166
+ if (!deleted) return false
167
+ const orphanCredentialId = deleted.credentialId || null
165
168
  delete gateways[id]
166
169
  saveGatewayProfiles(gateways)
167
170
 
@@ -195,6 +198,21 @@ export function deleteGatewayProfileAndDetachAgents(id: string): boolean {
195
198
  }
196
199
 
197
200
  if (changed.length > 0) saveAgentMany(changed)
201
+
202
+ // Clean up orphaned credential if no other gateway or agent references it
203
+ if (orphanCredentialId) {
204
+ const stillReferencedByGateway = Object.values(gateways).some(
205
+ (gw) => gw && gw.credentialId === orphanCredentialId,
206
+ )
207
+ const stillReferencedByAgent = !stillReferencedByGateway && Object.values(agents).some(
208
+ (a) => a.credentialId === orphanCredentialId
209
+ || (Array.isArray(a.fallbackCredentialIds) && a.fallbackCredentialIds.includes(orphanCredentialId)),
210
+ )
211
+ if (!stillReferencedByGateway && !stillReferencedByAgent) {
212
+ deleteCredentialRecord(orphanCredentialId)
213
+ }
214
+ }
215
+
198
216
  notify('gateways')
199
217
  return true
200
218
  }
@@ -0,0 +1,70 @@
1
+ import assert from 'node:assert/strict'
2
+ import test from 'node:test'
3
+ import { runWithTempDataDir } from '@/lib/server/test-utils/run-with-temp-data-dir'
4
+
5
+ test('appendMessage notifies both generic and per-session message topics', () => {
6
+ const output = runWithTempDataDir<{
7
+ genericTopics: string[]
8
+ sessionTopics: string[]
9
+ }>(`
10
+ const { WebSocket } = await import('ws')
11
+ const storageMod = await import('@/lib/server/storage')
12
+ const repoMod = await import('@/lib/server/messages/message-repository')
13
+ const storage = storageMod.default || storageMod
14
+ const repo = repoMod.default || repoMod
15
+
16
+ storage.saveSessions({
17
+ 'sess-notify': {
18
+ id: 'sess-notify',
19
+ name: 'Notify Session',
20
+ cwd: process.env.WORKSPACE_DIR,
21
+ user: 'tester',
22
+ provider: 'openai',
23
+ model: 'gpt-5',
24
+ claudeSessionId: null,
25
+ codexThreadId: null,
26
+ opencodeSessionId: null,
27
+ delegateResumeIds: { claudeCode: null, codex: null, opencode: null, gemini: null },
28
+ messages: [],
29
+ createdAt: Date.now(),
30
+ lastActiveAt: Date.now(),
31
+ },
32
+ })
33
+
34
+ const genericPayloads = []
35
+ const sessionPayloads = []
36
+ globalThis.__swarmclaw_ws__ = {
37
+ wss: null,
38
+ clients: new Set([
39
+ {
40
+ ws: {
41
+ readyState: WebSocket.OPEN,
42
+ send(payload) { genericPayloads.push(JSON.parse(payload)) },
43
+ },
44
+ topics: new Set(['messages']),
45
+ },
46
+ {
47
+ ws: {
48
+ readyState: WebSocket.OPEN,
49
+ send(payload) { sessionPayloads.push(JSON.parse(payload)) },
50
+ },
51
+ topics: new Set(['messages:sess-notify']),
52
+ },
53
+ ]),
54
+ }
55
+
56
+ repo.appendMessage('sess-notify', {
57
+ role: 'user',
58
+ text: 'hello',
59
+ time: 1,
60
+ })
61
+
62
+ console.log(JSON.stringify({
63
+ genericTopics: genericPayloads.map((payload) => payload.topic),
64
+ sessionTopics: sessionPayloads.map((payload) => payload.topic),
65
+ }))
66
+ `, { prefix: 'swarmclaw-message-repo-notify-' })
67
+
68
+ assert.deepEqual(output.genericTopics, ['messages'])
69
+ assert.deepEqual(output.sessionTopics, ['messages:sess-notify'])
70
+ })