@swarmclawai/swarmclaw 0.6.4 → 0.6.7
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.
- package/README.md +62 -30
- package/package.json +10 -1
- package/src/app/api/agents/[id]/clone/route.ts +40 -0
- package/src/app/api/agents/route.ts +39 -14
- package/src/app/api/chatrooms/[id]/chat/route.ts +58 -3
- package/src/app/api/chatrooms/[id]/moderate/route.ts +150 -0
- package/src/app/api/chatrooms/[id]/route.ts +34 -2
- package/src/app/api/chatrooms/route.ts +26 -3
- package/src/app/api/connectors/[id]/health/route.ts +64 -0
- package/src/app/api/connectors/route.ts +17 -2
- package/src/app/api/knowledge/route.ts +6 -1
- package/src/app/api/openclaw/doctor/route.ts +17 -0
- package/src/app/api/schedules/[id]/run/route.ts +3 -0
- package/src/app/api/sessions/[id]/chat/route.ts +5 -1
- package/src/app/api/sessions/route.ts +11 -2
- package/src/app/api/tasks/[id]/route.ts +18 -13
- package/src/app/api/tasks/route.ts +44 -1
- package/src/app/api/usage/route.ts +16 -7
- package/src/app/api/wallets/[id]/approve/route.ts +62 -0
- package/src/app/api/wallets/[id]/balance-history/route.ts +18 -0
- package/src/app/api/wallets/[id]/route.ts +118 -0
- package/src/app/api/wallets/[id]/send/route.ts +118 -0
- package/src/app/api/wallets/[id]/transactions/route.ts +18 -0
- package/src/app/api/wallets/route.ts +74 -0
- package/src/app/globals.css +8 -0
- package/src/cli/index.js +20 -0
- package/src/cli/index.ts +223 -39
- package/src/cli/spec.js +14 -0
- package/src/components/agents/agent-avatar.tsx +15 -1
- package/src/components/agents/agent-card.tsx +38 -6
- package/src/components/agents/agent-chat-list.tsx +79 -3
- package/src/components/agents/agent-sheet.tsx +191 -26
- package/src/components/auth/setup-wizard.tsx +268 -353
- package/src/components/chat/chat-area.tsx +24 -9
- package/src/components/chat/chat-header.tsx +48 -19
- package/src/components/chat/chat-tool-toggles.tsx +1 -1
- package/src/components/chat/delegation-banner.test.ts +27 -0
- package/src/components/chat/delegation-banner.tsx +109 -23
- package/src/components/chat/message-bubble.tsx +17 -16
- package/src/components/chat/message-list.tsx +6 -5
- package/src/components/chat/streaming-bubble.tsx +3 -2
- package/src/components/chat/thinking-indicator.tsx +3 -2
- package/src/components/chat/transfer-agent-picker.tsx +1 -1
- package/src/components/chatrooms/agent-hover-card.tsx +1 -1
- package/src/components/chatrooms/chatroom-input.tsx +1 -1
- package/src/components/chatrooms/chatroom-message.tsx +165 -23
- package/src/components/chatrooms/chatroom-sheet.tsx +289 -4
- package/src/components/chatrooms/chatroom-typing-bar.tsx +1 -1
- package/src/components/chatrooms/chatroom-view.tsx +62 -17
- package/src/components/connectors/connector-health.tsx +120 -0
- package/src/components/connectors/connector-list.tsx +1 -1
- package/src/components/connectors/connector-sheet.tsx +9 -0
- package/src/components/home/home-view.tsx +25 -3
- package/src/components/input/chat-input.tsx +8 -1
- package/src/components/knowledge/knowledge-list.tsx +1 -1
- package/src/components/knowledge/knowledge-sheet.tsx +1 -1
- package/src/components/layout/app-layout.tsx +35 -4
- package/src/components/memory/memory-agent-list.tsx +1 -1
- package/src/components/memory/memory-browser.tsx +1 -0
- package/src/components/memory/memory-card.tsx +3 -2
- package/src/components/memory/memory-detail.tsx +3 -3
- package/src/components/memory/memory-sheet.tsx +2 -2
- package/src/components/projects/project-detail.tsx +4 -4
- package/src/components/schedules/schedule-list.tsx +55 -9
- package/src/components/schedules/schedule-sheet.tsx +134 -23
- package/src/components/secrets/secret-sheet.tsx +1 -1
- package/src/components/secrets/secrets-list.tsx +1 -1
- package/src/components/sessions/session-card.tsx +1 -1
- package/src/components/shared/agent-picker-list.tsx +1 -1
- package/src/components/shared/agent-switch-dialog.tsx +1 -1
- package/src/components/shared/command-palette.tsx +237 -0
- package/src/components/shared/connector-platform-icon.tsx +1 -0
- package/src/components/shared/settings/section-user-preferences.tsx +4 -4
- package/src/components/skills/skill-list.tsx +1 -1
- package/src/components/skills/skill-sheet.tsx +1 -1
- package/src/components/tasks/task-board.tsx +3 -3
- package/src/components/tasks/task-card.tsx +22 -2
- package/src/components/tasks/task-sheet.tsx +112 -17
- package/src/components/usage/metrics-dashboard.tsx +13 -25
- package/src/components/wallets/wallet-approval-dialog.tsx +99 -0
- package/src/components/wallets/wallet-panel.tsx +616 -0
- package/src/components/wallets/wallet-section.tsx +100 -0
- package/src/hooks/use-swipe.ts +49 -0
- package/src/lib/providers/anthropic.ts +16 -2
- package/src/lib/providers/claude-cli.ts +7 -1
- package/src/lib/providers/index.ts +7 -0
- package/src/lib/providers/ollama.ts +16 -2
- package/src/lib/providers/openai.ts +7 -2
- package/src/lib/providers/openclaw.ts +6 -1
- package/src/lib/providers/provider-defaults.ts +7 -0
- package/src/lib/schedule-templates.ts +115 -0
- package/src/lib/server/agent-registry.ts +2 -2
- package/src/lib/server/alert-dispatch.ts +64 -0
- package/src/lib/server/chat-execution.ts +76 -4
- package/src/lib/server/chatroom-health.ts +60 -0
- package/src/lib/server/chatroom-helpers.test.ts +94 -0
- package/src/lib/server/chatroom-helpers.ts +86 -12
- package/src/lib/server/chatroom-routing.ts +65 -0
- package/src/lib/server/connectors/discord.ts +3 -0
- package/src/lib/server/connectors/email.ts +267 -0
- package/src/lib/server/connectors/inbound-audio-transcription.test.ts +191 -0
- package/src/lib/server/connectors/inbound-audio-transcription.ts +261 -0
- package/src/lib/server/connectors/manager.ts +239 -5
- package/src/lib/server/connectors/openclaw.ts +3 -0
- package/src/lib/server/connectors/slack.ts +6 -0
- package/src/lib/server/connectors/telegram.ts +18 -0
- package/src/lib/server/connectors/types.ts +2 -0
- package/src/lib/server/connectors/whatsapp-text.test.ts +29 -0
- package/src/lib/server/connectors/whatsapp-text.ts +26 -0
- package/src/lib/server/connectors/whatsapp.ts +17 -5
- package/src/lib/server/cost.ts +70 -0
- package/src/lib/server/create-notification.ts +2 -0
- package/src/lib/server/daemon-state.ts +124 -0
- package/src/lib/server/dag-validation.ts +115 -0
- package/src/lib/server/memory-db.ts +12 -7
- package/src/lib/server/openclaw-doctor.ts +48 -0
- package/src/lib/server/orchestrator-lg.ts +12 -2
- package/src/lib/server/orchestrator.ts +6 -1
- package/src/lib/server/queue-followups.test.ts +224 -0
- package/src/lib/server/queue.ts +238 -24
- package/src/lib/server/scheduler.ts +3 -0
- package/src/lib/server/session-run-manager.ts +22 -1
- package/src/lib/server/session-tools/chatroom.ts +11 -2
- package/src/lib/server/session-tools/context-mgmt.ts +2 -2
- package/src/lib/server/session-tools/index.ts +8 -2
- package/src/lib/server/session-tools/memory.ts +23 -4
- package/src/lib/server/session-tools/openclaw-workspace.ts +132 -0
- package/src/lib/server/session-tools/shell.ts +1 -1
- package/src/lib/server/session-tools/wallet.ts +124 -0
- package/src/lib/server/session-tools/web.ts +2 -2
- package/src/lib/server/solana.ts +122 -0
- package/src/lib/server/storage.ts +158 -6
- package/src/lib/server/stream-agent-chat.ts +126 -63
- package/src/lib/server/task-mention.test.ts +41 -0
- package/src/lib/server/task-mention.ts +3 -2
- package/src/lib/setup-defaults.ts +277 -0
- package/src/lib/tool-definitions.ts +1 -0
- package/src/lib/validation/schemas.ts +69 -0
- package/src/lib/view-routes.ts +1 -0
- package/src/stores/use-app-store.ts +15 -3
- package/src/stores/use-chatroom-store.ts +52 -2
- package/src/types/index.ts +98 -2
- package/tsconfig.json +2 -1
|
@@ -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
|
|
39
|
-
className={`relative w-9 h-5 rounded-full transition-colors ${appSettings.suggestionsEnabled
|
|
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
|
|
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
|
))}
|
|
@@ -4,6 +4,7 @@ import { useState, useCallback } from 'react'
|
|
|
4
4
|
import { useAppStore } from '@/stores/use-app-store'
|
|
5
5
|
import { api } from '@/lib/api-client'
|
|
6
6
|
import { updateTask, archiveTask } from '@/lib/tasks'
|
|
7
|
+
import { ConfirmDialog } from '@/components/shared/confirm-dialog'
|
|
7
8
|
import type { BoardTask } from '@/types'
|
|
8
9
|
|
|
9
10
|
function timeAgo(ts: number) {
|
|
@@ -30,7 +31,9 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect }: Task
|
|
|
30
31
|
const setCurrentSession = useAppStore((s) => s.setCurrentSession)
|
|
31
32
|
const setActiveView = useAppStore((s) => s.setActiveView)
|
|
32
33
|
const [dragging, setDragging] = useState(false)
|
|
34
|
+
const [confirmArchive, setConfirmArchive] = useState(false)
|
|
33
35
|
|
|
36
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
34
37
|
const agent = agents[task.agentId]
|
|
35
38
|
const project = task.projectId ? projects[task.projectId] : null
|
|
36
39
|
|
|
@@ -117,7 +120,7 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect }: Task
|
|
|
117
120
|
)}
|
|
118
121
|
{isBlocked && (
|
|
119
122
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-rose-400 shrink-0 mt-0.5">
|
|
120
|
-
<title>{`Blocked by ${task.blockedBy?.
|
|
123
|
+
<title>{`Blocked by: ${(task.blockedBy || []).map((bid) => tasks[bid]?.title || bid).join(', ')}`}</title>
|
|
121
124
|
<rect x="3" y="11" width="18" height="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
122
125
|
</svg>
|
|
123
126
|
)}
|
|
@@ -211,6 +214,14 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect }: Task
|
|
|
211
214
|
{task.comments.length}
|
|
212
215
|
</span>
|
|
213
216
|
)}
|
|
217
|
+
{Array.isArray(task.blocks) && task.blocks.length > 0 && (
|
|
218
|
+
<span
|
|
219
|
+
className="px-1.5 py-0.5 rounded-[5px] bg-amber-500/10 text-amber-400 text-[10px] font-600"
|
|
220
|
+
title={`Blocks: ${task.blocks.map((bid) => tasks[bid]?.title || bid).join(', ')}`}
|
|
221
|
+
>
|
|
222
|
+
blocks {task.blocks.length}
|
|
223
|
+
</span>
|
|
224
|
+
)}
|
|
214
225
|
|
|
215
226
|
{task.status === 'backlog' && (
|
|
216
227
|
<button
|
|
@@ -236,7 +247,8 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect }: Task
|
|
|
236
247
|
|
|
237
248
|
{(task.status === 'completed' || task.status === 'failed') && !task.sessionId && (
|
|
238
249
|
<button
|
|
239
|
-
onClick={
|
|
250
|
+
onClick={(e) => { e.stopPropagation(); setConfirmArchive(true) }}
|
|
251
|
+
aria-label="Archive task"
|
|
240
252
|
className="ml-auto px-2.5 py-1 rounded-[8px] text-[11px] font-600 bg-white/[0.04] text-text-3 border-none cursor-pointer
|
|
241
253
|
opacity-0 group-hover:opacity-100 transition-opacity hover:bg-white/[0.08]"
|
|
242
254
|
style={{ fontFamily: 'inherit' }}
|
|
@@ -304,6 +316,14 @@ export function TaskCard({ task, selectionMode, selected, onToggleSelect }: Task
|
|
|
304
316
|
))}
|
|
305
317
|
</div>
|
|
306
318
|
)}
|
|
319
|
+
<ConfirmDialog
|
|
320
|
+
open={confirmArchive}
|
|
321
|
+
title="Archive Task"
|
|
322
|
+
message={`Archive "${task.title}"? You can view archived tasks later.`}
|
|
323
|
+
confirmLabel="Archive"
|
|
324
|
+
onConfirm={() => { setConfirmArchive(false); handleArchive({ stopPropagation: () => {} } as React.MouseEvent) }}
|
|
325
|
+
onCancel={() => setConfirmArchive(false)}
|
|
326
|
+
/>
|
|
307
327
|
</div>
|
|
308
328
|
)
|
|
309
329
|
}
|
|
@@ -54,6 +54,8 @@ export function TaskSheet() {
|
|
|
54
54
|
const [tags, setTags] = useState<string[]>([])
|
|
55
55
|
const [tagInput, setTagInput] = useState('')
|
|
56
56
|
const [blockedBy, setBlockedBy] = useState<string[]>([])
|
|
57
|
+
const [depSearch, setDepSearch] = useState('')
|
|
58
|
+
const [depError, setDepError] = useState<string | null>(null)
|
|
57
59
|
const [dueAt, setDueAt] = useState<string>('')
|
|
58
60
|
const [customFields, setCustomFields] = useState<Record<string, string | number | boolean>>({})
|
|
59
61
|
const [priority, setPriority] = useState<'low' | 'medium' | 'high' | 'critical' | ''>('')
|
|
@@ -76,6 +78,8 @@ export function TaskSheet() {
|
|
|
76
78
|
setFile(editing.file || null)
|
|
77
79
|
setTags(editing.tags || [])
|
|
78
80
|
setBlockedBy(editing.blockedBy || [])
|
|
81
|
+
setDepSearch('')
|
|
82
|
+
setDepError(null)
|
|
79
83
|
setDueAt(editing.dueAt ? new Date(editing.dueAt).toISOString().slice(0, 10) : '')
|
|
80
84
|
setCustomFields(editing.customFields || {})
|
|
81
85
|
setPriority(editing.priority || '')
|
|
@@ -89,6 +93,8 @@ export function TaskSheet() {
|
|
|
89
93
|
setFile(null)
|
|
90
94
|
setTags([])
|
|
91
95
|
setBlockedBy([])
|
|
96
|
+
setDepSearch('')
|
|
97
|
+
setDepError(null)
|
|
92
98
|
setDueAt('')
|
|
93
99
|
setCustomFields({})
|
|
94
100
|
setPriority('')
|
|
@@ -119,11 +125,25 @@ export function TaskSheet() {
|
|
|
119
125
|
customFields: Object.keys(customFields).length > 0 ? customFields : undefined,
|
|
120
126
|
priority: priority || undefined,
|
|
121
127
|
} as Partial<BoardTask> & { title: string; description: string; agentId: string }
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
try {
|
|
129
|
+
if (editing) {
|
|
130
|
+
const res = await updateTask(editing.id, payload)
|
|
131
|
+
if (res && typeof res === 'object' && 'error' in res) {
|
|
132
|
+
setDepError(String((res as unknown as Record<string, unknown>).error))
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
const res = await createTask(payload)
|
|
137
|
+
if (res && typeof res === 'object' && 'error' in res) {
|
|
138
|
+
setDepError(String((res as unknown as Record<string, unknown>).error))
|
|
139
|
+
return
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (err: unknown) {
|
|
143
|
+
setDepError(err instanceof Error ? err.message : String(err))
|
|
144
|
+
return
|
|
126
145
|
}
|
|
146
|
+
setDepError(null)
|
|
127
147
|
await loadTasks()
|
|
128
148
|
onClose()
|
|
129
149
|
}
|
|
@@ -244,7 +264,7 @@ export function TaskSheet() {
|
|
|
244
264
|
<div className="mb-8">
|
|
245
265
|
<SectionLabel>Agent</SectionLabel>
|
|
246
266
|
<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} />
|
|
267
|
+
<AgentAvatar seed={taskAgent.avatarSeed || null} avatarUrl={taskAgent.avatarUrl} name={taskAgent.name} size={24} />
|
|
248
268
|
<span className="text-[14px] font-600 text-text">{taskAgent.name}</span>
|
|
249
269
|
</div>
|
|
250
270
|
</div>
|
|
@@ -366,6 +386,26 @@ export function TaskSheet() {
|
|
|
366
386
|
</div>
|
|
367
387
|
)}
|
|
368
388
|
|
|
389
|
+
{Array.isArray(editing.outputFiles) && editing.outputFiles.length > 0 && (
|
|
390
|
+
<div className="mb-8">
|
|
391
|
+
<SectionLabel>Output Files</SectionLabel>
|
|
392
|
+
<div className="flex flex-col gap-1.5">
|
|
393
|
+
{editing.outputFiles.map((fileRef) => (
|
|
394
|
+
<code key={fileRef} className="text-[12px] text-text-3 font-mono break-all">
|
|
395
|
+
{fileRef}
|
|
396
|
+
</code>
|
|
397
|
+
))}
|
|
398
|
+
</div>
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
401
|
+
|
|
402
|
+
{editing.completionReportPath && (
|
|
403
|
+
<div className="mb-8">
|
|
404
|
+
<SectionLabel>Task Report</SectionLabel>
|
|
405
|
+
<code className="text-[12px] text-text-3 font-mono break-all">{editing.completionReportPath}</code>
|
|
406
|
+
</div>
|
|
407
|
+
)}
|
|
408
|
+
|
|
369
409
|
{/* CLI Sessions */}
|
|
370
410
|
{(editing.claudeResumeId || editing.codexResumeId || editing.opencodeResumeId || editing.cliResumeId) && (
|
|
371
411
|
<div className="mb-8">
|
|
@@ -663,18 +703,73 @@ export function TaskSheet() {
|
|
|
663
703
|
{/* Dependencies */}
|
|
664
704
|
<div className="mb-8">
|
|
665
705
|
<SectionLabel>Blocked By <span className="normal-case tracking-normal font-normal text-text-3">(tasks that must complete first)</span></SectionLabel>
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
706
|
+
{/* Selected blockers as removable chips */}
|
|
707
|
+
{blockedBy.length > 0 && (
|
|
708
|
+
<div className="flex flex-wrap gap-1.5 mb-3">
|
|
709
|
+
{blockedBy.map((bid) => {
|
|
710
|
+
const bt = tasks[bid]
|
|
711
|
+
return (
|
|
712
|
+
<span key={bid} className="inline-flex items-center gap-1 px-2.5 py-1 rounded-[8px] bg-rose-500/10 text-rose-400 text-[12px] font-600">
|
|
713
|
+
{bt ? bt.title : bid}
|
|
714
|
+
<button
|
|
715
|
+
onClick={() => setBlockedBy((prev) => prev.filter((b) => b !== bid))}
|
|
716
|
+
className="text-rose-400/60 hover:text-rose-400 cursor-pointer border-none bg-transparent p-0 text-[14px] leading-none"
|
|
717
|
+
>
|
|
718
|
+
×
|
|
719
|
+
</button>
|
|
720
|
+
</span>
|
|
721
|
+
)
|
|
722
|
+
})}
|
|
723
|
+
</div>
|
|
724
|
+
)}
|
|
725
|
+
{/* Searchable dropdown for adding dependencies */}
|
|
726
|
+
<div className="relative">
|
|
727
|
+
<input
|
|
728
|
+
type="text"
|
|
729
|
+
value={depSearch}
|
|
730
|
+
onChange={(e) => setDepSearch(e.target.value)}
|
|
731
|
+
placeholder="Search tasks to add as dependency..."
|
|
732
|
+
className={inputClass}
|
|
733
|
+
style={{ fontFamily: 'inherit' }}
|
|
734
|
+
/>
|
|
735
|
+
{depSearch.trim() && (
|
|
736
|
+
<div className="absolute z-20 top-full left-0 right-0 mt-1 max-h-[200px] overflow-y-auto rounded-[12px] border border-white/[0.08] bg-surface shadow-xl">
|
|
737
|
+
{Object.values(tasks)
|
|
738
|
+
.filter((t) =>
|
|
739
|
+
t.id !== editingId &&
|
|
740
|
+
t.status !== 'archived' &&
|
|
741
|
+
!blockedBy.includes(t.id) &&
|
|
742
|
+
t.title.toLowerCase().includes(depSearch.toLowerCase())
|
|
743
|
+
)
|
|
744
|
+
.slice(0, 10)
|
|
745
|
+
.map((t) => (
|
|
746
|
+
<button
|
|
747
|
+
key={t.id}
|
|
748
|
+
onClick={() => {
|
|
749
|
+
setBlockedBy((prev) => [...prev, t.id])
|
|
750
|
+
setDepSearch('')
|
|
751
|
+
}}
|
|
752
|
+
className="w-full text-left px-4 py-2.5 text-[13px] text-text-2 hover:bg-surface-2 cursor-pointer border-none bg-transparent transition-colors flex items-center gap-2"
|
|
753
|
+
style={{ fontFamily: 'inherit' }}
|
|
754
|
+
>
|
|
755
|
+
<span className="flex-1 truncate">{t.title}</span>
|
|
756
|
+
<span className="text-[10px] text-text-3 shrink-0">({t.status})</span>
|
|
757
|
+
</button>
|
|
758
|
+
))}
|
|
759
|
+
{Object.values(tasks).filter((t) =>
|
|
760
|
+
t.id !== editingId &&
|
|
761
|
+
t.status !== 'archived' &&
|
|
762
|
+
!blockedBy.includes(t.id) &&
|
|
763
|
+
t.title.toLowerCase().includes(depSearch.toLowerCase())
|
|
764
|
+
).length === 0 && (
|
|
765
|
+
<div className="px-4 py-3 text-[13px] text-text-3">No matching tasks</div>
|
|
766
|
+
)}
|
|
767
|
+
</div>
|
|
768
|
+
)}
|
|
769
|
+
</div>
|
|
770
|
+
{depError && (
|
|
771
|
+
<p className="mt-2 text-[12px] text-red-400 font-600">{depError}</p>
|
|
772
|
+
)}
|
|
678
773
|
{editing && Array.isArray(editing.blocks) && editing.blocks.length > 0 && (
|
|
679
774
|
<div className="mt-3">
|
|
680
775
|
<span className="text-[11px] font-600 text-text-3 uppercase tracking-[0.06em]">Blocks:</span>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useCallback } from 'react'
|
|
4
4
|
import {
|
|
5
|
-
LineChart, Line, BarChart, Bar,
|
|
5
|
+
LineChart, Line, BarChart, Bar, Cell,
|
|
6
6
|
XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend,
|
|
7
7
|
} from 'recharts'
|
|
8
8
|
import { useAppStore } from '@/stores/use-app-store'
|
|
@@ -32,7 +32,7 @@ interface UsageResponse {
|
|
|
32
32
|
records: unknown[]
|
|
33
33
|
totalTokens: number
|
|
34
34
|
totalCost: number
|
|
35
|
-
byAgent: Record<string, { tokens: number;
|
|
35
|
+
byAgent: Record<string, { name: string; cost: number; tokens: number; count: number }>
|
|
36
36
|
byProvider: Record<string, { tokens: number; cost: number }>
|
|
37
37
|
timeSeries: TimePoint[]
|
|
38
38
|
providerHealth?: Record<string, ProviderHealthEntry>
|
|
@@ -171,8 +171,8 @@ export function MetricsDashboard() {
|
|
|
171
171
|
const agentData = Object.entries(data?.byAgent ?? {})
|
|
172
172
|
.sort((a, b) => b[1].cost - a[1].cost)
|
|
173
173
|
.slice(0, 10)
|
|
174
|
-
.map(([
|
|
175
|
-
name: name.length >
|
|
174
|
+
.map(([_id, v]) => ({
|
|
175
|
+
name: v.name.length > 16 ? v.name.slice(0, 16) + '…' : v.name,
|
|
176
176
|
cost: Math.round(v.cost * 10000) / 10000,
|
|
177
177
|
}))
|
|
178
178
|
|
|
@@ -270,32 +270,20 @@ export function MetricsDashboard() {
|
|
|
270
270
|
)}
|
|
271
271
|
</ChartCard>
|
|
272
272
|
|
|
273
|
-
<ChartCard title="
|
|
273
|
+
<ChartCard title="Agent Breakdown">
|
|
274
274
|
{agentData.length > 0 ? (
|
|
275
275
|
<ResponsiveContainer width="100%" height={280}>
|
|
276
|
-
<
|
|
277
|
-
<
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
outerRadius={100}
|
|
283
|
-
paddingAngle={2}
|
|
284
|
-
dataKey="cost"
|
|
285
|
-
nameKey="name"
|
|
286
|
-
>
|
|
276
|
+
<BarChart data={agentData} layout="vertical" margin={{ top: 5, right: 20, bottom: 5, left: 0 }}>
|
|
277
|
+
<CartesianGrid strokeDasharray="3 3" stroke="rgba(255,255,255,0.06)" horizontal={false} />
|
|
278
|
+
<XAxis type="number" tick={{ fill: '#888', fontSize: 11 }} axisLine={false} tickLine={false} tickFormatter={(v: number) => `$${v}`} />
|
|
279
|
+
<YAxis type="category" dataKey="name" tick={{ fill: '#888', fontSize: 11 }} axisLine={false} tickLine={false} width={100} />
|
|
280
|
+
<Tooltip {...tooltipStyle} formatter={(value: number | undefined) => [formatCost(value ?? 0), 'Cost']} />
|
|
281
|
+
<Bar dataKey="cost" radius={[0, 4, 4, 0]}>
|
|
287
282
|
{agentData.map((_entry, i) => (
|
|
288
283
|
<Cell key={i} fill={CHART_COLORS[i % CHART_COLORS.length]} />
|
|
289
284
|
))}
|
|
290
|
-
</
|
|
291
|
-
|
|
292
|
-
<Legend
|
|
293
|
-
verticalAlign="bottom"
|
|
294
|
-
iconType="circle"
|
|
295
|
-
iconSize={8}
|
|
296
|
-
formatter={(value: string) => <span style={{ color: '#a0a0b0', fontSize: 11 }}>{value}</span>}
|
|
297
|
-
/>
|
|
298
|
-
</PieChart>
|
|
285
|
+
</Bar>
|
|
286
|
+
</BarChart>
|
|
299
287
|
</ResponsiveContainer>
|
|
300
288
|
) : (
|
|
301
289
|
<EmptyChart />
|
|
@@ -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
|
+
}
|