@swarmclawai/swarmclaw 1.3.0 → 1.3.1
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
CHANGED
|
@@ -204,6 +204,14 @@ Read the full setup guide in [`SWARMDOCK.md`](./SWARMDOCK.md), browse the public
|
|
|
204
204
|
|
|
205
205
|
## Release Notes
|
|
206
206
|
|
|
207
|
+
### v1.3.1 Highlights
|
|
208
|
+
|
|
209
|
+
- **SwarmDock SDK v0.2.3**: upgraded marketplace integration with typed error handling, escrow state tracking, task invitation support for private tasks, and required example prompts for skill registration.
|
|
210
|
+
- **SDK error resilience**: registration now gracefully handles already-registered agents by falling back to authentication; heartbeat catches expired tokens and re-authenticates automatically.
|
|
211
|
+
- **Escrow event tracking**: new `escrow.releasing`, `escrow.refunding`, `escrow.release_failed`, and `escrow.refund_failed` SSE events are logged as activity entries, with failure events surfaced as incidents.
|
|
212
|
+
- **Private task invitations**: when a SwarmDock task invites this agent directly, auto-discovery now evaluates it alongside public `task.created` events.
|
|
213
|
+
- **SDK type imports**: replaced inlined SwarmDock type stubs with proper imports from `@swarmdock/shared`, eliminating type drift.
|
|
214
|
+
|
|
207
215
|
### v1.3.0 Highlights
|
|
208
216
|
|
|
209
217
|
- **SwarmDock SDK v0.2.0**: upgraded marketplace integration to handle the new task lifecycle — `review` and `disputed` states are now tracked on board tasks, skill registration supports `inputModes`/`outputModes`, task submission accepts `notes`, and connector config supports `paymentPrivateKey` for on-chain payment signing.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarmclawai/swarmclaw",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Self-hosted AI runtime for OpenClaw, delegation, autonomy, runtime skills, crypto wallets, and chat platform connectors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@multiavatar/multiavatar": "^1.0.7",
|
|
88
88
|
"@playwright/mcp": "^0.0.68",
|
|
89
89
|
"@slack/bolt": "^4.6.0",
|
|
90
|
-
"@swarmdock/sdk": "^0.2.
|
|
90
|
+
"@swarmdock/sdk": "^0.2.3",
|
|
91
91
|
"@tailwindcss/postcss": "^4",
|
|
92
92
|
"@tanstack/react-query": "^5.91.0",
|
|
93
93
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import { log } from '@/lib/server/logger'
|
|
2
|
-
import type { BidCreateInput } from '@swarmdock/shared'
|
|
2
|
+
import type { Task, BidCreateInput } from '@swarmdock/shared'
|
|
3
3
|
|
|
4
4
|
const TAG = 'swarmdock-bid'
|
|
5
5
|
|
|
6
|
-
interface SwarmDockTask {
|
|
7
|
-
id: string
|
|
8
|
-
title: string
|
|
9
|
-
skillRequirements: string[]
|
|
10
|
-
budgetMax: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
6
|
interface SwarmDockConfig {
|
|
14
7
|
skills: string
|
|
15
8
|
maxBudget: string
|
|
@@ -20,7 +13,7 @@ interface SwarmDockConfig {
|
|
|
20
13
|
* Determine if the agent should auto-bid on a discovered task.
|
|
21
14
|
* Checks skill overlap and budget limits.
|
|
22
15
|
*/
|
|
23
|
-
export function shouldAutoBid(task:
|
|
16
|
+
export function shouldAutoBid(task: Task, config: SwarmDockConfig): boolean {
|
|
24
17
|
if (!config.autoDiscover) return false
|
|
25
18
|
|
|
26
19
|
// Check budget
|
|
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
|
|
|
2
2
|
import test from 'node:test'
|
|
3
3
|
|
|
4
4
|
import { submitAutoBid } from '@/lib/server/connectors/swarmdock-bidding'
|
|
5
|
-
import { submitSwarmdockTaskResult } from '@/lib/server/connectors/swarmdock'
|
|
5
|
+
import { submitSwarmdockTaskResult, generateExamplePrompts } from '@/lib/server/connectors/swarmdock'
|
|
6
6
|
|
|
7
7
|
test('submitAutoBid includes empty portfolio refs for SDK compatibility', async () => {
|
|
8
8
|
const seen: {
|
|
@@ -37,6 +37,23 @@ test('submitAutoBid includes empty portfolio refs for SDK compatibility', async
|
|
|
37
37
|
})
|
|
38
38
|
})
|
|
39
39
|
|
|
40
|
+
test('generateExamplePrompts returns exactly 5 non-empty strings', () => {
|
|
41
|
+
const prompts = generateExamplePrompts('data-analysis')
|
|
42
|
+
assert.equal(prompts.length, 5)
|
|
43
|
+
for (const prompt of prompts) {
|
|
44
|
+
assert.equal(typeof prompt, 'string')
|
|
45
|
+
assert.ok(prompt.length > 0, 'prompt must be non-empty')
|
|
46
|
+
assert.ok(prompt.includes('data analysis'), 'prompt should include the humanized skill name')
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Single-word skill
|
|
50
|
+
const simple = generateExamplePrompts('coding')
|
|
51
|
+
assert.equal(simple.length, 5)
|
|
52
|
+
for (const prompt of simple) {
|
|
53
|
+
assert.ok(prompt.includes('coding'))
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
40
57
|
test('submitSwarmdockTaskResult includes empty files and propagates submit errors', async () => {
|
|
41
58
|
const seen: {
|
|
42
59
|
taskId?: string
|
|
@@ -2,23 +2,14 @@ import { genId } from '@/lib/id'
|
|
|
2
2
|
import { loadTasks, saveTasks } from '@/lib/server/tasks/task-repository'
|
|
3
3
|
import { logActivity } from '@/lib/server/activity/activity-log'
|
|
4
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
|
-
}
|
|
5
|
+
import type { Task } from '@swarmdock/shared'
|
|
15
6
|
|
|
16
7
|
/**
|
|
17
8
|
* Create a SwarmClaw BoardTask from a SwarmDock task assignment.
|
|
18
9
|
* Uses `externalSource` to link back to the SwarmDock task (same pattern as GitHub issue import).
|
|
19
10
|
*/
|
|
20
11
|
export async function createBoardTaskFromAssignment(
|
|
21
|
-
task:
|
|
12
|
+
task: Task,
|
|
22
13
|
agentId: string,
|
|
23
14
|
connectorId: string,
|
|
24
15
|
apiUrl: string,
|
|
@@ -5,29 +5,10 @@ import type { Connector, InboundMessage } from '@/types/connector'
|
|
|
5
5
|
import type { PlatformConnector, ConnectorInstance } from '@/lib/server/connectors/types'
|
|
6
6
|
import { createBoardTaskFromAssignment, updateBoardTaskFromEvent, findBoardTaskBySwarmdockId } from './swarmdock-tasks'
|
|
7
7
|
import { shouldAutoBid, submitAutoBid } from './swarmdock-bidding'
|
|
8
|
-
import type { TaskSubmitInput } from '@swarmdock/shared'
|
|
8
|
+
import type { Task, SSEEvent, TaskSubmitInput } from '@swarmdock/shared'
|
|
9
9
|
|
|
10
10
|
const TAG = 'swarmdock'
|
|
11
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
12
|
interface SwarmDockConfig {
|
|
32
13
|
apiUrl: string
|
|
33
14
|
walletAddress: string
|
|
@@ -51,7 +32,7 @@ function parseConfig(connector: Connector): SwarmDockConfig {
|
|
|
51
32
|
}
|
|
52
33
|
}
|
|
53
34
|
|
|
54
|
-
function buildTaskPrompt(task:
|
|
35
|
+
function buildTaskPrompt(task: Task): string {
|
|
55
36
|
const lines: string[] = [
|
|
56
37
|
`# SwarmDock Task: ${task.title}`,
|
|
57
38
|
'',
|
|
@@ -65,6 +46,17 @@ function buildTaskPrompt(task: SwarmDockTask): string {
|
|
|
65
46
|
return lines.join('\n')
|
|
66
47
|
}
|
|
67
48
|
|
|
49
|
+
export function generateExamplePrompts(skillId: string): string[] {
|
|
50
|
+
const name = skillId.replace(/-/g, ' ')
|
|
51
|
+
return [
|
|
52
|
+
`Perform a ${name} task`,
|
|
53
|
+
`Help me with ${name}`,
|
|
54
|
+
`I need ${name} work done`,
|
|
55
|
+
`Complete a ${name} assignment`,
|
|
56
|
+
`Handle a ${name} request`,
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
|
|
68
60
|
function formatUsdc(microUnits: string): string {
|
|
69
61
|
const cents = BigInt(microUnits)
|
|
70
62
|
const dollars = Number(cents) / 1_000_000
|
|
@@ -98,11 +90,15 @@ const swarmdock: PlatformConnector = {
|
|
|
98
90
|
if (!privateKey) throw new Error('SwarmDock connector requires an Ed25519 private key credential')
|
|
99
91
|
if (!config.walletAddress) throw new Error('SwarmDock connector requires a Base L2 wallet address in config')
|
|
100
92
|
|
|
101
|
-
// Dynamic import of the SDK
|
|
93
|
+
// Dynamic import of the SDK
|
|
102
94
|
let SwarmDockClient: typeof import('@swarmdock/sdk').SwarmDockClient
|
|
95
|
+
let ConflictError: typeof import('@swarmdock/sdk').ConflictError
|
|
96
|
+
let AuthenticationError: typeof import('@swarmdock/sdk').AuthenticationError
|
|
103
97
|
try {
|
|
104
98
|
const sdk = await import('@swarmdock/sdk')
|
|
105
99
|
SwarmDockClient = sdk.SwarmDockClient
|
|
100
|
+
ConflictError = sdk.ConflictError
|
|
101
|
+
AuthenticationError = sdk.AuthenticationError
|
|
106
102
|
} catch {
|
|
107
103
|
throw new Error('SwarmDock SDK (@swarmdock/sdk) is not installed. Run: npm install @swarmdock/sdk')
|
|
108
104
|
}
|
|
@@ -128,36 +124,47 @@ const swarmdock: PlatformConnector = {
|
|
|
128
124
|
basePrice: '1000000', // $1.00 default
|
|
129
125
|
inputModes: ['text'],
|
|
130
126
|
outputModes: ['text'],
|
|
127
|
+
examplePrompts: generateExamplePrompts(skillId),
|
|
131
128
|
}))
|
|
132
129
|
|
|
133
130
|
log.info(TAG, `Registering agent "${connector.name}" on SwarmDock at ${config.apiUrl}`)
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
131
|
+
try {
|
|
132
|
+
const registration = await client.register({
|
|
133
|
+
displayName: connector.name,
|
|
134
|
+
description: config.agentDescription,
|
|
135
|
+
framework: 'swarmclaw',
|
|
136
|
+
walletAddress: config.walletAddress,
|
|
137
|
+
skills: skillList,
|
|
138
|
+
})
|
|
139
|
+
log.info(TAG, `Registered as ${registration.agent.did} (trust level ${registration.agent.trustLevel})`)
|
|
140
|
+
|
|
141
|
+
logActivity({
|
|
142
|
+
entityType: 'connector',
|
|
143
|
+
entityId: connectorId,
|
|
144
|
+
action: 'swarmdock-registered',
|
|
145
|
+
actor: 'system',
|
|
146
|
+
summary: `Agent "${connector.name}" registered on SwarmDock as ${registration.agent.did}`,
|
|
147
|
+
})
|
|
148
|
+
} catch (err) {
|
|
149
|
+
if (err instanceof ConflictError) {
|
|
150
|
+
log.info(TAG, `Agent already registered, authenticating`)
|
|
151
|
+
await client.authenticate()
|
|
152
|
+
} else {
|
|
153
|
+
throw err
|
|
154
|
+
}
|
|
155
|
+
}
|
|
150
156
|
|
|
151
157
|
// Set up SSE event stream
|
|
152
158
|
let alive = true
|
|
153
159
|
|
|
154
|
-
const handleSSEEvent = async (event:
|
|
160
|
+
const handleSSEEvent = async (event: SSEEvent) => {
|
|
155
161
|
if (!alive) return
|
|
156
162
|
try {
|
|
157
163
|
switch (event.type) {
|
|
158
|
-
case 'task.created':
|
|
164
|
+
case 'task.created':
|
|
165
|
+
case 'task.invited': {
|
|
159
166
|
if (!config.autoDiscover) break
|
|
160
|
-
const task = event.data as unknown as
|
|
167
|
+
const task = event.data as unknown as Task
|
|
161
168
|
if (shouldAutoBid(task, config)) {
|
|
162
169
|
await submitAutoBid(client, task.id, config)
|
|
163
170
|
logActivity({
|
|
@@ -172,7 +179,7 @@ const swarmdock: PlatformConnector = {
|
|
|
172
179
|
}
|
|
173
180
|
|
|
174
181
|
case 'task.assigned': {
|
|
175
|
-
const task = event.data as unknown as
|
|
182
|
+
const task = event.data as unknown as Task
|
|
176
183
|
if (!task.assigneeId) break
|
|
177
184
|
|
|
178
185
|
// Signal work started on SwarmDock
|
|
@@ -236,6 +243,32 @@ const swarmdock: PlatformConnector = {
|
|
|
236
243
|
})
|
|
237
244
|
break
|
|
238
245
|
}
|
|
246
|
+
|
|
247
|
+
case 'escrow.releasing':
|
|
248
|
+
case 'escrow.refunding': {
|
|
249
|
+
const data = event.data as Record<string, string>
|
|
250
|
+
logActivity({
|
|
251
|
+
entityType: 'connector',
|
|
252
|
+
entityId: connectorId,
|
|
253
|
+
action: 'swarmdock-escrow',
|
|
254
|
+
actor: 'system',
|
|
255
|
+
summary: `Escrow ${event.type.split('.')[1]} for task ${data.taskId}`,
|
|
256
|
+
})
|
|
257
|
+
break
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case 'escrow.release_failed':
|
|
261
|
+
case 'escrow.refund_failed': {
|
|
262
|
+
const data = event.data as Record<string, string>
|
|
263
|
+
logActivity({
|
|
264
|
+
entityType: 'connector',
|
|
265
|
+
entityId: connectorId,
|
|
266
|
+
action: 'incident',
|
|
267
|
+
actor: 'system',
|
|
268
|
+
summary: `Escrow ${event.type.replace('escrow.', '')} for task ${data.taskId}`,
|
|
269
|
+
})
|
|
270
|
+
break
|
|
271
|
+
}
|
|
239
272
|
}
|
|
240
273
|
} catch (err) {
|
|
241
274
|
log.error(TAG, `Error handling SSE event ${event.type}: ${err instanceof Error ? err.message : String(err)}`)
|
|
@@ -250,7 +283,12 @@ const swarmdock: PlatformConnector = {
|
|
|
250
283
|
await client.heartbeat()
|
|
251
284
|
log.debug(TAG, 'SwarmDock token refreshed')
|
|
252
285
|
} catch (err) {
|
|
253
|
-
|
|
286
|
+
if (err instanceof AuthenticationError) {
|
|
287
|
+
log.warn(TAG, 'SwarmDock token expired, re-authenticating')
|
|
288
|
+
try { await client.authenticate() } catch {}
|
|
289
|
+
} else {
|
|
290
|
+
log.error(TAG, `SwarmDock heartbeat failed: ${err instanceof Error ? err.message : String(err)}`)
|
|
291
|
+
}
|
|
254
292
|
}
|
|
255
293
|
}, 23 * 60 * 60 * 1000)
|
|
256
294
|
|