@bolloon/bolloon-agent 0.2.11 → 0.2.13

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.
Files changed (284) hide show
  1. package/README.md +495 -467
  2. package/bin/bolloon-cli.cjs +183 -183
  3. package/bin/bolloon-daemon.sh +207 -0
  4. package/bin/bolloon.cjs +0 -0
  5. package/bin/ipfs +0 -0
  6. package/dist/agents/agent-manifest-protocol.js +52 -0
  7. package/dist/agents/chat-segmenter.js +298 -0
  8. package/dist/agents/constraint-layer.js +19 -19
  9. package/dist/agents/judgment-protocol.js +479 -0
  10. package/dist/agents/parse-tool-call.js +304 -0
  11. package/dist/agents/peer-manifest-loader.js +210 -0
  12. package/dist/agents/pi-sdk-session-factory.js +110 -0
  13. package/dist/agents/pi-sdk-session-manager.js +297 -0
  14. package/dist/agents/pi-sdk-tools.js +1288 -0
  15. package/dist/agents/pi-sdk-types.js +13 -0
  16. package/dist/agents/pi-sdk.js +553 -1306
  17. package/dist/agents/react-loop.js +120 -0
  18. package/dist/agents/session-store.js +171 -0
  19. package/dist/agents/shell-guard.js +2 -2
  20. package/dist/agents/tool-registry.js +136 -0
  21. package/dist/agents/workflow-pivot-loop.js +200 -22
  22. package/dist/bollharness/src/scripts/context_router.js +7 -0
  23. package/dist/bollharness/src/scripts/deploy-guard.js +8 -8
  24. package/dist/bollharness-integration/context-router-judgment.js +4 -4
  25. package/dist/bollharness-integration/context-router.js +292 -292
  26. package/dist/bollharness-integration/gate-state-machine.js +26 -13
  27. package/dist/bollharness-integration/integration.js +71 -0
  28. package/dist/bollharness-integration/llm-judgment-engine.js +134 -134
  29. package/dist/bollharness-integration/skill-adapter.js +57 -127
  30. package/dist/bootstrap/chat-archiver.js +276 -0
  31. package/dist/bootstrap/context-collector.js +6 -3
  32. package/dist/bootstrap/event-log.js +160 -0
  33. package/dist/bootstrap/lifecycle-hooks.js +15 -1
  34. package/dist/bootstrap/memory-compressor.js +170 -0
  35. package/dist/bootstrap/persona-loader.js +94 -0
  36. package/dist/bootstrap/project-state.js +159 -0
  37. package/dist/bootstrap/remote-mirror.js +97 -0
  38. package/dist/bootstrap/session-window.js +121 -0
  39. package/dist/bootstrap/vector-index.js +248 -0
  40. package/dist/cli/loading-tui.js +40 -0
  41. package/dist/cli-entry.js +41 -37
  42. package/dist/context-compaction/auto-compact.js +7 -7
  43. package/dist/electron/config.js +21 -0
  44. package/dist/electron/config.js.map +1 -0
  45. package/dist/electron/dialogs.js +108 -0
  46. package/dist/electron/dialogs.js.map +1 -0
  47. package/dist/electron/first-run.js +170 -0
  48. package/dist/electron/first-run.js.map +1 -0
  49. package/dist/electron/ipc.js +20 -0
  50. package/dist/electron/ipc.js.map +1 -0
  51. package/dist/electron/logger.js +114 -0
  52. package/dist/electron/logger.js.map +1 -0
  53. package/dist/electron/main.js +63 -0
  54. package/dist/electron/main.js.map +1 -0
  55. package/dist/electron/menu.js +145 -0
  56. package/dist/electron/menu.js.map +1 -0
  57. package/dist/electron/paths.js +75 -0
  58. package/dist/electron/paths.js.map +1 -0
  59. package/dist/electron/server.js +119 -0
  60. package/dist/electron/server.js.map +1 -0
  61. package/dist/electron/tray.js +111 -0
  62. package/dist/electron/tray.js.map +1 -0
  63. package/dist/electron/window.js +111 -0
  64. package/dist/electron/window.js.map +1 -0
  65. package/dist/electron-preload.js +26 -6
  66. package/dist/electron-preload.js.map +1 -1
  67. package/dist/electron.js +6 -168
  68. package/dist/electron.js.map +1 -1
  69. package/dist/index.js +323 -266
  70. package/dist/llm/config-store.js +24 -8
  71. package/dist/llm/llm-judgment-client.js +102 -102
  72. package/dist/llm/pi-ai.js +137 -85
  73. package/dist/llm/system-prompt/layers/channel/local.md +14 -14
  74. package/dist/llm/system-prompt/layers/channel/p2p-agent.md +18 -18
  75. package/dist/llm/system-prompt/layers/channel/p2p-visitor.md +19 -19
  76. package/dist/llm/system-prompt/layers/core/artifacts_storage.md +89 -89
  77. package/dist/llm/system-prompt/layers/core/evenhandedness.md +21 -21
  78. package/dist/llm/system-prompt/layers/core/hibs_reminders.md +15 -15
  79. package/dist/llm/system-prompt/layers/core/identity.md +37 -37
  80. package/dist/llm/system-prompt/layers/core/knowledge.md +17 -17
  81. package/dist/llm/system-prompt/layers/core/memory_system.md +12 -12
  82. package/dist/llm/system-prompt/layers/core/network_filesystem.md +28 -28
  83. package/dist/llm/system-prompt/layers/core/refusal.md +37 -37
  84. package/dist/llm/system-prompt/layers/core/tone.md +31 -31
  85. package/dist/llm/system-prompt/layers/core/tools.thin.md +13 -13
  86. package/dist/llm/system-prompt/layers/core/wellbeing.md +41 -41
  87. package/dist/llm/system-prompt/layers/role/architect.md +20 -20
  88. package/dist/llm/system-prompt/layers/role/expert.md +19 -19
  89. package/dist/llm/system-prompt/layers/role/implementer.md +15 -15
  90. package/dist/llm/system-prompt/layers/role/security.md +15 -15
  91. package/dist/llm/system-prompt/layers/tool/artifacts.md +72 -72
  92. package/dist/llm/system-prompt/layers/tool/bash.md +25 -25
  93. package/dist/llm/system-prompt/layers/tool/hibs_api.md +171 -171
  94. package/dist/llm/system-prompt/layers/tool/image_search.md +70 -70
  95. package/dist/llm/system-prompt/layers/tool/manifest.md +89 -89
  96. package/dist/llm/system-prompt/layers/tool/mcp_apps.md +53 -53
  97. package/dist/llm/system-prompt/layers/tool/web_search.md +83 -83
  98. package/dist/llm/tool-manifest/ask_user_input.js +1 -1
  99. package/dist/llm/tool-manifest/bash.js +3 -3
  100. package/dist/llm/tool-manifest/create_file.js +4 -4
  101. package/dist/llm/tool-manifest/fetch_sports_data.js +3 -3
  102. package/dist/llm/tool-manifest/image_search.js +2 -2
  103. package/dist/llm/tool-manifest/mcp.js +2 -2
  104. package/dist/llm/tool-manifest/message_compose.js +3 -3
  105. package/dist/llm/tool-manifest/places.js +2 -2
  106. package/dist/llm/tool-manifest/present_files.js +1 -1
  107. package/dist/llm/tool-manifest/recipe.js +2 -2
  108. package/dist/llm/tool-manifest/str_replace.js +5 -5
  109. package/dist/llm/tool-manifest/view.js +3 -3
  110. package/dist/llm/tool-manifest/weather.js +4 -4
  111. package/dist/llm/tool-manifest/web.js +4 -4
  112. package/dist/network/local-inbox-bus.js +73 -0
  113. package/dist/network/p2p-outbox.js +161 -0
  114. package/dist/network/peer-fs.js +420 -0
  115. package/dist/network/peer-resource-bridge.js +216 -0
  116. package/dist/pi-ecosystem-judgment/causal-judge.js +51 -51
  117. package/dist/pi-ecosystem-judgment/cleanup.js +126 -0
  118. package/dist/pi-ecosystem-judgment/distill-prompt.js +42 -42
  119. package/dist/pi-ecosystem-judgment/distillation.js +14 -14
  120. package/dist/pi-ecosystem-judgment/human-value-store.js +45 -11
  121. package/dist/pi-ecosystem-judgment/monitor-gate.js +28 -28
  122. package/dist/pi-ecosystem-judgment/value-injection.js +102 -102
  123. package/dist/security/context-router-tool.js +15 -15
  124. package/dist/security/tool-gate.js +11 -0
  125. package/dist/web/api-config.html +520 -520
  126. package/dist/web/client-loop-status.js +220 -0
  127. package/dist/web/client.js +1677 -244
  128. package/dist/web/client.js.map +7 -0
  129. package/dist/web/components/wallet-viem.mjs +118 -118
  130. package/dist/web/index.html +375 -374
  131. package/dist/web/input-validator.js +103 -0
  132. package/dist/web/manifest.json +20 -20
  133. package/dist/web/routes-judgments.js +738 -0
  134. package/dist/web/routes-llm-config.js +279 -0
  135. package/dist/web/routes-tasks.js +222 -0
  136. package/dist/web/server-sse.js +121 -0
  137. package/dist/web/server-storage.js +154 -0
  138. package/dist/web/server-types.js +15 -0
  139. package/dist/web/server-v3-p2p.js +224 -0
  140. package/dist/web/server.js +1070 -1307
  141. package/dist/web/style.css +4742 -4737
  142. package/dist/web/util/safe-name.js +32 -0
  143. package/package.json +162 -160
  144. package/scripts/build-cli.js +215 -215
  145. package/scripts/build-web.ts +130 -125
  146. package/scripts/postinstall.js +152 -152
  147. package/bin/bolloon.js +0 -157
  148. package/dist/constraint-runtime/src/_archive_helper.js +0 -9
  149. package/dist/constraint-runtime/src/agent/coordinator.js +0 -48
  150. package/dist/constraint-runtime/src/agent/index.js +0 -1
  151. package/dist/constraint-runtime/src/assistant/index.js +0 -12
  152. package/dist/constraint-runtime/src/bootstrap/index.js +0 -12
  153. package/dist/constraint-runtime/src/bootstrap_graph.js +0 -13
  154. package/dist/constraint-runtime/src/bridge/index.js +0 -12
  155. package/dist/constraint-runtime/src/buddy/index.js +0 -12
  156. package/dist/constraint-runtime/src/cli/index.js +0 -12
  157. package/dist/constraint-runtime/src/command_graph.js +0 -10
  158. package/dist/constraint-runtime/src/commands.js +0 -60
  159. package/dist/constraint-runtime/src/components/index.js +0 -12
  160. package/dist/constraint-runtime/src/constants/index.js +0 -12
  161. package/dist/constraint-runtime/src/constraint/budget.js +0 -22
  162. package/dist/constraint-runtime/src/constraint/index.js +0 -2
  163. package/dist/constraint-runtime/src/constraint/permission.js +0 -20
  164. package/dist/constraint-runtime/src/context.js +0 -30
  165. package/dist/constraint-runtime/src/coordinator/index.js +0 -12
  166. package/dist/constraint-runtime/src/cost_hook.js +0 -4
  167. package/dist/constraint-runtime/src/cost_tracker.js +0 -8
  168. package/dist/constraint-runtime/src/deferred_init.js +0 -10
  169. package/dist/constraint-runtime/src/direct_modes.js +0 -6
  170. package/dist/constraint-runtime/src/dynamic-tool-loader.js +0 -85
  171. package/dist/constraint-runtime/src/entrypoints/index.js +0 -12
  172. package/dist/constraint-runtime/src/execution_registry.js +0 -44
  173. package/dist/constraint-runtime/src/history.js +0 -9
  174. package/dist/constraint-runtime/src/hooks/index.js +0 -12
  175. package/dist/constraint-runtime/src/index.js +0 -26
  176. package/dist/constraint-runtime/src/ink.js +0 -4
  177. package/dist/constraint-runtime/src/keybindings/index.js +0 -12
  178. package/dist/constraint-runtime/src/memdir/index.js +0 -12
  179. package/dist/constraint-runtime/src/migrations/index.js +0 -12
  180. package/dist/constraint-runtime/src/models.js +0 -1
  181. package/dist/constraint-runtime/src/moreright/index.js +0 -12
  182. package/dist/constraint-runtime/src/native_ts/index.js +0 -12
  183. package/dist/constraint-runtime/src/output_styles/index.js +0 -12
  184. package/dist/constraint-runtime/src/parity_audit.js +0 -12
  185. package/dist/constraint-runtime/src/plugins/index.js +0 -12
  186. package/dist/constraint-runtime/src/port_manifest.js +0 -11
  187. package/dist/constraint-runtime/src/prefetch.js +0 -9
  188. package/dist/constraint-runtime/src/query.js +0 -1
  189. package/dist/constraint-runtime/src/remote/index.js +0 -12
  190. package/dist/constraint-runtime/src/remote_runtime.js +0 -9
  191. package/dist/constraint-runtime/src/runtime/index.js +0 -1
  192. package/dist/constraint-runtime/src/runtime/session.js +0 -35
  193. package/dist/constraint-runtime/src/schemas/index.js +0 -12
  194. package/dist/constraint-runtime/src/screens/index.js +0 -12
  195. package/dist/constraint-runtime/src/server/index.js +0 -12
  196. package/dist/constraint-runtime/src/services/index.js +0 -12
  197. package/dist/constraint-runtime/src/session_store.js +0 -22
  198. package/dist/constraint-runtime/src/setup.js +0 -30
  199. package/dist/constraint-runtime/src/skills/index.js +0 -1
  200. package/dist/constraint-runtime/src/skills/skill-registry.js +0 -28
  201. package/dist/constraint-runtime/src/state/index.js +0 -12
  202. package/dist/constraint-runtime/src/system_init.js +0 -20
  203. package/dist/constraint-runtime/src/thinking/engine.js +0 -42
  204. package/dist/constraint-runtime/src/thinking/index.js +0 -1
  205. package/dist/constraint-runtime/src/tool_pool.js +0 -8
  206. package/dist/constraint-runtime/src/tools/OpenCLI/execAdapter.js +0 -7
  207. package/dist/constraint-runtime/src/tools/OpenCLI/listAdapters.js +0 -7
  208. package/dist/constraint-runtime/src/tools/OpenCLI/runCommand.js +0 -7
  209. package/dist/constraint-runtime/src/tools/SafeSDK/confirmTransaction.js +0 -6
  210. package/dist/constraint-runtime/src/tools/SafeSDK/createTransaction.js +0 -8
  211. package/dist/constraint-runtime/src/tools/SafeSDK/executeTransaction.js +0 -6
  212. package/dist/constraint-runtime/src/tools/SafeSDK/getBalance.js +0 -6
  213. package/dist/constraint-runtime/src/tools/SafeSDK/getPendingTransactions.js +0 -6
  214. package/dist/constraint-runtime/src/tools/SafeSDK/proposeTransaction.js +0 -6
  215. package/dist/constraint-runtime/src/tools.js +0 -80
  216. package/dist/constraint-runtime/src/transcript.js +0 -19
  217. package/dist/constraint-runtime/src/types/index.js +0 -12
  218. package/dist/constraint-runtime/src/upstream_proxy/index.js +0 -12
  219. package/dist/constraint-runtime/src/utils/index.js +0 -12
  220. package/dist/constraint-runtime/src/vim/index.js +0 -12
  221. package/dist/constraint-runtime/src/voice/index.js +0 -12
  222. package/dist/constraint-runtime/tests/agent.test.js +0 -16
  223. package/dist/constraint-runtime/tests/constraint.test.js +0 -41
  224. package/dist/constraint-runtime/tests/skill.test.js +0 -19
  225. package/dist/constraint-runtime/tests/thinking.test.js +0 -22
  226. package/dist/pi-ecosystem-colony/index.js +0 -365
  227. package/dist/social/ant-colony/AdaptiveHeartbeat.js +0 -101
  228. package/dist/social/ant-colony/PheromoneEngine.js +0 -227
  229. package/dist/social/ant-colony/index.js +0 -6
  230. package/dist/social/ant-colony/types.js +0 -24
  231. package/dist/test/ai-judgment-test.js +0 -80
  232. package/dist/test/bollharness-integration.test.js +0 -318
  233. package/dist/test/channel-agent-multi-dialogue.js +0 -205
  234. package/dist/test/channel-heartbeat-agent-test.js +0 -201
  235. package/dist/test/constraint-layer.test.js +0 -164
  236. package/dist/test/diap-identity-test.js +0 -172
  237. package/dist/test/diap-quick-test.js +0 -62
  238. package/dist/test/global-shared-context.test.js +0 -315
  239. package/dist/test/harness-judgment-injection.test.js +0 -246
  240. package/dist/test/harness-workflow-integrator-test.js +0 -228
  241. package/dist/test/human-value-store.test.js +0 -243
  242. package/dist/test/hybrid-integration-test.js +0 -118
  243. package/dist/test/hybrid-messenger-verify.js +0 -55
  244. package/dist/test/iroh-bistream-debug.js +0 -38
  245. package/dist/test/iroh-communication.test.js +0 -66
  246. package/dist/test/iroh-debug-test.js +0 -57
  247. package/dist/test/iroh-diap-test.js +0 -71
  248. package/dist/test/iroh-direct-connect.js +0 -55
  249. package/dist/test/iroh-e2e-fixed.js +0 -89
  250. package/dist/test/iroh-e2e-same-process.js +0 -63
  251. package/dist/test/iroh-e2e.js +0 -66
  252. package/dist/test/iroh-final-e2e.js +0 -72
  253. package/dist/test/iroh-relay-test.js +0 -37
  254. package/dist/test/iroh-simple-test.js +0 -41
  255. package/dist/test/iroh-transport-verify.js +0 -54
  256. package/dist/test/iroh-transport.test.js +0 -37
  257. package/dist/test/iroh-two-nodes.js +0 -70
  258. package/dist/test/iroh-verify.js +0 -44
  259. package/dist/test/judgment-decision.test.js +0 -219
  260. package/dist/test/llm-judgment-integration.test.js +0 -220
  261. package/dist/test/p2p-agent-complex-dialogue.js +0 -385
  262. package/dist/test/p2p-agent-dialogue.js +0 -341
  263. package/dist/test/p2p-agent-full-bidirectional.js +0 -510
  264. package/dist/test/p2p-agent-harness-flow.js +0 -437
  265. package/dist/test/p2p-agent-harness-single.js +0 -143
  266. package/dist/test/p2p-ai-dialogue-test.js +0 -318
  267. package/dist/test/p2p-cid-connect-test.js +0 -195
  268. package/dist/test/p2p-connect-receiver.js +0 -69
  269. package/dist/test/p2p-doc-transfer.js +0 -110
  270. package/dist/test/p2p-identity-page-test.js +0 -77
  271. package/dist/test/p2p-iroh-test.js +0 -171
  272. package/dist/test/p2p-minimal-test.js +0 -241
  273. package/dist/test/p2p-node-1.js +0 -148
  274. package/dist/test/p2p-node-2.js +0 -148
  275. package/dist/test/p2p-server.js +0 -281
  276. package/dist/test/p2p-two-nodes-test.js +0 -438
  277. package/dist/test/pi-sdk.test.js +0 -44
  278. package/dist/test/set-persona.js +0 -40
  279. package/dist/test/simple.test.js +0 -9
  280. package/dist/test/storage-integration.test.js +0 -150
  281. package/dist/test/subagent-manager.test.js +0 -276
  282. package/dist/test/test-gate-flow.test.js +0 -81
  283. package/dist/test/workflow-engine.test.js +0 -87
  284. package/dist/test/workflow-pivot-loop.test.js +0 -246
@@ -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,13 +2135,23 @@
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;
786
2142
  }
787
2143
  expandedAgents.add(channelId);
788
2144
  console.log("[selectChannel] \u9891\u9053:", channel.name, "session:", currentSessionId);
2145
+ } else {
2146
+ console.warn("[selectChannel] channel \u4E0D\u5B58\u5728:", channelId);
2147
+ if (channelNameEl) channelNameEl.textContent = safeChannelName("(channel \u5DF2\u5220\u9664)");
2148
+ currentSessionId = targetSessionId || "default";
2149
+ const orphanContainer = ensureMessageContainer(channelId);
2150
+ showChannelView(channelId);
2151
+ orphanContainer.innerHTML = "";
2152
+ appendSystem(`\u26A0\uFE0F Channel ${channelId} \u5DF2\u4E0D\u5B58\u5728, \u65E0\u6CD5\u7EE7\u7EED\u5BF9\u8BDD\u3002
2153
+ \u8BF7\u5237\u65B0\u9875\u9762 (F5) \u6216\u5728\u5DE6\u4FA7\u9009\u62E9\u5176\u4ED6 channel\u3002`, "error");
2154
+ return;
789
2155
  }
790
2156
  const t0 = performance.now();
791
2157
  renderChannelsLite(channelId, currentSessionId);
@@ -806,18 +2172,18 @@
806
2172
  const tmpContainer = document.createElement("div");
807
2173
  tmpContainer.style.display = "none";
808
2174
  for (const msg of msgs) {
809
- addMessage(msg.content, msg.type, false, tmpContainer, msg.metadata?.usedJudgmentIds || []);
2175
+ addMessage2(msg.content, msg.type, false, tmpContainer, msg.metadata?.usedJudgmentIds || []);
810
2176
  }
811
2177
  while (tmpContainer.firstChild) {
812
2178
  frag.appendChild(tmpContainer.firstChild);
813
2179
  }
814
2180
  container.appendChild(frag);
815
2181
  } 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);
2182
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
817
2183
  }
818
2184
  } catch (err) {
819
2185
  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);
2186
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
821
2187
  }
822
2188
  }
823
2189
  async function loadSession(channelId, sessionId = null) {
@@ -826,33 +2192,41 @@
826
2192
  const targetSessionId = sessionId || currentSessionId || "default";
827
2193
  try {
828
2194
  const res = await fetch(`/sessions/${channelId}?sessionId=${encodeURIComponent(targetSessionId)}`);
2195
+ if (res.status === 404) {
2196
+ const data = await res.json().catch(() => ({}));
2197
+ container.innerHTML = "";
2198
+ appendSystem(`\u26A0\uFE0F Channel ${channelId} \u5DF2\u4E0D\u5B58\u5728, \u65E0\u6CD5\u52A0\u8F7D\u5386\u53F2\u6D88\u606F\u3002
2199
+ ${data.error || "channel not found"}`, "error");
2200
+ return;
2201
+ }
829
2202
  const session = await res.json();
830
2203
  container.innerHTML = "";
831
2204
  if (session.messages && session.messages.length > 0) {
832
2205
  session.messages.forEach((msg) => {
833
- addMessage(msg.content, msg.type, false, container, msg.metadata?.usedJudgmentIds || []);
2206
+ addMessage2(msg.content, msg.type, false, container, msg.metadata?.usedJudgmentIds || []);
834
2207
  });
835
2208
  } 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);
2209
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
837
2210
  }
838
2211
  } catch (err) {
839
2212
  console.error("Failed to load session:", err);
840
2213
  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);
2214
+ addMessage2("\u4F60\u597D\uFF01\u6211\u662F Bolloon Agent\u3002\u6709\u4EC0\u4E48\u6211\u53EF\u4EE5\u5E2E\u4F60\u7684\u5417\uFF1F", "ai", false, container);
842
2215
  }
843
2216
  }
844
- function addMessage(content, type, save = true, container, usedJudgmentIds = []) {
2217
+ function addMessage2(content, type, save = true, container, usedJudgmentIds = []) {
845
2218
  return MR_addMessage(content, type, save, container, usedJudgmentIds, getRendererCtx());
846
2219
  }
847
- function finalizeTimelineAsMessage() {
2220
+ function finalizeTimelineAsMessage2() {
848
2221
  return MR_finalizeTimelineAsMessage(getRendererCtx());
849
2222
  }
850
- function handleStepEvent(data) {
2223
+ function handleStepEvent2(data) {
851
2224
  return MR_handleStepEvent(data, getRendererCtx());
852
2225
  }
853
- let lastUsedJudgmentIds = [];
854
- let selfImproveCardSeq = 0;
855
- function getMessagesContainerForCurrent() {
2226
+ var lastUsedJudgmentIds = [];
2227
+ var currentPreviewBubble = null;
2228
+ var selfImproveCardSeq = 0;
2229
+ function getMessagesContainerForCurrent2() {
856
2230
  if (currentChannelId && messagesContainers.get(currentChannelId)) {
857
2231
  return messagesContainers.get(currentChannelId);
858
2232
  }
@@ -885,7 +2259,7 @@
885
2259
  return card;
886
2260
  }
887
2261
  function handleSelfImproveTriggered(data) {
888
- const container = getMessagesContainerForCurrent();
2262
+ const container = getMessagesContainerForCurrent2();
889
2263
  if (!container) return;
890
2264
  const card = makeSelfImproveCard(data);
891
2265
  card.querySelector(".self-improve-title").textContent = `\u{1F9E0} \u81EA\u8FED\u4EE3\u89E6\u53D1 \xB7 ${data.eventKind || "unknown"}`;
@@ -900,7 +2274,7 @@
900
2274
  card.scrollIntoView({ block: "end", behavior: "smooth" });
901
2275
  }
902
2276
  function handleSelfImproveResult(data) {
903
- const container = getMessagesContainerForCurrent();
2277
+ const container = getMessagesContainerForCurrent2();
904
2278
  if (!container) return;
905
2279
  const card = makeSelfImproveCard(data);
906
2280
  const ok = !!data.success;
@@ -1019,14 +2393,14 @@
1019
2393
  const container = messagesContainers.get(targetChannelId) || messagesEl;
1020
2394
  if (msg.type === "ai") {
1021
2395
  if (!MR_hasStreamingText()) {
1022
- addMessage(msg.content, "ai", true, container, lastUsedJudgmentIds || []);
2396
+ addMessage2(msg.content, "ai", true, container, lastUsedJudgmentIds || []);
1023
2397
  } else {
1024
2398
  MR_replaceStreamingText?.(msg.content);
1025
2399
  MR_finalizeTimelineAsMessage(getRendererCtx());
1026
2400
  }
1027
2401
  } else if (msg.type === "user") {
1028
2402
  if (msg.source === "remote" || msg.source === "local") {
1029
- addMessage(msg.content, "user", true, container);
2403
+ addMessage2(msg.content, "user", true, container);
1030
2404
  }
1031
2405
  }
1032
2406
  }
@@ -1107,10 +2481,26 @@
1107
2481
  const container = messagesContainers.get(msgChannelId) || messagesEl;
1108
2482
  if (data.type === "user") {
1109
2483
  if (data.source === "remote") {
1110
- addMessage(data.content, "user", true, container);
2484
+ addMessage2(data.content, "user", true, container);
1111
2485
  }
1112
2486
  } else if (data.type === "ai") {
1113
- addMessage(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
2487
+ addMessage2(data.content || "", "ai", true, container, lastUsedJudgmentIds || []);
2488
+ if (currentPreviewBubble) {
2489
+ currentPreviewBubble.remove();
2490
+ currentPreviewBubble = null;
2491
+ }
2492
+ } else if (data.type === "reply-preview") {
2493
+ const previewContent = data.content || "";
2494
+ if (!currentPreviewBubble) {
2495
+ currentPreviewBubble = addMessage2(previewContent, "ai", true, container, []);
2496
+ if (currentPreviewBubble) {
2497
+ currentPreviewBubble.classList.add("preview");
2498
+ }
2499
+ } else {
2500
+ currentPreviewBubble.replaceWith(addMessage2(previewContent, "ai", true, container, []));
2501
+ const msgs = container.querySelectorAll(".message-ai.preview");
2502
+ currentPreviewBubble = msgs.length > 0 ? msgs[msgs.length - 1] : null;
2503
+ }
1114
2504
  } else if (data.type === "stream") {
1115
2505
  if (false) handleStreamTokenEvent(data);
1116
2506
  } else if (data.type === "regenerating") {
@@ -1121,12 +2511,12 @@
1121
2511
  }
1122
2512
  setSendMode("abort");
1123
2513
  } else if (data.type === "status") {
1124
- renderLoopStatusBar(data.tool, data.content);
2514
+ renderLoopStatusBar2(data.tool, data.content);
1125
2515
  } else if (data.type === "step_start" || data.type === "step_done" || data.type === "step_error") {
1126
- handleStepEvent(data);
2516
+ handleStepEvent2(data);
1127
2517
  } else if (data.type === "done") {
1128
- finalizeTimelineAsMessage();
1129
- hideLoopStatusBar();
2518
+ finalizeTimelineAsMessage2();
2519
+ hideLoopStatusBar2();
1130
2520
  setSendMode("idle");
1131
2521
  } else if (data.type === "renamed") {
1132
2522
  const channel = channels.find((c) => c.id === data.channelId);
@@ -1134,18 +2524,18 @@
1134
2524
  channel.name = data.newName;
1135
2525
  renderChannels();
1136
2526
  if (currentChannelId === data.channelId && channelNameEl) {
1137
- channelNameEl.textContent = (0, import_safe_name.safeChannelName)(data.newName);
2527
+ channelNameEl.textContent = safeChannelName(data.newName);
1138
2528
  }
1139
2529
  }
1140
2530
  } else if (data.type === "error") {
1141
2531
  const errContent = String(data.content || "\u672A\u77E5\u9519\u8BEF");
1142
- addMessage(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
2532
+ addMessage2(`\u26A0\uFE0F ${errContent}`, "ai", false, container);
1143
2533
  if (typeof showSimpleToast === "function") {
1144
2534
  showSimpleToast("\u26A0\uFE0F " + errContent.slice(0, 200));
1145
2535
  } else {
1146
2536
  console.error("[SSE] error:", errContent);
1147
2537
  }
1148
- hideLoopStatusBar();
2538
+ hideLoopStatusBar2();
1149
2539
  setSendMode("idle");
1150
2540
  } else if (data.type === "task_status" || data.type === "workflow_step" || data.type === "workflow_loop") {
1151
2541
  if (data.type === "workflow_step" && (data.step === "AI \u601D\u8003" || data.step === "\u5F00\u59CB\u601D\u8003")) {
@@ -1171,11 +2561,15 @@
1171
2561
  async function sendMessage() {
1172
2562
  const text = input.value.trim();
1173
2563
  if (!text) return;
2564
+ setSendMode("abort");
1174
2565
  const container = messagesContainers.get(currentChannelId) || messagesEl;
1175
- addMessage(text, "user", true, container);
2566
+ addMessage2(text, "user", true, container);
1176
2567
  if (container) container.scrollTop = container.scrollHeight;
2568
+ if (currentPreviewBubble) {
2569
+ currentPreviewBubble.remove();
2570
+ currentPreviewBubble = null;
2571
+ }
1177
2572
  input.value = "";
1178
- setSendMode("abort");
1179
2573
  persistLastMessageToServer("user", text);
1180
2574
  const channel = channels.find((c) => c.id === currentChannelId);
1181
2575
  const channelDid = channel?.did || "";
@@ -1191,11 +2585,11 @@
1191
2585
  })
1192
2586
  });
1193
2587
  if (!res.ok) {
1194
- addMessage("\u53D1\u9001\u5931\u8D25", "ai");
2588
+ addMessage2("\u53D1\u9001\u5931\u8D25", "ai");
1195
2589
  setSendMode("idle");
1196
2590
  }
1197
2591
  } catch (err) {
1198
- addMessage("\u8FDE\u63A5\u9519\u8BEF", "ai");
2592
+ addMessage2("\u8FDE\u63A5\u9519\u8BEF", "ai");
1199
2593
  console.error("Send error", err);
1200
2594
  setSendMode("idle");
1201
2595
  }
@@ -1232,13 +2626,13 @@
1232
2626
  abortCurrentRun();
1233
2627
  }
1234
2628
  });
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;
2629
+ var mentionChannels = [];
2630
+ var mentionDropdownEl = null;
2631
+ var mentionHighlightIdx = -1;
2632
+ var mentionQuery = null;
2633
+ var mentionAnchor = -1;
2634
+ var mentionBlockEnd = -1;
2635
+ var mentionDocMousedownBound = false;
1242
2636
  function ensureMentionDocMousedown() {
1243
2637
  if (mentionDocMousedownBound) return;
1244
2638
  mentionDocMousedownBound = true;
@@ -1257,7 +2651,7 @@
1257
2651
  const remote = [];
1258
2652
  for (const p of remoteData.peers || []) {
1259
2653
  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 });
2654
+ remote.push({ id: c.id, name: safeChannelName(c.name, "(\u8FDC\u7AEF\u672A\u547D\u540D)"), source: "remote", ownerPublicKey: p.peerId });
1261
2655
  }
1262
2656
  }
1263
2657
  mentionChannels = [
@@ -1305,9 +2699,9 @@
1305
2699
  const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
1306
2700
  const bg = i === mentionHighlightIdx ? "#eff6ff" : "#fff";
1307
2701
  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};">
2702
+ 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
2703
  <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}
2704
+ <span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
1311
2705
  </div>`;
1312
2706
  }).join("");
1313
2707
  mentionDropdownEl.innerHTML = headerHtml + rows;
@@ -1342,7 +2736,7 @@
1342
2736
  }
1343
2737
  const before = input.value.slice(0, anchor);
1344
2738
  const after = input.value.slice(blockEnd);
1345
- const insert = `@${(0, import_safe_name.safeChannelName)(channel.name)} `;
2739
+ const insert = `@${safeChannelName(channel.name)} `;
1346
2740
  input.value = before + insert + after;
1347
2741
  const newPos = before.length + insert.length;
1348
2742
  input.focus();
@@ -1368,7 +2762,7 @@
1368
2762
  }
1369
2763
  mentionQuery = m.query;
1370
2764
  const q = m.query.toLowerCase();
1371
- const items = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2765
+ const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1372
2766
  mentionHighlightIdx = items.length > 0 ? 0 : -1;
1373
2767
  renderMentionDropdown(items);
1374
2768
  }
@@ -1383,21 +2777,21 @@
1383
2777
  if (items.length === 0) return;
1384
2778
  mentionHighlightIdx = (mentionHighlightIdx + 1) % items.length;
1385
2779
  const q = (mentionQuery || "").toLowerCase();
1386
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2780
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1387
2781
  renderMentionDropdown(filtered);
1388
2782
  } else if (e.key === "ArrowUp") {
1389
2783
  e.preventDefault();
1390
2784
  if (items.length === 0) return;
1391
2785
  mentionHighlightIdx = (mentionHighlightIdx - 1 + items.length) % items.length;
1392
2786
  const q = (mentionQuery || "").toLowerCase();
1393
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2787
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1394
2788
  renderMentionDropdown(filtered);
1395
2789
  } else if (e.key === "Enter" || e.key === "Tab") {
1396
2790
  if (items.length > 0) {
1397
2791
  e.preventDefault();
1398
2792
  e.stopPropagation();
1399
2793
  const q = (mentionQuery || "").toLowerCase();
1400
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2794
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1401
2795
  const cur = filtered[mentionHighlightIdx];
1402
2796
  if (cur) applyMention(cur);
1403
2797
  }
@@ -1440,7 +2834,7 @@
1440
2834
  }
1441
2835
  const before = inputEl.value.slice(0, anchor);
1442
2836
  const after = inputEl.value.slice(blockEnd);
1443
- const insert = `@${(0, import_safe_name.safeChannelName)(channel.name)} `;
2837
+ const insert = `@${safeChannelName(channel.name)} `;
1444
2838
  inputEl.value = before + insert + after;
1445
2839
  const newPos = before.length + insert.length;
1446
2840
  inputEl.focus();
@@ -1466,9 +2860,9 @@
1466
2860
  const owner = !isLocal && c.ownerPublicKey ? ` <span style="color:#9ca3af;font-size:11px;">(${c.ownerPublicKey.substring(0, 8)}\u2026)</span>` : "";
1467
2861
  const bg = i === localHighlight ? "#eff6ff" : "#fff";
1468
2862
  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};">
2863
+ 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
2864
  <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}
2865
+ <span style="flex:1;">${escapeHtml2(safeChannelName(c.name))}</span>${owner}
1472
2866
  </div>`;
1473
2867
  }).join("");
1474
2868
  inputEl.__mentionDD.querySelectorAll(".mention-item").forEach((el) => {
@@ -1510,7 +2904,7 @@
1510
2904
  }
1511
2905
  localQuery = m.query;
1512
2906
  const q = m.query.toLowerCase();
1513
- const items = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2907
+ const items = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1514
2908
  localHighlight = items.length > 0 ? 0 : -1;
1515
2909
  renderLocal(items);
1516
2910
  }
@@ -1523,19 +2917,19 @@
1523
2917
  if (items.length === 0) return;
1524
2918
  localHighlight = (localHighlight + 1) % items.length;
1525
2919
  const q = (localQuery || "").toLowerCase();
1526
- renderLocal(mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8));
2920
+ renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
1527
2921
  } else if (e.key === "ArrowUp") {
1528
2922
  e.preventDefault();
1529
2923
  if (items.length === 0) return;
1530
2924
  localHighlight = (localHighlight - 1 + items.length) % items.length;
1531
2925
  const q = (localQuery || "").toLowerCase();
1532
- renderLocal(mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8));
2926
+ renderLocal(mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8));
1533
2927
  } else if (e.key === "Enter" || e.key === "Tab") {
1534
2928
  if (items.length > 0) {
1535
2929
  e.preventDefault();
1536
2930
  e.stopPropagation();
1537
2931
  const q = (localQuery || "").toLowerCase();
1538
- const filtered = mentionChannels.filter((c) => (0, import_safe_name.safeChannelName)(c.name).toLowerCase().includes(q)).slice(0, 8);
2932
+ const filtered = mentionChannels.filter((c) => safeChannelName(c.name).toLowerCase().includes(q)).slice(0, 8);
1539
2933
  const cur = filtered[localHighlight];
1540
2934
  if (cur) applyLocal(cur);
1541
2935
  }
@@ -1545,7 +2939,7 @@
1545
2939
  }
1546
2940
  }, true);
1547
2941
  }
1548
- const inputArea = document.querySelector(".input-area");
2942
+ var inputArea = document.querySelector(".input-area");
1549
2943
  if (input && inputArea) {
1550
2944
  const onDragOver = (e) => {
1551
2945
  if (e.dataTransfer && Array.from(e.dataTransfer.types || []).includes("application/x-bolloon-judgment")) {
@@ -1584,14 +2978,14 @@
1584
2978
  if (themeToggle) {
1585
2979
  themeToggle.addEventListener("click", toggleTheme);
1586
2980
  }
1587
- const apiConfigBtn = document.getElementById("api-config-btn");
2981
+ var apiConfigBtn = document.getElementById("api-config-btn");
1588
2982
  if (apiConfigBtn) {
1589
2983
  apiConfigBtn.addEventListener("click", () => {
1590
2984
  window.location.href = "/api-config";
1591
2985
  });
1592
2986
  }
1593
- const walletBtn = document.getElementById("wallet-btn");
1594
- const walletBadge = document.getElementById("wallet-badge");
2987
+ var walletBtn = document.getElementById("wallet-btn");
2988
+ var walletBadge = document.getElementById("wallet-badge");
1595
2989
  if (walletBtn) {
1596
2990
  walletBtn.addEventListener("click", openWalletModal);
1597
2991
  }
@@ -1663,7 +3057,7 @@
1663
3057
  await createChannel("\u9ED8\u8BA4\u4F1A\u8BDD");
1664
3058
  }
1665
3059
  }
1666
- const p2pNetworkBtn = document.getElementById("p2p-network-btn");
3060
+ var p2pNetworkBtn = document.getElementById("p2p-network-btn");
1667
3061
  if (p2pNetworkBtn) {
1668
3062
  p2pNetworkBtn.addEventListener("click", () => {
1669
3063
  if (typeof window.showP2PModal === "function") {
@@ -1671,18 +3065,18 @@
1671
3065
  }
1672
3066
  });
1673
3067
  }
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;
3068
+ var judgmentsModal = document.getElementById("judgments-modal");
3069
+ var judgmentsBtn = document.getElementById("judgments-btn");
3070
+ var judgmentsModalClose = document.getElementById("judgments-modal-close");
3071
+ var judgmentDecision = document.getElementById("judgment-decision");
3072
+ var judgmentReason = document.getElementById("judgment-reason");
3073
+ var judgmentDomain = document.getElementById("judgment-domain");
3074
+ var judgmentStakes = document.getElementById("judgment-stakes");
3075
+ var judgmentSubmitBtn = document.getElementById("judgment-submit-btn");
3076
+ var judgmentError = document.getElementById("judgment-error");
3077
+ var judgmentsList = document.getElementById("judgments-list");
3078
+ var judgmentsBadge = document.getElementById("judgments-badge");
3079
+ var judgmentsLoaded = false;
1686
3080
  function showJudgmentsModal() {
1687
3081
  if (judgmentsModal) judgmentsModal.classList.add("active");
1688
3082
  if (!judgmentsLoaded) loadJudgments();
@@ -1720,9 +3114,9 @@
1720
3114
  function hideJudgmentsModal() {
1721
3115
  if (judgmentsModal) judgmentsModal.classList.remove("active");
1722
3116
  }
1723
- let currentJudgmentTab = "channel";
1724
- let currentStatusFilter = "all";
1725
- let lastJudgmentsCache = [];
3117
+ var currentJudgmentTab = "channel";
3118
+ var currentStatusFilter = "all";
3119
+ var lastJudgmentsCache = [];
1726
3120
  function renderJudgments(items) {
1727
3121
  if (!judgmentsList) return;
1728
3122
  const all = items || [];
@@ -1730,7 +3124,7 @@
1730
3124
  const chNameEl = document.getElementById("judgments-tab-channel-name");
1731
3125
  const currentCh = currentChannelId ? channels.find((c) => c.id === currentChannelId) : null;
1732
3126
  if (chNameEl) {
1733
- chNameEl.textContent = currentCh ? `(${(0, import_safe_name.safeChannelName)(currentCh.name)})` : "(\u672A\u9009)";
3127
+ chNameEl.textContent = currentCh ? `(${safeChannelName(currentCh.name)})` : "(\u672A\u9009)";
1734
3128
  }
1735
3129
  if (all.length === 0) {
1736
3130
  judgmentsList.innerHTML = '<div class="task-empty">\u8FD8\u6CA1\u6709\u5224\u65AD, \u5728\u4E0A\u9762\u8BB0\u5F55\u7B2C\u4E00\u6761\u5427</div>';
@@ -1756,7 +3150,7 @@
1756
3150
  );
1757
3151
  const bound = all.filter((j) => boundIds.has(j.id));
1758
3152
  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})`;
3153
+ if (titleEl) titleEl.textContent = `${safeChannelName(currentCh.name)} \u7684\u5224\u65AD\u529B (\u5DF2\u7ED1 ${bound.length} / \u5171 ${all.length})`;
1760
3154
  let html = "";
1761
3155
  if (bound.length > 0) {
1762
3156
  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 +3165,37 @@
1771
3165
  function renderJudgmentItems(items, opts) {
1772
3166
  const { showBindToggle, isBound } = opts || {};
1773
3167
  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";
3168
+ const reason = j.reasons && j.reasons[0] ? escapeHtml2(j.reasons[0]) : "";
3169
+ const domain = j.context && j.context.domain ? escapeHtml2(j.context.domain) : "general";
3170
+ const stakes = j.context && j.context.stakes ? escapeHtml2(j.context.stakes) : "medium";
1777
3171
  const isSuperseded = j.status === "superseded";
1778
3172
  const isRejected = j.status === "rejected";
1779
3173
  const dimmedStyle = isSuperseded || isRejected ? "opacity:0.55;background:#f3f4f6;" : "";
1780
3174
  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>` : "";
3175
+ 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>` : "";
3176
+ 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
3177
  return `
1784
3178
  <div class="task-item completed judgment-row"
1785
- data-judgment-id="${escapeHtml(j.id)}"
3179
+ data-judgment-id="${escapeHtml2(j.id)}"
1786
3180
  draggable="true"
1787
3181
  style="cursor:grab;${dimmedStyle}">
1788
3182
  <div class="task-item-header">
1789
3183
  <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();">
3184
+ <input type="checkbox" class="judgment-select-cb" data-id="${escapeHtml2(j.id)}" style="cursor:pointer;" onclick="event.stopPropagation();">
1791
3185
  </label>
1792
3186
  <div class="task-item-title">
1793
- <span class="judgment-decision">${escapeHtml(j.decision)}</span>${statusTag}
3187
+ <span class="judgment-decision">${escapeHtml2(j.decision)}</span>${statusTag}
1794
3188
  </div>
1795
3189
  <span class="task-item-status completed">${stakes}</span>
1796
3190
  </div>
1797
3191
  ${reason ? `<div class="task-item-desc" style="color:#555;font-size:13px;margin-top:4px;">\u7406\u7531: ${reason}</div>` : ""}
1798
3192
  ${evolveNote}
1799
3193
  <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>
3194
+ <span>${domain} \xB7 ${escapeHtml2(j.timestamp)} \xB7 ${escapeHtml2(j.id)}</span>
1801
3195
  <span style="display:flex;gap:4px;">
1802
3196
  ${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>
3197
+ <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>
3198
+ <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
3199
  </span>
1806
3200
  </div>
1807
3201
  </div>
@@ -1856,7 +3250,7 @@
1856
3250
  }
1857
3251
  judgmentsLoaded = true;
1858
3252
  } catch (e) {
1859
- if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' + escapeHtml(e.message) + "</div>";
3253
+ if (judgmentsList) judgmentsList.innerHTML = '<div class="task-empty">\u52A0\u8F7D\u5931\u8D25: ' + escapeHtml2(e.message) + "</div>";
1860
3254
  }
1861
3255
  }
1862
3256
  function renderViolations(items) {
@@ -1866,18 +3260,18 @@
1866
3260
  return;
1867
3261
  }
1868
3262
  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 || "");
3263
+ const ts = escapeHtml2((v.ts || "").substring(0, 19).replace("T", " "));
3264
+ const userPrev = escapeHtml2(v.userInputPreview || "");
3265
+ const aiPrev = escapeHtml2(v.aiReplyPreview || "");
1872
3266
  const principles = (v.result?.violatedPrinciples || []).map(
1873
3267
  (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>
3268
+ <span style="color:#dc2626;">\u26A0</span> <strong>${escapeHtml2(p.principle || "")}</strong>
3269
+ <span style="color:#991b1b;">\u2014 ${escapeHtml2(p.reason || "")}</span>
1876
3270
  </div>`
1877
3271
  ).join("");
1878
3272
  return `
1879
3273
  <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>
3274
+ <div style="font-size:11px;color:#6b7280;margin-bottom:4px;">${ts} \xB7 confidence=${escapeHtml2(String(v.result?.confidence ?? 0))}</div>
1881
3275
  <div style="font-size:12px;color:#1f2937;"><strong>\u7528\u6237:</strong> ${userPrev}</div>
1882
3276
  <div style="font-size:12px;color:#1f2937;margin-top:2px;"><strong>AI:</strong> ${aiPrev}</div>
1883
3277
  <div style="margin-top:6px;">${principles}</div>
@@ -1888,7 +3282,7 @@
1888
3282
  function renderAdaptiveSuggestions(data) {
1889
3283
  if (!judgmentsList) return;
1890
3284
  const { judgmentsTotal, usageEntriesScanned, suggestions, scannedAt } = data;
1891
- const ts = escapeHtml((scannedAt || "").substring(0, 19).replace("T", " "));
3285
+ const ts = escapeHtml2((scannedAt || "").substring(0, 19).replace("T", " "));
1892
3286
  if (!suggestions || suggestions.length === 0) {
1893
3287
  judgmentsList.innerHTML = `
1894
3288
  <div class="task-empty">\u{1F4CA} \u81EA\u9002\u5E94\u626B\u63CF: \u65E0\u5EFA\u8BAE
@@ -1912,21 +3306,21 @@
1912
3306
  const style = KIND_STYLE[s.kind] || KIND_STYLE.unused;
1913
3307
  const m = s.metrics || {};
1914
3308
  return `
1915
- <div class="task-item" data-suggestion-key="${escapeHtml(s.key)}"
3309
+ <div class="task-item" data-suggestion-key="${escapeHtml2(s.key)}"
1916
3310
  style="border-left:3px solid ${style.color};padding:8px 12px;background:${style.bg};margin-bottom:6px;">
1917
3311
  <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px;">
1918
3312
  <span style="color:${style.color};font-weight:600;font-size:12px;">${style.label}</span>
1919
3313
  <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
3314
  </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>
3315
+ <div style="font-size:12px;color:#1f2937;margin-bottom:4px;"><strong>${escapeHtml2(s.decision)}</strong></div>
3316
+ <div style="font-size:11px;color:#6b7280;margin-bottom:6px;">${escapeHtml2(s.reason)}</div>
1923
3317
  <div style="font-size:11px;color:#9ca3af;margin-bottom:6px;">
1924
3318
  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
3319
  </div>
1926
3320
  <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)}"
3321
+ <button class="adaptive-accept" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
1928
3322
  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)}"
3323
+ <button class="adaptive-reject" data-key="${escapeHtml2(s.key)}" data-id="${escapeHtml2(s.judgmentId)}" data-action-kind="${escapeHtml2(s.action)}"
1930
3324
  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
3325
  </div>
1932
3326
  </div>
@@ -1995,18 +3389,18 @@
1995
3389
  return;
1996
3390
  }
1997
3391
  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)}"
3392
+ <div class="task-item" data-causal-idx="${idx}" data-judgment-a="${escapeHtml2(p.judgmentA)}" data-judgment-b="${escapeHtml2(p.judgmentB)}"
1999
3393
  style="border-left:3px solid #7c3aed;padding:8px 12px;background:#faf5ff;margin-bottom:6px;">
2000
3394
  <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>
3395
+ <span style="color:#7c3aed;font-weight:600;font-size:12px;">${escapeHtml2(p.causalDirection)}</span>
2002
3396
  <span style="font-size:11px;color:#6b7280;">MI=${p.mutualInfo} \xB7 co=${p.coOccurrence}</span>
2003
3397
  </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>
3398
+ <div style="font-size:11px;color:#374151;margin-bottom:4px;">${escapeHtml2(p.explanation || "(\u65E0 LLM \u89E3\u91CA)")}</div>
3399
+ <div style="font-size:10px;color:#9ca3af;">A: ${escapeHtml2(p.judgmentA)} \u2194 B: ${escapeHtml2(p.judgmentB)}</div>
2006
3400
  <div style="margin-top:6px;display:flex;gap:6px;">
2007
- <button class="causal-intervention-a" data-jid="${escapeHtml(p.judgmentA)}"
3401
+ <button class="causal-intervention-a" data-jid="${escapeHtml2(p.judgmentA)}"
2008
3402
  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)}"
3403
+ <button class="causal-intervention-b" data-jid="${escapeHtml2(p.judgmentB)}"
2010
3404
  style="background:#7c3aed;color:#fff;border:none;padding:2px 10px;border-radius:3px;cursor:pointer;font-size:11px;">\u{1F52C} do(B)</button>
2011
3405
  </div>
2012
3406
  <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 +3444,11 @@
2050
3444
  const color = Math.abs(effect) > 0.5 ? "#dc2626" : Math.abs(effect) > 0.2 ? "#d97706" : "#059669";
2051
3445
  resultDiv.innerHTML = `
2052
3446
  <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>
3447
+ <div style="color:#374151;margin-top:4px;">${escapeHtml2(data.reasoning)}</div>
2054
3448
  <div style="color:#9ca3af;margin-top:4px;">confidence=${data.confidence}</div>
2055
3449
  `;
2056
3450
  } catch (err) {
2057
- resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${escapeHtml(err.message)}</div>`;
3451
+ resultDiv.innerHTML = `<div style="color:#dc2626;">\u5931\u8D25: ${escapeHtml2(err.message)}</div>`;
2058
3452
  } finally {
2059
3453
  btn.disabled = false;
2060
3454
  }
@@ -2138,9 +3532,9 @@
2138
3532
  }
2139
3533
  });
2140
3534
  }
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");
3535
+ var judgmentSelectAll = document.getElementById("judgment-select-all");
3536
+ var judgmentSelectedCount = document.getElementById("judgment-selected-count");
3537
+ var judgmentBulkDeleteBtn = document.getElementById("judgment-bulk-delete-btn");
2144
3538
  function getSelectedJudgmentIds() {
2145
3539
  if (!judgmentsList) return [];
2146
3540
  return Array.from(judgmentsList.querySelectorAll(".judgment-select-cb")).filter((cb) => cb.checked).map((cb) => cb.getAttribute("data-id")).filter(Boolean);
@@ -2288,8 +3682,37 @@
2288
3682
  if (e.target === judgmentsModal) hideJudgmentsModal();
2289
3683
  });
2290
3684
  }
2291
- const judgmentImportBtn = document.getElementById("judgment-import-btn");
2292
- const judgmentImportFile = document.getElementById("judgment-import-file");
3685
+ var judgmentImportBtn = document.getElementById("judgment-import-btn");
3686
+ var judgmentImportFile = document.getElementById("judgment-import-file");
3687
+ var judgmentCleanupBtn = document.getElementById("judgment-cleanup-btn");
3688
+ async function runCleanupJudgments(dryRun) {
3689
+ const url = dryRun ? "/api/judgments/cleanup-dry" : "/api/judgments/cleanup";
3690
+ const method = dryRun ? "GET" : "POST";
3691
+ if (judgmentCleanupBtn) judgmentCleanupBtn.disabled = true;
3692
+ const origText = judgmentCleanupBtn?.textContent;
3693
+ if (judgmentCleanupBtn) judgmentCleanupBtn.textContent = dryRun ? "\u{1F50D} \u626B\u63CF\u2026" : "\u2699\uFE0F \u6E05\u7406\u4E2D\u2026";
3694
+ try {
3695
+ const res = await fetch(url, { method });
3696
+ const json = await res.json().catch(() => ({}));
3697
+ if (!res.ok) {
3698
+ showJudgmentError("\u6E05\u7406\u5931\u8D25: " + (json.error || res.status));
3699
+ return;
3700
+ }
3701
+ if (dryRun) {
3702
+ 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)`);
3703
+ } else {
3704
+ showJudgmentOk(`\u6E05\u7406\u5B8C\u6210: ${json.totalBefore} \u2192 ${json.totalAfter} \u6761 (\u8F6F\u5220\u9664 ${json.removed} \u6761)`);
3705
+ if (typeof loadJudgments === "function") await loadJudgments();
3706
+ }
3707
+ } catch (err) {
3708
+ showJudgmentError("\u6E05\u7406\u8BF7\u6C42\u5931\u8D25: " + (err?.message || err));
3709
+ } finally {
3710
+ if (judgmentCleanupBtn) {
3711
+ judgmentCleanupBtn.disabled = false;
3712
+ judgmentCleanupBtn.textContent = origText || "\u6E05\u7406\u6D4B\u8BD5\u6570\u636E";
3713
+ }
3714
+ }
3715
+ }
2293
3716
  function showJudgmentError(msg) {
2294
3717
  if (!judgmentError) return;
2295
3718
  judgmentError.textContent = msg;
@@ -2303,12 +3726,12 @@
2303
3726
  judgmentError.style.color = "#15803d";
2304
3727
  }
2305
3728
  function fileToBase64(file) {
2306
- return new Promise((resolve, reject) => {
3729
+ return new Promise((resolve2, reject) => {
2307
3730
  const r = new FileReader();
2308
3731
  r.onload = () => {
2309
3732
  const s = String(r.result || "");
2310
3733
  const idx = s.indexOf(",");
2311
- resolve(idx >= 0 ? s.substring(idx + 1) : s);
3734
+ resolve2(idx >= 0 ? s.substring(idx + 1) : s);
2312
3735
  };
2313
3736
  r.onerror = () => reject(r.error || new Error("read failed"));
2314
3737
  r.readAsDataURL(file);
@@ -2346,6 +3769,15 @@
2346
3769
  if (f) importJudgmentFile(f);
2347
3770
  });
2348
3771
  }
3772
+ if (judgmentCleanupBtn) {
3773
+ judgmentCleanupBtn.addEventListener("click", async () => {
3774
+ 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;
3775
+ const dry = await runCleanupJudgments(true);
3776
+ if (dry === false) return;
3777
+ 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;
3778
+ await runCleanupJudgments(false);
3779
+ });
3780
+ }
2349
3781
  document.addEventListener("click", async (e) => {
2350
3782
  const btn = e.target.closest && e.target.closest(".save-as-judgment");
2351
3783
  if (!btn) return;
@@ -2445,8 +3877,8 @@
2445
3877
  }
2446
3878
  popup.innerHTML = `
2447
3879
  <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>` : ""}
3880
+ <div style="background:#f9fafb;padding:6px 8px;border-radius:4px;line-height:1.4;">${escapeHtml2(value)}</div>
3881
+ ${evidence ? `<div style="font-size:11px;color:#6b7280;margin-top:4px;">\u8BC1\u636E: ${escapeHtml2(evidence)}</div>` : ""}
2450
3882
  ${evolveNote}
2451
3883
  <div style="display:flex;gap:6px;margin-top:8px;">
2452
3884
  <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 +3905,7 @@
2473
3905
  if (judgmentSubmitBtn) judgmentSubmitBtn.addEventListener("click", submitJudgment);
2474
3906
  loadJudgments();
2475
3907
  setInterval(loadJudgments, 1e4);
2476
- let knownPeers = [];
3908
+ var knownPeers = [];
2477
3909
  async function loadRemoteChannels() {
2478
3910
  try {
2479
3911
  const res = await fetch("/api/p2p-peers");
@@ -2536,22 +3968,22 @@
2536
3968
  const caretChar = "\u25BE";
2537
3969
  return `
2538
3970
  <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)}"
3971
+ <div class="remote-peer-header" data-peer-name="${escapeHtml2(peer.name)}" data-peer-pk="${escapeHtml2(peer.publicKey)}"
2540
3972
  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"
3973
+ <button class="peer-caret-btn" data-toggle-peer="${escapeHtml2(peer.publicKey)}" title="\u6298\u53E0/\u5C55\u5F00"
2542
3974
  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
3975
  <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>
3976
+ <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
3977
  <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)}"
3978
+ <button class="peer-share-btn" title="\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peer.name)}"
2547
3979
  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
3980
  </div>
2549
3981
  <div class="remote-peer-channels" style="margin-top:4px;margin-left:8px;">
2550
3982
  ${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)}"
3983
+ <div class="remote-channel-row" data-peer-id="${escapeHtml2(peer.publicKey)}" data-channel-id="${escapeHtml2(c.id)}"
2552
3984
  style="display:flex;align-items:center;gap:6px;padding:4px 6px;cursor:pointer;border-radius:4px;font-size:12px;">
2553
3985
  <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>
3986
+ <span style="flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escapeHtml2(safeChannelName(c.name, ""))}">${escapeHtml2(safeChannelName(c.name))}</span>
2555
3987
  </div>
2556
3988
  `).join("")}
2557
3989
  </div>
@@ -2619,16 +4051,16 @@
2619
4051
  <span style="font-size:18px;">\u270F\uFE0F</span>
2620
4052
  <div style="flex:1;min-width:0;">
2621
4053
  <div class="friend-req-title">\u7F16\u8F91\u597D\u53CB</div>
2622
- <div class="friend-req-meta">publicKey: ${escapeHtml(peerPublicKey.substring(0, 16))}\u2026</div>
4054
+ <div class="friend-req-meta">publicKey: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
2623
4055
  </div>
2624
4056
  </div>
2625
4057
  <div class="friend-req-body">
2626
4058
  <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)}"
4059
+ <input id="epm-name" type="text" value="${escapeHtml2(currentName)}"
2628
4060
  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
4061
  <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
4062
  <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>
4063
+ 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
4064
  </div>
2633
4065
  <div class="friend-req-actions">
2634
4066
  <button id="epm-cancel" class="friend-req-btn-deny">\u53D6\u6D88</button>
@@ -2679,11 +4111,11 @@
2679
4111
  const isShared = Array.isArray(ch.shared_with_peers) && ch.shared_with_peers.includes(peerPublicKey);
2680
4112
  return `
2681
4113
  <label class="share-modal-row">
2682
- <input type="checkbox" data-cid="${escapeHtml(ch.id)}" ${isShared ? "checked" : ""} class="share-modal-cb">
4114
+ <input type="checkbox" data-cid="${escapeHtml2(ch.id)}" ${isShared ? "checked" : ""} class="share-modal-cb">
2683
4115
  <div class="share-modal-row-info">
2684
- <div class="share-modal-row-name">${escapeHtml(ch.name || "(\u672A\u547D\u540D)")}</div>
4116
+ <div class="share-modal-row-name">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</div>
2685
4117
  <div class="share-modal-row-meta">
2686
- ${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${escapeHtml(ch.id.slice(0, 24))}\u2026
4118
+ ${isShared ? "\u2713 \u5DF2\u5206\u4EAB" : "\u672A\u5206\u4EAB"} \xB7 ${escapeHtml2(ch.id.slice(0, 24))}\u2026
2687
4119
  </div>
2688
4120
  </div>
2689
4121
  </label>
@@ -2695,8 +4127,8 @@
2695
4127
  <div class="friend-req-header">
2696
4128
  <span style="font-size:18px;">\u{1F4E4}</span>
2697
4129
  <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>
4130
+ <div class="friend-req-title">\u5206\u4EAB channel \u7ED9 ${escapeHtml2(peerName)}</div>
4131
+ <div class="friend-req-meta">${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026</div>
2700
4132
  </div>
2701
4133
  <button id="spm-close" class="friend-req-btn-close">\xD7</button>
2702
4134
  </div>
@@ -2756,8 +4188,8 @@
2756
4188
  <div class="remote-chat-shell">
2757
4189
  <div class="remote-chat-header">
2758
4190
  <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>
4191
+ <div class="remote-chat-title">\u{1F310} \u8DDF ${escapeHtml2(channelName)} \u804A\u5929</div>
4192
+ <div class="remote-chat-meta">\u8FDC\u7AEF peer: ${escapeHtml2(peerPublicKey.substring(0, 16))}\u2026 \xB7 ${escapeHtml2(channelId)}</div>
2761
4193
  </div>
2762
4194
  <button id="rcm-refresh-history" title="\u91CD\u65B0\u62C9\u5386\u53F2" class="remote-chat-btn-secondary">\u21BB \u5386\u53F2</button>
2763
4195
  <button id="rcm-close" class="remote-chat-btn-close">\xD7</button>
@@ -2788,10 +4220,10 @@
2788
4220
  };
2789
4221
  document.getElementById("rcm-refresh-history").onclick = () => loadHistory(false);
2790
4222
  const append = (text, role) => {
2791
- addMessage(text, role === "user" ? "user" : "ai", false, log);
4223
+ addMessage2(text, role === "user" ? "user" : "ai", false, log);
2792
4224
  log.scrollTop = log.scrollHeight;
2793
4225
  };
2794
- const appendSystem = (text, kind = "info") => {
4226
+ const appendSystem2 = (text, kind = "info") => {
2795
4227
  const el = document.createElement("div");
2796
4228
  el.className = `remote-chat-sysmsg remote-chat-sysmsg-${kind}`;
2797
4229
  el.textContent = text;
@@ -2825,13 +4257,13 @@
2825
4257
  const res = await fetch(`/api/remote-channels/chat-history?targetPublicKey=${encodeURIComponent(peerPublicKey)}&channelId=${encodeURIComponent(channelId)}`);
2826
4258
  const data = await res.json();
2827
4259
  if (!res.ok) {
2828
- appendSystem(`\u62C9\u53D6\u5931\u8D25: ${data.error || "unknown"}`, "error");
4260
+ appendSystem2(`\u62C9\u53D6\u5931\u8D25: ${data.error || "unknown"}`, "error");
2829
4261
  thinkingEl.style.display = "none";
2830
4262
  return;
2831
4263
  }
2832
4264
  renderHistory(data);
2833
4265
  } catch (err) {
2834
- appendSystem(`\u62C9\u53D6\u5F02\u5E38: ${err.message}`, "error");
4266
+ appendSystem2(`\u62C9\u53D6\u5F02\u5E38: ${err.message}`, "error");
2835
4267
  } finally {
2836
4268
  thinkingEl.style.display = "none";
2837
4269
  }
@@ -2844,7 +4276,7 @@
2844
4276
  jh.className = "remote-chat-judgments";
2845
4277
  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
4278
  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>`;
4279
+ 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
4280
  }
2849
4281
  if (judgments.candidates && judgments.candidates.length > 0) {
2850
4282
  h += `<div class="remote-chat-judgments-foot">+ ${judgments.candidates.length} \u6761\u5019\u9009\u5224\u65AD\u529B (LLM \u53EF\u81EA\u9009\u53C2\u8003)</div>`;
@@ -2854,7 +4286,7 @@
2854
4286
  }
2855
4287
  const msgs = data.messages || [];
2856
4288
  if (msgs.length === 0) {
2857
- appendSystem("\u8FD8\u6CA1\u6709\u5386\u53F2\u6D88\u606F, \u5728\u4E0B\u9762\u53D1\u7B2C\u4E00\u6761\u5427", "info");
4289
+ appendSystem2("\u8FD8\u6CA1\u6709\u5386\u53F2\u6D88\u606F, \u5728\u4E0B\u9762\u53D1\u7B2C\u4E00\u6761\u5427", "info");
2858
4290
  } else {
2859
4291
  for (const m of msgs) {
2860
4292
  const type = m.type === "user" ? "user" : "ai";
@@ -2874,7 +4306,7 @@
2874
4306
 
2875
4307
  `;
2876
4308
  }
2877
- addMessage(prefix + (m.content || ""), type, false, log);
4309
+ addMessage2(prefix + (m.content || ""), type, false, log);
2878
4310
  }
2879
4311
  setTimeout(() => {
2880
4312
  log.scrollTop = log.scrollHeight;
@@ -2897,7 +4329,7 @@
2897
4329
  const data = await res.json();
2898
4330
  if (!res.ok) throw new Error(data.error || "send failed");
2899
4331
  } catch (err) {
2900
- appendSystem("\u53D1\u9001\u5931\u8D25: " + (err.message || err), "error");
4332
+ appendSystem2("\u53D1\u9001\u5931\u8D25: " + (err.message || err), "error");
2901
4333
  } finally {
2902
4334
  sendBtn2.disabled = false;
2903
4335
  sendBtn2.textContent = "\u53D1\u9001";
@@ -2913,7 +4345,7 @@
2913
4345
  loadHistory(false);
2914
4346
  historyRefreshTimer = setInterval(() => loadHistory(true), 15e3);
2915
4347
  }
2916
- const showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
4348
+ var showMyIdBtn = document.getElementById("show-my-p2p-id-btn");
2917
4349
  if (showMyIdBtn) {
2918
4350
  showMyIdBtn.addEventListener("click", async (e) => {
2919
4351
  e.stopPropagation();
@@ -2946,7 +4378,7 @@
2946
4378
  body.innerHTML = `
2947
4379
  <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
4380
  <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>
4381
+ <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
4382
  <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
4383
  </div>
2952
4384
  <div id="mpim-status" style="font-size:12px;color:#059669;min-height:16px;"></div>
@@ -2977,11 +4409,11 @@
2977
4409
  };
2978
4410
  } catch (err) {
2979
4411
  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>`;
4412
+ if (body) body.innerHTML = `<div style="color:#b91c1c;font-size:13px;">\u2717 \u83B7\u53D6\u5931\u8D25: ${escapeHtml2(err.message || String(err))}</div>`;
2981
4413
  }
2982
4414
  });
2983
4415
  }
2984
- const addPeerBtn = document.getElementById("add-p2p-peer-btn");
4416
+ var addPeerBtn = document.getElementById("add-p2p-peer-btn");
2985
4417
  if (addPeerBtn) {
2986
4418
  addPeerBtn.addEventListener("click", async (e) => {
2987
4419
  e.stopPropagation();
@@ -3037,11 +4469,11 @@
3037
4469
  <span style="font-size:20px;">\u{1F91D}</span>
3038
4470
  <div style="flex:1;min-width:0;">
3039
4471
  <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>
4472
+ <div class="friend-req-meta">\u6765\u81EA ${escapeHtml2(req.fromName)} (${escapeHtml2(req.fromPublicKey.substring(0, 16))}\u2026)</div>
3041
4473
  </div>
3042
4474
  </div>
3043
4475
  <div class="friend-req-body">
3044
- <p style="margin:0 0 8px;">${escapeHtml(req.message || "\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB")}</p>
4476
+ <p style="margin:0 0 8px;">${escapeHtml2(req.message || "\u60F3\u52A0\u4F60\u4E3A P2P \u597D\u53CB")}</p>
3045
4477
  <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
4478
  </div>
3047
4479
  <div class="friend-req-actions">
@@ -3094,7 +4526,7 @@
3094
4526
  setTimeout(() => el.remove(), 320);
3095
4527
  }, 3e3);
3096
4528
  }
3097
- const p2pToggleAllBtn = document.getElementById("p2p-toggle-all-btn");
4529
+ var p2pToggleAllBtn = document.getElementById("p2p-toggle-all-btn");
3098
4530
  if (p2pToggleAllBtn) {
3099
4531
  let syncToggleAllBtn = function() {
3100
4532
  const allPks = /* @__PURE__ */ new Set([
@@ -3117,7 +4549,7 @@
3117
4549
  p2pToggleAllBtn.title = "\u70B9\u51FB\u6298\u53E0\u6240\u6709 P2P \u597D\u53CB";
3118
4550
  }
3119
4551
  };
3120
- var syncToggleAllBtn2 = syncToggleAllBtn;
4552
+ syncToggleAllBtn2 = syncToggleAllBtn;
3121
4553
  p2pToggleAllBtn.addEventListener("click", (e) => {
3122
4554
  e.stopPropagation();
3123
4555
  const allPks = /* @__PURE__ */ new Set([
@@ -3138,7 +4570,8 @@
3138
4570
  window.__syncP2PToggleAllBtn = syncToggleAllBtn;
3139
4571
  syncToggleAllBtn();
3140
4572
  }
3141
- const refreshSharedBtn = document.getElementById("refresh-shared-btn");
4573
+ var syncToggleAllBtn2;
4574
+ var refreshSharedBtn = document.getElementById("refresh-shared-btn");
3142
4575
  if (refreshSharedBtn) {
3143
4576
  refreshSharedBtn.addEventListener("click", async (e) => {
3144
4577
  e.stopPropagation();
@@ -3163,17 +4596,17 @@
3163
4596
  loadRemoteChannels();
3164
4597
  setInterval(loadRemoteChannels, 8e3);
3165
4598
  startV3GlobalSSE();
3166
- const localSection = document.querySelector(".sidebar-section");
3167
- const remoteSection = document.getElementById("remote-agents-section");
4599
+ var localSection = document.querySelector(".sidebar-section");
4600
+ var remoteSection = document.getElementById("remote-agents-section");
3168
4601
  if (localSection) localSection.classList.add("local-flex");
3169
4602
  if (remoteSection) remoteSection.classList.add("remote-flex");
3170
- const remoteHeader = document.getElementById("remote-agents-header");
4603
+ var remoteHeader = document.getElementById("remote-agents-header");
3171
4604
  if (remoteHeader && remoteSection) {
3172
4605
  remoteHeader.addEventListener("click", (e) => {
3173
4606
  remoteSection.classList.toggle("collapsed");
3174
4607
  });
3175
4608
  }
3176
- const splitHandle = document.getElementById("sidebar-split-handle");
4609
+ var splitHandle = document.getElementById("sidebar-split-handle");
3177
4610
  if (splitHandle && localSection && remoteSection) {
3178
4611
  const updateFlexVars = (localRatio, remoteRatio) => {
3179
4612
  localSection.style.setProperty("--local-flex", String(localRatio));
@@ -3217,17 +4650,17 @@
3217
4650
  updateFlexVars(1, 1);
3218
4651
  });
3219
4652
  }
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;
4653
+ var walletModal = document.getElementById("wallet-modal");
4654
+ var walletModalClose = document.getElementById("wallet-modal-close");
4655
+ var walletBindAddress = document.getElementById("wallet-bind-address");
4656
+ var walletGenerateBtn = document.getElementById("wallet-generate-btn");
4657
+ var walletAutoTools = document.getElementById("wallet-auto-tools");
4658
+ var walletBindBtn = document.getElementById("wallet-bind-btn");
4659
+ var walletUnbindBtn = document.getElementById("wallet-unbind-btn");
4660
+ var walletNewInfo = document.getElementById("wallet-new-info");
4661
+ var walletListEl = document.getElementById("wallet-list");
4662
+ var walletModalPendingSecret = null;
4663
+ var walletModalPendingMnemonic = null;
3231
4664
  if (walletModalClose) {
3232
4665
  walletModalClose.addEventListener("click", closeWalletModal);
3233
4666
  }
@@ -3287,7 +4720,7 @@
3287
4720
  walletModalPendingSecret = null;
3288
4721
  walletModalPendingMnemonic = null;
3289
4722
  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>';
4723
+ 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
4724
  } catch (err) {
3292
4725
  alert("\u7ED1\u5B9A\u5931\u8D25: " + err.message);
3293
4726
  }
@@ -3345,24 +4778,24 @@
3345
4778
  const row = document.createElement("div");
3346
4779
  row.className = "wallet-row" + (isActive ? " is-active" : "");
3347
4780
  row.innerHTML = `
3348
- <span class="wallet-chain">${escapeHtml(chain)}</span>
4781
+ <span class="wallet-chain">${escapeHtml2(chain)}</span>
3349
4782
  <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>
4783
+ <span class="wallet-agent" title="${escapeHtml2(ch.name || "")}">${escapeHtml2(ch.name || "(\u672A\u547D\u540D)")}</span>
4784
+ <span class="wallet-address" title="${escapeHtml2(ch.walletAddress)}">${escapeHtml2(ch.walletAddress)}</span>
3352
4785
  </div>
3353
4786
  <div class="wallet-actions">
3354
- <button class="wallet-mini-btn" data-action="copy" data-addr="${escapeHtml(ch.walletAddress)}" title="\u590D\u5236\u5730\u5740">
4787
+ <button class="wallet-mini-btn" data-action="copy" data-addr="${escapeHtml2(ch.walletAddress)}" title="\u590D\u5236\u5730\u5740">
3355
4788
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3356
4789
  <rect x="9" y="9" width="13" height="13" rx="2"></rect>
3357
4790
  <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
3358
4791
  </svg>
3359
4792
  </button>
3360
- <button class="wallet-mini-btn" data-action="goto" data-id="${escapeHtml(ch.id)}" title="\u5207\u6362\u5230\u8BE5\u667A\u80FD\u4F53">
4793
+ <button class="wallet-mini-btn" data-action="goto" data-id="${escapeHtml2(ch.id)}" title="\u5207\u6362\u5230\u8BE5\u667A\u80FD\u4F53">
3361
4794
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3362
4795
  <path d="M5 12h14M12 5l7 7-7 7"></path>
3363
4796
  </svg>
3364
4797
  </button>
3365
- <button class="wallet-mini-btn" data-action="unbind" data-id="${escapeHtml(ch.id)}" title="\u89E3\u7ED1">
4798
+ <button class="wallet-mini-btn" data-action="unbind" data-id="${escapeHtml2(ch.id)}" title="\u89E3\u7ED1">
3366
4799
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
3367
4800
  <line x1="18" y1="6" x2="6" y2="18"></line>
3368
4801
  <line x1="6" y1="6" x2="18" y2="18"></line>
@@ -3421,18 +4854,18 @@
3421
4854
  return "?";
3422
4855
  }
3423
4856
  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;
4857
+ var agentAddModal = document.getElementById("agent-add-modal");
4858
+ var agentAddTitle = document.getElementById("agent-add-title");
4859
+ var agentAddModalClose = document.getElementById("agent-add-modal-close");
4860
+ var agentAddName = document.getElementById("agent-add-name");
4861
+ var agentAddWallet = document.getElementById("agent-add-wallet");
4862
+ var agentAddAutoTools = document.getElementById("agent-add-auto-tools");
4863
+ var agentAddConfirmBtn = document.getElementById("agent-add-confirm-btn");
4864
+ var agentAddCancelBtn = document.getElementById("agent-add-cancel-btn");
4865
+ var agentAddWalletInfo = document.getElementById("agent-add-wallet-info");
4866
+ var agentGenerateWalletBtn = document.getElementById("agent-generate-wallet-btn");
4867
+ var pendingWalletSecret = null;
4868
+ var pendingWalletMnemonic = null;
3436
4869
  function openAgentAddModal(existingChannel) {
3437
4870
  if (!agentAddModal) return;
3438
4871
  if (existingChannel) {
@@ -3481,17 +4914,17 @@
3481
4914
  function formatWalletInfoHtml({ address, privateKey, mnemonic }) {
3482
4915
  const parts = [
3483
4916
  "\u2713 \u5DF2\u751F\u6210\u771F\u5B9E EVM \u94B1\u5305 (BIP-39 + secp256k1 + EIP-55)",
3484
- "<strong>\u5730\u5740:</strong> <code>" + escapeHtml(address) + "</code>"
4917
+ "<strong>\u5730\u5740:</strong> <code>" + escapeHtml2(address) + "</code>"
3485
4918
  ];
3486
4919
  if (mnemonic) {
3487
4920
  parts.push(
3488
4921
  "<strong>\u52A9\u8BB0\u8BCD (12 \u8BCD, \u8BF7\u6284\u5199\u4FDD\u5B58):</strong>",
3489
- '<code style="color:#fc6;word-break:break-all;">' + escapeHtml(mnemonic) + "</code>"
4922
+ '<code style="color:#fc6;word-break:break-all;">' + escapeHtml2(mnemonic) + "</code>"
3490
4923
  );
3491
4924
  }
3492
4925
  parts.push(
3493
4926
  "<strong>\u79C1\u94A5 (0x + 32 \u5B57\u8282):</strong>",
3494
- '<code style="color:#f88;word-break:break-all;">' + escapeHtml(privateKey) + "</code>",
4927
+ '<code style="color:#f88;word-break:break-all;">' + escapeHtml2(privateKey) + "</code>",
3495
4928
  '<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
4929
  '<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
4930
  );
@@ -3508,7 +4941,7 @@
3508
4941
  pendingWalletMnemonic = wallet.mnemonic;
3509
4942
  agentAddWalletInfo.innerHTML = formatWalletInfoHtml(wallet);
3510
4943
  } catch (err) {
3511
- agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " + escapeHtml(err.message);
4944
+ agentAddWalletInfo.innerHTML = "\u2717 \u751F\u6210\u94B1\u5305\u5931\u8D25: " + escapeHtml2(err.message);
3512
4945
  }
3513
4946
  });
3514
4947
  }