@zhin.js/adapter-sandbox 7.0.11 → 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.
@@ -0,0 +1,559 @@
1
+ // Generated by build-plugin-runtime-entries.mjs. Do not edit.
2
+ import { getSandboxApiBase, getSandboxAuthHeaders } from './sandboxTransport.js';
3
+ const problemTypes = new Set(['tool_denied', 'tool_failed', 'tool_cancelled', 'turn_cancelled', 'budget_exceeded', 'error']);
4
+ const toolTypes = new Set(['tool_call', 'mcp_tool_call']);
5
+ const terminalTypes = new Set(['turn_end', 'turn_cancelled', 'budget_exceeded', 'error']);
6
+ const toolTerminalTypes = new Set(['tool_result', 'tool_denied', 'tool_failed', 'tool_cancelled']);
7
+ const traceCachePrefix = 'zhin.sandbox.agent-trace.v1:';
8
+ export function buildSandboxSessionKey(endpointId, scope, sceneId) {
9
+ return `sandbox:${endpointId || 'sandbox-bot'}:${scope}:${sceneId}`;
10
+ }
11
+ export function agentStudioPath(sessionKey) {
12
+ const params = new URLSearchParams({ sessionKey });
13
+ return `/agent/studio?${params}`;
14
+ }
15
+ export function mergeTraceSnapshot(previous, incoming) {
16
+ if (!previous || previous.sessionKey !== incoming.sessionKey)
17
+ return incoming;
18
+ const events = [...previous.events, ...incoming.events]
19
+ .filter((event, index, all) => all.findIndex((candidate) => (candidate.runtimeId === event.runtimeId
20
+ && candidate.turnId === event.turnId
21
+ && candidate.sequence === event.sequence)) === index)
22
+ .sort((left, right) => left.recordedAt - right.recordedAt || left.sequence - right.sequence)
23
+ .slice(-300);
24
+ return { ...incoming, events };
25
+ }
26
+ export function loadCachedAgentTrace(sessionKey, storage = browserTraceStorage()) {
27
+ if (!storage)
28
+ return null;
29
+ try {
30
+ const raw = storage.getItem(`${traceCachePrefix}${sessionKey}`);
31
+ if (!raw)
32
+ return null;
33
+ const parsed = JSON.parse(raw);
34
+ if (parsed.sessionKey !== sessionKey || !Array.isArray(parsed.events))
35
+ return null;
36
+ const events = parsed.events.map(parseCachedTraceEvent)
37
+ .filter((event) => event != null)
38
+ .slice(-300);
39
+ const activeTurnIds = Array.isArray(parsed.activeTurnIds)
40
+ ? parsed.activeTurnIds.filter((value) => typeof value === 'string').slice(-20)
41
+ : [];
42
+ return {
43
+ ...(typeof parsed.runtimeId === 'string' ? { runtimeId: parsed.runtimeId } : {}),
44
+ sessionKey,
45
+ events,
46
+ latestSequence: nonNegativeNumber(parsed.latestSequence),
47
+ activeTurnIds,
48
+ };
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ export function saveCachedAgentTrace(snapshot, storage = browserTraceStorage()) {
55
+ if (!storage)
56
+ return false;
57
+ try {
58
+ storage.setItem(`${traceCachePrefix}${snapshot.sessionKey}`, JSON.stringify({
59
+ ...snapshot,
60
+ events: snapshot.events.slice(-300),
61
+ activeTurnIds: snapshot.activeTurnIds.slice(-20),
62
+ }));
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
69
+ export function summarizeTrace(snapshot) {
70
+ if (!snapshot)
71
+ return { eventCount: 0, toolCount: 0, tokenCount: 0, problemCount: 0, activeTurns: 0 };
72
+ return {
73
+ eventCount: snapshot.latestSequence,
74
+ toolCount: snapshot.events.filter((event) => toolTypes.has(event.type)).length,
75
+ tokenCount: snapshot.events
76
+ .filter((event) => event.type === 'usage')
77
+ .reduce((total, event) => total + usageTotal(event.data), 0),
78
+ problemCount: snapshot.events.filter((event) => problemTypes.has(event.type)).length,
79
+ activeTurns: snapshot.activeTurnIds.length,
80
+ };
81
+ }
82
+ export function deriveTaskRuns(snapshot) {
83
+ if (!snapshot)
84
+ return [];
85
+ const active = new Set(snapshot.activeTurnIds);
86
+ const byRun = new Map();
87
+ for (const event of snapshot.events) {
88
+ const key = runCorrelationKey(event);
89
+ const events = byRun.get(key) ?? [];
90
+ events.push(event);
91
+ byRun.set(key, events);
92
+ }
93
+ return [...byRun.entries()].map(([id, events]) => {
94
+ const identity = events[0];
95
+ const turnId = identity.turnId;
96
+ const runtimeId = identity.runtimeId;
97
+ const startedAt = events.find((event) => event.type === 'turn_start')?.recordedAt ?? events[0]?.recordedAt ?? 0;
98
+ const terminal = [...events].reverse().find((event) => terminalTypes.has(event.type));
99
+ const sourceMessageId = stringValue(events.find((event) => event.type === 'turn_start')?.data.sourceMessageId);
100
+ const belongsToCurrentRuntime = snapshot.runtimeId === undefined || runtimeId === snapshot.runtimeId;
101
+ const status = belongsToCurrentRuntime && active.has(turnId)
102
+ ? 'running'
103
+ : terminal?.type === 'turn_end'
104
+ ? 'completed'
105
+ : terminal?.type === 'turn_cancelled'
106
+ ? 'cancelled'
107
+ : terminal
108
+ ? 'failed'
109
+ : 'failed';
110
+ const endedAt = terminal?.recordedAt;
111
+ return {
112
+ id,
113
+ ...(runtimeId ? { runtimeId } : {}),
114
+ ...(sourceMessageId ? { sourceMessageId } : {}),
115
+ turnId,
116
+ status,
117
+ startedAt,
118
+ ...(endedAt !== undefined ? { endedAt, durationMs: Math.max(0, endedAt - startedAt) } : {}),
119
+ eventCount: events.length,
120
+ toolCount: events.filter((event) => event.type === 'tool_call' || event.type === 'mcp_tool_call').length,
121
+ tokenCount: events.filter((event) => event.type === 'usage').reduce((total, event) => total + usageTotal(event.data), 0),
122
+ problemCount: events.filter((event) => problemTypes.has(event.type)).length,
123
+ };
124
+ }).sort((left, right) => right.startedAt - left.startedAt);
125
+ }
126
+ export function deriveWorkbenchArtifacts(snapshot) {
127
+ if (!snapshot)
128
+ return [];
129
+ const outcomes = new Map();
130
+ for (const event of snapshot.events) {
131
+ if (!toolTerminalTypes.has(event.type))
132
+ continue;
133
+ const toolUseId = stringValue(event.data.toolUseId);
134
+ if (toolUseId)
135
+ outcomes.set(toolCorrelationKey(event, toolUseId), event);
136
+ }
137
+ return snapshot.events.flatMap((event) => {
138
+ if (event.type !== 'tool_call')
139
+ return [];
140
+ const toolName = stringValue(event.data.toolName);
141
+ const toolUseId = stringValue(event.data.toolUseId) || `tool-${event.sequence}`;
142
+ const args = recordValue(event.data.args);
143
+ const artifactId = toolCorrelationKey(event, toolUseId);
144
+ const outcome = outcomes.get(artifactId);
145
+ const status = artifactStatus(outcome);
146
+ const durationMs = numberValue(outcome?.data.durationMs);
147
+ const common = {
148
+ id: artifactId,
149
+ ...(event.runtimeId ? { runtimeId: event.runtimeId } : {}),
150
+ turnId: event.turnId,
151
+ status,
152
+ ...(durationMs !== undefined ? { durationMs } : {}),
153
+ recordedAt: event.recordedAt,
154
+ };
155
+ if (toolName === 'write_file' || toolName === 'edit_file') {
156
+ const filePath = stringValue(args.file_path ?? args.path) || 'unknown file';
157
+ return [{
158
+ ...common,
159
+ kind: 'file-change',
160
+ title: toolName === 'write_file' ? '写入文件' : '编辑文件',
161
+ path: filePath,
162
+ detail: artifactOutputDetail(outcome),
163
+ diff: fileDiff(toolName, args, filePath),
164
+ }];
165
+ }
166
+ if (toolName === 'bash') {
167
+ const command = stringValue(args.command);
168
+ if (!command)
169
+ return [];
170
+ return [{
171
+ ...common,
172
+ kind: isTestCommand(command) ? 'test' : 'command',
173
+ title: command.slice(0, 160),
174
+ detail: artifactOutputDetail(outcome),
175
+ }];
176
+ }
177
+ return [];
178
+ }).sort((left, right) => right.recordedAt - left.recordedAt);
179
+ }
180
+ export function deriveAgentRunSteps(snapshot, run) {
181
+ if (!snapshot || !run)
182
+ return [];
183
+ const { turnId } = run;
184
+ const events = snapshot.events.filter((event) => matchesRun(event, run));
185
+ const outcomes = new Map();
186
+ for (const event of events) {
187
+ if (!toolTerminalTypes.has(event.type))
188
+ continue;
189
+ const toolUseId = stringValue(event.data.toolUseId);
190
+ if (toolUseId)
191
+ outcomes.set(toolCorrelationKey(event, toolUseId), event);
192
+ }
193
+ const steps = events.flatMap((event) => {
194
+ const common = { id: `${event.runtimeId ?? 'legacy'}:${turnId}:${event.sequence}`, recordedAt: event.recordedAt };
195
+ if (event.type === 'turn_start') {
196
+ return [{ ...common, title: '接收任务', detail: shortTurn(turnId), status: 'completed' }];
197
+ }
198
+ if (event.type === 'capability_resolution') {
199
+ return [{
200
+ ...common,
201
+ title: '准备运行能力',
202
+ detail: `${arrayLength(event.data.tools)} tools · ${arrayLength(event.data.skills)} skills`,
203
+ status: 'completed',
204
+ }];
205
+ }
206
+ if (event.type === 'iteration_start') {
207
+ return [{
208
+ ...common,
209
+ title: `推理迭代 ${String(event.data.iteration ?? '')}`.trim(),
210
+ detail: event.data.maxIterations == null ? undefined : `上限 ${String(event.data.maxIterations)}`,
211
+ status: 'completed',
212
+ }];
213
+ }
214
+ if (event.type === 'tool_call') {
215
+ const toolUseId = stringValue(event.data.toolUseId) || `tool-${event.sequence}`;
216
+ const outcome = outcomes.get(toolCorrelationKey(event, toolUseId));
217
+ const args = recordValue(event.data.args);
218
+ const toolName = stringValue(event.data.toolName) || 'tool';
219
+ const detail = toolStepDetail(toolName, args, toolUseId);
220
+ const durationMs = numberValue(outcome?.data.durationMs);
221
+ return [{
222
+ ...common,
223
+ id: toolCorrelationKey(event, toolUseId),
224
+ title: `运行 ${toolName}`,
225
+ ...(detail ? { detail } : {}),
226
+ status: artifactStatus(outcome),
227
+ ...(durationMs !== undefined ? { durationMs } : {}),
228
+ }];
229
+ }
230
+ if (event.type === 'subagent_start') {
231
+ return [{
232
+ ...common,
233
+ title: `委派 ${stringValue(event.data.agentName) || '子 Agent'}`,
234
+ detail: preview(event.data.description) || undefined,
235
+ status: 'running',
236
+ }];
237
+ }
238
+ if (event.type === 'subagent_end') {
239
+ const failed = event.data.status === 'error';
240
+ return [{
241
+ ...common,
242
+ title: failed ? '子 Agent 失败' : '子 Agent 返回',
243
+ detail: preview(event.data.summary ?? event.data.error) || undefined,
244
+ status: failed ? 'failed' : 'completed',
245
+ }];
246
+ }
247
+ if (event.type === 'turn_end')
248
+ return [{ ...common, title: '任务完成', detail: 'Agent 已返回结果', status: 'completed' }];
249
+ if (event.type === 'turn_cancelled')
250
+ return [{ ...common, title: '任务已停止', detail: preview(event.data.reason) || undefined, status: 'cancelled' }];
251
+ if (event.type === 'budget_exceeded')
252
+ return [{ ...common, title: '达到预算上限', detail: preview(event.data.budget) || undefined, status: 'failed' }];
253
+ if (event.type === 'error')
254
+ return [{ ...common, title: '任务失败', detail: preview(event.data.error) || undefined, status: 'failed' }];
255
+ return [];
256
+ });
257
+ const task = deriveTaskRuns(snapshot).find((candidate) => candidate.id === runIdentityKey(run));
258
+ for (let index = 0; index < steps.length; index += 1) {
259
+ const step = steps[index];
260
+ if (step.status !== 'running')
261
+ continue;
262
+ if (task?.status === 'cancelled')
263
+ steps[index] = { ...step, status: 'cancelled' };
264
+ else if (task?.status === 'failed')
265
+ steps[index] = { ...step, status: 'failed' };
266
+ else if (task?.status === 'completed' || index < steps.length - 1)
267
+ steps[index] = { ...step, status: 'completed' };
268
+ }
269
+ if (task?.status === 'running' && steps.length > 0 && steps.at(-1)?.status !== 'running') {
270
+ steps.push({
271
+ id: `${task.id}:waiting`,
272
+ title: '等待 Agent 返回',
273
+ detail: '任务仍在运行',
274
+ status: 'running',
275
+ recordedAt: events.at(-1)?.recordedAt ?? task.startedAt,
276
+ });
277
+ }
278
+ return steps;
279
+ }
280
+ export function buildAgentRunReport(snapshot, context = {}) {
281
+ const runs = deriveTaskRuns(snapshot);
282
+ const run = context.run ? runs.find((candidate) => candidate.id === runIdentityKey(context.run)) : runs[0];
283
+ const identity = run ?? context.run;
284
+ const turnId = identity?.turnId;
285
+ const steps = deriveAgentRunSteps(snapshot, identity);
286
+ const artifacts = deriveWorkbenchArtifacts(snapshot).filter((artifact) => identity && matchesRun(artifact, identity));
287
+ const status = run ? taskStatusText(run.status) : '无运行记录';
288
+ const lines = [
289
+ '# Agent 运行报告',
290
+ '',
291
+ `- **会话:** ${markdownInline(context.sessionName || snapshot?.sessionKey || '未命名会话')}`,
292
+ `- **任务 ID:** ${markdownInline(turnId || '—')}`,
293
+ `- **状态:** ${status}`,
294
+ `- **开始时间:** ${run ? new Date(run.startedAt).toISOString() : '—'}`,
295
+ `- **耗时:** ${run?.durationMs === undefined ? (run?.status === 'running' ? '进行中' : '—') : `${run.durationMs.toLocaleString()} ms`}`,
296
+ `- **工作目录:** ${markdownInline(context.workingDirectory || 'Host project root')}`,
297
+ `- **安全策略:** ${markdownInline(context.safetyMode || '—')} / ${markdownInline(context.approvalMode || '—')} / network ${context.networkAccess ? 'enabled' : 'disabled'}`,
298
+ `- **用量:** ${run?.toolCount ?? 0} tools / ${(run?.tokenCount ?? 0).toLocaleString()} tokens / ${run?.problemCount ?? 0} problems`,
299
+ ];
300
+ if (context.taskPrompt?.trim()) {
301
+ lines.push('', '## 任务', '', ...markdownBlock('text', context.taskPrompt.trim()));
302
+ }
303
+ lines.push('', '## 执行轨迹', '');
304
+ if (steps.length === 0)
305
+ lines.push('- 暂无可用轨迹');
306
+ else
307
+ for (const step of steps) {
308
+ lines.push(`- ${stepStatusMark(step.status)} **${reportStepTitle(step.title)}**${step.detail ? ` — ${markdownInline(step.detail)}` : ''}${step.durationMs === undefined ? '' : ` (${step.durationMs.toLocaleString()} ms)`}`);
309
+ }
310
+ lines.push('', '## 变更与产物', '');
311
+ if (artifacts.length === 0)
312
+ lines.push('- 本次运行没有文件、命令或测试产物。');
313
+ else
314
+ artifacts.forEach((artifact, index) => {
315
+ lines.push(`### ${index + 1}. ${markdownInline(artifact.title)}`, '');
316
+ lines.push(`- 类型:${artifact.kind}`, `- 状态:${artifact.status}`);
317
+ if (artifact.path)
318
+ lines.push(`- 路径:${markdownInlineCode(artifact.path)}`);
319
+ if (artifact.durationMs !== undefined)
320
+ lines.push(`- 耗时:${artifact.durationMs.toLocaleString()} ms`);
321
+ if (artifact.diff)
322
+ lines.push('', ...markdownBlock('diff', artifact.diff));
323
+ else if (artifact.detail)
324
+ lines.push('', ...markdownBlock('text', artifact.detail));
325
+ lines.push('');
326
+ });
327
+ lines.push('---', `由 Zhin Agent 试验台于 ${new Date().toISOString()} 导出。`);
328
+ return `${lines.join('\n').trim()}\n`;
329
+ }
330
+ export function presentTraceEvent(event) {
331
+ const data = event.data;
332
+ const tool = String(data.toolName ?? 'tool');
333
+ switch (event.type) {
334
+ case 'turn_start': return { title: '开始处理', detail: shortTurn(event.turnId), tone: 'running' };
335
+ case 'capability_resolution': return { title: '能力已解析', detail: `${arrayLength(data.tools)} tools · ${arrayLength(data.skills)} skills`, tone: 'capability' };
336
+ case 'iteration_start': return { title: `推理迭代 ${String(data.iteration ?? '')}`, detail: `上限 ${String(data.maxIterations ?? '—')}`, tone: 'thinking' };
337
+ case 'thinking': return { title: '模型思考', detail: preview(data.text), tone: 'thinking' };
338
+ case 'tool_call': return { title: `调用 ${tool}`, detail: preview(data.toolUseId), tone: 'tool' };
339
+ case 'tool_result': return { title: `${tool} 完成`, detail: duration(data.durationMs), tone: 'success' };
340
+ case 'tool_denied': return { title: `${tool} 被拒绝`, detail: preview(data.reason ?? data.policy), tone: 'problem' };
341
+ case 'tool_failed': return { title: `${tool} 失败`, detail: preview(data.error), tone: 'problem' };
342
+ case 'mcp_connect': return { title: `${String(data.serverName ?? 'MCP')} 连接`, detail: String(data.status ?? ''), tone: 'capability' };
343
+ case 'mcp_tool_call': return { title: String(data.toolName ?? 'MCP tool'), detail: `via ${String(data.serverName ?? 'MCP')}`, tone: 'tool' };
344
+ case 'subagent_start': return { title: `委派 ${String(data.agentName ?? 'subagent')}`, detail: preview(data.description), tone: 'capability' };
345
+ case 'subagent_progress': return { title: '子 Agent 更新', detail: preview(data.summary), tone: 'running' };
346
+ case 'subagent_end': return { title: '子 Agent 返回', detail: String(data.status ?? 'done'), tone: data.status === 'error' ? 'problem' : 'success' };
347
+ case 'usage': return { title: `${usageTotal(data).toLocaleString()} tokens`, detail: usageDetail(data), tone: 'usage' };
348
+ case 'turn_end': return { title: '本轮完成', detail: 'Agent 已返回结果', tone: 'success' };
349
+ case 'turn_cancelled': return { title: '本轮取消', detail: preview(data.reason), tone: 'muted' };
350
+ case 'budget_exceeded': return { title: '达到预算上限', detail: preview(data.budget), tone: 'problem' };
351
+ case 'error': return { title: '执行失败', detail: preview(data.error), tone: 'problem' };
352
+ default: return { title: event.type.replaceAll('_', ' '), detail: '', tone: 'muted' };
353
+ }
354
+ }
355
+ export async function fetchAgentTrace(sessionKey, afterSequence = 0) {
356
+ const base = getSandboxApiBase() || window.location.origin;
357
+ const url = new URL('/api/agent/traces', `${base}/`);
358
+ url.searchParams.set('sessionKey', sessionKey);
359
+ url.searchParams.set('limit', '300');
360
+ if (afterSequence > 0)
361
+ url.searchParams.set('after', String(afterSequence));
362
+ const response = await fetch(url, { headers: getSandboxAuthHeaders() });
363
+ const body = await response.json().catch(() => ({}));
364
+ if (!response.ok || body.success === false || !body.data) {
365
+ throw new Error(response.status === 503
366
+ ? 'Agent Trace 尚未启用'
367
+ : body.error || '无法读取 Agent Trace');
368
+ }
369
+ return body.data;
370
+ }
371
+ export async function cancelAgentTask(sessionKey) {
372
+ const base = getSandboxApiBase() || window.location.origin;
373
+ const response = await fetch(new URL('/api/agent/tasks/cancel', `${base}/`), {
374
+ method: 'POST',
375
+ headers: { ...getSandboxAuthHeaders(), 'content-type': 'application/json' },
376
+ body: JSON.stringify({ sessionKey }),
377
+ });
378
+ const body = await response.json().catch(() => ({}));
379
+ if (!response.ok || body.success === false) {
380
+ throw new Error(body.error || '无法停止 Agent 任务');
381
+ }
382
+ return body.data?.cancelled === true;
383
+ }
384
+ function usageRecord(data) {
385
+ return data.usage && typeof data.usage === 'object' ? data.usage : {};
386
+ }
387
+ function usageTotal(data) {
388
+ return Number(usageRecord(data).totalTokens ?? 0);
389
+ }
390
+ function usageDetail(data) {
391
+ const usage = usageRecord(data);
392
+ return `输入 ${Number(usage.promptTokens ?? 0).toLocaleString()} · 输出 ${Number(usage.completionTokens ?? 0).toLocaleString()}`;
393
+ }
394
+ function arrayLength(value) {
395
+ return Array.isArray(value) ? value.length : 0;
396
+ }
397
+ function preview(value) {
398
+ if (value == null)
399
+ return '';
400
+ if (typeof value === 'string')
401
+ return value.slice(0, 96);
402
+ try {
403
+ return JSON.stringify(value).slice(0, 96);
404
+ }
405
+ catch {
406
+ return String(value).slice(0, 96);
407
+ }
408
+ }
409
+ function duration(value) {
410
+ const ms = Number(value);
411
+ return Number.isFinite(ms) ? `${ms.toLocaleString()} ms` : '执行完成';
412
+ }
413
+ function shortTurn(value) {
414
+ return value.length > 12 ? value.slice(0, 10) : value;
415
+ }
416
+ function artifactStatus(event) {
417
+ if (!event)
418
+ return 'running';
419
+ if (event.type === 'tool_result')
420
+ return 'completed';
421
+ if (event.type === 'tool_denied')
422
+ return 'denied';
423
+ if (event.type === 'tool_cancelled')
424
+ return 'cancelled';
425
+ return 'failed';
426
+ }
427
+ function artifactOutputDetail(event) {
428
+ if (!event)
429
+ return undefined;
430
+ const value = event.data.output ?? event.data.error ?? event.data.reason;
431
+ if (typeof value === 'string')
432
+ return value.slice(0, 8_000) || undefined;
433
+ if (value == null)
434
+ return undefined;
435
+ try {
436
+ return JSON.stringify(value, null, 2).slice(0, 8_000);
437
+ }
438
+ catch {
439
+ return String(value).slice(0, 8_000);
440
+ }
441
+ }
442
+ function toolStepDetail(toolName, args, fallback) {
443
+ if (toolName === 'bash')
444
+ return stringValue(args.command).slice(0, 160) || fallback;
445
+ if (toolName === 'write_file' || toolName === 'edit_file' || toolName === 'read_file') {
446
+ return stringValue(args.file_path ?? args.path).slice(0, 160) || fallback;
447
+ }
448
+ return fallback;
449
+ }
450
+ function taskStatusText(status) {
451
+ if (status === 'running')
452
+ return '运行中';
453
+ if (status === 'completed')
454
+ return '已完成';
455
+ if (status === 'cancelled')
456
+ return '已取消';
457
+ return '失败';
458
+ }
459
+ function stepStatusMark(status) {
460
+ if (status === 'completed')
461
+ return '✓';
462
+ if (status === 'running')
463
+ return '◉';
464
+ if (status === 'cancelled')
465
+ return '■';
466
+ return '×';
467
+ }
468
+ function markdownInline(value) {
469
+ return value.replace(/[\\`*_[\]<>]/gu, '\\$&').replace(/\r?\n/gu, ' ');
470
+ }
471
+ function reportStepTitle(value) {
472
+ const tool = /^运行 (.+)$/u.exec(value)?.[1];
473
+ return tool ? `运行 ${markdownInlineCode(tool)}` : markdownInline(value);
474
+ }
475
+ function markdownInlineCode(value) {
476
+ const normalized = value.replace(/\r?\n/gu, ' ');
477
+ const longestTicks = Math.max(0, ...[...normalized.matchAll(/`+/gu)].map((match) => match[0].length));
478
+ const fence = '`'.repeat(Math.max(1, longestTicks + 1));
479
+ const padding = /^\s|\s$|^`|`$/u.test(normalized) ? ' ' : '';
480
+ return `${fence}${padding}${normalized}${padding}${fence}`;
481
+ }
482
+ function markdownBlock(language, value) {
483
+ const longestTicks = Math.max(0, ...[...value.matchAll(/`+/gu)].map((match) => match[0].length));
484
+ const fence = '`'.repeat(Math.max(3, longestTicks + 1));
485
+ return [`${fence}${language}`, value, fence];
486
+ }
487
+ function fileDiff(toolName, args, filePath) {
488
+ if (toolName !== 'edit_file')
489
+ return undefined;
490
+ const diffPath = filePath.replace(/^\/+/, '');
491
+ const before = stringValue(args.old_string);
492
+ const after = stringValue(args.new_string);
493
+ if (!before && !after)
494
+ return undefined;
495
+ return [
496
+ `--- a/${diffPath}`,
497
+ `+++ b/${diffPath}`,
498
+ '@@',
499
+ ...before.split('\n').map((line) => `- ${line}`),
500
+ ...after.split('\n').map((line) => `+ ${line}`),
501
+ ].join('\n').slice(0, 8_000);
502
+ }
503
+ function isTestCommand(command) {
504
+ return /(?:^|\s)(?:test|vitest|jest|pytest|cargo\s+test|go\s+test|pnpm\s+(?:run\s+)?test|npm\s+(?:run\s+)?test|yarn\s+test)(?:\s|$)/iu.test(command);
505
+ }
506
+ function recordValue(value) {
507
+ return value && typeof value === 'object' && !Array.isArray(value)
508
+ ? value
509
+ : {};
510
+ }
511
+ function toolCorrelationKey(event, toolUseId) {
512
+ return `${event.runtimeId ?? 'legacy'}:${event.turnId}:${toolUseId}`;
513
+ }
514
+ function runCorrelationKey(event) {
515
+ return runIdentityKey(event);
516
+ }
517
+ function runIdentityKey(run) {
518
+ return `${run.runtimeId ?? 'legacy'}:${run.turnId}`;
519
+ }
520
+ function matchesRun(value, run) {
521
+ return value.turnId === run.turnId && value.runtimeId === run.runtimeId;
522
+ }
523
+ function stringValue(value) {
524
+ return typeof value === 'string' ? value : '';
525
+ }
526
+ function numberValue(value) {
527
+ const number = Number(value);
528
+ return Number.isFinite(number) ? number : undefined;
529
+ }
530
+ function parseCachedTraceEvent(value) {
531
+ if (!value || typeof value !== 'object' || Array.isArray(value))
532
+ return undefined;
533
+ const item = value;
534
+ if (typeof item.sequence !== 'number'
535
+ || typeof item.recordedAt !== 'number'
536
+ || typeof item.sessionKey !== 'string'
537
+ || typeof item.turnId !== 'string'
538
+ || typeof item.type !== 'string'
539
+ || !item.data
540
+ || typeof item.data !== 'object'
541
+ || Array.isArray(item.data))
542
+ return undefined;
543
+ return {
544
+ ...(typeof item.runtimeId === 'string' ? { runtimeId: item.runtimeId } : {}),
545
+ sequence: item.sequence,
546
+ recordedAt: item.recordedAt,
547
+ sessionKey: item.sessionKey,
548
+ turnId: item.turnId,
549
+ type: item.type,
550
+ data: item.data,
551
+ };
552
+ }
553
+ function nonNegativeNumber(value) {
554
+ const number = Number(value);
555
+ return Number.isSafeInteger(number) && number >= 0 ? number : 0;
556
+ }
557
+ function browserTraceStorage() {
558
+ return typeof window === 'undefined' ? undefined : window.localStorage;
559
+ }