@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
@@ -1,174 +1,32 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadWallets, upsertWallet, deleteWallet as deleteWalletFromStore, loadAgent, loadAgents, upsertAgent } from '@/lib/server/storage'
4
- import { notify } from '@/lib/server/ws-hub'
5
- import { getWalletLimitAtomic, normalizeAtomicString } from '@/lib/wallet/wallet'
6
- import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary } from '@/types'
7
- import { buildEmptyWalletPortfolio, getCachedWalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
8
- import {
9
- getAgentActiveWalletId,
10
- getWalletPortfolioSnapshot,
11
- linkWalletToAgent,
12
- setAgentActiveWallet,
13
- stripWalletPrivateKey,
14
- unlinkWalletFromAgent,
15
- } from '@/lib/server/wallet/wallet-service'
3
+ import { getWalletSafe, removeWallet, updateWallet } from '@/lib/server/wallets/wallet-service'
16
4
  export const dynamic = 'force-dynamic'
17
- const WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS = 2500
18
5
 
19
- function withPortfolio(
20
- wallet: AgentWallet,
21
- portfolio: {
22
- balanceAtomic: string
23
- balanceFormatted: string
24
- balanceSymbol: string
25
- balanceDisplay: string
26
- balanceLamports?: number
27
- balanceSol?: number
28
- assets: WalletAssetBalance[]
29
- summary: WalletPortfolioSummary
30
- },
31
- isActive: boolean,
32
- ) {
33
- return {
34
- ...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
35
- balanceAtomic: portfolio.balanceAtomic,
36
- balanceFormatted: portfolio.balanceFormatted,
37
- balanceSymbol: portfolio.balanceSymbol,
38
- balanceDisplay: portfolio.balanceDisplay,
39
- balanceLamports: portfolio.balanceLamports,
40
- balanceSol: portfolio.balanceSol,
41
- assets: portfolio.assets,
42
- portfolioSummary: portfolio.summary,
43
- isActive,
44
- }
45
- }
46
-
47
- export async function GET(req: Request, { params }: { params: Promise<{ id: string }> }) {
6
+ export async function GET(_req: Request, { params }: { params: Promise<{ id: string }> }) {
48
7
  const { id } = await params
49
- const wallets = loadWallets() as Record<string, AgentWallet>
50
- const wallet = wallets[id]
8
+ const wallet = getWalletSafe(id)
51
9
  if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
52
-
53
- const url = new URL(req.url)
54
- const cachedOnly = url.searchParams.get('cached') === '1'
55
- const agents = loadAgents()
56
- const isActive = getAgentActiveWalletId(agents[wallet.agentId]) === wallet.id
57
-
58
- if (cachedOnly) {
59
- const cached = getCachedWalletPortfolio(wallet)
60
- if (!cached) {
61
- return NextResponse.json({
62
- ...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
63
- isActive,
64
- })
65
- }
66
- return NextResponse.json(withPortfolio(wallet, cached, isActive))
67
- }
68
-
69
- let portfolio = buildEmptyWalletPortfolio(wallet)
70
- try {
71
- portfolio = await getWalletPortfolioSnapshot(wallet, {
72
- timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
73
- allowStale: true,
74
- })
75
- } catch {
76
- // RPC failure — return 0
77
- }
78
-
79
- return NextResponse.json(withPortfolio(wallet, portfolio, isActive))
10
+ return NextResponse.json(wallet)
80
11
  }
81
12
 
82
13
  export async function PATCH(req: Request, { params }: { params: Promise<{ id: string }> }) {
83
14
  const { id } = await params
84
- const wallets = loadWallets() as Record<string, AgentWallet>
85
- const wallet = wallets[id]
86
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
87
-
88
15
  const { data: body, error } = await safeParseBody(req)
89
16
  if (error) return error
90
- const shouldMakeActive = body.makeActive === true
91
-
92
- // Reassign wallet to a different agent
93
- if (typeof body.agentId === 'string' && body.agentId !== wallet.agentId) {
94
- const newAgent = loadAgent(body.agentId)
95
- if (!newAgent) return NextResponse.json({ error: 'Agent not found' }, { status: 404 })
96
-
97
- // Only one wallet per chain per agent.
98
- const allWallets = loadWallets() as Record<string, AgentWallet>
99
- const conflict = Object.values(allWallets).find((w) => w.agentId === body.agentId && w.id !== id && w.chain === wallet.chain)
100
- if (conflict) return NextResponse.json({ error: `Target agent already has a ${wallet.chain} wallet` }, { status: 409 })
101
-
102
- const oldAgent = loadAgent(wallet.agentId)
103
- if (oldAgent) {
104
- unlinkWalletFromAgent(oldAgent as any, id)
105
- oldAgent.updatedAt = Date.now()
106
- upsertAgent(wallet.agentId, oldAgent)
107
- }
108
-
109
- linkWalletToAgent(newAgent as any, id, shouldMakeActive || getAgentActiveWalletId(newAgent as any) == null)
110
- newAgent.updatedAt = Date.now()
111
- upsertAgent(body.agentId, newAgent)
112
- notify('agents')
113
-
114
- wallet.agentId = body.agentId
115
- } else if (shouldMakeActive) {
116
- const agent = loadAgent(wallet.agentId)
117
- if (agent) {
118
- setAgentActiveWallet(agent as any, id)
119
- agent.updatedAt = Date.now()
120
- upsertAgent(wallet.agentId, agent)
121
- notify('agents')
122
- }
123
- }
124
-
125
- if (body.label !== undefined) wallet.label = body.label as string | undefined
126
- if (body.spendingLimitAtomic !== undefined || body.spendingLimitLamports !== undefined) {
127
- wallet.spendingLimitAtomic = normalizeAtomicString(body.spendingLimitAtomic ?? body.spendingLimitLamports, getWalletLimitAtomic(wallet, 'perTx'))
128
- }
129
- if (body.dailyLimitAtomic !== undefined || body.dailyLimitLamports !== undefined) {
130
- wallet.dailyLimitAtomic = normalizeAtomicString(body.dailyLimitAtomic ?? body.dailyLimitLamports, getWalletLimitAtomic(wallet, 'daily'))
131
- }
132
- if (typeof body.requireApproval === 'boolean') wallet.requireApproval = body.requireApproval
133
- wallet.updatedAt = Date.now()
134
-
135
- upsertWallet(id, wallet)
136
- notify('wallets')
137
-
138
- return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
17
+ const patch: Record<string, unknown> = {}
18
+ if (typeof body.label === 'string') patch.label = body.label
19
+ if (typeof body.spendingLimitUsdc === 'string' || body.spendingLimitUsdc === null) patch.spendingLimitUsdc = body.spendingLimitUsdc
20
+ if (typeof body.dailyLimitUsdc === 'string' || body.dailyLimitUsdc === null) patch.dailyLimitUsdc = body.dailyLimitUsdc
21
+ if (typeof body.requireApproval === 'boolean') patch.requireApproval = body.requireApproval
22
+ const updated = updateWallet(id, patch)
23
+ if (!updated) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
24
+ return NextResponse.json(updated)
139
25
  }
140
26
 
141
27
  export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) {
142
28
  const { id } = await params
143
- const wallets = loadWallets() as Record<string, AgentWallet>
144
- const wallet = wallets[id]
145
- if (!wallet) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
146
-
147
- // Check if balance > 0 and warn
148
- let portfolio = buildEmptyWalletPortfolio(wallet)
149
- try {
150
- portfolio = await getWalletPortfolioSnapshot(wallet, {
151
- timeoutMs: WALLET_DETAIL_PORTFOLIO_TIMEOUT_MS,
152
- allowStale: true,
153
- })
154
- } catch { /* ignore */ }
155
-
156
- // Unlink from agent
157
- const agent = loadAgent(wallet.agentId)
158
- if (agent) {
159
- unlinkWalletFromAgent(agent as any, id)
160
- agent.updatedAt = Date.now()
161
- upsertAgent(wallet.agentId, agent)
162
- notify('agents')
163
- }
164
-
165
- deleteWalletFromStore(id)
166
- notify('wallets')
167
-
168
- return NextResponse.json({
169
- ok: true,
170
- warning: portfolio.summary.nonZeroAssets > 0
171
- ? `Wallet still had ${portfolio.summary.nonZeroAssets} asset${portfolio.summary.nonZeroAssets === 1 ? '' : 's'} remaining, including ${portfolio.balanceDisplay}`
172
- : undefined,
173
- })
29
+ const deleted = removeWallet(id)
30
+ if (!deleted) return NextResponse.json({ error: 'Wallet not found' }, { status: 404 })
31
+ return NextResponse.json({ ok: true })
174
32
  }
@@ -0,0 +1,22 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { safeParseBody } from '@/lib/server/safe-parse-body'
3
+ import { generateWallet, WalletServiceError } from '@/lib/server/wallets/wallet-service'
4
+ export const dynamic = 'force-dynamic'
5
+
6
+ export async function POST(req: Request) {
7
+ const { data: body, error } = await safeParseBody(req)
8
+ if (error) return error
9
+ try {
10
+ const wallet = await generateWallet({
11
+ agentId: typeof body.agentId === 'string' ? body.agentId : '',
12
+ label: typeof body.label === 'string' ? body.label : undefined,
13
+ })
14
+ return NextResponse.json(wallet, { status: 201 })
15
+ } catch (err) {
16
+ if (err instanceof WalletServiceError) {
17
+ return NextResponse.json({ error: err.message }, { status: err.status })
18
+ }
19
+ const message = err instanceof Error ? err.message : 'Failed to generate wallet'
20
+ return NextResponse.json({ error: message }, { status: 500 })
21
+ }
22
+ }
@@ -0,0 +1,147 @@
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { spawnSync } from 'node:child_process'
6
+ import test from 'node:test'
7
+
8
+ const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), '../../../..')
9
+
10
+ function runWithTempDataDir(script: string) {
11
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'swarmclaw-wallet-routes-'))
12
+ try {
13
+ const result = spawnSync(process.execPath, ['--import', 'tsx', '--input-type=module', '--eval', script], {
14
+ cwd: repoRoot,
15
+ env: {
16
+ ...process.env,
17
+ CREDENTIAL_SECRET: 'test-credential-secret',
18
+ DATA_DIR: path.join(tempDir, 'data'),
19
+ WORKSPACE_DIR: path.join(tempDir, 'workspace'),
20
+ },
21
+ encoding: 'utf-8',
22
+ })
23
+ assert.equal(result.status, 0, result.stderr || result.stdout || 'subprocess failed')
24
+ const lines = (result.stdout || '')
25
+ .trim()
26
+ .split('\n')
27
+ .map((line) => line.trim())
28
+ .filter(Boolean)
29
+ const jsonLine = [...lines].reverse().find((line) => line.startsWith('{'))
30
+ return JSON.parse(jsonLine || '{}')
31
+ } finally {
32
+ fs.rmSync(tempDir, { recursive: true, force: true })
33
+ }
34
+ }
35
+
36
+ test('wallet routes reject unknown agents and invalid addresses while returning safe payloads', () => {
37
+ const output = runWithTempDataDir(`
38
+ const storageMod = await import('./src/lib/server/storage')
39
+ const walletsRouteMod = await import('./src/app/api/wallets/route')
40
+ const walletsGenerateRouteMod = await import('./src/app/api/wallets/generate/route')
41
+ const storage = storageMod.default || storageMod
42
+ const walletsRoute = walletsRouteMod.default || walletsRouteMod
43
+ const walletsGenerateRoute = walletsGenerateRouteMod.default || walletsGenerateRouteMod
44
+
45
+ storage.saveAgents({
46
+ agent_1: {
47
+ id: 'agent_1',
48
+ name: 'Agent One',
49
+ },
50
+ })
51
+
52
+ const missingAgentResponse = await walletsRoute.POST(new Request('http://local/api/wallets', {
53
+ method: 'POST',
54
+ headers: { 'content-type': 'application/json' },
55
+ body: JSON.stringify({
56
+ agentId: 'missing-agent',
57
+ walletAddress: '0x000000000000000000000000000000000000dEaD',
58
+ }),
59
+ }))
60
+ const missingAgentPayload = await missingAgentResponse.json()
61
+
62
+ const blankAddressResponse = await walletsRoute.POST(new Request('http://local/api/wallets', {
63
+ method: 'POST',
64
+ headers: { 'content-type': 'application/json' },
65
+ body: JSON.stringify({
66
+ agentId: 'agent_1',
67
+ walletAddress: ' ',
68
+ }),
69
+ }))
70
+ const blankAddressPayload = await blankAddressResponse.json()
71
+
72
+ const invalidAddressResponse = await walletsRoute.POST(new Request('http://local/api/wallets', {
73
+ method: 'POST',
74
+ headers: { 'content-type': 'application/json' },
75
+ body: JSON.stringify({
76
+ agentId: 'agent_1',
77
+ walletAddress: '0x1234',
78
+ }),
79
+ }))
80
+ const invalidAddressPayload = await invalidAddressResponse.json()
81
+
82
+ const createResponse = await walletsRoute.POST(new Request('http://local/api/wallets', {
83
+ method: 'POST',
84
+ headers: { 'content-type': 'application/json' },
85
+ body: JSON.stringify({
86
+ agentId: 'agent_1',
87
+ walletAddress: '0x000000000000000000000000000000000000dead',
88
+ label: 'Manual Wallet',
89
+ }),
90
+ }))
91
+ const createPayload = await createResponse.json()
92
+
93
+ const generateMissingResponse = await walletsGenerateRoute.POST(new Request('http://local/api/wallets/generate', {
94
+ method: 'POST',
95
+ headers: { 'content-type': 'application/json' },
96
+ body: JSON.stringify({
97
+ agentId: 'missing-agent',
98
+ }),
99
+ }))
100
+ const generateMissingPayload = await generateMissingResponse.json()
101
+
102
+ const generateResponse = await walletsGenerateRoute.POST(new Request('http://local/api/wallets/generate', {
103
+ method: 'POST',
104
+ headers: { 'content-type': 'application/json' },
105
+ body: JSON.stringify({
106
+ agentId: 'agent_1',
107
+ label: 'Generated Wallet',
108
+ }),
109
+ }))
110
+ const generatePayload = await generateResponse.json()
111
+
112
+ const storedWallets = Object.values(storage.loadWallets())
113
+ const generatedStoredWallet = storedWallets.find((wallet) => wallet.label === 'Generated Wallet') || null
114
+
115
+ console.log(JSON.stringify({
116
+ missingAgentStatus: missingAgentResponse.status,
117
+ missingAgentError: missingAgentPayload?.error || null,
118
+ blankAddressStatus: blankAddressResponse.status,
119
+ blankAddressError: blankAddressPayload?.error || null,
120
+ invalidAddressStatus: invalidAddressResponse.status,
121
+ invalidAddressError: invalidAddressPayload?.error || null,
122
+ createStatus: createResponse.status,
123
+ createAddress: createPayload?.walletAddress || null,
124
+ createHasPrivateKey: Boolean(createPayload && Object.prototype.hasOwnProperty.call(createPayload, 'encryptedPrivateKey')),
125
+ generateMissingStatus: generateMissingResponse.status,
126
+ generateMissingError: generateMissingPayload?.error || null,
127
+ generateStatus: generateResponse.status,
128
+ generateHasPrivateKey: Boolean(generatePayload && Object.prototype.hasOwnProperty.call(generatePayload, 'encryptedPrivateKey')),
129
+ storedGeneratedHasPrivateKey: Boolean(generatedStoredWallet?.encryptedPrivateKey),
130
+ }))
131
+ `)
132
+
133
+ assert.equal(output.missingAgentStatus, 404)
134
+ assert.match(String(output.missingAgentError || ''), /Agent not found/i)
135
+ assert.equal(output.blankAddressStatus, 400)
136
+ assert.equal(output.blankAddressError, 'walletAddress is required')
137
+ assert.equal(output.invalidAddressStatus, 400)
138
+ assert.match(String(output.invalidAddressError || ''), /valid Base\/Ethereum address/i)
139
+ assert.equal(output.createStatus, 201)
140
+ assert.equal(output.createAddress, '0x000000000000000000000000000000000000dEaD')
141
+ assert.equal(output.createHasPrivateKey, false)
142
+ assert.equal(output.generateMissingStatus, 404)
143
+ assert.match(String(output.generateMissingError || ''), /Agent not found/i)
144
+ assert.equal(output.generateStatus, 201)
145
+ assert.equal(output.generateHasPrivateKey, false)
146
+ assert.equal(output.storedGeneratedHasPrivateKey, true)
147
+ })
@@ -1,109 +1,27 @@
1
1
  import { NextResponse } from 'next/server'
2
2
  import { safeParseBody } from '@/lib/server/safe-parse-body'
3
- import { loadAgents, loadSettings, loadWallets } from '@/lib/server/storage'
4
- import { createAgentWallet, getAgentActiveWalletId, getWalletPortfolioSnapshot, stripWalletPrivateKey } from '@/lib/server/wallet/wallet-service'
5
- import { buildEmptyWalletPortfolio } from '@/lib/server/wallet/wallet-portfolio'
6
- import type { AgentWallet, WalletPortfolioSummary } from '@/types'
7
- import { errorMessage } from '@/lib/shared-utils'
3
+ import { listWalletsSafe, createWallet, WalletServiceError } from '@/lib/server/wallets/wallet-service'
8
4
  export const dynamic = 'force-dynamic'
9
- const WALLET_LIST_PORTFOLIO_TIMEOUT_MS = 1500
10
5
 
11
- function withPortfolio(
12
- wallet: AgentWallet,
13
- portfolio: {
14
- balanceAtomic: string
15
- balanceFormatted: string
16
- balanceSymbol: string
17
- balanceDisplay: string
18
- balanceLamports?: number
19
- balanceSol?: number
20
- assets: unknown[]
21
- summary: WalletPortfolioSummary
22
- },
23
- isActive: boolean,
24
- ) {
25
- return {
26
- ...stripWalletPrivateKey(wallet as unknown as Record<string, unknown>),
27
- balanceAtomic: portfolio.balanceAtomic,
28
- balanceFormatted: portfolio.balanceFormatted,
29
- balanceSymbol: portfolio.balanceSymbol,
30
- balanceDisplay: portfolio.balanceDisplay,
31
- balanceLamports: portfolio.balanceLamports,
32
- balanceSol: portfolio.balanceSol,
33
- assets: portfolio.assets,
34
- portfolioSummary: portfolio.summary,
35
- isActive,
36
- }
37
- }
38
-
39
- export async function GET(req: Request) {
40
- const wallets = loadWallets() as Record<string, AgentWallet>
41
- const agents = loadAgents()
42
- const { searchParams } = new URL(req.url)
43
- const agentId = searchParams.get('agentId')?.trim() || ''
44
- const walletEntries = Object.entries(wallets)
45
- .filter(([, wallet]) => !agentId || wallet.agentId === agentId)
46
- const entries = await Promise.all(
47
- walletEntries.map(async ([id, wallet]) => {
48
- let portfolio = buildEmptyWalletPortfolio(wallet)
49
- try {
50
- portfolio = await getWalletPortfolioSnapshot(wallet, {
51
- timeoutMs: WALLET_LIST_PORTFOLIO_TIMEOUT_MS,
52
- allowStale: true,
53
- })
54
- } catch {
55
- // Slow or failed RPC discovery — return empty/stale portfolio for list view
56
- }
57
- const activeWalletId = getAgentActiveWalletId(agents[wallet.agentId])
58
- return [id, withPortfolio(wallet, portfolio, activeWalletId === wallet.id)] as const
59
- }),
60
- )
61
- return NextResponse.json(Object.fromEntries(entries))
62
- }
63
-
64
- interface WalletCreateBody {
65
- agentId: string
66
- chain?: string | null
67
- provider?: string | null
68
- label?: string
69
- requireApproval?: boolean
70
- spendingLimitAtomic?: string | number | null
71
- spendingLimitLamports?: string | number | null
72
- dailyLimitAtomic?: string | number | null
73
- dailyLimitLamports?: string | number | null
6
+ export async function GET() {
7
+ return NextResponse.json(listWalletsSafe())
74
8
  }
75
9
 
76
10
  export async function POST(req: Request) {
77
- const { data: body, error } = await safeParseBody<WalletCreateBody>(req)
11
+ const { data: body, error } = await safeParseBody(req)
78
12
  if (error) return error
79
- const settings = loadSettings()
80
13
  try {
81
- const wallet = createAgentWallet({
82
- agentId: body.agentId,
83
- chain: body.chain,
84
- provider: body.provider,
85
- label: body.label,
86
- requireApproval: typeof body.requireApproval === 'boolean'
87
- ? body.requireApproval
88
- : settings.walletApprovalsEnabled !== false,
89
- spendingLimitAtomic: body.spendingLimitAtomic ?? body.spendingLimitLamports,
90
- dailyLimitAtomic: body.dailyLimitAtomic ?? body.dailyLimitLamports,
14
+ const wallet = await createWallet({
15
+ agentId: typeof body.agentId === 'string' ? body.agentId : '',
16
+ walletAddress: typeof body.walletAddress === 'string' ? body.walletAddress : '',
17
+ label: typeof body.label === 'string' ? body.label : undefined,
91
18
  })
92
- return NextResponse.json(stripWalletPrivateKey(wallet as unknown as Record<string, unknown>))
93
- } catch (err: unknown) {
94
- const message = errorMessage(err)
95
- if (message === 'agentId is required') {
96
- return NextResponse.json({ error: message }, { status: 400 })
97
- }
98
- if (/^Unsupported wallet chain or provider: /.test(message)) {
99
- return NextResponse.json({ error: message }, { status: 400 })
100
- }
101
- if (message === 'Agent not found') {
102
- return NextResponse.json({ error: message }, { status: 404 })
103
- }
104
- if (/^Agent already has a (solana|ethereum) wallet$/.test(message)) {
105
- return NextResponse.json({ error: message }, { status: 409 })
19
+ return NextResponse.json(wallet, { status: 201 })
20
+ } catch (err) {
21
+ if (err instanceof WalletServiceError) {
22
+ return NextResponse.json({ error: err.message }, { status: err.status })
106
23
  }
24
+ const message = err instanceof Error ? err.message : 'Failed to create wallet'
107
25
  return NextResponse.json({ error: message }, { status: 500 })
108
26
  }
109
27
  }
@@ -7,7 +7,7 @@ import { StatCard } from '@/components/ui/stat-card'
7
7
  import { isOrchestratorEligible } from '@/lib/orchestrator-config'
8
8
  import { timeAgo } from '@/lib/time-format'
9
9
  import { useWs } from '@/hooks/use-ws'
10
- import type { Agent, ApprovalRequest, EstopState, Mission, SupervisorIncident } from '@/types'
10
+ import type { Agent, ApprovalRequest, EstopState, SupervisorIncident } from '@/types'
11
11
 
12
12
  type EstopResponse = EstopState & {
13
13
  ok?: boolean
@@ -108,7 +108,6 @@ function previewIncidentDetails(value: string): string {
108
108
  export default function AutonomyPage() {
109
109
  const [estop, setEstop] = useState<EstopResponse | null>(null)
110
110
  const [incidents, setIncidents] = useState<SupervisorIncident[]>([])
111
- const [missions, setMissions] = useState<Mission[]>([])
112
111
  const [agents, setAgents] = useState<Agent[]>([])
113
112
  const [loading, setLoading] = useState(true)
114
113
  const [refreshing, setRefreshing] = useState(false)
@@ -122,15 +121,13 @@ export default function AutonomyPage() {
122
121
  if (mode === 'initial') setLoading(true)
123
122
  else setRefreshing(true)
124
123
  try {
125
- const [estopState, incidentList, missionList, agentMap] = await Promise.all([
124
+ const [estopState, incidentList, agentMap] = await Promise.all([
126
125
  api<EstopResponse>('GET', '/autonomy/estop'),
127
126
  api<SupervisorIncident[]>('GET', '/autonomy/incidents?limit=60'),
128
- api<Mission[]>('GET', '/missions?status=non_terminal&limit=20'),
129
127
  api<Record<string, Agent>>('GET', '/agents'),
130
128
  ])
131
129
  setEstop(estopState)
132
130
  setIncidents(Array.isArray(incidentList) ? incidentList : [])
133
- setMissions(Array.isArray(missionList) ? missionList : [])
134
131
  setAgents(agentMap ? Object.values(agentMap) : [])
135
132
  setRefreshedAt(Date.now())
136
133
  setError(null)
@@ -287,14 +284,6 @@ export default function AutonomyPage() {
287
284
  () => [...incidents].sort((left, right) => right.createdAt - left.createdAt),
288
285
  [incidents],
289
286
  )
290
- const activeMissions = useMemo(
291
- () => [...missions].sort((left, right) => right.updatedAt - left.updatedAt),
292
- [missions],
293
- )
294
- const blockedMissions = activeMissions.filter((mission) =>
295
- mission.status === 'waiting' || mission.status === 'failed' || mission.status === 'cancelled',
296
- )
297
-
298
287
  const filteredIncidents = useMemo(() => {
299
288
  if (incidentFilter === 'high') return sortedIncidents.filter((incident) => incident.severity === 'high')
300
289
  if (incidentFilter === 'runtime_failure') return sortedIncidents.filter((incident) => incident.kind === 'runtime_failure')
@@ -491,50 +480,6 @@ export default function AutonomyPage() {
491
480
  </div>
492
481
  )}
493
482
 
494
- <section className="rounded-[20px] border border-white/[0.06] bg-surface p-5">
495
- <div className="mb-4 flex items-start justify-between gap-4">
496
- <div>
497
- <h2 className="font-display text-[18px] font-700 tracking-[-0.02em] text-text">Active Missions</h2>
498
- <p className="mt-1 text-[12px] leading-[1.7] text-text-3/72">
499
- Durable goals that are still running or waiting. Waiting missions surface here so operators can see blockers without digging through incidents.
500
- </p>
501
- </div>
502
- <div className="rounded-full border border-white/[0.08] bg-white/[0.03] px-3 py-1 text-[11px] text-text-3/72">
503
- {activeMissions.length} active
504
- {blockedMissions.length > 0 ? ` · ${blockedMissions.length} waiting` : ''}
505
- </div>
506
- </div>
507
-
508
- {activeMissions.length === 0 ? (
509
- <div className="rounded-[16px] border border-white/[0.06] bg-white/[0.02] px-4 py-3 text-[12px] text-text-3/70">
510
- No durable missions are active right now.
511
- </div>
512
- ) : (
513
- <div className="grid gap-3 lg:grid-cols-2">
514
- {activeMissions.slice(0, 6).map((mission) => {
515
- const waiting = mission.status === 'waiting' || mission.status === 'failed' || mission.status === 'cancelled'
516
- return (
517
- <div
518
- key={mission.id}
519
- className={`rounded-[16px] border p-4 ${waiting ? 'border-amber-500/18 bg-amber-500/[0.05]' : 'border-white/[0.06] bg-white/[0.02]'}`}
520
- >
521
- <div className="mb-2 flex items-center justify-between gap-3">
522
- <span className={`rounded-full px-2.5 py-1 text-[10px] font-700 uppercase tracking-[0.08em] ${waiting ? 'bg-amber-500/12 text-amber-300' : 'bg-emerald-500/12 text-emerald-300'}`}>
523
- {mission.status}
524
- </span>
525
- <span className="text-[11px] text-text-3/65">{timeAgo(mission.updatedAt, now)}</span>
526
- </div>
527
- <div className="text-[14px] font-600 text-text">{mission.objective}</div>
528
- <div className="mt-2 text-[12px] leading-[1.7] text-text-3/72">
529
- {mission.waitState?.reason || mission.currentStep || mission.verifierSummary || mission.plannerSummary || 'Mission active.'}
530
- </div>
531
- </div>
532
- )
533
- })}
534
- </div>
535
- )}
536
- </section>
537
-
538
483
  <section className="rounded-[20px] border border-white/[0.06] bg-surface p-5">
539
484
  <div className="mb-4 flex items-start justify-between gap-4">
540
485
  <div>
@@ -42,6 +42,9 @@ const ACTIVITY_ICONS: Record<ActivityEntry['action'], string> = {
42
42
  incident: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4m0 4h.01',
43
43
  running: 'M12 2v4m0 12v4m10-10h-4M6 12H2',
44
44
  claimed: 'M9 12l2 2 4-4m6 2a10 10 0 1 1-20 0 10 10 0 0 1 20 0z',
45
+ configured: 'M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z',
46
+ budget_exceeded: 'M12 2a10 10 0 1 0 0 20 10 10 0 0 0 0-20m0 6v4m0 4h.01',
47
+ budget_warning: 'M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0zM12 9v4m0 4h.01',
45
48
  }
46
49
 
47
50
  const ACTIVITY_COLORS: Record<string, string> = {