@swarmclawai/swarmclaw 1.2.8 → 1.3.0
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.
- package/README.md +39 -6
- package/package.json +2 -2
- package/src/app/agents/[id]/page.tsx +1 -18
- package/src/app/api/activity/route.ts +9 -23
- package/src/app/api/agents/route.ts +17 -1
- package/src/app/api/agents/thread-route.test.ts +0 -1
- package/src/app/api/approvals/route.test.ts +6 -22
- package/src/app/api/approvals/route.ts +13 -5
- package/src/app/api/connectors/route.ts +2 -2
- package/src/app/api/credentials/[id]/route.ts +2 -0
- package/src/app/api/credentials/route.ts +4 -1
- package/src/app/api/goals/[id]/route.ts +28 -0
- package/src/app/api/goals/route.ts +33 -0
- package/src/app/api/portability/export/route.ts +8 -0
- package/src/app/api/portability/import/route.test.ts +80 -0
- package/src/app/api/portability/import/route.ts +28 -0
- package/src/app/api/protocols/templates/[id]/route.ts +2 -1
- package/src/app/api/protocols/templates/route.ts +2 -1
- package/src/app/api/settings/route.ts +13 -2
- package/src/app/api/wallets/[id]/route.ts +15 -157
- package/src/app/api/wallets/generate/route.ts +22 -0
- package/src/app/api/wallets/route.test.ts +147 -0
- package/src/app/api/wallets/route.ts +13 -95
- package/src/app/autonomy/page.tsx +2 -57
- package/src/app/home/page.tsx +3 -0
- package/src/app/protocols/page.tsx +2 -21
- package/src/app/settings/page.tsx +0 -9
- package/src/app/wallets/page.tsx +105 -5
- package/src/cli/index.js +32 -33
- package/src/cli/spec.js +26 -27
- package/src/components/agents/agent-sheet.tsx +2 -40
- package/src/components/agents/inspector-panel.tsx +0 -83
- package/src/components/chat/chat-card.tsx +0 -31
- package/src/components/chat/message-bubble.tsx +1 -108
- package/src/components/connectors/connector-sheet.tsx +25 -1
- package/src/components/layout/sidebar-rail.tsx +6 -10
- package/src/components/projects/project-detail.tsx +3 -35
- package/src/components/projects/tabs/overview-tab.tsx +3 -59
- package/src/components/projects/tabs/work-tab.tsx +7 -77
- package/src/components/protocols/structured-session-launcher.tsx +1 -22
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/tasks/task-card.tsx +4 -34
- package/src/components/tasks/task-sheet.tsx +6 -36
- package/src/components/wallets/wallet-list.tsx +150 -0
- package/src/lib/app/navigation.test.ts +0 -13
- package/src/lib/app/navigation.ts +2 -7
- package/src/lib/app/view-constants.ts +14 -19
- package/src/lib/server/activity/activity-log.ts +16 -1
- package/src/lib/server/agents/agent-service.ts +24 -11
- package/src/lib/server/agents/agent-thread-session.ts +0 -1
- package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
- package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
- package/src/lib/server/agents/delegation-jobs.ts +0 -25
- package/src/lib/server/agents/main-agent-loop.ts +1 -49
- package/src/lib/server/agents/subagent-runtime.ts +0 -1
- package/src/lib/server/approval-match.ts +14 -85
- package/src/lib/server/approvals/approval-hooks.ts +81 -0
- package/src/lib/server/approvals.test.ts +6 -6
- package/src/lib/server/approvals.ts +11 -6
- package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
- package/src/lib/server/builtin-extensions.ts +0 -2
- package/src/lib/server/capability-router.test.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
- package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
- package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
- package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
- package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
- package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
- package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
- package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
- package/src/lib/server/chat-execution/message-classifier.ts +1 -16
- package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
- package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
- package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
- package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
- package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
- package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
- package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
- package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
- package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
- package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
- package/src/lib/server/chats/chat-session-service.ts +3 -5
- package/src/lib/server/connectors/connector-inbound.ts +0 -1
- package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
- package/src/lib/server/connectors/connector-service.ts +39 -9
- package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
- package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
- package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
- package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
- package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
- package/src/lib/server/connectors/swarmdock.ts +285 -0
- package/src/lib/server/execution-brief.test.ts +2 -25
- package/src/lib/server/execution-brief.ts +30 -35
- package/src/lib/server/execution-engine/task-attempt.ts +0 -1
- package/src/lib/server/goals/goal-repository.ts +19 -0
- package/src/lib/server/goals/goal-service.ts +143 -0
- package/src/lib/server/persistence/storage-context.ts +0 -5
- package/src/lib/server/portability/export.ts +109 -0
- package/src/lib/server/portability/import.ts +159 -0
- package/src/lib/server/protocols/protocol-normalization.ts +0 -4
- package/src/lib/server/protocols/protocol-queries.ts +0 -6
- package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
- package/src/lib/server/protocols/protocol-service.ts +0 -1
- package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
- package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
- package/src/lib/server/protocols/protocol-swarm.ts +0 -2
- package/src/lib/server/protocols/protocol-types.ts +0 -2
- package/src/lib/server/provider-health.ts +0 -9
- package/src/lib/server/runtime/daemon-state/core.ts +0 -9
- package/src/lib/server/runtime/daemon-state.test.ts +0 -35
- package/src/lib/server/runtime/heartbeat-service.ts +3 -23
- package/src/lib/server/runtime/queue/core.ts +11 -33
- package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
- package/src/lib/server/runtime/scheduler.ts +0 -13
- package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
- package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
- package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
- package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
- package/src/lib/server/session-tools/crud.ts +0 -14
- package/src/lib/server/session-tools/delegate.ts +0 -4
- package/src/lib/server/session-tools/index.ts +0 -4
- package/src/lib/server/session-tools/team-context.ts +0 -3
- package/src/lib/server/storage-normalization.ts +13 -0
- package/src/lib/server/storage.ts +75 -45
- package/src/lib/server/tasks/task-checkout.ts +59 -0
- package/src/lib/server/tasks/task-lifecycle.ts +2 -0
- package/src/lib/server/tasks/task-route-service.ts +4 -26
- package/src/lib/server/tasks/task-service.ts +0 -7
- package/src/lib/server/tool-aliases.ts +0 -1
- package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
- package/src/lib/server/tool-capability-policy.ts +0 -2
- package/src/lib/server/tool-planning.ts +0 -12
- package/src/lib/server/universal-tool-access.ts +0 -1
- package/src/lib/server/usage/cost-rollup.ts +124 -0
- package/src/lib/server/usage/usage-repository.ts +6 -0
- package/src/lib/server/wallets/wallet-crypto.ts +33 -0
- package/src/lib/server/wallets/wallet-repository.ts +24 -0
- package/src/lib/server/wallets/wallet-service.ts +119 -0
- package/src/lib/server/working-state/extraction.ts +8 -42
- package/src/lib/server/working-state/normalization.ts +10 -103
- package/src/lib/server/working-state/service.ts +12 -21
- package/src/lib/strip-internal-metadata.test.ts +1 -1
- package/src/lib/strip-internal-metadata.ts +1 -1
- package/src/lib/tool-definitions.ts +0 -1
- package/src/lib/validation/schemas.ts +36 -32
- package/src/lib/validation/server-schemas.ts +35 -0
- package/src/stores/slices/data-slice.ts +5 -1
- package/src/stores/slices/ui-slice.ts +0 -4
- package/src/types/agent.ts +10 -84
- package/src/types/app-settings.ts +6 -2
- package/src/types/approval.ts +3 -2
- package/src/types/connector.ts +1 -0
- package/src/types/goal.ts +30 -0
- package/src/types/index.ts +2 -1
- package/src/types/message.ts +0 -1
- package/src/types/misc.ts +2 -4
- package/src/types/protocol.ts +0 -2
- package/src/types/run.ts +0 -3
- package/src/types/session.ts +1 -51
- package/src/types/swarmdock.ts +29 -0
- package/src/types/task.ts +9 -3
- package/src/types/working-state.ts +2 -9
- package/src/views/settings/section-runtime-loop.tsx +0 -14
- package/src/app/api/canvas/[sessionId]/route.ts +0 -35
- package/src/app/api/missions/[id]/actions/route.ts +0 -31
- package/src/app/api/missions/[id]/events/route.ts +0 -14
- package/src/app/api/missions/[id]/route.ts +0 -10
- package/src/app/api/missions/route.test.ts +0 -244
- package/src/app/api/missions/route.ts +0 -57
- package/src/app/api/wallets/[id]/approve/route.ts +0 -79
- package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
- package/src/app/api/wallets/[id]/send/route.ts +0 -113
- package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
- package/src/app/missions/[id]/page.tsx +0 -3
- package/src/app/missions/page.tsx +0 -685
- package/src/components/canvas/canvas-panel.tsx +0 -267
- package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
- package/src/components/wallets/wallet-panel.tsx +0 -1010
- package/src/components/wallets/wallet-section.tsx +0 -260
- package/src/features/missions/queries.ts +0 -23
- package/src/lib/canvas-content.test.ts +0 -360
- package/src/lib/canvas-content.ts +0 -198
- package/src/lib/server/canvas-content.test.ts +0 -32
- package/src/lib/server/canvas-content.ts +0 -6
- package/src/lib/server/ethereum.ts +0 -591
- package/src/lib/server/evm-swap.ts +0 -476
- package/src/lib/server/missions/mission-intent.test.ts +0 -63
- package/src/lib/server/missions/mission-intent.ts +0 -569
- package/src/lib/server/missions/mission-repository.ts +0 -74
- package/src/lib/server/missions/mission-service/actions.ts +0 -6
- package/src/lib/server/missions/mission-service/bindings.ts +0 -9
- package/src/lib/server/missions/mission-service/context.ts +0 -4
- package/src/lib/server/missions/mission-service/core.ts +0 -2271
- package/src/lib/server/missions/mission-service/queries.ts +0 -12
- package/src/lib/server/missions/mission-service/recovery.ts +0 -5
- package/src/lib/server/missions/mission-service/ticks.ts +0 -9
- package/src/lib/server/missions/mission-service.test.ts +0 -888
- package/src/lib/server/missions/mission-service.ts +0 -6
- package/src/lib/server/session-tools/canvas.ts +0 -105
- package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
- package/src/lib/server/session-tools/wallet.ts +0 -1287
- package/src/lib/server/solana.ts +0 -327
- package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
- package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
- package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
- package/src/lib/server/wallet/wallet-service.test.ts +0 -81
- package/src/lib/server/wallet/wallet-service.ts +0 -225
- package/src/lib/wallet/wallet-transactions.test.ts +0 -75
- package/src/lib/wallet/wallet-transactions.ts +0 -43
- package/src/lib/wallet/wallet.test.ts +0 -333
- package/src/lib/wallet/wallet.ts +0 -183
- package/src/types/mission.ts +0 -185
- package/src/views/settings/section-wallets.tsx +0 -35
|
@@ -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
|
|
138
|
-
if (!
|
|
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]) =>
|
|
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
|
|
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
|
|
141
|
-
|
|
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:
|
|
153
|
-
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
|
+
})
|
|
@@ -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
|
+
}
|