@bolloon/bolloon-agent 0.2.11 → 0.2.12

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,6 +1,1362 @@
1
1
  "use strict";
2
2
  (() => {
3
- var import_safe_name = require("./util/safe-name.js");
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
8
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
9
+ }) : x)(function(x) {
10
+ if (typeof require !== "undefined") return require.apply(this, arguments);
11
+ throw Error('Dynamic require of "' + x + '" is not supported');
12
+ });
13
+ var __esm = (fn, res) => function __init() {
14
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
15
+ };
16
+ var __export = (target, all) => {
17
+ for (var name in all)
18
+ __defProp(target, name, { get: all[name], enumerable: true });
19
+ };
20
+ var __copyProps = (to, from, except, desc) => {
21
+ if (from && typeof from === "object" || typeof from === "function") {
22
+ for (let key of __getOwnPropNames(from))
23
+ if (!__hasOwnProp.call(to, key) && key !== except)
24
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
25
+ }
26
+ return to;
27
+ };
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/web/ui/step-timeline.ts
31
+ function readExpanded(channelId) {
32
+ if (!channelId) return false;
33
+ try {
34
+ return localStorage.getItem(STORAGE_PREFIX + channelId) === "1";
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+ function writeExpanded(channelId, expanded) {
40
+ if (!channelId) return;
41
+ try {
42
+ localStorage.setItem(STORAGE_PREFIX + channelId, expanded ? "1" : "0");
43
+ } catch {
44
+ }
45
+ }
46
+ function computeTitle(steps) {
47
+ if (steps.length === 0) return "\u6267\u884C\u6B65\u9AA4";
48
+ const activeIdx = steps.findIndex((s) => s.status === "active");
49
+ if (activeIdx >= 0) return `\u25CF \u6267\u884C\u4E2D \xB7 ${steps[activeIdx].name}`;
50
+ const lastErrorIdx = (() => {
51
+ for (let i = steps.length - 1; i >= 0; i--) {
52
+ if (steps[i].status === "error") return i;
53
+ }
54
+ return -1;
55
+ })();
56
+ if (lastErrorIdx >= 0) return `\u2717 \u5931\u8D25 \xB7 ${steps[lastErrorIdx].name}`;
57
+ return `\u2713 \u5DF2\u5B8C\u6210 \xB7 ${steps.length} \u6B65`;
58
+ }
59
+ function render(timelineEl) {
60
+ const state = stateMap.get(timelineEl);
61
+ if (!state) return;
62
+ const { steps, expanded, showAll } = state;
63
+ const titleEl = timelineEl.querySelector("[data-current-tool]");
64
+ if (titleEl) titleEl.textContent = computeTitle(steps);
65
+ const dotsEl = timelineEl.querySelector("[data-dots]");
66
+ if (dotsEl) {
67
+ const existing = Array.from(dotsEl.children);
68
+ for (let i = 0; i < steps.length; i++) {
69
+ const step = steps[i];
70
+ let dot = existing[i];
71
+ if (!dot) {
72
+ dot = document.createElement("span");
73
+ dot.className = "step-dot";
74
+ dot.setAttribute("data-index", String(i));
75
+ dotsEl.appendChild(dot);
76
+ }
77
+ dot.setAttribute("data-status", step.status);
78
+ dot.setAttribute("title", `${step.name} \xB7 ${step.status}`);
79
+ }
80
+ while (dotsEl.children.length > steps.length) {
81
+ dotsEl.removeChild(dotsEl.lastChild);
82
+ }
83
+ }
84
+ const listEl = timelineEl.querySelector("[data-list]");
85
+ if (listEl) {
86
+ const hiddenCount = Math.max(0, steps.length - MAX_VISIBLE_NODES);
87
+ const visibleSteps = showAll ? steps : steps.slice(-MAX_VISIBLE_NODES);
88
+ const existing = Array.from(listEl.children);
89
+ let htmlIdx = 0;
90
+ if (!showAll && hiddenCount > 0) {
91
+ let moreBtn = existing[htmlIdx];
92
+ if (!moreBtn || !moreBtn.classList.contains("step-timeline-more")) {
93
+ moreBtn = document.createElement("li");
94
+ moreBtn.className = "step-timeline-more";
95
+ moreBtn.setAttribute("role", "button");
96
+ moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
97
+ moreBtn.onclick = () => {
98
+ state.showAll = true;
99
+ render(timelineEl);
100
+ };
101
+ if (existing[htmlIdx] && existing[htmlIdx] !== moreBtn) {
102
+ listEl.replaceChild(moreBtn, existing[htmlIdx]);
103
+ } else {
104
+ listEl.appendChild(moreBtn);
105
+ }
106
+ } else {
107
+ moreBtn.textContent = `+ ${hiddenCount} \u66F4\u591A`;
108
+ }
109
+ htmlIdx++;
110
+ }
111
+ for (let i = 0; i < visibleSteps.length; i++) {
112
+ const step = visibleSteps[i];
113
+ const realIndex = showAll ? i : steps.length - visibleSteps.length + i;
114
+ let node = existing[htmlIdx];
115
+ if (!node || !node.classList.contains("step-timeline-node")) {
116
+ node = document.createElement("li");
117
+ node.className = "step-timeline-node";
118
+ node.setAttribute("data-index", String(realIndex));
119
+ const marker = document.createElement("span");
120
+ marker.className = "step-timeline-marker";
121
+ const label2 = document.createElement("span");
122
+ label2.className = "step-timeline-label";
123
+ const args = document.createElement("span");
124
+ args.className = "step-timeline-args";
125
+ node.appendChild(marker);
126
+ node.appendChild(label2);
127
+ node.appendChild(args);
128
+ if (existing[htmlIdx] && existing[htmlIdx] !== node) {
129
+ listEl.replaceChild(node, existing[htmlIdx]);
130
+ } else {
131
+ listEl.appendChild(node);
132
+ }
133
+ } else {
134
+ node.setAttribute("data-index", String(realIndex));
135
+ }
136
+ node.setAttribute("data-status", step.status);
137
+ const label = node.querySelector(".step-timeline-label");
138
+ if (label) label.textContent = step.name;
139
+ const argsEl = node.querySelector(".step-timeline-args");
140
+ if (argsEl) {
141
+ const argStr = step.args && Object.keys(step.args).length > 0 ? JSON.stringify(step.args).slice(0, 60) : "";
142
+ argsEl.textContent = argStr;
143
+ argsEl.style.display = argStr ? "" : "none";
144
+ }
145
+ htmlIdx++;
146
+ }
147
+ while (listEl.children.length > htmlIdx) {
148
+ listEl.removeChild(listEl.lastChild);
149
+ }
150
+ }
151
+ if (steps.length === 0) {
152
+ timelineEl.setAttribute("data-empty", "true");
153
+ } else {
154
+ timelineEl.removeAttribute("data-empty");
155
+ }
156
+ const body = timelineEl.querySelector("[data-body]");
157
+ if (body) {
158
+ if (expanded) {
159
+ body.style.maxHeight = body.scrollHeight + "px";
160
+ setTimeout(() => {
161
+ if (state.expanded) body.style.maxHeight = "";
162
+ }, 300);
163
+ } else {
164
+ body.style.maxHeight = "0";
165
+ }
166
+ }
167
+ const arrow = timelineEl.querySelector(".step-timeline-arrow");
168
+ if (arrow) arrow.style.transform = expanded ? "rotate(180deg)" : "rotate(0deg)";
169
+ if (expanded && listEl) {
170
+ listEl.scrollTop = listEl.scrollHeight;
171
+ }
172
+ }
173
+ function createEmptyStepTimeline(channelId = null) {
174
+ const root = document.createElement("div");
175
+ root.className = "step-timeline";
176
+ root.setAttribute("data-step-timeline", "");
177
+ root.setAttribute("data-empty", "true");
178
+ const summary = document.createElement("div");
179
+ summary.className = "step-timeline-summary";
180
+ summary.setAttribute("data-summary", "");
181
+ const titleSpan = document.createElement("span");
182
+ titleSpan.className = "step-timeline-title";
183
+ const titleContent = document.createElement("span");
184
+ titleContent.setAttribute("data-current-tool", "");
185
+ titleContent.textContent = "\u6267\u884C\u6B65\u9AA4";
186
+ titleSpan.appendChild(titleContent);
187
+ const dots = document.createElement("div");
188
+ dots.className = "step-timeline-dots";
189
+ dots.setAttribute("data-dots", "");
190
+ const arrow = document.createElement("span");
191
+ arrow.className = "step-timeline-arrow";
192
+ arrow.textContent = "\u25BE";
193
+ summary.appendChild(titleSpan);
194
+ summary.appendChild(dots);
195
+ summary.appendChild(arrow);
196
+ summary.onclick = () => {
197
+ const state = stateMap.get(root);
198
+ if (!state) return;
199
+ state.expanded = !state.expanded;
200
+ writeExpanded(state.channelId, state.expanded);
201
+ render(root);
202
+ };
203
+ const body = document.createElement("div");
204
+ body.className = "step-timeline-body";
205
+ body.setAttribute("data-body", "");
206
+ const list = document.createElement("ul");
207
+ list.className = "step-timeline-list";
208
+ list.setAttribute("data-list", "");
209
+ body.appendChild(list);
210
+ root.appendChild(summary);
211
+ root.appendChild(body);
212
+ const initialExpanded = readExpanded(channelId);
213
+ stateMap.set(root, {
214
+ steps: [],
215
+ currentIndex: -1,
216
+ expanded: initialExpanded,
217
+ channelId: channelId || null,
218
+ showAll: false
219
+ });
220
+ render(root);
221
+ return root;
222
+ }
223
+ function mountStepTimeline(messageEl, channelId) {
224
+ const existing = messageEl.querySelector("[data-step-timeline]");
225
+ if (existing) {
226
+ const state = stateMap.get(existing);
227
+ if (state && channelId && !state.channelId) state.channelId = channelId;
228
+ return existing;
229
+ }
230
+ const bubble = messageEl.querySelector(".bubble");
231
+ const actions = messageEl.querySelector(".message-actions");
232
+ const timeline = createEmptyStepTimeline(channelId);
233
+ if (actions && actions.parentNode === messageEl) {
234
+ messageEl.insertBefore(timeline, actions);
235
+ } else if (bubble && bubble.parentNode === messageEl) {
236
+ bubble.parentNode.insertBefore(timeline, bubble.nextSibling);
237
+ } else {
238
+ messageEl.appendChild(timeline);
239
+ }
240
+ return timeline;
241
+ }
242
+ function pushStepToTimeline(timelineEl, eventType, data) {
243
+ const state = stateMap.get(timelineEl);
244
+ if (!state) return;
245
+ const { steps } = state;
246
+ if (eventType === "step_start") {
247
+ state.currentIndex = steps.length;
248
+ steps.push({
249
+ name: data.tool,
250
+ status: "active",
251
+ args: data.args,
252
+ eventType
253
+ });
254
+ } else {
255
+ let targetIdx = state.currentIndex;
256
+ if (targetIdx < 0 || !steps[targetIdx] || steps[targetIdx].name !== data.tool) {
257
+ for (let i = steps.length - 1; i >= 0; i--) {
258
+ if (steps[i].name === data.tool && steps[i].status === "active") {
259
+ targetIdx = i;
260
+ break;
261
+ }
262
+ }
263
+ }
264
+ if (targetIdx < 0) {
265
+ targetIdx = steps.length;
266
+ steps.push({ name: data.tool, status: "error", eventType });
267
+ }
268
+ if (eventType === "step_done") {
269
+ steps[targetIdx].status = data.success === false ? "error" : "done";
270
+ if (data.output) steps[targetIdx].output = data.output;
271
+ if (data.error) steps[targetIdx].error = data.error;
272
+ } else {
273
+ steps[targetIdx].status = "error";
274
+ if (data.error) steps[targetIdx].error = data.error;
275
+ }
276
+ steps[targetIdx].eventType = eventType;
277
+ state.currentIndex = -1;
278
+ }
279
+ render(timelineEl);
280
+ }
281
+ function migrateStepTimeline(fromEl, toEl) {
282
+ const fromTimeline = fromEl.querySelector("[data-step-timeline]");
283
+ const toTimeline = toEl.querySelector("[data-step-timeline]");
284
+ if (!fromTimeline) return;
285
+ if (toTimeline && toTimeline !== fromTimeline) {
286
+ toTimeline.remove();
287
+ }
288
+ if (toTimeline !== fromTimeline) {
289
+ toEl.appendChild(fromTimeline);
290
+ }
291
+ const state = stateMap.get(fromTimeline);
292
+ if (state) {
293
+ render(fromTimeline);
294
+ }
295
+ }
296
+ function getStepTimeline(messageEl) {
297
+ if (!messageEl) return null;
298
+ return messageEl.querySelector("[data-step-timeline]");
299
+ }
300
+ var MAX_VISIBLE_NODES, STORAGE_PREFIX, stateMap;
301
+ var init_step_timeline = __esm({
302
+ "src/web/ui/step-timeline.ts"() {
303
+ "use strict";
304
+ MAX_VISIBLE_NODES = 8;
305
+ STORAGE_PREFIX = "bolloon.stepTimeline.expanded.";
306
+ stateMap = /* @__PURE__ */ new WeakMap();
307
+ }
308
+ });
309
+
310
+ // src/agents/parse-tool-call.ts
311
+ function defaultResolveAlias(name, tools) {
312
+ if (tools.has(name)) return name;
313
+ const lower = name.toLowerCase();
314
+ const aliasMap = {
315
+ read: "read_file",
316
+ edit: "edit_file",
317
+ write: "write_file",
318
+ rm: "delete_file",
319
+ mv: "move_file",
320
+ bash: "shell_exec",
321
+ shell: "shell_exec",
322
+ sh: "shell_exec",
323
+ cat: "read_file",
324
+ test: "vitest_run",
325
+ vitest: "vitest_run",
326
+ typecheck: "tsc_check",
327
+ tsc: "tsc_check",
328
+ log: "git_log",
329
+ show: "git_show",
330
+ diff: "git_diff",
331
+ commit: "git_commit",
332
+ push: "git_push",
333
+ branch: "git_branch",
334
+ checkout: "git_branch",
335
+ stash: "git_stash",
336
+ todo_write: "create_task",
337
+ todowrite: "create_task",
338
+ task: "create_task"
339
+ };
340
+ const aliased = aliasMap[lower];
341
+ if (aliased && tools.has(aliased)) return aliased;
342
+ if (tools.has(lower)) return lower;
343
+ return null;
344
+ }
345
+ function autoSplitCommand(args) {
346
+ if (typeof args.command === "string" && args.command.includes(" ") && !args.args) {
347
+ const parts = args.command.split(/\s+/);
348
+ args.command = parts[0];
349
+ args.args = parts.slice(1).join(" ");
350
+ }
351
+ }
352
+ function resolve(ctx, name) {
353
+ if (ctx.resolveAlias) return ctx.resolveAlias(name);
354
+ return defaultResolveAlias(name, ctx.tools);
355
+ }
356
+ function parseToolCall(content, ctx) {
357
+ if (!content) return null;
358
+ const strippedContent = content.replace(/<think[\s\S]*?<\/think/g, "");
359
+ const jsonPatterns = [
360
+ // markdown json code block + OpenAI 块, 同时匹配 arguments/input 字段
361
+ /(?:```(?:json|json5)?\s*\n?)?\{[\s\S]*?"name"\s*:\s*["'](\w+)["']\s*,\s*["']?(?:arguments|input)["']?\s*:\s*(\{[\s\S]*?\})\s*\}/
362
+ ];
363
+ for (const p of jsonPatterns) {
364
+ const m = content.match(p);
365
+ if (m) {
366
+ const name = m[1];
367
+ let args = {};
368
+ try {
369
+ const parsed = JSON.parse(m[2]);
370
+ if (parsed && typeof parsed === "object") {
371
+ args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
372
+ }
373
+ } catch {
374
+ }
375
+ autoSplitCommand(args);
376
+ const resolved = resolve(ctx, name);
377
+ if (resolved) {
378
+ return { name: resolved, args };
379
+ }
380
+ }
381
+ }
382
+ const toolsCallMatch = content.match(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/);
383
+ if (toolsCallMatch) {
384
+ const name = toolsCallMatch[1];
385
+ const inner = toolsCallMatch[2];
386
+ const args = {};
387
+ const argTags = inner.matchAll(/<tools:call\s+name=["'](\w+)["']>([\s\S]*?)<\/tools:call>/g);
388
+ for (const m of argTags) {
389
+ args[m[1]] = m[2].trim();
390
+ }
391
+ const resolved = resolve(ctx, name);
392
+ if (resolved) {
393
+ return { name: resolved, args };
394
+ }
395
+ if (args.command) {
396
+ const cmdFirst = args.command.split(/\s+/)[0];
397
+ if (SHELL_KEYWORDS.includes(cmdFirst)) {
398
+ return { name: "shell_exec", args };
399
+ }
400
+ }
401
+ }
402
+ const patterns = [
403
+ // minimax/Hermes 自闭合 XML 格式 <invoke name="X">...</invoke>
404
+ new RegExp(`<invoke\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</invoke>`),
405
+ // <function_calls> 包裹
406
+ new RegExp(`<function_calls>[\\s\\S]*?<invoke\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</invoke>[\\s\\S]*?</function_calls>`),
407
+ // <function_calls><tool name="X"><param name="Y">value</param></tool></function_calls>
408
+ new RegExp(`<function_calls>[\\s\\S]*?<tool\\s+name=["']([\\w]+)["']>([\\s\\S]*?)</tool>[\\s\\S]*?</function_calls>`),
409
+ /调用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
410
+ /使用工具[::]\s*(\w+)\s*\(([^)]*)\)/,
411
+ /tool[_\w]*[::]\s*(\w+)\s*\(([^)]*)\)/i,
412
+ /(\w+)\s*\(\s*([^)]*)\s*\)/,
413
+ // 对象字面量格式 {tool => "get_identity", args => {...}}
414
+ /\{\s*tool\s*=>\s*["'](\w+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/,
415
+ // tool => "get_identity" (无 args 包裹)
416
+ /\btool\s*=>\s*["'](\w+)["']/,
417
+ // [TOOL_CALL] 块内 JSON 形式 {"name": "x", "args": {...}}
418
+ /\[TOOL_CALL\][\s\S]*?\{\s*"name"\s*:\s*"(\w+)"\s*,\s*"args"\s*:\s*(\{[\s\S]*?\})/i,
419
+ // "tool_name {json_args}" 形式 (单行, 无 name 字段)
420
+ /(?:^|\n)(\w+)\s+(\{[\s\S]*?\})(?=\n|$)/,
421
+ // XML 格式 <tool_name>...<arg>value</arg>...</tool_name>
422
+ /<(\w+)>([\s\S]*?)<\/\1>/
423
+ ];
424
+ for (const pattern of patterns) {
425
+ const match = strippedContent.match(pattern);
426
+ if (!match) continue;
427
+ const name = match[1];
428
+ let args = {};
429
+ const rawArgs = match[2] || "";
430
+ if (rawArgs && rawArgs.trim().startsWith("{")) {
431
+ try {
432
+ const parsed = JSON.parse(rawArgs);
433
+ if (parsed && typeof parsed === "object") {
434
+ args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
435
+ }
436
+ } catch {
437
+ const argPairs = rawArgs.split(",").map((s) => s.trim()).filter(Boolean);
438
+ for (const pair of argPairs) {
439
+ const [key, ...valueParts] = pair.split(":").map((s) => s.trim().replace(/['"]/g, ""));
440
+ if (key) args[key] = valueParts.join(":") || "";
441
+ }
442
+ }
443
+ } else if (rawArgs && /<[\w]/.test(rawArgs) && /<\/\w+>/.test(rawArgs)) {
444
+ const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
445
+ const paramReShort = /<param\s+name=["'](\w+)["']>([\s\S]*?)<\/param>/g;
446
+ let pMatch;
447
+ while ((pMatch = paramRe.exec(rawArgs)) !== null) {
448
+ const argName = pMatch[1];
449
+ const argValue = pMatch[2].trim();
450
+ if (argName && argValue) {
451
+ args[argName] = argValue;
452
+ }
453
+ }
454
+ if (Object.keys(args).length === 0) {
455
+ let sMatch;
456
+ paramReShort.lastIndex = 0;
457
+ while ((sMatch = paramReShort.exec(rawArgs)) !== null) {
458
+ const argName = sMatch[1];
459
+ const argValue = sMatch[2].trim().replace(/^["']|["']$/g, "");
460
+ if (argName && argValue) {
461
+ args[argName] = argValue;
462
+ }
463
+ }
464
+ }
465
+ if (Object.keys(args).length === 0) {
466
+ const xmlArgPattern = /<(\w+)>([\s\S]*?)<\/\1>/g;
467
+ let xmlMatch;
468
+ while ((xmlMatch = xmlArgPattern.exec(rawArgs)) !== null) {
469
+ const argName = xmlMatch[1];
470
+ const argValue = xmlMatch[2].trim();
471
+ if (argName && argValue) {
472
+ args[argName] = argValue;
473
+ }
474
+ }
475
+ }
476
+ } else if (rawArgs) {
477
+ const argPairs = rawArgs.split(",").map((s) => s.trim()).filter(Boolean);
478
+ for (const pair of argPairs) {
479
+ const [key, ...valueParts] = pair.split(":").map((s) => s.trim().replace(/['"]/g, ""));
480
+ if (key) args[key] = valueParts.join(":") || "";
481
+ }
482
+ }
483
+ const resolved = ctx.tools.has(name) ? name : resolve(ctx, name);
484
+ if (resolved) {
485
+ autoSplitCommand(args);
486
+ return { name: resolved, args };
487
+ }
488
+ if (rawArgs && /<\w+>[\s\S]*<\/\w+>/.test(strippedContent)) {
489
+ break;
490
+ }
491
+ }
492
+ const xmlTagMatch = strippedContent.match(/<(\w+)>([\s\S]*?)<\/\1>/);
493
+ if (xmlTagMatch) {
494
+ const outerTag = xmlTagMatch[1];
495
+ const inner = xmlTagMatch[2];
496
+ if (!resolve(ctx, outerTag)) {
497
+ const cmdMatch = inner.match(/<command>([\s\S]*?)<\/command>/);
498
+ if (cmdMatch) {
499
+ const cmd = cmdMatch[1].trim();
500
+ const cmdFirst = cmd.split(/\s+/)[0];
501
+ if (SHELL_KEYWORDS.includes(cmdFirst)) {
502
+ const args = {};
503
+ const remaining = cmd.slice(cmdFirst.length).trim();
504
+ if (remaining) {
505
+ args.command = cmdFirst;
506
+ args.args = remaining;
507
+ } else {
508
+ args.command = cmd;
509
+ }
510
+ const argsM = inner.match(/<args>([\s\S]*?)<\/args>/);
511
+ if (argsM && argsM[1].trim()) {
512
+ args.args = argsM[1].trim();
513
+ }
514
+ return { name: "shell_exec", args };
515
+ }
516
+ }
517
+ }
518
+ }
519
+ return null;
520
+ }
521
+ var SHELL_KEYWORDS;
522
+ var init_parse_tool_call = __esm({
523
+ "src/agents/parse-tool-call.ts"() {
524
+ "use strict";
525
+ SHELL_KEYWORDS = ["git", "npx", "npm", "tsx", "tsc", "vitest", "node", "mkdir", "touch", "ls", "echo", "cat", "head", "tail", "wc", "pwd", "date"];
526
+ }
527
+ });
528
+
529
+ // src/agents/chat-segmenter.ts
530
+ function segmentChatReply(reply, ctx) {
531
+ if (!reply) return [];
532
+ const segments = [];
533
+ let remaining = reply;
534
+ if (!remaining.startsWith("<think>")) {
535
+ remaining = extractLeadingThinking(remaining, segments);
536
+ }
537
+ remaining = extractAndPush("think", remaining, segments, /<think>([\s\S]*?)<\/think>/g);
538
+ remaining = extractAndPush("env_details", remaining, segments, /<environment_details>([\s\S]*?)<\/environment_details>/g);
539
+ remaining = stripToolCallMarkers(remaining, segments, ctx);
540
+ if (remaining.trim()) {
541
+ remaining = filterFillerText(remaining);
542
+ }
543
+ const finalIdx = remaining.indexOf("<final gen>");
544
+ let beforeFinalText = remaining;
545
+ if (finalIdx >= 0) {
546
+ beforeFinalText = remaining.substring(0, finalIdx);
547
+ }
548
+ const textContent = beforeFinalText.trim();
549
+ if (textContent) {
550
+ segments.push({ type: "text", content: textContent });
551
+ }
552
+ if (finalIdx >= 0) {
553
+ const afterFinal = remaining.substring(finalIdx + "<final gen>".length).trim();
554
+ if (afterFinal) {
555
+ segments.push({ type: "final", content: afterFinal });
556
+ }
557
+ }
558
+ return segments;
559
+ }
560
+ function extractAndPush(type, text, out, re) {
561
+ const matches = [];
562
+ const localRe = new RegExp(re.source, re.flags);
563
+ let m;
564
+ while ((m = localRe.exec(text)) !== null) {
565
+ const content = m[1] ? m[1].trim() : "";
566
+ matches.push({
567
+ start: m.index,
568
+ end: m.index + m[0].length,
569
+ content
570
+ });
571
+ if (m[0].length === 0) localRe.lastIndex++;
572
+ }
573
+ if (matches.length === 0) return text;
574
+ for (const m2 of matches) {
575
+ if (m2.content) out.push({ type, content: m2.content });
576
+ }
577
+ let next = "";
578
+ let cursor = 0;
579
+ for (const m2 of matches) {
580
+ next += text.substring(cursor, m2.start);
581
+ cursor = m2.end;
582
+ }
583
+ next += text.substring(cursor);
584
+ return next;
585
+ }
586
+ function extractLeadingThinking(text, out) {
587
+ const firstLineEnd = text.indexOf("\n");
588
+ const firstLine = firstLineEnd === -1 ? text : text.substring(0, firstLineEnd);
589
+ const rest = firstLineEnd === -1 ? "" : text.substring(firstLineEnd + 1);
590
+ const thinkingStartRe = /^(让我|我先|我应该|先来|先|接下来|好的[,,]?\s*我|让我先|先看看|让我看看|思考|考虑)/;
591
+ const enThinkingStartRe = /^(Let me|I'll|I will|First,|Next,|Now,|So,|Alright[,.]\s+(?:let me|I'll|I will))/i;
592
+ if (firstLine.trim().length === 0) {
593
+ return text;
594
+ }
595
+ if (firstLine.length <= 120 && (thinkingStartRe.test(firstLine) || enThinkingStartRe.test(firstLine))) {
596
+ out.push({ type: "think", content: firstLine.trim() });
597
+ return rest;
598
+ }
599
+ if (firstLine.length <= 80 && (thinkingStartRe.test(firstLine) || enThinkingStartRe.test(firstLine))) {
600
+ out.push({ type: "think", content: firstLine.trim() });
601
+ return rest;
602
+ }
603
+ return text;
604
+ }
605
+ function filterFillerText(text) {
606
+ const lines = text.split("\n");
607
+ const filtered = [];
608
+ for (const line of lines) {
609
+ const t = line.trim();
610
+ if (!t) continue;
611
+ if (/^(好|好了|好的|完成|完成了|任务完成|可以|可以了|答完了|说完了|就这样|完了|done|ok|OK|Okay|okay|alright|fine|let me check|我来)\.?$/i.test(t)) {
612
+ continue;
613
+ }
614
+ filtered.push(line);
615
+ }
616
+ return filtered.join("\n").trim();
617
+ }
618
+ function stripToolCallMarkers(text, out, ctx) {
619
+ let result = text.replace(/\[TOOL_CALL\][\s\S]*?\[\/TOOL_CALL\]/g, (m) => {
620
+ const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
621
+ if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
622
+ return "";
623
+ });
624
+ result = result.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, (m) => {
625
+ const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
626
+ if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
627
+ return "";
628
+ });
629
+ result = result.replace(/<invoke\s+name=["']([\w]+)["']>([\s\S]*?)<\/invoke>/g, (_m, name, inner) => {
630
+ if (ctx.knownToolNames.has(name)) {
631
+ const args = extractSimpleArgs(inner);
632
+ out.push({ type: "tool_call", tool: { name, args } });
633
+ }
634
+ return "";
635
+ });
636
+ result = result.replace(/<function_calls>[\s\S]*?<\/function_calls>/g, (m) => {
637
+ const parsed = parseToolCall(m, { tools: ctx.knownToolNames });
638
+ if (parsed) out.push({ type: "tool_call", tool: { name: parsed.name, args: parsed.args } });
639
+ return "";
640
+ });
641
+ result = result.replace(
642
+ /\{\s*"name"\s*:\s*"([\w]+)"\s*,\s*"(?:arguments|input|args|params)"\s*:\s*(\{[\s\S]*?\})\s*\}/g,
643
+ (_m, name, argsJson) => {
644
+ if (!ctx.knownToolNames.has(name)) return "";
645
+ let args = {};
646
+ try {
647
+ const parsed = JSON.parse(argsJson);
648
+ if (parsed && typeof parsed === "object") {
649
+ args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
650
+ }
651
+ } catch {
652
+ }
653
+ out.push({ type: "tool_call", tool: { name, args } });
654
+ return "";
655
+ }
656
+ );
657
+ result = result.replace(/\{\s*tool\s*=>\s*["']([\w]+)["']\s*(?:,\s*args\s*=>\s*(\{[\s\S]*?\}))?\s*\}/g, (_m, name, argsJson) => {
658
+ if (!ctx.knownToolNames.has(name)) return "";
659
+ let args = {};
660
+ if (argsJson) {
661
+ try {
662
+ const parsed = JSON.parse(argsJson);
663
+ if (parsed && typeof parsed === "object") {
664
+ args = Object.fromEntries(Object.entries(parsed).map(([k, v]) => [k, String(v)]));
665
+ }
666
+ } catch {
667
+ }
668
+ }
669
+ out.push({ type: "tool_call", tool: { name, args } });
670
+ return "";
671
+ });
672
+ result = result.replace(/\[Function[^\]]*\]\s*/g, "");
673
+ result = stripUnclosedToolCallTags(result);
674
+ return result;
675
+ }
676
+ function stripUnclosedToolCallTags(text) {
677
+ const startTags = [
678
+ { tag: "<tool_call>", closeTag: "</tool_call>", openRe: /<tool_call>/g },
679
+ { tag: "<invoke ", closeTag: "</invoke>", openRe: /<invoke\s+name=["']([\w]+)["']>/g },
680
+ { tag: "<function_calls>", closeTag: "</function_calls>", openRe: /<function_calls>/g },
681
+ { tag: "[TOOL_CALL]", closeTag: "[/TOOL_CALL]", openRe: /\[TOOL_CALL\]/g }
682
+ ];
683
+ let result = text;
684
+ for (const { tag, closeTag, openRe } of startTags) {
685
+ let m;
686
+ while ((m = openRe.exec(result)) !== null) {
687
+ const tail = result.substring(m.index);
688
+ const closeIdx = tail.indexOf(closeTag);
689
+ if (closeIdx === -1) {
690
+ const nextBlank = result.indexOf("\n\n", m.index);
691
+ const endIdx = nextBlank === -1 ? result.length : nextBlank;
692
+ result = result.substring(0, m.index) + result.substring(endIdx);
693
+ break;
694
+ }
695
+ }
696
+ }
697
+ return result;
698
+ }
699
+ function extractSimpleArgs(inner) {
700
+ const args = {};
701
+ const paramRe = /<parameter\s+name=["'](\w+)["']>([\s\S]*?)<\/parameter>/g;
702
+ let m;
703
+ while ((m = paramRe.exec(inner)) !== null) {
704
+ args[m[1]] = m[2].trim();
705
+ }
706
+ if (Object.keys(args).length === 0) {
707
+ const shortRe = /<param\s+name=["'](\w+)["']>([\s\S]*?)<\/param>/g;
708
+ while ((m = shortRe.exec(inner)) !== null) {
709
+ args[m[1]] = m[2].trim().replace(/^["']|["']$/g, "");
710
+ }
711
+ }
712
+ if (Object.keys(args).length === 0) {
713
+ const cmdM = inner.match(/<command>([\s\S]*?)<\/command>/);
714
+ const argsM = inner.match(/<args>([\s\S]*?)<\/args>/);
715
+ if (cmdM) {
716
+ const cmd = cmdM[1].trim();
717
+ if (cmd.includes(" ") && !argsM) {
718
+ const parts = cmd.split(/\s+/);
719
+ args.command = parts[0];
720
+ args.args = parts.slice(1).join(" ");
721
+ } else {
722
+ args.command = cmd;
723
+ }
724
+ }
725
+ if (argsM) args.args = argsM[1].trim();
726
+ }
727
+ return args;
728
+ }
729
+ var init_chat_segmenter = __esm({
730
+ "src/agents/chat-segmenter.ts"() {
731
+ "use strict";
732
+ init_parse_tool_call();
733
+ }
734
+ });
735
+
736
+ // src/web/ui/message-renderer.ts
737
+ var message_renderer_exports = {};
738
+ __export(message_renderer_exports, {
739
+ MessageRenderer: () => MessageRenderer,
740
+ addMessage: () => addMessage,
741
+ escapeHtml: () => escapeHtml,
742
+ finalizeTimelineAsMessage: () => finalizeTimelineAsMessage,
743
+ getMessagesContainerForCurrent: () => getMessagesContainerForCurrent,
744
+ handleStepEvent: () => handleStepEvent,
745
+ handleStreamTokenEvent: () => handleStreamTokenEvent,
746
+ hasStreamingText: () => hasStreamingText,
747
+ injectRecoveredText: () => injectRecoveredText,
748
+ replaceStreamingText: () => replaceStreamingText,
749
+ resetRendererState: () => resetRendererState
750
+ });
751
+ function hasStreamingText() {
752
+ return streamingText.length > 0;
753
+ }
754
+ function replaceStreamingText(fullContent) {
755
+ if (!streamingTextNode || !streamingMessageEl) {
756
+ return;
757
+ }
758
+ streamingTextNode.nodeValue = String(fullContent || "");
759
+ streamingText = String(fullContent || "");
760
+ }
761
+ function injectRecoveredText(partialText, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
762
+ if (streamingMessageEl && streamingTextNode) {
763
+ streamingTextNode.nodeValue = String(partialText || "");
764
+ streamingText = String(partialText || "");
765
+ return;
766
+ }
767
+ handleStreamTokenEvent(
768
+ {
769
+ type: "token",
770
+ streamType: "token",
771
+ content: String(partialText || ""),
772
+ delta: String(partialText || "")
773
+ },
774
+ ctx
775
+ );
776
+ }
777
+ function scheduleScrollToBottom(container) {
778
+ if (!container) return;
779
+ if (scrollToBottomTimer) return;
780
+ scrollToBottomTimer = setTimeout(() => {
781
+ container.scrollTop = container.scrollHeight;
782
+ scrollToBottomTimer = null;
783
+ }, 60);
784
+ }
785
+ function getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2) {
786
+ if (currentChannelId2 && messagesContainers2.get(currentChannelId2)) {
787
+ return messagesContainers2.get(currentChannelId2) || null;
788
+ }
789
+ return messagesEl2;
790
+ }
791
+ function escapeHtml(s) {
792
+ return String(s ?? "").replace(/[&<>"']/g, (c) => ({
793
+ "&": "&amp;",
794
+ "<": "&lt;",
795
+ ">": "&gt;",
796
+ '"': "&quot;",
797
+ "'": "&#39;"
798
+ })[c]);
799
+ }
800
+ function addMessage(content, type, save = true, container, usedJudgmentIds = [], ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
801
+ const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
802
+ const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
803
+ const currentChannelId2 = ctx.currentChannelId;
804
+ const msgContainer = container || getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
805
+ if (!save && msgContainer && msgContainer.children.length > 200) {
806
+ const toRemove = msgContainer.children.length - 200;
807
+ for (let i = 0; i < toRemove; i++) {
808
+ const first = msgContainer.firstElementChild;
809
+ if (first) msgContainer.removeChild(first);
810
+ }
811
+ }
812
+ if (save) {
813
+ const lastContent = type === "user" ? lastUserCommand : lastAiContent;
814
+ if (lastContent && content === lastContent) {
815
+ console.log(`[addMessage] \u8DF3\u8FC7\u91CD\u590D\u7684 ${type} \u6D88\u606F`);
816
+ return;
817
+ }
818
+ if (type === "user") lastUserCommand = content;
819
+ else lastAiContent = content;
820
+ }
821
+ const div = document.createElement("div");
822
+ div.className = `message message-${type}`;
823
+ let cleanContent = content;
824
+ if (type === "ai") {
825
+ cleanContent = cleanContent.replace(/<think>[\s\S]*?<\/think>/g, "");
826
+ const finalGenIdx = cleanContent.indexOf("<final gen>");
827
+ if (finalGenIdx >= 0) {
828
+ cleanContent = cleanContent.substring(0, finalGenIdx).trim();
829
+ }
830
+ }
831
+ const knownToolNames = ctx && ctx.knownToolNames || /* @__PURE__ */ new Set();
832
+ const segments = segmentChatReply(cleanContent, { knownToolNames });
833
+ if (segments.length === 0) {
834
+ return;
835
+ }
836
+ let thinkContainer = null;
837
+ let renderedAny = false;
838
+ for (const seg of segments) {
839
+ if (seg.type === "think" && seg.content) {
840
+ thinkContainer = buildThinkContainer(seg.content);
841
+ div.appendChild(thinkContainer);
842
+ renderedAny = true;
843
+ } else if (seg.type === "env_details" && seg.content) {
844
+ div.appendChild(buildEnvContainer(seg.content));
845
+ renderedAny = true;
846
+ } else if (seg.type === "text" && seg.content) {
847
+ if (thinkContainer) div.appendChild(thinkContainer);
848
+ thinkContainer = null;
849
+ div.appendChild(buildBubble(seg.content, type));
850
+ renderedAny = true;
851
+ } else if (seg.type === "final" && seg.content) {
852
+ if (thinkContainer) div.appendChild(thinkContainer);
853
+ thinkContainer = null;
854
+ const finalEl = buildBubble(seg.content, type);
855
+ finalEl.classList.add("bubble-final");
856
+ div.appendChild(finalEl);
857
+ renderedAny = true;
858
+ } else if (seg.type === "tool_call" && seg.tool) {
859
+ if (ctx && ctx.toolCallCallback) {
860
+ ctx.toolCallCallback(seg.tool, div);
861
+ }
862
+ renderedAny = true;
863
+ }
864
+ }
865
+ if (!renderedAny) {
866
+ return;
867
+ }
868
+ const rawContent = segments.filter((s) => s.type === "text" || s.type === "final").map((s) => s.content || "").join("\n");
869
+ const time = document.createElement("div");
870
+ time.className = "time";
871
+ time.textContent = (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" });
872
+ if (type === "ai") {
873
+ div.appendChild(buildMessageActions(div, rawContent, ctx));
874
+ }
875
+ if (type === "ai" && Array.isArray(usedJudgmentIds) && usedJudgmentIds.length > 0) {
876
+ const link = document.createElement("a");
877
+ link.className = "used-judgments-link";
878
+ link.textContent = `\u{1F4CE} \u53C2\u8003 ${usedJudgmentIds.length} \u6761\u539F\u5219`;
879
+ link.onclick = (e) => {
880
+ e.preventDefault();
881
+ if (typeof ctx.openJudgmentsModalWithFilter === "function") {
882
+ ctx.openJudgmentsModalWithFilter(usedJudgmentIds);
883
+ }
884
+ };
885
+ div.appendChild(link);
886
+ }
887
+ if (type === "ai" && msgContainer) {
888
+ mountStepTimeline(div, currentChannelId2);
889
+ }
890
+ div.appendChild(time);
891
+ if (msgContainer) {
892
+ msgContainer.appendChild(div);
893
+ scheduleScrollToBottom(msgContainer);
894
+ }
895
+ }
896
+ function buildThinkContainer(thinkContent) {
897
+ const container = document.createElement("div");
898
+ container.className = "think-container";
899
+ const toggle = document.createElement("div");
900
+ toggle.className = "think-toggle";
901
+ toggle.innerHTML = '\u{1F4AD} \u601D\u8003\u8FC7\u7A0B <span class="think-arrow">\u25B8</span>';
902
+ toggle.onclick = function() {
903
+ const details = container.querySelector(".think-content");
904
+ const arrow = toggle.querySelector(".think-arrow");
905
+ if (!details || !arrow) return;
906
+ if (details.style.display === "none") {
907
+ details.style.display = "block";
908
+ arrow.textContent = "\u25BE";
909
+ } else {
910
+ details.style.display = "none";
911
+ arrow.textContent = "\u25B8";
912
+ }
913
+ };
914
+ const content = document.createElement("div");
915
+ content.className = "think-content";
916
+ content.style.display = "none";
917
+ content.innerHTML = `<pre>${escapeHtml(thinkContent)}</pre>`;
918
+ container.appendChild(toggle);
919
+ container.appendChild(content);
920
+ return container;
921
+ }
922
+ function buildEnvContainer(envDetails) {
923
+ const container = document.createElement("div");
924
+ container.className = "env-container";
925
+ const toggle = document.createElement("div");
926
+ toggle.className = "env-toggle";
927
+ toggle.innerHTML = '\u2699\uFE0F \u73AF\u5883\u4FE1\u606F <span class="env-arrow">\u25B8</span>';
928
+ toggle.onclick = function() {
929
+ const details = container.querySelector(".environment-details");
930
+ const arrow = toggle.querySelector(".env-arrow");
931
+ if (!details || !arrow) return;
932
+ if (details.style.display === "none") {
933
+ details.style.display = "block";
934
+ arrow.textContent = "\u25BE";
935
+ } else {
936
+ details.style.display = "none";
937
+ arrow.textContent = "\u25B8";
938
+ }
939
+ };
940
+ const content = document.createElement("div");
941
+ content.className = "environment-details";
942
+ content.style.display = "none";
943
+ content.innerHTML = `<pre>${escapeHtml(envDetails)}</pre>`;
944
+ container.appendChild(toggle);
945
+ container.appendChild(content);
946
+ return container;
947
+ }
948
+ function buildBubble(text, type) {
949
+ const bubble = document.createElement("div");
950
+ bubble.className = `bubble bubble-${type}`;
951
+ const marked2 = window.marked;
952
+ bubble.innerHTML = marked2 ? marked2.parse(text) : escapeHtml(text);
953
+ return bubble;
954
+ }
955
+ function buildMessageActions(div, rawContent, ctx) {
956
+ const actions = document.createElement("div");
957
+ actions.className = "message-actions";
958
+ const copyBtn = document.createElement("button");
959
+ copyBtn.className = "action-btn copy-btn";
960
+ copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
961
+ copyBtn.title = "\u590D\u5236\u6D88\u606F";
962
+ copyBtn.onclick = () => {
963
+ navigator.clipboard.writeText(rawContent).then(() => {
964
+ copyBtn.innerHTML = checkIcon() + " \u5DF2\u590D\u5236";
965
+ setTimeout(() => {
966
+ copyBtn.innerHTML = copyIcon() + " \u590D\u5236";
967
+ }, 2e3);
968
+ });
969
+ };
970
+ actions.appendChild(copyBtn);
971
+ const saveJudgmentBtn = document.createElement("button");
972
+ saveJudgmentBtn.className = "action-btn save-as-judgment";
973
+ saveJudgmentBtn.title = "AI \u84B8\u998F\u4E3A 30-80 \u5B57\u5224\u65AD\u529B + \u81EA\u52A8\u6F14\u5316\u5BF9\u9F50";
974
+ saveJudgmentBtn.setAttribute("data-decision", rawContent.substring(0, 800));
975
+ if (ctx.currentChannelId) saveJudgmentBtn.setAttribute("data-channel-id", ctx.currentChannelId);
976
+ saveJudgmentBtn.innerHTML = shieldIcon() + " \u84B8\u998F\u4E3A\u5224\u65AD";
977
+ actions.appendChild(saveJudgmentBtn);
978
+ const regenBtn = document.createElement("button");
979
+ regenBtn.className = "action-btn regenerate-btn";
980
+ regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
981
+ regenBtn.title = "\u91CD\u65B0\u751F\u6210\u56DE\u590D";
982
+ regenBtn.onclick = () => {
983
+ regenBtn.innerHTML = refreshIcon(true) + " \u751F\u6210\u4E2D...";
984
+ regenBtn.disabled = true;
985
+ const messages = div.parentElement?.querySelectorAll(".message") || [];
986
+ let lastUserMsg = "";
987
+ for (let i = messages.length - 1; i >= 0; i--) {
988
+ const msg = messages[i];
989
+ if (msg.classList.contains("message-user")) {
990
+ const bubble = msg.querySelector(".bubble");
991
+ if (bubble) {
992
+ lastUserMsg = bubble.textContent || bubble.innerText || "";
993
+ break;
994
+ }
995
+ }
996
+ }
997
+ fetch("/regenerate", {
998
+ method: "POST",
999
+ headers: { "Content-Type": "application/json" },
1000
+ body: JSON.stringify({ channelId: ctx.currentChannelId, userMessage: lastUserMsg })
1001
+ }).then((res) => {
1002
+ if (!res.ok) throw new Error("regenerate failed");
1003
+ }).catch((err) => {
1004
+ console.error("\u91CD\u65B0\u751F\u6210\u5931\u8D25:", err);
1005
+ regenBtn.innerHTML = refreshIcon(false) + " \u5931\u8D25";
1006
+ setTimeout(() => {
1007
+ regenBtn.innerHTML = refreshIcon(false) + " \u91CD\u65B0\u56DE\u7B54";
1008
+ regenBtn.disabled = false;
1009
+ }, 2e3);
1010
+ });
1011
+ };
1012
+ actions.appendChild(regenBtn);
1013
+ return actions;
1014
+ }
1015
+ function copyIcon() {
1016
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
1017
+ }
1018
+ function checkIcon() {
1019
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>';
1020
+ }
1021
+ function shieldIcon() {
1022
+ return '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2L4 6v6c0 5 3.5 9.5 8 10 4.5-.5 8-5 8-10V6l-8-4z"></path><path d="M9 12l2 2 4-4"></path></svg>';
1023
+ }
1024
+ function refreshIcon(spin = false) {
1025
+ const cls = spin ? ' class="spin"' : "";
1026
+ return `<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"${cls}><path d="M21 2v6h-6M3 12a9 9 0 0 1 15-6.7L21 8M3 22v-6h6M21 12a9 9 0 0 1-15 6.7L3 16"></path></svg>`;
1027
+ }
1028
+ function handleStreamTokenEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
1029
+ const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
1030
+ const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
1031
+ const currentChannelId2 = ctx.currentChannelId;
1032
+ const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
1033
+ if (!container) return;
1034
+ const delta = data.content || "";
1035
+ if (!delta) return;
1036
+ if (!streamingMessageEl || !streamingMessageEl.isConnected) {
1037
+ streamingMessageEl = document.createElement("div");
1038
+ streamingMessageEl.className = "message message-ai message-streaming";
1039
+ streamingTextNode = document.createTextNode("");
1040
+ streamingMessageEl.appendChild(streamingTextNode);
1041
+ streamingText = "";
1042
+ mountStepTimeline(streamingMessageEl, currentChannelId2);
1043
+ container.appendChild(streamingMessageEl);
1044
+ if (typeof ctx.setTimelineState === "function") {
1045
+ ctx.setTimelineState("streaming");
1046
+ }
1047
+ scheduleScrollToBottom(container);
1048
+ }
1049
+ if (data.streamType === "token") {
1050
+ if (streamingTextNode) streamingTextNode.nodeValue = delta;
1051
+ streamingText = delta;
1052
+ } else {
1053
+ if (streamingTextNode) streamingTextNode.appendData(delta);
1054
+ streamingText += delta;
1055
+ }
1056
+ scheduleScrollToBottom(container);
1057
+ }
1058
+ function finalizeTimelineAsMessage(ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
1059
+ const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
1060
+ const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
1061
+ const currentChannelId2 = ctx.currentChannelId;
1062
+ const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
1063
+ if (streamingText.trim().length > 0) {
1064
+ const oldStreamingEl = streamingMessageEl;
1065
+ if (oldStreamingEl && oldStreamingEl.parentNode) {
1066
+ oldStreamingEl.parentNode.removeChild(oldStreamingEl);
1067
+ }
1068
+ addMessage(streamingText, "ai", true, container, ctx.lastUsedJudgmentIds || [], ctx);
1069
+ if (oldStreamingEl && container) {
1070
+ const newAiMsg = container.querySelector(".message-ai:last-of-type");
1071
+ if (newAiMsg && newAiMsg !== oldStreamingEl) {
1072
+ migrateStepTimeline(oldStreamingEl, newAiMsg);
1073
+ }
1074
+ }
1075
+ }
1076
+ streamingMessageEl = null;
1077
+ streamingTextNode = null;
1078
+ streamingText = "";
1079
+ if (typeof ctx.setTimelineState === "function") {
1080
+ ctx.setTimelineState("done");
1081
+ }
1082
+ }
1083
+ function handleStepEvent(data, ctx = { messagesEl: null, messagesContainers: /* @__PURE__ */ new Map(), currentChannelId: null }) {
1084
+ const messagesEl2 = ctx.messagesEl || (typeof document !== "undefined" ? document.getElementById("messages") : null);
1085
+ const messagesContainers2 = ctx.messagesContainers || /* @__PURE__ */ new Map();
1086
+ const currentChannelId2 = ctx.currentChannelId;
1087
+ const container = getMessagesContainerForCurrent(currentChannelId2, messagesContainers2, messagesEl2);
1088
+ if (!container) return;
1089
+ if (!data || !data.type) return;
1090
+ let target = streamingMessageEl && streamingMessageEl.isConnected ? streamingMessageEl : null;
1091
+ if (!target) {
1092
+ const aiMsgs = container.querySelectorAll(".message-ai");
1093
+ if (aiMsgs.length === 0) return;
1094
+ target = aiMsgs[aiMsgs.length - 1];
1095
+ }
1096
+ if (!target) return;
1097
+ const timeline = getStepTimeline(target);
1098
+ if (!timeline) return;
1099
+ pushStepToTimeline(timeline, data.type, {
1100
+ tool: data.tool || "unknown",
1101
+ args: data.args,
1102
+ success: data.success,
1103
+ output: data.output,
1104
+ error: data.error
1105
+ });
1106
+ }
1107
+ function resetRendererState() {
1108
+ streamingMessageEl = null;
1109
+ streamingTextNode = null;
1110
+ streamingText = "";
1111
+ lastUserCommand = "";
1112
+ lastAiContent = "";
1113
+ if (scrollToBottomTimer) {
1114
+ clearTimeout(scrollToBottomTimer);
1115
+ scrollToBottomTimer = null;
1116
+ }
1117
+ }
1118
+ var streamingMessageEl, streamingTextNode, streamingText, lastUserCommand, lastAiContent, scrollToBottomTimer, MessageRenderer;
1119
+ var init_message_renderer = __esm({
1120
+ "src/web/ui/message-renderer.ts"() {
1121
+ "use strict";
1122
+ init_step_timeline();
1123
+ init_chat_segmenter();
1124
+ streamingMessageEl = null;
1125
+ streamingTextNode = null;
1126
+ streamingText = "";
1127
+ lastUserCommand = "";
1128
+ lastAiContent = "";
1129
+ scrollToBottomTimer = null;
1130
+ MessageRenderer = {
1131
+ addMessage,
1132
+ handleStreamTokenEvent,
1133
+ finalizeTimelineAsMessage,
1134
+ handleStepEvent,
1135
+ escapeHtml,
1136
+ getMessagesContainerForCurrent,
1137
+ resetRendererState
1138
+ };
1139
+ if (typeof window !== "undefined") {
1140
+ window.MR = MessageRenderer;
1141
+ }
1142
+ }
1143
+ });
1144
+
1145
+ // src/web/client-loop-status.ts
1146
+ var client_loop_status_exports = {};
1147
+ __export(client_loop_status_exports, {
1148
+ applyLoopBarState: () => applyLoopBarState,
1149
+ hideLoopStatusBar: () => hideLoopStatusBar,
1150
+ inspectLoopResult: () => inspectLoopResult,
1151
+ markLoopBarDone: () => markLoopBarDone,
1152
+ openLoopInspectModal: () => openLoopInspectModal,
1153
+ renderLoopStatusBar: () => renderLoopStatusBar
1154
+ });
1155
+ function renderLoopStatusBar(tool, content) {
1156
+ if (!loopStatusBar || !loopStatusText) return;
1157
+ const t = String(tool || "").toLowerCase();
1158
+ if (!LOOP_STATUS_TOOLS.has(t)) {
1159
+ console.log("[SSE] status (tool=" + t + ", ignored by UI):", content?.slice(0, 80));
1160
+ return;
1161
+ }
1162
+ const retryMatch = String(content || "").match(/自动重试(?: loop)?\s+(\d+)\/(\d+)/);
1163
+ const retryFinal = /自动重试\s+\d+\s*次后仍失败/.test(String(content || ""));
1164
+ loopStatusBar.hidden = false;
1165
+ const isPivotLoopMsg = /🔄\s*循环\s*\d+\s*\/\s*\d+/.test(String(content || ""));
1166
+ let mainText;
1167
+ if (isPivotLoopMsg && !retryMatch) {
1168
+ mainText = "";
1169
+ } else {
1170
+ mainText = String(content || "").replace(/^[\u{1F000}-\u{1FFFF}\u{2600}-\u{27BF}]\s*/u, "").replace(/^↻\s*/, "").replace(/^⛔\s*/, "").replace(/^⚠️\s*/, "").slice(0, 200);
1171
+ }
1172
+ loopStatusText.textContent = mainText;
1173
+ if (retryMatch) {
1174
+ loopBarState = "retrying";
1175
+ const retryEl = document.getElementById("loop-status-retry");
1176
+ if (retryEl) {
1177
+ retryEl.hidden = false;
1178
+ retryEl.textContent = `\u81EA\u52A8\u91CD\u8BD5 ${retryMatch[1]}/${retryMatch[2]}`;
1179
+ }
1180
+ } else if (retryFinal) {
1181
+ loopBarState = "done";
1182
+ const retryEl = document.getElementById("loop-status-retry");
1183
+ if (retryEl) retryEl.hidden = true;
1184
+ } else {
1185
+ if (loopBarState !== "loading") loopBarState = "loading";
1186
+ const retryEl = document.getElementById("loop-status-retry");
1187
+ if (retryEl) retryEl.hidden = true;
1188
+ }
1189
+ applyLoopBarState();
1190
+ }
1191
+ function markLoopBarDone(summary) {
1192
+ loopBarState = "done";
1193
+ if (summary) loopBarLastSummary = summary;
1194
+ applyLoopBarState();
1195
+ }
1196
+ function applyLoopBarState() {
1197
+ if (!loopStatusBar) return;
1198
+ loopStatusBar.dataset.state = loopBarState;
1199
+ const checkBtn = document.getElementById("loop-status-check");
1200
+ if (checkBtn) checkBtn.hidden = loopBarState !== "done";
1201
+ }
1202
+ function hideLoopStatusBar() {
1203
+ if (!loopStatusBar) return;
1204
+ loopStatusBar.hidden = true;
1205
+ loopBarState = "loading";
1206
+ loopBarLastSummary = "";
1207
+ const retryEl = document.getElementById("loop-status-retry");
1208
+ if (retryEl) retryEl.hidden = true;
1209
+ applyLoopBarState();
1210
+ }
1211
+ async function inspectLoopResult() {
1212
+ const checkBtn = document.getElementById("loop-status-check");
1213
+ if (checkBtn) {
1214
+ checkBtn.disabled = true;
1215
+ checkBtn.textContent = "\u23F3 \u52A0\u8F7D...";
1216
+ }
1217
+ try {
1218
+ const channelId = window.currentChannelId || "";
1219
+ const r = await fetch(`/api/loop/inspect?channelId=${encodeURIComponent(channelId)}`);
1220
+ const j = await r.json().catch(() => ({}));
1221
+ openLoopInspectModal(j);
1222
+ } catch (err) {
1223
+ console.error("[inspect] error:", err);
1224
+ if (typeof window.showSimpleToast === "function") window.showSimpleToast("\u2717 \u68C0\u67E5\u5931\u8D25");
1225
+ } finally {
1226
+ if (checkBtn) {
1227
+ checkBtn.disabled = false;
1228
+ checkBtn.textContent = "\u2713 \u68C0\u67E5";
1229
+ }
1230
+ }
1231
+ }
1232
+ function openLoopInspectModal(data) {
1233
+ const existing = document.getElementById("loop-inspect-modal");
1234
+ if (existing) existing.remove();
1235
+ const modal = document.createElement("div");
1236
+ modal.id = "loop-inspect-modal";
1237
+ modal.className = "modal active";
1238
+ modal.style.cssText = "position:fixed;inset:0;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:1000;";
1239
+ const panel = document.createElement("div");
1240
+ panel.className = "modal-panel";
1241
+ panel.style.cssText = "background:var(--bg);border:1px solid var(--border);border-radius:8px;padding:20px;max-width:720px;width:90%;max-height:80vh;overflow:auto;position:relative;";
1242
+ const title = document.createElement("h3");
1243
+ title.textContent = "\u{1F50D} \u5FAA\u73AF\u68C0\u67E5";
1244
+ title.style.cssText = "margin:0 0 12px;font-size:16px;";
1245
+ panel.appendChild(title);
1246
+ const close = document.createElement("button");
1247
+ close.textContent = "\xD7";
1248
+ close.style.cssText = "position:absolute;top:8px;right:12px;background:transparent;border:0;font-size:24px;cursor:pointer;color:var(--text-secondary);";
1249
+ close.onclick = () => modal.remove();
1250
+ panel.appendChild(close);
1251
+ if (data.error) {
1252
+ const e = document.createElement("div");
1253
+ e.style.cssText = "padding:8px 12px;background:rgba(239,68,68,0.12);color:var(--error,#ef4444);border-radius:4px;margin-bottom:12px;font-size:13px;";
1254
+ e.textContent = "\u26A0\uFE0F " + data.error;
1255
+ panel.appendChild(e);
1256
+ }
1257
+ if (data.summary) {
1258
+ const s = document.createElement("div");
1259
+ s.style.cssText = "padding:8px 12px;background:var(--bg-tertiary);border-radius:4px;margin-bottom:12px;font-size:13px;";
1260
+ s.textContent = data.summary;
1261
+ panel.appendChild(s);
1262
+ }
1263
+ if (data.tokens && (data.tokens.input || data.tokens.output)) {
1264
+ const t = document.createElement("div");
1265
+ t.style.cssText = "font-size:12px;color:var(--text-muted);margin-bottom:12px;";
1266
+ t.textContent = `token: input ${data.tokens.input || 0} \xB7 output ${data.tokens.output || 0}`;
1267
+ panel.appendChild(t);
1268
+ }
1269
+ if (Array.isArray(data.steps) && data.steps.length > 0) {
1270
+ const h = document.createElement("div");
1271
+ h.textContent = `\u6B65\u9AA4 (${data.steps.length})`;
1272
+ h.style.cssText = "font-weight:600;margin-bottom:8px;";
1273
+ panel.appendChild(h);
1274
+ for (const step of data.steps) {
1275
+ const row = document.createElement("div");
1276
+ row.style.cssText = "padding:6px 10px;margin-bottom:4px;background:var(--bg-secondary);border-left:3px solid var(--accent);border-radius:3px;font-size:12px;";
1277
+ const icon = step.status === "ok" || step.status === "completed" ? "\u2713" : step.status === "error" || step.status === "failed" ? "\u2717" : "\u25CB";
1278
+ const dur = step.durationMs ? ` (${(step.durationMs / 1e3).toFixed(1)}s)` : "";
1279
+ row.innerHTML = `<b>${icon} ${window.escapeHtml ? window.escapeHtml(step.name) : step.name}</b>${dur}`;
1280
+ if (step.output) {
1281
+ const pre = document.createElement("pre");
1282
+ pre.style.cssText = "margin:4px 0 0;padding:6px;background:var(--bg);border-radius:3px;font-size:11px;white-space:pre-wrap;word-break:break-word;max-height:120px;overflow:auto;";
1283
+ pre.textContent = String(step.output).slice(0, 800);
1284
+ row.appendChild(pre);
1285
+ }
1286
+ panel.appendChild(row);
1287
+ }
1288
+ }
1289
+ if (data.finalReply) {
1290
+ const h = document.createElement("div");
1291
+ h.textContent = "\u6700\u7EC8\u56DE\u590D";
1292
+ h.style.cssText = "font-weight:600;margin:12px 0 8px;";
1293
+ panel.appendChild(h);
1294
+ const r = document.createElement("div");
1295
+ r.style.cssText = "padding:8px 12px;background:var(--bg-secondary);border-radius:4px;font-size:13px;white-space:pre-wrap;word-break:break-word;";
1296
+ r.textContent = data.finalReply;
1297
+ panel.appendChild(r);
1298
+ }
1299
+ if (!data.error && !data.summary && (!data.steps || data.steps.length === 0) && !data.finalReply) {
1300
+ const empty = document.createElement("div");
1301
+ empty.style.cssText = "text-align:center;padding:24px;color:var(--text-muted);font-size:13px;";
1302
+ empty.textContent = "\u65E0\u5FAA\u73AF\u4EA7\u51FA (\u53EF\u80FD\u5DF2 abort, \u6216\u6CA1\u4EA7\u751F step)";
1303
+ panel.appendChild(empty);
1304
+ }
1305
+ modal.appendChild(panel);
1306
+ modal.onclick = (e) => {
1307
+ if (e.target === modal) modal.remove();
1308
+ };
1309
+ document.body.appendChild(modal);
1310
+ }
1311
+ var LOOP_STATUS_TOOLS, loopBarState, loopBarLastSummary, loopStatusBar, loopStatusText, loopStatusMeta, LoopStatusExports;
1312
+ var init_client_loop_status = __esm({
1313
+ "src/web/client-loop-status.ts"() {
1314
+ "use strict";
1315
+ LOOP_STATUS_TOOLS = /* @__PURE__ */ new Set(["loop", "compactor", "recovery", "system"]);
1316
+ loopBarState = "loading";
1317
+ loopBarLastSummary = "";
1318
+ loopStatusBar = document.getElementById("loop-status-bar");
1319
+ loopStatusText = document.getElementById("loop-status-text");
1320
+ loopStatusMeta = document.getElementById("loop-status-meta");
1321
+ LoopStatusExports = {
1322
+ renderLoopStatusBar,
1323
+ markLoopBarDone,
1324
+ applyLoopBarState,
1325
+ hideLoopStatusBar,
1326
+ inspectLoopResult,
1327
+ openLoopInspectModal,
1328
+ get loopBarState() {
1329
+ return loopBarState;
1330
+ },
1331
+ get loopBarLastSummary() {
1332
+ return loopBarLastSummary;
1333
+ }
1334
+ };
1335
+ if (typeof window !== "undefined") {
1336
+ window.LoopStatus = LoopStatusExports;
1337
+ }
1338
+ if (typeof module !== "undefined" && module.exports) {
1339
+ module.exports = LoopStatusExports;
1340
+ }
1341
+ }
1342
+ });
1343
+
1344
+ // src/web/util/safe-name.ts
1345
+ function safeChannelName(input2, fallback) {
1346
+ return safeNameInternal(input2, fallback ?? "(\u672A\u547D\u540D)", ["undefined", "null", "NaN"]);
1347
+ }
1348
+ function safeNameInternal(input2, fallback, invalidLiterals) {
1349
+ if (input2 === void 0 || input2 === null) return fallback;
1350
+ if (typeof input2 === "number" && Number.isNaN(input2)) return fallback;
1351
+ const s = String(input2).trim();
1352
+ if (!s) return fallback;
1353
+ for (const lit of invalidLiterals) {
1354
+ if (s === lit) return fallback;
1355
+ }
1356
+ return s;
1357
+ }
1358
+
1359
+ // src/web/client.ts
4
1360
  if (typeof marked === "undefined") {
5
1361
  window.marked = { parse: (text) => String(text).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\n/g, "<br>") };
6
1362
  }
@@ -19,9 +1375,9 @@
19
1375
  return orig(...args);
20
1376
  };
21
1377
  })();
22
- let MR = {};
1378
+ var MR = {};
23
1379
  try {
24
- if (typeof require !== "undefined") MR = require("./ui/message-renderer.js") || {};
1380
+ if (typeof __require !== "undefined") MR = (init_message_renderer(), __toCommonJS(message_renderer_exports)) || {};
25
1381
  } catch (e) {
26
1382
  }
27
1383
  function _getMR() {
@@ -29,13 +1385,13 @@
29
1385
  if (typeof window !== "undefined" && window.MR) return window.MR;
30
1386
  return {};
31
1387
  }
32
- const MR_addMessage = (...args) => _getMR().addMessage?.(...args);
33
- const MR_finalizeTimelineAsMessage = (...args) => _getMR().finalizeTimelineAsMessage?.(...args);
34
- const MR_handleStepEvent = (...args) => _getMR().handleStepEvent?.(...args);
35
- const MR_escapeHtml = (s) => _getMR().escapeHtml?.(s);
36
- const MR_hasStreamingText = () => _getMR().hasStreamingText?.() ?? false;
37
- const MR_replaceStreamingText = (text) => _getMR().replaceStreamingText?.(text);
38
- const MR_injectRecoveredText = (text, ctx) => _getMR().injectRecoveredText?.(text, ctx ?? getRendererCtx());
1388
+ var MR_addMessage = (...args) => _getMR().addMessage?.(...args);
1389
+ var MR_finalizeTimelineAsMessage = (...args) => _getMR().finalizeTimelineAsMessage?.(...args);
1390
+ var MR_handleStepEvent = (...args) => _getMR().handleStepEvent?.(...args);
1391
+ var MR_escapeHtml = (s) => _getMR().escapeHtml?.(s);
1392
+ var MR_hasStreamingText = () => _getMR().hasStreamingText?.() ?? false;
1393
+ var MR_replaceStreamingText = (text) => _getMR().replaceStreamingText?.(text);
1394
+ var MR_injectRecoveredText = (text, ctx) => _getMR().injectRecoveredText?.(text, ctx ?? getRendererCtx());
39
1395
  function getRendererCtx() {
40
1396
  return {
41
1397
  messagesEl,
@@ -46,13 +1402,13 @@
46
1402
  // 引用 client.js 函数, 通过参数注入避免循环 import
47
1403
  };
48
1404
  }
49
- const messagesEl = document.getElementById("messages");
50
- const input = document.getElementById("input");
51
- const sendBtn = document.getElementById("send");
52
- const sidebar = document.getElementById("sidebar");
53
- let LS = {};
1405
+ var messagesEl = document.getElementById("messages");
1406
+ var input = document.getElementById("input");
1407
+ var sendBtn = document.getElementById("send");
1408
+ var sidebar = document.getElementById("sidebar");
1409
+ var LS = {};
54
1410
  try {
55
- if (typeof require !== "undefined") LS = require("./client-loop-status.js") || {};
1411
+ if (typeof __require !== "undefined") LS = (init_client_loop_status(), __toCommonJS(client_loop_status_exports)) || {};
56
1412
  } catch (e) {
57
1413
  }
58
1414
  function _getLS() {
@@ -60,28 +1416,28 @@
60
1416
  if (typeof window !== "undefined" && window.LoopStatus) return window.LoopStatus;
61
1417
  return {};
62
1418
  }
63
- const renderLoopStatusBar = (...args) => _getLS().renderLoopStatusBar?.(...args);
64
- const hideLoopStatusBar = (...args) => _getLS().hideLoopStatusBar?.(...args);
65
- const sidebarToggle = document.getElementById("sidebar-toggle");
66
- const themeToggle = document.getElementById("theme-toggle");
67
- const channelList = document.getElementById("channel-list");
68
- const newChannelBtn = document.getElementById("new-channel-btn");
69
- const newChannelInput = document.getElementById("new-channel-input");
70
- const channelNameEl = document.getElementById("channel-name");
71
- let eventSources = /* @__PURE__ */ new Map();
72
- let currentChannelId = null;
73
- let currentAgentId = "";
74
- let channels = [];
75
- let remoteChannels = [];
76
- let isSidebarCollapsed = false;
77
- let reconnectAttempts = /* @__PURE__ */ new Map();
78
- let reconnectTimers = /* @__PURE__ */ new Map();
79
- let heartbeatTimers = /* @__PURE__ */ new Map();
80
- const lastKnownSeq = /* @__PURE__ */ new Map();
81
- const lastSeenMsgIds = /* @__PURE__ */ new Map();
82
- const COLLAPSED_PEERS_KEY = "bolloon.p2p.collapsedPeers";
83
- const SEEN_PEERS_KEY = "bolloon.p2p.seenPeers";
84
- let collapsedPeers = function loadCollapsed() {
1419
+ var renderLoopStatusBar2 = (...args) => _getLS().renderLoopStatusBar?.(...args);
1420
+ var hideLoopStatusBar2 = (...args) => _getLS().hideLoopStatusBar?.(...args);
1421
+ var sidebarToggle = document.getElementById("sidebar-toggle");
1422
+ var themeToggle = document.getElementById("theme-toggle");
1423
+ var channelList = document.getElementById("channel-list");
1424
+ var newChannelBtn = document.getElementById("new-channel-btn");
1425
+ var newChannelInput = document.getElementById("new-channel-input");
1426
+ var channelNameEl = document.getElementById("channel-name");
1427
+ var eventSources = /* @__PURE__ */ new Map();
1428
+ var currentChannelId = null;
1429
+ var currentAgentId = "";
1430
+ var channels = [];
1431
+ var remoteChannels = [];
1432
+ var isSidebarCollapsed = false;
1433
+ var reconnectAttempts = /* @__PURE__ */ new Map();
1434
+ var reconnectTimers = /* @__PURE__ */ new Map();
1435
+ var heartbeatTimers = /* @__PURE__ */ new Map();
1436
+ var lastKnownSeq = /* @__PURE__ */ new Map();
1437
+ var lastSeenMsgIds = /* @__PURE__ */ new Map();
1438
+ var COLLAPSED_PEERS_KEY = "bolloon.p2p.collapsedPeers";
1439
+ var SEEN_PEERS_KEY = "bolloon.p2p.seenPeers";
1440
+ var collapsedPeers = function loadCollapsed() {
85
1441
  try {
86
1442
  const raw = localStorage.getItem(COLLAPSED_PEERS_KEY);
87
1443
  return new Set(raw ? JSON.parse(raw) : []);
@@ -89,7 +1445,7 @@
89
1445
  return /* @__PURE__ */ new Set();
90
1446
  }
91
1447
  }();
92
- let seenPeers = function loadSeen() {
1448
+ var seenPeers = function loadSeen() {
93
1449
  try {
94
1450
  const raw = localStorage.getItem(SEEN_PEERS_KEY);
95
1451
  return new Set(raw ? JSON.parse(raw) : []);
@@ -139,10 +1495,10 @@
139
1495
  renderRemoteChannels();
140
1496
  if (typeof window.__syncP2PToggleAllBtn === "function") window.__syncP2PToggleAllBtn();
141
1497
  }
142
- let messagesContainers = /* @__PURE__ */ new Map();
143
- let sessionMessages = /* @__PURE__ */ new Map();
144
- let currentSessionId = null;
145
- let expandedAgents = /* @__PURE__ */ new Set();
1498
+ var messagesContainers = /* @__PURE__ */ new Map();
1499
+ var sessionMessages = /* @__PURE__ */ new Map();
1500
+ var currentSessionId = null;
1501
+ var expandedAgents = /* @__PURE__ */ new Set();
146
1502
  function generateId() {
147
1503
  return crypto.randomUUID();
148
1504
  }
@@ -205,7 +1561,7 @@
205
1561
  console.error("[\u52A0\u8F7D\u9891\u9053] \u5931\u8D25:", err);
206
1562
  }
207
1563
  }
208
- let v3GlobalEventSource = null;
1564
+ var v3GlobalEventSource = null;
209
1565
  function startV3GlobalSSE() {
210
1566
  if (v3GlobalEventSource) return;
211
1567
  try {
@@ -229,7 +1585,7 @@
229
1585
  const prefix = `\u{1F916} \u8FDC\u7AEF AI \u56DE\u590D
230
1586
 
231
1587
  `;
232
- addMessage(prefix + (msg.text || "(\u7A7A\u56DE\u590D)"), "ai", false, log);
1588
+ addMessage2(prefix + (msg.text || "(\u7A7A\u56DE\u590D)"), "ai", false, log);
233
1589
  }
234
1590
  log.scrollTop = log.scrollHeight;
235
1591
  } else {
@@ -251,7 +1607,7 @@
251
1607
  if (judgments.bound && judgments.bound.length > 0) {
252
1608
  jh += '<div style="color:#78350f;margin-bottom:4px;"><b>\u786C\u7ED1\u5B9A</b> (\u5FC5\u987B\u9075\u5FAA):</div>';
253
1609
  for (const j of judgments.bound) {
254
- jh += `<div style="margin:2px 0;padding-left:8px;">\u2022 <b>${escapeHtml((j.decision || "").slice(0, 80))}</b>${j.reasons && j.reasons.length ? '<br><span style="color:#92400e;font-size:11px;">\u7406\u7531: ' + escapeHtml(j.reasons.join("; ").slice(0, 80)) + "</span>" : ""}</div>`;
1610
+ jh += `<div style="margin:2px 0;padding-left:8px;">\u2022 <b>${escapeHtml2((j.decision || "").slice(0, 80))}</b>${j.reasons && j.reasons.length ? '<br><span style="color:#92400e;font-size:11px;">\u7406\u7531: ' + escapeHtml2(j.reasons.join("; ").slice(0, 80)) + "</span>" : ""}</div>`;
255
1611
  }
256
1612
  }
257
1613
  if (judgments.candidates && judgments.candidates.length > 0) {
@@ -278,7 +1634,7 @@
278
1634
  const toast = document.createElement("div");
279
1635
  toast.style.cssText = "margin:6px 0;padding:8px 10px;background:#fce7f3;border-left:3px solid #ec4899;border-radius:4px;font-size:12px;color:#831843;";
280
1636
  const fromTxt = msg.source === "ai-mention-remote" ? `\u8FDC\u7AEF\u8282\u70B9 ${(msg.fromPublicKey || "").substring(0, 8)}\u2026 \u7684 ${msg.originChannelName}` : `${msg.originChannelName} (\u672C\u5730)`;
281
- toast.innerHTML = `\u{1F4E1} <b>${fromTxt}</b> @-mention \u2192 \u5F53\u524D channel: <i>${escapeHtml((msg.text || "").slice(0, 100))}</i>${msg.text && msg.text.length > 100 ? "\u2026" : ""}`;
1637
+ toast.innerHTML = `\u{1F4E1} <b>${fromTxt}</b> @-mention \u2192 \u5F53\u524D channel: <i>${escapeHtml2((msg.text || "").slice(0, 100))}</i>${msg.text && msg.text.length > 100 ? "\u2026" : ""}`;
282
1638
  log.appendChild(toast);
283
1639
  log.scrollTop = log.scrollHeight;
284
1640
  }
@@ -363,7 +1719,7 @@
363
1719
  currentSessionId = null;
364
1720
  if (currentChannelId) {
365
1721
  const ch = channels.find((c) => c.id === currentChannelId);
366
- if (channelNameEl) channelNameEl.textContent = (0, import_safe_name.safeChannelName)(ch?.name, "Bolloon Agent");
1722
+ if (channelNameEl) channelNameEl.textContent = safeChannelName(ch?.name, "Bolloon Agent");
367
1723
  await selectChannel(currentChannelId);
368
1724
  } else {
369
1725
  messagesEl.innerHTML = "";
@@ -427,7 +1783,7 @@
427
1783
  if (container) {
428
1784
  container.innerHTML = "";
429
1785
  showChannelView(currentChannelId);
430
- addMessage("\u4F60\u597D\uFF01\u65B0\u4F1A\u8BDD\u5DF2\u5F00\u59CB\uFF0C\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
1786
+ addMessage2("\u4F60\u597D\uFF01\u65B0\u4F1A\u8BDD\u5DF2\u5F00\u59CB\uFF0C\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
431
1787
  }
432
1788
  expandedAgents.add(currentChannelId);
433
1789
  renderChannels();
@@ -514,8 +1870,8 @@
514
1870
  console.error("Failed to delete session:", err);
515
1871
  }
516
1872
  }
517
- let _saveSessionMessagesDirty = false;
518
- let _saveSessionMessagesTimer = null;
1873
+ var _saveSessionMessagesDirty = false;
1874
+ var _saveSessionMessagesTimer = null;
519
1875
  function saveCurrentSessionMessages() {
520
1876
  if (!currentChannelId || !currentSessionId) return;
521
1877
  _saveSessionMessagesDirty = true;
@@ -540,8 +1896,8 @@
540
1896
  scheduleChannelsRefresh();
541
1897
  await new Promise((r) => setTimeout(r, 600));
542
1898
  }
543
- let channelRefreshTimer = null;
544
- let channelRefreshInFlight = null;
1899
+ var channelRefreshTimer = null;
1900
+ var channelRefreshInFlight = null;
545
1901
  function scheduleChannelsRefresh() {
546
1902
  if (channelRefreshTimer) return;
547
1903
  channelRefreshTimer = setTimeout(async () => {
@@ -637,12 +1993,12 @@
637
1993
  <polyline points="9 18 15 12 9 6"></polyline>
638
1994
  </svg>
639
1995
  <div class="channel-icon">\u{1F4AC}</div>
640
- <span class="channel-name" title="${escapeHtml((0, import_safe_name.safeChannelName)(ch.name, ""))}">${escapeHtml((0, import_safe_name.safeChannelName)(ch.name))}</span>
1996
+ <span class="channel-name" title="${escapeHtml2(safeChannelName(ch.name, ""))}">${escapeHtml2(safeChannelName(ch.name))}</span>
641
1997
  <span class="agent-row-meta">
642
1998
  ${walletBadge2}
643
1999
  ${toolsBadge}
644
2000
  ${sessionCount > 1 ? `<span class="agent-session-count" title="${sessionCount} \u4E2A\u4F1A\u8BDD">${sessionCount}</span>` : ""}
645
- ${currentSessLabel ? `<span class="agent-current-session" title="\u5F53\u524D\u4F1A\u8BDD\uFF1A${escapeHtml(currentSessLabel)}">\xB7 ${escapeHtml(currentSessLabel)}</span>` : ""}
2001
+ ${currentSessLabel ? `<span class="agent-current-session" title="\u5F53\u524D\u4F1A\u8BDD\uFF1A${escapeHtml2(currentSessLabel)}">\xB7 ${escapeHtml2(currentSessLabel)}</span>` : ""}
646
2002
  <button class="agent-config-btn" title="\u914D\u7F6E\u667A\u80FD\u4F53 (\u94B1\u5305 / \u5DE5\u5177)">
647
2003
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
648
2004
  <circle cx="12" cy="12" r="3"></circle>
@@ -704,7 +2060,7 @@
704
2060
  sessLi.className = `session-item ${isActive ? "active" : ""}`;
705
2061
  sessLi.dataset.sessionId = sess.id;
706
2062
  sessLi.innerHTML = `
707
- <span class="session-name" title="${escapeHtml(formatSessionName(sess))}">${escapeHtml(formatSessionName(sess))}</span>
2063
+ <span class="session-name" title="${escapeHtml2(formatSessionName(sess))}">${escapeHtml2(formatSessionName(sess))}</span>
708
2064
  <button class="session-delete" title="\u5220\u9664\u4F1A\u8BDD">\xD7</button>
709
2065
  `;
710
2066
  sessLi.addEventListener("click", (ev) => {
@@ -743,7 +2099,7 @@
743
2099
  const id = sess.id || "";
744
2100
  return id ? `\u4F1A\u8BDD ${id.slice(-6)}` : "\u65B0\u4F1A\u8BDD";
745
2101
  }
746
- const escapeHtml = MR_escapeHtml || ((s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({
2102
+ var escapeHtml2 = MR_escapeHtml || ((s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({
747
2103
  "&": "&amp;",
748
2104
  "<": "&lt;",
749
2105
  ">": "&gt;",
@@ -779,7 +2135,7 @@
779
2135
  }
780
2136
  const channel = channels.find((c) => c.id === channelId);
781
2137
  if (channel) {
782
- if (channelNameEl) channelNameEl.textContent = (0, import_safe_name.safeChannelName)(channel.name);
2138
+ if (channelNameEl) channelNameEl.textContent = safeChannelName(channel.name);
783
2139
  currentSessionId = targetSessionId || channel.currentSessionId || "default";
784
2140
  if (targetSessionId) {
785
2141
  channel.currentSessionId = targetSessionId;
@@ -806,18 +2162,18 @@
806
2162
  const tmpContainer = document.createElement("div");
807
2163
  tmpContainer.style.display = "none";
808
2164
  for (const msg of msgs) {
809
- addMessage(msg.content, msg.type, false, tmpContainer, msg.metadata?.usedJudgmentIds || []);
2165
+ addMessage2(msg.content, msg.type, false, tmpContainer, msg.metadata?.usedJudgmentIds || []);
810
2166
  }
811
2167
  while (tmpContainer.firstChild) {
812
2168
  frag.appendChild(tmpContainer.firstChild);
813
2169
  }
814
2170
  container.appendChild(frag);
815
2171
  } else {
816
- addMessage("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
2172
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
817
2173
  }
818
2174
  } catch (err) {
819
2175
  console.error("[selectChannel] \u52A0\u8F7D session \u5931\u8D25:", err);
820
- addMessage("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
2176
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
821
2177
  }
822
2178
  }
823
2179
  async function loadSession(channelId, sessionId = null) {
@@ -830,29 +2186,30 @@
830
2186
  container.innerHTML = "";
831
2187
  if (session.messages && session.messages.length > 0) {
832
2188
  session.messages.forEach((msg) => {
833
- addMessage(msg.content, msg.type, false, container, msg.metadata?.usedJudgmentIds || []);
2189
+ addMessage2(msg.content, msg.type, false, container, msg.metadata?.usedJudgmentIds || []);
834
2190
  });
835
2191
  } else {
836
- addMessage("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
2192
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
837
2193
  }
838
2194
  } catch (err) {
839
2195
  console.error("Failed to load session:", err);
840
2196
  container.innerHTML = "";
841
- addMessage("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
2197
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
842
2198
  }
843
2199
  }
844
- function addMessage(content, type, save = true, container, usedJudgmentIds = []) {
2200
+ function addMessage2(content, type, save = true, container, usedJudgmentIds = []) {
845
2201
  return MR_addMessage(content, type, save, container, usedJudgmentIds, getRendererCtx());
846
2202
  }
847
- function finalizeTimelineAsMessage() {
2203
+ function finalizeTimelineAsMessage2() {
848
2204
  return MR_finalizeTimelineAsMessage(getRendererCtx());
849
2205
  }
850
- function handleStepEvent(data) {
2206
+ function handleStepEvent2(data) {
851
2207
  return MR_handleStepEvent(data, getRendererCtx());
852
2208
  }
853
- let lastUsedJudgmentIds = [];
854
- let selfImproveCardSeq = 0;
855
- function getMessagesContainerForCurrent() {
2209
+ var lastUsedJudgmentIds = [];
2210
+ var currentPreviewBubble = null;
2211
+ var selfImproveCardSeq = 0;
2212
+ function getMessagesContainerForCurrent2() {
856
2213
  if (currentChannelId && messagesContainers.get(currentChannelId)) {
857
2214
  return messagesContainers.get(currentChannelId);
858
2215
  }
@@ -885,7 +2242,7 @@
885
2242
  return card;
886
2243
  }
887
2244
  function handleSelfImproveTriggered(data) {
888
- const container = getMessagesContainerForCurrent();
2245
+ const container = getMessagesContainerForCurrent2();
889
2246
  if (!container) return;
890
2247
  const card = makeSelfImproveCard(data);
891
2248
  card.querySelector(".self-improve-title").textContent = `\u{1F9E0} \u81EA\u8FED\u4EE3\u89E6\u53D1 \xB7 ${data.eventKind || "unknown"}`;
@@ -900,7 +2257,7 @@
900
2257
  card.scrollIntoView({ block: "end", behavior: "smooth" });
901
2258
  }
902
2259
  function handleSelfImproveResult(data) {
903
- const container = getMessagesContainerForCurrent();
2260
+ const container = getMessagesContainerForCurrent2();
904
2261
  if (!container) return;
905
2262
  const card = makeSelfImproveCard(data);
906
2263
  const ok = !!data.success;
@@ -1019,14 +2376,14 @@
1019
2376
  const container = messagesContainers.get(targetChannelId) || messagesEl;
1020
2377
  if (msg.type === "ai") {
1021
2378
  if (!MR_hasStreamingText()) {
1022
- addMessage(msg.content, "ai", true, container, lastUsedJudgmentIds || []);
2379
+ addMessage2(msg.content, "ai", true, container, lastUsedJudgmentIds || []);
1023
2380
  } else {
1024
2381
  MR_replaceStreamingText?.(msg.content);
1025
2382
  MR_finalizeTimelineAsMessage(getRendererCtx());
1026
2383
  }
1027
2384
  } else if (msg.type === "user") {
1028
2385
  if (msg.source === "remote" || msg.source === "local") {
1029
- addMessage(msg.content, "user", true, container);
2386
+ addMessage2(msg.content, "user", true, container);
1030
2387
  }
1031
2388
  }
1032
2389
  }
@@ -1107,10 +2464,26 @@
1107
2464
  const container = messagesContainers.get(msgChannelId) || messagesEl;
1108
2465
  if (data.type === "user") {
1109
2466
  if (data.source === "remote") {
1110
- addMessage(data.content, "user", true, container);
2467
+ addMessage2(data.content, "user", true, container);
1111
2468
  }
1112
2469
  } else if (data.type === "ai") {
1113
- addMessage(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
2470
+ addMessage2(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
2471
+ if (currentPreviewBubble) {
2472
+ currentPreviewBubble.remove();
2473
+ currentPreviewBubble = null;
2474
+ }
2475
+ } else if (data.type === "reply-preview") {
2476
+ const previewContent = data.content || "";
2477
+ if (!currentPreviewBubble) {
2478
+ currentPreviewBubble = addMessage2(previewContent, "ai", true, container, []);
2479
+ if (currentPreviewBubble) {
2480
+ currentPreviewBubble.classList.add("preview");
2481
+ }
2482
+ } else {
2483
+ currentPreviewBubble.replaceWith(addMessage2(previewContent, "ai", true, container, []));
2484
+ const msgs = container.querySelectorAll(".message-ai.preview");
2485
+ currentPreviewBubble = msgs.length > 0 ? msgs[msgs.length - 1] : null;
2486
+ }
1114
2487
  } else if (data.type === "stream") {
1115
2488
  if (false) handleStreamTokenEvent(data);
1116
2489
  } else if (data.type === "regenerating") {
@@ -1121,12 +2494,12 @@
1121
2494
  }
1122
2495
  setSendMode("abort");
1123
2496
  } else if (data.type === "status") {
1124
- renderLoopStatusBar(data.tool, data.content);
2497
+ renderLoopStatusBar2(data.tool, data.content);
1125
2498
  } else if (data.type === "step_start" || data.type === "step_done" || data.type === "step_error") {
1126
- handleStepEvent(data);
2499
+ handleStepEvent2(data);
1127
2500
  } else if (data.type === "done") {
1128
- finalizeTimelineAsMessage();
1129
- hideLoopStatusBar();
2501
+ finalizeTimelineAsMessage2();
2502
+ hideLoopStatusBar2();
1130
2503
  setSendMode("idle");
1131
2504
  } else if (data.type === "renamed") {
1132
2505
  const channel = channels.find((c) => c.id === data.channelId);
@@ -1134,18 +2507,18 @@
1134
2507
  channel.name = data.newName;
1135
2508
  renderChannels();
1136
2509
  if (currentChannelId === data.channelId && channelNameEl) {
1137
- channelNameEl.textContent = (0, import_safe_name.safeChannelName)(data.newName);
2510
+ channelNameEl.textContent = safeChannelName(data.newName);
1138
2511
  }
1139
2512
  }
1140
2513
  } else if (data.type === "error") {
1141
2514
  const errContent = String(data.content || "\u672A\u77E5\u9519\u8BEF");
1142
- addMessage(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
2515
+ addMessage2(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
1143
2516
  if (typeof showSimpleToast === "function") {
1144
2517
  showSimpleToast("\u26A0\uFE0F " + errContent.slice(0, 200));
1145
2518
  } else {
1146
2519
  console.error("[SSE] error:", errContent);
1147
2520
  }
1148
- hideLoopStatusBar();
2521
+ hideLoopStatusBar2();
1149
2522
  setSendMode("idle");
1150
2523
  } else if (data.type === "task_status" || data.type === "workflow_step" || data.type === "workflow_loop") {
1151
2524
  if (data.type === "workflow_step" && (data.step === "AI \u601D\u8003" || data.step === "\u5F00\u59CB\u601D\u8003")) {
@@ -1171,11 +2544,15 @@
1171
2544
  async function sendMessage() {
1172
2545
  const text = input.value.trim();
1173
2546
  if (!text) return;
2547
+ setSendMode("abort");
1174
2548
  const container = messagesContainers.get(currentChannelId) || messagesEl;
1175
- addMessage(text, "user", true, container);
2549
+ addMessage2(text, "user", true, container);
1176
2550
  if (container) container.scrollTop = container.scrollHeight;
2551
+ if (currentPreviewBubble) {
2552
+ currentPreviewBubble.remove();
2553
+ currentPreviewBubble = null;
2554
+ }
1177
2555
  input.value = "";
1178
- setSendMode("abort");
1179
2556
  persistLastMessageToServer("user", text);
1180
2557
  const channel = channels.find((c) => c.id === currentChannelId);
1181
2558
  const channelDid = channel?.did || "";
@@ -1191,11 +2568,11 @@
1191
2568
  })
1192
2569
  });
1193
2570
  if (!res.ok) {
1194
- addMessage("\u53D1\u9001\u5931\u8D25", "ai");
2571
+ addMessage2("\u53D1\u9001\u5931\u8D25", "ai");
1195
2572
  setSendMode("idle");
1196
2573
  }
1197
2574
  } catch (err) {
1198
- addMessage("\u8FDE\u63A5\u9519\u8BEF", "ai");
2575
+ addMessage2("\u8FDE\u63A5\u9519\u8BEF", "ai");
1199
2576
  console.error("Send error", err);
1200
2577
  setSendMode("idle");
1201
2578
  }
@@ -1232,13 +2609,13 @@
1232
2609
  abortCurrentRun();
1233
2610
  }
1234
2611
  });
1235
- let mentionChannels = [];
1236
- let mentionDropdownEl = null;
1237
- let mentionHighlightIdx = -1;
1238
- let mentionQuery = null;
1239
- let mentionAnchor = -1;
1240
- let mentionBlockEnd = -1;
1241
- let mentionDocMousedownBound = false;
2612
+ var mentionChannels = [];
2613
+ var mentionDropdownEl = null;
2614
+ var mentionHighlightIdx = -1;
2615
+ var mentionQuery = null;
2616
+ var mentionAnchor = -1;
2617
+ var mentionBlockEnd = -1;
2618
+ var mentionDocMousedownBound = false;
1242
2619
  function ensureMentionDocMousedown() {
1243
2620
  if (mentionDocMousedownBound) return;
1244
2621
  mentionDocMousedownBound = true;
@@ -1257,7 +2634,7 @@
1257
2634
  const remote = [];
1258
2635
  for (const p of remoteData.peers || []) {
1259
2636
  for (const c of p.channels || []) {
1260
- remote.push({ id: c.id, name: (0, import_safe_name.safeChannelName)(c.name, "(\u8FDC\u7AEF\u672A\u547D\u540D)"), source: "remote", ownerPublicKey: p.peerId });
2637
+ remote.push({ id: c.id, name: safeChannelName(c.name, "(\u8FDC\u7AEF\u672A\u547D\u540D)"), source: "remote", ownerPublicKey: p.peerId });
1261
2638
  }
1262
2639
  }
1263
2640
  mentionChannels = [
@@ -1305,9 +2682,9 @@
1305
2682
  const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
1306
2683
  const bg = i === mentionHighlightIdx ? "#eff6ff" : "#fff";
1307
2684
  const borderLeft = i === mentionHighlightIdx ? "3px solid #93c5fd" : "3px solid transparent";
1308
- return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml(c.id)}" data-channel-name="${escapeHtml((0, import_safe_name.safeChannelName)(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
2685
+ return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml2(c.id)}" data-channel-name="${escapeHtml2(safeChannelName(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
1309
2686
  <span style="font-size:10px;color:${isLocal ? "#059669" : "#2563eb"};background:${isLocal ? "#d1fae5" : "#dbeafe"};padding:1px 6px;border-radius:3px;white-space:nowrap;">${tag}</span>
1310
- <span style="flex:1;">${escapeHtml((0, import_safe_name.safeChannelName)(c.name))}</span>${owner}
2687
+ <span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
1311
2688
  </div>`;
1312
2689
  }).join("");
1313
2690
  mentionDropdownEl.innerHTML = headerHtml + rows;
@@ -1342,7 +2719,7 @@
1342
2719
  }
1343
2720
  const before = input.value.slice(0, anchor);
1344
2721
  const after = input.value.slice(blockEnd);
1345
- const insert = `@${(0, import_safe_name.safeChannelName)(channel.name)} `;
2722
+ const insert = `@${safeChannelName(channel.name)} `;
1346
2723
  input.value = before + insert + after;
1347
2724
  const newPos = before.length + insert.length;
1348
2725
  input.focus();
@@ -1368,7 +2745,7 @@
1368
2745
  }
1369
2746
  mentionQuery = m.query;
1370
2747
  const q = m.query.toLowerCase();
1371
- const items = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2748
+ const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1372
2749
  mentionHighlightIdx = items.length > 0 ? 0 : -1;
1373
2750
  renderMentionDropdown(items);
1374
2751
  }
@@ -1383,21 +2760,21 @@
1383
2760
  if (items.length === 0) return;
1384
2761
  mentionHighlightIdx = (mentionHighlightIdx + 1) % items.length;
1385
2762
  const q = (mentionQuery || "").toLowerCase();
1386
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2763
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1387
2764
  renderMentionDropdown(filtered);
1388
2765
  } else if (e.key === "ArrowUp") {
1389
2766
  e.preventDefault();
1390
2767
  if (items.length === 0) return;
1391
2768
  mentionHighlightIdx = (mentionHighlightIdx - 1 + items.length) % items.length;
1392
2769
  const q = (mentionQuery || "").toLowerCase();
1393
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2770
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1394
2771
  renderMentionDropdown(filtered);
1395
2772
  } else if (e.key === "Enter" || e.key === "Tab") {
1396
2773
  if (items.length > 0) {
1397
2774
  e.preventDefault();
1398
2775
  e.stopPropagation();
1399
2776
  const q = (mentionQuery || "").toLowerCase();
1400
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2777
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1401
2778
  const cur = filtered[mentionHighlightIdx];
1402
2779
  if (cur) applyMention(cur);
1403
2780
  }
@@ -1440,7 +2817,7 @@
1440
2817
  }
1441
2818
  const before = inputEl.value.slice(0, anchor);
1442
2819
  const after = inputEl.value.slice(blockEnd);
1443
- const insert = `@${(0, import_safe_name.safeChannelName)(channel.name)} `;
2820
+ const insert = `@${safeChannelName(channel.name)} `;
1444
2821
  inputEl.value = before + insert + after;
1445
2822
  const newPos = before.length + insert.length;
1446
2823
  inputEl.focus();
@@ -1466,9 +2843,9 @@
1466
2843
  const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
1467
2844
  const bg = i === localHighlight ? "#eff6ff" : "#fff";
1468
2845
  const borderLeft = i === localHighlight ? "3px solid #93c5fd" : "3px solid transparent";
1469
- return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml(c.id)}" data-channel-name="${escapeHtml((0, import_safe_name.safeChannelName)(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
2846
+ return `<div class="mention-item" data-idx="${i}" data-channel-id="${escapeHtml2(c.id)}" data-channel-name="${escapeHtml2(safeChannelName(c.name, ""))}" style="padding:8px 12px;cursor:pointer;background:${bg};border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:8px;border-left:${borderLeft};">
1470
2847
  <span style="font-size:10px;color:${isLocal ? "#059669" : "#2563eb"};background:${isLocal ? "#d1fae5" : "#dbeafe"};padding:1px 6px;border-radius:3px;white-space:nowrap;">${tag}</span>
1471
- <span style="flex:1;">${escapeHtml((0, import_safe_name.safeChannelName)(c.name))}</span>${owner}
2848
+ <span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
1472
2849
  </div>`;
1473
2850
  }).join("");
1474
2851
  inputEl.__mentionDD.querySelectorAll(".mention-item").forEach((el) => {
@@ -1510,7 +2887,7 @@
1510
2887
  }
1511
2888
  localQuery = m.query;
1512
2889
  const q = m.query.toLowerCase();
1513
- const items = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2890
+ const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1514
2891
  localHighlight = items.length > 0 ? 0 : -1;
1515
2892
  renderLocal(items);
1516
2893
  }
@@ -1523,19 +2900,19 @@
1523
2900
  if (items.length === 0) return;
1524
2901
  localHighlight = (localHighlight + 1) % items.length;
1525
2902
  const q = (localQuery || "").toLowerCase();
1526
- renderLocal(mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8));
2903
+ renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
1527
2904
  } else if (e.key === "ArrowUp") {
1528
2905
  e.preventDefault();
1529
2906
  if (items.length === 0) return;
1530
2907
  localHighlight = (localHighlight - 1 + items.length) % items.length;
1531
2908
  const q = (localQuery || "").toLowerCase();
1532
- renderLocal(mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8));
2909
+ renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
1533
2910
  } else if (e.key === "Enter" || e.key === "Tab") {
1534
2911
  if (items.length > 0) {
1535
2912
  e.preventDefault();
1536
2913
  e.stopPropagation();
1537
2914
  const q = (localQuery || "").toLowerCase();
1538
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2915
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1539
2916
  const cur = filtered[localHighlight];
1540
2917
  if (cur) applyLocal(cur);
1541
2918
  }
@@ -1545,7 +2922,7 @@
1545
2922
  }
1546
2923
  }, true);
1547
2924
  }
1548
- const inputArea = document.querySelector(".input-area");
2925
+ var inputArea = document.querySelector(".input-area");
1549
2926
  if (input && inputArea) {
1550
2927
  const onDragOver = (e) => {
1551
2928
  if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes("application/x-bolloon-judgment")) {
@@ -1584,14 +2961,14 @@
1584
2961
  if (themeToggle) {
1585
2962
  themeToggle.addEventListener("click", toggleTheme);
1586
2963
  }
1587
- const apiConfigBtn = document.getElementById("api-config-btn");
2964
+ var apiConfigBtn = document.getElementById("api-config-btn");
1588
2965
  if (apiConfigBtn) {
1589
2966
  apiConfigBtn.addEventListener("click", () => {
1590
2967
  window.location.href = "/api-config";
1591
2968
  });
1592
2969
  }
1593
- const walletBtn = document.getElementById("wallet-btn");
1594
- const walletBadge = document.getElementById("wallet-badge");
2970
+ var walletBtn = document.getElementById("wallet-btn");
2971
+ var walletBadge = document.getElementById("wallet-badge");
1595
2972
  if (walletBtn) {
1596
2973
  walletBtn.addEventListener("click", openWalletModal);
1597
2974
  }
@@ -1663,7 +3040,7 @@
1663
3040
  await createChannel("\u9ED8\u8BA4\u4F1A\u8BDD");
1664
3041
  }
1665
3042
  }
1666
- const p2pNetworkBtn = document.getElementById("p2p-network-btn");
3043
+ var p2pNetworkBtn = document.getElementById("p2p-network-btn");
1667
3044
  if (p2pNetworkBtn) {
1668
3045
  p2pNetworkBtn.addEventListener("click", () => {
1669
3046
  if (typeof window.showP2PModal === "function") {
@@ -1671,18 +3048,18 @@
1671
3048
  }
1672
3049
  });
1673
3050
  }
1674
- const judgmentsModal = document.getElementById("judgments-modal");
1675
- const judgmentsBtn = document.getElementById("judgments-btn");
1676
- const judgmentsModalClose = document.getElementById("judgments-modal-close");
1677
- const judgmentDecision = document.getElementById("judgment-decision");
1678
- const judgmentReason = document.getElementById("judgment-reason");
1679
- const judgmentDomain = document.getElementById("judgment-domain");
1680
- const judgmentStakes = document.getElementById("judgment-stakes");
1681
- const judgmentSubmitBtn = document.getElementById("judgment-submit-btn");
1682
- const judgmentError = document.getElementById("judgment-error");
1683
- const judgmentsList = document.getElementById("judgments-list");
1684
- const judgmentsBadge = document.getElementById("judgments-badge");
1685
- let judgmentsLoaded = false;
3051
+ var judgmentsModal = document.getElementById("judgments-modal");
3052
+ var judgmentsBtn = document.getElementById("judgments-btn");
3053
+ var judgmentsModalClose = document.getElementById("judgments-modal-close");
3054
+ var judgmentDecision = document.getElementById("judgment-decision");
3055
+ var judgmentReason = document.getElementById("judgment-reason");
3056
+ var judgmentDomain = document.getElementById("judgment-domain");
3057
+ var judgmentStakes = document.getElementById("judgment-stakes");
3058
+ var judgmentSubmitBtn = document.getElementById("judgment-submit-btn");
3059
+ var judgmentError = document.getElementById("judgment-error");
3060
+ var judgmentsList = document.getElementById("judgments-list");
3061
+ var judgmentsBadge = document.getElementById("judgments-badge");
3062
+ var judgmentsLoaded = false;
1686
3063
  function showJudgmentsModal() {
1687
3064
  if (judgmentsModal) judgmentsModal.classList.add("active");
1688
3065
  if (!judgmentsLoaded) loadJudgments();
@@ -1720,9 +3097,9 @@
1720
3097
  function hideJudgmentsModal() {
1721
3098
  if (judgmentsModal) judgmentsModal.classList.remove("active");
1722
3099
  }
1723
- let currentJudgmentTab = "channel";
1724
- let currentStatusFilter = "all";
1725
- let lastJudgmentsCache = [];
3100
+ var currentJudgmentTab = "channel";
3101
+ var currentStatusFilter = "all";
3102
+ var lastJudgmentsCache = [];
1726
3103
  function renderJudgments(items) {
1727
3104
  if (!judgmentsList) return;
1728
3105
  const all = items || [];
@@ -1730,7 +3107,7 @@
1730
3107
  const chNameEl = document.getElementById("judgments-tab-channel-name");
1731
3108
  const currentCh = currentChannelId ? channels.find((c) => c.id === currentChannelId) : null;
1732
3109
  if (chNameEl) {
1733
- chNameEl.textContent = currentCh ? `(${(0, import_safe_name.safeChannelName)(currentCh.name)})` : "(\u672A\u9009)";
3110
+ chNameEl.textContent = currentCh ? `(${safeChannelName(currentCh.name)})` : "(\u672A\u9009)";
1734
3111
  }
1735
3112
  if (all.length === 0) {
1736
3113
  judgmentsList.innerHTML = '<div class="task-empty">\u8FD8\u6CA1\u6709\u5224\u65AD, \u5728\u4E0A\u9762\u8BB0\u5F55\u7B2C\u4E00\u6761\u5427</div>';
@@ -1756,7 +3133,7 @@
1756
3133
  );
1757
3134
  const bound = all.filter((j) => boundIds.has(j.id));
1758
3135
  const unbound = all.filter((j) => !boundIds.has(j.id));
1759
- if (titleEl) titleEl.textContent = `${(0, import_safe_name.safeChannelName)(currentCh.name)} \u7684\u5224\u65AD\u529B (\u5DF2\u7ED1 ${bound.length} / \u5171 ${all.length})`;
3136
+ if (titleEl) titleEl.textContent = `${safeChannelName(currentCh.name)} \u7684\u5224\u65AD\u529B (\u5DF2\u7ED1 ${bound.length} / \u5171 ${all.length})`;
1760
3137
  let html = "";
1761
3138
  if (bound.length > 0) {
1762
3139
  html += `<div style="font-size:11px;color:#6b7280;text-transform:uppercase;letter-spacing:0.5px;padding:8px 4px 4px;">\u5DF2\u7ED1\u5B9A (${bound.length})</div>`;
@@ -1771,37 +3148,37 @@
1771
3148
  function renderJudgmentItems(items, opts) {
1772
3149
  const { showBindToggle, isBound } = opts || {};
1773
3150
  return items.map((j) => {
1774
- const reason = j.reasons && j.reasons[0] ? escapeHtml(j.reasons[0]) : "";
1775
- const domain = j.context && j.context.domain ? escapeHtml(j.context.domain) : "general";
1776
- const stakes = j.context && j.context.stakes ? escapeHtml(j.context.stakes) : "medium";
3151
+ const reason = j.reasons && j.reasons[0] ? escapeHtml2(j.reasons[0]) : "";
3152
+ const domain = j.context && j.context.domain ? escapeHtml2(j.context.domain) : "general";
3153
+ const stakes = j.context && j.context.stakes ? escapeHtml2(j.context.stakes) : "medium";
1777
3154
  const isSuperseded = j.status === "superseded";
1778
3155
  const isRejected = j.status === "rejected";
1779
3156
  const dimmedStyle = isSuperseded || isRejected ? "opacity:0.55;background:#f3f4f6;" : "";
1780
3157
  const statusTag = isSuperseded ? `<span style="display:inline-block;background:#fef3c7;color:#92400e;font-size:10px;padding:1px 6px;border-radius:3px;margin-left:6px;" title="\u5DF2\u88AB\u65B0\u5224\u65AD\u529B\u6F14\u5316\u66FF\u4EE3">\u5DF2\u8FC7\u65F6</span>` : isRejected ? `<span style="display:inline-block;background:#fee2e2;color:#991b1b;font-size:10px;padding:1px 6px;border-radius:3px;margin-left:6px;">\u5DF2\u62D2\u7EDD</span>` : "";
1781
- const evolveNote = isSuperseded && j.supersededBy ? `<div style="font-size:10px;color:#6b7280;margin-top:2px;">\u88AB\u65B0\u6761\u66FF\u4EE3 \xB7 ${escapeHtml(j.evolutionReason || "merged")} \xB7 ${escapeHtml(j.evolvedAt || "").substring(0, 10)}</div>` : "";
1782
- const bindBtn = showBindToggle ? isBound ? `<button class="judgment-toggle-btn" data-id="${escapeHtml(j.id)}" data-action="unbind" title="\u4ECE\u5F53\u524D channel \u79FB\u9664" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\xD7 \u79FB\u9664</button>` : `<button class="judgment-toggle-btn" data-id="${escapeHtml(j.id)}" data-action="bind" title="\u52A0\u8FDB\u5F53\u524D channel" style="background:none;border:1px solid #6b7280;color:#6b7280;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">+ \u52A0\u5165</button>` : "";
3158
+ const evolveNote = isSuperseded && j.supersededBy ? `<div style="font-size:10px;color:#6b7280;margin-top:2px;">\u88AB\u65B0\u6761\u66FF\u4EE3 \xB7 ${escapeHtml2(j.evolutionReason || "merged")} \xB7 ${escapeHtml2(j.evolvedAt || "").substring(0, 10)}</div>` : "";
3159
+ const bindBtn = showBindToggle ? isBound ? `<button class="judgment-toggle-btn" data-id="${escapeHtml2(j.id)}" data-action="unbind" title="\u4ECE\u5F53\u524D channel \u79FB\u9664" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\xD7 \u79FB\u9664</button>` : `<button class="judgment-toggle-btn" data-id="${escapeHtml2(j.id)}" data-action="bind" title="\u52A0\u8FDB\u5F53\u524D channel" style="background:none;border:1px solid #6b7280;color:#6b7280;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">+ \u52A0\u5165</button>` : "";
1783
3160
  return `
1784
3161
  <div class="task-item completed judgment-row"
1785
- data-judgment-id="${escapeHtml(j.id)}"
3162
+ data-judgment-id="${escapeHtml2(j.id)}"
1786
3163
  draggable="true"
1787
3164
  style="cursor:grab;${dimmedStyle}">
1788
3165
  <div class="task-item-header">
1789
3166
  <label class="judgment-checkbox" style="display:flex;align-items:center;cursor:pointer;margin-right:8px;" onclick="event.stopPropagation();">
1790
- <input type="checkbox" class="judgment-select-cb" data-id="${escapeHtml(j.id)}" style="cursor:pointer;" onclick="event.stopPropagation();">
3167
+ <input type="checkbox" class="judgment-select-cb" data-id="${escapeHtml2(j.id)}" style="cursor:pointer;" onclick="event.stopPropagation();">
1791
3168
  </label>
1792
3169
  <div class="task-item-title">
1793
- <span class="judgment-decision">${escapeHtml(j.decision)}</span>${statusTag}
3170
+ <span class="judgment-decision">${escapeHtml2(j.decision)}</span>${statusTag}
1794
3171
  </div>
1795
3172
  <span class="task-item-status completed">${stakes}</span>
1796
3173
  </div>
1797
3174
  ${reason ? `<div class="task-item-desc" style="color:#555;font-size:13px;margin-top:4px;">\u7406\u7531: ${reason}</div>` : ""}
1798
3175
  ${evolveNote}
1799
3176
  <div class="task-item-meta" style="color:#999;font-size:11px;margin-top:4px;display:flex;justify-content:space-between;align-items:center;">
1800
- <span>${domain} \xB7 ${escapeHtml(j.timestamp)} \xB7 ${escapeHtml(j.id)}</span>
3177
+ <span>${domain} \xB7 ${escapeHtml2(j.timestamp)} \xB7 ${escapeHtml2(j.id)}</span>
1801
3178
  <span style="display:flex;gap:4px;">
1802
3179
  ${bindBtn}
1803
- <button class="judgment-edit-btn" data-id="${escapeHtml(j.id)}" title="\u7F16\u8F91\u5224\u65AD" style="background:none;border:1px solid #d1d5db;color:#374151;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u7F16\u8F91</button>
1804
- <button class="judgment-del-btn" data-id="${escapeHtml(j.id)}" title="\u5220\u9664\u5224\u65AD" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u5220\u9664</button>
3180
+ <button class="judgment-edit-btn" data-id="${escapeHtml2(j.id)}" title="\u7F16\u8F91\u5224\u65AD" style="background:none;border:1px solid #d1d5db;color:#374151;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u7F16\u8F91</button>
3181
+ <button class="judgment-del-btn" data-id="${escapeHtml2(j.id)}" title="\u5220\u9664\u5224\u65AD" style="background:none;border:1px solid #fca5a5;color:#b91c1c;padding:1px 8px;border-radius:3px;cursor:pointer;font-size:11px;">\u5220\u9664</button>
1805
3182
  </span>
1806
3183
  </div>
1807
3184
  </div>
@@ -1856,7 +3233,7 @@
1856
3233
  }
1857
3234
  judgmentsLoaded = true;
1858
3235
  } catch (e) {
1859
- if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' + escapeHtml(e.message) + "</div>";
3236
+ if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' + escapeHtml2(e.message) + "</div>";
1860
3237
  }
1861
3238
  }
1862
3239
  function renderViolations(items) {
@@ -1866,18 +3243,18 @@
1866
3243
  return;
1867
3244
  }
1868
3245
  judgmentsList.innerHTML = items.map((v) => {
1869
- const ts = escapeHtml((v.ts || "").substring(0, 19).replace("T", " "));
1870
- const userPrev = escapeHtml(v.userInputPreview || "");
1871
- const aiPrev = escapeHtml(v.aiReplyPreview || "");
3246
+ const ts = escapeHtml2((v.ts || "").substring(0, 19).replace("T", " "));
3247
+ const userPrev = escapeHtml2(v.userInputPreview || "");
3248
+ const aiPrev = escapeHtml2(v.aiReplyPreview || "");
1872
3249
  const principles = (v.result?.violatedPrinciples || []).map(
1873
3250
  (p) => `<div style="margin-top:3px;padding:4px 8px;background:#fef2f2;border-radius:3px;">
1874
- <span style="color:#dc2626;">\u26A0</span> <strong>${escapeHtml(p.principle || "")}</strong>
1875
- <span style="color:#991b1b;">\u2014 ${escapeHtml(p.reason || "")}</span>
3251
+ <span style="color:#dc2626;">\u26A0</span> <strong>${escapeHtml2(p.principle || "")}</strong>
3252
+ <span style="color:#991b1b;">\u2014 ${escapeHtml2(p.reason || "")}</span>
1876
3253
  </div>`
1877
3254
  ).join("");
1878
3255
  return `
1879
3256
  <div class="task-item" style="border-left:3px solid #dc2626;padding:8px 12px;background:#fffbfb;">
1880
- <div style="font-size:11px;color:#6b7280;margin-bottom:4px;">${ts} \xB7 confidence=${escapeHtml(String(v.result?.confidence ?? 0))}</div>
3257
+ <div style="font-size:11px;color:#6b7280;margin-bottom:4px;">${ts} \xB7 confidence=${escapeHtml2(String(v.result?.confidence ?? 0))}</div>
1881
3258
  <div style="font-size:12px;color:#1f2937;"><strong>\u7528\u6237:</strong> ${userPrev}</div>
1882
3259
  <div style="font-size:12px;color:#1f2937;margin-top:2px;"><strong>AI:</strong> ${aiPrev}</div>
1883
3260
  <div style="margin-top:6px;">${principles}</div>
@@ -1888,7 +3265,7 @@
1888
3265
  function renderAdaptiveSuggestions(data) {
1889
3266
  if (!judgmentsList) return;
1890
3267
  const { judgmentsTotal, usageEntriesScanned, suggestions, scannedAt } = data;
1891
- const ts = escapeHtml((scannedAt || "").substring(0, 19).replace("T", " "));
3268
+ const ts = escapeHtml2((scannedAt || "").substring(0, 19).replace("T", " "));
1892
3269
  if (!suggestions || suggestions.length === 0) {
1893
3270
  judgmentsList.innerHTML = `
1894
3271
  <div class="task-empty">\u{1F4CA} \u81EA\u9002\u5E94\u626B\u63CF: \u65E0\u5EFA\u8BAE
@@ -1912,21 +3289,21 @@
1912
3289
  const style = KIND_STYLE[s.kind] || KIND_STYLE.unused;
1913
3290
  const m = s.metrics || {};
1914
3291
  return `
1915
- <div class="task-item" data-suggestion-key="${escapeHtml(s.key)}"
3292
+ <div class="task-item" data-suggestion-key="${escapeHtml2(s.key)}"
1916
3293
  style="border-left:3px solid ${style.color};padding:8px 12px;background:${style.bg};margin-bottom:6px;">
1917
3294
  <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
1918
3295
  <span style="color:${style.color};font-weight:600;font-size:12px;">${style.label}</span>
1919
3296
  <span style="font-size:11px;color:#6b7280;">${s.action === "boost" ? "\u5EFA\u8BAE\u52A0\u6743" : s.action === "deprecate" ? "\u5EFA\u8BAE\u5E9F\u5F03" : "\u5EFA\u8BAE\u5BA1\u89C6"}</span>
1920
3297
  </div>
1921
- <div style="font-size:12px;color:#1f2937;margin-bottom:4px;"><strong>${escapeHtml(s.decision)}</strong></div>
1922
- <div style="font-size:11px;color:#6b7280;margin-bottom:6px;">${escapeHtml(s.reason)}</div>
3298
+ <div style="font-size:12px;color:#1f2937;margin-bottom:4px;"><strong>${escapeHtml2(s.decision)}</strong></div>
3299
+ <div style="font-size:11px;color:#6b7280;margin-bottom:6px;">${escapeHtml2(s.reason)}</div>
1923
3300
  <div style="font-size:11px;color:#9ca3af;margin-bottom:6px;">
1924
3301
  7\u5929 ${m.usage7d || 0} \xB7 30\u5929 ${m.usage30d || 0} \xB7 \u5171 ${m.totalUsage || 0} \xB7 \u4E0A\u6B21\u7528 ${m.daysSinceLastUse || 0} \u5929\u524D
1925
3302
  </div>
1926
3303
  <div style="display:flex;gap:6px;">
1927
- <button class="adaptive-accept" data-key="${escapeHtml(s.key)}" data-id="${escapeHtml(s.judgmentId)}" data-action-kind="${escapeHtml(s.action)}"
3304
+ <button class="adaptive-accept" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
1928
3305
  style="background:#059669;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u2713 \u63A5\u53D7</button>
1929
- <button class="adaptive-reject" data-key="${escapeHtml(s.key)}" data-id="${escapeHtml(s.judgmentId)}" data-action-kind="${escapeHtml(s.action)}"
3306
+ <button class="adaptive-reject" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
1930
3307
  style="background:none;border:1px solid #d1d5db;color:#6b7280;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u2717 \u62D2\u7EDD</button>
1931
3308
  </div>
1932
3309
  </div>
@@ -1995,18 +3372,18 @@
1995
3372
  return;
1996
3373
  }
1997
3374
  const rows = items.map((p, idx) => `
1998
- <div class="task-item" data-causal-idx="${idx}" data-judgment-a="${escapeHtml(p.judgmentA)}" data-judgment-b="${escapeHtml(p.judgmentB)}"
3375
+ <div class="task-item" data-causal-idx="${idx}" data-judgment-a="${escapeHtml2(p.judgmentA)}" data-judgment-b="${escapeHtml2(p.judgmentB)}"
1999
3376
  style="border-left:3px solid #7c3aed;padding:8px 12px;background:#faf5ff;margin-bottom:6px;">
2000
3377
  <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
2001
- <span style="color:#7c3aed;font-weight:600;font-size:12px;">${escapeHtml(p.causalDirection)}</span>
3378
+ <span style="color:#7c3aed;font-weight:600;font-size:12px;">${escapeHtml2(p.causalDirection)}</span>
2002
3379
  <span style="font-size:11px;color:#6b7280;">MI=${p.mutualInfo} \xB7 co=${p.coOccurrence}</span>
2003
3380
  </div>
2004
- <div style="font-size:11px;color:#374151;margin-bottom:4px;">${escapeHtml(p.explanation || "(\u65E0 LLM \u89E3\u91CA)")}</div>
2005
- <div style="font-size:10px;color:#9ca3af;">A: ${escapeHtml(p.judgmentA)} \u2194 B: ${escapeHtml(p.judgmentB)}</div>
3381
+ <div style="font-size:11px;color:#374151;margin-bottom:4px;">${escapeHtml2(p.explanation || "(\u65E0 LLM \u89E3\u91CA)")}</div>
3382
+ <div style="font-size:10px;color:#9ca3af;">A: ${escapeHtml2(p.judgmentA)} \u2194 B: ${escapeHtml2(p.judgmentB)}</div>
2006
3383
  <div style="margin-top:6px;display:flex;gap:6px;">
2007
- <button class="causal-intervention-a" data-jid="${escapeHtml(p.judgmentA)}"
3384
+ <button class="causal-intervention-a" data-jid="${escapeHtml2(p.judgmentA)}"
2008
3385
  style="background:#7c3aed;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u{1F52C} do(A)</button>
2009
- <button class="causal-intervention-b" data-jid="${escapeHtml(p.judgmentB)}"
3386
+ <button class="causal-intervention-b" data-jid="${escapeHtml2(p.judgmentB)}"
2010
3387
  style="background:#7c3aed;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u{1F52C} do(B)</button>
2011
3388
  </div>
2012
3389
  <div class="causal-result" data-jid="" style="display:none;margin-top:6px;padding:6px;background:#f3e8ff;border-radius:3px;font-size:11px;"></div>
@@ -2050,11 +3427,11 @@
2050
3427
  const color = Math.abs(effect) > 0.5 ? "#dc2626" : Math.abs(effect) > 0.2 ? "#d97706" : "#059669";
2051
3428
  resultDiv.innerHTML = `
2052
3429
  <div style="color:${color};font-weight:600;">do-calculus: causalEffect = ${sign}${effect} (${data.marginalContribution})</div>
2053
- <div style="color:#374151;margin-top:4px;">${escapeHtml(data.reasoning)}</div>
3430
+ <div style="color:#374151;margin-top:4px;">${escapeHtml2(data.reasoning)}</div>
2054
3431
  <div style="color:#9ca3af;margin-top:4px;">confidence=${data.confidence}</div>
2055
3432
  `;
2056
3433
  } catch (err) {
2057
- resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${escapeHtml(err.message)}</div>`;
3434
+ resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${escapeHtml2(err.message)}</div>`;
2058
3435
  } finally {
2059
3436
  btn.disabled = false;
2060
3437
  }
@@ -2138,9 +3515,9 @@
2138
3515
  }
2139
3516
  });
2140
3517
  }
2141
- const judgmentSelectAll = document.getElementById("judgment-select-all");
2142
- const judgmentSelectedCount = document.getElementById("judgment-selected-count");
2143
- const judgmentBulkDeleteBtn = document.getElementById("judgment-bulk-delete-btn");
3518
+ var judgmentSelectAll = document.getElementById("judgment-select-all");
3519
+ var judgmentSelectedCount = document.getElementById("judgment-selected-count");
3520
+ var judgmentBulkDeleteBtn = document.getElementById("judgment-bulk-delete-btn");
2144
3521
  function getSelectedJudgmentIds() {
2145
3522
  if (!judgmentsList) return [];
2146
3523
  return Array.from(judgmentsList.querySelectorAll(".judgment-select-cb")).filter((cb) => cb.checked).map((cb) => cb.getAttribute("data-id")).filter(Boolean);
@@ -2288,8 +3665,37 @@
2288
3665
  if (e.target === judgmentsModal) hideJudgmentsModal();
2289
3666
  });
2290
3667
  }
2291
- const judgmentImportBtn = document.getElementById("judgment-import-btn");
2292
- const judgmentImportFile = document.getElementById("judgment-import-file");
3668
+ var judgmentImportBtn = document.getElementById("judgment-import-btn");
3669
+ var judgmentImportFile = document.getElementById("judgment-import-file");
3670
+ var judgmentCleanupBtn = document.getElementById("judgment-cleanup-btn");
3671
+ async function runCleanupJudgments(dryRun) {
3672
+ const url = dryRun ? "/api/judgments/cleanup-dry" : "/api/judgments/cleanup";
3673
+ const method = dryRun ? "GET" : "POST";
3674
+ if (judgmentCleanupBtn) judgmentCleanupBtn.disabled = true;
3675
+ const origText = judgmentCleanupBtn?.textContent;
3676
+ if (judgmentCleanupBtn) judgmentCleanupBtn.textContent = dryRun ? "\u{1F50D} \u626B\u63CF\u2026" : "\u2699\uFE0F \u6E05\u7406\u4E2D\u2026";
3677
+ try {
3678
+ const res = await fetch(url, { method });
3679
+ const json = await res.json().catch(() => ({}));
3680
+ if (!res.ok) {
3681
+ showJudgmentError("\u6E05\u7406\u5931\u8D25: " + (json.error || res.status));
3682
+ return;
3683
+ }
3684
+ if (dryRun) {
3685
+ showJudgmentOk(`\u626B\u63CF: ${json.totalBefore} \u6761 \u2192 \u4FDD\u7559 ${json.totalAfter}, \u5C06\u88AB\u8F6F\u5220\u9664 ${json.removed} \u6761 (loadAll \u6D4B\u8BD5/\u6D4B\u8BD5\u539F\u5219\u7B49\u542F\u53D1\u5F0F)`);
3686
+ } else {
3687
+ showJudgmentOk(`\u6E05\u7406\u5B8C\u6210: ${json.totalBefore} \u2192 ${json.totalAfter} \u6761 (\u8F6F\u5220\u9664 ${json.removed} \u6761)`);
3688
+ if (typeof loadJudgments === "function") await loadJudgments();
3689
+ }
3690
+ } catch (err) {
3691
+ showJudgmentError("\u6E05\u7406\u8BF7\u6C42\u5931\u8D25: " + (err?.message || err));
3692
+ } finally {
3693
+ if (judgmentCleanupBtn) {
3694
+ judgmentCleanupBtn.disabled = false;
3695
+ judgmentCleanupBtn.textContent = origText || "\u6E05\u7406\u6D4B\u8BD5\u6570\u636E";
3696
+ }
3697
+ }
3698
+ }
2293
3699
  function showJudgmentError(msg) {
2294
3700
  if (!judgmentError) return;
2295
3701
  judgmentError.textContent = msg;
@@ -2303,12 +3709,12 @@
2303
3709
  judgmentError.style.color = "#15803d";
2304
3710
  }
2305
3711
  function fileToBase64(file) {
2306
- return new Promise((resolve, reject) => {
3712
+ return new Promise((resolve2, reject) => {
2307
3713
  const r = new FileReader();
2308
3714
  r.onload = () => {
2309
3715
  const s = String(r.result || "");
2310
3716
  const idx = s.indexOf(",");
2311
- resolve(idx >= 0 ? s.substring(idx + 1) : s);
3717
+ resolve2(idx >= 0 ? s.substring(idx + 1) : s);
2312
3718
  };
2313
3719
  r.onerror = () => reject(r.error || new Error("read failed"));
2314
3720
  r.readAsDataURL(file);
@@ -2346,6 +3752,15 @@
2346
3752
  if (f) importJudgmentFile(f);
2347
3753
  });
2348
3754
  }
3755
+ if (judgmentCleanupBtn) {
3756
+ judgmentCleanupBtn.addEventListener("click", async () => {
3757
+ if (!window.confirm("\u5C06\u4F1A\u8F6F\u5220\u9664\u6240\u6709\u300C\u6D4B\u8BD5\u704C\u6C34\u300D\u5224\u65AD\u529B (loadAll \u6D4B\u8BD5/\u6D4B\u8BD5\u539F\u5219\u7B49\u542F\u53D1\u5F0F\u5339\u914D).\n\u4E0B\u4E00\u6B65\u5C06\u5148 dry-run \u9884\u89C8, \u4E8C\u6B21\u786E\u8BA4\u518D\u771F\u6E05.")) return;
3758
+ const dry = await runCleanupJudgments(true);
3759
+ if (dry === false) return;
3760
+ if (!window.confirm("\u786E\u8BA4\u6E05\u7406\u5417? \u8F6F\u5220\u9664\u53EF\u8FFD\u6EAF, \u72B6\u6001\u6807\u8BB0\u4E3A rejected, \u4E0D\u5F71\u54CD\u5DF2 active \u6570\u636E.")) return;
3761
+ await runCleanupJudgments(false);
3762
+ });
3763
+ }
2349
3764
  document.addEventListener("click", async (e) => {
2350
3765
  const btn = e.target.closest && e.target.closest(".save-as-judgment");
2351
3766
  if (!btn) return;
@@ -2445,8 +3860,8 @@
2445
3860
  }
2446
3861
  popup.innerHTML = `
2447
3862
  <div style="font-weight:600;margin-bottom:4px;">\u5DF2\u84B8\u998F\u4E3A\u5224\u65AD\u529B</div>
2448
- <div style="background:#f9fafb;padding:6px 8px;border-radius:4px;line-height:1.4;">${escapeHtml(value)}</div>
2449
- ${evidence ? `<div style="font-size:11px;color:#6b7280;margin-top:4px;">\u8BC1\u636E: ${escapeHtml(evidence)}</div>` : ""}
3863
+ <div style="background:#f9fafb;padding:6px 8px;border-radius:4px;line-height:1.4;">${escapeHtml2(value)}</div>
3864
+ ${evidence ? `<div style="font-size:11px;color:#6b7280;margin-top:4px;">\u8BC1\u636E: ${escapeHtml2(evidence)}</div>` : ""}
2450
3865
  ${evolveNote}
2451
3866
  <div style="display:flex;gap:6px;margin-top:8px;">
2452
3867
  <button class="dc-edit" style="background:none;border:1px solid #d1d5db;color:#374151;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u7F16\u8F91</button>
@@ -2473,7 +3888,7 @@
2473
3888
  if (judgmentSubmitBtn) judgmentSubmitBtn.addEventListener("click", submitJudgment);
2474
3889
  loadJudgments();
2475
3890
  setInterval(loadJudgments, 1e4);
2476
- let knownPeers = [];
3891
+ var knownPeers = [];
2477
3892
  async function loadRemoteChannels() {
2478
3893
  try {
2479
3894
  const res = await fetch("/api/p2p-peers");
@@ -2536,22 +3951,22 @@
2536
3951
  const caretChar = "\u25BE";
2537
3952
  return `
2538
3953
  <li class="remote-peer-group ${isCollapsed ? "collapsed" : ""}" style="margin-bottom:10px;${strangerStyle}">
2539
- <div class="remote-peer-header" data-peer-name="${escapeHtml(peer.name)}" data-peer-pk="${escapeHtml(peer.publicKey)}"
3954
+ <div class="remote-peer-header" data-peer-name="${escapeHtml2(peer.name)}" data-peer-pk="${escapeHtml2(peer.publicKey)}"
2540
3955
  style="display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-hover);border-radius:4px;cursor:pointer;">
2541
- <button class="peer-caret-btn" data-toggle-peer="${escapeHtml(peer.publicKey)}" title="\u6298\u53E0/\u5C55\u5F00"
3956
+ <button class="peer-caret-btn" data-toggle-peer="${escapeHtml2(peer.publicKey)}" title="\u6298\u53E0/\u5C55\u5F00"
2542
3957
  style="background:var(--bg-active);border:1px solid var(--border);color:var(--text);cursor:pointer;width:22px;height:22px;border-radius:4px;font-size:12px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">${caretChar}</button>
2543
3958
  <span style="font-size:13px;">${strangerIcon}</span>
2544
- <span style="flex:1;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml(peer.publicKey)}">${escapeHtml(peer.name)}</span>
3959
+ <span style="flex:1;font-size:12px;font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(peer.publicKey)}">${escapeHtml2(peer.name)}</span>
2545
3960
  <span style="font-size:9px;color:var(--text-muted);">${peerChannels.length > 0 ? `${peerChannels.length} ch \xB7 ` : ""}${lastConn}</span>
2546
- <button class="peer-share-btn" title="\u5206\u4EAB channel \u7ED9 ${escapeHtml(peer.name)}"
3961
+ <button class="peer-share-btn" title="\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peer.name)}"
2547
3962
  style="background:transparent;border:1px solid var(--border);color:var(--text);cursor:pointer;width:22px;height:22px;border-radius:4px;font-size:12px;line-height:1;padding:0;display:flex;align-items:center;justify-content:center;flex:0 0 auto;">\u{1F4E4}</button>
2548
3963
  </div>
2549
3964
  <div class="remote-peer-channels" style="margin-top:4px;margin-left:8px;">
2550
3965
  ${peerChannels.length === 0 ? '<div style="font-size:10px;color:var(--text-muted);padding:2px 4px;">(\u5BF9\u65B9\u8FD8\u6CA1\u5206\u4EAB channel \u7ED9\u4F60)</div>' : peerChannels.map((c) => `
2551
- <div class="remote-channel-row" data-peer-id="${escapeHtml(peer.publicKey)}" data-channel-id="${escapeHtml(c.id)}"
3966
+ <div class="remote-channel-row" data-peer-id="${escapeHtml2(peer.publicKey)}" data-channel-id="${escapeHtml2(c.id)}"
2552
3967
  style="display:flex;align-items:center;gap:6px;padding:4px 6px;cursor:pointer;border-radius:4px;font-size:12px;">
2553
3968
  <span>\u{1F916}</span>
2554
- <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml((0, import_safe_name.safeChannelName)(c.name, ""))}">${escapeHtml((0, import_safe_name.safeChannelName)(c.name))}</span>
3969
+ <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(safeChannelName(c.name, ""))}">${escapeHtml2(safeChannelName(c.name))}</span>
2555
3970
  </div>
2556
3971
  `).join("")}
2557
3972
  </div>
@@ -2619,16 +4034,16 @@
2619
4034
  <span style="font-size:18px;">\u270F\uFE0F</span>
2620
4035
  <div style="flex:1;min-width:0;">
2621
4036
  <div class="friend-req-title">\u7F16\u8F91\u597D\u53CB</div>
2622
- <div class="friend-req-meta">publicKey: ${escapeHtml(peerPublicKey.substring(0, 16))}\u2026</div>
4037
+ <div class="friend-req-meta">publicKey: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
2623
4038
  </div>
2624
4039
  </div>
2625
4040
  <div class="friend-req-body">
2626
4041
  <label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u663E\u793A\u540D\u5B57</label>
2627
- <input id="epm-name" type="text" value="${escapeHtml(currentName)}"
4042
+ <input id="epm-name" type="text" value="${escapeHtml2(currentName)}"
2628
4043
  style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;margin-bottom:12px;">
2629
4044
  <label style="display:block;margin-bottom:6px;font-size:12px;color:var(--text-secondary);">\u5907\u6CE8 (\u81EA\u7531\u6587\u672C, \u4F8B\u5982\u5408\u4F5C\u9886\u57DF / \u600E\u4E48\u8BA4\u8BC6\u7684)</label>
2630
4045
  <textarea id="epm-notes" rows="4" placeholder="\u4F8B\u5982: 2026-06 \u5408\u4F5C LLM \u4EE3\u53D1\u9A8C\u8BC1"
2631
- style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;">${escapeHtml(currentNotes)}</textarea>
4046
+ style="width:100%;padding:8px 10px;border:1px solid var(--border);border-radius:4px;background:var(--bg-main);color:var(--text);font-family:inherit;font-size:13px;box-sizing:border-box;resize:vertical;">${escapeHtml2(currentNotes)}</textarea>
2632
4047
  </div>
2633
4048
  <div class="friend-req-actions">
2634
4049
  <button id="epm-cancel" class="friend-req-btn-deny">\u53D6\u6D88</button>
@@ -2679,11 +4094,11 @@
2679
4094
  const isShared = Array.isArray(ch.shared_with_peers) && ch.shared_with_peers.includes(peerPublicKey);
2680
4095
  return `
2681
4096
  <label class="share-modal-row">
2682
- <input type="checkbox" data-cid="${escapeHtml(ch.id)}" ${isShared ? "checked" : ""} class="share-modal-cb">
4097
+ <input type="checkbox" data-cid="${escapeHtml2(ch.id)}" ${isShared ? "checked" : ""} class="share-modal-cb">
2683
4098
  <div class="share-modal-row-info">
2684
- <div class="share-modal-row-name">${escapeHtml(ch.name || "(\u672A\u547D\u540D)")}</div>
4099
+ <div class="share-modal-row-name">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</div>
2685
4100
  <div class="share-modal-row-meta">
2686
- ${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${escapeHtml(ch.id.slice(0, 24))}\u2026
4101
+ ${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${escapeHtml2(ch.id.slice(0, 24))}\u2026
2687
4102
  </div>
2688
4103
  </div>
2689
4104
  </label>
@@ -2695,8 +4110,8 @@
2695
4110
  <div class="friend-req-header">
2696
4111
  <span style="font-size:18px;">\u{1F4E4}</span>
2697
4112
  <div style="flex:1;min-width:0;">
2698
- <div class="friend-req-title">\u5206\u4EAB channel \u7ED9 ${escapeHtml(peerName)}</div>
2699
- <div class="friend-req-meta">${escapeHtml(peerPublicKey.substring(0, 16))}\u2026</div>
4113
+ <div class="friend-req-title">\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peerName)}</div>
4114
+ <div class="friend-req-meta">${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
2700
4115
  </div>
2701
4116
  <button id="spm-close" class="friend-req-btn-close">\xD7</button>
2702
4117
  </div>
@@ -2756,8 +4171,8 @@
2756
4171
  <div class="remote-chat-shell">
2757
4172
  <div class="remote-chat-header">
2758
4173
  <div style="flex:1;min-width:0;">
2759
- <div class="remote-chat-title">\u{1F310} \u8DDF ${escapeHtml(channelName)} \u804A\u5929</div>
2760
- <div class="remote-chat-meta">\u8FDC\u7AEF peer: ${escapeHtml(peerPublicKey.substring(0, 16))}\u2026 \xB7 ${escapeHtml(channelId)}</div>
4174
+ <div class="remote-chat-title">\u{1F310} \u8DDF ${escapeHtml2(channelName)} \u804A\u5929</div>
4175
+ <div class="remote-chat-meta">\u8FDC\u7AEF peer: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026 \xB7 ${escapeHtml2(channelId)}</div>
2761
4176
  </div>
2762
4177
  <button id="rcm-refresh-history" title="\u91CD\u65B0\u62C9\u5386\u53F2" class="remote-chat-btn-secondary">\u21BB \u5386\u53F2</button>
2763
4178
  <button id="rcm-close" class="remote-chat-btn-close">\xD7</button>
@@ -2788,7 +4203,7 @@
2788
4203
  };
2789
4204
  document.getElementById("rcm-refresh-history").onclick = () => loadHistory(false);
2790
4205
  const append = (text, role) => {
2791
- addMessage(text, role === "user" ? "user" : "ai", false, log);
4206
+ addMessage2(text, role === "user" ? "user" : "ai", false, log);
2792
4207
  log.scrollTop = log.scrollHeight;
2793
4208
  };
2794
4209
  const appendSystem = (text, kind = "info") => {
@@ -2844,7 +4259,7 @@
2844
4259
  jh.className = "remote-chat-judgments";
2845
4260
  let h = `<div class="remote-chat-judgments-title">\u{1F6E1}\uFE0F \u5BF9\u65B9 channel \u7ED1\u5B9A\u7684\u5224\u65AD\u529B (${judgments.bound.length} \u6761\u786C\u7EA6\u675F)</div>`;
2846
4261
  for (const j of judgments.bound) {
2847
- h += `<div class="remote-chat-judgment-item">\u2022 <b>${escapeHtml((j.decision || "").slice(0, 100))}</b>${j.domain ? `<span class="remote-chat-judgment-tag"> [${escapeHtml(j.domain)}${j.stakes ? "/" + escapeHtml(j.stakes) : ""}]</span>` : ""}${j.reasons && j.reasons.length ? '<br><span class="remote-chat-judgment-reason">\u7406\u7531: ' + escapeHtml(j.reasons.join("; ").slice(0, 100)) + "</span>" : ""}</div>`;
4262
+ h += `<div class="remote-chat-judgment-item">\u2022 <b>${escapeHtml2((j.decision || "").slice(0, 100))}</b>${j.domain ? `<span class="remote-chat-judgment-tag"> [${escapeHtml2(j.domain)}${j.stakes ? "/" + escapeHtml2(j.stakes) : ""}]</span>` : ""}${j.reasons && j.reasons.length ? '<br><span class="remote-chat-judgment-reason">\u7406\u7531: ' + escapeHtml2(j.reasons.join("; ").slice(0, 100)) + "</span>" : ""}</div>`;
2848
4263
  }
2849
4264
  if (judgments.candidates && judgments.candidates.length > 0) {
2850
4265
  h += `<div class="remote-chat-judgments-foot">+ ${judgments.candidates.length} \u6761\u5019\u9009\u5224\u65AD\u529B (LLM \u53EF\u81EA\u9009\u53C2\u8003)</div>`;
@@ -2874,7 +4289,7 @@
2874
4289
 
2875
4290
  `;
2876
4291
  }
2877
- addMessage(prefix + (m.content || ""), type, false, log);
4292
+ addMessage2(prefix + (m.content || ""), type, false, log);
2878
4293
  }
2879
4294
  setTimeout(() => {
2880
4295
  log.scrollTop = log.scrollHeight;
@@ -2913,7 +4328,7 @@
2913
4328
  loadHistory(false);
2914
4329
  historyRefreshTimer = setInterval(() => loadHistory(true), 15e3);
2915
4330
  }
2916
- const showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
4331
+ var showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
2917
4332
  if (showMyIdBtn) {
2918
4333
  showMyIdBtn.addEventListener("click", async (e) => {
2919
4334
  e.stopPropagation();
@@ -2946,7 +4361,7 @@
2946
4361
  body.innerHTML = `
2947
4362
  <div style="font-size:12px;color:#6b7280;margin-bottom:8px;">\u628A\u4E0B\u9762\u8FD9\u4E32\u53D1\u7ED9\u597D\u53CB, \u597D\u53CB\u5728 P2P \u597D\u53CB\u533A\u70B9 "+ \u597D\u53CB" \u7C98\u8D34\u5373\u53EF\u52A0\u4F60:</div>
2948
4363
  <div style="display:flex;gap:6px;align-items:center;margin-bottom:12px;">
2949
- <code id="mpim-pk" style="flex:1;padding:8px 10px;background:#f3f4f6;border:1px solid #d1d5db;border-radius:4px;font-family:monospace;font-size:11px;word-break:break-all;line-height:1.4;">${escapeHtml(pk)}</code>
4364
+ <code id="mpim-pk" style="flex:1;padding:8px 10px;background:#f3f4f6;border:1px solid #d1d5db;border-radius:4px;font-family:monospace;font-size:11px;word-break:break-all;line-height:1.4;">${escapeHtml2(pk)}</code>
2950
4365
  <button id="mpim-copy" style="padding:8px 14px;background:#2563eb;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:13px;white-space:nowrap;">\u{1F4CB} \u590D\u5236</button>
2951
4366
  </div>
2952
4367
  <div id="mpim-status" style="font-size:12px;color:#059669;min-height:16px;"></div>
@@ -2977,11 +4392,11 @@
2977
4392
  };
2978
4393
  } catch (err) {
2979
4394
  const body = document.getElementById("mpim-body");
2980
- if (body) body.innerHTML = `<div style="color:#b91c1c;font-size:13px;">\u2717 \u83B7\u53D6\u5931\u8D25: ${escapeHtml(err.message || String(err))}</div>`;
4395
+ if (body) body.innerHTML = `<div style="color:#b91c1c;font-size:13px;">\u2717 \u83B7\u53D6\u5931\u8D25: ${escapeHtml2(err.message || String(err))}</div>`;
2981
4396
  }
2982
4397
  });
2983
4398
  }
2984
- const addPeerBtn = document.getElementById("add-p2p-peer-btn");
4399
+ var addPeerBtn = document.getElementById("add-p2p-peer-btn");
2985
4400
  if (addPeerBtn) {
2986
4401
  addPeerBtn.addEventListener("click", async (e) => {
2987
4402
  e.stopPropagation();
@@ -3037,11 +4452,11 @@
3037
4452
  <span style="font-size:20px;">\u{1F91D}</span>
3038
4453
  <div style="flex:1;min-width:0;">
3039
4454
  <div class="friend-req-title">\u597D\u53CB\u7533\u8BF7</div>
3040
- <div class="friend-req-meta">\u6765\u81EA ${escapeHtml(req.fromName)} (${escapeHtml(req.fromPublicKey.substring(0, 16))}\u2026)</div>
4455
+ <div class="friend-req-meta">\u6765\u81EA ${escapeHtml2(req.fromName)} (${escapeHtml2(req.fromPublicKey.substring(0, 16))}\u2026)</div>
3041
4456
  </div>
3042
4457
  </div>
3043
4458
  <div class="friend-req-body">
3044
- <p style="margin:0 0 8px;">${escapeHtml(req.message || "\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB")}</p>
4459
+ <p style="margin:0 0 8px;">${escapeHtml2(req.message || "\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB")}</p>
3045
4460
  <p style="margin:0;color:var(--text-muted);font-size:11px;">\u63A5\u53D7\u540E: \u53CC\u65B9\u4E92\u52A0\u597D\u53CB, \u5BF9\u65B9\u5206\u4EAB\u7684 channel \u4F1A\u81EA\u52A8\u51FA\u73B0\u5728 P2P \u597D\u53CB\u533A.</p>
3046
4461
  </div>
3047
4462
  <div class="friend-req-actions">
@@ -3094,7 +4509,7 @@
3094
4509
  setTimeout(() => el.remove(), 320);
3095
4510
  }, 3e3);
3096
4511
  }
3097
- const p2pToggleAllBtn = document.getElementById("p2p-toggle-all-btn");
4512
+ var p2pToggleAllBtn = document.getElementById("p2p-toggle-all-btn");
3098
4513
  if (p2pToggleAllBtn) {
3099
4514
  let syncToggleAllBtn = function() {
3100
4515
  const allPks = /* @__PURE__ */ new Set([
@@ -3117,7 +4532,7 @@
3117
4532
  p2pToggleAllBtn.title = "\u70B9\u51FB\u6298\u53E0\u6240\u6709 P2P \u597D\u53CB";
3118
4533
  }
3119
4534
  };
3120
- var syncToggleAllBtn2 = syncToggleAllBtn;
4535
+ syncToggleAllBtn2 = syncToggleAllBtn;
3121
4536
  p2pToggleAllBtn.addEventListener("click", (e) => {
3122
4537
  e.stopPropagation();
3123
4538
  const allPks = /* @__PURE__ */ new Set([
@@ -3138,7 +4553,8 @@
3138
4553
  window.__syncP2PToggleAllBtn = syncToggleAllBtn;
3139
4554
  syncToggleAllBtn();
3140
4555
  }
3141
- const refreshSharedBtn = document.getElementById("refresh-shared-btn");
4556
+ var syncToggleAllBtn2;
4557
+ var refreshSharedBtn = document.getElementById("refresh-shared-btn");
3142
4558
  if (refreshSharedBtn) {
3143
4559
  refreshSharedBtn.addEventListener("click", async (e) => {
3144
4560
  e.stopPropagation();
@@ -3163,17 +4579,17 @@
3163
4579
  loadRemoteChannels();
3164
4580
  setInterval(loadRemoteChannels, 8e3);
3165
4581
  startV3GlobalSSE();
3166
- const localSection = document.querySelector(".sidebar-section");
3167
- const remoteSection = document.getElementById("remote-agents-section");
4582
+ var localSection = document.querySelector(".sidebar-section");
4583
+ var remoteSection = document.getElementById("remote-agents-section");
3168
4584
  if (localSection) localSection.classList.add("local-flex");
3169
4585
  if (remoteSection) remoteSection.classList.add("remote-flex");
3170
- const remoteHeader = document.getElementById("remote-agents-header");
4586
+ var remoteHeader = document.getElementById("remote-agents-header");
3171
4587
  if (remoteHeader && remoteSection) {
3172
4588
  remoteHeader.addEventListener("click", (e) => {
3173
4589
  remoteSection.classList.toggle("collapsed");
3174
4590
  });
3175
4591
  }
3176
- const splitHandle = document.getElementById("sidebar-split-handle");
4592
+ var splitHandle = document.getElementById("sidebar-split-handle");
3177
4593
  if (splitHandle && localSection && remoteSection) {
3178
4594
  const updateFlexVars = (localRatio, remoteRatio) => {
3179
4595
  localSection.style.setProperty("--local-flex", String(localRatio));
@@ -3217,17 +4633,17 @@
3217
4633
  updateFlexVars(1, 1);
3218
4634
  });
3219
4635
  }
3220
- const walletModal = document.getElementById("wallet-modal");
3221
- const walletModalClose = document.getElementById("wallet-modal-close");
3222
- const walletBindAddress = document.getElementById("wallet-bind-address");
3223
- const walletGenerateBtn = document.getElementById("wallet-generate-btn");
3224
- const walletAutoTools = document.getElementById("wallet-auto-tools");
3225
- const walletBindBtn = document.getElementById("wallet-bind-btn");
3226
- const walletUnbindBtn = document.getElementById("wallet-unbind-btn");
3227
- const walletNewInfo = document.getElementById("wallet-new-info");
3228
- const walletListEl = document.getElementById("wallet-list");
3229
- let walletModalPendingSecret = null;
3230
- let walletModalPendingMnemonic = null;
4636
+ var walletModal = document.getElementById("wallet-modal");
4637
+ var walletModalClose = document.getElementById("wallet-modal-close");
4638
+ var walletBindAddress = document.getElementById("wallet-bind-address");
4639
+ var walletGenerateBtn = document.getElementById("wallet-generate-btn");
4640
+ var walletAutoTools = document.getElementById("wallet-auto-tools");
4641
+ var walletBindBtn = document.getElementById("wallet-bind-btn");
4642
+ var walletUnbindBtn = document.getElementById("wallet-unbind-btn");
4643
+ var walletNewInfo = document.getElementById("wallet-new-info");
4644
+ var walletListEl = document.getElementById("wallet-list");
4645
+ var walletModalPendingSecret = null;
4646
+ var walletModalPendingMnemonic = null;
3231
4647
  if (walletModalClose) {
3232
4648
  walletModalClose.addEventListener("click", closeWalletModal);
3233
4649
  }
@@ -3287,7 +4703,7 @@
3287
4703
  walletModalPendingSecret = null;
3288
4704
  walletModalPendingMnemonic = null;
3289
4705
  walletNewInfo.style.display = "block";
3290
- walletNewInfo.innerHTML = "\u2705 \u7ED1\u5B9A\u6210\u529F<br><strong>\u5730\u5740:</strong> <code>" + escapeHtml(updated.walletAddress) + "</code><br><strong>\u7B7E\u540D DID:</strong> <code>" + escapeHtml(did) + '</code><br><small style="color:#9c9;">\u670D\u52A1\u7AEF\u5DF2\u7528 recoverMessage \u6821\u9A8C\u7B7E\u540D, \u8BC1\u660E\u4F60\u6301\u6709\u8BE5\u94B1\u5305\u79C1\u94A5\u3002</small>';
4706
+ walletNewInfo.innerHTML = "\u2705 \u7ED1\u5B9A\u6210\u529F<br><strong>\u5730\u5740:</strong> <code>" + escapeHtml2(updated.walletAddress) + "</code><br><strong>\u7B7E\u540D DID:</strong> <code>" + escapeHtml2(did) + '</code><br><small style="color:#9c9;">\u670D\u52A1\u7AEF\u5DF2\u7528 recoverMessage \u6821\u9A8C\u7B7E\u540D, \u8BC1\u660E\u4F60\u6301\u6709\u8BE5\u94B1\u5305\u79C1\u94A5\u3002</small>';
3291
4707
  } catch (err) {
3292
4708
  alert("\u7ED1\u5B9A\u5931\u8D25: " + err.message);
3293
4709
  }
@@ -3345,24 +4761,24 @@
3345
4761
  const row = document.createElement("div");
3346
4762
  row.className = "wallet-row" + (isActive ? " is-active" : "");
3347
4763
  row.innerHTML = `
3348
- <span class="wallet-chain">${escapeHtml(chain)}</span>
4764
+ <span class="wallet-chain">${escapeHtml2(chain)}</span>
3349
4765
  <div class="wallet-info">
3350
- <span class="wallet-agent" title="${escapeHtml(ch.name || "")}">${escapeHtml(ch.name || "(\u672A\u547D\u540D)")}</span>
3351
- <span class="wallet-address" title="${escapeHtml(ch.walletAddress)}">${escapeHtml(ch.walletAddress)}</span>
4766
+ <span class="wallet-agent" title="${escapeHtml2(ch.name || "")}">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</span>
4767
+ <span class="wallet-address" title="${escapeHtml2(ch.walletAddress)}">${escapeHtml2(ch.walletAddress)}</span>
3352
4768
  </div>
3353
4769
  <div class="wallet-actions">
3354
- <button class="wallet-mini-btn" data-action="copy" data-addr="${escapeHtml(ch.walletAddress)}" title="\u590D\u5236\u5730\u5740">
4770
+ <button class="wallet-mini-btn" data-action="copy" data-addr="${escapeHtml2(ch.walletAddress)}" title="\u590D\u5236\u5730\u5740">
3355
4771
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3356
4772
  <rect x="9" y="9" width="13" height="13" rx="2"></rect>
3357
4773
  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
3358
4774
  </svg>
3359
4775
  </button>
3360
- <button class="wallet-mini-btn" data-action="goto" data-id="${escapeHtml(ch.id)}" title="\u5207\u6362\u5230\u8BE5\u667A\u80FD\u4F53">
4776
+ <button class="wallet-mini-btn" data-action="goto" data-id="${escapeHtml2(ch.id)}" title="\u5207\u6362\u5230\u8BE5\u667A\u80FD\u4F53">
3361
4777
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3362
4778
  <path d="M5 12h14M12 5l7 7-7 7"></path>
3363
4779
  </svg>
3364
4780
  </button>
3365
- <button class="wallet-mini-btn" data-action="unbind" data-id="${escapeHtml(ch.id)}" title="\u89E3\u7ED1">
4781
+ <button class="wallet-mini-btn" data-action="unbind" data-id="${escapeHtml2(ch.id)}" title="\u89E3\u7ED1">
3366
4782
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3367
4783
  <line x1="18" y1="6" x2="6" y2="18"></line>
3368
4784
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -3421,18 +4837,18 @@
3421
4837
  return "?";
3422
4838
  }
3423
4839
  init();
3424
- const agentAddModal = document.getElementById("agent-add-modal");
3425
- const agentAddTitle = document.getElementById("agent-add-title");
3426
- const agentAddModalClose = document.getElementById("agent-add-modal-close");
3427
- const agentAddName = document.getElementById("agent-add-name");
3428
- const agentAddWallet = document.getElementById("agent-add-wallet");
3429
- const agentAddAutoTools = document.getElementById("agent-add-auto-tools");
3430
- const agentAddConfirmBtn = document.getElementById("agent-add-confirm-btn");
3431
- const agentAddCancelBtn = document.getElementById("agent-add-cancel-btn");
3432
- const agentAddWalletInfo = document.getElementById("agent-add-wallet-info");
3433
- const agentGenerateWalletBtn = document.getElementById("agent-generate-wallet-btn");
3434
- let pendingWalletSecret = null;
3435
- let pendingWalletMnemonic = null;
4840
+ var agentAddModal = document.getElementById("agent-add-modal");
4841
+ var agentAddTitle = document.getElementById("agent-add-title");
4842
+ var agentAddModalClose = document.getElementById("agent-add-modal-close");
4843
+ var agentAddName = document.getElementById("agent-add-name");
4844
+ var agentAddWallet = document.getElementById("agent-add-wallet");
4845
+ var agentAddAutoTools = document.getElementById("agent-add-auto-tools");
4846
+ var agentAddConfirmBtn = document.getElementById("agent-add-confirm-btn");
4847
+ var agentAddCancelBtn = document.getElementById("agent-add-cancel-btn");
4848
+ var agentAddWalletInfo = document.getElementById("agent-add-wallet-info");
4849
+ var agentGenerateWalletBtn = document.getElementById("agent-generate-wallet-btn");
4850
+ var pendingWalletSecret = null;
4851
+ var pendingWalletMnemonic = null;
3436
4852
  function openAgentAddModal(existingChannel) {
3437
4853
  if (!agentAddModal) return;
3438
4854
  if (existingChannel) {
@@ -3481,17 +4897,17 @@
3481
4897
  function formatWalletInfoHtml({ address, privateKey, mnemonic }) {
3482
4898
  const parts = [
3483
4899
  "\u2713 \u5DF2\u751F\u6210\u771F\u5B9E EVM \u94B1\u5305 (BIP-39 + secp256k1 + EIP-55)",
3484
- "<strong>\u5730\u5740:</strong> <code>" + escapeHtml(address) + "</code>"
4900
+ "<strong>\u5730\u5740:</strong> <code>" + escapeHtml2(address) + "</code>"
3485
4901
  ];
3486
4902
  if (mnemonic) {
3487
4903
  parts.push(
3488
4904
  "<strong>\u52A9\u8BB0\u8BCD (12 \u8BCD, \u8BF7\u6284\u5199\u4FDD\u5B58):</strong>",
3489
- '<code style="color:#fc6;word-break:break-all;">' + escapeHtml(mnemonic) + "</code>"
4905
+ '<code style="color:#fc6;word-break:break-all;">' + escapeHtml2(mnemonic) + "</code>"
3490
4906
  );
3491
4907
  }
3492
4908
  parts.push(
3493
4909
  "<strong>\u79C1\u94A5 (0x + 32 \u5B57\u8282):</strong>",
3494
- '<code style="color:#f88;word-break:break-all;">' + escapeHtml(privateKey) + "</code>",
4910
+ '<code style="color:#f88;word-break:break-all;">' + escapeHtml2(privateKey) + "</code>",
3495
4911
  '<small style="color:#f88;">\u26A0 \u52A9\u8BB0\u8BCD + \u79C1\u94A5\u5747\u4EC5\u5728\u672C\u6D4F\u89C8\u5668\u5185\u5B58, \u5173\u95ED\u9875\u9762\u540E\u65E0\u6CD5\u627E\u56DE\u3002</small>',
3496
4912
  '<small style="color:#999;">\u7B7E\u540D\u7ED1\u5B9A\u5230 channel DID (EIP-191 personal_sign) \u4F1A\u53D1\u9001\u5230\u670D\u52A1\u7AEF, \u7528\u4E8E\u8BC1\u660E\u94B1\u5305\u6240\u6709\u6743\u3002</small>'
3497
4913
  );
@@ -3508,7 +4924,7 @@
3508
4924
  pendingWalletMnemonic = wallet.mnemonic;
3509
4925
  agentAddWalletInfo.innerHTML = formatWalletInfoHtml(wallet);
3510
4926
  } catch (err) {
3511
- agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " + escapeHtml(err.message);
4927
+ agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " + escapeHtml2(err.message);
3512
4928
  }
3513
4929
  });
3514
4930
  }