@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
@@ -24,8 +24,6 @@ import type {
24
24
  GuardianCheckpoint,
25
25
  LearnedSkill,
26
26
  Message,
27
- Mission,
28
- MissionEvent,
29
27
  ProtocolTemplate,
30
28
  ProtocolRun,
31
29
  ProtocolRunEvent,
@@ -138,9 +136,6 @@ const COLLECTIONS = [
138
136
  'webhook_retry_queue',
139
137
  'notifications',
140
138
  'chatrooms',
141
- 'wallets',
142
- 'wallet_transactions',
143
- 'wallet_balance_history',
144
139
  'moderation_logs',
145
140
  'connector_health',
146
141
  'connector_outbox',
@@ -152,8 +147,6 @@ const COLLECTIONS = [
152
147
  'watch_jobs',
153
148
  'delegation_jobs',
154
149
  'external_agents',
155
- 'missions',
156
- 'mission_events',
157
150
  'protocol_templates',
158
151
  'protocol_runs',
159
152
  'protocol_run_events',
@@ -162,6 +155,9 @@ const COLLECTIONS = [
162
155
  'main_loop_states',
163
156
  'working_states',
164
157
  'daemon_status',
158
+ 'wallets',
159
+ 'wallet_transactions',
160
+ 'goals',
165
161
  ] as const
166
162
 
167
163
  export type StorageCollection = (typeof COLLECTIONS)[number]
@@ -173,6 +169,10 @@ for (const table of COLLECTIONS) {
173
169
  // Index for efficient protocol_run_events queries by runId
174
170
  db.exec(`CREATE INDEX IF NOT EXISTS idx_protocol_run_events_runid ON protocol_run_events (json_extract(data, '$.runId'))`)
175
171
 
172
+ // Indexes for efficient activity log queries
173
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_timestamp ON activity (json_extract(data, '$.timestamp'))`)
174
+ db.exec(`CREATE INDEX IF NOT EXISTS idx_activity_entity ON activity (json_extract(data, '$.entityType'), json_extract(data, '$.entityId'))`)
175
+
176
176
  // Singleton tables (single row)
177
177
  db.exec(`CREATE TABLE IF NOT EXISTS settings (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
178
178
  db.exec(`CREATE TABLE IF NOT EXISTS queue (id INTEGER PRIMARY KEY CHECK (id = 1), data TEXT NOT NULL)`)
@@ -545,8 +545,6 @@ const JSON_FILES: Record<string, string> = {
545
545
  documents: path.join(DATA_DIR, 'documents.json'),
546
546
  webhooks: path.join(DATA_DIR, 'webhooks.json'),
547
547
  external_agents: path.join(DATA_DIR, 'external-agents.json'),
548
- missions: path.join(DATA_DIR, 'missions.json'),
549
- mission_events: path.join(DATA_DIR, 'mission-events.json'),
550
548
  protocol_templates: path.join(DATA_DIR, 'protocol-templates.json'),
551
549
  protocol_runs: path.join(DATA_DIR, 'protocol-runs.json'),
552
550
  protocol_run_events: path.join(DATA_DIR, 'protocol-run-events.json'),
@@ -1208,25 +1206,6 @@ export const loadProjects = projectsStore.load
1208
1206
  export const saveProjects = projectsStore.save
1209
1207
  export const deleteProject = projectsStore.deleteItem
1210
1208
 
1211
- // --- Missions ---
1212
- const missionsStore = createCollectionStore('missions')
1213
- export const loadMissions = missionsStore.load as () => Record<string, Mission>
1214
- export const saveMissions = missionsStore.save as (items: Record<string, Mission>) => void
1215
- export const loadMission = missionsStore.loadItem as (id: string) => Mission | null
1216
- export const upsertMission = missionsStore.upsert as (id: string, value: Mission) => void
1217
- export const patchMission = missionsStore.patch as (
1218
- id: string,
1219
- updater: (current: Mission | null) => Mission | null,
1220
- ) => Mission | null
1221
- export const deleteMission = missionsStore.deleteItem
1222
-
1223
- const missionEventsStore = createCollectionStore('mission_events')
1224
- export const loadMissionEvents = missionEventsStore.load as () => Record<string, MissionEvent>
1225
- export const saveMissionEvents = missionEventsStore.save as (items: Record<string, MissionEvent>) => void
1226
- export const loadMissionEvent = missionEventsStore.loadItem as (id: string) => MissionEvent | null
1227
- export const upsertMissionEvent = missionEventsStore.upsert as (id: string, value: MissionEvent) => void
1228
- export const upsertMissionEvents = missionEventsStore.upsertMany as (entries: Array<[string, MissionEvent]>) => void
1229
-
1230
1209
  const protocolTemplatesStore = createCollectionStore('protocol_templates')
1231
1210
  export const loadProtocolTemplates = protocolTemplatesStore.load as () => Record<string, ProtocolTemplate>
1232
1211
  export const saveProtocolTemplates = protocolTemplatesStore.save as (items: Record<string, ProtocolTemplate>) => void
@@ -1491,6 +1470,51 @@ export function logActivity(entry: {
1491
1470
  notify('activity')
1492
1471
  }
1493
1472
 
1473
+ /** Paginated activity query using SQL WHERE + LIMIT/OFFSET instead of loading the full collection. */
1474
+ export function queryActivity(filters: {
1475
+ entityType?: string
1476
+ entityId?: string
1477
+ actor?: string
1478
+ action?: string
1479
+ since?: number
1480
+ limit?: number
1481
+ offset?: number
1482
+ }): unknown[] {
1483
+ const conditions: string[] = []
1484
+ const params: unknown[] = []
1485
+
1486
+ if (filters.entityType) {
1487
+ conditions.push(`json_extract(data, '$.entityType') = ?`)
1488
+ params.push(filters.entityType)
1489
+ }
1490
+ if (filters.entityId) {
1491
+ conditions.push(`json_extract(data, '$.entityId') = ?`)
1492
+ params.push(filters.entityId)
1493
+ }
1494
+ if (filters.actor) {
1495
+ conditions.push(`json_extract(data, '$.actor') = ?`)
1496
+ params.push(filters.actor)
1497
+ }
1498
+ if (filters.action) {
1499
+ conditions.push(`json_extract(data, '$.action') = ?`)
1500
+ params.push(filters.action)
1501
+ }
1502
+ if (typeof filters.since === 'number' && Number.isFinite(filters.since)) {
1503
+ conditions.push(`json_extract(data, '$.timestamp') >= ?`)
1504
+ params.push(filters.since)
1505
+ }
1506
+
1507
+ const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
1508
+ const limit = Math.min(200, Math.max(1, filters.limit ?? 50))
1509
+ const offset = Math.max(0, filters.offset ?? 0)
1510
+
1511
+ const sql = `SELECT data FROM activity ${where} ORDER BY json_extract(data, '$.timestamp') DESC LIMIT ? OFFSET ?`
1512
+ params.push(limit, offset)
1513
+
1514
+ const rows = db.prepare(sql).all(...params) as Array<{ data: string }>
1515
+ return rows.map((r) => JSON.parse(r.data))
1516
+ }
1517
+
1494
1518
  // --- Webhook Retry Queue ---
1495
1519
  const webhookRetryQueueStore = createCollectionStore('webhook_retry_queue')
1496
1520
  export const loadWebhookRetryQueue = webhookRetryQueueStore.load
@@ -1541,23 +1565,6 @@ export function markNotificationRead(id: string) {
1541
1565
  }
1542
1566
  }
1543
1567
 
1544
- // --- Wallets ---
1545
- const walletsStore = createCollectionStore('wallets')
1546
- export const loadWallets = walletsStore.load
1547
- export const upsertWallet = walletsStore.upsert
1548
- export const deleteWallet = walletsStore.deleteItem
1549
-
1550
- // --- Wallet Transactions ---
1551
- const walletTransactionsStore = createCollectionStore('wallet_transactions')
1552
- export const loadWalletTransactions = walletTransactionsStore.load
1553
- export const upsertWalletTransaction = walletTransactionsStore.upsert
1554
- export const deleteWalletTransaction = walletTransactionsStore.deleteItem
1555
-
1556
- // --- Wallet Balance History ---
1557
- const walletBalanceHistoryStore = createCollectionStore('wallet_balance_history')
1558
- export const loadWalletBalanceHistory = walletBalanceHistoryStore.load
1559
- export const upsertWalletBalanceSnapshot = walletBalanceHistoryStore.upsert
1560
-
1561
1568
  // --- Moderation Logs ---
1562
1569
  const moderationLogsStore = createCollectionStore('moderation_logs')
1563
1570
  export const loadModerationLogs = moderationLogsStore.load
@@ -1613,6 +1620,29 @@ export const loadPersistedWorkingState = workingStatesStore.loadItem
1613
1620
  export const upsertPersistedWorkingState = workingStatesStore.upsert
1614
1621
  export const deletePersistedWorkingState = workingStatesStore.deleteItem
1615
1622
 
1623
+ // --- Wallets ---
1624
+ const walletsStore = createCollectionStore('wallets')
1625
+ export const loadWallets = walletsStore.load
1626
+ export const saveWallets = walletsStore.save
1627
+ export const loadWallet = walletsStore.loadItem
1628
+ export const upsertWallet = walletsStore.upsert
1629
+ export const deleteWalletItem = walletsStore.deleteItem
1630
+
1631
+ // --- Wallet Transactions ---
1632
+ const walletTransactionsStore = createCollectionStore('wallet_transactions')
1633
+ export const loadWalletTransactions = walletTransactionsStore.load
1634
+ export const saveWalletTransactions = walletTransactionsStore.save
1635
+ export const loadWalletTransaction = walletTransactionsStore.loadItem
1636
+ export const upsertWalletTransaction = walletTransactionsStore.upsert
1637
+ export const deleteWalletTransaction = walletTransactionsStore.deleteItem
1638
+
1639
+ // --- Goals ---
1640
+ const goalsStore = createCollectionStore('goals')
1641
+ export const loadGoals = goalsStore.load
1642
+ export const loadGoal = goalsStore.loadItem
1643
+ export const upsertGoal = goalsStore.upsert
1644
+ export const deleteGoalItem = goalsStore.deleteItem
1645
+
1616
1646
  export function getSessionMessages(sessionId: string): Message[] {
1617
1647
  const session = loadSession(sessionId)
1618
1648
  return Array.isArray(session?.messages) ? session.messages : []
@@ -0,0 +1,59 @@
1
+ import type { BoardTask } from '@/types'
2
+ import { withTransaction } from '@/lib/server/persistence/transaction'
3
+ import { loadTasks, saveTasks } from '@/lib/server/tasks/task-repository'
4
+
5
+ /**
6
+ * Atomically transition a task from queued → running with a checkout run ID.
7
+ *
8
+ * Uses a SQLite IMMEDIATE transaction to prevent two runners from starting the
9
+ * same task concurrently (Paperclip-inspired atomic checkout pattern).
10
+ *
11
+ * Returns the checked-out task on success, or null if the task was already
12
+ * taken, missing, or no longer in queued status.
13
+ */
14
+ export function checkoutTask(
15
+ taskId: string,
16
+ runId: string,
17
+ ): BoardTask | null {
18
+ return withTransaction(() => {
19
+ const tasks = loadTasks() as Record<string, BoardTask>
20
+ const task = tasks[taskId]
21
+ if (!task || task.status !== 'queued') return null
22
+ if (task.checkoutRunId) return null // already checked out
23
+
24
+ const now = Date.now()
25
+ task.status = 'running'
26
+ task.checkoutRunId = runId
27
+ task.startedAt = now
28
+ task.lastActivityAt = now
29
+ task.retryScheduledAt = null
30
+ task.deadLetteredAt = null
31
+ task.error = null
32
+ task.validation = null
33
+ task.updatedAt = now
34
+
35
+ saveTasks(tasks)
36
+ return { ...task }
37
+ })
38
+ }
39
+
40
+ /**
41
+ * Release a checkout after task completion or failure.
42
+ * Only the holder of the checkout (matching runId) can release it.
43
+ */
44
+ export function releaseCheckout(
45
+ taskId: string,
46
+ runId: string,
47
+ ): boolean {
48
+ return withTransaction(() => {
49
+ const tasks = loadTasks() as Record<string, BoardTask>
50
+ const task = tasks[taskId]
51
+ if (!task) return false
52
+ if (task.checkoutRunId !== runId) return false
53
+
54
+ task.checkoutRunId = null
55
+ task.updatedAt = Date.now()
56
+ saveTasks(tasks)
57
+ return true
58
+ })
59
+ }
@@ -176,6 +176,7 @@ export function markValidatedTaskCompleted(
176
176
  task.completedAt = options.preserveCompletedAt ? (task.completedAt || options.now) : options.now
177
177
  task.updatedAt = options.now
178
178
  task.error = null
179
+ task.checkoutRunId = null
179
180
  return task
180
181
  }
181
182
 
@@ -187,6 +188,7 @@ export function markInvalidCompletedTaskFailed(
187
188
  task.status = 'failed'
188
189
  task.completedAt = null
189
190
  task.updatedAt = options.now
191
+ task.checkoutRunId = null
190
192
  task.error = formatValidationFailure(validation.reasons).slice(0, 500)
191
193
  if (options.comment) {
192
194
  if (!task.comments) task.comments = []
@@ -6,11 +6,6 @@ import { logActivity } from '@/lib/server/activity/activity-log'
6
6
  import { createNotification } from '@/lib/server/create-notification'
7
7
  import { validateDag, cascadeUnblock } from '@/lib/server/dag-validation'
8
8
  import { getExtensionManager } from '@/lib/server/extensions'
9
- import {
10
- enrichTaskWithMissionSummary,
11
- ensureMissionForTask,
12
- noteMissionTaskFinished,
13
- } from '@/lib/server/missions/mission-service'
14
9
  import {
15
10
  disableSessionHeartbeat,
16
11
  enqueueTask,
@@ -64,9 +59,7 @@ export function prepareTasksForListing() {
64
59
  validateCompletedTasksQueue()
65
60
  recoverStalledRunningTasks()
66
61
  const allTasks = loadTasks()
67
- return Object.fromEntries(
68
- Object.entries(allTasks).map(([id, task]) => [id, enrichTaskWithMissionSummary(task)]),
69
- )
62
+ return allTasks
70
63
  }
71
64
 
72
65
  export function updateTaskFromRoute(id: string, body: Record<string, unknown>): ServiceResult<BoardTask> {
@@ -128,10 +121,6 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
128
121
  }
129
122
 
130
123
  saveTask(id, tasks[id])
131
- const mission = ensureMissionForTask(tasks[id], { source: 'manual' })
132
- if (tasks[id].status === 'completed' || tasks[id].status === 'failed' || tasks[id].status === 'cancelled') {
133
- noteMissionTaskFinished(tasks[id], tasks[id].status, tasks[id].id)
134
- }
135
124
  logActivity({ entityType: 'task', entityId: id, action: 'updated', actor: 'user', summary: `Task updated: "${tasks[id].title}" (${prevStatus} → ${tasks[id].status})` })
136
125
  if (prevStatus !== tasks[id].status) {
137
126
  pushMainLoopEventToMainSessions({
@@ -143,10 +132,7 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
143
132
  if (prevStatus !== tasks[id].status && tasks[id].status === 'cancelled') {
144
133
  disableSessionHeartbeat(tasks[id].sessionId)
145
134
  notify('tasks')
146
- return serviceOk(enrichTaskWithMissionSummary({
147
- ...tasks[id],
148
- missionId: mission?.id || tasks[id].missionId || null,
149
- }))
135
+ return serviceOk(tasks[id])
150
136
  }
151
137
 
152
138
  if (prevStatus !== tasks[id].status && (tasks[id].status === 'completed' || tasks[id].status === 'failed')) {
@@ -216,10 +202,7 @@ export function updateTaskFromRoute(id: string, body: Record<string, unknown>):
216
202
  }
217
203
 
218
204
  notify('tasks')
219
- return serviceOk(enrichTaskWithMissionSummary({
220
- ...tasks[id],
221
- missionId: mission?.id || tasks[id].missionId || null,
222
- }))
205
+ return serviceOk(tasks[id])
223
206
  }
224
207
 
225
208
  export function archiveTaskFromRoute(id: string): ServiceResult<BoardTask> {
@@ -346,11 +329,6 @@ export function createTaskFromRoute(body: Record<string, unknown>): ServiceResul
346
329
  }
347
330
 
348
331
  saveTask(id, task)
349
- const mission = ensureMissionForTask(task, { source: 'manual' })
350
- const finalTask = enrichTaskWithMissionSummary({
351
- ...task,
352
- missionId: mission?.id || task.missionId || null,
353
- })
354
332
  logActivity({ entityType: 'task', entityId: id, action: 'created', actor: 'user', summary: `Task created: "${task.title}"` })
355
333
  pushMainLoopEventToMainSessions({
356
334
  type: 'task_created',
@@ -360,7 +338,7 @@ export function createTaskFromRoute(body: Record<string, unknown>): ServiceResul
360
338
  enqueueTask(id)
361
339
  }
362
340
  notify('tasks')
363
- return serviceOk(finalTask)
341
+ return serviceOk(task)
364
342
  }
365
343
 
366
344
  export function bulkUpdateTasksFromRoute(body: Record<string, unknown>): ServiceResult<{ updated: number; ids: string[] }> {
@@ -106,13 +106,6 @@ export function applyTaskContinuationDefaults(
106
106
  if (!Object.prototype.hasOwnProperty.call(explicit, 'cwd') && typeof sourceTask.cwd === 'string' && sourceTask.cwd.trim()) {
107
107
  parsed.cwd = sourceTask.cwd.trim()
108
108
  }
109
- if (
110
- !Object.prototype.hasOwnProperty.call(explicit, 'missionId')
111
- && typeof sourceTask.missionId === 'string'
112
- && sourceTask.missionId.trim()
113
- ) {
114
- parsed.missionId = sourceTask.missionId.trim()
115
- }
116
109
  const sourceSessionId = typeof sourceTask.checkpoint?.lastSessionId === 'string' && sourceTask.checkpoint.lastSessionId.trim()
117
110
  ? sourceTask.checkpoint.lastSessionId.trim()
118
111
  : typeof sourceTask.sessionId === 'string' && sourceTask.sessionId.trim()
@@ -22,7 +22,6 @@ const EXTENSION_ALIAS_GROUPS: string[][] = [
22
22
  ['schedule_wake', 'schedule'],
23
23
  // http_request/http now aliased into web group above
24
24
  ['memory', 'memory_tool', 'memory_search', 'memory_get', 'memory_store', 'memory_update'],
25
- ['wallet', 'wallet_tool'],
26
25
  ['monitor', 'monitor_tool'],
27
26
  ['context_mgmt', 'context_status', 'context_summarize'],
28
27
  ['openclaw_workspace'],
@@ -423,17 +423,17 @@ describe('compound scenarios', () => {
423
423
  assert.match(tasksBlock.reason, /task management is disabled/)
424
424
  })
425
425
 
426
- it('20 tools requested: correctly partitioned into enabled vs blocked', () => {
426
+ it('19 tools requested: correctly partitioned into enabled vs blocked', () => {
427
427
  const tools = [
428
428
  'shell', 'files', 'web', 'web_search', 'web_fetch', 'browser',
429
429
  'memory', 'delegate', 'manage_platform', 'manage_tasks',
430
- 'manage_schedules', 'wallet', 'delete_file', 'canvas',
430
+ 'manage_schedules', 'wallet', 'delete_file',
431
431
  'manage_connectors', 'git', 'sandbox', 'claude_code',
432
432
  'monitor', 'http_request',
433
433
  ]
434
434
  const d = resolveSessionToolPolicy(tools, { capabilityPolicyMode: 'strict' })
435
- assert.equal(d.requestedExtensions.length, 20)
436
- assert.equal(d.enabledExtensions.length + d.blockedExtensions.length, 20)
435
+ assert.equal(d.requestedExtensions.length, 19)
436
+ assert.equal(d.enabledExtensions.length + d.blockedExtensions.length, 19)
437
437
 
438
438
  // memory, web, web_search, web_fetch should be enabled
439
439
  assert.ok(d.enabledExtensions.includes('memory'))
@@ -60,8 +60,6 @@ const TOOL_DESCRIPTORS: Record<string, ToolDescriptor> = {
60
60
  gemini_cli: { categories: ['delegation', 'execution'], concreteTools: ['delegate_to_gemini_cli'] },
61
61
  memory: { categories: ['memory'], concreteTools: ['memory', 'memory_tool', 'memory_search', 'memory_get', 'memory_store', 'memory_update', 'context_status', 'context_summarize'] },
62
62
  // http_request consolidated into web 'api' action — no separate descriptor
63
- canvas: { categories: ['filesystem'], concreteTools: ['canvas'] },
64
- wallet: { categories: ['outbound'], concreteTools: ['wallet', 'wallet_tool'] },
65
63
  monitor: { categories: ['execution'], concreteTools: ['monitor', 'monitor_tool'] },
66
64
  openclaw_workspace: { categories: ['filesystem', 'platform'], concreteTools: ['openclaw_workspace'] },
67
65
  openclaw_nodes: { categories: ['platform'], concreteTools: ['openclaw_nodes'] },
@@ -13,8 +13,6 @@ export const TOOL_CAPABILITY = {
13
13
  deliveryMessage: 'delivery.message',
14
14
  deliveryMedia: 'delivery.media',
15
15
  deliveryVoiceNote: 'delivery.voice_note',
16
- walletInspect: 'wallet.inspect',
17
- walletExecute: 'wallet.execute',
18
16
  } as const
19
17
 
20
18
  export interface ToolPlanningEntry {
@@ -373,16 +371,6 @@ const CORE_TOOL_PLANNING: Record<string, LegacyToolPlanningEntry[]> = {
373
371
  requestMatchers: [],
374
372
  },
375
373
  ],
376
- wallet: [
377
- {
378
- toolName: 'wallet_tool',
379
- capabilities: [TOOL_CAPABILITY.walletInspect, TOOL_CAPABILITY.walletExecute],
380
- disciplineGuidance: [
381
- 'Inspect wallet state once, then act. Use `simulate_transaction` to validate before executing. Do not re-inspect balances between each operation unless the operation changes them.',
382
- ],
383
- requestMatchers: [],
384
- },
385
- ],
386
374
  image_gen: [
387
375
  {
388
376
  toolName: 'generate_image',
@@ -22,7 +22,6 @@ const UNIVERSAL_CORE_EXTENSION_IDS = [
22
22
  'manage_secrets',
23
23
  'manage_chatrooms',
24
24
  'spawn_subagent',
25
- 'canvas',
26
25
  'http_request',
27
26
  'wallet',
28
27
  'monitor',
@@ -0,0 +1,124 @@
1
+ import type { Agent } from '@/types'
2
+ import { patchAgent } from '@/lib/server/agents/agent-repository'
3
+ import { logActivity } from '@/lib/server/activity/activity-log'
4
+ import { notify } from '@/lib/server/ws-hub'
5
+ import { log } from '@/lib/server/logger'
6
+
7
+ const TAG = 'cost-rollup'
8
+
9
+ const ONE_HOUR_MS = 60 * 60 * 1000
10
+
11
+ function toWindowBoundaries(now: number) {
12
+ const d = new Date(now)
13
+ const dayStart = new Date(d)
14
+ dayStart.setUTCHours(0, 0, 0, 0)
15
+ const monthStart = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1))
16
+ return {
17
+ hourStartTs: now - ONE_HOUR_MS,
18
+ dayStartTs: dayStart.getTime(),
19
+ monthStartTs: monthStart.getTime(),
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Push-based cost rollup: atomically increments the agent's persisted spend
25
+ * fields and checks budgets. Called immediately after each usage record is appended.
26
+ */
27
+ export function rollupCostToAgent(agentId: string, costUsd: number): void {
28
+ if (!agentId || !Number.isFinite(costUsd) || costUsd <= 0) return
29
+
30
+ const now = Date.now()
31
+ const costCents = Math.round(costUsd * 100)
32
+ const { hourStartTs, dayStartTs, monthStartTs } = toWindowBoundaries(now)
33
+
34
+ const updated = patchAgent(agentId, (current) => {
35
+ if (!current) return null
36
+ const lastRollup = current.lastSpendRollupAt ?? 0
37
+ const lastBounds = toWindowBoundaries(lastRollup)
38
+
39
+ // Reset windows that have rolled over since last rollup
40
+ let hourly = current.spentHourlyCents ?? 0
41
+ let daily = current.spentDailyCents ?? 0
42
+ let monthly = current.spentMonthlyCents ?? 0
43
+
44
+ if (lastRollup < hourStartTs) hourly = 0
45
+ if (lastBounds.dayStartTs < dayStartTs) daily = 0
46
+ if (lastBounds.monthStartTs < monthStartTs) monthly = 0
47
+
48
+ return {
49
+ ...current,
50
+ spentHourlyCents: hourly + costCents,
51
+ spentDailyCents: daily + costCents,
52
+ spentMonthlyCents: monthly + costCents,
53
+ lastSpendRollupAt: now,
54
+ }
55
+ })
56
+
57
+ if (!updated) return
58
+
59
+ // Check budgets and enforce
60
+ checkAndEnforceBudget(updated)
61
+ }
62
+
63
+ /**
64
+ * Checks agent's persisted spend against configured budgets and logs
65
+ * activity entries when thresholds are hit.
66
+ */
67
+ function checkAndEnforceBudget(agent: Agent): void {
68
+ const WARNING_RATIO = 0.8
69
+
70
+ const windows = [
71
+ { key: 'hourly' as const, budget: agent.hourlyBudget, spent: (agent.spentHourlyCents ?? 0) / 100 },
72
+ { key: 'daily' as const, budget: agent.dailyBudget, spent: (agent.spentDailyCents ?? 0) / 100 },
73
+ { key: 'monthly' as const, budget: agent.monthlyBudget, spent: (agent.spentMonthlyCents ?? 0) / 100 },
74
+ ]
75
+
76
+ for (const { key, budget, spent } of windows) {
77
+ if (!budget || !Number.isFinite(budget) || budget <= 0) continue
78
+ const ratio = spent / budget
79
+
80
+ if (ratio >= 1) {
81
+ log.warn(TAG, `Agent "${agent.name}" exceeded ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)}`)
82
+ logActivity({
83
+ entityType: 'budget',
84
+ entityId: agent.id,
85
+ action: 'budget_exceeded',
86
+ actor: 'system',
87
+ summary: `Agent "${agent.name}" exceeded ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)}`,
88
+ detail: { window: key, spent, budget, ratio },
89
+ })
90
+ notify('agents')
91
+ } else if (ratio >= WARNING_RATIO) {
92
+ logActivity({
93
+ entityType: 'budget',
94
+ entityId: agent.id,
95
+ action: 'budget_warning',
96
+ actor: 'system',
97
+ summary: `Agent "${agent.name}" nearing ${key} budget: $${spent.toFixed(4)} / $${budget.toFixed(2)} (${Math.round(ratio * 100)}%)`,
98
+ detail: { window: key, spent, budget, ratio },
99
+ })
100
+ }
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Reset all agents' daily spend counters. Call at UTC midnight.
106
+ */
107
+ export function resetDailySpends(agents: Record<string, Agent>): void {
108
+ for (const agent of Object.values(agents)) {
109
+ if ((agent.spentDailyCents ?? 0) > 0) {
110
+ patchAgent(agent.id, (current) => current ? { ...current, spentDailyCents: 0 } : null)
111
+ }
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Reset all agents' monthly spend counters. Call at UTC month boundary.
117
+ */
118
+ export function resetMonthlySpends(agents: Record<string, Agent>): void {
119
+ for (const agent of Object.values(agents)) {
120
+ if ((agent.spentMonthlyCents ?? 0) > 0) {
121
+ patchAgent(agent.id, (current) => current ? { ...current, spentMonthlyCents: 0 } : null)
122
+ }
123
+ }
124
+ }
@@ -8,6 +8,7 @@ import {
8
8
  saveUsage as saveStoredUsage,
9
9
  } from '@/lib/server/storage'
10
10
  import { perf } from '@/lib/server/runtime/perf'
11
+ import { rollupCostToAgent } from './cost-rollup'
11
12
 
12
13
  export function loadUsage(): Record<string, UsageRecord[]> {
13
14
  return perf.measureSync('repository', 'usage.list', () => loadStoredUsage())
@@ -19,6 +20,11 @@ export function saveUsage(data: Record<string, UsageRecord[]>): void {
19
20
 
20
21
  export function appendUsage(sessionId: string, record: unknown): void {
21
22
  perf.measureSync('repository', 'usage.append', () => appendStoredUsage(sessionId, record), { sessionId })
23
+ // Push-based cost rollup: update persisted spend fields and check budgets
24
+ const rec = record as Partial<UsageRecord>
25
+ if (rec.agentId && typeof rec.estimatedCost === 'number' && rec.estimatedCost > 0) {
26
+ rollupCostToAgent(rec.agentId, rec.estimatedCost)
27
+ }
22
28
  }
23
29
 
24
30
  export function getUsageSpendSince(minTimestamp: number): number {
@@ -0,0 +1,33 @@
1
+ import { encryptKey, decryptKey } from '@/lib/server/storage'
2
+
3
+ /**
4
+ * Generate a new Ethereum wallet (Base L2 compatible) with an encrypted private key.
5
+ * Uses ethers v6 for keypair generation and the existing AES-256-GCM encryption
6
+ * from storage.ts (CREDENTIAL_SECRET env var).
7
+ */
8
+ export async function generateEthereumWallet(): Promise<{ address: string; encryptedPrivateKey: string }> {
9
+ const { Wallet } = await import('ethers')
10
+ const wallet = Wallet.createRandom()
11
+ const encryptedPrivateKey = encryptKey(wallet.privateKey)
12
+ return {
13
+ address: wallet.address,
14
+ encryptedPrivateKey,
15
+ }
16
+ }
17
+
18
+ export async function normalizeEthereumAddress(address: string): Promise<string | null> {
19
+ const { getAddress } = await import('ethers')
20
+ try {
21
+ return getAddress(address.trim())
22
+ } catch {
23
+ return null
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Decrypt a wallet's private key for server-side use only.
29
+ * Never expose the result to API responses or the frontend.
30
+ */
31
+ export function decryptWalletPrivateKey(encrypted: string): string {
32
+ return decryptKey(encrypted)
33
+ }
@@ -0,0 +1,24 @@
1
+ import {
2
+ loadWallets as loadWalletsStore,
3
+ saveWallets as saveWalletsStore,
4
+ loadWallet as loadWalletItem,
5
+ upsertWallet,
6
+ deleteWalletItem,
7
+ } from '@/lib/server/storage'
8
+ import type { AgentWallet } from '@/types/swarmdock'
9
+
10
+ export function loadWallets(): Record<string, AgentWallet> {
11
+ return loadWalletsStore() as Record<string, AgentWallet>
12
+ }
13
+
14
+ export function loadWallet(id: string): AgentWallet | null {
15
+ return loadWalletItem(id) as AgentWallet | null
16
+ }
17
+
18
+ export function saveWallet(id: string, wallet: AgentWallet): void {
19
+ upsertWallet(id, wallet)
20
+ }
21
+
22
+ export function deleteWallet(id: string): void {
23
+ deleteWalletItem(id)
24
+ }