@swarmclawai/swarmclaw 0.7.7 → 0.8.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 (281) hide show
  1. package/README.md +12 -14
  2. package/next.config.ts +13 -2
  3. package/package.json +4 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +9 -0
  5. package/src/app/api/agents/route.ts +4 -0
  6. package/src/app/api/agents/thread-route.test.ts +133 -0
  7. package/src/app/api/approvals/route.test.ts +148 -0
  8. package/src/app/api/canvas/[sessionId]/route.ts +3 -1
  9. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -2
  10. package/src/app/api/chats/[id]/devserver/route.ts +48 -7
  11. package/src/app/api/chats/[id]/messages/route.ts +42 -18
  12. package/src/app/api/chats/[id]/route.ts +1 -1
  13. package/src/app/api/chats/[id]/stop/route.ts +5 -4
  14. package/src/app/api/chats/route.ts +23 -2
  15. package/src/app/api/clawhub/install/route.ts +28 -8
  16. package/src/app/api/connectors/[id]/route.ts +46 -3
  17. package/src/app/api/connectors/route.ts +12 -8
  18. package/src/app/api/external-agents/route.test.ts +165 -0
  19. package/src/app/api/gateways/[id]/health/route.ts +27 -12
  20. package/src/app/api/gateways/[id]/route.ts +2 -0
  21. package/src/app/api/gateways/health-route.test.ts +135 -0
  22. package/src/app/api/gateways/route.ts +2 -0
  23. package/src/app/api/mcp-servers/route.test.ts +130 -0
  24. package/src/app/api/openclaw/deploy/route.ts +38 -5
  25. package/src/app/api/plugins/install/route.ts +46 -6
  26. package/src/app/api/plugins/marketplace/route.ts +48 -15
  27. package/src/app/api/preview-server/route.ts +26 -11
  28. package/src/app/api/projects/[id]/route.ts +6 -2
  29. package/src/app/api/projects/route.ts +4 -3
  30. package/src/app/api/schedules/[id]/run/route.ts +4 -0
  31. package/src/app/api/schedules/route.test.ts +86 -0
  32. package/src/app/api/schedules/route.ts +6 -1
  33. package/src/app/api/secrets/[id]/route.ts +1 -0
  34. package/src/app/api/secrets/route.ts +2 -1
  35. package/src/app/api/settings/route.ts +2 -0
  36. package/src/app/api/setup/check-provider/route.test.ts +19 -0
  37. package/src/app/api/setup/check-provider/route.ts +40 -10
  38. package/src/app/api/skills/[id]/route.ts +12 -0
  39. package/src/app/api/skills/import/route.ts +14 -12
  40. package/src/app/api/skills/route.ts +13 -1
  41. package/src/app/api/tasks/[id]/route.ts +10 -1
  42. package/src/app/api/tasks/import/github/route.test.ts +65 -0
  43. package/src/app/api/tasks/import/github/route.ts +337 -0
  44. package/src/app/api/wallets/[id]/approve/route.ts +17 -3
  45. package/src/app/api/wallets/[id]/route.ts +79 -33
  46. package/src/app/api/wallets/[id]/send/route.ts +19 -33
  47. package/src/app/api/wallets/route.ts +78 -61
  48. package/src/app/api/webhooks/[id]/route.ts +33 -6
  49. package/src/app/api/webhooks/route.test.ts +272 -0
  50. package/src/cli/index.js +1 -0
  51. package/src/cli/spec.js +1 -0
  52. package/src/components/agents/agent-card.tsx +9 -2
  53. package/src/components/agents/agent-chat-list.tsx +18 -2
  54. package/src/components/agents/agent-list.tsx +1 -0
  55. package/src/components/agents/agent-sheet.tsx +257 -38
  56. package/src/components/agents/inspector-panel.tsx +41 -0
  57. package/src/components/canvas/canvas-panel.tsx +236 -65
  58. package/src/components/chat/chat-area.tsx +36 -19
  59. package/src/components/chat/chat-card.tsx +36 -13
  60. package/src/components/chat/chat-header.tsx +48 -16
  61. package/src/components/chat/chat-list.tsx +28 -4
  62. package/src/components/chat/checkpoint-timeline.tsx +50 -34
  63. package/src/components/chat/delegation-banner.test.ts +14 -1
  64. package/src/components/chat/delegation-banner.tsx +1 -1
  65. package/src/components/chat/message-bubble.tsx +208 -145
  66. package/src/components/chat/message-list.tsx +48 -19
  67. package/src/components/chatrooms/chatroom-message.tsx +2 -2
  68. package/src/components/chatrooms/chatroom-sheet.tsx +16 -2
  69. package/src/components/connectors/connector-health.tsx +1 -1
  70. package/src/components/connectors/connector-list.tsx +7 -2
  71. package/src/components/connectors/connector-sheet.tsx +337 -148
  72. package/src/components/gateways/gateway-sheet.tsx +2 -2
  73. package/src/components/layout/app-layout.tsx +40 -23
  74. package/src/components/mcp-servers/mcp-server-list.tsx +26 -5
  75. package/src/components/mcp-servers/mcp-server-sheet.tsx +19 -2
  76. package/src/components/openclaw/openclaw-deploy-panel.tsx +269 -21
  77. package/src/components/plugins/plugin-list.tsx +45 -9
  78. package/src/components/plugins/plugin-sheet.tsx +55 -7
  79. package/src/components/projects/project-detail.tsx +217 -0
  80. package/src/components/projects/project-sheet.tsx +176 -4
  81. package/src/components/providers/provider-list.tsx +2 -1
  82. package/src/components/providers/provider-sheet.tsx +21 -2
  83. package/src/components/schedules/schedule-card.tsx +25 -1
  84. package/src/components/schedules/schedule-sheet.tsx +44 -2
  85. package/src/components/secrets/secret-sheet.tsx +21 -2
  86. package/src/components/shared/agent-switch-dialog.tsx +12 -1
  87. package/src/components/shared/bottom-sheet.tsx +13 -3
  88. package/src/components/shared/command-palette.tsx +8 -1
  89. package/src/components/shared/confirm-dialog.tsx +19 -4
  90. package/src/components/shared/connector-platform-icon.test.ts +28 -0
  91. package/src/components/shared/connector-platform-icon.tsx +39 -6
  92. package/src/components/shared/settings/plugin-manager.tsx +29 -6
  93. package/src/components/shared/settings/section-capability-policy.tsx +45 -3
  94. package/src/components/shared/settings/section-voice.tsx +11 -3
  95. package/src/components/skills/skill-list.tsx +25 -0
  96. package/src/components/skills/skill-sheet.tsx +84 -12
  97. package/src/components/tasks/approvals-panel.tsx +289 -34
  98. package/src/components/tasks/task-board.tsx +410 -25
  99. package/src/components/tasks/task-card.tsx +66 -8
  100. package/src/components/tasks/task-sheet.tsx +16 -4
  101. package/src/components/ui/dialog.tsx +2 -2
  102. package/src/components/wallets/wallet-approval-dialog.tsx +4 -2
  103. package/src/components/wallets/wallet-panel.tsx +435 -90
  104. package/src/components/wallets/wallet-section.tsx +198 -48
  105. package/src/components/webhooks/webhook-sheet.tsx +22 -2
  106. package/src/lib/approval-display.ts +20 -0
  107. package/src/lib/canvas-content.ts +198 -0
  108. package/src/lib/chat-artifact-summary.ts +165 -0
  109. package/src/lib/chat-display.test.ts +91 -0
  110. package/src/lib/chat-display.ts +58 -0
  111. package/src/lib/chat-streaming-state.test.ts +47 -1
  112. package/src/lib/chat-streaming-state.ts +42 -0
  113. package/src/lib/ollama-model.ts +10 -0
  114. package/src/lib/openclaw-endpoint.test.ts +8 -0
  115. package/src/lib/openclaw-endpoint.ts +6 -1
  116. package/src/lib/plugin-install-cors.ts +46 -0
  117. package/src/lib/plugin-sources.test.ts +43 -0
  118. package/src/lib/plugin-sources.ts +77 -0
  119. package/src/lib/providers/ollama.ts +16 -6
  120. package/src/lib/providers/openclaw.test.ts +54 -0
  121. package/src/lib/providers/openclaw.ts +127 -11
  122. package/src/lib/schedule-dedupe-advanced.test.ts +1335 -0
  123. package/src/lib/schedule-dedupe.test.ts +66 -1
  124. package/src/lib/schedule-dedupe.ts +169 -12
  125. package/src/lib/schedule-origin.test.ts +20 -0
  126. package/src/lib/schedule-origin.ts +15 -0
  127. package/src/lib/server/__fixtures__/fake-mcp-stdio-server.mjs +27 -0
  128. package/src/lib/server/agent-availability.ts +16 -0
  129. package/src/lib/server/agent-runtime-config.ts +12 -4
  130. package/src/lib/server/agent-thread-session.test.ts +51 -0
  131. package/src/lib/server/agent-thread-session.ts +7 -0
  132. package/src/lib/server/approval-match.ts +205 -0
  133. package/src/lib/server/approvals-auto-approve.test.ts +538 -1
  134. package/src/lib/server/approvals.ts +214 -1
  135. package/src/lib/server/assistant-control.test.ts +29 -0
  136. package/src/lib/server/assistant-control.ts +23 -0
  137. package/src/lib/server/build-llm.test.ts +79 -0
  138. package/src/lib/server/build-llm.ts +14 -4
  139. package/src/lib/server/canvas-content.test.ts +32 -0
  140. package/src/lib/server/canvas-content.ts +6 -0
  141. package/src/lib/server/capability-router.test.ts +33 -0
  142. package/src/lib/server/capability-router.ts +80 -19
  143. package/src/lib/server/chat-execution-advanced.test.ts +651 -0
  144. package/src/lib/server/chat-execution-disabled.test.ts +94 -0
  145. package/src/lib/server/chat-execution-tool-events.test.ts +157 -0
  146. package/src/lib/server/chat-execution.ts +378 -73
  147. package/src/lib/server/clawhub-client.test.ts +14 -8
  148. package/src/lib/server/connectors/manager-reconnect.test.ts +47 -0
  149. package/src/lib/server/connectors/manager.test.ts +1147 -0
  150. package/src/lib/server/connectors/manager.ts +461 -137
  151. package/src/lib/server/connectors/pairing.ts +26 -5
  152. package/src/lib/server/connectors/types.ts +2 -0
  153. package/src/lib/server/connectors/whatsapp.test.ts +134 -0
  154. package/src/lib/server/connectors/whatsapp.ts +271 -47
  155. package/src/lib/server/context-manager.ts +6 -1
  156. package/src/lib/server/daemon-state.ts +84 -47
  157. package/src/lib/server/data-dir.test.ts +37 -0
  158. package/src/lib/server/data-dir.ts +20 -1
  159. package/src/lib/server/delegation-jobs-advanced.test.ts +513 -0
  160. package/src/lib/server/devserver-launch.test.ts +60 -0
  161. package/src/lib/server/devserver-launch.ts +85 -0
  162. package/src/lib/server/elevenlabs.test.ts +247 -1
  163. package/src/lib/server/elevenlabs.ts +147 -43
  164. package/src/lib/server/ethereum.ts +590 -0
  165. package/src/lib/server/eval/agent-regression-advanced.test.ts +302 -0
  166. package/src/lib/server/eval/agent-regression.test.ts +18 -1
  167. package/src/lib/server/eval/agent-regression.ts +383 -11
  168. package/src/lib/server/evm-swap.ts +475 -0
  169. package/src/lib/server/execution-log.ts +1 -0
  170. package/src/lib/server/heartbeat-service-timer.test.ts +173 -0
  171. package/src/lib/server/heartbeat-service.ts +20 -11
  172. package/src/lib/server/heartbeat-wake.test.ts +112 -0
  173. package/src/lib/server/heartbeat-wake.ts +338 -57
  174. package/src/lib/server/main-agent-loop-advanced.test.ts +538 -0
  175. package/src/lib/server/main-agent-loop.test.ts +260 -0
  176. package/src/lib/server/main-agent-loop.ts +559 -14
  177. package/src/lib/server/mcp-client.test.ts +16 -0
  178. package/src/lib/server/mcp-client.ts +25 -0
  179. package/src/lib/server/memory-integration.test.ts +719 -0
  180. package/src/lib/server/memory-policy.test.ts +43 -0
  181. package/src/lib/server/memory-policy.ts +132 -0
  182. package/src/lib/server/memory-tiers.test.ts +60 -0
  183. package/src/lib/server/memory-tiers.ts +16 -0
  184. package/src/lib/server/ollama-runtime.ts +58 -0
  185. package/src/lib/server/openclaw-deploy.test.ts +109 -1
  186. package/src/lib/server/openclaw-deploy.ts +557 -81
  187. package/src/lib/server/openclaw-gateway.test.ts +131 -0
  188. package/src/lib/server/openclaw-gateway.ts +10 -4
  189. package/src/lib/server/openclaw-health.test.ts +35 -0
  190. package/src/lib/server/openclaw-health.ts +215 -47
  191. package/src/lib/server/orchestrator-lg.ts +3 -2
  192. package/src/lib/server/orchestrator.ts +2 -0
  193. package/src/lib/server/plugins-advanced.test.ts +351 -0
  194. package/src/lib/server/plugins.ts +211 -6
  195. package/src/lib/server/project-context.ts +162 -0
  196. package/src/lib/server/project-utils.ts +150 -0
  197. package/src/lib/server/queue-advanced.test.ts +528 -0
  198. package/src/lib/server/queue-followups.test.ts +409 -2
  199. package/src/lib/server/queue-reconcile.test.ts +128 -0
  200. package/src/lib/server/queue.ts +527 -68
  201. package/src/lib/server/scheduler.ts +29 -1
  202. package/src/lib/server/session-note.test.ts +36 -0
  203. package/src/lib/server/session-note.ts +42 -0
  204. package/src/lib/server/session-run-manager.ts +83 -4
  205. package/src/lib/server/session-tools/canvas.ts +14 -12
  206. package/src/lib/server/session-tools/connector-inputs.test.ts +37 -0
  207. package/src/lib/server/session-tools/connector.test.ts +138 -0
  208. package/src/lib/server/session-tools/connector.ts +366 -54
  209. package/src/lib/server/session-tools/context.ts +17 -3
  210. package/src/lib/server/session-tools/crud.ts +484 -84
  211. package/src/lib/server/session-tools/delegate-fallback.test.ts +103 -0
  212. package/src/lib/server/session-tools/delegate-resume.test.ts +50 -0
  213. package/src/lib/server/session-tools/delegate.ts +102 -10
  214. package/src/lib/server/session-tools/discovery-approvals.test.ts +142 -0
  215. package/src/lib/server/session-tools/discovery.ts +80 -12
  216. package/src/lib/server/session-tools/file-normalize.test.ts +36 -0
  217. package/src/lib/server/session-tools/file.ts +43 -4
  218. package/src/lib/server/session-tools/human-loop.ts +35 -5
  219. package/src/lib/server/session-tools/index.ts +44 -9
  220. package/src/lib/server/session-tools/manage-connectors.test.ts +139 -0
  221. package/src/lib/server/session-tools/manage-schedules-advanced.test.ts +564 -0
  222. package/src/lib/server/session-tools/manage-schedules.test.ts +283 -0
  223. package/src/lib/server/session-tools/manage-tasks-advanced.test.ts +852 -0
  224. package/src/lib/server/session-tools/manage-tasks.test.ts +114 -0
  225. package/src/lib/server/session-tools/memory.test.ts +93 -0
  226. package/src/lib/server/session-tools/memory.ts +554 -75
  227. package/src/lib/server/session-tools/normalize-tool-args.ts +1 -1
  228. package/src/lib/server/session-tools/platform-access.test.ts +58 -0
  229. package/src/lib/server/session-tools/platform.ts +60 -19
  230. package/src/lib/server/session-tools/plugin-creator.ts +57 -1
  231. package/src/lib/server/session-tools/primitive-tools.test.ts +6 -0
  232. package/src/lib/server/session-tools/schedule.ts +6 -1
  233. package/src/lib/server/session-tools/shell-normalize.test.ts +25 -1
  234. package/src/lib/server/session-tools/shell.ts +22 -3
  235. package/src/lib/server/session-tools/wallet-tool.test.ts +254 -0
  236. package/src/lib/server/session-tools/wallet.ts +1374 -139
  237. package/src/lib/server/session-tools/web-inputs.test.ts +178 -0
  238. package/src/lib/server/session-tools/web.ts +621 -70
  239. package/src/lib/server/skill-discovery.ts +128 -0
  240. package/src/lib/server/skill-eligibility.test.ts +84 -0
  241. package/src/lib/server/skill-eligibility.ts +95 -0
  242. package/src/lib/server/skill-prompt-budget.test.ts +102 -0
  243. package/src/lib/server/skill-prompt-budget.ts +125 -0
  244. package/src/lib/server/skills-normalize.test.ts +54 -0
  245. package/src/lib/server/skills-normalize.ts +372 -26
  246. package/src/lib/server/solana.ts +214 -29
  247. package/src/lib/server/storage.ts +65 -36
  248. package/src/lib/server/stream-agent-chat.test.ts +437 -2
  249. package/src/lib/server/stream-agent-chat.ts +957 -79
  250. package/src/lib/server/system-events.ts +1 -1
  251. package/src/lib/server/tool-aliases.ts +2 -0
  252. package/src/lib/server/tool-capability-policy-advanced.test.ts +502 -0
  253. package/src/lib/server/tool-capability-policy.test.ts +24 -0
  254. package/src/lib/server/tool-capability-policy.ts +29 -1
  255. package/src/lib/server/tool-loop-detection.test.ts +105 -0
  256. package/src/lib/server/tool-loop-detection.ts +260 -0
  257. package/src/lib/server/tool-planning.test.ts +44 -0
  258. package/src/lib/server/tool-planning.ts +271 -0
  259. package/src/lib/server/wallet-execution.test.ts +198 -0
  260. package/src/lib/server/wallet-portfolio.test.ts +98 -0
  261. package/src/lib/server/wallet-portfolio.ts +724 -0
  262. package/src/lib/server/wallet-service.test.ts +57 -0
  263. package/src/lib/server/wallet-service.ts +213 -0
  264. package/src/lib/server/watch-jobs-advanced.test.ts +594 -0
  265. package/src/lib/server/watch-jobs.ts +17 -2
  266. package/src/lib/server/workspace-context.ts +111 -0
  267. package/src/lib/skill-save-payload.test.ts +39 -0
  268. package/src/lib/skill-save-payload.ts +37 -0
  269. package/src/lib/tasks.ts +28 -0
  270. package/src/lib/tool-definitions.ts +2 -1
  271. package/src/lib/tool-event-summary.test.ts +30 -0
  272. package/src/lib/tool-event-summary.ts +37 -0
  273. package/src/lib/validation/schemas.ts +1 -0
  274. package/src/lib/wallet-transactions.test.ts +75 -0
  275. package/src/lib/wallet-transactions.ts +43 -0
  276. package/src/lib/wallet.test.ts +17 -0
  277. package/src/lib/wallet.ts +183 -0
  278. package/src/proxy.test.ts +31 -0
  279. package/src/proxy.ts +34 -2
  280. package/src/stores/use-chat-store.ts +15 -1
  281. package/src/types/index.ts +249 -14
@@ -0,0 +1,57 @@
1
+ import assert from 'node:assert/strict'
2
+ import { describe, it } from 'node:test'
3
+
4
+ import type { AgentWallet, WalletTransaction } from '@/types'
5
+
6
+ import { validateWalletSendLimits } from './wallet-service'
7
+
8
+ function buildWallet(overrides: Partial<AgentWallet> = {}): AgentWallet {
9
+ return {
10
+ id: 'wallet-1',
11
+ agentId: 'agent-1',
12
+ chain: 'ethereum',
13
+ publicKey: '0x0000000000000000000000000000000000000001',
14
+ encryptedPrivateKey: 'secret',
15
+ requireApproval: true,
16
+ spendingLimitAtomic: '1000000000000000000',
17
+ dailyLimitAtomic: '1500000000000000000',
18
+ createdAt: 1,
19
+ updatedAt: 1,
20
+ ...overrides,
21
+ }
22
+ }
23
+
24
+ function buildTransaction(overrides: Partial<WalletTransaction> = {}): WalletTransaction {
25
+ return {
26
+ id: 'tx-1',
27
+ walletId: 'wallet-1',
28
+ agentId: 'agent-1',
29
+ chain: 'ethereum',
30
+ type: 'send',
31
+ signature: '0xhash',
32
+ fromAddress: '0xfrom',
33
+ toAddress: '0xto',
34
+ amountAtomic: '1000000000000000000',
35
+ status: 'confirmed',
36
+ timestamp: Date.now(),
37
+ ...overrides,
38
+ }
39
+ }
40
+
41
+ describe('validateWalletSendLimits', () => {
42
+ it('blocks approvals that would exceed the current daily limit', () => {
43
+ const wallet = buildWallet()
44
+ const transactions = [
45
+ buildTransaction({ id: 'existing', amountAtomic: '1000000000000000000' }),
46
+ ]
47
+
48
+ const error = validateWalletSendLimits({
49
+ wallet,
50
+ amountAtomic: '600000000000000000',
51
+ transactions,
52
+ now: Date.now(),
53
+ })
54
+
55
+ assert.match(error || '', /Daily limit exceeded/)
56
+ })
57
+ })
@@ -0,0 +1,213 @@
1
+ import { genId } from '@/lib/id'
2
+ import {
3
+ formatWalletAmount,
4
+ getWalletAssetSymbol,
5
+ getWalletAtomicAmount,
6
+ getWalletChainOrDefault,
7
+ getWalletDefaultLimitAtomic,
8
+ getWalletLimitAtomic,
9
+ normalizeAtomicString,
10
+ } from '@/lib/wallet'
11
+ import type { Agent, AgentWallet, WalletChain, WalletTransaction } from '@/types'
12
+ import { loadAgents, loadWalletTransactions, loadWallets, saveAgents, upsertWallet } from './storage'
13
+ import { generateEthereumWallet, isValidEthereumAddress, sendEth } from './ethereum'
14
+ import { generateSolanaKeypair, isValidSolanaAddress, sendSol } from './solana'
15
+ import { notify } from './ws-hub'
16
+ import { clearWalletPortfolioCache, getWalletPortfolio, type GetWalletPortfolioOptions, type WalletPortfolio } from './wallet-portfolio'
17
+
18
+ function generateWalletCredentials(chain: WalletChain): { publicKey: string; encryptedPrivateKey: string } {
19
+ if (chain === 'ethereum') return generateEthereumWallet()
20
+ return generateSolanaKeypair()
21
+ }
22
+
23
+ export function stripWalletPrivateKey<T extends Record<string, unknown>>(wallet: T): Omit<T, 'encryptedPrivateKey'> {
24
+ return Object.fromEntries(Object.entries(wallet).filter(([key]) => key !== 'encryptedPrivateKey')) as Omit<T, 'encryptedPrivateKey'>
25
+ }
26
+
27
+ export function getAgentWalletIds(agent: Pick<Agent, 'walletIds' | 'walletId'> | null | undefined): string[] {
28
+ const ids = Array.isArray(agent?.walletIds)
29
+ ? agent.walletIds.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
30
+ : []
31
+ const legacy = typeof agent?.walletId === 'string' && agent.walletId.trim()
32
+ ? [agent.walletId.trim()]
33
+ : []
34
+ return [...new Set([...ids, ...legacy])]
35
+ }
36
+
37
+ export function getAgentActiveWalletId(
38
+ agent: Pick<Agent, 'walletIds' | 'walletId' | 'activeWalletId'> | null | undefined,
39
+ walletIds = getAgentWalletIds(agent),
40
+ ): string | null {
41
+ if (typeof agent?.activeWalletId === 'string' && walletIds.includes(agent.activeWalletId)) return agent.activeWalletId
42
+ if (typeof agent?.walletId === 'string' && walletIds.includes(agent.walletId)) return agent.walletId
43
+ return walletIds[0] || null
44
+ }
45
+
46
+ function syncAgentWalletPointers(agent: Agent, walletIds: string[], activeWalletId?: string | null): Agent {
47
+ const normalizedIds = [...new Set(walletIds.filter(Boolean))]
48
+ const normalizedActive = activeWalletId && normalizedIds.includes(activeWalletId)
49
+ ? activeWalletId
50
+ : normalizedIds[0] || null
51
+ agent.walletIds = normalizedIds
52
+ agent.activeWalletId = normalizedActive
53
+ agent.walletId = normalizedActive
54
+ return agent
55
+ }
56
+
57
+ export function linkWalletToAgent(agent: Agent, walletId: string, makeActive = false): Agent {
58
+ const walletIds = getAgentWalletIds(agent)
59
+ if (!walletIds.includes(walletId)) walletIds.push(walletId)
60
+ const activeWalletId = makeActive ? walletId : getAgentActiveWalletId(agent, walletIds)
61
+ return syncAgentWalletPointers(agent, walletIds, activeWalletId)
62
+ }
63
+
64
+ export function unlinkWalletFromAgent(agent: Agent, walletId: string): Agent {
65
+ const walletIds = getAgentWalletIds(agent).filter((id) => id !== walletId)
66
+ const activeWalletId = getAgentActiveWalletId(agent, walletIds)
67
+ return syncAgentWalletPointers(agent, walletIds, activeWalletId)
68
+ }
69
+
70
+ export function setAgentActiveWallet(agent: Agent, walletId: string | null): Agent {
71
+ const walletIds = getAgentWalletIds(agent)
72
+ const activeWalletId = walletId && walletIds.includes(walletId) ? walletId : walletIds[0] || null
73
+ return syncAgentWalletPointers(agent, walletIds, activeWalletId)
74
+ }
75
+
76
+ export function getWalletsByAgentId(agentId: string): AgentWallet[] {
77
+ const wallets = loadWallets() as Record<string, AgentWallet>
78
+ return Object.values(wallets)
79
+ .filter((wallet) => wallet.agentId === agentId)
80
+ .sort((a, b) => a.createdAt - b.createdAt)
81
+ }
82
+
83
+ export function getWalletByAgentId(agentId: string, chain?: WalletChain | null): AgentWallet | null {
84
+ const wallets = getWalletsByAgentId(agentId)
85
+ if (chain) return wallets.find((wallet) => wallet.chain === chain) ?? null
86
+
87
+ const agents = loadAgents()
88
+ const agent = agents[agentId]
89
+ const activeWalletId = getAgentActiveWalletId(agent)
90
+ return wallets.find((wallet) => wallet.id === activeWalletId) ?? wallets[0] ?? null
91
+ }
92
+
93
+ export function createAgentWallet(input: {
94
+ agentId: string
95
+ chain?: WalletChain | string | null
96
+ provider?: WalletChain | string | null
97
+ label?: string
98
+ requireApproval?: boolean
99
+ spendingLimitAtomic?: string | number | null
100
+ dailyLimitAtomic?: string | number | null
101
+ }): AgentWallet {
102
+ const agentId = String(input.agentId || '').trim()
103
+ if (!agentId) throw new Error('agentId is required')
104
+
105
+ const agents = loadAgents()
106
+ if (!agents[agentId]) throw new Error('Agent not found')
107
+
108
+ const chain = getWalletChainOrDefault(input.chain ?? input.provider, 'solana')
109
+ const existing = getWalletByAgentId(agentId, chain)
110
+ if (existing) throw new Error(`Agent already has a ${chain} wallet`)
111
+ const { publicKey, encryptedPrivateKey } = generateWalletCredentials(chain)
112
+ const id = genId()
113
+ const now = Date.now()
114
+ const wallet: AgentWallet = {
115
+ id,
116
+ agentId,
117
+ chain,
118
+ publicKey,
119
+ encryptedPrivateKey,
120
+ label: typeof input.label === 'string' && input.label.trim() ? input.label.trim() : undefined,
121
+ spendingLimitAtomic: normalizeAtomicString(input.spendingLimitAtomic, getWalletDefaultLimitAtomic(chain, 'perTx')),
122
+ dailyLimitAtomic: normalizeAtomicString(input.dailyLimitAtomic, getWalletDefaultLimitAtomic(chain, 'daily')),
123
+ requireApproval: input.requireApproval !== false,
124
+ createdAt: now,
125
+ updatedAt: now,
126
+ }
127
+
128
+ upsertWallet(id, wallet)
129
+ clearWalletPortfolioCache(id)
130
+
131
+ const agent = agents[agentId]
132
+ linkWalletToAgent(agent, id, getAgentActiveWalletId(agent) == null)
133
+ agent.updatedAt = now
134
+ agents[agentId] = agent
135
+ saveAgents(agents)
136
+
137
+ notify('wallets')
138
+ notify('agents')
139
+
140
+ return wallet
141
+ }
142
+
143
+ export async function getWalletBalanceAtomic(wallet: AgentWallet): Promise<string> {
144
+ return (await getWalletPortfolio(wallet)).balanceAtomic
145
+ }
146
+
147
+ export async function getWalletPortfolioSnapshot(
148
+ wallet: AgentWallet,
149
+ options?: GetWalletPortfolioOptions,
150
+ ): Promise<WalletPortfolio> {
151
+ return getWalletPortfolio(wallet, options)
152
+ }
153
+
154
+ export function validateWalletSendLimits(params: {
155
+ wallet: AgentWallet
156
+ amountAtomic: string
157
+ transactions?: WalletTransaction[]
158
+ now?: number
159
+ excludeTransactionId?: string
160
+ }): string | null {
161
+ const { wallet } = params
162
+ const amountAtomic = normalizeAtomicString(params.amountAtomic, '0')
163
+ const assetSymbol = getWalletAssetSymbol(wallet.chain)
164
+
165
+ if (BigInt(amountAtomic) <= BigInt(0)) {
166
+ return 'Amount must be positive'
167
+ }
168
+
169
+ const perTxLimitAtomic = getWalletLimitAtomic(wallet, 'perTx')
170
+ if (BigInt(amountAtomic) > BigInt(perTxLimitAtomic)) {
171
+ return `Amount ${formatWalletAmount(wallet.chain, amountAtomic, { maxFractionDigits: 6 })} ${assetSymbol} exceeds per-transaction limit of ${formatWalletAmount(wallet.chain, perTxLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
172
+ }
173
+
174
+ const dailyLimitAtomic = getWalletLimitAtomic(wallet, 'daily')
175
+ const oneDayAgo = (params.now ?? Date.now()) - 24 * 60 * 60 * 1000
176
+ const transactions = params.transactions
177
+ ?? Object.values(loadWalletTransactions() as Record<string, WalletTransaction>)
178
+ const dailySpentAtomic = transactions
179
+ .filter((tx) => tx.walletId === wallet.id)
180
+ .filter((tx) => tx.id !== params.excludeTransactionId)
181
+ .filter((tx) => tx.type === 'send' && tx.status === 'confirmed' && tx.timestamp > oneDayAgo)
182
+ .reduce((sum, tx) => sum + BigInt(getWalletAtomicAmount(tx)), BigInt(0))
183
+
184
+ if (dailySpentAtomic + BigInt(amountAtomic) > BigInt(dailyLimitAtomic)) {
185
+ return `Daily limit exceeded. Spent ${formatWalletAmount(wallet.chain, dailySpentAtomic.toString(), { maxFractionDigits: 6 })} ${assetSymbol} in the last 24h, limit is ${formatWalletAmount(wallet.chain, dailyLimitAtomic, { maxFractionDigits: 6 })} ${assetSymbol}`
186
+ }
187
+
188
+ return null
189
+ }
190
+
191
+ export function isValidWalletAddress(chain: WalletChain, address: string): boolean {
192
+ if (chain === 'ethereum') return isValidEthereumAddress(address)
193
+ return isValidSolanaAddress(address)
194
+ }
195
+
196
+ export async function sendWalletNativeAsset(
197
+ wallet: AgentWallet,
198
+ toAddress: string,
199
+ amountAtomic: string,
200
+ ): Promise<{ signature: string; feeAtomic?: string }> {
201
+ if (wallet.chain === 'ethereum') {
202
+ const result = await sendEth(wallet.encryptedPrivateKey, toAddress, amountAtomic)
203
+ clearWalletPortfolioCache(wallet.id)
204
+ return { signature: result.signature, feeAtomic: result.fee }
205
+ }
206
+
207
+ const result = await sendSol(wallet.encryptedPrivateKey, toAddress, Number.parseInt(amountAtomic, 10))
208
+ clearWalletPortfolioCache(wallet.id)
209
+ return {
210
+ signature: result.signature,
211
+ feeAtomic: String(result.fee),
212
+ }
213
+ }