@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.
Files changed (214) hide show
  1. package/README.md +39 -6
  2. package/package.json +2 -2
  3. package/src/app/agents/[id]/page.tsx +1 -18
  4. package/src/app/api/activity/route.ts +9 -23
  5. package/src/app/api/agents/route.ts +17 -1
  6. package/src/app/api/agents/thread-route.test.ts +0 -1
  7. package/src/app/api/approvals/route.test.ts +6 -22
  8. package/src/app/api/approvals/route.ts +13 -5
  9. package/src/app/api/connectors/route.ts +2 -2
  10. package/src/app/api/credentials/[id]/route.ts +2 -0
  11. package/src/app/api/credentials/route.ts +4 -1
  12. package/src/app/api/goals/[id]/route.ts +28 -0
  13. package/src/app/api/goals/route.ts +33 -0
  14. package/src/app/api/portability/export/route.ts +8 -0
  15. package/src/app/api/portability/import/route.test.ts +80 -0
  16. package/src/app/api/portability/import/route.ts +28 -0
  17. package/src/app/api/protocols/templates/[id]/route.ts +2 -1
  18. package/src/app/api/protocols/templates/route.ts +2 -1
  19. package/src/app/api/settings/route.ts +13 -2
  20. package/src/app/api/wallets/[id]/route.ts +15 -157
  21. package/src/app/api/wallets/generate/route.ts +22 -0
  22. package/src/app/api/wallets/route.test.ts +147 -0
  23. package/src/app/api/wallets/route.ts +13 -95
  24. package/src/app/autonomy/page.tsx +2 -57
  25. package/src/app/home/page.tsx +3 -0
  26. package/src/app/protocols/page.tsx +2 -21
  27. package/src/app/settings/page.tsx +0 -9
  28. package/src/app/wallets/page.tsx +105 -5
  29. package/src/cli/index.js +32 -33
  30. package/src/cli/spec.js +26 -27
  31. package/src/components/agents/agent-sheet.tsx +2 -40
  32. package/src/components/agents/inspector-panel.tsx +0 -83
  33. package/src/components/chat/chat-card.tsx +0 -31
  34. package/src/components/chat/message-bubble.tsx +1 -108
  35. package/src/components/connectors/connector-sheet.tsx +25 -1
  36. package/src/components/layout/sidebar-rail.tsx +6 -10
  37. package/src/components/projects/project-detail.tsx +3 -35
  38. package/src/components/projects/tabs/overview-tab.tsx +3 -59
  39. package/src/components/projects/tabs/work-tab.tsx +7 -77
  40. package/src/components/protocols/structured-session-launcher.tsx +1 -22
  41. package/src/components/shared/connector-platform-icon.tsx +1 -0
  42. package/src/components/tasks/task-card.tsx +4 -34
  43. package/src/components/tasks/task-sheet.tsx +6 -36
  44. package/src/components/wallets/wallet-list.tsx +150 -0
  45. package/src/lib/app/navigation.test.ts +0 -13
  46. package/src/lib/app/navigation.ts +2 -7
  47. package/src/lib/app/view-constants.ts +14 -19
  48. package/src/lib/server/activity/activity-log.ts +16 -1
  49. package/src/lib/server/agents/agent-service.ts +24 -11
  50. package/src/lib/server/agents/agent-thread-session.ts +0 -1
  51. package/src/lib/server/agents/delegation-advisory.test.ts +0 -1
  52. package/src/lib/server/agents/delegation-jobs.test.ts +0 -69
  53. package/src/lib/server/agents/delegation-jobs.ts +0 -25
  54. package/src/lib/server/agents/main-agent-loop.ts +1 -49
  55. package/src/lib/server/agents/subagent-runtime.ts +0 -1
  56. package/src/lib/server/approval-match.ts +14 -85
  57. package/src/lib/server/approvals/approval-hooks.ts +81 -0
  58. package/src/lib/server/approvals.test.ts +6 -6
  59. package/src/lib/server/approvals.ts +11 -6
  60. package/src/lib/server/autonomy/supervisor-reflection.test.ts +0 -1
  61. package/src/lib/server/builtin-extensions.ts +0 -2
  62. package/src/lib/server/capability-router.test.ts +0 -2
  63. package/src/lib/server/chat-execution/chat-execution-tool-events.test.ts +14 -14
  64. package/src/lib/server/chat-execution/chat-execution-types.ts +0 -2
  65. package/src/lib/server/chat-execution/chat-execution-utils.ts +0 -2
  66. package/src/lib/server/chat-execution/chat-streaming-utils.ts +2 -30
  67. package/src/lib/server/chat-execution/chat-turn-finalization.ts +1 -36
  68. package/src/lib/server/chat-execution/chat-turn-preparation.ts +2 -22
  69. package/src/lib/server/chat-execution/iteration-event-handler.ts +0 -24
  70. package/src/lib/server/chat-execution/message-classifier.test.ts +0 -45
  71. package/src/lib/server/chat-execution/message-classifier.ts +1 -16
  72. package/src/lib/server/chat-execution/prompt-builder.test.ts +0 -1
  73. package/src/lib/server/chat-execution/prompt-builder.ts +0 -30
  74. package/src/lib/server/chat-execution/prompt-sections.ts +0 -1
  75. package/src/lib/server/chat-execution/situational-awareness.test.ts +2 -73
  76. package/src/lib/server/chat-execution/situational-awareness.ts +4 -38
  77. package/src/lib/server/chat-execution/stream-agent-chat.test.ts +8 -123
  78. package/src/lib/server/chat-execution/stream-agent-chat.ts +1 -5
  79. package/src/lib/server/chat-execution/stream-continuation.test.ts +4 -52
  80. package/src/lib/server/chat-execution/stream-continuation.ts +6 -48
  81. package/src/lib/server/chatrooms/session-mailbox.ts +0 -10
  82. package/src/lib/server/chats/chat-session-service.ts +3 -5
  83. package/src/lib/server/connectors/connector-inbound.ts +0 -1
  84. package/src/lib/server/connectors/connector-lifecycle.ts +19 -3
  85. package/src/lib/server/connectors/connector-service.ts +39 -9
  86. package/src/lib/server/connectors/swarmdock-bidding.ts +74 -0
  87. package/src/lib/server/connectors/swarmdock-payloads.test.ts +85 -0
  88. package/src/lib/server/connectors/swarmdock-secret.test.ts +128 -0
  89. package/src/lib/server/connectors/swarmdock-secret.ts +152 -0
  90. package/src/lib/server/connectors/swarmdock-tasks.ts +127 -0
  91. package/src/lib/server/connectors/swarmdock.ts +285 -0
  92. package/src/lib/server/execution-brief.test.ts +2 -25
  93. package/src/lib/server/execution-brief.ts +30 -35
  94. package/src/lib/server/execution-engine/task-attempt.ts +0 -1
  95. package/src/lib/server/goals/goal-repository.ts +19 -0
  96. package/src/lib/server/goals/goal-service.ts +143 -0
  97. package/src/lib/server/persistence/storage-context.ts +0 -5
  98. package/src/lib/server/portability/export.ts +109 -0
  99. package/src/lib/server/portability/import.ts +159 -0
  100. package/src/lib/server/protocols/protocol-normalization.ts +0 -4
  101. package/src/lib/server/protocols/protocol-queries.ts +0 -6
  102. package/src/lib/server/protocols/protocol-run-lifecycle.ts +4 -32
  103. package/src/lib/server/protocols/protocol-service.ts +0 -1
  104. package/src/lib/server/protocols/protocol-step-helpers.ts +0 -4
  105. package/src/lib/server/protocols/protocol-step-processors.ts +0 -6
  106. package/src/lib/server/protocols/protocol-swarm.ts +0 -2
  107. package/src/lib/server/protocols/protocol-types.ts +0 -2
  108. package/src/lib/server/provider-health.ts +0 -9
  109. package/src/lib/server/runtime/daemon-state/core.ts +0 -9
  110. package/src/lib/server/runtime/daemon-state.test.ts +0 -35
  111. package/src/lib/server/runtime/heartbeat-service.ts +3 -23
  112. package/src/lib/server/runtime/queue/core.ts +11 -33
  113. package/src/lib/server/runtime/runtime-storage-write-paths.test.ts +6 -6
  114. package/src/lib/server/runtime/scheduler.ts +0 -13
  115. package/src/lib/server/runtime/session-run-manager/drain.ts +0 -24
  116. package/src/lib/server/runtime/session-run-manager/enqueue.ts +0 -1
  117. package/src/lib/server/runtime/session-run-manager/queries.ts +0 -1
  118. package/src/lib/server/runtime/session-run-manager/recovery.ts +0 -1
  119. package/src/lib/server/runtime/session-run-manager.test.ts +0 -28
  120. package/src/lib/server/session-tools/crud.ts +0 -14
  121. package/src/lib/server/session-tools/delegate.ts +0 -4
  122. package/src/lib/server/session-tools/index.ts +0 -4
  123. package/src/lib/server/session-tools/team-context.ts +0 -3
  124. package/src/lib/server/storage-normalization.ts +13 -0
  125. package/src/lib/server/storage.ts +75 -45
  126. package/src/lib/server/tasks/task-checkout.ts +59 -0
  127. package/src/lib/server/tasks/task-lifecycle.ts +2 -0
  128. package/src/lib/server/tasks/task-route-service.ts +4 -26
  129. package/src/lib/server/tasks/task-service.ts +0 -7
  130. package/src/lib/server/tool-aliases.ts +0 -1
  131. package/src/lib/server/tool-capability-policy-advanced.test.ts +4 -4
  132. package/src/lib/server/tool-capability-policy.ts +0 -2
  133. package/src/lib/server/tool-planning.ts +0 -12
  134. package/src/lib/server/universal-tool-access.ts +0 -1
  135. package/src/lib/server/usage/cost-rollup.ts +124 -0
  136. package/src/lib/server/usage/usage-repository.ts +6 -0
  137. package/src/lib/server/wallets/wallet-crypto.ts +33 -0
  138. package/src/lib/server/wallets/wallet-repository.ts +24 -0
  139. package/src/lib/server/wallets/wallet-service.ts +119 -0
  140. package/src/lib/server/working-state/extraction.ts +8 -42
  141. package/src/lib/server/working-state/normalization.ts +10 -103
  142. package/src/lib/server/working-state/service.ts +12 -21
  143. package/src/lib/strip-internal-metadata.test.ts +1 -1
  144. package/src/lib/strip-internal-metadata.ts +1 -1
  145. package/src/lib/tool-definitions.ts +0 -1
  146. package/src/lib/validation/schemas.ts +36 -32
  147. package/src/lib/validation/server-schemas.ts +35 -0
  148. package/src/stores/slices/data-slice.ts +5 -1
  149. package/src/stores/slices/ui-slice.ts +0 -4
  150. package/src/types/agent.ts +10 -84
  151. package/src/types/app-settings.ts +6 -2
  152. package/src/types/approval.ts +3 -2
  153. package/src/types/connector.ts +1 -0
  154. package/src/types/goal.ts +30 -0
  155. package/src/types/index.ts +2 -1
  156. package/src/types/message.ts +0 -1
  157. package/src/types/misc.ts +2 -4
  158. package/src/types/protocol.ts +0 -2
  159. package/src/types/run.ts +0 -3
  160. package/src/types/session.ts +1 -51
  161. package/src/types/swarmdock.ts +29 -0
  162. package/src/types/task.ts +9 -3
  163. package/src/types/working-state.ts +2 -9
  164. package/src/views/settings/section-runtime-loop.tsx +0 -14
  165. package/src/app/api/canvas/[sessionId]/route.ts +0 -35
  166. package/src/app/api/missions/[id]/actions/route.ts +0 -31
  167. package/src/app/api/missions/[id]/events/route.ts +0 -14
  168. package/src/app/api/missions/[id]/route.ts +0 -10
  169. package/src/app/api/missions/route.test.ts +0 -244
  170. package/src/app/api/missions/route.ts +0 -57
  171. package/src/app/api/wallets/[id]/approve/route.ts +0 -79
  172. package/src/app/api/wallets/[id]/balance-history/route.ts +0 -18
  173. package/src/app/api/wallets/[id]/send/route.ts +0 -113
  174. package/src/app/api/wallets/[id]/transactions/route.ts +0 -18
  175. package/src/app/missions/[id]/page.tsx +0 -3
  176. package/src/app/missions/page.tsx +0 -685
  177. package/src/components/canvas/canvas-panel.tsx +0 -267
  178. package/src/components/wallets/wallet-approval-dialog.tsx +0 -107
  179. package/src/components/wallets/wallet-panel.tsx +0 -1010
  180. package/src/components/wallets/wallet-section.tsx +0 -260
  181. package/src/features/missions/queries.ts +0 -23
  182. package/src/lib/canvas-content.test.ts +0 -360
  183. package/src/lib/canvas-content.ts +0 -198
  184. package/src/lib/server/canvas-content.test.ts +0 -32
  185. package/src/lib/server/canvas-content.ts +0 -6
  186. package/src/lib/server/ethereum.ts +0 -591
  187. package/src/lib/server/evm-swap.ts +0 -476
  188. package/src/lib/server/missions/mission-intent.test.ts +0 -63
  189. package/src/lib/server/missions/mission-intent.ts +0 -569
  190. package/src/lib/server/missions/mission-repository.ts +0 -74
  191. package/src/lib/server/missions/mission-service/actions.ts +0 -6
  192. package/src/lib/server/missions/mission-service/bindings.ts +0 -9
  193. package/src/lib/server/missions/mission-service/context.ts +0 -4
  194. package/src/lib/server/missions/mission-service/core.ts +0 -2271
  195. package/src/lib/server/missions/mission-service/queries.ts +0 -12
  196. package/src/lib/server/missions/mission-service/recovery.ts +0 -5
  197. package/src/lib/server/missions/mission-service/ticks.ts +0 -9
  198. package/src/lib/server/missions/mission-service.test.ts +0 -888
  199. package/src/lib/server/missions/mission-service.ts +0 -6
  200. package/src/lib/server/session-tools/canvas.ts +0 -105
  201. package/src/lib/server/session-tools/wallet-tool.test.ts +0 -150
  202. package/src/lib/server/session-tools/wallet.ts +0 -1287
  203. package/src/lib/server/solana.ts +0 -327
  204. package/src/lib/server/wallet/wallet-execution.test.ts +0 -198
  205. package/src/lib/server/wallet/wallet-portfolio.test.ts +0 -98
  206. package/src/lib/server/wallet/wallet-portfolio.ts +0 -772
  207. package/src/lib/server/wallet/wallet-service.test.ts +0 -81
  208. package/src/lib/server/wallet/wallet-service.ts +0 -225
  209. package/src/lib/wallet/wallet-transactions.test.ts +0 -75
  210. package/src/lib/wallet/wallet-transactions.ts +0 -43
  211. package/src/lib/wallet/wallet.test.ts +0 -333
  212. package/src/lib/wallet/wallet.ts +0 -183
  213. package/src/types/mission.ts +0 -185
  214. package/src/views/settings/section-wallets.tsx +0 -35
@@ -0,0 +1,127 @@
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
+ case 'task.review':
104
+ // Work submitted, awaiting requester review on SwarmDock
105
+ if (boardTask.externalSource) boardTask.externalSource.state = 'review'
106
+ break
107
+ case 'task.disputed':
108
+ // Task disputed on SwarmDock
109
+ if (boardTask.externalSource) boardTask.externalSource.state = 'disputed'
110
+ break
111
+ }
112
+
113
+ boardTask.updatedAt = now
114
+ boardTask.lastActivityAt = now
115
+ saveTasks(tasks)
116
+ }
117
+
118
+ /**
119
+ * Find a SwarmClaw BoardTask ID by its SwarmDock task ID.
120
+ */
121
+ export function findBoardTaskBySwarmdockId(swarmdockTaskId: string): string | null {
122
+ const tasks = loadTasks() as Record<string, BoardTask>
123
+ const task = Object.values(tasks).find(
124
+ (t) => t.externalSource?.source === 'swarmdock' && t.externalSource.id === swarmdockTaskId,
125
+ )
126
+ return task?.id || null
127
+ }
@@ -0,0 +1,285 @@
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
+ paymentPrivateKey?: string
39
+ }
40
+
41
+ function parseConfig(connector: Connector): SwarmDockConfig {
42
+ const c = connector.config || {}
43
+ return {
44
+ apiUrl: c.apiUrl || 'https://api.swarmdock.ai',
45
+ walletAddress: c.walletAddress || '',
46
+ agentDescription: c.agentDescription || connector.name || '',
47
+ skills: c.skills || '',
48
+ autoDiscover: c.autoDiscover === 'true',
49
+ maxBudget: c.maxBudget || '0',
50
+ paymentPrivateKey: c.paymentPrivateKey || undefined,
51
+ }
52
+ }
53
+
54
+ function buildTaskPrompt(task: SwarmDockTask): string {
55
+ const lines: string[] = [
56
+ `# SwarmDock Task: ${task.title}`,
57
+ '',
58
+ task.description,
59
+ '',
60
+ `**Required Skills:** ${task.skillRequirements.join(', ')}`,
61
+ `**Budget:** ${formatUsdc(task.budgetMax)}`,
62
+ ]
63
+ if (task.deadline) lines.push(`**Deadline:** ${task.deadline}`)
64
+ lines.push('', 'Complete this task and provide your deliverables. Your response will be submitted as the task result on the SwarmDock marketplace.')
65
+ return lines.join('\n')
66
+ }
67
+
68
+ function formatUsdc(microUnits: string): string {
69
+ const cents = BigInt(microUnits)
70
+ const dollars = Number(cents) / 1_000_000
71
+ return `$${dollars.toFixed(2)} USDC`
72
+ }
73
+
74
+ export async function submitSwarmdockTaskResult(
75
+ client: { tasks: { submit: (taskId: string, input: TaskSubmitInput) => Promise<unknown> } },
76
+ swarmdockTaskId: string,
77
+ text: string,
78
+ notes?: string,
79
+ ): Promise<void> {
80
+ const payload: TaskSubmitInput = {
81
+ artifacts: [{ type: 'text/markdown', content: text }],
82
+ files: [],
83
+ }
84
+ if (notes) payload.notes = notes
85
+ await client.tasks.submit(swarmdockTaskId, payload)
86
+ }
87
+
88
+ /** Runtime state: maps SwarmDock task IDs → SwarmClaw BoardTask IDs */
89
+ const taskIdMap = hmrSingleton('__swarmclaw_swarmdock_task_map__', () => new Map<string, string>())
90
+
91
+ const swarmdock: PlatformConnector = {
92
+ async start(connector, _botToken, onMessage): Promise<ConnectorInstance> {
93
+ const config = parseConfig(connector)
94
+ const connectorId = connector.id
95
+ const agentId = connector.agentId || ''
96
+ const privateKey = _botToken || ''
97
+
98
+ if (!privateKey) throw new Error('SwarmDock connector requires an Ed25519 private key credential')
99
+ if (!config.walletAddress) throw new Error('SwarmDock connector requires a Base L2 wallet address in config')
100
+
101
+ // Dynamic import of the SDK (must be built and linked first)
102
+ let SwarmDockClient: typeof import('@swarmdock/sdk').SwarmDockClient
103
+ try {
104
+ const sdk = await import('@swarmdock/sdk')
105
+ SwarmDockClient = sdk.SwarmDockClient
106
+ } catch {
107
+ throw new Error('SwarmDock SDK (@swarmdock/sdk) is not installed. Run: npm install @swarmdock/sdk')
108
+ }
109
+
110
+ const client = new SwarmDockClient({
111
+ baseUrl: config.apiUrl,
112
+ privateKey,
113
+ ...(config.paymentPrivateKey?.startsWith('0x')
114
+ ? { paymentPrivateKey: config.paymentPrivateKey as `0x${string}` }
115
+ : {}),
116
+ })
117
+
118
+ // Register agent on SwarmDock (Ed25519 challenge-response)
119
+ const skillList = config.skills
120
+ .split(',')
121
+ .map((s) => s.trim())
122
+ .filter(Boolean)
123
+ .map((skillId) => ({
124
+ skillId,
125
+ skillName: skillId.replace(/-/g, ' '),
126
+ description: `${skillId} capability`,
127
+ category: skillId,
128
+ basePrice: '1000000', // $1.00 default
129
+ inputModes: ['text'],
130
+ outputModes: ['text'],
131
+ }))
132
+
133
+ 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
+ })
150
+
151
+ // Set up SSE event stream
152
+ let alive = true
153
+
154
+ const handleSSEEvent = async (event: SwarmDockSSEEvent) => {
155
+ if (!alive) return
156
+ try {
157
+ switch (event.type) {
158
+ case 'task.created': {
159
+ if (!config.autoDiscover) break
160
+ const task = event.data as unknown as SwarmDockTask
161
+ if (shouldAutoBid(task, config)) {
162
+ await submitAutoBid(client, task.id, config)
163
+ logActivity({
164
+ entityType: 'connector',
165
+ entityId: connectorId,
166
+ action: 'swarmdock-bid',
167
+ actor: 'system',
168
+ summary: `Auto-bid on SwarmDock task: "${task.title}"`,
169
+ })
170
+ }
171
+ break
172
+ }
173
+
174
+ case 'task.assigned': {
175
+ const task = event.data as unknown as SwarmDockTask
176
+ if (!task.assigneeId) break
177
+
178
+ // Signal work started on SwarmDock
179
+ try { await client.tasks.start(task.id) } catch {}
180
+
181
+ // Create a BoardTask in SwarmClaw
182
+ const boardTaskId = await createBoardTaskFromAssignment(task, agentId, connectorId, config.apiUrl)
183
+ taskIdMap.set(task.id, boardTaskId)
184
+
185
+ // Dispatch as inbound message to the assigned agent
186
+ const inbound: InboundMessage = {
187
+ platform: 'swarmdock',
188
+ channelId: `swarmdock-task:${task.id}`,
189
+ channelName: `SwarmDock: ${task.title}`,
190
+ senderId: task.requesterId,
191
+ senderName: `SwarmDock Requester`,
192
+ text: buildTaskPrompt(task),
193
+ messageId: task.id,
194
+ }
195
+ await onMessage(inbound)
196
+ break
197
+ }
198
+
199
+ case 'task.completed':
200
+ case 'task.cancelled':
201
+ case 'task.failed': {
202
+ const taskId = (event.data as Record<string, string>).taskId
203
+ if (taskId) await updateBoardTaskFromEvent(taskId, event.type)
204
+ break
205
+ }
206
+
207
+ case 'task.review': {
208
+ const taskId = (event.data as Record<string, string>).taskId
209
+ if (taskId) await updateBoardTaskFromEvent(taskId, 'task.review')
210
+ break
211
+ }
212
+
213
+ case 'task.disputed': {
214
+ const taskId = (event.data as Record<string, string>).taskId
215
+ if (taskId) {
216
+ await updateBoardTaskFromEvent(taskId, 'task.disputed')
217
+ logActivity({
218
+ entityType: 'connector',
219
+ entityId: connectorId,
220
+ action: 'incident',
221
+ actor: 'system',
222
+ summary: `SwarmDock task ${taskId} disputed`,
223
+ })
224
+ }
225
+ break
226
+ }
227
+
228
+ case 'payment.released': {
229
+ const data = event.data as Record<string, string>
230
+ logActivity({
231
+ entityType: 'connector',
232
+ entityId: connectorId,
233
+ action: 'swarmdock-payment',
234
+ actor: 'system',
235
+ summary: `Payment received: ${formatUsdc(data.amount || '0')} for task ${data.taskId}`,
236
+ })
237
+ break
238
+ }
239
+ }
240
+ } catch (err) {
241
+ log.error(TAG, `Error handling SSE event ${event.type}: ${err instanceof Error ? err.message : String(err)}`)
242
+ }
243
+ }
244
+
245
+ client.events.subscribe(handleSSEEvent as (event: unknown) => void)
246
+
247
+ // Token refresh heartbeat (23h, token lasts 24h)
248
+ const heartbeatInterval = setInterval(async () => {
249
+ try {
250
+ await client.heartbeat()
251
+ log.debug(TAG, 'SwarmDock token refreshed')
252
+ } catch (err) {
253
+ log.error(TAG, `SwarmDock heartbeat failed: ${err instanceof Error ? err.message : String(err)}`)
254
+ }
255
+ }, 23 * 60 * 60 * 1000)
256
+
257
+ return {
258
+ connector,
259
+
260
+ stop: async () => {
261
+ alive = false
262
+ clearInterval(heartbeatInterval)
263
+ client.events.unsubscribe()
264
+ log.info(TAG, 'SwarmDock connector stopped')
265
+ },
266
+
267
+ sendMessage: async (channelId: string, text: string) => {
268
+ // channelId format: "swarmdock-task:{taskId}"
269
+ const swarmdockTaskId = channelId.replace('swarmdock-task:', '')
270
+ if (!swarmdockTaskId) return
271
+
272
+ await submitSwarmdockTaskResult(client, swarmdockTaskId, text)
273
+ log.info(TAG, `Submitted results for SwarmDock task ${swarmdockTaskId}`)
274
+
275
+ if (findBoardTaskBySwarmdockId(swarmdockTaskId)) {
276
+ await updateBoardTaskFromEvent(swarmdockTaskId, 'task.submitted')
277
+ }
278
+
279
+ return { messageId: swarmdockTaskId }
280
+ },
281
+ }
282
+ },
283
+ }
284
+
285
+ export default swarmdock
@@ -2,7 +2,7 @@ import assert from 'node:assert/strict'
2
2
  import { test } from 'node:test'
3
3
 
4
4
  import { buildExecutionBrief, buildExecutionBriefContextBlock, serializeExecutionBriefForDelegation } from './execution-brief'
5
- import type { Mission, Session, SessionWorkingState } from '@/types'
5
+ import type { Session, SessionWorkingState } from '@/types'
6
6
 
7
7
  test('buildExecutionBrief prefers working state and folds in mission and run-context fallback data', () => {
8
8
  const session = {
@@ -31,25 +31,8 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
31
31
  },
32
32
  } satisfies Partial<Session> as Session
33
33
 
34
- const mission = {
35
- id: 'm1',
36
- source: 'chat',
37
- objective: 'Ship the release fix',
38
- status: 'active',
39
- phase: 'executing',
40
- currentStep: 'Verify staging auth',
41
- successCriteria: ['Restore staging deploys'],
42
- waitState: {
43
- kind: 'approval',
44
- reason: 'Waiting for deploy approval.',
45
- },
46
- createdAt: 1,
47
- updatedAt: 1,
48
- } satisfies Partial<Mission> as Mission
49
-
50
34
  const workingState = {
51
35
  sessionId: 's1',
52
- missionId: 'm1',
53
36
  objective: 'Ship the release fix safely',
54
37
  summary: 'Auth mismatch isolated to staging.',
55
38
  constraints: ['Do not change the API'],
@@ -79,7 +62,7 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
79
62
  updatedAt: 1,
80
63
  } satisfies SessionWorkingState
81
64
 
82
- const brief = buildExecutionBrief({ session, mission, workingState })
65
+ const brief = buildExecutionBrief({ session, workingState })
83
66
 
84
67
  assert.equal(brief.objective, 'Ship the release fix safely')
85
68
  assert.equal(brief.summary, 'Auth mismatch isolated to staging.')
@@ -87,11 +70,9 @@ test('buildExecutionBrief prefers working state and folds in mission and run-con
87
70
  assert.equal(brief.nextAction, 'Request deploy approval')
88
71
  assert.equal(brief.plan[0]?.text, 'Request deploy approval')
89
72
  assert.equal(brief.blockers[0], 'Deploy approval is pending. | next: Request deploy approval')
90
- assert.ok(brief.blockers.some((entry) => /waiting for deploy approval/i.test(entry)))
91
73
  assert.ok(brief.facts.some((entry) => /auth mismatch isolated to staging/i.test(entry)))
92
74
  assert.ok(brief.artifacts.some((entry) => /deploy\.log/i.test(entry)))
93
75
  assert.equal(brief.parentContext, 'Parent asked for a contained fix.')
94
- assert.equal(brief.missionPhase, 'executing')
95
76
  })
96
77
 
97
78
  test('buildExecutionBriefContextBlock renders a single canonical state block', () => {
@@ -139,7 +120,6 @@ test('buildExecutionBriefContextBlock renders a single canonical state block', (
139
120
  test('serializeExecutionBriefForDelegation creates a bounded handoff summary', () => {
140
121
  const text = serializeExecutionBriefForDelegation({
141
122
  sessionId: 's3',
142
- missionId: 'm3',
143
123
  objective: 'Repair the deployment pipeline',
144
124
  summary: 'The regression is isolated to the release job.',
145
125
  status: 'blocked',
@@ -153,9 +133,6 @@ test('serializeExecutionBriefForDelegation creates a bounded handoff summary', (
153
133
  artifacts: ['/tmp/project/release.log'],
154
134
  constraints: ['Keep the current release shape.'],
155
135
  successCriteria: ['Production deploy completes'],
156
- missionStatus: 'active',
157
- missionPhase: 'executing',
158
- waitState: null,
159
136
  evidenceRefs: [],
160
137
  parentContext: 'Parent is waiting on a concise status update.',
161
138
  })
@@ -2,7 +2,6 @@ import type {
2
2
  EvidenceRef,
3
3
  ExecutionBrief,
4
4
  ExecutionBriefPlanStep,
5
- Mission,
6
5
  Session,
7
6
  SessionWorkingState,
8
7
  WorkingPlanStep,
@@ -13,6 +12,7 @@ import { getSession } from '@/lib/server/sessions/session-repository'
13
12
  import { loadSessionWorkingState } from '@/lib/server/working-state/service'
14
13
  import { ensureRunContext } from '@/lib/server/run-context'
15
14
  import { cleanText, cleanMultiline } from '@/lib/server/text-normalization'
15
+ import { resolveEffectiveGoal, getGoalChain, formatGoalChainForBrief } from '@/lib/server/goals/goal-service'
16
16
 
17
17
  const MAX_PLAN_ITEMS = 8
18
18
  const MAX_FACTS = 8
@@ -75,14 +75,8 @@ function dedupePlan(steps: ExecutionBriefPlanStep[]): ExecutionBriefPlanStep[] {
75
75
  return out
76
76
  }
77
77
 
78
- function inferStatus(mission: Mission | null | undefined, workingState: SessionWorkingState | null): WorkingStateStatus {
78
+ function inferStatus(workingState: SessionWorkingState | null): WorkingStateStatus {
79
79
  if (workingState?.status) return workingState.status
80
- if (!mission) return 'idle'
81
- if (mission.status === 'completed') return 'completed'
82
- if (mission.status === 'waiting') return 'waiting'
83
- if (mission.status === 'failed' || mission.phase === 'failed') return 'blocked'
84
- if (mission.waitState) return 'waiting'
85
- if (mission.phase === 'executing' || mission.phase === 'dispatching' || mission.phase === 'verifying') return 'progress'
86
80
  return 'idle'
87
81
  }
88
82
 
@@ -121,15 +115,14 @@ function buildFacts(workingState: SessionWorkingState | null, session: Session |
121
115
  return uniqueStrings([...activeFacts, ...runContextFacts], MAX_FACTS, 240)
122
116
  }
123
117
 
124
- function buildBlockers(workingState: SessionWorkingState | null, mission: Mission | null | undefined, session: Session | null): string[] {
118
+ function buildBlockers(workingState: SessionWorkingState | null, session: Session | null): string[] {
125
119
  const activeBlockers = workingState
126
120
  ? workingState.blockers
127
121
  .filter((blocker) => blocker.status === 'active')
128
122
  .map((blocker) => blocker.nextAction ? `${blocker.summary} | next: ${blocker.nextAction}` : blocker.summary)
129
123
  : []
130
- const missionBlockers = mission?.waitState?.reason ? [cleanText(mission.waitState.reason, 280)] : []
131
124
  const runContextBlockers = session?.runContext ? ensureRunContext(session.runContext).blockers : []
132
- return uniqueStrings([...activeBlockers, ...missionBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
125
+ return uniqueStrings([...activeBlockers, ...runContextBlockers], MAX_BLOCKERS, 280)
133
126
  }
134
127
 
135
128
  function buildArtifacts(workingState: SessionWorkingState | null): string[] {
@@ -147,8 +140,8 @@ function buildConstraints(workingState: SessionWorkingState | null, session: Ses
147
140
  return uniqueStrings([...(workingConstraints || []), ...(runContext?.constraints || [])], 10, 220)
148
141
  }
149
142
 
150
- function buildSuccessCriteria(workingState: SessionWorkingState | null, mission: Mission | null | undefined): string[] {
151
- return uniqueStrings([...(workingState?.successCriteria || []), ...(mission?.successCriteria || [])], 10, 220)
143
+ function buildSuccessCriteria(workingState: SessionWorkingState | null): string[] {
144
+ return uniqueStrings([...(workingState?.successCriteria || [])], 10, 220)
152
145
  }
153
146
 
154
147
  function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRef[] {
@@ -161,49 +154,40 @@ function buildEvidenceRefs(workingState: SessionWorkingState | null): EvidenceRe
161
154
  export function buildExecutionBrief(params: {
162
155
  sessionId?: string | null
163
156
  session?: Session | null
164
- mission?: Mission | null
157
+ mission?: null
165
158
  workingState?: SessionWorkingState | null
166
159
  }): ExecutionBrief {
167
160
  const session = params.session
168
161
  || (params.sessionId ? getSession(params.sessionId) || null : null)
169
- const mission = params.mission || null
170
162
  const workingState = params.workingState
171
- || (session?.id ? loadSessionWorkingState(session.id, { mission }) : null)
163
+ || (session?.id ? loadSessionWorkingState(session.id) : null)
172
164
  const runContext = session?.runContext ? ensureRunContext(session.runContext) : null
173
165
  const plan = buildPlan(workingState, session)
174
166
  const nextAction = cleanText(
175
167
  workingState?.nextAction
176
- || mission?.currentStep
177
168
  || plan.find((step) => step.status === 'active')?.text,
178
169
  240,
179
170
  ) || null
180
171
 
181
172
  return {
182
173
  sessionId: session?.id || params.sessionId || null,
183
- missionId: mission?.id || workingState?.missionId || null,
184
174
  objective: cleanMultiline(
185
175
  workingState?.objective
186
- || mission?.objective
187
176
  || runContext?.objective,
188
177
  900,
189
178
  ) || null,
190
179
  summary: cleanMultiline(
191
- workingState?.summary
192
- || mission?.verifierSummary
193
- || mission?.plannerSummary,
180
+ workingState?.summary,
194
181
  700,
195
182
  ) || null,
196
- status: inferStatus(mission, workingState),
183
+ status: inferStatus(workingState),
197
184
  nextAction,
198
185
  plan,
199
- blockers: buildBlockers(workingState, mission, session),
186
+ blockers: buildBlockers(workingState, session),
200
187
  facts: buildFacts(workingState, session),
201
188
  artifacts: buildArtifacts(workingState),
202
189
  constraints: buildConstraints(workingState, session),
203
- successCriteria: buildSuccessCriteria(workingState, mission),
204
- missionStatus: mission?.status || null,
205
- missionPhase: mission?.phase || null,
206
- waitState: mission?.waitState || null,
190
+ successCriteria: buildSuccessCriteria(workingState),
207
191
  evidenceRefs: buildEvidenceRefs(workingState),
208
192
  parentContext: cleanMultiline(runContext?.parentContext, 900) || null,
209
193
  }
@@ -238,21 +222,32 @@ export function buildExecutionBriefContextBlock(
238
222
  || brief.artifacts.length > 0
239
223
  || brief.constraints.length > 0
240
224
  || brief.successCriteria.length > 0
241
- || brief.evidenceRefs.length > 0
242
- || brief.missionStatus
243
- || brief.missionPhase
244
- || brief.waitState?.reason,
225
+ || brief.evidenceRefs.length > 0,
245
226
  )
246
227
  if (!hasContent && brief.status === 'idle') return ''
228
+ // Resolve goal chain for the session's agent/task/project context
229
+ let goalBlock = ''
230
+ if (brief.sessionId) {
231
+ const session = getSession(brief.sessionId)
232
+ if (session) {
233
+ const goal = resolveEffectiveGoal({
234
+ agentId: session.agentId || null,
235
+ projectId: session.projectId || null,
236
+ })
237
+ if (goal) {
238
+ const chain = getGoalChain(goal.id)
239
+ goalBlock = formatGoalChainForBrief(chain)
240
+ }
241
+ }
242
+ }
243
+
247
244
  const sections = [
248
245
  options?.title || '## Execution Brief',
246
+ goalBlock,
249
247
  brief.parentContext ? `Parent context:\n${brief.parentContext}` : '',
250
248
  brief.objective ? `Objective: ${brief.objective}` : '',
251
249
  brief.summary ? `Summary: ${brief.summary}` : '',
252
250
  `Status: ${brief.status}`,
253
- brief.missionStatus ? `Mission status: ${brief.missionStatus}` : '',
254
- brief.missionPhase ? `Mission phase: ${brief.missionPhase}` : '',
255
- brief.waitState?.reason ? `Waiting reason: ${cleanText(brief.waitState.reason, 280)}` : '',
256
251
  brief.nextAction ? `Next action: ${brief.nextAction}` : '',
257
252
  brief.successCriteria.length > 0 ? `Success criteria: ${brief.successCriteria.join(' | ')}` : '',
258
253
  brief.constraints.length > 0 ? `Constraints: ${brief.constraints.join(' | ')}` : '',
@@ -238,7 +238,6 @@ export function enqueueTaskAttemptExecution(
238
238
  const run: SessionRunRecord = {
239
239
  id: executionId,
240
240
  sessionId: input.sessionId,
241
- missionId: input.task.missionId || null,
242
241
  kind: 'task_attempt',
243
242
  ownerType: 'task',
244
243
  ownerId: input.task.id,
@@ -0,0 +1,19 @@
1
+ import type { Goal } from '@/types'
2
+ import { loadGoals, loadGoal, upsertGoal, deleteGoalItem } from '@/lib/server/storage'
3
+ import { perf } from '@/lib/server/runtime/perf'
4
+
5
+ export function listGoals(): Record<string, Goal> {
6
+ return perf.measureSync('repository', 'goals.list', () => loadGoals()) as Record<string, Goal>
7
+ }
8
+
9
+ export function getGoal(id: string): Goal | null {
10
+ return perf.measureSync('repository', 'goals.get', () => loadGoal(id)) as Goal | null
11
+ }
12
+
13
+ export function saveGoal(id: string, goal: Goal): void {
14
+ perf.measureSync('repository', 'goals.upsert', () => upsertGoal(id, goal), { id })
15
+ }
16
+
17
+ export function removeGoal(id: string): void {
18
+ perf.measureSync('repository', 'goals.delete', () => deleteGoalItem(id), { id })
19
+ }