@swarmclawai/swarmclaw 0.6.7 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +82 -39
- 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 +19 -5
- package/src/app/api/approvals/route.ts +22 -0
- package/src/app/api/chatrooms/[id]/chat/route.ts +4 -0
- package/src/app/api/clawhub/install/route.ts +2 -2
- package/src/app/api/eval/run/route.ts +37 -0
- package/src/app/api/eval/scenarios/route.ts +24 -0
- package/src/app/api/eval/suite/route.ts +29 -0
- 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/graph/route.ts +46 -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/sessions/[id]/checkpoints/route.ts +31 -0
- package/src/app/api/sessions/[id]/restore/route.ts +36 -0
- package/src/app/api/settings/route.ts +62 -0
- package/src/app/api/setup/doctor/route.ts +22 -5
- package/src/app/api/souls/[id]/route.ts +65 -0
- package/src/app/api/souls/route.ts +70 -0
- package/src/app/api/tasks/[id]/approve/route.ts +4 -3
- package/src/app/api/tasks/[id]/route.ts +16 -3
- package/src/app/api/tasks/route.ts +10 -2
- package/src/app/api/usage/route.ts +9 -2
- package/src/app/globals.css +27 -0
- package/src/app/page.tsx +10 -5
- package/src/cli/index.js +37 -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 +112 -34
- package/src/components/agents/inspector-panel.tsx +1 -1
- package/src/components/agents/soul-library-picker.tsx +84 -13
- package/src/components/auth/access-key-gate.tsx +63 -54
- package/src/components/auth/user-picker.tsx +37 -32
- package/src/components/chat/activity-moment.tsx +2 -0
- 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/checkpoint-timeline.tsx +112 -0
- 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 +46 -4
- package/src/components/chat/session-approval-card.tsx +80 -0
- package/src/components/chat/session-debug-panel.tsx +106 -84
- package/src/components/chat/streaming-bubble.tsx +6 -5
- package/src/components/chat/task-approval-card.tsx +78 -0
- package/src/components/chat/thinking-indicator.tsx +48 -12
- package/src/components/chat/tool-call-bubble.tsx +3 -0
- 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 +37 -7
- package/src/components/home/home-view.tsx +54 -24
- 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 +87 -19
- 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-browser.tsx +73 -45
- package/src/components/memory/memory-graph-view.tsx +203 -0
- package/src/components/memory/memory-list.tsx +20 -13
- package/src/components/plugins/plugin-list.tsx +214 -60
- 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 +28 -9
- 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/hint-tip.tsx +31 -0
- 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 +149 -4
- 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 +224 -0
- 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 +72 -48
- 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 +319 -74
- package/src/lib/server/chatroom-helpers.ts +63 -5
- package/src/lib/server/chatroom-orchestration.ts +74 -0
- package/src/lib/server/clawhub-client.ts +82 -6
- package/src/lib/server/connectors/manager.ts +27 -1
- package/src/lib/server/context-manager.ts +132 -50
- package/src/lib/server/cost.test.ts +73 -0
- package/src/lib/server/cost.ts +165 -34
- package/src/lib/server/daemon-state.ts +112 -1
- package/src/lib/server/data-dir.ts +18 -1
- package/src/lib/server/eval/runner.ts +126 -0
- package/src/lib/server/eval/scenarios.ts +218 -0
- package/src/lib/server/eval/scorer.ts +96 -0
- package/src/lib/server/eval/store.ts +37 -0
- package/src/lib/server/eval/types.ts +48 -0
- package/src/lib/server/execution-log.ts +12 -8
- package/src/lib/server/guardian.ts +34 -0
- package/src/lib/server/heartbeat-service.ts +53 -1
- package/src/lib/server/integrity-monitor.ts +208 -0
- package/src/lib/server/langgraph-checkpoint.ts +10 -0
- package/src/lib/server/link-understanding.ts +55 -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 +115 -16
- 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 +193 -19
- package/src/lib/server/memory-retrieval.test.ts +56 -0
- package/src/lib/server/mmr.ts +73 -0
- package/src/lib/server/orchestrator-lg.ts +7 -1
- package/src/lib/server/orchestrator.ts +4 -3
- package/src/lib/server/plugins.ts +662 -132
- package/src/lib/server/process-manager.ts +18 -0
- package/src/lib/server/query-expansion.ts +57 -0
- package/src/lib/server/queue.ts +280 -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 +32 -2
- 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 +95 -33
- package/src/lib/server/session-tools/index.ts +217 -138
- package/src/lib/server/session-tools/memory.ts +154 -239
- 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 +78 -0
- 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 +181 -327
- package/src/lib/server/storage.ts +36 -0
- package/src/lib/server/stream-agent-chat.ts +348 -242
- 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 +24 -5
- package/src/lib/server/tool-retry.ts +62 -0
- package/src/lib/server/transcript-repair.ts +72 -0
- package/src/lib/setup-defaults.ts +1 -0
- package/src/lib/tasks.ts +7 -1
- package/src/lib/tool-definitions.ts +24 -23
- package/src/lib/validation/schemas.ts +13 -0
- package/src/lib/view-routes.ts +2 -23
- package/src/stores/use-app-store.ts +23 -1
- package/src/types/index.ts +155 -10
|
@@ -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"
|
|
@@ -11,6 +11,8 @@ import { StreamingBubble } from './streaming-bubble'
|
|
|
11
11
|
import { ThinkingIndicator } from './thinking-indicator'
|
|
12
12
|
import { SuggestionsBar } from './suggestions-bar'
|
|
13
13
|
import { ExecApprovalCard } from './exec-approval-card'
|
|
14
|
+
import { TaskApprovalCard } from './task-approval-card'
|
|
15
|
+
import { SessionApprovalCard } from './session-approval-card'
|
|
14
16
|
import { HeartbeatMoment, ActivityMoment, isNotableTool } from './activity-moment'
|
|
15
17
|
import { useApprovalStore } from '@/stores/use-approval-store'
|
|
16
18
|
import { useWs } from '@/hooks/use-ws'
|
|
@@ -511,7 +513,14 @@ export function MessageList({ messages, streaming, connectorFilter = null }: Pro
|
|
|
511
513
|
}
|
|
512
514
|
|
|
513
515
|
return (
|
|
514
|
-
<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
|
+
>
|
|
515
524
|
{showDateSep && (
|
|
516
525
|
<div className="flex items-center gap-4 py-2 mb-2">
|
|
517
526
|
<div className="flex-1 h-px bg-white/[0.06]" />
|
|
@@ -573,13 +582,46 @@ export function MessageList({ messages, streaming, connectorFilter = null }: Pro
|
|
|
573
582
|
|
|
574
583
|
function ApprovalCards({ agentId }: { agentId?: string | null }) {
|
|
575
584
|
const approvals = useApprovalStore((s) => s.approvals)
|
|
585
|
+
const tasks = useAppStore((s) => s.tasks)
|
|
586
|
+
const sessionId = useAppStore((s) => s.currentSessionId)
|
|
587
|
+
const serverApprovals = useAppStore((s) => s.approvals)
|
|
588
|
+
|
|
576
589
|
const cards = Object.values(approvals).filter((a) => !agentId || a.agentId === agentId)
|
|
577
|
-
|
|
590
|
+
|
|
591
|
+
// Find tasks associated with this session that need approval
|
|
592
|
+
const pendingTasks = Object.values(tasks).filter((t) => {
|
|
593
|
+
if (!t.pendingApproval) return false
|
|
594
|
+
return t.sessionId === sessionId || (agentId && t.agentId === agentId)
|
|
595
|
+
})
|
|
596
|
+
|
|
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
|
+
}
|
|
613
|
+
|
|
578
614
|
return (
|
|
579
|
-
|
|
615
|
+
<div className="flex flex-col gap-2">
|
|
580
616
|
{cards.map((a) => (
|
|
581
617
|
<ExecApprovalCard key={a.id} approval={a} />
|
|
582
618
|
))}
|
|
583
|
-
|
|
619
|
+
{pendingTasks.map((t) => (
|
|
620
|
+
<TaskApprovalCard key={t.id} task={t} />
|
|
621
|
+
))}
|
|
622
|
+
{sessionApprovalCards.map((a) => (
|
|
623
|
+
<SessionApprovalCard key={a.id} approval={a} onResolved={handleSessionApprovalResolved} />
|
|
624
|
+
))}
|
|
625
|
+
</div>
|
|
584
626
|
)
|
|
585
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
|
+
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { useState, useEffect } from 'react'
|
|
4
4
|
import type { Message } from '@/types'
|
|
5
5
|
import { IconButton } from '@/components/shared/icon-button'
|
|
6
|
+
import { CheckpointTimeline } from './checkpoint-timeline'
|
|
7
|
+
import { useAppStore } from '@/stores/use-app-store'
|
|
6
8
|
|
|
7
9
|
interface Props {
|
|
8
10
|
messages: Message[]
|
|
@@ -60,23 +62,16 @@ const TYPE_COLORS: Record<EventType, string> = {
|
|
|
60
62
|
tool_call: '#8B5CF6',
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
const TYPE_ICONS: Record<EventType, string> = {
|
|
64
|
-
user: 'U',
|
|
65
|
-
assistant: 'AI',
|
|
66
|
-
delegation: 'D',
|
|
67
|
-
agent_result: 'R',
|
|
68
|
-
system: 'S',
|
|
69
|
-
error: '!',
|
|
70
|
-
tool_call: 'T',
|
|
71
|
-
}
|
|
72
|
-
|
|
73
65
|
function fmtTime(ts: number) {
|
|
74
66
|
return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })
|
|
75
67
|
}
|
|
76
68
|
|
|
77
69
|
export function SessionDebugPanel({ messages, open, onClose }: Props) {
|
|
70
|
+
const [tab, setTab] = useState<'log' | 'timeline'>('log')
|
|
78
71
|
const [filter, setFilter] = useState<EventType | 'all'>('all')
|
|
79
72
|
const [expandedIdx, setExpandedIdx] = useState<number | null>(null)
|
|
73
|
+
|
|
74
|
+
const currentSessionId = useAppStore((s) => s.currentSessionId)
|
|
80
75
|
|
|
81
76
|
const events = messages.map(classifyMessage)
|
|
82
77
|
const filtered = filter === 'all' ? events : events.filter((e) => e.type === filter)
|
|
@@ -105,8 +100,23 @@ export function SessionDebugPanel({ messages, open, onClose }: Props) {
|
|
|
105
100
|
<path d="M18 20V4" />
|
|
106
101
|
<path d="M6 20v-4" />
|
|
107
102
|
</svg>
|
|
108
|
-
<span className="font-display text-[16px] font-600 tracking-[-0.02em] flex-1">Session
|
|
109
|
-
|
|
103
|
+
<span className="font-display text-[16px] font-600 tracking-[-0.02em] flex-1">Session X-Ray</span>
|
|
104
|
+
|
|
105
|
+
<div className="flex bg-white/[0.04] p-0.5 rounded-[8px] mr-2">
|
|
106
|
+
<button
|
|
107
|
+
onClick={() => setTab('log')}
|
|
108
|
+
className={`px-3 py-1 rounded-[6px] text-[11px] font-600 transition-all ${tab === 'log' ? 'bg-white/[0.08] text-text shadow-sm' : 'text-text-3 hover:text-text-2'}`}
|
|
109
|
+
>
|
|
110
|
+
Event Log
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
onClick={() => setTab('timeline')}
|
|
114
|
+
className={`px-3 py-1 rounded-[6px] text-[11px] font-600 transition-all ${tab === 'timeline' ? 'bg-accent-soft text-accent-bright' : 'text-text-3 hover:text-text-2'}`}
|
|
115
|
+
>
|
|
116
|
+
Time Travel
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
|
|
110
120
|
<IconButton onClick={onClose} aria-label="Close debug panel">
|
|
111
121
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
|
|
112
122
|
<line x1="18" y1="6" x2="6" y2="18" />
|
|
@@ -115,82 +125,94 @@ export function SessionDebugPanel({ messages, open, onClose }: Props) {
|
|
|
115
125
|
</IconButton>
|
|
116
126
|
</div>
|
|
117
127
|
|
|
118
|
-
{
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
onClick={() => setFilter(f.id)}
|
|
124
|
-
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all border whitespace-nowrap
|
|
125
|
-
${filter === f.id
|
|
126
|
-
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
127
|
-
: 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
|
|
128
|
-
style={{ fontFamily: 'inherit' }}
|
|
129
|
-
>
|
|
130
|
-
{f.label}
|
|
131
|
-
</button>
|
|
132
|
-
))}
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
{/* Event timeline */}
|
|
136
|
-
<div className="flex-1 overflow-y-auto px-5 py-4">
|
|
137
|
-
<div className="relative">
|
|
138
|
-
{/* Timeline line */}
|
|
139
|
-
<div className="absolute left-[15px] top-0 bottom-0 w-px bg-white/[0.06]" />
|
|
140
|
-
|
|
141
|
-
{filtered.map((event, i) => {
|
|
142
|
-
const color = TYPE_COLORS[event.type]
|
|
143
|
-
const expanded = expandedIdx === i
|
|
144
|
-
return (
|
|
128
|
+
{tab === 'log' ? (
|
|
129
|
+
<>
|
|
130
|
+
{/* Filters */}
|
|
131
|
+
<div className="flex gap-2 px-5 py-3 border-b border-white/[0.04] overflow-x-auto shrink-0">
|
|
132
|
+
{filters.map((f) => (
|
|
145
133
|
<button
|
|
146
|
-
key={
|
|
147
|
-
onClick={() =>
|
|
148
|
-
className=
|
|
134
|
+
key={f.id}
|
|
135
|
+
onClick={() => setFilter(f.id)}
|
|
136
|
+
className={`px-3 py-1.5 rounded-[8px] text-[11px] font-600 cursor-pointer transition-all border whitespace-nowrap
|
|
137
|
+
${filter === f.id
|
|
138
|
+
? 'bg-accent-soft border-accent-bright/25 text-accent-bright'
|
|
139
|
+
: 'bg-surface border-white/[0.06] text-text-3 hover:text-text-2'}`}
|
|
140
|
+
style={{ fontFamily: 'inherit' }}
|
|
149
141
|
>
|
|
150
|
-
{
|
|
151
|
-
<div
|
|
152
|
-
className="absolute left-[10px] top-1 w-[11px] h-[11px] rounded-full border-2"
|
|
153
|
-
style={{ borderColor: color, backgroundColor: expanded ? color : 'transparent' }}
|
|
154
|
-
/>
|
|
155
|
-
|
|
156
|
-
{/* Content */}
|
|
157
|
-
<div className="flex items-center gap-2 mb-0.5">
|
|
158
|
-
<span className="text-[11px] font-700 uppercase tracking-wider" style={{ color }}>
|
|
159
|
-
{event.label}
|
|
160
|
-
</span>
|
|
161
|
-
<span className="text-[10px] text-text-3/70 font-mono">{fmtTime(event.time)}</span>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<p className={`text-[12px] text-text-3 leading-[1.5] ${expanded ? 'whitespace-pre-wrap' : 'line-clamp-2'}`}>
|
|
165
|
-
{event.detail}
|
|
166
|
-
</p>
|
|
167
|
-
|
|
168
|
-
{!expanded && event.detail.length > 150 && (
|
|
169
|
-
<span className="text-[11px] text-accent-bright/60 mt-1 inline-block">click to expand</span>
|
|
170
|
-
)}
|
|
142
|
+
{f.label}
|
|
171
143
|
</button>
|
|
172
|
-
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
{
|
|
176
|
-
|
|
177
|
-
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
{/* Event timeline */}
|
|
148
|
+
<div className="flex-1 overflow-y-auto px-5 py-4">
|
|
149
|
+
<div className="relative">
|
|
150
|
+
{/* Timeline line */}
|
|
151
|
+
<div className="absolute left-[15px] top-0 bottom-0 w-px bg-white/[0.06]" />
|
|
152
|
+
|
|
153
|
+
{filtered.map((event, i) => {
|
|
154
|
+
const color = TYPE_COLORS[event.type]
|
|
155
|
+
const expanded = expandedIdx === i
|
|
156
|
+
return (
|
|
157
|
+
<button
|
|
158
|
+
key={i}
|
|
159
|
+
onClick={() => setExpandedIdx(expanded ? null : i)}
|
|
160
|
+
className="w-full text-left relative pl-10 pb-4 group cursor-pointer"
|
|
161
|
+
>
|
|
162
|
+
{/* Dot */}
|
|
163
|
+
<div
|
|
164
|
+
className="absolute left-[10px] top-1 w-[11px] h-[11px] rounded-full border-2"
|
|
165
|
+
style={{ borderColor: color, backgroundColor: expanded ? color : 'transparent' }}
|
|
166
|
+
/>
|
|
167
|
+
|
|
168
|
+
{/* Content */}
|
|
169
|
+
<div className="flex items-center gap-2 mb-0.5">
|
|
170
|
+
<span className="text-[11px] font-700 uppercase tracking-wider" style={{ color }}>
|
|
171
|
+
{event.label}
|
|
172
|
+
</span>
|
|
173
|
+
<span className="text-[10px] text-text-3/70 font-mono">{fmtTime(event.time)}</span>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
<p className={`text-[12px] text-text-3 leading-[1.5] ${expanded ? 'whitespace-pre-wrap' : 'line-clamp-2'}`}>
|
|
177
|
+
{event.detail}
|
|
178
|
+
</p>
|
|
179
|
+
|
|
180
|
+
{!expanded && event.detail.length > 150 && (
|
|
181
|
+
<span className="text-[11px] text-accent-bright/60 mt-1 inline-block">click to expand</span>
|
|
182
|
+
)}
|
|
183
|
+
</button>
|
|
184
|
+
)
|
|
185
|
+
})}
|
|
186
|
+
|
|
187
|
+
{filtered.length === 0 && (
|
|
188
|
+
<p className="text-center text-[13px] text-text-3 py-12">No events matching filter</p>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{/* Stats bar */}
|
|
194
|
+
<div className="flex items-center gap-4 px-5 py-3 border-t border-white/[0.06] shrink-0">
|
|
195
|
+
{(['delegation', 'agent_result', 'error'] as EventType[]).map((type) => {
|
|
196
|
+
const count = events.filter((e) => e.type === type).length
|
|
197
|
+
if (!count) return null
|
|
198
|
+
return (
|
|
199
|
+
<span key={type} className="flex items-center gap-1.5 text-[11px] font-mono" style={{ color: TYPE_COLORS[type] }}>
|
|
200
|
+
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: TYPE_COLORS[type] }} />
|
|
201
|
+
{count} {type === 'delegation' ? 'delegations' : type === 'agent_result' ? 'results' : 'errors'}
|
|
202
|
+
</span>
|
|
203
|
+
)
|
|
204
|
+
})}
|
|
205
|
+
</div>
|
|
206
|
+
</>
|
|
207
|
+
) : (
|
|
208
|
+
<div className="flex-1 overflow-y-auto">
|
|
209
|
+
{currentSessionId ? (
|
|
210
|
+
<CheckpointTimeline sessionId={currentSessionId} />
|
|
211
|
+
) : (
|
|
212
|
+
<div className="p-12 text-center text-text-3">No active session</div>
|
|
213
|
+
)}
|
|
178
214
|
</div>
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
{/* Stats bar */}
|
|
182
|
-
<div className="flex items-center gap-4 px-5 py-3 border-t border-white/[0.06] shrink-0">
|
|
183
|
-
{(['delegation', 'agent_result', 'error'] as EventType[]).map((type) => {
|
|
184
|
-
const count = events.filter((e) => e.type === type).length
|
|
185
|
-
if (!count) return null
|
|
186
|
-
return (
|
|
187
|
-
<span key={type} className="flex items-center gap-1.5 text-[11px] font-mono" style={{ color: TYPE_COLORS[type] }}>
|
|
188
|
-
<span className="w-2 h-2 rounded-full" style={{ backgroundColor: TYPE_COLORS[type] }} />
|
|
189
|
-
{count} {type === 'delegation' ? 'delegations' : type === 'agent_result' ? 'results' : 'errors'}
|
|
190
|
-
</span>
|
|
191
|
-
)
|
|
192
|
-
})}
|
|
193
|
-
</div>
|
|
215
|
+
)}
|
|
194
216
|
</div>
|
|
195
217
|
)
|
|
196
218
|
}
|
|
@@ -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>
|