@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,260 +0,0 @@
1
- 'use client'
2
-
3
- import { useCallback, useEffect, useMemo, useState } from 'react'
4
- import { api } from '@/lib/app/api-client'
5
- import { copyTextToClipboard } from '@/lib/clipboard'
6
- import { useAppStore } from '@/stores/use-app-store'
7
- import type { AgentWallet, WalletAssetBalance, WalletPortfolioSummary, WalletChain } from '@/types'
8
- import { toast } from 'sonner'
9
- import { errorMessage } from '@/lib/shared-utils'
10
- import {
11
- SUPPORTED_WALLET_CHAINS,
12
- formatWalletAmount,
13
- getWalletBalanceAtomic,
14
- getWalletChainMeta,
15
- getWalletLimitAtomic,
16
- } from '@/lib/wallet/wallet'
17
-
18
- type SafeWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & {
19
- balanceAtomic?: string
20
- balanceLamports?: number
21
- balanceFormatted?: string
22
- balanceSymbol?: string
23
- assets?: WalletAssetBalance[]
24
- portfolioSummary?: WalletPortfolioSummary
25
- isActive?: boolean
26
- }
27
-
28
- interface WalletSectionProps {
29
- agentId: string
30
- wallets: SafeWallet[]
31
- activeWalletId: string | null
32
- onWalletCreated: () => void
33
- }
34
-
35
- export function WalletSection({ agentId, wallets, activeWalletId, onWalletCreated }: WalletSectionProps) {
36
- const appSettings = useAppStore((s) => s.appSettings)
37
- const [creating, setCreating] = useState(false)
38
- const [activatingWalletId, setActivatingWalletId] = useState<string | null>(null)
39
- const [error, setError] = useState<string | null>(null)
40
- const [copiedWalletId, setCopiedWalletId] = useState<string | null>(null)
41
-
42
- const connectedChains = useMemo(() => new Set(wallets.map((wallet) => wallet.chain)), [wallets])
43
- const availableChains = useMemo(
44
- () => SUPPORTED_WALLET_CHAINS.filter((chain) => !connectedChains.has(chain)),
45
- [connectedChains],
46
- )
47
- const sortedWallets = useMemo(
48
- () => [...wallets].sort((a, b) => {
49
- const aActive = a.id === activeWalletId || a.isActive === true
50
- const bActive = b.id === activeWalletId || b.isActive === true
51
- if (aActive !== bActive) return aActive ? -1 : 1
52
- return a.chain.localeCompare(b.chain)
53
- }),
54
- [activeWalletId, wallets],
55
- )
56
-
57
- const [chain, setChain] = useState<WalletChain>(availableChains[0] || 'solana')
58
- const walletApprovalsEnabled = appSettings.walletApprovalsEnabled !== false
59
-
60
- useEffect(() => {
61
- if (availableChains.length === 0) return
62
- if (!availableChains.includes(chain)) setChain(availableChains[0])
63
- }, [availableChains, chain])
64
-
65
- const createWallet = useCallback(async () => {
66
- if (!availableChains.includes(chain)) return
67
- setCreating(true)
68
- setError(null)
69
- try {
70
- await api('POST', '/wallets', { agentId, chain })
71
- toast.success('Agent wallet created successfully')
72
- await onWalletCreated()
73
- } catch (err: unknown) {
74
- const msg = errorMessage(err)
75
- setError(msg)
76
- toast.error(msg)
77
- } finally {
78
- setCreating(false)
79
- }
80
- }, [agentId, availableChains, chain, onWalletCreated])
81
-
82
- const copyAddress = useCallback(async (wallet: SafeWallet) => {
83
- const copiedValue = await copyTextToClipboard(wallet.publicKey)
84
- if (!copiedValue) return
85
- setCopiedWalletId(wallet.id)
86
- setTimeout(() => {
87
- setCopiedWalletId((current) => current === wallet.id ? null : current)
88
- }, 2000)
89
- }, [])
90
-
91
- const setActiveWallet = useCallback(async (walletId: string) => {
92
- setActivatingWalletId(walletId)
93
- setError(null)
94
- try {
95
- await api('PATCH', `/wallets/${walletId}`, { makeActive: true })
96
- toast.success('Default wallet updated')
97
- await onWalletCreated()
98
- } catch (err: unknown) {
99
- const msg = errorMessage(err)
100
- setError(msg)
101
- toast.error(msg)
102
- } finally {
103
- setActivatingWalletId(null)
104
- }
105
- }, [onWalletCreated])
106
-
107
- return (
108
- <div className="mb-8">
109
- <div className="flex items-center gap-2 mb-3">
110
- <label className="block font-display text-[12px] font-600 text-text-2 uppercase tracking-[0.08em]">
111
- Wallets
112
- </label>
113
- <span className="px-1.5 py-0.5 rounded-[4px] bg-amber-500/15 text-amber-400 text-[9px] font-600 uppercase tracking-wide">
114
- Experimental
115
- </span>
116
- </div>
117
-
118
- {sortedWallets.length > 0 ? (
119
- <div className="space-y-3">
120
- <div className="p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
121
- <div className="flex items-center justify-between gap-3 mb-2">
122
- <div>
123
- <div className="text-[12px] font-600 text-text-1">Combined Wallet Summary</div>
124
- <p className="text-[11px] text-text-3/70 mt-1">
125
- {sortedWallets.length} wallet{sortedWallets.length === 1 ? '' : 's'} connected. The active wallet is used by default when the agent does not specify a chain explicitly.
126
- </p>
127
- </div>
128
- <div className="text-right">
129
- <div className="text-[18px] font-600 text-text-1">{sortedWallets.length}</div>
130
- <div className="text-[10px] uppercase tracking-wide text-text-3/50">Connected</div>
131
- </div>
132
- </div>
133
- <div className="grid gap-2 md:grid-cols-2">
134
- {sortedWallets.map((wallet) => {
135
- const walletMeta = getWalletChainMeta(wallet.chain)
136
- const balanceFormatted = wallet.balanceFormatted || formatWalletAmount(wallet.chain, getWalletBalanceAtomic(wallet), { minFractionDigits: 4, maxFractionDigits: 6 })
137
- const isActive = wallet.id === activeWalletId || wallet.isActive === true
138
- return (
139
- <div key={wallet.id} className="rounded-[10px] border border-white/[0.06] bg-black/10 px-3 py-2">
140
- <div className="flex items-center gap-2">
141
- <span className="text-[10px] text-text-3/60 uppercase tracking-wide font-600">{walletMeta.label}</span>
142
- {isActive && (
143
- <span className="px-1.5 py-0.5 rounded-[999px] bg-accent-soft text-accent-bright text-[9px] font-600 uppercase tracking-wide">
144
- Active
145
- </span>
146
- )}
147
- </div>
148
- <div className="mt-1 text-[14px] font-600 text-text-1">{balanceFormatted} {walletMeta.symbol}</div>
149
- {wallet.portfolioSummary?.nonZeroAssets ? (
150
- <div className="mt-1 text-[10px] text-text-3/55">
151
- {wallet.portfolioSummary.nonZeroAssets} asset{wallet.portfolioSummary.nonZeroAssets === 1 ? '' : 's'} tracked
152
- </div>
153
- ) : null}
154
- <div className="mt-1 text-[10px] text-text-3/55 font-mono truncate">{wallet.publicKey}</div>
155
- </div>
156
- )
157
- })}
158
- </div>
159
- </div>
160
-
161
- {sortedWallets.map((wallet) => {
162
- const walletMeta = getWalletChainMeta(wallet.chain)
163
- const balanceFormatted = wallet.balanceFormatted || formatWalletAmount(wallet.chain, getWalletBalanceAtomic(wallet), { minFractionDigits: 4, maxFractionDigits: 6 })
164
- const perTxLimit = formatWalletAmount(wallet.chain, getWalletLimitAtomic(wallet, 'perTx'), { maxFractionDigits: 6 })
165
- const dailyLimit = formatWalletAmount(wallet.chain, getWalletLimitAtomic(wallet, 'daily'), { maxFractionDigits: 6 })
166
- const isActive = wallet.id === activeWalletId || wallet.isActive === true
167
- return (
168
- <div key={wallet.id} className="p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50 space-y-3">
169
- <div className="flex items-center gap-2">
170
- <span className="text-[10px] text-text-3/60 uppercase tracking-wide font-600">
171
- {walletMeta.label}
172
- </span>
173
- {isActive && (
174
- <span className="px-1.5 py-0.5 rounded-[999px] bg-accent-soft text-accent-bright text-[9px] font-600 uppercase tracking-wide">
175
- Default
176
- </span>
177
- )}
178
- <span className="flex-1" />
179
- <span className="text-[13px] font-600 text-text-1">
180
- {balanceFormatted} {walletMeta.symbol}
181
- </span>
182
- </div>
183
- <div className="flex items-center gap-2">
184
- <code className="text-[11px] text-text-3 bg-black/20 px-2 py-1 rounded-[6px] font-mono truncate flex-1">
185
- {wallet.publicKey}
186
- </code>
187
- <button
188
- type="button"
189
- onClick={() => copyAddress(wallet)}
190
- className="shrink-0 px-2 py-1 rounded-[6px] text-[10px] text-text-3 hover:text-text-2 border border-white/[0.08] bg-surface transition-colors cursor-pointer"
191
- style={{ fontFamily: 'inherit' }}
192
- >
193
- {copiedWalletId === wallet.id ? 'Copied!' : 'Copy'}
194
- </button>
195
- {!isActive && (
196
- <button
197
- type="button"
198
- onClick={() => setActiveWallet(wallet.id)}
199
- disabled={activatingWalletId === wallet.id}
200
- className="shrink-0 px-2 py-1 rounded-[6px] text-[10px] font-600 text-accent-bright border border-accent-bright/20 bg-accent-soft/10 hover:bg-accent-soft/20 transition-colors cursor-pointer disabled:opacity-50"
201
- style={{ fontFamily: 'inherit' }}
202
- >
203
- {activatingWalletId === wallet.id ? 'Setting...' : 'Set Default'}
204
- </button>
205
- )}
206
- </div>
207
- <div className="flex flex-wrap items-center gap-3 text-[10px] text-text-3/60">
208
- <span>Limit: {perTxLimit} {walletMeta.symbol}/tx</span>
209
- <span>Daily: {dailyLimit} {walletMeta.symbol}</span>
210
- <span>{!walletApprovalsEnabled ? 'Approvals off globally' : (wallet.requireApproval ? 'Approval required' : 'Auto-send')}</span>
211
- {wallet.portfolioSummary?.nonZeroAssets ? (
212
- <span>{wallet.portfolioSummary.nonZeroAssets} asset{wallet.portfolioSummary.nonZeroAssets === 1 ? '' : 's'} detected</span>
213
- ) : null}
214
- </div>
215
- </div>
216
- )
217
- })}
218
- </div>
219
- ) : null}
220
-
221
- {availableChains.length > 0 ? (
222
- <div className="mt-3 p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
223
- <p className="text-[12px] text-text-3/70 mb-3">
224
- {getWalletChainMeta(chain).createDescription}
225
- </p>
226
- <label className="block text-[11px] text-text-3/70 mb-1">Wallet Type</label>
227
- <select
228
- value={chain}
229
- onChange={(event) => setChain(event.target.value as WalletChain)}
230
- className="w-full mb-3 px-3 py-2 rounded-[8px] border border-white/[0.08] bg-surface text-[12px] text-text-1 outline-none focus:border-accent/40"
231
- style={{ fontFamily: 'inherit' }}
232
- >
233
- {availableChains.map((availableChain) => (
234
- <option key={availableChain} value={availableChain}>
235
- {getWalletChainMeta(availableChain).label}
236
- </option>
237
- ))}
238
- </select>
239
- <button
240
- type="button"
241
- onClick={createWallet}
242
- disabled={creating}
243
- className="px-3 py-1.5 rounded-[8px] bg-accent-soft text-accent-bright text-[11px] font-600 hover:bg-accent-bright/15 transition-all cursor-pointer disabled:opacity-50 border border-accent-bright/20"
244
- style={{ fontFamily: 'inherit' }}
245
- >
246
- {creating ? 'Creating...' : `Create ${getWalletChainMeta(chain).label} Wallet`}
247
- </button>
248
- {error && <p className="text-[11px] text-red-400 mt-2">{error}</p>}
249
- </div>
250
- ) : (
251
- <div className="mt-3 p-4 rounded-[12px] border border-white/[0.06] bg-surface-2/50">
252
- <p className="text-[12px] text-text-3/70">
253
- This agent already has both supported wallet types connected. Use the default toggle above to choose which wallet autonomous actions use when no chain is specified.
254
- </p>
255
- {error && <p className="text-[11px] text-red-400 mt-2">{error}</p>}
256
- </div>
257
- )}
258
- </div>
259
- )
260
- }
@@ -1,23 +0,0 @@
1
- import { useQuery } from '@tanstack/react-query'
2
- import { api } from '@/lib/app/api-client'
3
- import type { Mission } from '@/types'
4
-
5
- type MissionListOptions = {
6
- enabled?: boolean
7
- limit?: number
8
- }
9
-
10
- export const missionQueryKeys = {
11
- all: ['missions'] as const,
12
- list: (limit: number) => ['missions', 'list', { limit }] as const,
13
- }
14
-
15
- export function useMissionsQuery(options: MissionListOptions = {}) {
16
- const limit = options.limit ?? 80
17
- return useQuery<Mission[]>({
18
- queryKey: missionQueryKeys.list(limit),
19
- queryFn: () => api<Mission[]>('GET', `/missions?limit=${limit}`),
20
- enabled: options.enabled,
21
- staleTime: 30_000,
22
- })
23
- }
@@ -1,360 +0,0 @@
1
- import { describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import {
4
- normalizeCanvasDocument,
5
- normalizeCanvasContent,
6
- isCanvasDocument,
7
- summarizeCanvasContent,
8
- } from './canvas-content'
9
-
10
- describe('normalizeCanvasContent', () => {
11
- it('returns null for null/undefined', () => {
12
- assert.equal(normalizeCanvasContent(null), null)
13
- assert.equal(normalizeCanvasContent(undefined), null)
14
- })
15
-
16
- it('returns string content as-is', () => {
17
- assert.equal(normalizeCanvasContent('<p>hello</p>'), '<p>hello</p>')
18
- })
19
-
20
- it('returns null for empty string', () => {
21
- assert.equal(normalizeCanvasContent(''), null)
22
- })
23
-
24
- it('normalizes a valid structured document', () => {
25
- const doc = normalizeCanvasContent({
26
- title: 'Test',
27
- blocks: [{ type: 'markdown', markdown: '# Hello' }],
28
- })
29
- assert.ok(doc !== null && typeof doc === 'object')
30
- assert.equal((doc as { kind: string }).kind, 'structured')
31
- })
32
-
33
- it('returns null for object without valid blocks', () => {
34
- assert.equal(normalizeCanvasContent({ blocks: [] }), null)
35
- assert.equal(normalizeCanvasContent({}), null)
36
- assert.equal(normalizeCanvasContent({ blocks: [{ type: 'unknown' }] }), null)
37
- })
38
- })
39
-
40
- describe('normalizeCanvasDocument', () => {
41
- it('returns null for non-object input', () => {
42
- assert.equal(normalizeCanvasDocument('string'), null)
43
- assert.equal(normalizeCanvasDocument(42), null)
44
- assert.equal(normalizeCanvasDocument(null), null)
45
- assert.equal(normalizeCanvasDocument([]), null)
46
- assert.equal(normalizeCanvasDocument(true), null)
47
- })
48
-
49
- it('normalizes a markdown block', () => {
50
- const doc = normalizeCanvasDocument({
51
- blocks: [{ type: 'markdown', markdown: '# Hi' }],
52
- })
53
- assert.ok(doc)
54
- assert.equal(doc.blocks.length, 1)
55
- assert.equal(doc.blocks[0].type, 'markdown')
56
- assert.equal(doc.kind, 'structured')
57
- assert.equal(doc.theme, 'slate') // default theme
58
- })
59
-
60
- it('applies theme when valid', () => {
61
- const doc = normalizeCanvasDocument({
62
- blocks: [{ type: 'markdown', markdown: 'x' }],
63
- theme: 'emerald',
64
- })
65
- assert.ok(doc)
66
- assert.equal(doc.theme, 'emerald')
67
- })
68
-
69
- it('defaults to slate for invalid theme', () => {
70
- const doc = normalizeCanvasDocument({
71
- blocks: [{ type: 'markdown', markdown: 'x' }],
72
- theme: 'neon',
73
- })
74
- assert.ok(doc)
75
- assert.equal(doc.theme, 'slate')
76
- })
77
-
78
- it('truncates title and subtitle', () => {
79
- const doc = normalizeCanvasDocument({
80
- title: 'A'.repeat(300),
81
- subtitle: 'B'.repeat(500),
82
- blocks: [{ type: 'markdown', markdown: 'x' }],
83
- })
84
- assert.ok(doc)
85
- assert.equal(doc.title!.length, 180)
86
- assert.equal(doc.subtitle!.length, 320)
87
- })
88
-
89
- it('skips invalid blocks and keeps valid ones', () => {
90
- const doc = normalizeCanvasDocument({
91
- blocks: [
92
- { type: 'markdown', markdown: 'valid' },
93
- { type: 'unknown_type' },
94
- null,
95
- 'just a string',
96
- { type: 'code', code: 'console.log(1)' },
97
- ],
98
- })
99
- assert.ok(doc)
100
- assert.equal(doc.blocks.length, 2)
101
- assert.equal(doc.blocks[0].type, 'markdown')
102
- assert.equal(doc.blocks[1].type, 'code')
103
- })
104
-
105
- it('enforces max 24 blocks', () => {
106
- const blocks = Array.from({ length: 30 }, () => ({
107
- type: 'markdown',
108
- markdown: 'x',
109
- }))
110
- const doc = normalizeCanvasDocument({ blocks })
111
- assert.ok(doc)
112
- assert.equal(doc.blocks.length, 24)
113
- })
114
-
115
- it('normalizes metrics block', () => {
116
- const doc = normalizeCanvasDocument({
117
- blocks: [{
118
- type: 'metrics',
119
- items: [
120
- { label: 'CPU', value: '90%', tone: 'warning' },
121
- { label: 'Mem', value: '4GB', detail: 'of 16GB', tone: 'positive' },
122
- { label: 'missing value' }, // should be skipped
123
- { value: 'missing label' }, // should be skipped
124
- ],
125
- }],
126
- })
127
- assert.ok(doc)
128
- assert.equal(doc.blocks.length, 1)
129
- const block = doc.blocks[0] as { type: string; items: Array<{ label: string; value: string; tone: string }> }
130
- assert.equal(block.items.length, 2)
131
- assert.equal(block.items[0].tone, 'warning')
132
- assert.equal(block.items[1].tone, 'positive')
133
- })
134
-
135
- it('enforces max 24 metric items', () => {
136
- const items = Array.from({ length: 30 }, (_, i) => ({
137
- label: `L${i}`, value: `V${i}`,
138
- }))
139
- const doc = normalizeCanvasDocument({
140
- blocks: [{ type: 'metrics', items }],
141
- })
142
- assert.ok(doc)
143
- const block = doc.blocks[0] as { items: unknown[] }
144
- assert.equal(block.items.length, 24)
145
- })
146
-
147
- it('normalizes cards block', () => {
148
- const doc = normalizeCanvasDocument({
149
- blocks: [{
150
- type: 'cards',
151
- items: [
152
- { title: 'Card 1', body: 'content', tone: 'negative' },
153
- { body: 'no title' }, // skipped
154
- ],
155
- }],
156
- })
157
- assert.ok(doc)
158
- const block = doc.blocks[0] as { items: Array<{ title: string; tone: string }> }
159
- assert.equal(block.items.length, 1)
160
- assert.equal(block.items[0].tone, 'negative')
161
- })
162
-
163
- it('normalizes table block', () => {
164
- const doc = normalizeCanvasDocument({
165
- blocks: [{
166
- type: 'table',
167
- table: {
168
- columns: ['Name', 'Age'],
169
- rows: [['Alice', 30], ['Bob', true]],
170
- caption: 'Users',
171
- },
172
- }],
173
- })
174
- assert.ok(doc)
175
- assert.equal(doc.blocks[0].type, 'table')
176
- })
177
-
178
- it('rejects table with no columns', () => {
179
- const doc = normalizeCanvasDocument({
180
- blocks: [{
181
- type: 'table',
182
- table: { columns: [], rows: [] },
183
- }],
184
- })
185
- assert.equal(doc, null) // no valid blocks
186
- })
187
-
188
- it('rejects table with no rows', () => {
189
- const doc = normalizeCanvasDocument({
190
- blocks: [{
191
- type: 'table',
192
- table: { columns: ['A'], rows: [] },
193
- }],
194
- })
195
- assert.equal(doc, null)
196
- })
197
-
198
- it('limits table to 20 columns and 100 rows', () => {
199
- const columns = Array.from({ length: 25 }, (_, i) => `Col${i}`)
200
- const rows = Array.from({ length: 110 }, () => columns.map((_, i) => `val${i}`))
201
- const doc = normalizeCanvasDocument({
202
- blocks: [{
203
- type: 'table',
204
- table: { columns, rows },
205
- }],
206
- })
207
- assert.ok(doc)
208
- const block = doc.blocks[0] as { table: { columns: string[]; rows: unknown[][] } }
209
- assert.equal(block.table.columns.length, 20)
210
- assert.equal(block.table.rows.length, 100)
211
- })
212
-
213
- it('normalizes actions block', () => {
214
- const doc = normalizeCanvasDocument({
215
- blocks: [{
216
- type: 'actions',
217
- items: [
218
- { label: 'Submit', intent: 'primary', href: 'https://x.com' },
219
- { label: 'Delete', intent: 'danger' },
220
- { intent: 'primary' }, // no label → skipped
221
- ],
222
- }],
223
- })
224
- assert.ok(doc)
225
- const block = doc.blocks[0] as { items: Array<{ label: string; intent: string }> }
226
- assert.equal(block.items.length, 2)
227
- assert.equal(block.items[0].intent, 'primary')
228
- assert.equal(block.items[1].intent, 'danger')
229
- })
230
-
231
- it('defaults action intent to secondary', () => {
232
- const doc = normalizeCanvasDocument({
233
- blocks: [{
234
- type: 'actions',
235
- items: [{ label: 'Go', intent: 'invalid' }],
236
- }],
237
- })
238
- assert.ok(doc)
239
- const block = doc.blocks[0] as { items: Array<{ intent: string }> }
240
- assert.equal(block.items[0].intent, 'secondary')
241
- })
242
-
243
- it('normalizes code block with language', () => {
244
- const doc = normalizeCanvasDocument({
245
- blocks: [{
246
- type: 'code',
247
- code: 'const x = 1;',
248
- language: 'typescript',
249
- }],
250
- })
251
- assert.ok(doc)
252
- const block = doc.blocks[0] as { type: string; code: string; language: string }
253
- assert.equal(block.code, 'const x = 1;')
254
- assert.equal(block.language, 'typescript')
255
- })
256
-
257
- it('rejects code block with empty code', () => {
258
- const doc = normalizeCanvasDocument({
259
- blocks: [{ type: 'code', code: '' }],
260
- })
261
- assert.equal(doc, null)
262
- })
263
-
264
- it('coerces number/boolean to string in asTrimmedString contexts', () => {
265
- const doc = normalizeCanvasDocument({
266
- blocks: [{
267
- type: 'metrics',
268
- items: [{ label: 42, value: true }],
269
- }],
270
- })
271
- assert.ok(doc)
272
- const block = doc.blocks[0] as { items: Array<{ label: string; value: string }> }
273
- assert.equal(block.items[0].label, '42')
274
- assert.equal(block.items[0].value, 'true')
275
- })
276
-
277
- it('truncates markdown content to 20000 chars', () => {
278
- const longMarkdown = 'x'.repeat(25000)
279
- const doc = normalizeCanvasDocument({
280
- blocks: [{ type: 'markdown', markdown: longMarkdown }],
281
- })
282
- assert.ok(doc)
283
- const block = doc.blocks[0] as { markdown: string }
284
- assert.equal(block.markdown.length, 20000)
285
- })
286
-
287
- it('uses provided updatedAt when valid', () => {
288
- const doc = normalizeCanvasDocument({
289
- blocks: [{ type: 'markdown', markdown: 'x' }],
290
- updatedAt: 1234567890,
291
- })
292
- assert.ok(doc)
293
- assert.equal(doc.updatedAt, 1234567890)
294
- })
295
-
296
- it('falls back to Date.now() for invalid updatedAt', () => {
297
- const before = Date.now()
298
- const doc = normalizeCanvasDocument({
299
- blocks: [{ type: 'markdown', markdown: 'x' }],
300
- updatedAt: 'not a number',
301
- })
302
- const after = Date.now()
303
- assert.ok(doc)
304
- assert.ok(doc.updatedAt! >= before && doc.updatedAt! <= after)
305
- })
306
- })
307
-
308
- describe('isCanvasDocument', () => {
309
- it('returns true for valid document object', () => {
310
- assert.equal(isCanvasDocument({
311
- blocks: [{ type: 'markdown', markdown: 'hi' }],
312
- }), true)
313
- })
314
-
315
- it('returns false for non-documents', () => {
316
- assert.equal(isCanvasDocument('string'), false)
317
- assert.equal(isCanvasDocument(null), false)
318
- assert.equal(isCanvasDocument({}), false)
319
- assert.equal(isCanvasDocument({ blocks: [] }), false)
320
- })
321
- })
322
-
323
- describe('summarizeCanvasContent', () => {
324
- it('summarizes null content', () => {
325
- const summary = summarizeCanvasContent(null)
326
- assert.equal(summary.kind, 'empty')
327
- assert.equal(summary.hasContent, false)
328
- assert.equal(summary.contentLength, 0)
329
- })
330
-
331
- it('summarizes string content', () => {
332
- const summary = summarizeCanvasContent('<p>hello</p>')
333
- assert.equal(summary.kind, 'html')
334
- assert.equal(summary.hasContent, true)
335
- assert.equal(summary.contentLength, 12)
336
- assert.equal(summary.preview, '<p>hello</p>')
337
- })
338
-
339
- it('truncates preview to 500 chars for long strings', () => {
340
- const long = 'x'.repeat(600)
341
- const summary = summarizeCanvasContent(long)
342
- assert.equal((summary.preview as string).length, 500)
343
- })
344
-
345
- it('summarizes structured document', () => {
346
- const doc = normalizeCanvasDocument({
347
- title: 'My Doc',
348
- blocks: [
349
- { type: 'markdown', markdown: '# Heading' },
350
- { type: 'code', code: 'x = 1' },
351
- ],
352
- })!
353
- const summary = summarizeCanvasContent(doc)
354
- assert.equal(summary.kind, 'structured')
355
- assert.equal(summary.hasContent, true)
356
- assert.equal(summary.blockCount, 2)
357
- assert.equal(summary.title, 'My Doc')
358
- assert.deepEqual(summary.blockTypes, ['markdown', 'code'])
359
- })
360
- })