@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.0",
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.0",
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: SwarmDockTask, config: SwarmDockConfig): boolean {
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: SwarmDockTask,
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: SwarmDockTask): string {
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 (must be built and linked first)
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
- const registration = await client.register({
135
- displayName: connector.name,
136
- description: config.agentDescription,
137
- framework: 'swarmclaw',
138
- walletAddress: config.walletAddress,
139
- skills: skillList,
140
- })
141
- log.info(TAG, `Registered as ${registration.agent.did} (trust level ${registration.agent.trustLevel})`)
142
-
143
- logActivity({
144
- entityType: 'connector',
145
- entityId: connectorId,
146
- action: 'swarmdock-registered',
147
- actor: 'system',
148
- summary: `Agent "${connector.name}" registered on SwarmDock as ${registration.agent.did}`,
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: SwarmDockSSEEvent) => {
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 SwarmDockTask
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 SwarmDockTask
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
- log.error(TAG, `SwarmDock heartbeat failed: ${err instanceof Error ? err.message : String(err)}`)
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