@zhin.js/adapter-sandbox 7.0.12 → 7.0.15

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.
@@ -1,42 +1,79 @@
1
1
  // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import React, { useState, useEffect, useRef } from 'react';
4
- import { cn, resolveMediaSrc, pickMediaRawUrl, } from '@zhin.js/client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useState, useEffect, useRef, useMemo, useCallback } from 'react';
4
+ import { cn, CodeBlock, MarkdownContent, resolveMediaSrc, pickMediaRawUrl, } from '@zhin.js/client';
5
5
  import { buildSandboxWebSocketUrl } from './sandboxTransport';
6
- import { User, Bot, Users, Trash2, Send, Hash, MessageSquare, Wifi, WifiOff, Smile, Image, X, Check, Info, Search, UserPlus, Bell, Video, Music, } from 'lucide-react';
6
+ import { User, Bot, Users, Trash2, Send, Hash, MessageSquare, Wifi, WifiOff, Smile, Image, X, Check, Info, Search, Video, Music, Plus, PanelRight, ExternalLink, RefreshCw, Sparkles, Activity, Wrench, Coins, CircleAlert, Gauge, SlidersHorizontal, FolderOpen, ShieldCheck, Network, Square, RotateCcw, FileDiff, Terminal, FlaskConical, ChevronDown, FileDown, ListChecks, } from 'lucide-react';
7
7
  import RichTextEditor, {} from './RichTextEditor';
8
+ import { agentStudioPath, buildAgentRunReport, buildSandboxSessionKey, cancelAgentTask, deriveAgentRunSteps, deriveTaskRuns, deriveWorkbenchArtifacts, fetchAgentTrace, loadCachedAgentTrace, mergeTraceSnapshot, presentTraceEvent, summarizeTrace, saveCachedAgentTrace, } from './agentTrace.js';
9
+ import { createDefaultPlaygroundState, loadPlaygroundState, savePlaygroundState, } from './playgroundState.js';
8
10
  export default function Sandbox() {
9
- const [messages, setMessages] = useState([]);
10
- const [channels, setChannels] = useState([
11
- { id: 'user_1001', name: '测试用户', type: 'private', unread: 0 },
12
- { id: 'group_2001', name: '测试群组', type: 'group', unread: 0 },
13
- { id: 'channel_3001', name: '测试频道', type: 'channel', unread: 0 }
14
- ]);
11
+ const [initialState] = useState(() => loadPlaygroundState());
12
+ const [messages, setMessages] = useState(() => [...initialState.messages]);
13
+ const [channels, setChannels] = useState(() => [...initialState.sessions]);
15
14
  const [faceList, setFaceList] = useState([]);
16
- const [activeChannel, setActiveChannel] = useState(channels[0]);
15
+ const [activeChannel, setActiveChannel] = useState(() => (initialState.sessions.find((session) => session.id === initialState.activeSessionId && (initialState.activeSessionType === undefined || session.type === initialState.activeSessionType))
16
+ ?? initialState.sessions[0]
17
+ ?? createDefaultPlaygroundState().sessions[0]));
17
18
  const [inputText, setInputText] = useState('');
18
- const [endpointId, setBotName] = useState('ProcessEndpoint');
19
+ const [endpointId, setBotName] = useState('sandbox-bot');
19
20
  const [connected, setConnected] = useState(false);
21
+ const [canExecute, setCanExecute] = useState(true);
22
+ const [persistenceStatus, setPersistenceStatus] = useState('saved');
23
+ const [transportNotice, setTransportNotice] = useState(null);
24
+ const [shellIsolation, setShellIsolation] = useState(null);
25
+ const [inspectorView, setInspectorView] = useState('runs');
26
+ const [expandedArtifact, setExpandedArtifact] = useState(null);
27
+ const [stoppingTask, setStoppingTask] = useState(false);
28
+ const [inlineRunExpanded, setInlineRunExpanded] = useState(false);
20
29
  const [showFacePicker, setShowFacePicker] = useState(false);
21
30
  /** 输入区:插入图片 / 视频 / 音频 URL */
22
31
  const [mediaPanel, setMediaPanel] = useState(null);
23
32
  const [mediaUrl, setMediaUrl] = useState('');
24
- const [showAtPicker, setShowAtPicker] = useState(false);
25
33
  const [atPopoverPosition, setAtPopoverPosition] = useState(null);
26
34
  const [atSearchQuery, setAtSearchQuery] = useState('');
27
35
  const [faceSearchQuery, setFaceSearchQuery] = useState('');
28
- const [atUserName, setAtUserName] = useState('');
29
36
  const [atSuggestions] = useState([
30
- { id: '10001', name: '张三' }, { id: '10002', name: '李四' }, { id: '10003', name: '王五' },
31
- { id: '10004', name: '赵六' }, { id: '10005', name: '测试用户' }, { id: '10086', name: 'Admin' },
32
- { id: '10010', name: 'Test User' }
37
+ { id: 'actor-owner', name: '当前用户' }, { id: 'actor-reviewer', name: '审阅者' },
38
+ { id: 'actor-operator', name: '协作者' }, { id: 'actor-bot', name: 'Sandbox Agent' }
33
39
  ]);
34
40
  const [previewSegments, setPreviewSegments] = useState([]);
41
+ const [composerMode, setComposerMode] = useState('write');
35
42
  const [showChannelList, setShowChannelList] = useState(false);
36
- const [viewMode, setViewMode] = useState('chat');
43
+ const [showInspector, setShowInspector] = useState(false);
44
+ const [showRunSettings, setShowRunSettings] = useState(false);
45
+ const [showNewSession, setShowNewSession] = useState(false);
46
+ const [newSessionName, setNewSessionName] = useState('');
47
+ const [newSessionScope, setNewSessionScope] = useState('private');
48
+ const [confirmClear, setConfirmClear] = useState(false);
49
+ const [trace, setTrace] = useState(null);
50
+ const [traceLoading, setTraceLoading] = useState(true);
51
+ const [traceNotice, setTraceNotice] = useState(null);
37
52
  const messagesEndRef = useRef(null);
38
53
  const wsRef = useRef(null);
39
54
  const editorRef = useRef(null);
55
+ const traceRef = useRef(null);
56
+ const activeChannelRef = useRef(activeChannel);
57
+ const endpointIdRef = useRef(endpointId);
58
+ const sessionKey = useMemo(() => buildSandboxSessionKey(endpointId, activeChannel.type, activeChannel.id), [activeChannel.id, activeChannel.type, endpointId]);
59
+ const traceSummary = useMemo(() => summarizeTrace(trace), [trace]);
60
+ const taskRuns = useMemo(() => deriveTaskRuns(trace), [trace]);
61
+ const workbenchArtifacts = useMemo(() => deriveWorkbenchArtifacts(trace), [trace]);
62
+ const currentTask = taskRuns.find((run) => run.status === 'running') ?? taskRuns[0];
63
+ const currentRunSteps = useMemo(() => deriveAgentRunSteps(trace, currentTask), [currentTask, trace]);
64
+ const currentRunArtifacts = useMemo(() => workbenchArtifacts.filter((artifact) => (artifact.turnId === currentTask?.turnId && artifact.runtimeId === currentTask?.runtimeId)), [currentTask?.runtimeId, currentTask?.turnId, workbenchArtifacts]);
65
+ useEffect(() => { activeChannelRef.current = activeChannel; }, [activeChannel]);
66
+ useEffect(() => { endpointIdRef.current = endpointId; }, [endpointId]);
67
+ useEffect(() => { setInlineRunExpanded(currentTask?.status === 'running'); }, [currentTask?.id]);
68
+ useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [currentTask?.id]);
69
+ useEffect(() => {
70
+ setPersistenceStatus(savePlaygroundState({
71
+ activeSessionId: activeChannel.id,
72
+ activeSessionType: activeChannel.type,
73
+ sessions: channels,
74
+ messages,
75
+ }) ? 'saved' : 'error');
76
+ }, [activeChannel.id, channels, messages]);
40
77
  const fetchFaceList = async () => {
41
78
  try {
42
79
  const res = await fetch('https://face.viki.moe/metadata.json');
@@ -48,6 +85,35 @@ export default function Sandbox() {
48
85
  };
49
86
  useEffect(() => { fetchFaceList(); }, []);
50
87
  const handleInboundPayload = (data) => {
88
+ if (data.type === 'ready') {
89
+ setBotName(data.endpoint || data.bot || 'sandbox-bot');
90
+ setCanExecute(data.canExecute !== false);
91
+ setTransportNotice(data.canExecute === false ? '当前 Token 只有演示权限,任务运行已禁用。' : null);
92
+ if (data.shellIsolation) {
93
+ setShellIsolation({
94
+ available: data.shellIsolation.available === true,
95
+ provider: data.shellIsolation.provider || 'docker',
96
+ message: data.shellIsolation.message || '未检测到隔离执行环境',
97
+ });
98
+ }
99
+ if (data.workingDirectory?.trim()) {
100
+ const workingDirectory = data.workingDirectory.trim();
101
+ setChannels((current) => current.map((session) => session.runConfig.workingDirectory
102
+ ? session
103
+ : { ...session, runConfig: { ...session.runConfig, workingDirectory } }));
104
+ setActiveChannel((current) => current.runConfig.workingDirectory
105
+ ? current
106
+ : { ...current, runConfig: { ...current.runConfig, workingDirectory } });
107
+ }
108
+ return;
109
+ }
110
+ if (data.type === 'error') {
111
+ const notice = Array.isArray(data.content)
112
+ ? String(data.content[0]?.data?.text ?? 'Sandbox 拒绝了本次操作')
113
+ : String(data.content ?? 'Sandbox 拒绝了本次操作');
114
+ setTransportNotice(notice);
115
+ return;
116
+ }
51
117
  if (data.type === 'edit' && data.messageId) {
52
118
  const content = Array.isArray(data.content)
53
119
  ? data.content
@@ -58,29 +124,53 @@ export default function Sandbox() {
58
124
  const content = typeof data.content === 'string'
59
125
  ? parseTextToSegments(data.content)
60
126
  : Array.isArray(data.content) ? data.content : parseTextToSegments(String(data.content ?? ''));
61
- const channelName = data.type === 'private'
62
- ? `私聊-${data.bot || endpointId}`
63
- : data.type === 'group'
64
- ? `群组-${data.id}`
65
- : `频道-${data.id}`;
66
- const channelType = data.type;
127
+ const channelType = data.type === 'group' || data.type === 'channel' ? data.type : 'private';
128
+ const channelName = channelType === 'private'
129
+ ? `会话 ${data.id}`
130
+ : channelType === 'group'
131
+ ? `群组场景 ${data.id}`
132
+ : `频道场景 ${data.id}`;
67
133
  setChannels((prev) => {
68
- if (prev.some((c) => c.id === data.id))
134
+ if (prev.some((c) => c.id === data.id && c.type === channelType))
69
135
  return prev;
70
- const created = { id: data.id, name: channelName, type: channelType, unread: 0 };
136
+ const created = {
137
+ id: data.id, name: channelName, type: channelType, unread: 0,
138
+ runConfig: { ...activeChannelRef.current.runConfig },
139
+ };
71
140
  setActiveChannel(created);
72
141
  return [...prev, created];
73
142
  });
74
143
  setMessages((prev) => [...prev, {
75
144
  id: data.messageId ?? `bot_${data.timestamp}`, type: 'received', channelType,
76
145
  channelId: data.id, channelName, senderId: 'endpoint',
77
- senderName: data.bot || endpointId, content, timestamp: data.timestamp,
146
+ senderName: data.bot || endpointIdRef.current, content, timestamp: data.timestamp,
78
147
  }]);
79
148
  };
80
- const sendInteractiveAction = (payload) => {
149
+ const sendInteractiveAction = (payload, messageId) => {
150
+ const ws = wsRef.current;
151
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
152
+ setTransportNotice('Sandbox 连接尚未就绪,审批选择未发送。');
153
+ return;
154
+ }
81
155
  const segments = [{ type: 'action', data: { id: payload, payload } }];
82
- const payloadJson = JSON.stringify({ type: activeChannel.type, id: activeChannel.id, content: segments, timestamp: Date.now() });
83
- wsRef.current?.send(payloadJson);
156
+ const payloadJson = JSON.stringify({
157
+ type: activeChannel.type,
158
+ id: activeChannel.id,
159
+ content: segments,
160
+ agentRun: activeChannel.runConfig,
161
+ timestamp: Date.now(),
162
+ });
163
+ try {
164
+ ws.send(payloadJson);
165
+ if (messageId) {
166
+ setMessages((current) => current.map((message) => message.id === messageId
167
+ ? { ...message, interactionResolved: true }
168
+ : message));
169
+ }
170
+ }
171
+ catch {
172
+ setTransportNotice('审批选择发送失败,请等待连接恢复后重试。');
173
+ }
84
174
  };
85
175
  useEffect(() => {
86
176
  let closed = false;
@@ -181,6 +271,41 @@ export default function Sandbox() {
181
271
  }, []);
182
272
  useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]);
183
273
  useEffect(() => { setPreviewSegments(inputText.trim() ? parseTextToSegments(inputText) : []); }, [inputText]);
274
+ const loadTrace = useCallback(async (quiet = false) => {
275
+ if (!quiet)
276
+ setTraceLoading(true);
277
+ try {
278
+ const current = traceRef.current?.sessionKey === sessionKey ? traceRef.current : null;
279
+ let incoming = await fetchAgentTrace(sessionKey, quiet ? current?.latestSequence ?? 0 : 0);
280
+ if (quiet && current?.runtimeId && incoming.runtimeId && current.runtimeId !== incoming.runtimeId) {
281
+ incoming = await fetchAgentTrace(sessionKey, 0);
282
+ }
283
+ const merged = mergeTraceSnapshot(current, incoming);
284
+ traceRef.current = merged;
285
+ setTrace(merged);
286
+ saveCachedAgentTrace(merged);
287
+ setTraceNotice(null);
288
+ }
289
+ catch (error) {
290
+ setTraceNotice(error instanceof Error ? error.message : 'Agent Trace 暂不可用');
291
+ }
292
+ finally {
293
+ if (!quiet)
294
+ setTraceLoading(false);
295
+ }
296
+ }, [sessionKey]);
297
+ useEffect(() => {
298
+ const cached = loadCachedAgentTrace(sessionKey);
299
+ traceRef.current = cached;
300
+ setTrace(cached);
301
+ setTraceNotice(null);
302
+ void loadTrace();
303
+ const timer = window.setInterval(() => {
304
+ if (document.visibilityState === 'visible')
305
+ void loadTrace(true);
306
+ }, 2_000);
307
+ return () => window.clearInterval(timer);
308
+ }, [loadTrace]);
184
309
  const parseTextToSegments = (text) => {
185
310
  const segments = [];
186
311
  const regex = /\[@([^\]]+)\]|\[face:(\d+)\]|\[image:([^\]]+)\]|\[video:([^\]]+)\]|\[audio:([^\]]+)\]/g;
@@ -222,16 +347,20 @@ export default function Sandbox() {
222
347
  return true;
223
348
  });
224
349
  };
225
- const renderMessageSegments = (segments, isSent) => {
350
+ const renderMessageSegments = (segments, isSent, messageId, interactionResolved = false) => {
226
351
  const ring = isSent ? 'ring-1 ring-primary-foreground/25' : 'ring-1 ring-border/60';
227
352
  return segments.map((segment, index) => {
228
353
  if (typeof segment === 'string') {
229
- return _jsx("span", { children: segment.split('\n').map((part, i) => _jsxs(React.Fragment, { children: [part, i < segment.split('\n').length - 1 && _jsx("br", {})] }, i)) }, index);
354
+ return _jsx(MarkdownContent, { text: segment, className: isSent ? 'zhin-markdown--inverse' : undefined }, index);
230
355
  }
231
356
  const d = segment.data;
232
357
  switch (segment.type) {
233
358
  case 'text':
234
- return _jsx("span", { children: String(d.text ?? '').split('\n').map((part, i) => _jsxs(React.Fragment, { children: [part, i < String(d.text ?? '').split('\n').length - 1 && _jsx("br", {})] }, i)) }, index);
359
+ case 'markdown':
360
+ case 'md':
361
+ return _jsx(MarkdownContent, { text: String(d.text ?? d.content ?? ''), className: isSent ? 'zhin-markdown--inverse' : undefined }, index);
362
+ case 'code':
363
+ return _jsx(CodeBlock, { code: String(d.code ?? d.text ?? d.content ?? ''), language: String(d.language ?? d.lang ?? '') }, index);
235
364
  case 'mention':
236
365
  case 'at':
237
366
  return _jsxs("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded bg-accent text-accent-foreground text-xs mx-0.5", children: ["@", String(d.name ?? d.target ?? d.qq ?? '')] }, index);
@@ -272,9 +401,8 @@ export default function Sandbox() {
272
401
  }
273
402
  case 'keyboard': {
274
403
  const rows = d.rows ?? [];
275
- return (_jsx("div", { className: "inline-grid gap-1 my-1", children: rows.map((row, ri) => (_jsx("div", { className: "flex gap-1", children: row.map((btn) => (_jsx("button", { type: "button", disabled: btn.disabled || isSent, onClick: () => sendInteractiveAction(btn.payload), className: cn('min-w-9 h-9 rounded-md border text-sm font-medium transition-colors', btn.disabled
276
- ? 'opacity-50 cursor-not-allowed'
277
- : 'hover:bg-accent active:scale-95'), children: btn.label }, btn.payload))) }, ri))) }, index));
404
+ const resolved = Boolean(messageId && interactionResolved);
405
+ return (_jsxs("div", { className: "agent-playground-approval-actions", children: [rows.map((row, ri) => (_jsx("div", { children: row.map((btn) => (_jsx("button", { type: "button", disabled: btn.disabled || isSent || resolved, onClick: () => sendInteractiveAction(btn.payload, messageId), className: cn((btn.style === 'primary' || /^允许/u.test(btn.label)) && 'is-primary', (btn.style === 'danger' || /拒绝|取消/u.test(btn.label)) && 'is-danger'), children: btn.label }, btn.payload))) }, ri))), resolved && _jsxs("small", { children: [_jsx(Check, { size: 12 }), "\u5DF2\u63D0\u4EA4\u672C\u6B21\u9009\u62E9"] })] }, index));
278
406
  }
279
407
  default:
280
408
  return _jsxs("span", { className: "text-xs opacity-70", children: ["[", segment.type, "]"] }, index);
@@ -282,30 +410,47 @@ export default function Sandbox() {
282
410
  });
283
411
  };
284
412
  const handleSendMessage = (text, segments) => {
285
- if (!hasRenderableSegments(segments))
413
+ if (!canExecute || !hasRenderableSegments(segments))
286
414
  return;
287
415
  const newMessage = { id: `msg_${Date.now()}`, type: 'sent', channelType: activeChannel.type, channelId: activeChannel.id, channelName: activeChannel.name, senderId: 'test_user', senderName: '测试用户', content: segments, timestamp: Date.now() };
288
416
  setMessages((prev) => [...prev, newMessage]);
289
417
  setInputText('');
290
418
  setPreviewSegments([]);
419
+ setComposerMode('write');
291
420
  editorRef.current?.clear();
292
421
  // Stamp type+id so Host sandbox endpoint preserves channel context for outbound replies.
293
- const payload = JSON.stringify({ type: activeChannel.type, id: activeChannel.id, content: segments, timestamp: Date.now() });
422
+ const payload = JSON.stringify({
423
+ type: activeChannel.type,
424
+ id: activeChannel.id,
425
+ messageId: newMessage.id,
426
+ content: segments,
427
+ agentRun: activeChannel.runConfig,
428
+ timestamp: Date.now(),
429
+ });
294
430
  wsRef.current?.send(payload);
295
431
  };
296
- const clearMessages = () => { if (confirm('确定清空所有消息记录?'))
297
- setMessages([]); };
298
- const switchChannel = (channel) => { setViewMode('chat'); setActiveChannel(channel); setChannels((prev) => prev.map((c) => c.id === channel.id ? { ...c, unread: 0 } : c)); if (window.innerWidth < 768)
432
+ const clearMessages = () => {
433
+ setMessages((current) => current.filter((message) => !(message.channelId === activeChannel.id && message.channelType === activeChannel.type)));
434
+ setConfirmClear(false);
435
+ };
436
+ const switchChannel = (channel) => { setActiveChannel(channel); setChannels((prev) => prev.map((c) => c.id === channel.id && c.type === channel.type ? { ...c, unread: 0 } : c)); if (window.innerWidth < 900)
299
437
  setShowChannelList(false); };
300
- const addChannel = () => {
301
- const types = ['private', 'group', 'channel'];
302
- const type = types[Math.floor(Math.random() * types.length)];
303
- const name = prompt(`请输入频道名称:`);
304
- if (name) {
305
- const nc = { id: `${type}_${Date.now()}`, name, type, unread: 0 };
306
- setChannels((p) => [...p, nc]);
307
- setActiveChannel(nc);
308
- }
438
+ const updateRunConfig = (patch) => {
439
+ const next = { ...activeChannel, runConfig: { ...activeChannel.runConfig, ...patch } };
440
+ setActiveChannel(next);
441
+ setChannels((sessions) => sessions.map((session) => session.id === activeChannel.id && session.type === activeChannel.type ? next : session));
442
+ };
443
+ const addSession = () => {
444
+ const label = newSessionName.trim() || '未命名试验';
445
+ const id = `${newSessionScope}-${Date.now().toString(36)}`;
446
+ const session = {
447
+ id, name: label, type: newSessionScope, unread: 0,
448
+ runConfig: { ...activeChannel.runConfig },
449
+ };
450
+ setChannels((current) => [...current, session]);
451
+ setActiveChannel(session);
452
+ setNewSessionName('');
453
+ setShowNewSession(false);
309
454
  };
310
455
  const getChannelIcon = (type) => { switch (type) {
311
456
  case 'private': return _jsx(User, { size: 16 });
@@ -327,8 +472,6 @@ export default function Sandbox() {
327
472
  setMediaUrl('');
328
473
  setMediaPanel(null);
329
474
  };
330
- const insertAtUser = () => { if (!atUserName.trim())
331
- return; editorRef.current?.insertAt(atUserName.trim()); setAtUserName(''); setShowAtPicker(false); };
332
475
  const selectAtUser = (user) => { editorRef.current?.replaceAtTrigger(user.name, user.id); setAtPopoverPosition(null); setAtSearchQuery(''); };
333
476
  const handleAtTrigger = (show, searchQuery, position) => {
334
477
  if (activeChannel.type === 'private') {
@@ -349,15 +492,124 @@ export default function Sandbox() {
349
492
  return true; const q = atSearchQuery.toLowerCase(); return user.name.toLowerCase().includes(q) || user.id.toLowerCase().includes(q); });
350
493
  const handleEditorChange = (text, segments) => { setInputText(text); setPreviewSegments(segments); };
351
494
  const filteredFaces = faceList.filter(face => face.name.toLowerCase().includes(faceSearchQuery.toLowerCase()) || face.describe.toLowerCase().includes(faceSearchQuery.toLowerCase()));
352
- const channelMessages = messages.filter((msg) => msg.channelId === activeChannel.id);
353
- return (_jsxs("div", { className: "sandbox-container rounded-xl border border-border/70 bg-card/30 shadow-sm", children: [_jsxs("button", { className: "mobile-channel-toggle md:hidden", onClick: () => setShowChannelList(!showChannelList), children: [_jsx(MessageSquare, { size: 20 }), " \u9891\u9053\u5217\u8868"] }), _jsxs("div", { className: cn("channel-sidebar rounded-lg border bg-card", showChannelList && "show"), children: [_jsx("div", { className: "p-3 border-b", children: _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "p-1 rounded-md bg-secondary", children: _jsx(MessageSquare, { size: 16, className: "text-muted-foreground" }) }), _jsx("h3", { className: "font-semibold", children: "\u9891\u9053\u5217\u8868" })] }), _jsxs("span", { className: cn("inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-xs font-medium border", connected ? "bg-emerald-100 text-emerald-800 border-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400 dark:border-emerald-800" : "bg-muted text-muted-foreground"), children: [connected ? _jsx(Wifi, { size: 12 }) : _jsx(WifiOff, { size: 12 }), connected ? '已连接' : '未连接'] })] }) }), _jsxs("div", { className: "flex-1 overflow-y-auto p-2 space-y-1", children: [channels.map((channel) => {
354
- const isActive = viewMode === 'chat' && activeChannel.id === channel.id;
355
- return (_jsxs("div", { className: cn("menu-item", isActive && "active"), onClick: () => switchChannel(channel), children: [_jsx("span", { className: "shrink-0", children: getChannelIcon(channel.type) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: channel.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: channel.type === 'private' ? '私聊' : channel.type === 'group' ? '群聊' : '频道' })] }), channel.unread > 0 && _jsx("span", { className: "inline-flex items-center justify-center h-5 min-w-5 rounded-full bg-destructive text-destructive-foreground text-[10px] font-medium px-1", children: channel.unread })] }, channel.id));
356
- }), _jsxs("div", { className: "pt-2 mt-2 border-t space-y-1", children: [_jsxs("div", { className: cn("menu-item", viewMode === 'requests' && "active"), onClick: () => { setViewMode('requests'); if (window.innerWidth < 768)
357
- setShowChannelList(false); }, children: [_jsx(UserPlus, { size: 16, className: "shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium", children: "\u8BF7\u6C42" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "\u597D\u53CB/\u7FA4\u9080\u8BF7\u7B49" })] })] }), _jsxs("div", { className: cn("menu-item", viewMode === 'notices' && "active"), onClick: () => { setViewMode('notices'); if (window.innerWidth < 768)
358
- setShowChannelList(false); }, children: [_jsx(Bell, { size: 16, className: "shrink-0" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium", children: "\u901A\u77E5" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "\u7FA4\u7BA1/\u64A4\u56DE\u7B49" })] })] })] })] }), _jsx("div", { className: "p-2 border-t", children: _jsx("button", { className: "w-full py-2 px-3 rounded-md border border-dashed text-sm text-muted-foreground hover:bg-accent transition-colors", onClick: addChannel, children: "+ \u6DFB\u52A0\u9891\u9053" }) })] }), showChannelList && _jsx("div", { className: "channel-overlay md:hidden", onClick: () => setShowChannelList(false) }), _jsxs("div", { className: "chat-area", children: [viewMode === 'requests' && (_jsxs("div", { className: "rounded-lg border bg-card flex-1 flex flex-col min-h-0 overflow-hidden", children: [_jsx("div", { className: "p-3 border-b flex-shrink-0", children: _jsxs("h2", { className: "text-lg font-bold flex items-center gap-2", children: [_jsx(UserPlus, { size: 20 }), " \u8BF7\u6C42"] }) }), _jsxs("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col items-center justify-center gap-3 text-muted-foreground text-center", children: [_jsx(UserPlus, { size: 48, className: "opacity-30" }), _jsx("span", { children: "\u6C99\u76D2\u4E3A\u6A21\u62DF\u73AF\u5883\uFF0C\u6682\u65E0\u8BF7\u6C42\u6570\u636E" }), _jsxs("span", { className: "text-sm", children: ["\u5B9E\u9645\u597D\u53CB/\u7FA4\u9080\u8BF7\u7B49\u8BF7\u6C42\u8BF7\u5230\u4FA7\u8FB9\u680F ", _jsx("strong", { children: "\u673A\u5668\u4EBA" }), " \u9875\u9762\u8FDB\u5165\u5BF9\u5E94\u673A\u5668\u4EBA\u7BA1\u7406\u67E5\u770B"] })] })] })), viewMode === 'notices' && (_jsxs("div", { className: "rounded-lg border bg-card flex-1 flex flex-col min-h-0 overflow-hidden", children: [_jsx("div", { className: "p-3 border-b flex-shrink-0", children: _jsxs("h2", { className: "text-lg font-bold flex items-center gap-2", children: [_jsx(Bell, { size: 20 }), " \u901A\u77E5"] }) }), _jsxs("div", { className: "flex-1 overflow-y-auto p-4 flex flex-col items-center justify-center gap-3 text-muted-foreground text-center", children: [_jsx(Bell, { size: 48, className: "opacity-30" }), _jsx("span", { children: "\u6C99\u76D2\u4E3A\u6A21\u62DF\u73AF\u5883\uFF0C\u6682\u65E0\u901A\u77E5\u6570\u636E" }), _jsxs("span", { className: "text-sm", children: ["\u5B9E\u9645\u7FA4\u7BA1\u3001\u64A4\u56DE\u7B49\u901A\u77E5\u8BF7\u5230\u4FA7\u8FB9\u680F ", _jsx("strong", { children: "\u673A\u5668\u4EBA" }), " \u9875\u9762\u8FDB\u5165\u5BF9\u5E94\u673A\u5668\u4EBA\u7BA1\u7406\u67E5\u770B"] })] })] })), viewMode === 'chat' && (_jsxs(_Fragment, { children: [_jsx("div", { className: "rounded-lg border bg-card p-3 flex-shrink-0", children: _jsxs("div", { className: "flex justify-between items-center flex-wrap gap-2", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx("div", { className: "p-2 rounded-lg bg-secondary", children: getChannelIcon(activeChannel.type) }), _jsxs("div", { children: [_jsx("h2", { className: "text-lg font-bold", children: activeChannel.name }), _jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx("span", { children: activeChannel.id }), _jsx("span", { className: "inline-flex items-center px-1.5 py-0.5 rounded border text-[10px]", children: channelMessages.length }), _jsx("span", { children: "\u6761\u6D88\u606F" })] })] }), _jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-secondary text-secondary-foreground", children: activeChannel.type === 'private' ? '私聊' : activeChannel.type === 'group' ? '群聊' : '频道' })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("input", { value: endpointId, onChange: (e) => setBotName(e.target.value), placeholder: "\u673A\u5668\u4EBA\u540D\u79F0", className: "h-8 w-28 rounded-md border bg-transparent px-2 text-sm" }), _jsxs("button", { className: "inline-flex items-center gap-1 h-8 px-3 rounded-md bg-secondary text-secondary-foreground text-sm hover:bg-secondary/80", onClick: clearMessages, children: [_jsx(Trash2, { size: 14 }), " \u6E05\u7A7A"] })] })] }) }), _jsx("div", { className: "rounded-lg border bg-card flex-1 flex flex-col min-h-0", children: _jsx("div", { className: "flex-1 overflow-y-auto p-4", children: channelMessages.length === 0 ? (_jsxs("div", { className: "flex flex-col items-center justify-center h-full gap-3", children: [_jsx(MessageSquare, { size: 64, className: "text-muted-foreground/20" }), _jsx("span", { className: "text-muted-foreground", children: "\u6682\u65E0\u6D88\u606F\uFF0C\u5F00\u59CB\u5BF9\u8BDD\u5427\uFF01" })] })) : (_jsxs("div", { className: "space-y-2", children: [channelMessages.map((msg) => (_jsx("div", { className: cn("flex", msg.type === 'sent' ? "justify-end" : "justify-start"), children: _jsxs("div", { className: cn("max-w-[70%] p-3 rounded-2xl", msg.type === 'sent' ? "bg-primary text-primary-foreground" : "bg-muted"), children: [_jsxs("div", { className: "flex items-center gap-2 mb-1", children: [msg.type === 'received' && _jsx(Bot, { size: 14 }), msg.type === 'sent' && _jsx(User, { size: 14 }), _jsx("span", { className: "text-xs font-medium opacity-90", children: msg.senderName }), _jsx("span", { className: "text-xs opacity-70", children: new Date(msg.timestamp).toLocaleTimeString() })] }), _jsx("div", { className: "text-sm space-y-1", children: renderMessageSegments(msg.content, msg.type === 'sent') })] }) }, msg.id))), _jsx("div", { ref: messagesEndRef })] })) }) }), _jsxs("div", { className: "rounded-lg border bg-card p-3 flex-shrink-0 space-y-3", children: [_jsxs("div", { className: "flex gap-2 items-center flex-wrap", children: [_jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", showFacePicker ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setShowFacePicker(!showFacePicker); setMediaPanel(null); }, title: "\u63D2\u5165\u8868\u60C5", children: _jsx(Smile, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'image' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'image' ? null : 'image')); setShowFacePicker(false); }, title: "\u63D2\u5165\u56FE\u7247 URL", children: _jsx(Image, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'video' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'video' ? null : 'video')); setShowFacePicker(false); }, title: "\u63D2\u5165\u89C6\u9891 URL", children: _jsx(Video, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'audio' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'audio' ? null : 'audio')); setShowFacePicker(false); }, title: "\u63D2\u5165\u97F3\u9891 URL", children: _jsx(Music, { size: 16 }) }), _jsx("div", { className: "flex-1 min-w-[1rem]" }), inputText && (_jsx("button", { className: "h-8 w-8 rounded-md flex items-center justify-center hover:bg-accent transition-colors", onClick: () => { setInputText(''); setPreviewSegments([]); }, children: _jsx(X, { size: 16 }) }))] }), showFacePicker && (_jsxs("div", { className: "p-3 rounded-md border bg-muted/30 max-h-64 overflow-y-auto space-y-2", children: [_jsx("input", { value: faceSearchQuery, onChange: (e) => setFaceSearchQuery(e.target.value), placeholder: "\u641C\u7D22\u8868\u60C5...", className: "w-full h-8 rounded-md border bg-transparent px-2 text-sm" }), _jsx("div", { className: "grid grid-cols-8 gap-1", children: filteredFaces.slice(0, 80).map((face) => (_jsx("button", { onClick: () => insertFace(face.id), title: face.name, className: "w-10 h-10 rounded-md border flex items-center justify-center hover:bg-accent transition-colors", children: _jsx("img", { src: `https://face.viki.moe/apng/${face.id}.png`, alt: face.name, className: "w-8 h-8" }) }, face.id))) }), filteredFaces.length === 0 && (_jsxs("div", { className: "flex flex-col items-center gap-2 py-4", children: [_jsx(Search, { size: 32, className: "text-muted-foreground/30" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "\u672A\u627E\u5230\u5339\u914D\u7684\u8868\u60C5" })] }))] })), mediaPanel && (_jsxs("div", { className: "p-3 rounded-md border bg-muted/30 space-y-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: [mediaPanel === 'image' && '支持 http(s) 图片链接或 data URL', mediaPanel === 'video' && '支持浏览器可解码的视频直链(如 .mp4、.webm)', mediaPanel === 'audio' && '支持 .mp3、.ogg、.wav 等音频直链'] }), _jsx("input", { value: mediaUrl, onChange: (e) => setMediaUrl(e.target.value), placeholder: mediaPanel === 'image' ? '图片 URL…' : mediaPanel === 'video' ? '视频 URL…' : '音频 URL…', className: "w-full h-8 rounded-md border border-input bg-background px-2 text-sm", onKeyDown: (e) => { if (e.key === 'Enter') {
359
- e.preventDefault();
360
- commitMediaUrl();
361
- } } }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 h-8 px-3 rounded-md bg-primary text-primary-foreground text-sm disabled:opacity-50", onClick: commitMediaUrl, disabled: !mediaUrl.trim(), children: [_jsx(Check, { size: 14 }), " \u63D2\u5165\u5230\u8F93\u5165\u6846"] })] })), _jsxs("div", { className: "flex gap-2 items-start", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx(RichTextEditor, { ref: editorRef, placeholder: `向 ${activeChannel.name} 发送消息...`, onSend: handleSendMessage, onChange: handleEditorChange, onAtTrigger: handleAtTrigger, minHeight: "44px", maxHeight: "200px" }), atPopoverPosition && (_jsx("div", { className: "absolute z-50 rounded-lg border bg-popover shadow-md min-w-60 max-h-72 overflow-y-auto p-1", style: { top: `${atPopoverPosition.top}px`, left: `${atPopoverPosition.left}px` }, children: filteredAtSuggestions.length > 0 ? filteredAtSuggestions.map((user) => (_jsxs("div", { className: "flex items-center gap-2 p-2 rounded-md cursor-pointer hover:bg-accent transition-colors", onClick: () => selectAtUser(user), children: [_jsx(User, { size: 16, className: "text-muted-foreground" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: user.name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["ID: ", user.id] })] })] }, user.id))) : (_jsxs("div", { className: "flex flex-col items-center gap-2 p-4", children: [_jsx(Search, { size: 20, className: "text-muted-foreground/50" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "\u672A\u627E\u5230\u5339\u914D\u7684\u7528\u6237" })] })) }))] }), _jsxs("button", { className: "inline-flex items-center gap-1.5 h-10 px-4 rounded-md bg-primary text-primary-foreground text-sm font-medium disabled:opacity-50 transition-colors hover:bg-primary/90", onClick: () => { const c = editorRef.current?.getContent(); if (c)
362
- handleSendMessage(c.text, c.segments); }, disabled: !hasRenderableSegments(previewSegments), children: [_jsx(Send, { size: 16 }), " \u53D1\u9001"] })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap text-xs text-muted-foreground", children: [_jsx(Info, { size: 12 }), " \u5FEB\u6377\u64CD\u4F5C:", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "Enter" }), " \u53D1\u9001", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "Shift+Enter" }), " \u6362\u884C", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[@\u540D\u79F0]" }), " @\u67D0\u4EBA", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[video:URL]" }), _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[audio:URL]" })] })] })] }))] })] }));
495
+ const channelMessages = messages.filter((msg) => msg.channelId === activeChannel.id && msg.channelType === activeChannel.type);
496
+ const lastUserMessage = [...channelMessages].reverse().find((message) => message.type === 'sent');
497
+ const currentTaskMessage = currentTask?.sourceMessageId
498
+ ? channelMessages.find((message) => message.id === currentTask.sourceMessageId && message.type === 'sent')
499
+ : undefined;
500
+ const scopeLabel = activeChannel.type === 'private' ? '单用户' : activeChannel.type === 'group' ? '群组上下文' : '频道上下文';
501
+ const runPrompt = (prompt) => handleSendMessage(prompt, [{ type: 'text', data: { text: prompt } }]);
502
+ const recentTraceEvents = trace?.events.slice(-8).reverse() ?? [];
503
+ const stopActiveTask = async () => {
504
+ setStoppingTask(true);
505
+ try {
506
+ const cancelled = await cancelAgentTask(sessionKey);
507
+ setTraceNotice(cancelled ? '已发送停止请求,正在等待任务结束。' : '当前会话没有运行中的任务。');
508
+ window.setTimeout(() => void loadTrace(true), 240);
509
+ }
510
+ catch (error) {
511
+ setTraceNotice(error instanceof Error ? error.message : '无法停止任务');
512
+ setShowInspector(true);
513
+ }
514
+ finally {
515
+ setStoppingTask(false);
516
+ }
517
+ };
518
+ const retryLastTask = () => {
519
+ if (!lastUserMessage)
520
+ return;
521
+ handleSendMessage(messageText(lastUserMessage.content), [...lastUserMessage.content]);
522
+ };
523
+ const retryCurrentTask = () => {
524
+ if (!currentTaskMessage)
525
+ return;
526
+ handleSendMessage(messageText(currentTaskMessage.content), [...currentTaskMessage.content]);
527
+ };
528
+ const exportCurrentRun = () => {
529
+ if (!trace || !currentTask)
530
+ return;
531
+ const report = buildAgentRunReport(trace, {
532
+ run: currentTask,
533
+ sessionName: activeChannel.name,
534
+ taskPrompt: currentTaskMessage ? messageText(currentTaskMessage.content) : undefined,
535
+ workingDirectory: activeChannel.runConfig.workingDirectory,
536
+ safetyMode: activeChannel.runConfig.safetyMode,
537
+ approvalMode: activeChannel.runConfig.approvalMode,
538
+ networkAccess: activeChannel.runConfig.safetyMode === 'danger-full-access' || activeChannel.runConfig.networkAccess,
539
+ });
540
+ const url = URL.createObjectURL(new Blob([report], { type: 'text/markdown;charset=utf-8' }));
541
+ const link = document.createElement('a');
542
+ link.href = url;
543
+ link.download = `zhin-agent-run-${safeFileName(activeChannel.name)}-${currentTask.turnId.slice(0, 8)}.md`;
544
+ document.body.appendChild(link);
545
+ link.click();
546
+ link.remove();
547
+ window.setTimeout(() => URL.revokeObjectURL(url), 0);
548
+ };
549
+ const inlineRunCard = currentTask ? (_jsxs("article", { className: cn('agent-playground-inline-run', `is-${currentTask.status}`), "aria-live": currentTask.status === 'running' ? 'polite' : undefined, children: [_jsx("span", { className: "agent-playground-inline-run-rail", "aria-hidden": "true" }), _jsxs("div", { className: "agent-playground-inline-run-head", children: [_jsx("span", { className: "agent-playground-inline-run-icon", children: _jsx(ListChecks, { size: 17 }) }), _jsxs("div", { className: "agent-playground-inline-run-copy", children: [_jsx("span", { children: currentTask.status === 'running' ? 'Live agent run' : 'Agent run summary' }), _jsx("h3", { children: taskStatusLabel(currentTask.status) }), currentTaskMessage && _jsx("p", { children: messageText(currentTaskMessage.content) })] }), _jsxs("div", { className: "agent-playground-inline-run-actions", children: [currentTask.status === 'running' ? (_jsxs("button", { type: "button", className: "is-stop", onClick: () => void stopActiveTask(), disabled: stoppingTask, children: [_jsx(Square, { size: 12 }), stoppingTask ? '停止中' : '停止'] })) : currentTaskMessage ? (_jsxs("button", { type: "button", onClick: retryCurrentTask, children: [_jsx(RotateCcw, { size: 13 }), "\u91CD\u8BD5"] })) : null, _jsxs("button", { type: "button", onClick: exportCurrentRun, title: "\u5BFC\u51FA Markdown \u8FD0\u884C\u62A5\u544A", children: [_jsx(FileDown, { size: 13 }), "\u5BFC\u51FA"] })] })] }), _jsxs("dl", { className: "agent-playground-inline-run-metrics", children: [_jsxs("div", { children: [_jsx("dt", { children: "\u8017\u65F6" }), _jsx("dd", { children: currentTask.durationMs === undefined ? '进行中' : formatDuration(currentTask.durationMs) })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u6B65\u9AA4" }), _jsx("dd", { children: currentRunSteps.length })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5DE5\u5177" }), _jsx("dd", { children: currentTask.toolCount })] }), _jsxs("div", { children: [_jsx("dt", { children: "Token" }), _jsx("dd", { children: currentTask.tokenCount.toLocaleString() })] }), _jsxs("div", { className: cn(currentTask.problemCount > 0 && 'has-problem'), children: [_jsx("dt", { children: "\u5F02\u5E38" }), _jsx("dd", { children: currentTask.problemCount })] })] }), inlineRunExpanded && (currentRunSteps.length > 0 ? (_jsx("ol", { className: "agent-playground-inline-run-steps", children: currentRunSteps.map((step) => (_jsxs("li", { className: cn(`is-${step.status}`), children: [_jsx("i", { "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("strong", { children: step.title }), step.detail && _jsx("small", { children: step.detail })] }), _jsx("span", { children: step.durationMs === undefined ? runStepStatusLabel(step.status) : formatDuration(step.durationMs) })] }, step.id))) })) : _jsx("div", { className: "agent-playground-inline-run-empty", children: "\u6B63\u5728\u7B49\u5F85\u7B2C\u4E00\u6761\u8FD0\u884C\u4E8B\u4EF6\u2026" })), _jsxs("footer", { children: [_jsxs("button", { type: "button", "aria-expanded": inlineRunExpanded, onClick: () => setInlineRunExpanded((expanded) => !expanded), children: [_jsx(ChevronDown, { size: 13 }), inlineRunExpanded ? '收起步骤' : `查看 ${currentRunSteps.length} 个步骤`] }), _jsxs("button", { type: "button", onClick: () => { setInspectorView(currentRunArtifacts.length > 0 ? 'artifacts' : 'runs'); setShowInspector(true); }, children: [currentRunArtifacts.length > 0 ? `${currentRunArtifacts.length} 个变更与产物` : '打开运行检查器', _jsx(PanelRight, { size: 13 })] })] })] })) : null;
550
+ return (_jsxs("section", { className: "agent-playground-shell", children: [_jsxs("div", { className: "agent-playground-mobilebar", children: [_jsxs("button", { type: "button", "aria-expanded": showChannelList, onClick: () => setShowChannelList(!showChannelList), children: [_jsx(MessageSquare, { size: 17 }), "\u6D4B\u8BD5\u4F1A\u8BDD"] }), _jsx("strong", { children: "Agent \u8BD5\u9A8C\u53F0" }), _jsxs("button", { type: "button", "aria-expanded": showInspector, onClick: () => setShowInspector(!showInspector), children: [_jsx(PanelRight, { size: 17 }), "\u68C0\u67E5\u5668"] })] }), _jsxs("nav", { className: cn("channel-sidebar agent-playground-sessions", showChannelList && "show"), "aria-label": "\u6D4B\u8BD5\u4F1A\u8BDD", children: [_jsx("div", { className: "agent-playground-brand", children: _jsxs("div", { className: "flex justify-between items-center", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "agent-playground-mark", children: _jsx(Sparkles, { size: 16 }) }), _jsxs("div", { children: [_jsx("h2", { children: "Agent \u8BD5\u9A8C\u53F0" }), _jsx("small", { children: "Sandbox playground" })] })] }), _jsx("span", { className: cn("agent-playground-connection", connected && "is-online"), title: connected ? 'Sandbox WebSocket 已连接' : '正在重连 Sandbox WebSocket', children: connected ? _jsx(Wifi, { size: 12 }) : _jsx(WifiOff, { size: 12 }) })] }) }), _jsxs("div", { className: "agent-playground-section-label", children: [_jsx("span", { children: "\u6D4B\u8BD5\u4F1A\u8BDD" }), _jsx("span", { children: channels.length })] }), _jsx("div", { className: "agent-playground-session-list", children: channels.map((channel) => {
551
+ const isActive = activeChannel.id === channel.id && activeChannel.type === channel.type;
552
+ return (_jsxs("button", { type: "button", "aria-current": isActive ? 'page' : undefined, className: cn("agent-playground-session", isActive && "active"), onClick: () => switchChannel(channel), children: [_jsx("span", { className: "agent-playground-session-icon", children: getChannelIcon(channel.type) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "text-sm font-medium truncate", children: channel.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: channel.type === 'private' ? '单用户作用域' : channel.type === 'group' ? '群组作用域' : '频道作用域' })] }), channel.unread > 0 && _jsx("span", { className: "inline-flex items-center justify-center h-5 min-w-5 rounded-full bg-destructive text-destructive-foreground text-[10px] font-medium px-1", children: channel.unread })] }, `${channel.type}:${channel.id}`));
553
+ }) }), _jsx("div", { className: "agent-playground-new-session", children: showNewSession ? (_jsxs("div", { className: "agent-playground-new-form", children: [_jsx("input", { value: newSessionName, onChange: (event) => setNewSessionName(event.target.value), onKeyDown: (event) => { if (event.key === 'Enter')
554
+ addSession(); }, placeholder: "\u4F1A\u8BDD\u540D\u79F0", autoFocus: true }), _jsx("div", { className: "agent-playground-scope-picker", role: "group", "aria-label": "\u6D88\u606F\u4F5C\u7528\u57DF", children: ['private', 'group', 'channel'].map((scope) => (_jsx("button", { type: "button", "aria-pressed": newSessionScope === scope, className: cn(newSessionScope === scope && 'active'), onClick: () => setNewSessionScope(scope), children: scope === 'private' ? '单用户' : scope === 'group' ? '群组' : '频道' }, scope))) }), _jsxs("div", { className: "agent-playground-new-actions", children: [_jsx("button", { type: "button", onClick: () => setShowNewSession(false), children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "primary", onClick: addSession, children: "\u521B\u5EFA" })] })] })) : (_jsxs("button", { type: "button", className: "agent-playground-add", onClick: () => setShowNewSession(true), children: [_jsx(Plus, { size: 15 }), "\u65B0\u5EFA\u6D4B\u8BD5\u4F1A\u8BDD"] })) }), _jsxs("div", { className: "agent-playground-endpoint", children: [_jsx("span", { children: persistenceStatus === 'saved' ? '会话已持久化' : '会话保存失败' }), _jsx("strong", { children: endpointId })] })] }), showChannelList && _jsx("div", { className: "channel-overlay", onClick: () => setShowChannelList(false) }), _jsxs("main", { className: "chat-area agent-playground-main", children: [_jsxs("header", { className: "agent-playground-runbar", children: [_jsxs("div", { className: "agent-playground-run-identity", children: [_jsx("span", { className: "agent-playground-run-icon", children: getChannelIcon(activeChannel.type) }), _jsxs("div", { children: [_jsxs("div", { className: "agent-playground-run-title", children: [_jsx("h1", { children: activeChannel.name }), _jsx("span", { children: scopeLabel })] }), _jsx("code", { children: sessionKey })] })] }), _jsxs("div", { className: "agent-playground-run-actions", children: [_jsxs("span", { className: cn("agent-playground-run-state", currentTask?.status === 'running' && "is-running", currentTask?.status === 'failed' && 'has-problem'), children: [currentTask?.status === 'running' ? _jsx(Activity, { size: 14 }) : _jsx(Gauge, { size: 14 }), currentTask ? taskStatusLabel(currentTask.status) : `${channelMessages.length} 条消息`] }), currentTask?.status === 'running' ? (_jsxs("button", { type: "button", className: "agent-playground-stop", onClick: () => void stopActiveTask(), disabled: stoppingTask, "aria-label": "\u505C\u6B62\u5F53\u524D\u4EFB\u52A1", children: [_jsx(Square, { size: 13 }), stoppingTask ? '停止中' : '停止'] })) : lastUserMessage ? (_jsxs("button", { type: "button", onClick: retryLastTask, "aria-label": "\u91CD\u65B0\u8FD0\u884C\u4E0A\u4E00\u4E2A\u4EFB\u52A1", children: [_jsx(RotateCcw, { size: 14 }), "\u91CD\u8BD5"] })) : null, _jsxs("a", { href: agentStudioPath(sessionKey), children: [_jsx(ExternalLink, { size: 14 }), "Agent Studio"] }), _jsx("button", { type: "button", className: cn(showRunSettings && 'active'), "aria-expanded": showRunSettings, onClick: () => setShowRunSettings((visible) => !visible), "aria-label": "\u914D\u7F6E\u8FD0\u884C\u73AF\u5883", children: _jsx(SlidersHorizontal, { size: 16 }) }), _jsx("button", { type: "button", "aria-expanded": showInspector, onClick: () => setShowInspector(!showInspector), "aria-label": "\u5207\u6362\u8FD0\u884C\u68C0\u67E5\u5668", children: _jsx(PanelRight, { size: 16 }) }), _jsx("button", { type: "button", onClick: () => setConfirmClear(true), "aria-label": "\u6E05\u7A7A\u5F53\u524D\u4F1A\u8BDD", children: _jsx(Trash2, { size: 15 }) })] })] }), confirmClear && (_jsxs("div", { className: "agent-playground-confirm", role: "alert", children: [_jsxs("div", { children: [_jsx("strong", { children: "\u6E05\u7A7A\u5F53\u524D\u6D4B\u8BD5\u8BB0\u5F55\uFF1F" }), _jsxs("span", { children: ["\u53EA\u79FB\u9664\u201C", activeChannel.name, "\u201D\u5728\u6D4F\u89C8\u5668\u4E2D\u7684\u6D88\u606F\uFF0C\u4E0D\u5F71\u54CD Agent \u4F1A\u8BDD\u5B58\u50A8\u3002"] })] }), _jsx("button", { type: "button", onClick: () => setConfirmClear(false), children: "\u53D6\u6D88" }), _jsx("button", { type: "button", className: "danger", onClick: clearMessages, children: "\u786E\u8BA4\u6E05\u7A7A" })] })), transportNotice && (_jsxs("div", { className: "agent-playground-confirm", role: "status", children: [_jsxs("div", { children: [_jsx("strong", { children: "\u8FD0\u884C\u6682\u4E0D\u53EF\u7528" }), _jsx("span", { children: transportNotice })] }), _jsx("button", { type: "button", onClick: () => setTransportNotice(null), children: "\u77E5\u9053\u4E86" })] })), showRunSettings && (_jsxs("section", { className: "agent-playground-run-settings", "aria-label": "\u8FD0\u884C\u914D\u7F6E", children: [_jsxs("label", { className: "agent-playground-directory-field", children: [_jsxs("span", { children: [_jsx(FolderOpen, { size: 14 }), "\u5DE5\u4F5C\u76EE\u5F55"] }), _jsx("input", { value: activeChannel.runConfig.workingDirectory, onChange: (event) => updateRunConfig({ workingDirectory: event.target.value }), placeholder: "\u4F7F\u7528 Host \u9879\u76EE\u76EE\u5F55", spellCheck: false })] }), _jsxs("label", { children: [_jsxs("span", { children: [_jsx(ShieldCheck, { size: 14 }), "\u5B89\u5168\u7B56\u7565"] }), _jsxs("select", { value: activeChannel.runConfig.safetyMode, onChange: (event) => {
555
+ const safetyMode = event.target.value;
556
+ updateRunConfig({ safetyMode, ...(safetyMode === 'danger-full-access' ? { networkAccess: true } : {}) });
557
+ }, children: [_jsx("option", { value: "read-only", children: "\u53EA\u8BFB" }), _jsx("option", { value: "workspace-write", children: "\u5DE5\u4F5C\u533A\u5199\u5165" }), _jsx("option", { value: "danger-full-access", children: "\u5B8C\u5168\u8BBF\u95EE" })] })] }), _jsxs("label", { children: [_jsx("span", { children: "\u5BA1\u6279\u7B56\u7565" }), _jsxs("select", { value: activeChannel.runConfig.safetyMode === 'read-only' ? 'deny' : activeChannel.runConfig.safetyMode === 'danger-full-access' ? 'allow' : activeChannel.runConfig.approvalMode, disabled: activeChannel.runConfig.safetyMode !== 'workspace-write', onChange: (event) => updateRunConfig({ approvalMode: event.target.value }), children: [_jsx("option", { value: "ask", children: "\u6309\u9700\u786E\u8BA4" }), _jsx("option", { value: "deny", children: "\u81EA\u52A8\u62D2\u7EDD" }), _jsx("option", { value: "allow", children: "\u81EA\u52A8\u5141\u8BB8" })] })] }), _jsxs("label", { className: "agent-playground-network-toggle", children: [_jsxs("span", { children: [_jsx(Network, { size: 14 }), "\u7F51\u7EDC\u8BBF\u95EE"] }), _jsx("input", { type: "checkbox", checked: activeChannel.runConfig.safetyMode === 'danger-full-access' || activeChannel.runConfig.networkAccess, disabled: activeChannel.runConfig.safetyMode === 'danger-full-access', onChange: (event) => updateRunConfig({ networkAccess: event.target.checked }) }), _jsx("i", { "aria-hidden": "true" })] }), activeChannel.runConfig.safetyMode === 'danger-full-access' && (_jsxs("p", { children: [_jsx(CircleAlert, { size: 14 }), "\u5B8C\u5168\u8BBF\u95EE\u5141\u8BB8 Agent \u64CD\u4F5C\u5DE5\u4F5C\u76EE\u5F55\u4E4B\u5916\u7684\u6587\u4EF6\u5E76\u8BBF\u95EE\u7F51\u7EDC\uFF0C\u8BF7\u4EC5\u7528\u4E8E\u53EF\u4FE1\u4EFB\u52A1\u3002"] })), activeChannel.runConfig.safetyMode !== 'danger-full-access' && shellIsolation?.available === false && (_jsxs("p", { children: [_jsx(CircleAlert, { size: 14 }), "\u5B89\u5168 Shell \u9700\u8981\u53EF\u7528\u7684 Docker daemon\uFF1B\u5F53\u524D\u4EC5\u6587\u4EF6\u5DE5\u5177\u53EF\u8FD0\u884C\u3002", shellIsolation.message] }))] })), _jsx("section", { className: "agent-playground-conversation", "aria-label": "Agent \u5BF9\u8BDD", children: _jsx("div", { className: "agent-playground-message-scroll", children: channelMessages.length === 0 ? (_jsxs("div", { className: "agent-playground-empty", children: [_jsx("span", { className: "agent-playground-empty-mark", children: _jsx(Sparkles, { size: 24 }) }), _jsxs("div", { children: [_jsx("h2", { children: "\u5F00\u59CB\u4E00\u6B21\u53EF\u89C2\u5BDF\u7684 Agent \u8FD0\u884C" }), _jsx("p", { children: "\u53D1\u9001\u4EFB\u52A1\u540E\uFF0C\u56DE\u590D\u3001\u5DE5\u5177\u8C03\u7528\u3001Token \u4E0E\u5F02\u5E38\u4F1A\u5728\u540C\u4E00\u4F1A\u8BDD\u4E0A\u4E0B\u6587\u4E2D\u5173\u8054\u3002" })] }), _jsxs("div", { className: "agent-playground-prompts", children: [_jsx("button", { type: "button", onClick: () => runPrompt('介绍当前 Agent 可以使用的能力,并给出三个具体示例。'), children: "\u63A2\u7D22\u53EF\u7528\u80FD\u529B" }), _jsx("button", { type: "button", onClick: () => runPrompt('用 Markdown 表格总结当前运行环境,并附上一段 TypeScript 示例代码。'), children: "\u6D4B\u8BD5\u5BCC\u6587\u672C\u8F93\u51FA" }), _jsx("button", { type: "button", onClick: () => runPrompt('分析一个任务从推理、工具调用到最终回复的完整执行路径。'), children: "\u89C2\u5BDF\u6267\u884C\u8DEF\u5F84" })] })] })) : (_jsxs("div", { className: "agent-playground-message-list", children: [channelMessages.map((msg) => {
558
+ const isApproval = msg.type === 'received' && msg.content.some((segment) => segment.type === 'keyboard');
559
+ return (_jsxs(React.Fragment, { children: [_jsxs("article", { className: cn("agent-playground-message", msg.type === 'sent' ? "is-user" : "is-agent", isApproval && "is-approval"), children: [_jsx("span", { className: "agent-playground-message-avatar", children: msg.type === 'received' ? _jsx(Bot, { size: 16 }) : _jsx(User, { size: 16 }) }), _jsxs("div", { className: "agent-playground-message-main", children: [_jsxs("div", { className: "agent-playground-message-meta", children: [_jsx("strong", { children: isApproval ? '需要你的确认' : msg.type === 'received' ? endpointId : '你' }), isApproval && _jsx("span", { children: "approval required" }), _jsx("time", { children: new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) })] }), _jsx("div", { className: "agent-playground-message-content", children: renderMessageSegments(msg.content, msg.type === 'sent', msg.id, msg.interactionResolved === true) })] })] }), currentTaskMessage?.id === msg.id && inlineRunCard] }, msg.id));
560
+ }), !currentTaskMessage && inlineRunCard, _jsx("div", { ref: messagesEndRef })] })) }) }), _jsxs("section", { className: "agent-playground-composer", "aria-label": "\u4EFB\u52A1\u8F93\u5165", children: [_jsxs("div", { className: "flex gap-2 items-center flex-wrap", children: [_jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", showFacePicker ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setShowFacePicker(!showFacePicker); setMediaPanel(null); }, title: "\u63D2\u5165\u8868\u60C5", children: _jsx(Smile, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'image' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'image' ? null : 'image')); setShowFacePicker(false); }, title: "\u63D2\u5165\u56FE\u7247 URL", children: _jsx(Image, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'video' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'video' ? null : 'video')); setShowFacePicker(false); }, title: "\u63D2\u5165\u89C6\u9891 URL", children: _jsx(Video, { size: 16 }) }), _jsx("button", { type: "button", className: cn("h-8 w-8 rounded-md flex items-center justify-center border transition-colors", mediaPanel === 'audio' ? "bg-primary text-primary-foreground" : "hover:bg-accent"), onClick: () => { setMediaPanel((p) => (p === 'audio' ? null : 'audio')); setShowFacePicker(false); }, title: "\u63D2\u5165\u97F3\u9891 URL", children: _jsx(Music, { size: 16 }) }), _jsx("div", { className: "flex-1 min-w-[1rem]" }), _jsxs("div", { className: "sandbox-composer-tabs", role: "tablist", "aria-label": "\u6D88\u606F\u7F16\u8F91\u6A21\u5F0F", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": composerMode === 'write', className: cn(composerMode === 'write' && 'active'), onClick: () => setComposerMode('write'), children: "\u7F16\u5199" }), _jsx("button", { type: "button", role: "tab", "aria-selected": composerMode === 'preview', className: cn(composerMode === 'preview' && 'active'), onClick: () => setComposerMode('preview'), children: "\u9884\u89C8" })] }), inputText && (_jsx("button", { className: "h-8 w-8 rounded-md flex items-center justify-center hover:bg-accent transition-colors", onClick: () => { editorRef.current?.clear(); setInputText(''); setPreviewSegments([]); setComposerMode('write'); }, "aria-label": "\u6E05\u7A7A\u8F93\u5165", children: _jsx(X, { size: 16 }) }))] }), showFacePicker && (_jsxs("div", { className: "p-3 rounded-md border bg-muted/30 max-h-64 overflow-y-auto space-y-2", children: [_jsx("input", { value: faceSearchQuery, onChange: (e) => setFaceSearchQuery(e.target.value), placeholder: "\u641C\u7D22\u8868\u60C5...", className: "w-full h-8 rounded-md border bg-transparent px-2 text-sm" }), _jsx("div", { className: "grid grid-cols-8 gap-1", children: filteredFaces.slice(0, 80).map((face) => (_jsx("button", { onClick: () => insertFace(face.id), title: face.name, className: "w-10 h-10 rounded-md border flex items-center justify-center hover:bg-accent transition-colors", children: _jsx("img", { src: `https://face.viki.moe/apng/${face.id}.png`, alt: face.name, className: "w-8 h-8" }) }, face.id))) }), filteredFaces.length === 0 && (_jsxs("div", { className: "flex flex-col items-center gap-2 py-4", children: [_jsx(Search, { size: 32, className: "text-muted-foreground/30" }), _jsx("span", { className: "text-sm text-muted-foreground", children: "\u672A\u627E\u5230\u5339\u914D\u7684\u8868\u60C5" })] }))] })), mediaPanel && (_jsxs("div", { className: "p-3 rounded-md border bg-muted/30 space-y-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: [mediaPanel === 'image' && '支持 http(s) 图片链接或 data URL', mediaPanel === 'video' && '支持浏览器可解码的视频直链(如 .mp4、.webm)', mediaPanel === 'audio' && '支持 .mp3、.ogg、.wav 等音频直链'] }), _jsx("input", { value: mediaUrl, onChange: (e) => setMediaUrl(e.target.value), placeholder: mediaPanel === 'image' ? '图片 URL…' : mediaPanel === 'video' ? '视频 URL…' : '音频 URL…', className: "w-full h-8 rounded-md border border-input bg-background px-2 text-sm", onKeyDown: (e) => { if (e.key === 'Enter') {
561
+ e.preventDefault();
562
+ commitMediaUrl();
563
+ } } }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 h-8 px-3 rounded-md bg-primary text-primary-foreground text-sm disabled:opacity-50", onClick: commitMediaUrl, disabled: !mediaUrl.trim(), children: [_jsx(Check, { size: 14 }), " \u63D2\u5165\u5230\u8F93\u5165\u6846"] })] })), _jsxs("div", { className: "flex gap-2 items-start", children: [_jsxs("div", { className: "flex-1 relative", children: [_jsx("div", { className: composerMode === 'write' ? 'block' : 'hidden', children: _jsx(RichTextEditor, { ref: editorRef, placeholder: `向 ${activeChannel.name} 发送消息,支持 Markdown...`, onSend: handleSendMessage, onChange: handleEditorChange, onAtTrigger: handleAtTrigger, minHeight: "44px", maxHeight: "200px" }) }), composerMode === 'preview' && (_jsx("div", { className: "sandbox-markdown-preview", role: "tabpanel", children: inputText.trim()
564
+ ? _jsx(MarkdownContent, { text: inputText })
565
+ : _jsx("span", { className: "sandbox-markdown-preview-empty", children: "\u8F93\u5165 Markdown \u540E\u53EF\u5728\u8FD9\u91CC\u68C0\u67E5\u6700\u7EC8\u6548\u679C" }) })), atPopoverPosition && (_jsx("div", { className: "absolute z-50 rounded-lg border bg-popover shadow-md min-w-60 max-h-72 overflow-y-auto p-1", style: { top: `${atPopoverPosition.top}px`, left: `${atPopoverPosition.left}px` }, children: filteredAtSuggestions.length > 0 ? filteredAtSuggestions.map((user) => (_jsxs("div", { className: "flex items-center gap-2 p-2 rounded-md cursor-pointer hover:bg-accent transition-colors", onClick: () => selectAtUser(user), children: [_jsx(User, { size: 16, className: "text-muted-foreground" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: user.name }), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["ID: ", user.id] })] })] }, user.id))) : (_jsxs("div", { className: "flex flex-col items-center gap-2 p-4", children: [_jsx(Search, { size: 20, className: "text-muted-foreground/50" }), _jsx("span", { className: "text-xs text-muted-foreground", children: "\u672A\u627E\u5230\u5339\u914D\u7684\u7528\u6237" })] })) }))] }), _jsxs("button", { className: "inline-flex items-center gap-1.5 h-10 px-4 rounded-md bg-primary text-primary-foreground text-sm font-medium disabled:opacity-50 transition-colors hover:bg-primary/90", onClick: () => { const c = editorRef.current?.getContent(); if (c)
566
+ handleSendMessage(c.text, c.segments); }, disabled: !canExecute || !hasRenderableSegments(previewSegments), children: [_jsx(Send, { size: 16 }), " \u53D1\u9001"] })] }), _jsxs("div", { className: "flex items-center gap-2 flex-wrap text-xs text-muted-foreground", children: [_jsx(Info, { size: 12 }), " \u5FEB\u6377\u64CD\u4F5C:", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "Enter" }), " \u53D1\u9001", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "Shift+Enter" }), " \u6362\u884C", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "```ts" }), " \u4EE3\u7801\u5757", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "**\u6587\u672C**" }), " \u52A0\u7C97", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[@\u540D\u79F0]" }), " @\u67D0\u4EBA", _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[video:URL]" }), _jsx("span", { className: "px-1 py-0.5 rounded border text-[10px]", children: "[audio:URL]" })] })] })] }), _jsxs("aside", { className: cn("agent-playground-inspector", showInspector && "show"), "aria-label": "\u8FD0\u884C\u68C0\u67E5\u5668", children: [_jsxs("div", { className: "agent-playground-inspector-head", children: [_jsxs("div", { children: [_jsx("span", { children: "Run inspector" }), _jsx("h2", { children: "\u8FD0\u884C\u68C0\u67E5\u5668" })] }), _jsxs("div", { children: [_jsx("button", { type: "button", onClick: () => void loadTrace(), "aria-label": "\u5237\u65B0 Agent Trace", disabled: traceLoading, children: _jsx(RefreshCw, { size: 15, className: cn(traceLoading && 'animate-spin') }) }), _jsx("button", { type: "button", className: "agent-playground-inspector-close", onClick: () => setShowInspector(false), "aria-label": "\u5173\u95ED\u8FD0\u884C\u68C0\u67E5\u5668", children: _jsx(X, { size: 15 }) })] })] }), _jsxs("div", { className: "agent-playground-inspector-tabs", role: "tablist", "aria-label": "\u68C0\u67E5\u5668\u89C6\u56FE", children: [_jsxs("button", { type: "button", role: "tab", "aria-selected": inspectorView === 'runs', className: cn(inspectorView === 'runs' && 'active'), onClick: () => setInspectorView('runs'), children: [_jsx(Activity, { size: 13 }), "\u4EFB\u52A1"] }), _jsxs("button", { type: "button", role: "tab", "aria-selected": inspectorView === 'artifacts', className: cn(inspectorView === 'artifacts' && 'active'), onClick: () => setInspectorView('artifacts'), children: [_jsx(FileDiff, { size: 13 }), "\u53D8\u66F4\u4E0E\u4EA7\u7269 ", _jsx("span", { children: workbenchArtifacts.length })] })] }), _jsxs("div", { className: cn("agent-playground-inspector-view", inspectorView !== 'runs' && 'hidden'), children: [_jsxs("section", { className: "agent-playground-inspector-section", children: [_jsxs("div", { className: "agent-playground-inspector-title", children: [_jsx("span", { children: "\u8FD0\u884C\u6982\u89C8" }), _jsx("small", { children: traceSummary.activeTurns > 0 ? 'live' : 'idle' })] }), _jsxs("div", { className: "agent-playground-metrics", children: [_jsxs("div", { children: [_jsx(Activity, {}), _jsx("strong", { children: traceSummary.eventCount.toLocaleString() }), _jsx("span", { children: "\u4E8B\u4EF6" })] }), _jsxs("div", { children: [_jsx(Wrench, {}), _jsx("strong", { children: traceSummary.toolCount.toLocaleString() }), _jsx("span", { children: "\u5DE5\u5177" })] }), _jsxs("div", { children: [_jsx(Coins, {}), _jsx("strong", { children: traceSummary.tokenCount.toLocaleString() }), _jsx("span", { children: "Token" })] }), _jsxs("div", { className: cn(traceSummary.problemCount > 0 && 'has-problem'), children: [_jsx(CircleAlert, {}), _jsx("strong", { children: traceSummary.problemCount }), _jsx("span", { children: "\u5F02\u5E38" })] })] })] }), _jsxs("section", { className: "agent-playground-inspector-section", children: [_jsx("div", { className: "agent-playground-inspector-title", children: _jsx("span", { children: "\u4E0A\u4E0B\u6587" }) }), _jsxs("dl", { className: "agent-playground-context-list", children: [_jsxs("div", { children: [_jsx("dt", { children: "Endpoint" }), _jsx("dd", { children: endpointId })] }), _jsxs("div", { children: [_jsx("dt", { children: "Scope" }), _jsx("dd", { children: activeChannel.type })] }), _jsxs("div", { children: [_jsx("dt", { children: "Scene" }), _jsx("dd", { children: activeChannel.id })] }), _jsxs("div", { children: [_jsx("dt", { children: "Workdir" }), _jsx("dd", { title: activeChannel.runConfig.workingDirectory, children: activeChannel.runConfig.workingDirectory || 'Host project root' })] }), _jsxs("div", { children: [_jsx("dt", { children: "Security" }), _jsx("dd", { children: activeChannel.runConfig.safetyMode })] }), _jsxs("div", { children: [_jsx("dt", { children: "Approval" }), _jsx("dd", { children: activeChannel.runConfig.safetyMode === 'read-only' ? 'deny' : activeChannel.runConfig.safetyMode === 'danger-full-access' ? 'allow' : activeChannel.runConfig.approvalMode })] }), _jsxs("div", { children: [_jsx("dt", { children: "Network" }), _jsx("dd", { children: activeChannel.runConfig.networkAccess ? 'enabled' : 'disabled' })] }), _jsxs("div", { children: [_jsx("dt", { children: "Isolation" }), _jsx("dd", { className: cn(shellIsolation?.available && 'is-online'), children: shellIsolation ? `${shellIsolation.provider}: ${shellIsolation.available ? 'ready' : 'unavailable'}` : 'checking' })] }), _jsxs("div", { children: [_jsx("dt", { children: "Transport" }), _jsx("dd", { className: cn(connected && 'is-online'), children: connected ? 'WebSocket online' : 'reconnecting' })] })] })] }), _jsxs("section", { className: "agent-playground-inspector-section agent-playground-task-section", children: [_jsxs("div", { className: "agent-playground-inspector-title", children: [_jsx("span", { children: "\u4EFB\u52A1\u5386\u53F2" }), _jsxs("small", { children: [taskRuns.length, " runs"] })] }), taskRuns.length > 0 ? (_jsx("div", { className: "agent-playground-task-list", children: taskRuns.slice(0, 8).map((run) => (_jsxs("article", { className: cn(`is-${run.status}`), children: [_jsx("i", {}), _jsxs("div", { children: [_jsx("strong", { children: taskStatusLabel(run.status) }), _jsx("code", { children: run.turnId.slice(0, 10) })] }), _jsxs("dl", { children: [_jsxs("div", { children: [_jsx("dt", { children: "\u8017\u65F6" }), _jsx("dd", { children: run.durationMs === undefined ? '进行中' : `${run.durationMs.toLocaleString()} ms` })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5DE5\u5177" }), _jsx("dd", { children: run.toolCount })] }), _jsxs("div", { children: [_jsx("dt", { children: "Token" }), _jsx("dd", { children: run.tokenCount.toLocaleString() })] })] })] }, run.id))) })) : _jsxs("div", { className: "agent-playground-artifact-empty", children: [_jsx(Activity, { size: 18 }), _jsx("span", { children: "\u8FD0\u884C\u4EFB\u52A1\u540E\u4F1A\u5728\u8FD9\u91CC\u5F62\u6210\u53EF\u56DE\u6EAF\u8BB0\u5F55" })] })] }), _jsxs("section", { className: "agent-playground-inspector-section agent-playground-trace-section", children: [_jsxs("div", { className: "agent-playground-inspector-title", children: [_jsx("span", { children: "\u6700\u8FD1\u6267\u884C" }), _jsx("small", { children: "2s sync" })] }), traceLoading && !trace ? (_jsxs("div", { className: "agent-playground-trace-loading", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] })) : traceNotice && !trace?.events.length ? (_jsxs("div", { className: "agent-playground-trace-notice", children: [_jsx(CircleAlert, { size: 16 }), _jsx("span", { children: traceNotice })] })) : recentTraceEvents.length ? (_jsx("div", { className: "agent-playground-trace-list", children: recentTraceEvents.map((event) => {
567
+ const item = presentTraceEvent(event);
568
+ return (_jsxs("div", { className: cn("agent-playground-trace-item", `is-${item.tone}`), children: [_jsx("span", { className: "agent-playground-trace-dot" }), _jsxs("div", { children: [_jsx("strong", { children: item.title }), item.detail ? _jsx("small", { children: item.detail }) : null] }), _jsx("time", { children: new Date(event.recordedAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }) })] }, event.sequence));
569
+ }) })) : (_jsxs("div", { className: "agent-playground-trace-empty", children: [_jsx(Activity, { size: 18 }), _jsx("span", { children: "\u53D1\u9001\u4EFB\u52A1\u540E\u663E\u793A\u63A8\u7406\u4E0E\u5DE5\u5177\u8F68\u8FF9" })] }))] })] }), _jsx("div", { className: cn("agent-playground-inspector-view", inspectorView !== 'artifacts' && 'hidden'), children: _jsxs("section", { className: "agent-playground-inspector-section agent-playground-artifact-section", children: [_jsxs("div", { className: "agent-playground-inspector-title", children: [_jsx("span", { children: "\u6587\u4EF6\u4E0E\u547D\u4EE4\u4EA7\u7269" }), _jsxs("small", { children: [workbenchArtifacts.length, " items"] })] }), workbenchArtifacts.length > 0 ? (_jsx("div", { className: "agent-playground-artifact-list", children: workbenchArtifacts.map((artifact) => {
570
+ const ArtifactIcon = artifact.kind === 'file-change' ? FileDiff : artifact.kind === 'test' ? FlaskConical : Terminal;
571
+ const expanded = expandedArtifact === artifact.id;
572
+ return (_jsxs("article", { className: cn(`is-${artifact.status}`, expanded && 'is-expanded'), children: [_jsxs("button", { type: "button", onClick: () => setExpandedArtifact(expanded ? null : artifact.id), "aria-expanded": expanded, children: [_jsx("span", { className: "agent-playground-artifact-icon", children: _jsx(ArtifactIcon, { size: 14 }) }), _jsxs("span", { children: [_jsx("strong", { children: artifact.title }), _jsx("small", { children: artifact.path ?? artifact.detail ?? artifact.kind })] }), _jsx("em", { children: artifact.status }), _jsx(ChevronDown, { size: 14 })] }), expanded && (_jsxs("div", { className: "agent-playground-artifact-detail", children: [artifact.path && _jsx("code", { children: artifact.path }), artifact.diff ? _jsx("pre", { children: artifact.diff }) : _jsx("pre", { children: artifact.detail || '暂无输出' }), _jsxs("footer", { children: [_jsx("span", { children: artifact.durationMs === undefined ? '等待结果' : `${artifact.durationMs.toLocaleString()} ms` }), _jsx("code", { children: artifact.turnId.slice(0, 12) })] })] }))] }, artifact.id));
573
+ }) })) : _jsxs("div", { className: "agent-playground-artifact-empty", children: [_jsx(FileDiff, { size: 18 }), _jsx("span", { children: "Agent \u4FEE\u6539\u6587\u4EF6\u3001\u6267\u884C\u547D\u4EE4\u6216\u6D4B\u8BD5\u540E\uFF0C\u4EA7\u7269\u4F1A\u96C6\u4E2D\u51FA\u73B0\u5728\u8FD9\u91CC" })] })] }) }), _jsxs("a", { className: "agent-playground-studio-link", href: agentStudioPath(sessionKey), children: [_jsxs("span", { children: [_jsx(ExternalLink, { size: 15 }), "\u5728 Agent Studio \u4E2D\u5B8C\u6574\u8BCA\u65AD"] }), _jsx("code", { children: sessionKey })] })] }), showInspector && _jsx("div", { className: "agent-playground-inspector-overlay", onClick: () => setShowInspector(false) })] }));
574
+ }
575
+ function taskStatusLabel(status) {
576
+ if (status === 'running')
577
+ return 'Agent 运行中';
578
+ if (status === 'completed')
579
+ return '最近任务已完成';
580
+ if (status === 'failed')
581
+ return '最近任务失败';
582
+ return '最近任务已取消';
583
+ }
584
+ function messageText(segments) {
585
+ return segments.map((segment) => {
586
+ const data = segment.data;
587
+ if (segment.type === 'text' || segment.type === 'markdown' || segment.type === 'md') {
588
+ return String(data.text ?? data.content ?? '');
589
+ }
590
+ if (segment.type === 'mention' || segment.type === 'at')
591
+ return `@${String(data.name ?? data.target ?? '')}`;
592
+ return `[${segment.type}]`;
593
+ }).join(' ').trim();
594
+ }
595
+ function formatDuration(durationMs) {
596
+ if (durationMs < 1_000)
597
+ return `${durationMs.toLocaleString()} ms`;
598
+ if (durationMs < 60_000)
599
+ return `${(durationMs / 1_000).toFixed(durationMs < 10_000 ? 1 : 0)} s`;
600
+ return `${Math.floor(durationMs / 60_000)}m ${Math.round((durationMs % 60_000) / 1_000)}s`;
601
+ }
602
+ function runStepStatusLabel(status) {
603
+ if (status === 'running')
604
+ return '进行中';
605
+ if (status === 'completed')
606
+ return '完成';
607
+ if (status === 'denied')
608
+ return '已拒绝';
609
+ if (status === 'cancelled')
610
+ return '已取消';
611
+ return '失败';
612
+ }
613
+ function safeFileName(value) {
614
+ return value.trim().replace(/[^\p{L}\p{N}._-]+/gu, '-').replace(/^-+|-+$/gu, '').slice(0, 48) || 'session';
363
615
  }