@swarmclawai/swarmclaw 1.2.8 → 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 (195) hide show
  1. package/README.md +30 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/agents/thread-route.test.ts +0 -1
  5. package/src/app/api/approvals/route.test.ts +6 -22
  6. package/src/app/api/connectors/route.ts +2 -2
  7. package/src/app/api/portability/export/route.ts +8 -0
  8. package/src/app/api/portability/import/route.test.ts +80 -0
  9. package/src/app/api/portability/import/route.ts +28 -0
  10. package/src/app/api/settings/route.ts +0 -2
  11. package/src/app/api/wallets/[id]/route.ts +15 -157
  12. package/src/app/api/wallets/generate/route.ts +22 -0
  13. package/src/app/api/wallets/route.test.ts +147 -0
  14. package/src/app/api/wallets/route.ts +13 -95
  15. package/src/app/autonomy/page.tsx +2 -57
  16. package/src/app/protocols/page.tsx +2 -21
  17. package/src/app/settings/page.tsx +0 -9
  18. package/src/app/wallets/page.tsx +105 -5
  19. package/src/cli/index.js +21 -33
  20. package/src/cli/spec.js +19 -30
  21. package/src/components/agents/agent-sheet.tsx +2 -40
  22. package/src/components/agents/inspector-panel.tsx +0 -83
  23. package/src/components/chat/chat-card.tsx +0 -31
  24. package/src/components/chat/message-bubble.tsx +1 -108
  25. package/src/components/connectors/connector-sheet.tsx +25 -1
  26. package/src/components/layout/sidebar-rail.tsx +6 -10
  27. package/src/components/projects/project-detail.tsx +3 -35
  28. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  29. package/src/components/projects/tabs/work-tab.tsx +7 -77
  30. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  31. package/src/components/shared/connector-platform-icon.tsx +1 -0
  32. package/src/components/tasks/task-card.tsx +4 -34
  33. package/src/components/tasks/task-sheet.tsx +6 -36
  34. package/src/components/wallets/wallet-list.tsx +150 -0
  35. package/src/lib/app/navigation.test.ts +0 -13
  36. package/src/lib/app/navigation.ts +2 -7
  37. package/src/lib/app/view-constants.ts +14 -19
  38. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  39. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  40. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  41. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  42. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  43. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  44. package/src/lib/server/approval-match.ts +0 -85
  45. package/src/lib/server/approvals.test.ts +6 -6
  46. package/src/lib/server/approvals.ts +0 -6
  47. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  48. package/src/lib/server/builtin-extensions.ts +0 -2
  49. package/src/lib/server/capability-router.test.ts +0 -2
  50. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  51. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  52. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  53. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  54. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  55. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  56. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  57. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  58. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  59. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  60. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  61. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  62. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  63. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  64. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  65. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  66. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  67. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  68. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  69. package/src/lib/server/chats/chat-session-service.ts +3 -5
  70. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  71. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  72. package/src/lib/server/connectors/connector-service.ts +39 -9
  73. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  74. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  75. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  76. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  77. package/src/lib/server/connectors/swarmdock-tasks.ts +119 -0
  78. package/src/lib/server/connectors/swarmdock.ts +255 -0
  79. package/src/lib/server/execution-brief.test.ts +2 -25
  80. package/src/lib/server/execution-brief.ts +12 -35
  81. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  82. package/src/lib/server/persistence/storage-context.ts +0 -5
  83. package/src/lib/server/portability/export.ts +109 -0
  84. package/src/lib/server/portability/import.ts +159 -0
  85. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  86. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  87. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  88. package/src/lib/server/protocols/protocol-service.ts +0 -1
  89. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  90. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  91. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  92. package/src/lib/server/protocols/protocol-types.ts +0 -2
  93. package/src/lib/server/provider-health.ts +0 -9
  94. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  95. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  96. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  97. package/src/lib/server/runtime/queue/core.ts +11 -33
  98. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  99. package/src/lib/server/runtime/scheduler.ts +0 -13
  100. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  101. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  102. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  103. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  104. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  105. package/src/lib/server/session-tools/crud.ts +0 -14
  106. package/src/lib/server/session-tools/delegate.ts +0 -4
  107. package/src/lib/server/session-tools/index.ts +0 -4
  108. package/src/lib/server/session-tools/team-context.ts +0 -3
  109. package/src/lib/server/storage-normalization.ts +8 -0
  110. package/src/lib/server/storage.ts +18 -45
  111. package/src/lib/server/tasks/task-checkout.ts +59 -0
  112. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  113. package/src/lib/server/tasks/task-route-service.ts +4 -26
  114. package/src/lib/server/tasks/task-service.ts +0 -7
  115. package/src/lib/server/tool-aliases.ts +0 -1
  116. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  117. package/src/lib/server/tool-capability-policy.ts +0 -2
  118. package/src/lib/server/tool-planning.ts +0 -12
  119. package/src/lib/server/universal-tool-access.ts +0 -1
  120. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  121. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  122. package/src/lib/server/wallets/wallet-service.ts +119 -0
  123. package/src/lib/server/working-state/extraction.ts +8 -42
  124. package/src/lib/server/working-state/normalization.ts +10 -103
  125. package/src/lib/server/working-state/service.ts +12 -21
  126. package/src/lib/strip-internal-metadata.test.ts +1 -1
  127. package/src/lib/strip-internal-metadata.ts +1 -1
  128. package/src/lib/tool-definitions.ts +0 -1
  129. package/src/lib/validation/schemas.ts +33 -2
  130. package/src/stores/slices/data-slice.ts +5 -1
  131. package/src/stores/slices/ui-slice.ts +0 -4
  132. package/src/types/agent.ts +0 -84
  133. package/src/types/app-settings.ts +0 -2
  134. package/src/types/approval.ts +0 -2
  135. package/src/types/connector.ts +1 -0
  136. package/src/types/index.ts +1 -1
  137. package/src/types/message.ts +0 -1
  138. package/src/types/misc.ts +0 -2
  139. package/src/types/protocol.ts +0 -2
  140. package/src/types/run.ts +0 -3
  141. package/src/types/session.ts +1 -51
  142. package/src/types/swarmdock.ts +29 -0
  143. package/src/types/task.ts +7 -3
  144. package/src/types/working-state.ts +2 -9
  145. package/src/views/settings/section-runtime-loop.tsx +0 -14
  146. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  147. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  148. package/src/app/api/missions/[id]/events/route.ts +0 -14
  149. package/src/app/api/missions/[id]/route.ts +0 -10
  150. package/src/app/api/missions/route.test.ts +0 -244
  151. package/src/app/api/missions/route.ts +0 -57
  152. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  153. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  154. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  155. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  156. package/src/app/missions/[id]/page.tsx +0 -3
  157. package/src/app/missions/page.tsx +0 -685
  158. package/src/components/canvas/canvas-panel.tsx +0 -267
  159. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  160. package/src/components/wallets/wallet-panel.tsx +0 -1010
  161. package/src/components/wallets/wallet-section.tsx +0 -260
  162. package/src/features/missions/queries.ts +0 -23
  163. package/src/lib/canvas-content.test.ts +0 -360
  164. package/src/lib/canvas-content.ts +0 -198
  165. package/src/lib/server/canvas-content.test.ts +0 -32
  166. package/src/lib/server/canvas-content.ts +0 -6
  167. package/src/lib/server/ethereum.ts +0 -591
  168. package/src/lib/server/evm-swap.ts +0 -476
  169. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  170. package/src/lib/server/missions/mission-intent.ts +0 -569
  171. package/src/lib/server/missions/mission-repository.ts +0 -74
  172. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  173. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  174. package/src/lib/server/missions/mission-service/context.ts +0 -4
  175. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  176. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  177. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  178. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  179. package/src/lib/server/missions/mission-service.test.ts +0 -888
  180. package/src/lib/server/missions/mission-service.ts +0 -6
  181. package/src/lib/server/session-tools/canvas.ts +0 -105
  182. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  183. package/src/lib/server/session-tools/wallet.ts +0 -1287
  184. package/src/lib/server/solana.ts +0 -327
  185. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  186. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  187. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  188. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  189. package/src/lib/server/wallet/wallet-service.ts +0 -225
  190. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  191. package/src/lib/wallet/wallet-transactions.ts +0 -43
  192. package/src/lib/wallet/wallet.test.ts +0 -333
  193. package/src/lib/wallet/wallet.ts +0 -183
  194. package/src/types/mission.ts +0 -185
  195. package/src/views/settings/section-wallets.tsx +0 -35
@@ -46,36 +46,6 @@ describe('stream-continuation', () => {
46
46
  })
47
47
  })
48
48
 
49
- // ---- looksLikeExternalWalletTask ----
50
- describe('looksLikeExternalWalletTask', () => {
51
- it('matches wallet keywords', () => {
52
- assert.equal(mod.looksLikeExternalWalletTask('swap 100 USDC on Arbitrum'), true)
53
- assert.equal(mod.looksLikeExternalWalletTask('check my wallet balance'), true)
54
- assert.equal(mod.looksLikeExternalWalletTask('trade ETH for SOL'), true)
55
- })
56
-
57
- it('returns false for empty/unrelated text', () => {
58
- assert.equal(mod.looksLikeExternalWalletTask(''), false)
59
- assert.equal(mod.looksLikeExternalWalletTask('write me a poem about cats'), false)
60
- })
61
- })
62
-
63
- // ---- looksLikeBoundedExternalExecutionTask ----
64
- describe('looksLikeBoundedExternalExecutionTask', () => {
65
- it('requires wallet keywords + action verbs', () => {
66
- assert.equal(mod.looksLikeBoundedExternalExecutionTask('swap 100 USDC on Arbitrum'), true)
67
- assert.equal(mod.looksLikeBoundedExternalExecutionTask('buy ETH on the exchange'), true)
68
- })
69
-
70
- it('returns false without action verbs', () => {
71
- assert.equal(mod.looksLikeBoundedExternalExecutionTask('check my wallet balance'), false)
72
- })
73
-
74
- it('returns false for non-wallet text', () => {
75
- assert.equal(mod.looksLikeBoundedExternalExecutionTask('execute the test suite'), false)
76
- })
77
- })
78
-
79
49
  // ---- looksLikeOpenEndedDeliverableTask ----
80
50
  describe('looksLikeOpenEndedDeliverableTask', () => {
81
51
  it('matches deliverable patterns', () => {
@@ -112,24 +82,6 @@ describe('stream-continuation', () => {
112
82
  })
113
83
  })
114
84
 
115
- // ---- hasStateChangingWalletEvidence ----
116
- describe('hasStateChangingWalletEvidence', () => {
117
- it('detects send_transaction action in wallet_tool', () => {
118
- const events = [{ name: 'wallet_tool', input: '{"action":"send_transaction"}', output: '{}' }]
119
- assert.equal(mod.hasStateChangingWalletEvidence(events), true)
120
- })
121
-
122
- it('returns false for non-wallet tools', () => {
123
- const events = [{ name: 'web', input: 'search', output: 'results' }]
124
- assert.equal(mod.hasStateChangingWalletEvidence(events), false)
125
- })
126
-
127
- it('returns false for read-only wallet actions', () => {
128
- const events = [{ name: 'wallet_tool', input: '{"action":"balance"}', output: '{}' }]
129
- assert.equal(mod.hasStateChangingWalletEvidence(events), false)
130
- })
131
- })
132
-
133
85
  // ---- countExternalExecutionResearchSteps ----
134
86
  describe('countExternalExecutionResearchSteps', () => {
135
87
  it('counts http/web/browser tools', () => {
@@ -141,12 +93,12 @@ describe('stream-continuation', () => {
141
93
  assert.equal(mod.countExternalExecutionResearchSteps(events), 2)
142
94
  })
143
95
 
144
- it('counts read-only wallet actions', () => {
96
+ it('does not count non-research tools', () => {
145
97
  const events = [
146
- { name: 'wallet_tool', input: '{"action":"balance"}', output: '{}' },
147
- { name: 'wallet_tool', input: '{"action":"send_transaction"}', output: '{}' },
98
+ { name: 'shell', input: 'ls', output: 'files' },
99
+ { name: 'files', input: 'read', output: 'content' },
148
100
  ]
149
- assert.equal(mod.countExternalExecutionResearchSteps(events), 1)
101
+ assert.equal(mod.countExternalExecutionResearchSteps(events), 0)
150
102
  })
151
103
  })
152
104
 
@@ -194,25 +194,9 @@ function getRequestedArtifactStatus(params: {
194
194
  // Tool evidence analysis
195
195
  // ---------------------------------------------------------------------------
196
196
 
197
- export function hasStateChangingWalletEvidence(toolEvents: MessageToolEvent[]): boolean {
198
- return toolEvents.some((event) => {
199
- const input = `${event.input || ''}\n${event.output || ''}`
200
- return event.name === 'wallet_tool' && (
201
- /"action":"send_transaction"/.test(input)
202
- || /"action":"send"/.test(input)
203
- || /"action":"sign_transaction"/.test(input)
204
- || /"type":"extension_wallet_action_request"/.test(input)
205
- || /"type":"extension_wallet_transfer_request"/.test(input)
206
- || /"status":"broadcast"/.test(input)
207
- )
208
- })
209
- }
210
-
211
197
  export function countExternalExecutionResearchSteps(toolEvents: MessageToolEvent[]): number {
212
198
  return toolEvents.filter((event) => {
213
- if (['http_request', 'web', 'web_search', 'web_fetch', 'browser'].includes(event.name)) return true
214
- if (event.name !== 'wallet_tool') return false
215
- return /"action":"(balance|address|transactions|call_contract|encode_contract_call)"/.test(event.input || '')
199
+ return ['http_request', 'web', 'web_search', 'web_fetch', 'browser'].includes(event.name)
216
200
  }).length
217
201
  }
218
202
 
@@ -238,49 +222,24 @@ export function countDistinctExternalResearchHosts(toolEvents: MessageToolEvent[
238
222
  // Continuation decision helpers
239
223
  // ---------------------------------------------------------------------------
240
224
 
241
- export function shouldForceExternalExecutionFollowthrough(params: {
225
+ export function shouldForceExternalExecutionFollowthrough(_params: {
242
226
  userMessage: string
243
227
  finalResponse: string
244
228
  hasToolCalls: boolean
245
229
  toolEvents: MessageToolEvent[]
246
230
  classification?: MessageClassification | null
247
231
  }): boolean {
248
- const isTransactional = params.classification?.walletIntent === 'transactional'
249
- if (!isTransactional) return false
250
- if (!params.hasToolCalls || params.toolEvents.length < 4) return false
251
- if (hasStateChangingWalletEvidence(params.toolEvents)) return false
252
- const distinctHosts = countDistinctExternalResearchHosts(params.toolEvents)
253
- const trimmed = params.finalResponse.trim()
254
- if (!trimmed) return countExternalExecutionResearchSteps(params.toolEvents) >= 4 || distinctHosts >= 3
255
- if (/\b(last reversible step|exact blocker|safest next action|blocked|cannot|can't|missing capability|no-key route unavailable)\b/i.test(trimmed)) {
256
- return false
257
- }
258
- if (countExternalExecutionResearchSteps(params.toolEvents) < 4 && distinctHosts < 3) return false
259
- return /(let me|i'll|i will|trying|research|query|check|look|promising|now let me|good -|good,)/i.test(trimmed) || trimmed.length < 500
232
+ return false
260
233
  }
261
234
 
262
- export function shouldForceExternalExecutionKickoffFollowthrough(params: {
235
+ export function shouldForceExternalExecutionKickoffFollowthrough(_params: {
263
236
  userMessage: string
264
237
  finalResponse: string
265
238
  hasToolCalls: boolean
266
239
  toolEvents: MessageToolEvent[]
267
240
  classification?: MessageClassification | null
268
241
  }): boolean {
269
- const isTransactional = params.classification?.walletIntent === 'transactional'
270
- if (!isTransactional) return false
271
- if (params.hasToolCalls || params.toolEvents.length > 0) return false
272
-
273
- const trimmed = params.finalResponse.trim()
274
- if (!trimmed) return true
275
- if (/^(?:HEARTBEAT_OK|NO_MESSAGE)\b/i.test(trimmed)) return false
276
- if (/\?\s*$/.test(trimmed)) return false
277
- if (/\b(last reversible step|exact blocker|blocked|cannot|can't|missing capability|need approval|requires approval|approval boundary|requires human|ask_human|credential|authentication|login|2fa|mfa|captcha)\b/i.test(trimmed)) {
278
- return false
279
- }
280
- if (/\b(done|completed|finished|sent|broadcast|minted|purchased|bought|swapped|claimed)\b/i.test(trimmed)) {
281
- return false
282
- }
283
- return looksLikeIncompleteDeliverableResponse(trimmed) || trimmed.length < 220
242
+ return false
284
243
  }
285
244
 
286
245
  export function shouldForceDeliverableFollowthrough(params: {
@@ -454,8 +413,7 @@ function buildExternalExecutionFollowthroughPrompt(params: {
454
413
  'You are in a bounded external execution task and have already done enough research.',
455
414
  'Do not restart broad discovery. Do not ask the user for another prompt.',
456
415
  'Do not spend this continuation on more venue shopping. Use the already confirmed route unless one last fetch is strictly required to prepare execution.',
457
- 'If several venue or aggregator APIs already failed, stop searching for more venues. Either use a direct onchain read path with the available wallet tools, or state the blocker.',
458
- 'A prose approval request does not count as completion. If the next step is a sign/send/approve action, call the real wallet tool action so the runtime can create the approval request.',
416
+ 'If several venue or aggregator APIs already failed, stop searching for more venues and state the blocker.',
459
417
  'Do not mutate already confirmed token addresses, router addresses, spender addresses, or network identifiers unless newer tool evidence proves the earlier value was wrong.',
460
418
  'Within this continuation, do exactly one of the following:',
461
419
  '1. Take the next concrete execution step now using the existing tools and stop at the first approval boundary for a state-changing action.',
@@ -1,7 +1,6 @@
1
1
  import { genId } from '@/lib/id'
2
2
  import type { MailboxEnvelope } from '@/types'
3
3
  import { loadSession, patchSession } from '@/lib/server/sessions/session-repository'
4
- import { requestMissionTicksForHumanReply } from '@/lib/server/missions/mission-service'
5
4
 
6
5
  interface MailboxOptions {
7
6
  limit?: number
@@ -167,15 +166,6 @@ export function sendMailboxEnvelope(input: {
167
166
  lastActiveAt: now,
168
167
  }
169
168
  })
170
- if (envelope.type === 'human_reply') {
171
- requestMissionTicksForHumanReply({
172
- sessionId: input.toSessionId,
173
- correlationId: envelope.correlationId || null,
174
- envelopeId: envelope.id,
175
- payload: envelope.payload,
176
- fromSessionId: envelope.fromSessionId || null,
177
- })
178
- }
179
169
  import('@/lib/server/runtime/watch-jobs')
180
170
  .then(({ triggerMailboxWatchJobs }) => {
181
171
  triggerMailboxWatchJobs({ sessionId: input.toSessionId, envelope })
@@ -7,7 +7,6 @@ import { buildAgentDisabledMessage, isAgentDisabled } from '@/lib/server/agents/
7
7
  import { loadAgent } from '@/lib/server/agents/agent-repository'
8
8
  import { clearMainLoopStateForSession } from '@/lib/server/agents/main-agent-loop'
9
9
  import { applyResolvedRoute, resolvePrimaryAgentRoute } from '@/lib/server/agents/agent-runtime-config'
10
- import { enrichSessionWithMissionSummary } from '@/lib/server/missions/mission-service'
11
10
  import { cleanupSessionProcesses } from '@/lib/server/runtime/process-manager'
12
11
  import { stopActiveSessionProcess } from '@/lib/server/runtime/runtime-state'
13
12
  import {
@@ -60,7 +59,7 @@ export function listChatsForApi(): Record<string, ReturnType<typeof buildSession
60
59
  sessions[id].currentRunId = run.runningRunId || null
61
60
  }
62
61
  return Object.fromEntries(
63
- Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(enrichSessionWithMissionSummary(session))]),
62
+ Object.entries(sessions).map(([id, session]) => [id, buildSessionListSummary(session)]),
64
63
  )
65
64
  }
66
65
 
@@ -72,7 +71,7 @@ export function getChatSessionForApi(sessionId: string): Session | null {
72
71
  session.active = !!run.runningRunId
73
72
  session.queuedCount = queue.queueLength
74
73
  session.currentRunId = run.runningRunId || null
75
- return enrichSessionWithMissionSummary(session)
74
+ return session
76
75
  }
77
76
 
78
77
  export function createChatSession(input: Record<string, unknown>): ServiceResult<Session> {
@@ -285,7 +284,7 @@ export function updateChatSession(sessionId: string, updates: Record<string, unk
285
284
 
286
285
  saveSession(sessionId, original)
287
286
  notify('sessions')
288
- return enrichSessionWithMissionSummary(original)
287
+ return original
289
288
  }
290
289
 
291
290
  export function deleteChatSession(sessionId: string): boolean {
@@ -320,7 +319,6 @@ export function queueChatMessage(sessionId: string, body: Record<string, unknown
320
319
  }
321
320
  const queued = enqueueSessionRun({
322
321
  sessionId,
323
- missionId: session.missionId || null,
324
322
  message,
325
323
  imagePath,
326
324
  imageUrl,
@@ -1095,7 +1095,6 @@ If media sending fails, report the exact error and retry with a corrected path/t
1095
1095
 
1096
1096
  const queued = enqueueSessionRun({
1097
1097
  sessionId: session.id,
1098
- missionId: session.missionId || null,
1099
1098
  message: modelInputText,
1100
1099
  imagePath: firstImagePath,
1101
1100
  imageUrl: firstImageUrl,
@@ -19,6 +19,7 @@ import {
19
19
  setReconnectState,
20
20
  } from './reconnect-state'
21
21
  import { connectorRuntimeState, runningConnectors } from './runtime-state'
22
+ import { ensureSwarmdockConnectorCredential } from './swarmdock-secret'
22
23
 
23
24
  const TAG = 'connector-lifecycle'
24
25
 
@@ -70,6 +71,7 @@ export async function getPlatform(platform: string) {
70
71
  case 'googlechat': return (await import('./googlechat')).default
71
72
  case 'matrix': return (await import('./matrix')).default
72
73
  case 'email': return (await import('./email')).default
74
+ case 'swarmdock': return (await import('./swarmdock')).default
73
75
  }
74
76
 
75
77
  // 2. Check Extension-provided connectors
@@ -134,8 +136,9 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
134
136
  }
135
137
 
136
138
  const connectors = loadConnectors()
137
- const connector = connectors[connectorId] as Connector | undefined
138
- if (!connector) throw new Error('Connector not found')
139
+ const storedConnector = connectors[connectorId] as Connector | undefined
140
+ if (!storedConnector) throw new Error('Connector not found')
141
+ let connector: Connector = storedConnector
139
142
 
140
143
  // Starting a connector expresses durable intent: keep it enabled across
141
144
  // transient failures so daemon recovery and server restarts can retry it.
@@ -148,6 +151,16 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
148
151
  }
149
152
 
150
153
  try {
154
+ let swarmdockFallbackPrivateKey: string | null = null
155
+ if (connector.platform === 'swarmdock') {
156
+ const prepared = ensureSwarmdockConnectorCredential(connector, {
157
+ allowMigrationFailureFallback: true,
158
+ })
159
+ connector = prepared.connector
160
+ connectors[connectorId] = connector
161
+ swarmdockFallbackPrivateKey = prepared.fallbackPrivateKey
162
+ }
163
+
151
164
  // Resolve bot token from credential
152
165
  let botToken = ''
153
166
  if (connector.credentialId) {
@@ -164,8 +177,11 @@ async function _startConnectorImpl(connectorId: string): Promise<void> {
164
177
  if (!botToken && connector.platform === 'bluebubbles' && connector.config.password) {
165
178
  botToken = connector.config.password
166
179
  }
180
+ if (!botToken && swarmdockFallbackPrivateKey) {
181
+ botToken = swarmdockFallbackPrivateKey
182
+ }
167
183
 
168
- if (!botToken && connector.platform !== 'whatsapp' && connector.platform !== 'openclaw' && connector.platform !== 'signal' && connector.platform !== 'email') {
184
+ if (!botToken && connector.platform !== 'whatsapp' && connector.platform !== 'openclaw' && connector.platform !== 'signal' && connector.platform !== 'email' && connector.platform !== 'swarmdock') {
169
185
  throw new Error('No bot token configured')
170
186
  }
171
187
 
@@ -43,6 +43,11 @@ import type {
43
43
  } from '@/types'
44
44
  import type { ServiceResult } from '@/lib/server/service-result'
45
45
  import type { DaemonConnectorRuntimeState } from '@/lib/server/daemon/types'
46
+ import {
47
+ ensureSwarmdockConnectorCredential,
48
+ prepareSwarmdockConnectorInput,
49
+ redactConnectorSecrets,
50
+ } from './swarmdock-secret'
46
51
 
47
52
  function cloneConnector<T extends Connector>(connector: T): T {
48
53
  return {
@@ -124,35 +129,52 @@ function requireSenderId(body: Record<string, unknown>): string {
124
129
  export async function listConnectorsWithRuntime(): Promise<Record<string, Connector>> {
125
130
  await ensureDaemonProcessRunning('api/connectors:get')
126
131
  const connectors = Object.fromEntries(
127
- Object.entries(loadConnectors()).map(([id, connector]) => [id, cloneConnector(connector)]),
132
+ Object.entries(loadConnectors()).map(([id, connector]) => {
133
+ const prepared = ensureSwarmdockConnectorCredential(connector, {
134
+ allowMigrationFailureFallback: true,
135
+ })
136
+ return [id, cloneConnector(prepared.connector)]
137
+ }),
128
138
  ) as Record<string, Connector>
129
139
  const runtimeByConnector = await listDaemonConnectorRuntime()
130
140
  for (const connector of Object.values(connectors)) {
131
141
  applyRuntimeFields(connector, runtimeByConnector[connector.id] || null)
132
142
  }
133
- return connectors
143
+ return Object.fromEntries(
144
+ Object.entries(connectors).map(([id, connector]) => [id, redactConnectorSecrets(connector)]),
145
+ )
134
146
  }
135
147
 
136
148
  export async function getConnectorWithRuntime(id: string): Promise<Connector | null> {
137
149
  await ensureDaemonProcessRunning('api/connectors/[id]:get')
138
150
  const connector = loadConnector(id)
139
151
  if (!connector) return null
140
- const current = cloneConnector(connector)
141
- return applyRuntimeFields(current, await getDaemonConnectorRuntime(id))
152
+ const prepared = ensureSwarmdockConnectorCredential(connector, {
153
+ allowMigrationFailureFallback: true,
154
+ })
155
+ const current = cloneConnector(prepared.connector)
156
+ return redactConnectorSecrets(applyRuntimeFields(current, await getDaemonConnectorRuntime(id)))
142
157
  }
143
158
 
144
159
  export function createConnector(body: Record<string, unknown>): Connector {
145
160
  const id = genId()
161
+ const rawConfig = body.config && typeof body.config === 'object' && !Array.isArray(body.config)
162
+ ? body.config as Record<string, string>
163
+ : {}
164
+ const prepared = prepareSwarmdockConnectorInput({
165
+ platform: body.platform as Connector['platform'],
166
+ name: (body.name as string) || `${String(body.platform || '')} Connector`,
167
+ credentialId: (body.credentialId as string | null | undefined) || null,
168
+ config: rawConfig,
169
+ })
146
170
  const connector: Connector = {
147
171
  id,
148
172
  name: (body.name as string) || `${String(body.platform || '')} Connector`,
149
173
  platform: body.platform as Connector['platform'],
150
174
  agentId: (body.agentId as string | null | undefined) || null,
151
175
  chatroomId: (body.chatroomId as string | null | undefined) || null,
152
- credentialId: (body.credentialId as string | null | undefined) || null,
153
- config: body.config && typeof body.config === 'object' && !Array.isArray(body.config)
154
- ? body.config as Record<string, string>
155
- : {},
176
+ credentialId: prepared.credentialId,
177
+ config: prepared.config,
156
178
  isEnabled: false,
157
179
  status: 'stopped',
158
180
  lastError: null,
@@ -210,6 +232,14 @@ export async function updateConnectorFromRoute(id: string, body: Record<string,
210
232
  if (body.credentialId !== undefined) next.credentialId = typeof body.credentialId === 'string' || body.credentialId === null ? body.credentialId : next.credentialId
211
233
  if (body.config !== undefined) next.config = body.config && typeof body.config === 'object' && !Array.isArray(body.config) ? body.config as Record<string, string> : next.config
212
234
  if (body.isEnabled !== undefined) next.isEnabled = typeof body.isEnabled === 'boolean' ? body.isEnabled : next.isEnabled
235
+ const prepared = prepareSwarmdockConnectorInput({
236
+ platform: next.platform,
237
+ name: next.name,
238
+ credentialId: next.credentialId || null,
239
+ config: next.config,
240
+ })
241
+ next.credentialId = prepared.credentialId
242
+ next.config = prepared.config
213
243
  persistConnector(next)
214
244
 
215
245
  try {
@@ -235,7 +265,7 @@ export async function updateConnectorFromRoute(id: string, body: Record<string,
235
265
  }
236
266
 
237
267
  notify('connectors')
238
- return serviceOk(await getConnectorWithRuntime(id) || next)
268
+ return serviceOk(await getConnectorWithRuntime(id) || redactConnectorSecrets(next))
239
269
  }
240
270
 
241
271
  export async function deleteConnectorFromRoute(id: string): Promise<ServiceResult<{ ok: true }>> {
@@ -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
+ })