@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
@@ -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
+ }
@@ -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