@swarmclawai/swarmclaw 0.6.7 → 0.7.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 (203) hide show
  1. package/README.md +82 -39
  2. package/next.config.ts +31 -6
  3. package/package.json +3 -2
  4. package/src/app/api/agents/[id]/thread/route.ts +1 -0
  5. package/src/app/api/agents/route.ts +19 -5
  6. package/src/app/api/approvals/route.ts +22 -0
  7. package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
  8. package/src/app/api/clawhub/install/route.ts +2 -2
  9. package/src/app/api/eval/run/route.ts +37 -0
  10. package/src/app/api/eval/scenarios/route.ts +24 -0
  11. package/src/app/api/eval/suite/route.ts +29 -0
  12. package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
  13. package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
  14. package/src/app/api/memory/graph/route.ts +46 -0
  15. package/src/app/api/memory/route.ts +36 -5
  16. package/src/app/api/notifications/route.ts +3 -0
  17. package/src/app/api/plugins/install/route.ts +57 -5
  18. package/src/app/api/plugins/marketplace/route.ts +73 -22
  19. package/src/app/api/plugins/route.ts +61 -1
  20. package/src/app/api/plugins/ui/route.ts +34 -0
  21. package/src/app/api/sessions/[id]/checkpoints/route.ts +31 -0
  22. package/src/app/api/sessions/[id]/restore/route.ts +36 -0
  23. package/src/app/api/settings/route.ts +62 -0
  24. package/src/app/api/setup/doctor/route.ts +22 -5
  25. package/src/app/api/souls/[id]/route.ts +65 -0
  26. package/src/app/api/souls/route.ts +70 -0
  27. package/src/app/api/tasks/[id]/approve/route.ts +4 -3
  28. package/src/app/api/tasks/[id]/route.ts +16 -3
  29. package/src/app/api/tasks/route.ts +10 -2
  30. package/src/app/api/usage/route.ts +9 -2
  31. package/src/app/globals.css +27 -0
  32. package/src/app/page.tsx +10 -5
  33. package/src/cli/index.js +37 -0
  34. package/src/components/activity/activity-feed.tsx +9 -2
  35. package/src/components/agents/agent-avatar.tsx +5 -1
  36. package/src/components/agents/agent-card.tsx +55 -9
  37. package/src/components/agents/agent-sheet.tsx +112 -34
  38. package/src/components/agents/inspector-panel.tsx +1 -1
  39. package/src/components/agents/soul-library-picker.tsx +84 -13
  40. package/src/components/auth/access-key-gate.tsx +63 -54
  41. package/src/components/auth/user-picker.tsx +37 -32
  42. package/src/components/chat/activity-moment.tsx +2 -0
  43. package/src/components/chat/chat-area.tsx +11 -0
  44. package/src/components/chat/chat-header.tsx +69 -25
  45. package/src/components/chat/chat-tool-toggles.tsx +2 -2
  46. package/src/components/chat/checkpoint-timeline.tsx +112 -0
  47. package/src/components/chat/code-block.tsx +3 -1
  48. package/src/components/chat/exec-approval-card.tsx +8 -1
  49. package/src/components/chat/message-bubble.tsx +164 -4
  50. package/src/components/chat/message-list.tsx +46 -4
  51. package/src/components/chat/session-approval-card.tsx +80 -0
  52. package/src/components/chat/session-debug-panel.tsx +106 -84
  53. package/src/components/chat/streaming-bubble.tsx +6 -5
  54. package/src/components/chat/task-approval-card.tsx +78 -0
  55. package/src/components/chat/thinking-indicator.tsx +48 -12
  56. package/src/components/chat/tool-call-bubble.tsx +3 -0
  57. package/src/components/chat/tool-request-banner.tsx +39 -20
  58. package/src/components/chatrooms/chatroom-list.tsx +11 -4
  59. package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
  60. package/src/components/connectors/connector-list.tsx +33 -11
  61. package/src/components/connectors/connector-sheet.tsx +37 -7
  62. package/src/components/home/home-view.tsx +54 -24
  63. package/src/components/input/chat-input.tsx +22 -1
  64. package/src/components/knowledge/knowledge-list.tsx +17 -18
  65. package/src/components/knowledge/knowledge-sheet.tsx +9 -5
  66. package/src/components/layout/app-layout.tsx +87 -19
  67. package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
  68. package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
  69. package/src/components/memory/memory-browser.tsx +73 -45
  70. package/src/components/memory/memory-graph-view.tsx +203 -0
  71. package/src/components/memory/memory-list.tsx +20 -13
  72. package/src/components/plugins/plugin-list.tsx +214 -60
  73. package/src/components/plugins/plugin-sheet.tsx +119 -24
  74. package/src/components/projects/project-list.tsx +17 -9
  75. package/src/components/providers/provider-list.tsx +21 -6
  76. package/src/components/providers/provider-sheet.tsx +42 -25
  77. package/src/components/runs/run-list.tsx +17 -13
  78. package/src/components/schedules/schedule-card.tsx +10 -3
  79. package/src/components/schedules/schedule-list.tsx +2 -2
  80. package/src/components/schedules/schedule-sheet.tsx +28 -9
  81. package/src/components/secrets/secret-sheet.tsx +7 -2
  82. package/src/components/secrets/secrets-list.tsx +18 -5
  83. package/src/components/sessions/new-session-sheet.tsx +183 -376
  84. package/src/components/sessions/session-card.tsx +10 -2
  85. package/src/components/settings/gateway-connection-panel.tsx +9 -8
  86. package/src/components/shared/command-palette.tsx +13 -5
  87. package/src/components/shared/empty-state.tsx +20 -8
  88. package/src/components/shared/hint-tip.tsx +31 -0
  89. package/src/components/shared/notification-center.tsx +134 -86
  90. package/src/components/shared/profile-sheet.tsx +4 -0
  91. package/src/components/shared/settings/plugin-manager.tsx +360 -135
  92. package/src/components/shared/settings/section-capability-policy.tsx +3 -3
  93. package/src/components/shared/settings/section-runtime-loop.tsx +149 -4
  94. package/src/components/skills/clawhub-browser.tsx +1 -0
  95. package/src/components/skills/skill-list.tsx +31 -12
  96. package/src/components/skills/skill-sheet.tsx +20 -7
  97. package/src/components/tasks/approvals-panel.tsx +224 -0
  98. package/src/components/tasks/task-board.tsx +20 -12
  99. package/src/components/tasks/task-card.tsx +21 -7
  100. package/src/components/tasks/task-column.tsx +4 -3
  101. package/src/components/tasks/task-list.tsx +1 -1
  102. package/src/components/tasks/task-sheet.tsx +130 -1
  103. package/src/components/ui/dialog.tsx +1 -0
  104. package/src/components/ui/sheet.tsx +1 -0
  105. package/src/components/usage/metrics-dashboard.tsx +72 -48
  106. package/src/components/wallets/wallet-panel.tsx +65 -41
  107. package/src/components/wallets/wallet-section.tsx +9 -3
  108. package/src/components/webhooks/webhook-list.tsx +21 -12
  109. package/src/components/webhooks/webhook-sheet.tsx +13 -3
  110. package/src/lib/approval-display.test.ts +45 -0
  111. package/src/lib/approval-display.ts +62 -0
  112. package/src/lib/clipboard.ts +38 -0
  113. package/src/lib/memory.ts +8 -0
  114. package/src/lib/providers/claude-cli.ts +5 -3
  115. package/src/lib/providers/index.ts +67 -21
  116. package/src/lib/runtime-loop.ts +3 -2
  117. package/src/lib/server/approvals.ts +150 -0
  118. package/src/lib/server/chat-execution.ts +319 -74
  119. package/src/lib/server/chatroom-helpers.ts +63 -5
  120. package/src/lib/server/chatroom-orchestration.ts +74 -0
  121. package/src/lib/server/clawhub-client.ts +82 -6
  122. package/src/lib/server/connectors/manager.ts +27 -1
  123. package/src/lib/server/context-manager.ts +132 -50
  124. package/src/lib/server/cost.test.ts +73 -0
  125. package/src/lib/server/cost.ts +165 -34
  126. package/src/lib/server/daemon-state.ts +112 -1
  127. package/src/lib/server/data-dir.ts +18 -1
  128. package/src/lib/server/eval/runner.ts +126 -0
  129. package/src/lib/server/eval/scenarios.ts +218 -0
  130. package/src/lib/server/eval/scorer.ts +96 -0
  131. package/src/lib/server/eval/store.ts +37 -0
  132. package/src/lib/server/eval/types.ts +48 -0
  133. package/src/lib/server/execution-log.ts +12 -8
  134. package/src/lib/server/guardian.ts +34 -0
  135. package/src/lib/server/heartbeat-service.ts +53 -1
  136. package/src/lib/server/integrity-monitor.ts +208 -0
  137. package/src/lib/server/langgraph-checkpoint.ts +10 -0
  138. package/src/lib/server/link-understanding.ts +55 -0
  139. package/src/lib/server/llm-response-cache.test.ts +102 -0
  140. package/src/lib/server/llm-response-cache.ts +227 -0
  141. package/src/lib/server/main-agent-loop.ts +115 -16
  142. package/src/lib/server/main-session.ts +6 -3
  143. package/src/lib/server/mcp-conformance.test.ts +18 -0
  144. package/src/lib/server/mcp-conformance.ts +233 -0
  145. package/src/lib/server/memory-db.ts +193 -19
  146. package/src/lib/server/memory-retrieval.test.ts +56 -0
  147. package/src/lib/server/mmr.ts +73 -0
  148. package/src/lib/server/orchestrator-lg.ts +7 -1
  149. package/src/lib/server/orchestrator.ts +4 -3
  150. package/src/lib/server/plugins.ts +662 -132
  151. package/src/lib/server/process-manager.ts +18 -0
  152. package/src/lib/server/query-expansion.ts +57 -0
  153. package/src/lib/server/queue.ts +280 -11
  154. package/src/lib/server/runtime-settings.ts +9 -0
  155. package/src/lib/server/session-run-manager.test.ts +23 -0
  156. package/src/lib/server/session-run-manager.ts +32 -2
  157. package/src/lib/server/session-tools/canvas.ts +85 -50
  158. package/src/lib/server/session-tools/chatroom.ts +130 -127
  159. package/src/lib/server/session-tools/connector.ts +233 -454
  160. package/src/lib/server/session-tools/context-mgmt.ts +87 -105
  161. package/src/lib/server/session-tools/crud.ts +84 -7
  162. package/src/lib/server/session-tools/delegate.ts +351 -752
  163. package/src/lib/server/session-tools/discovery.ts +198 -0
  164. package/src/lib/server/session-tools/edit_file.ts +82 -0
  165. package/src/lib/server/session-tools/file-send.test.ts +39 -0
  166. package/src/lib/server/session-tools/file.ts +257 -425
  167. package/src/lib/server/session-tools/git.ts +87 -47
  168. package/src/lib/server/session-tools/http.ts +95 -33
  169. package/src/lib/server/session-tools/index.ts +217 -138
  170. package/src/lib/server/session-tools/memory.ts +154 -239
  171. package/src/lib/server/session-tools/monitor.ts +126 -0
  172. package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
  173. package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
  174. package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
  175. package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
  176. package/src/lib/server/session-tools/platform.ts +86 -0
  177. package/src/lib/server/session-tools/plugin-creator.ts +239 -0
  178. package/src/lib/server/session-tools/sample-ui.ts +97 -0
  179. package/src/lib/server/session-tools/sandbox.ts +175 -148
  180. package/src/lib/server/session-tools/schedule.ts +78 -0
  181. package/src/lib/server/session-tools/session-info.ts +104 -410
  182. package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
  183. package/src/lib/server/session-tools/shell.ts +171 -143
  184. package/src/lib/server/session-tools/subagent.ts +77 -77
  185. package/src/lib/server/session-tools/wallet.ts +182 -106
  186. package/src/lib/server/session-tools/web.ts +181 -327
  187. package/src/lib/server/storage.ts +36 -0
  188. package/src/lib/server/stream-agent-chat.ts +348 -242
  189. package/src/lib/server/task-quality-gate.test.ts +44 -0
  190. package/src/lib/server/task-quality-gate.ts +67 -0
  191. package/src/lib/server/task-validation.test.ts +78 -0
  192. package/src/lib/server/task-validation.ts +67 -2
  193. package/src/lib/server/tool-aliases.ts +68 -0
  194. package/src/lib/server/tool-capability-policy.ts +24 -5
  195. package/src/lib/server/tool-retry.ts +62 -0
  196. package/src/lib/server/transcript-repair.ts +72 -0
  197. package/src/lib/setup-defaults.ts +1 -0
  198. package/src/lib/tasks.ts +7 -1
  199. package/src/lib/tool-definitions.ts +24 -23
  200. package/src/lib/validation/schemas.ts +13 -0
  201. package/src/lib/view-routes.ts +2 -23
  202. package/src/stores/use-app-store.ts +23 -1
  203. package/src/types/index.ts +155 -10
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useState, useEffect, useCallback, useMemo } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { copyTextToClipboard } from '@/lib/clipboard'
5
6
  import { useAppStore } from '@/stores/use-app-store'
6
7
  import { useWs } from '@/hooks/use-ws'
7
8
  import { WalletApprovalDialog } from './wallet-approval-dialog'
@@ -11,19 +12,24 @@ import type { AgentWallet, WalletTransaction, WalletBalanceSnapshot, Agent } fro
11
12
 
12
13
  type SafeWallet = Omit<AgentWallet, 'encryptedPrivateKey'> & { balanceLamports?: number; balanceSol?: number }
13
14
 
14
- function SolanaIcon({ size = 12, className = '' }: { size?: number; className?: string }) {
15
+ function SolanaIcon({ size = 12, className = '', shimmer = false }: { size?: number; className?: string; shimmer?: boolean }) {
15
16
  return (
16
- <svg width={size} height={size} viewBox="0 0 128 128" className={className}>
17
- <defs>
18
- <linearGradient id="sol-grad" x1="0%" y1="0%" x2="100%" y2="100%">
19
- <stop offset="0%" stopColor="#00FFA3" />
20
- <stop offset="100%" stopColor="#DC1FFF" />
21
- </linearGradient>
22
- </defs>
23
- <path d="M25.5 100.5a4.3 4.3 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7l-17.7 17.8a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7l17.7-17.8z" fill="url(#sol-grad)" />
24
- <path d="M25.5 7.3a4.4 4.4 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7L105.5 27.5a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7L25.5 7.3z" fill="url(#sol-grad)" />
25
- <path d="M105.5 53.7a4.3 4.3 0 0 0-3-1.3H9.3a2.2 2.2 0 0 0-1.5 3.7l17.7 17.8a4.3 4.3 0 0 0 3 1.3h93.2a2.2 2.2 0 0 0 1.5-3.7L105.5 53.7z" fill="url(#sol-grad)" />
26
- </svg>
17
+ <div className={`relative flex items-center justify-center ${className}`}>
18
+ <svg width={size} height={size} viewBox="0 0 128 128" className="relative z-10">
19
+ <defs>
20
+ <linearGradient id="sol-grad" x1="0%" y1="0%" x2="100%" y2="100%">
21
+ <stop offset="0%" stopColor="#00FFA3" />
22
+ <stop offset="100%" stopColor="#DC1FFF" />
23
+ </linearGradient>
24
+ </defs>
25
+ <path d="M25.5 100.5a4.3 4.3 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7l-17.7 17.8a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7l17.7-17.8z" fill="url(#sol-grad)" />
26
+ <path d="M25.5 7.3a4.4 4.4 0 0 1 3-1.3h93.2a2.2 2.2 0 0 1 1.5 3.7L105.5 27.5a4.3 4.3 0 0 1-3 1.3H9.3a2.2 2.2 0 0 1-1.5-3.7L25.5 7.3z" fill="url(#sol-grad)" />
27
+ <path d="M105.5 53.7a4.3 4.3 0 0 0-3-1.3H9.3a2.2 2.2 0 0 0-1.5 3.7l17.7 17.8a4.3 4.3 0 0 0 3 1.3h93.2a2.2 2.2 0 0 0 1.5-3.7L105.5 53.7z" fill="url(#sol-grad)" />
28
+ </svg>
29
+ {shimmer && (
30
+ <div className="absolute inset-0 bg-accent-bright/20 blur-md rounded-full animate-pulse" />
31
+ )}
32
+ </div>
27
33
  )
28
34
  }
29
35
 
@@ -139,9 +145,10 @@ export function WalletPanel() {
139
145
  }, [selectedWalletId, loadWallets])
140
146
 
141
147
  const [copied, setCopied] = useState(false)
142
- const copyAddress = useCallback(() => {
148
+ const copyAddress = useCallback(async () => {
143
149
  if (!selectedWallet) return
144
- navigator.clipboard.writeText(selectedWallet.publicKey)
150
+ const copiedValue = await copyTextToClipboard(selectedWallet.publicKey)
151
+ if (!copiedValue) return
145
152
  setCopied(true)
146
153
  setTimeout(() => setCopied(false), 2000)
147
154
  }, [selectedWallet])
@@ -179,7 +186,7 @@ export function WalletPanel() {
179
186
  if (walletList.length === 0) {
180
187
  return (
181
188
  <div className="flex-1 flex items-center justify-center">
182
- <div className="text-center max-w-sm">
189
+ <div className="text-center max-w-sm" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
183
190
  <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" className="mx-auto mb-4 text-text-3/30">
184
191
  <rect x="2" y="6" width="20" height="14" rx="2" /><path d="M22 10H18a2 2 0 0 0 0 4h4" /><path d="M6 6V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v2" />
185
192
  </svg>
@@ -233,7 +240,8 @@ export function WalletPanel() {
233
240
  </div>
234
241
  <div className="flex-1 overflow-y-auto px-2 pb-4">
235
242
  {showCreateForm && (
236
- <div className="mx-1 mb-2 p-2.5 rounded-[8px] border border-accent/20 bg-accent-soft/10 space-y-2">
243
+ <div className="mx-1 mb-2 p-2.5 rounded-[8px] border border-accent/20 bg-accent-soft/10 space-y-2"
244
+ style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}>
237
245
  <AgentPickerList
238
246
  agents={agentsWithoutWallets}
239
247
  selected={createAgentId}
@@ -262,15 +270,19 @@ export function WalletPanel() {
262
270
  {createError && <p className="text-[10px] text-red-400">{createError}</p>}
263
271
  </div>
264
272
  )}
265
- {walletList.map((w) => {
273
+ {walletList.map((w, idx) => {
266
274
  const a = agents[w.agentId] as Agent | undefined
267
275
  return (
268
276
  <button
269
277
  key={w.id}
270
278
  onClick={() => { setSelectedWalletId(w.id); setWalletPanelAgentId(w.agentId) }}
271
- className={`w-full text-left px-3 py-2.5 rounded-[8px] mb-1 transition-colors cursor-pointer flex items-center gap-2.5 ${
279
+ className={`w-full text-left px-3 py-2.5 rounded-[8px] mb-1 transition-all cursor-pointer flex items-center gap-2.5 hover:scale-[1.02] ${
272
280
  selectedWalletId === w.id ? 'bg-accent-soft/30 text-text-1' : 'text-text-3 hover:bg-white/[0.04]'
273
281
  }`}
282
+ style={{
283
+ animation: 'fade-up 0.4s var(--ease-spring) both',
284
+ animationDelay: `${idx * 0.03}s`
285
+ }}
274
286
  >
275
287
  <AgentAvatar seed={a?.avatarSeed || null} avatarUrl={a?.avatarUrl} name={a?.name || '?'} size={28} />
276
288
  <div className="flex-1 min-w-0">
@@ -291,9 +303,10 @@ export function WalletPanel() {
291
303
 
292
304
  {/* Main detail area */}
293
305
  {selectedWallet ? (
294
- <div className="flex-1 overflow-y-auto p-6 space-y-6">
306
+ <div className="flex-1 overflow-y-auto p-6 space-y-6" key={selectedWallet.id}>
295
307
  {/* Warning banner */}
296
- <div className="flex items-start gap-3 p-3 rounded-[10px] bg-amber-500/10 border border-amber-500/20">
308
+ <div className="flex items-start gap-3 p-3 rounded-[10px] bg-amber-500/10 border border-amber-500/20"
309
+ style={{ animation: 'fade-up 0.4s var(--ease-spring)' }}>
297
310
  <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="text-amber-400 shrink-0 mt-0.5">
298
311
  <path d="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 0z" />
299
312
  <line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
@@ -304,7 +317,7 @@ export function WalletPanel() {
304
317
  </div>
305
318
 
306
319
  {/* Agent & Address */}
307
- <div>
320
+ <div style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.05s both' }}>
308
321
  <div className="flex items-center gap-2 mb-2">
309
322
  {(() => {
310
323
  const a = agents[selectedWallet.agentId] as Agent | undefined
@@ -337,7 +350,7 @@ export function WalletPanel() {
337
350
  </button>
338
351
  </div>
339
352
  {reassigning && (
340
- <div className="mb-2 space-y-2">
353
+ <div className="mb-2 space-y-2" style={{ animation: 'spring-in 0.4s var(--ease-spring)' }}>
341
354
  <p className="text-[11px] text-text-3/60">Select a new agent to control this wallet:</p>
342
355
  <AgentPickerList
343
356
  agents={agentsWithoutWallets}
@@ -350,7 +363,7 @@ export function WalletPanel() {
350
363
  setReassigning(false)
351
364
  loadWallets()
352
365
  } catch (err: unknown) {
353
- setReassignError(err instanceof Error ? err.message : String(err))
366
+ setReassignError(err instanceof Error ? err.message : String(err) || 'Reassign failed')
354
367
  }
355
368
  setReassignSaving(false)
356
369
  }}
@@ -376,15 +389,22 @@ export function WalletPanel() {
376
389
  </div>
377
390
 
378
391
  {/* Balance card */}
379
- <div className="p-5 rounded-[14px] border border-white/[0.06] bg-surface-2/50">
392
+ <div className="p-5 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
393
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.1s both' }}>
380
394
  <div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-2">Balance</div>
381
- <div className="text-[28px] font-600 text-text-1 tracking-tight">
382
- {(selectedWallet.balanceSol ?? 0).toFixed(4)} <span className="text-[14px] text-text-3/60">SOL</span>
395
+ <div className="flex items-baseline gap-3">
396
+ <div className="text-[28px] font-600 text-text-1 tracking-tight">
397
+ {(selectedWallet.balanceSol ?? 0).toFixed(4)} <span className="text-[14px] text-text-3/60 font-mono">SOL</span>
398
+ </div>
399
+ {selectedWallet.chain === 'solana' && (
400
+ <SolanaIcon size={16} shimmer className="opacity-80" />
401
+ )}
383
402
  </div>
384
403
  </div>
385
404
 
386
405
  {/* Funding help */}
387
- <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50">
406
+ <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
407
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.15s both' }}>
388
408
  <div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-2">How to Fund This Wallet</div>
389
409
  <div className="space-y-2 text-[12px] text-text-3/70 leading-relaxed">
390
410
  <p>Send SOL to the wallet address above from any Solana wallet (Phantom, Solflare, an exchange, etc.). Copy the address and use it as the recipient.</p>
@@ -395,7 +415,8 @@ export function WalletPanel() {
395
415
 
396
416
  {/* Balance history chart (simple) */}
397
417
  {balanceHistory.length > 1 && (
398
- <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50">
418
+ <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
419
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.2s both' }}>
399
420
  <div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-3">Balance Over Time</div>
400
421
  <div className="h-[120px] flex items-end gap-[2px]">
401
422
  {(() => {
@@ -403,8 +424,8 @@ export function WalletPanel() {
403
424
  return balanceHistory.slice(-60).map((s, i) => (
404
425
  <div
405
426
  key={s.id || i}
406
- className="flex-1 bg-accent/40 rounded-t-[2px] min-w-[3px]"
407
- style={{ height: `${Math.max(2, (s.balanceLamports / max) * 100)}%` }}
427
+ className="flex-1 bg-accent/40 rounded-t-[2px] min-w-[3px] transition-all hover:bg-accent hover:scale-y-110"
428
+ style={{ height: `${Math.max(2, (s.balanceLamports / max) * 100)}%`, transitionDelay: `${i * 10}ms` }}
408
429
  title={`${(s.balanceLamports / 1e9).toFixed(4)} SOL — ${new Date(s.timestamp).toLocaleString()}`}
409
430
  />
410
431
  ))
@@ -414,7 +435,8 @@ export function WalletPanel() {
414
435
  )}
415
436
 
416
437
  {/* Spending config */}
417
- <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50">
438
+ <div className="p-4 rounded-[14px] border border-white/[0.06] bg-surface-2/50"
439
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.25s both' }}>
418
440
  <div className="flex items-center justify-between mb-3">
419
441
  <div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600">Spending Limits</div>
420
442
  {!editingLimits && (
@@ -430,7 +452,7 @@ export function WalletPanel() {
430
452
  </div>
431
453
 
432
454
  {editingLimits ? (
433
- <div className="space-y-3">
455
+ <div className="space-y-3" style={{ animation: 'fade-in 0.3s ease' }}>
434
456
  <div>
435
457
  <label className="block text-[11px] text-text-3/70 mb-1">Per-transaction limit (SOL)</label>
436
458
  <input
@@ -502,14 +524,15 @@ export function WalletPanel() {
502
524
  </div>
503
525
 
504
526
  {/* Transaction history */}
505
- <div>
527
+ <div style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.3s both' }}>
506
528
  <div className="text-[11px] text-text-3/60 uppercase tracking-wide font-600 mb-3">Transactions</div>
507
529
  {transactions.length === 0 ? (
508
530
  <p className="text-[12px] text-text-3/50">No transactions yet.</p>
509
531
  ) : (
510
532
  <div className="space-y-2">
511
- {transactions.map((tx) => (
512
- <div key={tx.id} className="flex items-center gap-3 p-3 rounded-[10px] border border-white/[0.06] bg-surface-2/30">
533
+ {transactions.map((tx, idx) => (
534
+ <div key={tx.id} className="flex items-center gap-3 p-3 rounded-[10px] border border-white/[0.06] bg-surface-2/30 transition-all hover:bg-surface-2/50"
535
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) both', animationDelay: `${0.35 + idx * 0.03}s` }}>
513
536
  <div className={`w-6 h-6 rounded-full flex items-center justify-center text-[12px] ${
514
537
  tx.type === 'send' ? 'bg-red-500/15 text-red-400' :
515
538
  tx.type === 'receive' ? 'bg-green-500/15 text-green-400' :
@@ -524,7 +547,7 @@ export function WalletPanel() {
524
547
  </span>
525
548
  <span className={`px-1.5 py-0.5 rounded-[4px] text-[9px] font-600 uppercase ${
526
549
  tx.status === 'confirmed' ? 'bg-green-500/15 text-green-400' :
527
- tx.status === 'pending_approval' ? 'bg-amber-500/15 text-amber-400' :
550
+ tx.status === 'pending_approval' ? 'bg-amber-500/15 text-amber-400 animate-pulse' :
528
551
  tx.status === 'failed' ? 'bg-red-500/15 text-red-400' :
529
552
  tx.status === 'denied' ? 'bg-red-500/15 text-red-400' :
530
553
  'bg-blue-500/15 text-blue-400'
@@ -544,8 +567,8 @@ export function WalletPanel() {
544
567
  <button
545
568
  type="button"
546
569
  onClick={() => setPendingApproval(tx)}
547
- className="shrink-0 px-2 py-1 rounded-[6px] bg-amber-500/15 text-amber-400 text-[10px] font-600 hover:bg-amber-500/25 cursor-pointer transition-colors"
548
- style={{ fontFamily: 'inherit' }}
570
+ className="shrink-0 px-2 py-1 rounded-[6px] bg-amber-500/15 text-amber-400 text-[10px] font-600 hover:bg-amber-500/25 cursor-pointer transition-all hover:scale-[1.05]"
571
+ style={{ fontFamily: 'inherit', animation: 'spring-in 0.4s var(--ease-spring)' }}
549
572
  >
550
573
  Review
551
574
  </button>
@@ -557,10 +580,11 @@ export function WalletPanel() {
557
580
  </div>
558
581
 
559
582
  {/* Danger zone */}
560
- <div className="p-4 rounded-[14px] border border-red-500/15 bg-red-500/5">
583
+ <div className="p-4 rounded-[14px] border border-red-500/15 bg-red-500/5"
584
+ style={{ animation: 'fade-up 0.4s var(--ease-spring) 0.4s both' }}>
561
585
  <div className="text-[11px] text-red-400/80 uppercase tracking-wide font-600 mb-2">Danger Zone</div>
562
586
  {confirmDelete ? (
563
- <div className="space-y-2">
587
+ <div className="space-y-2" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
564
588
  <p className="text-[11px] text-text-3/70">
565
589
  This will permanently delete the wallet and its private key. Any remaining balance will be inaccessible. This cannot be undone.
566
590
  </p>
@@ -598,7 +622,7 @@ export function WalletPanel() {
598
622
  </div>
599
623
  ) : (
600
624
  <div className="flex-1 flex items-center justify-center">
601
- <p className="text-[12px] text-text-3/50">Select a wallet to view details</p>
625
+ <p className="text-[12px] text-text-3/50" style={{ animation: 'fade-in 1s ease' }}>Select a wallet to view details</p>
602
626
  </div>
603
627
  )}
604
628
 
@@ -2,7 +2,9 @@
2
2
 
3
3
  import { useState, useCallback } from 'react'
4
4
  import { api } from '@/lib/api-client'
5
+ import { copyTextToClipboard } from '@/lib/clipboard'
5
6
  import type { AgentWallet, WalletChain } from '@/types'
7
+ import { toast } from 'sonner'
6
8
 
7
9
  interface WalletSectionProps {
8
10
  agentId: string
@@ -20,17 +22,21 @@ export function WalletSection({ agentId, wallet, onWalletCreated }: WalletSectio
20
22
  setError(null)
21
23
  try {
22
24
  await api('POST', '/wallets', { agentId, chain: 'solana' as WalletChain })
25
+ toast.success('Agent wallet created successfully')
23
26
  onWalletCreated()
24
27
  } catch (err: unknown) {
25
- setError(err instanceof Error ? err.message : String(err))
28
+ const msg = err instanceof Error ? err.message : String(err)
29
+ setError(msg)
30
+ toast.error(msg)
26
31
  } finally {
27
32
  setCreating(false)
28
33
  }
29
34
  }, [agentId, onWalletCreated])
30
35
 
31
- const copyAddress = useCallback(() => {
36
+ const copyAddress = useCallback(async () => {
32
37
  if (!wallet) return
33
- navigator.clipboard.writeText(wallet.publicKey)
38
+ const copiedValue = await copyTextToClipboard(wallet.publicKey)
39
+ if (!copiedValue) return
34
40
  setCopied(true)
35
41
  setTimeout(() => setCopied(false), 2000)
36
42
  }, [wallet])
@@ -2,6 +2,7 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
+ import { copyTextToClipboard } from '@/lib/clipboard'
5
6
 
6
7
  function webhookUrl(id: string): string {
7
8
  if (typeof window === 'undefined') return `/api/webhooks/${id}`
@@ -36,7 +37,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
36
37
 
37
38
  const copyText = async (key: string, value: string) => {
38
39
  try {
39
- await navigator.clipboard.writeText(value)
40
+ const copiedValue = await copyTextToClipboard(value)
41
+ if (!copiedValue) return
40
42
  setCopied(key)
41
43
  setTimeout(() => setCopied((prev) => (prev === key ? null : prev)), 1400)
42
44
  } catch {
@@ -46,8 +48,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
46
48
 
47
49
  if (!list.length) {
48
50
  return (
49
- <div className="flex-1 flex flex-col items-center justify-center px-6 py-12 text-center">
50
- <div className="w-12 h-12 rounded-[14px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center mb-4">
51
+ <div className="flex-1 flex flex-col items-center justify-center gap-4 text-text-3 p-8 text-center" style={{ animation: 'fade-up 0.5s var(--ease-spring)' }}>
52
+ <div className="w-12 h-12 rounded-[14px] bg-white/[0.03] border border-white/[0.06] flex items-center justify-center mb-1">
51
53
  <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" className="text-text-3">
52
54
  <path d="M22 12h-4l-3 7L9 5l-3 7H2" />
53
55
  </svg>
@@ -59,7 +61,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
59
61
  setEditingWebhookId(null)
60
62
  setWebhookSheetOpen(true)
61
63
  }}
62
- className="mt-3 text-[13px] text-accent-bright hover:underline cursor-pointer bg-transparent border-none"
64
+ className="mt-3 px-4 py-2 rounded-[10px] bg-transparent text-accent-bright text-[13px] font-600 cursor-pointer border border-accent-bright/20 hover:bg-accent-soft transition-all"
65
+ style={{ fontFamily: 'inherit' }}
63
66
  >
64
67
  + Add Webhook
65
68
  </button>
@@ -69,7 +72,7 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
69
72
 
70
73
  return (
71
74
  <div className={`flex-1 overflow-y-auto ${inSidebar ? 'pb-10' : 'pb-20'}`}>
72
- {list.map((hook) => {
75
+ {list.map((hook, idx) => {
73
76
  const agentName = hook.agentId ? agents[hook.agentId]?.name : null
74
77
  const endpoint = webhookUrl(hook.id)
75
78
  const copiedEndpoint = copied === `endpoint:${hook.id}`
@@ -80,6 +83,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
80
83
  <div
81
84
  key={hook.id}
82
85
  className="w-full flex items-center gap-2.5 px-5 py-3 hover:bg-white/[0.02] transition-colors group"
86
+ style={{
87
+ animation: 'fade-up 0.4s var(--ease-spring) both',
88
+ animationDelay: `${idx * 0.02}s`
89
+ }}
83
90
  >
84
91
  <button
85
92
  onClick={() => {
@@ -88,11 +95,12 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
88
95
  }}
89
96
  className="flex items-center gap-3 flex-1 min-w-0 cursor-pointer bg-transparent border-none text-left p-0"
90
97
  >
91
- <div className={`shrink-0 w-9 h-9 rounded-[10px] border flex items-center justify-center ${
98
+ <div className={`shrink-0 w-9 h-9 rounded-[10px] border flex items-center justify-center transition-all ${
92
99
  hook.isEnabled
93
100
  ? 'bg-emerald-500/12 border-emerald-500/20 text-emerald-300'
94
101
  : 'bg-white/[0.03] border-white/[0.08] text-text-3'
95
- }`}>
102
+ }`}
103
+ style={hook.isEnabled ? { animation: 'spring-in 0.4s var(--ease-spring)' } : undefined}>
96
104
  <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
97
105
  <path d="M22 12h-4l-3 7L9 5l-3 7H2" />
98
106
  </svg>
@@ -101,7 +109,8 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
101
109
  <div className="flex-1 min-w-0">
102
110
  <div className="flex items-center gap-2">
103
111
  <span className="text-[13px] font-600 text-text truncate">{hook.name || 'Unnamed Webhook'}</span>
104
- <span className={`shrink-0 w-2 h-2 rounded-full ${hook.isEnabled ? 'bg-emerald-400' : 'bg-white/20'}`} />
112
+ <span className={`shrink-0 w-2 h-2 rounded-full ${hook.isEnabled ? 'bg-emerald-400' : 'bg-white/20'}`}
113
+ style={hook.isEnabled ? { animation: 'pulse-subtle 2s infinite' } : undefined} />
105
114
  </div>
106
115
  <div className="text-[11px] text-text-3 truncate">
107
116
  {hook.source || 'custom'} · {formatEvents(hook.events)}{agentName ? ` · ${agentName}` : ''}
@@ -119,10 +128,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
119
128
  copiedEndpoint
120
129
  ? 'opacity-100 bg-emerald-500/15 text-emerald-300'
121
130
  : 'opacity-0 group-hover:opacity-100 focus:opacity-100 bg-accent-soft/40 text-accent-bright hover:bg-accent-soft'
122
- }`}
131
+ } hover:scale-[1.1] active:scale-[0.9]`}
123
132
  >
124
133
  {copiedEndpoint ? (
125
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
134
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
126
135
  <polyline points="20 6 9 17 4 12" />
127
136
  </svg>
128
137
  ) : (
@@ -144,10 +153,10 @@ export function WebhookList({ inSidebar }: { inSidebar?: boolean }) {
144
153
  copiedSecret
145
154
  ? 'opacity-100 bg-emerald-500/15 text-emerald-300'
146
155
  : 'opacity-0 group-hover:opacity-100 focus:opacity-100 bg-white/[0.04] text-text-2 hover:bg-white/[0.08]'
147
- }`}
156
+ } hover:scale-[1.1] active:scale-[0.9]`}
148
157
  >
149
158
  {copiedSecret ? (
150
- <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
159
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round" style={{ animation: 'spring-in 0.3s var(--ease-spring)' }}>
151
160
  <polyline points="20 6 9 17 4 12" />
152
161
  </svg>
153
162
  ) : (
@@ -4,7 +4,9 @@ import { useEffect, useMemo, useState } from 'react'
4
4
  import { useAppStore } from '@/stores/use-app-store'
5
5
  import { BottomSheet } from '@/components/shared/bottom-sheet'
6
6
  import { api } from '@/lib/api-client'
7
+ import { copyTextToClipboard } from '@/lib/clipboard'
7
8
  import type { Webhook, WebhookLogEntry } from '@/types'
9
+ import { toast } from 'sonner'
8
10
 
9
11
  type WebhookApiResponse = Webhook | { error: string }
10
12
  type DeleteWebhookResponse = { ok: boolean } | { error: string }
@@ -114,7 +116,8 @@ export function WebhookSheet() {
114
116
  const copyText = async (type: 'endpoint' | 'secret', value: string) => {
115
117
  if (!value) return
116
118
  try {
117
- await navigator.clipboard.writeText(value)
119
+ const copied = await copyTextToClipboard(value)
120
+ if (!copied) return
118
121
  setCopied(type)
119
122
  setTimeout(() => setCopied((prev) => (prev === type ? null : prev)), 1500)
120
123
  } catch {
@@ -143,14 +146,18 @@ export function WebhookSheet() {
143
146
  if (editing) {
144
147
  const updated = await api<WebhookApiResponse>('PUT', `/webhooks/${editing.id}`, payload)
145
148
  if ('error' in updated && updated.error) throw new Error(updated.error)
149
+ toast.success('Webhook updated successfully')
146
150
  } else {
147
151
  const created = await api<WebhookApiResponse>('POST', '/webhooks', payload)
148
152
  if ('error' in created && created.error) throw new Error(created.error)
153
+ toast.success('Webhook created successfully')
149
154
  }
150
155
  await loadWebhooks()
151
156
  handleClose()
152
157
  } catch (err: unknown) {
153
- setError(err instanceof Error ? err.message : 'Failed to save webhook')
158
+ const msg = err instanceof Error ? err.message : 'Failed to save webhook'
159
+ setError(msg)
160
+ toast.error(msg)
154
161
  } finally {
155
162
  setSaving(false)
156
163
  }
@@ -161,10 +168,13 @@ export function WebhookSheet() {
161
168
  try {
162
169
  const res = await api<DeleteWebhookResponse>('DELETE', `/webhooks/${editing.id}`)
163
170
  if ('error' in res && res.error) throw new Error(res.error)
171
+ toast.success('Webhook deleted')
164
172
  await loadWebhooks()
165
173
  handleClose()
166
174
  } catch (err: unknown) {
167
- setError(err instanceof Error ? err.message : 'Failed to delete webhook')
175
+ const msg = err instanceof Error ? err.message : 'Failed to delete webhook'
176
+ setError(msg)
177
+ toast.error(msg)
168
178
  }
169
179
  }
170
180
 
@@ -0,0 +1,45 @@
1
+ import { describe, it } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { getApprovalTitle, getApprovalPayload, getApprovalPluginId } from './approval-display'
4
+ import type { ApprovalRequest } from '@/types'
5
+
6
+ function req(overrides: Partial<ApprovalRequest>): ApprovalRequest {
7
+ return {
8
+ id: 'a1',
9
+ category: 'tool_access',
10
+ title: 'Enable Plugin: undefined',
11
+ data: {},
12
+ createdAt: 1,
13
+ updatedAt: 1,
14
+ status: 'pending',
15
+ ...overrides,
16
+ }
17
+ }
18
+
19
+ describe('approval display helpers', () => {
20
+ it('normalizes plugin title from toolId/pluginId', () => {
21
+ const approval = req({ data: { toolId: 'wikipedia' } })
22
+ assert.equal(getApprovalPluginId(approval), 'wikipedia')
23
+ assert.equal(getApprovalTitle(approval), 'Enable Plugin: wikipedia')
24
+ })
25
+
26
+ it('falls back with warning when plugin id is missing', () => {
27
+ const approval = req({ data: {} })
28
+ const payload = getApprovalPayload(approval)
29
+ assert.equal(getApprovalTitle(approval), 'Enable Plugin')
30
+ assert.equal(payload.warning, 'Missing plugin/tool identifier')
31
+ })
32
+
33
+ it('summarizes scaffold payload without dumping full code', () => {
34
+ const approval = req({
35
+ category: 'plugin_scaffold',
36
+ title: 'Scaffold Plugin',
37
+ data: { filename: 'wiki.js', code: 'x'.repeat(400) },
38
+ })
39
+ const payload = getApprovalPayload(approval)
40
+ assert.equal(payload.filename, 'wiki.js')
41
+ assert.equal(payload.codeLength, 400)
42
+ assert.equal(typeof payload.codePreview, 'string')
43
+ assert.ok(String(payload.codePreview).includes('(400 chars)'))
44
+ })
45
+ })
@@ -0,0 +1,62 @@
1
+ import type { ApprovalRequest } from '@/types'
2
+
3
+ function truncate(value: string, max = 320): string {
4
+ if (value.length <= max) return value
5
+ return `${value.slice(0, max)}... (${value.length} chars)`
6
+ }
7
+
8
+ function sanitizeValue(value: unknown, depth = 0): unknown {
9
+ if (depth > 4) return '[truncated]'
10
+ if (typeof value === 'string') return truncate(value)
11
+ if (Array.isArray(value)) {
12
+ return value.slice(0, 20).map((entry) => sanitizeValue(entry, depth + 1))
13
+ }
14
+ if (!value || typeof value !== 'object') return value
15
+ const out: Record<string, unknown> = {}
16
+ for (const [k, v] of Object.entries(value as Record<string, unknown>).slice(0, 30)) {
17
+ out[k] = sanitizeValue(v, depth + 1)
18
+ }
19
+ return out
20
+ }
21
+
22
+ function dataObject(approval: ApprovalRequest): Record<string, unknown> {
23
+ return approval.data && typeof approval.data === 'object' ? approval.data : {}
24
+ }
25
+
26
+ export function getApprovalPluginId(approval: ApprovalRequest): string | null {
27
+ const data = dataObject(approval)
28
+ const toolId = typeof data.toolId === 'string' ? data.toolId.trim() : ''
29
+ if (toolId) return toolId
30
+ const pluginId = typeof data.pluginId === 'string' ? data.pluginId.trim() : ''
31
+ return pluginId || null
32
+ }
33
+
34
+ export function getApprovalTitle(approval: ApprovalRequest): string {
35
+ if (approval.category === 'tool_access') {
36
+ const pluginId = getApprovalPluginId(approval)
37
+ return pluginId ? `Enable Plugin: ${pluginId}` : 'Enable Plugin'
38
+ }
39
+ return approval.title || 'Approval Request'
40
+ }
41
+
42
+ export function getApprovalPayload(approval: ApprovalRequest): Record<string, unknown> {
43
+ const data = dataObject(approval)
44
+
45
+ if (approval.category === 'tool_access') {
46
+ const pluginId = getApprovalPluginId(approval)
47
+ if (pluginId) return { pluginId }
48
+ return { warning: 'Missing plugin/tool identifier', raw: sanitizeValue(data) }
49
+ }
50
+
51
+ if (approval.category === 'plugin_scaffold') {
52
+ const filename = typeof data.filename === 'string' ? data.filename : null
53
+ const code = typeof data.code === 'string' ? data.code : ''
54
+ return {
55
+ filename,
56
+ codeLength: code.length,
57
+ codePreview: code ? truncate(code, 260) : '',
58
+ }
59
+ }
60
+
61
+ return (sanitizeValue(data) || {}) as Record<string, unknown>
62
+ }
@@ -0,0 +1,38 @@
1
+ export async function copyTextToClipboard(text: string): Promise<boolean> {
2
+ if (!text) return false
3
+
4
+ if (
5
+ typeof navigator !== 'undefined'
6
+ && navigator.clipboard
7
+ && typeof navigator.clipboard.writeText === 'function'
8
+ ) {
9
+ try {
10
+ await navigator.clipboard.writeText(text)
11
+ return true
12
+ } catch {
13
+ // fall through to legacy fallback
14
+ }
15
+ }
16
+
17
+ if (typeof document === 'undefined') return false
18
+
19
+ let textarea: HTMLTextAreaElement | null = null
20
+ try {
21
+ textarea = document.createElement('textarea')
22
+ textarea.value = text
23
+ textarea.setAttribute('readonly', 'true')
24
+ textarea.style.position = 'fixed'
25
+ textarea.style.top = '-9999px'
26
+ textarea.style.left = '-9999px'
27
+ textarea.style.opacity = '0'
28
+ document.body.appendChild(textarea)
29
+ textarea.focus()
30
+ textarea.select()
31
+ textarea.setSelectionRange(0, textarea.value.length)
32
+ return document.execCommand('copy')
33
+ } catch {
34
+ return false
35
+ } finally {
36
+ if (textarea?.parentNode) textarea.parentNode.removeChild(textarea)
37
+ }
38
+ }
package/src/lib/memory.ts CHANGED
@@ -4,6 +4,10 @@ import type { MemoryEntry } from '../types'
4
4
  interface MemoryQueryOptions {
5
5
  q?: string
6
6
  agentId?: string
7
+ scope?: 'auto' | 'all' | 'global' | 'shared' | 'agent' | 'session' | 'project'
8
+ scopeSessionId?: string
9
+ projectRoot?: string
10
+ rerank?: 'balanced' | 'semantic' | 'lexical'
7
11
  depth?: number
8
12
  limit?: number
9
13
  linkedLimit?: number
@@ -14,6 +18,10 @@ export const searchMemory = (opts: MemoryQueryOptions = {}) => {
14
18
  const params = new URLSearchParams()
15
19
  if (opts.q) params.set('q', opts.q)
16
20
  if (opts.agentId) params.set('agentId', opts.agentId)
21
+ if (opts.scope) params.set('scope', opts.scope)
22
+ if (opts.scopeSessionId) params.set('scopeSessionId', opts.scopeSessionId)
23
+ if (opts.projectRoot) params.set('projectRoot', opts.projectRoot)
24
+ if (opts.rerank) params.set('rerank', opts.rerank)
17
25
  if (typeof opts.depth === 'number') params.set('depth', String(opts.depth))
18
26
  if (typeof opts.limit === 'number') params.set('limit', String(opts.limit))
19
27
  if (typeof opts.linkedLimit === 'number') params.set('linkedLimit', String(opts.linkedLimit))