@swarmclawai/swarmclaw 0.6.4 → 0.6.6

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 (92) hide show
  1. package/README.md +5 -3
  2. package/package.json +5 -1
  3. package/src/app/api/chatrooms/[id]/chat/route.ts +41 -2
  4. package/src/app/api/chatrooms/[id]/route.ts +15 -1
  5. package/src/app/api/chatrooms/route.ts +15 -2
  6. package/src/app/api/schedules/[id]/run/route.ts +3 -0
  7. package/src/app/api/tasks/route.ts +24 -0
  8. package/src/app/api/wallets/[id]/approve/route.ts +62 -0
  9. package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
  10. package/src/app/api/wallets/[id]/route.ts +118 -0
  11. package/src/app/api/wallets/[id]/send/route.ts +118 -0
  12. package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
  13. package/src/app/api/wallets/route.ts +74 -0
  14. package/src/app/globals.css +8 -0
  15. package/src/cli/index.js +15 -0
  16. package/src/cli/spec.js +14 -0
  17. package/src/components/agents/agent-avatar.tsx +15 -1
  18. package/src/components/agents/agent-card.tsx +1 -0
  19. package/src/components/agents/agent-chat-list.tsx +1 -1
  20. package/src/components/agents/agent-sheet.tsx +112 -26
  21. package/src/components/chat/chat-area.tsx +2 -2
  22. package/src/components/chat/chat-header.tsx +48 -19
  23. package/src/components/chat/chat-tool-toggles.tsx +1 -1
  24. package/src/components/chat/delegation-banner.test.ts +27 -0
  25. package/src/components/chat/delegation-banner.tsx +109 -23
  26. package/src/components/chat/message-bubble.tsx +3 -2
  27. package/src/components/chat/message-list.tsx +5 -4
  28. package/src/components/chat/streaming-bubble.tsx +3 -2
  29. package/src/components/chat/thinking-indicator.tsx +3 -2
  30. package/src/components/chat/transfer-agent-picker.tsx +1 -1
  31. package/src/components/chatrooms/agent-hover-card.tsx +1 -1
  32. package/src/components/chatrooms/chatroom-input.tsx +1 -1
  33. package/src/components/chatrooms/chatroom-message.tsx +1 -1
  34. package/src/components/chatrooms/chatroom-sheet.tsx +1 -1
  35. package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
  36. package/src/components/chatrooms/chatroom-view.tsx +1 -1
  37. package/src/components/connectors/connector-list.tsx +1 -1
  38. package/src/components/home/home-view.tsx +2 -1
  39. package/src/components/knowledge/knowledge-list.tsx +1 -1
  40. package/src/components/knowledge/knowledge-sheet.tsx +1 -1
  41. package/src/components/layout/app-layout.tsx +18 -3
  42. package/src/components/memory/memory-agent-list.tsx +1 -1
  43. package/src/components/memory/memory-browser.tsx +1 -0
  44. package/src/components/memory/memory-card.tsx +3 -2
  45. package/src/components/memory/memory-detail.tsx +3 -3
  46. package/src/components/memory/memory-sheet.tsx +2 -2
  47. package/src/components/projects/project-detail.tsx +4 -4
  48. package/src/components/secrets/secret-sheet.tsx +1 -1
  49. package/src/components/secrets/secrets-list.tsx +1 -1
  50. package/src/components/sessions/session-card.tsx +1 -1
  51. package/src/components/shared/agent-picker-list.tsx +1 -1
  52. package/src/components/shared/agent-switch-dialog.tsx +1 -1
  53. package/src/components/shared/settings/section-user-preferences.tsx +4 -4
  54. package/src/components/skills/skill-list.tsx +1 -1
  55. package/src/components/skills/skill-sheet.tsx +1 -1
  56. package/src/components/tasks/task-board.tsx +3 -3
  57. package/src/components/tasks/task-sheet.tsx +21 -1
  58. package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
  59. package/src/components/wallets/wallet-panel.tsx +616 -0
  60. package/src/components/wallets/wallet-section.tsx +100 -0
  61. package/src/lib/server/agent-registry.ts +2 -2
  62. package/src/lib/server/chat-execution.ts +35 -3
  63. package/src/lib/server/chatroom-health.ts +60 -0
  64. package/src/lib/server/chatroom-helpers.test.ts +94 -0
  65. package/src/lib/server/chatroom-helpers.ts +64 -11
  66. package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
  67. package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
  68. package/src/lib/server/connectors/manager.ts +80 -2
  69. package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
  70. package/src/lib/server/connectors/whatsapp-text.ts +26 -0
  71. package/src/lib/server/connectors/whatsapp.ts +8 -5
  72. package/src/lib/server/orchestrator-lg.ts +12 -2
  73. package/src/lib/server/orchestrator.ts +6 -1
  74. package/src/lib/server/queue-followups.test.ts +224 -0
  75. package/src/lib/server/queue.ts +226 -24
  76. package/src/lib/server/scheduler.ts +3 -0
  77. package/src/lib/server/session-tools/chatroom.ts +11 -2
  78. package/src/lib/server/session-tools/context-mgmt.ts +2 -2
  79. package/src/lib/server/session-tools/index.ts +6 -2
  80. package/src/lib/server/session-tools/memory.ts +1 -1
  81. package/src/lib/server/session-tools/shell.ts +1 -1
  82. package/src/lib/server/session-tools/wallet.ts +124 -0
  83. package/src/lib/server/session-tools/web.ts +2 -2
  84. package/src/lib/server/solana.ts +122 -0
  85. package/src/lib/server/storage.ts +38 -0
  86. package/src/lib/server/stream-agent-chat.ts +126 -63
  87. package/src/lib/server/task-mention.test.ts +41 -0
  88. package/src/lib/server/task-mention.ts +3 -2
  89. package/src/lib/tool-definitions.ts +1 -0
  90. package/src/lib/view-routes.ts +1 -0
  91. package/src/stores/use-app-store.ts +8 -0
  92. package/src/types/index.ts +60 -1
@@ -68,7 +68,7 @@ function AssignAgentPicker({ projectId, onClose }: { projectId: string; onClose:
68
68
  className="w-full flex items-center gap-2.5 px-3 py-2 rounded-[8px] text-left hover:bg-white/[0.06] transition-colors cursor-pointer bg-transparent border-none"
69
69
  style={{ fontFamily: 'inherit' }}
70
70
  >
71
- <AgentAvatar seed={a.avatarSeed} name={a.name} size={22} />
71
+ <AgentAvatar seed={a.avatarSeed} avatarUrl={a.avatarUrl} name={a.name} size={22} />
72
72
  <div className="min-w-0 flex-1">
73
73
  <div className="text-[12px] text-text truncate">{a.name}</div>
74
74
  <div className="text-[10px] text-text-3/40 truncate">{a.model || a.provider}</div>
@@ -312,7 +312,7 @@ export function ProjectDetail() {
312
312
  className="flex items-center gap-3 flex-1 min-w-0 cursor-pointer bg-transparent border-none text-left p-0"
313
313
  style={{ fontFamily: 'inherit' }}
314
314
  >
315
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={28} />
315
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={28} />
316
316
  <div className="min-w-0 flex-1">
317
317
  <div className="text-[13px] font-600 text-text truncate">{agent.name}</div>
318
318
  <div className="text-[11px] text-text-3/50 truncate">{agent.model || agent.provider}</div>
@@ -381,7 +381,7 @@ export function ProjectDetail() {
381
381
  <span className="text-[13px] text-text truncate flex-1">{task.title}</span>
382
382
  {agent && (
383
383
  <span className="shrink-0 flex items-center gap-1.5 text-[11px] text-text-3/40">
384
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={16} />
384
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={16} />
385
385
  {agent.name}
386
386
  </span>
387
387
  )}
@@ -448,7 +448,7 @@ export function ProjectDetail() {
448
448
  </span>
449
449
  {agent && (
450
450
  <span className="shrink-0 flex items-center gap-1.5 text-[11px] text-text-3/40">
451
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={16} />
451
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={16} />
452
452
  </span>
453
453
  )}
454
454
  {schedule.nextRunAt && (
@@ -161,7 +161,7 @@ export function SecretSheet() {
161
161
  }`}
162
162
  style={{ fontFamily: 'inherit' }}
163
163
  >
164
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
164
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={24} />
165
165
  <span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
166
166
  {selected && (
167
167
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright shrink-0">
@@ -102,7 +102,7 @@ export function SecretsList({ inSidebar }: Props) {
102
102
  <div className="flex items-center gap-1.5 mt-1.5 pl-[22px]">
103
103
  <div className="flex items-center -space-x-1.5">
104
104
  {scopedAgents.slice(0, 5).map((agent) => (
105
- <AgentAvatar key={agent.id} seed={agent.avatarSeed} name={agent.name} size={16} className="ring-1 ring-surface" />
105
+ <AgentAvatar key={agent.id} seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={16} className="ring-1 ring-surface" />
106
106
  ))}
107
107
  </div>
108
108
  {scopedAgents.length > 5 && (
@@ -86,7 +86,7 @@ export function SessionCard({ session, active, onClick }: Props) {
86
86
  <div className="flex items-center gap-2.5">
87
87
  {agent && (
88
88
  <div className="relative shrink-0">
89
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
89
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={24} />
90
90
  {(heartbeatEnabled || session.active) && (
91
91
  <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-emerald-400 ring-2 ring-[#0f0f1a]" />
92
92
  )}
@@ -72,7 +72,7 @@ export function AgentPickerList({
72
72
  {active && (
73
73
  <div className="absolute left-0 top-2 bottom-2 w-[2.5px] rounded-full bg-accent-bright" />
74
74
  )}
75
- <AgentAvatar seed={a.avatarSeed || null} name={a.name} size={28} />
75
+ <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={28} />
76
76
  <span className={`text-[13px] font-600 flex-1 truncate ${active ? 'text-accent-bright' : 'text-text-2'}`}>
77
77
  {a.name}
78
78
  </span>
@@ -115,7 +115,7 @@ export function AgentSwitchDialog() {
115
115
  ${idx === selectedIdx ? 'bg-white/[0.06]' : 'hover:bg-white/[0.04]'}`}
116
116
  style={{ fontFamily: 'inherit' }}
117
117
  >
118
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={28} />
118
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={28} />
119
119
  <div className="flex-1 min-w-0">
120
120
  <div className="flex items-center gap-2">
121
121
  <span className="text-[13px] font-500 text-text truncate">{agent.name}</span>
@@ -35,11 +35,11 @@ export function UserPreferencesSection({ appSettings, patchSettings, inputClass
35
35
  </div>
36
36
  <button
37
37
  type="button"
38
- onClick={() => patchSettings({ suggestionsEnabled: appSettings.suggestionsEnabled === false })}
39
- className={`relative w-9 h-5 rounded-full transition-colors ${appSettings.suggestionsEnabled !== false ? 'bg-accent-bright' : 'bg-white/[0.10]'}`}
38
+ onClick={() => patchSettings({ suggestionsEnabled: !appSettings.suggestionsEnabled })}
39
+ className={`relative w-9 h-5 rounded-full transition-colors ${appSettings.suggestionsEnabled ? 'bg-accent-bright' : 'bg-white/[0.10]'}`}
40
40
  style={{ fontFamily: 'inherit' }}
41
41
  >
42
- <span className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${appSettings.suggestionsEnabled !== false ? 'translate-x-4' : ''}`} />
42
+ <span className={`absolute top-0.5 left-0.5 w-4 h-4 rounded-full bg-white transition-transform ${appSettings.suggestionsEnabled ? 'translate-x-4' : ''}`} />
43
43
  </button>
44
44
  </div>
45
45
 
@@ -70,7 +70,7 @@ export function UserPreferencesSection({ appSettings, patchSettings, inputClass
70
70
  : 'bg-transparent border-white/[0.06] text-text-3 hover:bg-white/[0.03]'}`}
71
71
  style={{ fontFamily: 'inherit' }}
72
72
  >
73
- <AgentAvatar seed={agent.avatarSeed || null} name={agent.name} size={18} />
73
+ <AgentAvatar seed={agent.avatarSeed || null} avatarUrl={agent.avatarUrl} name={agent.name} size={18} />
74
74
  {agent.name}
75
75
  </button>
76
76
  ))}
@@ -309,7 +309,7 @@ export function SkillList({ inSidebar }: { inSidebar?: boolean }) {
309
309
  <div className="flex items-center gap-1.5 mt-1.5">
310
310
  <div className="flex items-center -space-x-1.5">
311
311
  {scopedAgents.slice(0, 5).map((agent) => (
312
- <AgentAvatar key={agent.id} seed={agent.avatarSeed} name={agent.name} size={16} className="ring-1 ring-surface" />
312
+ <AgentAvatar key={agent.id} seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={16} className="ring-1 ring-surface" />
313
313
  ))}
314
314
  </div>
315
315
  {scopedAgents.length > 5 && (
@@ -255,7 +255,7 @@ export function SkillSheet() {
255
255
  }`}
256
256
  style={{ fontFamily: 'inherit' }}
257
257
  >
258
- <AgentAvatar seed={agent.avatarSeed} name={agent.name} size={24} />
258
+ <AgentAvatar seed={agent.avatarSeed} avatarUrl={agent.avatarUrl} name={agent.name} size={24} />
259
259
  <span className="text-[13px] text-text flex-1 truncate">{agent.name}</span>
260
260
  {selected && (
261
261
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" className="text-accent-bright shrink-0">
@@ -264,7 +264,7 @@ export function TaskBoard() {
264
264
  >
265
265
  {filterAgentId && agents[filterAgentId] ? (
266
266
  <>
267
- <AgentAvatar seed={agents[filterAgentId].avatarSeed || null} name={agents[filterAgentId].name} size={18} />
267
+ <AgentAvatar seed={agents[filterAgentId].avatarSeed || null} avatarUrl={agents[filterAgentId].avatarUrl} name={agents[filterAgentId].name} size={18} />
268
268
  {agents[filterAgentId].name}
269
269
  </>
270
270
  ) : 'All Agents'}
@@ -290,7 +290,7 @@ export function TaskBoard() {
290
290
  ${filterAgentId === a.id ? 'bg-white/[0.06] text-text' : 'bg-transparent text-text-3 hover:bg-white/[0.04]'}`}
291
291
  style={{ fontFamily: 'inherit' }}
292
292
  >
293
- <AgentAvatar seed={a.avatarSeed || null} name={a.name} size={20} />
293
+ <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={20} />
294
294
  {a.name}
295
295
  </button>
296
296
  ))}
@@ -480,7 +480,7 @@ export function TaskBoard() {
480
480
  className="w-full flex items-center gap-2 px-3 py-2 text-[12px] font-600 cursor-pointer border-none text-left bg-transparent text-text-3 hover:bg-white/[0.06] hover:text-text transition-colors"
481
481
  style={{ fontFamily: 'inherit' }}
482
482
  >
483
- <AgentAvatar seed={a.avatarSeed || null} name={a.name} size={16} />
483
+ <AgentAvatar seed={a.avatarSeed || null} avatarUrl={a.avatarUrl} name={a.name} size={16} />
484
484
  {a.name}
485
485
  </button>
486
486
  ))}
@@ -244,7 +244,7 @@ export function TaskSheet() {
244
244
  <div className="mb-8">
245
245
  <SectionLabel>Agent</SectionLabel>
246
246
  <div className="flex items-center gap-2.5 px-4 py-3 rounded-[14px] border border-white/[0.06] bg-surface">
247
- <AgentAvatar seed={taskAgent.avatarSeed || null} name={taskAgent.name} size={24} />
247
+ <AgentAvatar seed={taskAgent.avatarSeed || null} avatarUrl={taskAgent.avatarUrl} name={taskAgent.name} size={24} />
248
248
  <span className="text-[14px] font-600 text-text">{taskAgent.name}</span>
249
249
  </div>
250
250
  </div>
@@ -366,6 +366,26 @@ export function TaskSheet() {
366
366
  </div>
367
367
  )}
368
368
 
369
+ {Array.isArray(editing.outputFiles) && editing.outputFiles.length > 0 && (
370
+ <div className="mb-8">
371
+ <SectionLabel>Output Files</SectionLabel>
372
+ <div className="flex flex-col gap-1.5">
373
+ {editing.outputFiles.map((fileRef) => (
374
+ <code key={fileRef} className="text-[12px] text-text-3 font-mono break-all">
375
+ {fileRef}
376
+ </code>
377
+ ))}
378
+ </div>
379
+ </div>
380
+ )}
381
+
382
+ {editing.completionReportPath && (
383
+ <div className="mb-8">
384
+ <SectionLabel>Task Report</SectionLabel>
385
+ <code className="text-[12px] text-text-3 font-mono break-all">{editing.completionReportPath}</code>
386
+ </div>
387
+ )}
388
+
369
389
  {/* CLI Sessions */}
370
390
  {(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.cliResumeId) && (
371
391
  <div className="mb-8">
@@ -0,0 +1,99 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback } from 'react'
4
+ import { api } from '@/lib/api-client'
5
+ import type { WalletTransaction } from '@/types'
6
+
7
+ interface WalletApprovalDialogProps {
8
+ transaction: WalletTransaction
9
+ walletAddress: string
10
+ onClose: () => void
11
+ onResolved: () => void
12
+ }
13
+
14
+ export function WalletApprovalDialog({ transaction, walletAddress, onClose, onResolved }: WalletApprovalDialogProps) {
15
+ const [submitting, setSubmitting] = useState(false)
16
+ const [error, setError] = useState<string | null>(null)
17
+
18
+ const handleDecision = useCallback(async (decision: 'approve' | 'deny') => {
19
+ setSubmitting(true)
20
+ setError(null)
21
+ try {
22
+ await api('POST', `/wallets/${transaction.walletId}/approve`, {
23
+ transactionId: transaction.id,
24
+ decision,
25
+ })
26
+ onResolved()
27
+ onClose()
28
+ } catch (err: unknown) {
29
+ setError(err instanceof Error ? err.message : String(err))
30
+ } finally {
31
+ setSubmitting(false)
32
+ }
33
+ }, [transaction, onResolved, onClose])
34
+
35
+ const amountSol = transaction.amountLamports / 1e9
36
+
37
+ return (
38
+ <div className="fixed inset-0 z-50 flex items-center justify-center">
39
+ <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
40
+ <div className="relative w-full max-w-md rounded-[16px] border border-white/[0.08] bg-surface-1 shadow-2xl p-6 space-y-5">
41
+ <div className="flex items-center gap-2">
42
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-amber-400">
43
+ <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" />
44
+ <line x1="12" y1="9" x2="12" y2="13" /><line x1="12" y1="17" x2="12.01" y2="17" />
45
+ </svg>
46
+ <h3 className="font-display text-[15px] font-600 text-text-1">Transaction Approval</h3>
47
+ </div>
48
+
49
+ <div className="p-4 rounded-[12px] bg-black/20 border border-white/[0.06] space-y-3">
50
+ <div className="flex items-center justify-between">
51
+ <span className="text-[11px] text-text-3/70 uppercase tracking-wide">Amount</span>
52
+ <span className="text-[16px] font-600 text-text-1">{amountSol.toFixed(4)} SOL</span>
53
+ </div>
54
+ <div>
55
+ <span className="text-[11px] text-text-3/70 uppercase tracking-wide block mb-1">From</span>
56
+ <code className="text-[10px] text-text-3 font-mono break-all">{walletAddress}</code>
57
+ </div>
58
+ <div>
59
+ <span className="text-[11px] text-text-3/70 uppercase tracking-wide block mb-1">To</span>
60
+ <code className="text-[10px] text-text-3 font-mono break-all">{transaction.toAddress}</code>
61
+ </div>
62
+ {transaction.memo && (
63
+ <div>
64
+ <span className="text-[11px] text-text-3/70 uppercase tracking-wide block mb-1">Reason</span>
65
+ <p className="text-[12px] text-text-2">{transaction.memo}</p>
66
+ </div>
67
+ )}
68
+ </div>
69
+
70
+ <p className="text-[11px] text-amber-400/80">
71
+ Crypto transactions are irreversible. Verify the recipient address carefully.
72
+ </p>
73
+
74
+ {error && <p className="text-[11px] text-red-400">{error}</p>}
75
+
76
+ <div className="flex gap-3">
77
+ <button
78
+ type="button"
79
+ onClick={() => handleDecision('deny')}
80
+ disabled={submitting}
81
+ className="flex-1 px-4 py-2.5 rounded-[10px] border border-white/[0.08] bg-surface text-text-3 text-[12px] font-600 hover:text-red-400 hover:border-red-400/30 transition-colors cursor-pointer disabled:opacity-50"
82
+ style={{ fontFamily: 'inherit' }}
83
+ >
84
+ Deny
85
+ </button>
86
+ <button
87
+ type="button"
88
+ onClick={() => handleDecision('approve')}
89
+ disabled={submitting}
90
+ className="flex-1 px-4 py-2.5 rounded-[10px] bg-accent text-white text-[12px] font-600 hover:brightness-110 transition-all cursor-pointer disabled:opacity-50"
91
+ style={{ fontFamily: 'inherit' }}
92
+ >
93
+ {submitting ? 'Processing...' : 'Approve & Send'}
94
+ </button>
95
+ </div>
96
+ </div>
97
+ </div>
98
+ )
99
+ }