@swarmclawai/swarmclaw 0.6.8 → 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.
- package/README.md +70 -45
- package/next.config.ts +31 -6
- package/package.json +3 -2
- package/src/app/api/agents/[id]/thread/route.ts +1 -0
- package/src/app/api/agents/route.ts +18 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/mcp-servers/[id]/conformance/route.ts +26 -0
- package/src/app/api/mcp-servers/[id]/invoke/route.ts +81 -0
- package/src/app/api/memory/route.ts +36 -5
- package/src/app/api/notifications/route.ts +3 -0
- package/src/app/api/plugins/install/route.ts +57 -5
- package/src/app/api/plugins/marketplace/route.ts +73 -22
- package/src/app/api/plugins/route.ts +61 -1
- package/src/app/api/plugins/ui/route.ts +34 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +11 -3
- package/src/app/api/tasks/route.ts +8 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +13 -0
- package/src/components/activity/activity-feed.tsx +9 -2
- package/src/components/agents/agent-avatar.tsx +5 -1
- package/src/components/agents/agent-card.tsx +55 -9
- package/src/components/agents/agent-sheet.tsx +86 -29
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/chat-area.tsx +11 -0
- package/src/components/chat/chat-header.tsx +69 -25
- package/src/components/chat/chat-tool-toggles.tsx +2 -2
- package/src/components/chat/code-block.tsx +3 -1
- package/src/components/chat/exec-approval-card.tsx +8 -1
- package/src/components/chat/message-bubble.tsx +164 -4
- package/src/components/chat/message-list.tsx +30 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-request-banner.tsx +39 -20
- package/src/components/chatrooms/chatroom-list.tsx +11 -4
- package/src/components/chatrooms/chatroom-sheet.tsx +7 -2
- package/src/components/connectors/connector-list.tsx +33 -11
- package/src/components/connectors/connector-sheet.tsx +29 -6
- package/src/components/home/home-view.tsx +20 -14
- package/src/components/input/chat-input.tsx +22 -1
- package/src/components/knowledge/knowledge-list.tsx +17 -18
- package/src/components/knowledge/knowledge-sheet.tsx +9 -5
- package/src/components/layout/app-layout.tsx +73 -21
- package/src/components/mcp-servers/mcp-server-list.tsx +352 -50
- package/src/components/mcp-servers/mcp-server-sheet.tsx +25 -9
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +213 -59
- package/src/components/plugins/plugin-sheet.tsx +119 -24
- package/src/components/projects/project-list.tsx +17 -9
- package/src/components/providers/provider-list.tsx +21 -6
- package/src/components/providers/provider-sheet.tsx +42 -25
- package/src/components/runs/run-list.tsx +17 -13
- package/src/components/schedules/schedule-card.tsx +10 -3
- package/src/components/schedules/schedule-list.tsx +2 -2
- package/src/components/schedules/schedule-sheet.tsx +19 -7
- package/src/components/secrets/secret-sheet.tsx +7 -2
- package/src/components/secrets/secrets-list.tsx +18 -5
- package/src/components/sessions/new-session-sheet.tsx +183 -376
- package/src/components/sessions/session-card.tsx +10 -2
- package/src/components/settings/gateway-connection-panel.tsx +9 -8
- package/src/components/shared/command-palette.tsx +13 -5
- package/src/components/shared/empty-state.tsx +20 -8
- package/src/components/shared/notification-center.tsx +134 -86
- package/src/components/shared/profile-sheet.tsx +4 -0
- package/src/components/shared/settings/plugin-manager.tsx +360 -135
- package/src/components/shared/settings/section-capability-policy.tsx +3 -3
- package/src/components/shared/settings/section-runtime-loop.tsx +144 -0
- package/src/components/skills/clawhub-browser.tsx +1 -0
- package/src/components/skills/skill-list.tsx +31 -12
- package/src/components/skills/skill-sheet.tsx +20 -7
- package/src/components/tasks/approvals-panel.tsx +170 -66
- package/src/components/tasks/task-board.tsx +20 -12
- package/src/components/tasks/task-card.tsx +21 -7
- package/src/components/tasks/task-column.tsx +4 -3
- package/src/components/tasks/task-list.tsx +1 -1
- package/src/components/tasks/task-sheet.tsx +130 -1
- package/src/components/ui/dialog.tsx +1 -0
- package/src/components/ui/sheet.tsx +1 -0
- package/src/components/usage/metrics-dashboard.tsx +66 -64
- package/src/components/wallets/wallet-panel.tsx +65 -41
- package/src/components/wallets/wallet-section.tsx +9 -3
- package/src/components/webhooks/webhook-list.tsx +21 -12
- package/src/components/webhooks/webhook-sheet.tsx +13 -3
- package/src/lib/approval-display.test.ts +45 -0
- package/src/lib/approval-display.ts +62 -0
- package/src/lib/clipboard.ts +38 -0
- package/src/lib/memory.ts +8 -0
- package/src/lib/providers/claude-cli.ts +5 -3
- package/src/lib/providers/index.ts +67 -21
- package/src/lib/runtime-loop.ts +3 -2
- package/src/lib/server/approvals.ts +150 -0
- package/src/lib/server/chat-execution.ts +223 -62
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +42 -0
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/llm-response-cache.test.ts +102 -0
- package/src/lib/server/llm-response-cache.ts +227 -0
- package/src/lib/server/main-agent-loop.ts +1 -1
- package/src/lib/server/main-session.ts +6 -3
- package/src/lib/server/mcp-conformance.test.ts +18 -0
- package/src/lib/server/mcp-conformance.ts +233 -0
- package/src/lib/server/memory-db.ts +180 -17
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/orchestrator-lg.ts +4 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +650 -142
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/queue.ts +253 -11
- package/src/lib/server/runtime-settings.ts +9 -0
- package/src/lib/server/session-run-manager.test.ts +23 -0
- package/src/lib/server/session-run-manager.ts +11 -1
- package/src/lib/server/session-tools/canvas.ts +85 -50
- package/src/lib/server/session-tools/chatroom.ts +130 -127
- package/src/lib/server/session-tools/connector.ts +233 -454
- package/src/lib/server/session-tools/context-mgmt.ts +87 -105
- package/src/lib/server/session-tools/crud.ts +84 -7
- package/src/lib/server/session-tools/delegate.ts +351 -752
- package/src/lib/server/session-tools/discovery.ts +198 -0
- package/src/lib/server/session-tools/edit_file.ts +82 -0
- package/src/lib/server/session-tools/file-send.test.ts +39 -0
- package/src/lib/server/session-tools/file.ts +257 -425
- package/src/lib/server/session-tools/git.ts +87 -47
- package/src/lib/server/session-tools/http.ts +85 -33
- package/src/lib/server/session-tools/index.ts +205 -160
- package/src/lib/server/session-tools/memory.ts +152 -265
- package/src/lib/server/session-tools/monitor.ts +126 -0
- package/src/lib/server/session-tools/normalize-tool-args.test.ts +61 -0
- package/src/lib/server/session-tools/normalize-tool-args.ts +48 -0
- package/src/lib/server/session-tools/openclaw-nodes.ts +82 -99
- package/src/lib/server/session-tools/openclaw-workspace.ts +103 -93
- package/src/lib/server/session-tools/platform.ts +86 -0
- package/src/lib/server/session-tools/plugin-creator.ts +239 -0
- package/src/lib/server/session-tools/sample-ui.ts +97 -0
- package/src/lib/server/session-tools/sandbox.ts +175 -148
- package/src/lib/server/session-tools/schedule.ts +66 -31
- package/src/lib/server/session-tools/session-info.ts +104 -410
- package/src/lib/server/session-tools/shell-normalize.test.ts +43 -0
- package/src/lib/server/session-tools/shell.ts +171 -143
- package/src/lib/server/session-tools/subagent.ts +77 -77
- package/src/lib/server/session-tools/wallet.ts +182 -106
- package/src/lib/server/session-tools/web.ts +179 -349
- package/src/lib/server/storage.ts +24 -0
- package/src/lib/server/stream-agent-chat.ts +301 -244
- package/src/lib/server/task-quality-gate.test.ts +44 -0
- package/src/lib/server/task-quality-gate.ts +67 -0
- package/src/lib/server/task-validation.test.ts +78 -0
- package/src/lib/server/task-validation.ts +67 -2
- package/src/lib/server/tool-aliases.ts +68 -0
- package/src/lib/server/tool-capability-policy.ts +23 -5
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +23 -23
- package/src/lib/validation/schemas.ts +12 -0
- package/src/lib/view-routes.ts +2 -24
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +121 -7
|
@@ -5,7 +5,6 @@ import type { Session } from '@/types'
|
|
|
5
5
|
import { useAppStore } from '@/stores/use-app-store'
|
|
6
6
|
import { useChatStore } from '@/stores/use-chat-store'
|
|
7
7
|
import { IconButton } from '@/components/shared/icon-button'
|
|
8
|
-
import { UsageBadge } from '@/components/shared/usage-badge'
|
|
9
8
|
import { ChatToolToggles } from './chat-tool-toggles'
|
|
10
9
|
import { api } from '@/lib/api-client'
|
|
11
10
|
import {
|
|
@@ -17,6 +16,7 @@ import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
|
17
16
|
import { ModelCombobox } from '@/components/shared/model-combobox'
|
|
18
17
|
import { toast } from 'sonner'
|
|
19
18
|
import type { ProviderType } from '@/types'
|
|
19
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
20
20
|
import { useWs } from '@/hooks/use-ws'
|
|
21
21
|
|
|
22
22
|
function shortPath(p: string): string {
|
|
@@ -67,7 +67,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
67
67
|
const toggleSound = useChatStore((s) => s.toggleSound)
|
|
68
68
|
const debugOpen = useChatStore((s) => s.debugOpen)
|
|
69
69
|
const setDebugOpen = useChatStore((s) => s.setDebugOpen)
|
|
70
|
-
const lastUsage = useChatStore((s) => s.lastUsage)
|
|
71
70
|
const agentStatus = useChatStore((s) => s.agentStatus)
|
|
72
71
|
const agents = useAppStore((s) => s.agents)
|
|
73
72
|
const tasks = useAppStore((s) => s.tasks)
|
|
@@ -109,18 +108,50 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
109
108
|
const renameContainerRef = useRef<HTMLSpanElement>(null)
|
|
110
109
|
const setWalletPanelAgentId = useAppStore((s) => s.setWalletPanelAgentId)
|
|
111
110
|
const [walletBalance, setWalletBalance] = useState<number | null>(null)
|
|
111
|
+
const [headerWidgets, setHeaderWidgets] = useState<Array<{ id: string; label: string; icon?: string }>>([])
|
|
112
|
+
|
|
113
|
+
useEffect(() => {
|
|
114
|
+
api<Array<{ id: string; label: string; icon?: string }>>('GET', `/plugins/ui?type=header&sessionId=${session.id}`).then(widgets => {
|
|
115
|
+
if (Array.isArray(widgets)) setHeaderWidgets(widgets)
|
|
116
|
+
}).catch(() => {})
|
|
117
|
+
}, [session.id])
|
|
112
118
|
|
|
113
119
|
const fetchWalletBalance = useCallback(async () => {
|
|
114
|
-
if (!agent?.walletId) {
|
|
120
|
+
if (!agent?.walletId) {
|
|
121
|
+
setWalletBalance(null)
|
|
122
|
+
return
|
|
123
|
+
}
|
|
115
124
|
try {
|
|
116
125
|
const data = await api<{ balanceSol?: number }>('GET', `/wallets/${agent.walletId}`)
|
|
117
126
|
setWalletBalance(data.balanceSol ?? null)
|
|
118
|
-
} catch {
|
|
127
|
+
} catch {
|
|
128
|
+
setWalletBalance(null)
|
|
129
|
+
}
|
|
119
130
|
}, [agent?.walletId])
|
|
120
131
|
|
|
121
|
-
useEffect(() => {
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
void fetchWalletBalance()
|
|
134
|
+
}, [fetchWalletBalance])
|
|
122
135
|
useWs('wallets', fetchWalletBalance)
|
|
123
136
|
|
|
137
|
+
|
|
138
|
+
const visibleHeaderWidgets = useMemo(() => {
|
|
139
|
+
const seen = new Set<string>()
|
|
140
|
+
return headerWidgets.filter((widget) => {
|
|
141
|
+
const key = widget.id || widget.label
|
|
142
|
+
if (seen.has(key)) return false
|
|
143
|
+
seen.add(key)
|
|
144
|
+
return true
|
|
145
|
+
})
|
|
146
|
+
}, [headerWidgets])
|
|
147
|
+
|
|
148
|
+
const handleHeaderWidgetClick = (widgetId: string) => {
|
|
149
|
+
if (widgetId === 'wallet-status') {
|
|
150
|
+
if (agent?.id) setWalletPanelAgentId(agent.id)
|
|
151
|
+
setActiveView('wallets')
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
124
155
|
// Find linked task for this session
|
|
125
156
|
const linkedTask = useMemo(() => {
|
|
126
157
|
return Object.values(tasks).find((t) => t.sessionId === session.id)
|
|
@@ -156,9 +187,11 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
156
187
|
|
|
157
188
|
const handleCopySessionId = () => {
|
|
158
189
|
if (!resumeHandle) return
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
190
|
+
void copyTextToClipboard(resumeHandle.command).then((copiedCommand) => {
|
|
191
|
+
if (!copiedCommand) return
|
|
192
|
+
setCopied(true)
|
|
193
|
+
setTimeout(() => setCopied(false), 2000)
|
|
194
|
+
})
|
|
162
195
|
}
|
|
163
196
|
|
|
164
197
|
const handleDismissResumeHandle = async (e: React.MouseEvent) => {
|
|
@@ -606,21 +639,37 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
606
639
|
{/* Metadata tray: wallet · model · path · status */}
|
|
607
640
|
<div className="flex items-center gap-1.5 min-w-0 overflow-hidden">
|
|
608
641
|
<span className="text-text-3/10 text-[10px] select-none shrink-0">/</span>
|
|
609
|
-
{
|
|
610
|
-
|
|
642
|
+
{visibleHeaderWidgets.map((widget) => {
|
|
643
|
+
const actionable = widget.id === 'wallet-status'
|
|
644
|
+
const walletLabel = walletBalance !== null
|
|
645
|
+
? `${walletBalance.toFixed(3)} SOL`
|
|
646
|
+
: (widget.label || 'Wallet')
|
|
647
|
+
return (
|
|
611
648
|
<button
|
|
649
|
+
key={widget.id}
|
|
612
650
|
type="button"
|
|
613
|
-
onClick={() =>
|
|
614
|
-
className=
|
|
615
|
-
|
|
651
|
+
onClick={actionable ? () => handleHeaderWidgetClick(widget.id) : undefined}
|
|
652
|
+
className={`inline-flex items-center gap-1 shrink-0 bg-transparent border-none p-0.5 rounded-[4px] text-[11px] font-mono transition-colors ${
|
|
653
|
+
actionable ? 'cursor-pointer text-text-3/45 hover:text-text-3/70 hover:bg-white/[0.04]' : 'cursor-default text-text-3/55'
|
|
654
|
+
}`}
|
|
655
|
+
title={actionable ? 'View wallet' : widget.label}
|
|
616
656
|
>
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
657
|
+
{actionable ? (
|
|
658
|
+
<>
|
|
659
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
|
|
660
|
+
<rect x="2" y="6" width="20" height="14" rx="2" />
|
|
661
|
+
<path d="M22 10H18a2 2 0 0 0 0 4h4" />
|
|
662
|
+
</svg>
|
|
663
|
+
{walletLabel}
|
|
664
|
+
</>
|
|
665
|
+
) : (
|
|
666
|
+
widget.label
|
|
667
|
+
)}
|
|
621
668
|
</button>
|
|
622
|
-
|
|
623
|
-
|
|
669
|
+
)
|
|
670
|
+
})}
|
|
671
|
+
{visibleHeaderWidgets.length > 0 && (
|
|
672
|
+
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
624
673
|
)}
|
|
625
674
|
{modelName && (
|
|
626
675
|
<div className="relative shrink-0" ref={modelSwitcherRef}>
|
|
@@ -668,12 +717,6 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
668
717
|
)}
|
|
669
718
|
</div>
|
|
670
719
|
)}
|
|
671
|
-
{lastUsage && !streaming && (
|
|
672
|
-
<>
|
|
673
|
-
<span className="text-text-3/10 text-[10px] select-none shrink-0">·</span>
|
|
674
|
-
<UsageBadge {...lastUsage} />
|
|
675
|
-
</>
|
|
676
|
-
)}
|
|
677
720
|
<button
|
|
678
721
|
type="button"
|
|
679
722
|
onClick={() => { api('POST', '/files/open', { path: session.cwd }).catch(() => {}) }}
|
|
@@ -855,6 +898,7 @@ export function ChatHeader({ session, streaming, onStop, onMenuToggle, onBack, m
|
|
|
855
898
|
<span className={`w-1.5 h-1.5 rounded-full ${missionPaused ? 'bg-amber-300' : 'bg-emerald-400'}`} />
|
|
856
899
|
{missionPaused ? 'Paused' : 'Live'}
|
|
857
900
|
</button>
|
|
901
|
+
|
|
858
902
|
<button
|
|
859
903
|
onClick={handleToggleMissionMode}
|
|
860
904
|
disabled={mainLoopSaving}
|
|
@@ -8,8 +8,8 @@ import type { ToolDefinition } from '@/lib/tool-definitions'
|
|
|
8
8
|
import type { Session } from '@/types'
|
|
9
9
|
|
|
10
10
|
const TOOL_GROUPS: { label: string; tools: ToolDefinition[] }[] = [
|
|
11
|
-
{ label: '
|
|
12
|
-
{ label: 'Platform', tools: PLATFORM_TOOLS },
|
|
11
|
+
{ label: 'Plugins', tools: AVAILABLE_TOOLS },
|
|
12
|
+
{ label: 'Platform Plugins', tools: PLATFORM_TOOLS },
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
const TOTAL_TOOL_COUNT = AVAILABLE_TOOLS.length + PLATFORM_TOOLS.length
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import { useCallback, useState, type ReactNode } from 'react'
|
|
4
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
4
5
|
|
|
5
6
|
function extractText(node: ReactNode): string {
|
|
6
7
|
if (typeof node === 'string') return node
|
|
@@ -29,7 +30,8 @@ export function CodeBlock({ children, className }: Props) {
|
|
|
29
30
|
const getText = useCallback(() => extractText(children), [children])
|
|
30
31
|
|
|
31
32
|
const handleCopy = useCallback(() => {
|
|
32
|
-
|
|
33
|
+
void copyTextToClipboard(getText()).then((copiedText) => {
|
|
34
|
+
if (!copiedText) return
|
|
33
35
|
setCopied(true)
|
|
34
36
|
setTimeout(() => setCopied(false), 2000)
|
|
35
37
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
3
4
|
import type { PendingExecApproval, ExecApprovalDecision } from '@/types'
|
|
4
5
|
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
5
6
|
|
|
@@ -9,13 +10,19 @@ interface Props {
|
|
|
9
10
|
|
|
10
11
|
export function ExecApprovalCard({ approval }: Props) {
|
|
11
12
|
const resolveApproval = useApprovalStore((s) => s.resolveApproval)
|
|
13
|
+
const [now, setNow] = useState(() => Date.now())
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const timer = setInterval(() => setNow(Date.now()), 5000)
|
|
17
|
+
return () => clearInterval(timer)
|
|
18
|
+
}, [])
|
|
12
19
|
|
|
13
20
|
const handleResolve = (decision: ExecApprovalDecision) => {
|
|
14
21
|
resolveApproval(approval.id, decision)
|
|
15
22
|
}
|
|
16
23
|
|
|
17
24
|
const alreadyResolved = approval.error?.includes('Already resolved') ?? false
|
|
18
|
-
const expired = approval.expiresAtMs <
|
|
25
|
+
const expired = approval.expiresAtMs < now
|
|
19
26
|
const disabled = !!approval.resolving || expired || alreadyResolved
|
|
20
27
|
|
|
21
28
|
return (
|
|
@@ -6,6 +6,7 @@ import remarkGfm from 'remark-gfm'
|
|
|
6
6
|
import rehypeHighlight from 'rehype-highlight'
|
|
7
7
|
import type { Message } from '@/types'
|
|
8
8
|
import { useAppStore } from '@/stores/use-app-store'
|
|
9
|
+
import { useChatStore } from '@/stores/use-chat-store'
|
|
9
10
|
import { AiAvatar } from '@/components/shared/avatar'
|
|
10
11
|
import { AgentAvatar } from '@/components/agents/agent-avatar'
|
|
11
12
|
import { CodeBlock } from './code-block'
|
|
@@ -17,6 +18,7 @@ import { FilePathChip, FILE_PATH_RE, DIR_PATH_RE } from './file-path-chip'
|
|
|
17
18
|
import { TransferAgentPicker } from './transfer-agent-picker'
|
|
18
19
|
import { DelegationBanner, DelegationSourceBanner, TaskCompletionCard, parseTaskCompletion } from './delegation-banner'
|
|
19
20
|
import { ConnectorPlatformIcon, CONNECTOR_PLATFORM_META } from '@/components/shared/connector-platform-icon'
|
|
21
|
+
import { copyTextToClipboard } from '@/lib/clipboard'
|
|
20
22
|
|
|
21
23
|
/** Parse delegation-source metadata prefix from system messages */
|
|
22
24
|
const DELEGATION_SOURCE_RE = /^\[delegation-source:([^:]*):([^:]*):([^\]]*)\]/
|
|
@@ -151,6 +153,33 @@ interface Props {
|
|
|
151
153
|
export const MessageBubble = memo(function MessageBubble({ message, assistantName, agentAvatarSeed, agentAvatarUrl, agentName, isLast, onRetry, messageIndex, onToggleBookmark, onEditResend, onFork, onTransferToAgent, momentOverlay }: Props) {
|
|
152
154
|
const isUser = message.role === 'user'
|
|
153
155
|
const isHeartbeat = !isUser && (message.kind === 'heartbeat' || /^\s*HEARTBEAT_OK\b/i.test(message.text || ''))
|
|
156
|
+
const isPluginUI = !isUser && message.kind === 'plugin-ui'
|
|
157
|
+
const scaffoldRequest = useMemo(() => {
|
|
158
|
+
if (isUser) return null
|
|
159
|
+
try {
|
|
160
|
+
const data = JSON.parse(message.text)
|
|
161
|
+
if (data.type === 'plugin_scaffold_request') return data
|
|
162
|
+
} catch { /* ignore */ }
|
|
163
|
+
return null
|
|
164
|
+
}, [message.text, isUser])
|
|
165
|
+
|
|
166
|
+
const installRequest = useMemo(() => {
|
|
167
|
+
if (isUser) return null
|
|
168
|
+
try {
|
|
169
|
+
const data = JSON.parse(message.text)
|
|
170
|
+
if (data.type === 'plugin_install_request') return data
|
|
171
|
+
} catch { /* ignore */ }
|
|
172
|
+
return null
|
|
173
|
+
}, [message.text, isUser])
|
|
174
|
+
|
|
175
|
+
const walletRequest = useMemo(() => {
|
|
176
|
+
if (isUser) return null
|
|
177
|
+
try {
|
|
178
|
+
const data = JSON.parse(message.text)
|
|
179
|
+
if (data.type === 'plugin_wallet_transfer_request') return data
|
|
180
|
+
} catch { /* ignore */ }
|
|
181
|
+
return null
|
|
182
|
+
}, [message.text, isUser])
|
|
154
183
|
const currentUser = useAppStore((s) => s.currentUser)
|
|
155
184
|
const [copied, setCopied] = useState(false)
|
|
156
185
|
const [heartbeatExpanded, setHeartbeatExpanded] = useState(false)
|
|
@@ -217,7 +246,8 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
217
246
|
const displayText = delegationSource ? delegationSource.rest : message.text
|
|
218
247
|
|
|
219
248
|
const handleCopy = useCallback(() => {
|
|
220
|
-
|
|
249
|
+
void copyTextToClipboard(message.text).then((copiedText) => {
|
|
250
|
+
if (!copiedText) return
|
|
221
251
|
setCopied(true)
|
|
222
252
|
setTimeout(() => setCopied(false), 2000)
|
|
223
253
|
})
|
|
@@ -226,7 +256,6 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
226
256
|
return (
|
|
227
257
|
<div
|
|
228
258
|
className={`group ${isUser ? 'flex flex-col items-end' : 'flex flex-col items-start relative pl-[44px]'}`}
|
|
229
|
-
style={{ animation: `${isUser ? 'msg-in-right' : 'msg-in-left'} 0.35s cubic-bezier(0.16, 1, 0.3, 1)` }}
|
|
230
259
|
>
|
|
231
260
|
{/* Avatar on spine (assistant) */}
|
|
232
261
|
{!isUser && (
|
|
@@ -447,7 +476,138 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
447
476
|
<div className={`${isStructured ? 'max-w-[92%] md:max-w-[85%]' : 'max-w-[85%] md:max-w-[72%]'} ${isUser ? 'bubble-user px-5 py-3.5' : isHeartbeat ? 'bubble-ai px-4 py-3' : 'bubble-ai px-5 py-3.5'}`}>
|
|
448
477
|
{renderAttachments(message)}
|
|
449
478
|
|
|
450
|
-
{
|
|
479
|
+
{walletRequest ? (
|
|
480
|
+
<div className="flex flex-col gap-3 p-4 rounded-[18px] bg-sky-500/[0.03] border border-sky-500/20 shadow-[0_0_20px_rgba(14,165,233,0.05)]">
|
|
481
|
+
<div className="flex items-center gap-2 mb-1">
|
|
482
|
+
<div className="w-5 h-5 rounded-full bg-sky-500/20 flex items-center justify-center text-sky-400">
|
|
483
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
484
|
+
<path d="M12 1v22M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
|
485
|
+
</svg>
|
|
486
|
+
</div>
|
|
487
|
+
<span className="text-[11px] font-700 uppercase tracking-wider text-sky-400/80">Wallet Transfer Request</span>
|
|
488
|
+
</div>
|
|
489
|
+
<p className="text-[13px] text-text-2/90 leading-relaxed">{walletRequest.message}</p>
|
|
490
|
+
<div className="p-3 rounded-[12px] bg-black/40 border border-white/5 flex flex-col gap-2">
|
|
491
|
+
<div className="flex justify-between items-center">
|
|
492
|
+
<span className="text-[11px] text-text-3/60 font-600 uppercase">Amount</span>
|
|
493
|
+
<span className="text-[13px] font-700 text-sky-400">{walletRequest.amountSol} SOL</span>
|
|
494
|
+
</div>
|
|
495
|
+
<div className="flex flex-col gap-1">
|
|
496
|
+
<span className="text-[11px] text-text-3/60 font-600 uppercase">To Address</span>
|
|
497
|
+
<span className="text-[11px] font-mono text-text-2/70 break-all">{walletRequest.toAddress}</span>
|
|
498
|
+
</div>
|
|
499
|
+
{walletRequest.memo && (
|
|
500
|
+
<div className="flex flex-col gap-1 border-t border-white/5 pt-2">
|
|
501
|
+
<span className="text-[11px] text-text-3/60 font-600 uppercase">Memo</span>
|
|
502
|
+
<span className="text-[12px] text-text-3/80 italic">"{walletRequest.memo}"</span>
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
</div>
|
|
506
|
+
<div className="flex gap-2 mt-1">
|
|
507
|
+
<button
|
|
508
|
+
onClick={() => useChatStore.getState().sendMessage(`I approve this transfer of ${walletRequest.amountSol} SOL to ${walletRequest.toAddress}. Proceed with wallet_tool and set approved=true.`)}
|
|
509
|
+
className="px-4 py-2 rounded-[12px] bg-sky-500 text-black text-[13px] font-700 hover:bg-sky-400 transition-all active:scale-[0.98]"
|
|
510
|
+
>
|
|
511
|
+
Approve & Send
|
|
512
|
+
</button>
|
|
513
|
+
<button
|
|
514
|
+
onClick={() => useChatStore.getState().sendMessage(`I do not approve this transaction. Cancel it.`)}
|
|
515
|
+
className="px-4 py-2 rounded-[12px] bg-white/[0.05] hover:bg-white/[0.1] text-text-2 text-[13px] font-600 transition-all border border-white/10"
|
|
516
|
+
>
|
|
517
|
+
Reject
|
|
518
|
+
</button>
|
|
519
|
+
</div>
|
|
520
|
+
</div>
|
|
521
|
+
) : installRequest ? (
|
|
522
|
+
<div className="flex flex-col gap-3 p-4 rounded-[18px] bg-emerald-500/[0.03] border border-emerald-500/20 shadow-[0_0_20px_rgba(16,185,129,0.05)]">
|
|
523
|
+
<div className="flex items-center gap-2 mb-1">
|
|
524
|
+
<div className="w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center text-emerald-400">
|
|
525
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
526
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
|
|
527
|
+
<polyline points="17 8 12 3 7 8" />
|
|
528
|
+
<line x1="12" y1="3" x2="12" y2="15" />
|
|
529
|
+
</svg>
|
|
530
|
+
</div>
|
|
531
|
+
<span className="text-[11px] font-700 uppercase tracking-wider text-emerald-400/80">Plugin Install Request</span>
|
|
532
|
+
</div>
|
|
533
|
+
<p className="text-[13px] text-text-2/90 leading-relaxed">{installRequest.message}</p>
|
|
534
|
+
<div className="p-3 rounded-[12px] bg-black/40 border border-white/5 flex flex-col gap-1">
|
|
535
|
+
<div className="text-[11px] text-text-3/60 font-600 uppercase tracking-tight">Source URL</div>
|
|
536
|
+
<div className="text-[12px] font-mono text-emerald-200/70 truncate">{installRequest.url}</div>
|
|
537
|
+
</div>
|
|
538
|
+
<div className="flex gap-2 mt-1">
|
|
539
|
+
<button
|
|
540
|
+
onClick={() => useChatStore.getState().sendMessage(`I approve the installation of the plugin from ${installRequest.url}. Proceed with install_request and set approved=true.`)}
|
|
541
|
+
className="px-4 py-2 rounded-[12px] bg-emerald-500 text-black text-[13px] font-700 hover:bg-emerald-400 transition-all active:scale-[0.98]"
|
|
542
|
+
>
|
|
543
|
+
Approve & Install
|
|
544
|
+
</button>
|
|
545
|
+
<button
|
|
546
|
+
onClick={() => useChatStore.getState().sendMessage(`I do not approve this plugin installation. Please stop.`)}
|
|
547
|
+
className="px-4 py-2 rounded-[12px] bg-white/[0.05] hover:bg-white/[0.1] text-text-2 text-[13px] font-600 transition-all border border-white/10"
|
|
548
|
+
>
|
|
549
|
+
Reject
|
|
550
|
+
</button>
|
|
551
|
+
</div>
|
|
552
|
+
</div>
|
|
553
|
+
) : scaffoldRequest ? (
|
|
554
|
+
<div className="flex flex-col gap-3 p-4 rounded-[18px] bg-amber-500/[0.03] border border-amber-500/20 shadow-[0_0_20px_rgba(245,158,11,0.05)]">
|
|
555
|
+
<div className="flex items-center gap-2 mb-1">
|
|
556
|
+
<div className="w-5 h-5 rounded-full bg-amber-500/20 flex items-center justify-center text-amber-400">
|
|
557
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
558
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
559
|
+
</svg>
|
|
560
|
+
</div>
|
|
561
|
+
<span className="text-[11px] font-700 uppercase tracking-wider text-amber-400/80">Plugin Creation Request</span>
|
|
562
|
+
</div>
|
|
563
|
+
<p className="text-[13px] text-text-2/90 leading-relaxed">{scaffoldRequest.message}</p>
|
|
564
|
+
<div className="p-3 rounded-[12px] bg-black/40 border border-white/5">
|
|
565
|
+
<div className="text-[11px] font-mono text-text-3/60 mb-2 border-b border-white/5 pb-1">filename: {scaffoldRequest.filename}</div>
|
|
566
|
+
<div className="text-[12px] font-mono text-amber-200/70 max-h-[200px] overflow-y-auto whitespace-pre-wrap break-all">
|
|
567
|
+
{scaffoldRequest.code}
|
|
568
|
+
</div>
|
|
569
|
+
</div>
|
|
570
|
+
<div className="flex gap-2 mt-1">
|
|
571
|
+
<button
|
|
572
|
+
onClick={() => useChatStore.getState().sendMessage(`I approve the creation of the plugin ${scaffoldRequest.filename}. Proceed with scaffold and set approved=true.`)}
|
|
573
|
+
className="px-4 py-2 rounded-[12px] bg-amber-500 text-black text-[13px] font-700 hover:bg-amber-400 transition-all active:scale-[0.98]"
|
|
574
|
+
>
|
|
575
|
+
Approve & Install
|
|
576
|
+
</button>
|
|
577
|
+
<button
|
|
578
|
+
onClick={() => useChatStore.getState().sendMessage(`I do not approve the creation of this plugin. Please find another way or stop.`)}
|
|
579
|
+
className="px-4 py-2 rounded-[12px] bg-white/[0.05] hover:bg-white/[0.1] text-text-2 text-[13px] font-600 transition-all border border-white/10"
|
|
580
|
+
>
|
|
581
|
+
Reject
|
|
582
|
+
</button>
|
|
583
|
+
</div>
|
|
584
|
+
</div>
|
|
585
|
+
) : isPluginUI ? (
|
|
586
|
+
<div className="flex flex-col gap-2 p-4 rounded-[18px] bg-emerald-500/[0.03] border border-emerald-500/10 shadow-[0_0_20px_rgba(16,185,129,0.05)]">
|
|
587
|
+
<div className="flex items-center gap-2 mb-2">
|
|
588
|
+
<div className="w-5 h-5 rounded-full bg-emerald-500/20 flex items-center justify-center">
|
|
589
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#10B981" strokeWidth="2.5">
|
|
590
|
+
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
|
|
591
|
+
</svg>
|
|
592
|
+
</div>
|
|
593
|
+
<span className="text-[11px] font-700 uppercase tracking-wider text-emerald-400/80">Plugin UI Extension</span>
|
|
594
|
+
</div>
|
|
595
|
+
<div className="text-[14px] text-text-2/90 leading-relaxed">
|
|
596
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{message.text}</ReactMarkdown>
|
|
597
|
+
</div>
|
|
598
|
+
<div className="flex gap-2 mt-2">
|
|
599
|
+
{tryParseJson(message.text)?.actions ? (tryParseJson(message.text)!.actions as Array<{ id: string; href: string; label: string }>).map((action) => (
|
|
600
|
+
<button
|
|
601
|
+
key={action.id}
|
|
602
|
+
onClick={() => window.open(action.href, '_blank')}
|
|
603
|
+
className="px-3 py-1.5 rounded-[10px] bg-emerald-500/10 hover:bg-emerald-500/20 text-emerald-400 text-[11px] font-600 transition-all border border-emerald-500/10"
|
|
604
|
+
>
|
|
605
|
+
{action.label}
|
|
606
|
+
</button>
|
|
607
|
+
)) : null}
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
) : isHeartbeat ? (
|
|
451
611
|
<div className="flex flex-col gap-2">
|
|
452
612
|
<button
|
|
453
613
|
type="button"
|
|
@@ -660,7 +820,7 @@ export const MessageBubble = memo(function MessageBubble({ message, assistantNam
|
|
|
660
820
|
)}
|
|
661
821
|
|
|
662
822
|
{/* Action buttons */}
|
|
663
|
-
<div className={`flex items-center gap-1 mt-1.5 px-1 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-
|
|
823
|
+
<div className={`flex items-center gap-1 mt-1.5 px-1 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-all duration-200 translate-y-2 md:group-hover:translate-y-0 ${isUser ? 'justify-end' : ''}`}>
|
|
664
824
|
<button
|
|
665
825
|
onClick={handleCopy}
|
|
666
826
|
aria-label="Copy message"
|
|
@@ -12,6 +12,7 @@ import { ThinkingIndicator } from './thinking-indicator'
|
|
|
12
12
|
import { SuggestionsBar } from './suggestions-bar'
|
|
13
13
|
import { ExecApprovalCard } from './exec-approval-card'
|
|
14
14
|
import { TaskApprovalCard } from './task-approval-card'
|
|
15
|
+
import { SessionApprovalCard } from './session-approval-card'
|
|
15
16
|
import { HeartbeatMoment, ActivityMoment, isNotableTool } from './activity-moment'
|
|
16
17
|
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
17
18
|
import { useWs } from '@/hooks/use-ws'
|
|
@@ -512,7 +513,14 @@ export function MessageList({ messages, streaming, connectorFilter = null }: Pro
|
|
|
512
513
|
}
|
|
513
514
|
|
|
514
515
|
return (
|
|
515
|
-
<div
|
|
516
|
+
<div
|
|
517
|
+
key={`${sessionId}-${msg.time}-${i}`}
|
|
518
|
+
data-message-index={i}
|
|
519
|
+
style={{
|
|
520
|
+
animation: `${msg.role === 'user' ? 'msg-in-right' : 'msg-in-left'} 0.4s var(--ease-spring) both`,
|
|
521
|
+
animationDelay: `${Math.min(i * 0.05, 0.4)}s`
|
|
522
|
+
}}
|
|
523
|
+
>
|
|
516
524
|
{showDateSep && (
|
|
517
525
|
<div className="flex items-center gap-4 py-2 mb-2">
|
|
518
526
|
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
@@ -576,17 +584,32 @@ function ApprovalCards({ agentId }: { agentId?: string | null }) {
|
|
|
576
584
|
const approvals = useApprovalStore((s) => s.approvals)
|
|
577
585
|
const tasks = useAppStore((s) => s.tasks)
|
|
578
586
|
const sessionId = useAppStore((s) => s.currentSessionId)
|
|
587
|
+
const serverApprovals = useAppStore((s) => s.approvals)
|
|
579
588
|
|
|
580
589
|
const cards = Object.values(approvals).filter((a) => !agentId || a.agentId === agentId)
|
|
581
|
-
|
|
590
|
+
|
|
582
591
|
// Find tasks associated with this session that need approval
|
|
583
592
|
const pendingTasks = Object.values(tasks).filter((t) => {
|
|
584
593
|
if (!t.pendingApproval) return false
|
|
585
|
-
// Show if matches the current session OR the current agent
|
|
586
594
|
return t.sessionId === sessionId || (agentId && t.agentId === agentId)
|
|
587
595
|
})
|
|
588
596
|
|
|
589
|
-
|
|
597
|
+
// Server-side approvals (tool_access, wallet, plugin) matching this session/agent
|
|
598
|
+
// Exclude any that overlap with task-based approvals to prevent duplicates
|
|
599
|
+
const pendingTaskIds = new Set(pendingTasks.map((t) => t.id))
|
|
600
|
+
const sessionApprovalCards = Object.values(serverApprovals).filter((a) => {
|
|
601
|
+
if (a.status !== 'pending') return false
|
|
602
|
+
if (a.category === 'task_tool') return false // Already shown via TaskApprovalCard
|
|
603
|
+
if (a.category === 'tool_access') return false // Handled inline by ToolRequestBanner (with auto-continue)
|
|
604
|
+
if (a.taskId && pendingTaskIds.has(a.taskId)) return false // Dedupe with task card
|
|
605
|
+
return a.sessionId === sessionId || (agentId && a.agentId === agentId)
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
if (!cards.length && !pendingTasks.length && !sessionApprovalCards.length) return null
|
|
609
|
+
|
|
610
|
+
const handleSessionApprovalResolved = () => {
|
|
611
|
+
useAppStore.getState().loadApprovals()
|
|
612
|
+
}
|
|
590
613
|
|
|
591
614
|
return (
|
|
592
615
|
<div className="flex flex-col gap-2">
|
|
@@ -596,6 +619,9 @@ function ApprovalCards({ agentId }: { agentId?: string | null }) {
|
|
|
596
619
|
{pendingTasks.map((t) => (
|
|
597
620
|
<TaskApprovalCard key={t.id} task={t} />
|
|
598
621
|
))}
|
|
622
|
+
{sessionApprovalCards.map((a) => (
|
|
623
|
+
<SessionApprovalCard key={a.id} approval={a} onResolved={handleSessionApprovalResolved} />
|
|
624
|
+
))}
|
|
599
625
|
</div>
|
|
600
626
|
)
|
|
601
627
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import type { ApprovalRequest } from '@/types'
|
|
5
|
+
import { api } from '@/lib/api-client'
|
|
6
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
7
|
+
import { toast } from 'sonner'
|
|
8
|
+
import { getApprovalPayload, getApprovalTitle } from '@/lib/approval-display'
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
approval: ApprovalRequest
|
|
12
|
+
onResolved?: () => void
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function SessionApprovalCard({ approval, onResolved }: Props) {
|
|
16
|
+
const [resolving, setResolving] = useState(false)
|
|
17
|
+
const loadApprovals = useAppStore((s) => s.loadApprovals)
|
|
18
|
+
const loadSessions = useAppStore((s) => s.loadSessions)
|
|
19
|
+
|
|
20
|
+
const handleResolve = async (approved: boolean) => {
|
|
21
|
+
setResolving(true)
|
|
22
|
+
try {
|
|
23
|
+
await api('POST', '/approvals', { id: approval.id, approved })
|
|
24
|
+
toast.success(approved ? 'Action approved' : 'Action rejected')
|
|
25
|
+
await Promise.all([loadApprovals(), loadSessions()])
|
|
26
|
+
onResolved?.()
|
|
27
|
+
} catch (err: unknown) {
|
|
28
|
+
toast.error(err instanceof Error ? err.message : 'Failed to submit decision')
|
|
29
|
+
} finally {
|
|
30
|
+
setResolving(false)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const payload = getApprovalPayload(approval)
|
|
35
|
+
const title = getApprovalTitle(approval)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div className="my-2 rounded-[12px] border border-amber-500/20 bg-amber-500/[0.04] p-4 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
|
39
|
+
<div className="flex items-center gap-2 mb-3">
|
|
40
|
+
<div className="w-5 h-5 rounded-full bg-amber-500/20 flex items-center justify-center">
|
|
41
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" className="text-amber-400">
|
|
42
|
+
<path d="M12 9v2m0 4h.01" />
|
|
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
|
+
</svg>
|
|
45
|
+
</div>
|
|
46
|
+
<span className="text-[12px] font-700 text-amber-400 uppercase tracking-wider">Approval Required</span>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<p className="text-[13px] text-text-2 mb-2 font-600">{title}</p>
|
|
50
|
+
{approval.description && (
|
|
51
|
+
<p className="text-[12px] text-text-3/90 mb-3">{approval.description}</p>
|
|
52
|
+
)}
|
|
53
|
+
|
|
54
|
+
<div className="bg-black/30 rounded-[10px] border border-white/[0.04] p-3 mb-4 overflow-x-auto max-h-[200px] overflow-y-auto">
|
|
55
|
+
<pre className="text-[11px] font-mono text-text-2/80 leading-relaxed whitespace-pre-wrap break-all">
|
|
56
|
+
{JSON.stringify(payload, null, 2)}
|
|
57
|
+
</pre>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
<div className="flex items-center gap-2">
|
|
61
|
+
<button
|
|
62
|
+
onClick={() => handleResolve(true)}
|
|
63
|
+
disabled={resolving}
|
|
64
|
+
className="flex-1 px-4 py-2 rounded-[10px] bg-emerald-500 text-[#000] text-[12px] font-700 hover:brightness-110 active:scale-[0.98] transition-all disabled:opacity-50"
|
|
65
|
+
style={{ fontFamily: 'inherit' }}
|
|
66
|
+
>
|
|
67
|
+
{resolving ? 'Applying...' : 'Approve'}
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
onClick={() => handleResolve(false)}
|
|
71
|
+
disabled={resolving}
|
|
72
|
+
className="px-4 py-2 rounded-[10px] bg-white/[0.04] border border-white/[0.08] text-text-3 text-[12px] font-600 hover:bg-white/[0.08] active:scale-[0.98] transition-all disabled:opacity-50"
|
|
73
|
+
style={{ fontFamily: 'inherit' }}
|
|
74
|
+
>
|
|
75
|
+
Reject
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
)
|
|
80
|
+
}
|
|
@@ -135,7 +135,7 @@ export function StreamingBubble({ text, assistantName, agentAvatarSeed, agentAva
|
|
|
135
135
|
return (
|
|
136
136
|
<div
|
|
137
137
|
className="flex flex-col items-start relative pl-[44px]"
|
|
138
|
-
style={{ animation: 'msg-in-left 0.
|
|
138
|
+
style={{ animation: 'msg-in-left 0.4s var(--ease-spring) both' }}
|
|
139
139
|
>
|
|
140
140
|
<div className="absolute left-[4px] top-0 relative">
|
|
141
141
|
{agentName ? <AgentAvatar seed={agentAvatarSeed || null} avatarUrl={agentAvatarUrl} name={agentName} size={28} /> : <AiAvatar size="sm" mood={streamPhase === 'tool' ? 'tool' : 'thinking'} />}
|
|
@@ -160,13 +160,14 @@ export function StreamingBubble({ text, assistantName, agentAvatarSeed, agentAva
|
|
|
160
160
|
{text && thinkingText && (
|
|
161
161
|
<div className="max-w-[85%] md:max-w-[72%] mb-2">
|
|
162
162
|
<details className="group rounded-[12px] border border-purple-500/15 bg-purple-500/[0.04]">
|
|
163
|
-
<summary className="flex items-center gap-2 px-3.5 py-2.5 cursor-pointer select-none list-none [&::-webkit-details-marker]:hidden">
|
|
164
|
-
<
|
|
163
|
+
<summary className="flex items-center gap-2 px-3.5 py-2.5 cursor-pointer select-none list-none [&::-webkit-details-marker]:hidden relative overflow-hidden group-open:rounded-b-none">
|
|
164
|
+
<div className="absolute inset-0 bg-purple-500/5 opacity-0 group-hover:opacity-100 transition-opacity" style={{ animation: 'pulse-subtle 3s ease-in-out infinite' }} />
|
|
165
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-purple-400/60 shrink-0 transition-transform group-open:rotate-90 relative z-10">
|
|
165
166
|
<polyline points="9 18 15 12 9 6" />
|
|
166
167
|
</svg>
|
|
167
|
-
<span className="text-[11px] font-600 text-purple-400/70 uppercase tracking-[0.05em]">Thinking</span>
|
|
168
|
+
<span className="text-[11px] font-600 text-purple-400/70 uppercase tracking-[0.05em] relative z-10">Thinking</span>
|
|
168
169
|
</summary>
|
|
169
|
-
<div className="px-3.5 pb-3 pt-1 max-h-[300px] overflow-y-auto">
|
|
170
|
+
<div className="px-3.5 pb-3 pt-1 max-h-[300px] overflow-y-auto border-t border-white/[0.04]">
|
|
170
171
|
<div className="text-[13px] leading-[1.6] text-text-3/70 whitespace-pre-wrap break-words">
|
|
171
172
|
{thinkingText}
|
|
172
173
|
</div>
|