@bolloon/bolloon-agent 0.4.33 → 0.5.1

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 (214) hide show
  1. package/dist/agents/contacts/chain.js +27 -7
  2. package/dist/agents/execution-supervisor.js +580 -40
  3. package/dist/agents/external-events.js +35 -6
  4. package/dist/agents/gateway-group.js +100 -12
  5. package/dist/agents/goal-criteria.js +10 -3
  6. package/dist/agents/goal-flywheel/block-executor.js +346 -0
  7. package/dist/agents/goal-flywheel/continuation-decision.js +592 -0
  8. package/dist/agents/goal-flywheel/goal-change.js +754 -0
  9. package/dist/agents/goal-flywheel/index.js +26 -0
  10. package/dist/agents/goal-flywheel/memory-layers.js +227 -0
  11. package/dist/agents/goal-flywheel/run-closure.js +809 -0
  12. package/dist/agents/goal-flywheel/skill-candidate.js +628 -0
  13. package/dist/agents/goal-flywheel/types.js +329 -0
  14. package/dist/agents/goal-flywheel/wiring/change.js +288 -0
  15. package/dist/agents/goal-flywheel/wiring/closure.js +697 -0
  16. package/dist/agents/goal-flywheel/wiring/continuation.js +432 -0
  17. package/dist/agents/goal-flywheel/wiring/contract.js +270 -0
  18. package/dist/agents/goal-flywheel/wiring/index.js +26 -0
  19. package/dist/agents/goal-flywheel/wiring/monitor.js +254 -0
  20. package/dist/agents/goal-flywheel/wiring/seams.js +592 -0
  21. package/dist/agents/goal-flywheel/work-contract.js +478 -0
  22. package/dist/agents/goal-flywheel/work-monitor.js +488 -0
  23. package/dist/agents/goal-flywheel-wiring.js +1870 -0
  24. package/dist/agents/goal-state-reducer.js +406 -0
  25. package/dist/agents/goal-store.js +35 -1
  26. package/dist/agents/mobile-flywheel-view.js +144 -0
  27. package/dist/agents/mobile-task-actions-verify.js +74 -0
  28. package/dist/agents/mobile-task-actions.js +178 -0
  29. package/dist/agents/mobile-task-views.js +199 -0
  30. package/dist/agents/model-policy.js +651 -0
  31. package/dist/agents/network-pulse.js +304 -18
  32. package/dist/agents/pi-sdk-tools.js +84 -33
  33. package/dist/agents/pi-sdk.js +157 -42
  34. package/dist/agents/run-store.js +71 -5
  35. package/dist/agents/skill-readiness.js +52 -10
  36. package/dist/agents/subagent-manager.js +209 -3
  37. package/dist/agents/task/task-runner.js +17 -4
  38. package/dist/agents/task-group.js +8 -51
  39. package/dist/agents/task-public-text.js +89 -0
  40. package/dist/agents/workflow-pivot-loop.js +19 -12
  41. package/dist/cli/clipboard.js +65 -0
  42. package/dist/cli/commands/index.js +1 -0
  43. package/dist/cli/commands/tasks.js +350 -2
  44. package/dist/cli/identity-command.js +177 -0
  45. package/dist/cli/ink-app.js +145 -17
  46. package/dist/cli/log-gate.js +386 -0
  47. package/dist/cli/mention-data.js +2 -0
  48. package/dist/cli/model-selector.js +538 -0
  49. package/dist/cli/reply-hygiene.js +54 -0
  50. package/dist/cli/setup-wizard.js +757 -69
  51. package/dist/cli/startup-notice.js +160 -0
  52. package/dist/cli/stores.js +22 -1
  53. package/dist/cli/theme.js +49 -0
  54. package/dist/cli/tui-select.js +970 -0
  55. package/dist/cli/update-commands.js +150 -24
  56. package/dist/cli-entry.js +137 -20
  57. package/dist/electron-build/agents/contacts/chain.js +26 -6
  58. package/dist/electron-build/agents/contacts/chain.js.map +1 -1
  59. package/dist/electron-build/agents/execution-supervisor.js +578 -38
  60. package/dist/electron-build/agents/execution-supervisor.js.map +1 -1
  61. package/dist/electron-build/agents/external-events.js +34 -5
  62. package/dist/electron-build/agents/external-events.js.map +1 -1
  63. package/dist/electron-build/agents/goal-criteria.js +10 -3
  64. package/dist/electron-build/agents/goal-criteria.js.map +1 -1
  65. package/dist/electron-build/agents/goal-flywheel/continuation-decision.js +598 -0
  66. package/dist/electron-build/agents/goal-flywheel/continuation-decision.js.map +1 -0
  67. package/dist/electron-build/agents/goal-flywheel/goal-change.js +773 -0
  68. package/dist/electron-build/agents/goal-flywheel/goal-change.js.map +1 -0
  69. package/dist/electron-build/agents/goal-flywheel/memory-layers.js +269 -0
  70. package/dist/electron-build/agents/goal-flywheel/memory-layers.js.map +1 -0
  71. package/dist/electron-build/agents/goal-flywheel/run-closure.js +818 -0
  72. package/dist/electron-build/agents/goal-flywheel/run-closure.js.map +1 -0
  73. package/dist/electron-build/agents/goal-flywheel/skill-candidate.js +642 -0
  74. package/dist/electron-build/agents/goal-flywheel/skill-candidate.js.map +1 -0
  75. package/dist/electron-build/agents/goal-flywheel/types.js +333 -0
  76. package/dist/electron-build/agents/goal-flywheel/types.js.map +1 -0
  77. package/dist/electron-build/agents/goal-flywheel/wiring/change.js +294 -0
  78. package/dist/electron-build/agents/goal-flywheel/wiring/change.js.map +1 -0
  79. package/dist/electron-build/agents/goal-flywheel/wiring/closure.js +742 -0
  80. package/dist/electron-build/agents/goal-flywheel/wiring/closure.js.map +1 -0
  81. package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js +444 -0
  82. package/dist/electron-build/agents/goal-flywheel/wiring/continuation.js.map +1 -0
  83. package/dist/electron-build/agents/goal-flywheel/wiring/contract.js +279 -0
  84. package/dist/electron-build/agents/goal-flywheel/wiring/contract.js.map +1 -0
  85. package/dist/electron-build/agents/goal-flywheel/wiring/index.js +111 -0
  86. package/dist/electron-build/agents/goal-flywheel/wiring/index.js.map +1 -0
  87. package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js +264 -0
  88. package/dist/electron-build/agents/goal-flywheel/wiring/monitor.js.map +1 -0
  89. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js +612 -0
  90. package/dist/electron-build/agents/goal-flywheel/wiring/seams.js.map +1 -0
  91. package/dist/electron-build/agents/goal-flywheel/work-contract.js +488 -0
  92. package/dist/electron-build/agents/goal-flywheel/work-contract.js.map +1 -0
  93. package/dist/electron-build/agents/goal-flywheel/work-monitor.js +501 -0
  94. package/dist/electron-build/agents/goal-flywheel/work-monitor.js.map +1 -0
  95. package/dist/electron-build/agents/goal-flywheel-wiring.js +1962 -0
  96. package/dist/electron-build/agents/goal-flywheel-wiring.js.map +1 -0
  97. package/dist/electron-build/agents/goal-state-reducer.js +414 -0
  98. package/dist/electron-build/agents/goal-state-reducer.js.map +1 -0
  99. package/dist/electron-build/agents/goal-store.js +35 -1
  100. package/dist/electron-build/agents/goal-store.js.map +1 -1
  101. package/dist/electron-build/agents/model-policy.js +706 -0
  102. package/dist/electron-build/agents/model-policy.js.map +1 -0
  103. package/dist/electron-build/agents/network-pulse.js +307 -19
  104. package/dist/electron-build/agents/network-pulse.js.map +1 -1
  105. package/dist/electron-build/agents/pi-sdk-tools.js +84 -33
  106. package/dist/electron-build/agents/pi-sdk-tools.js.map +1 -1
  107. package/dist/electron-build/agents/pi-sdk-types.js.map +1 -1
  108. package/dist/electron-build/agents/pi-sdk.js +156 -41
  109. package/dist/electron-build/agents/pi-sdk.js.map +1 -1
  110. package/dist/electron-build/agents/run-store.js +75 -6
  111. package/dist/electron-build/agents/run-store.js.map +1 -1
  112. package/dist/electron-build/agents/skill-readiness.js +52 -9
  113. package/dist/electron-build/agents/skill-readiness.js.map +1 -1
  114. package/dist/electron-build/agents/task/task-runner.js +16 -3
  115. package/dist/electron-build/agents/task/task-runner.js.map +1 -1
  116. package/dist/electron-build/agents/workflow-pivot-loop.js +19 -12
  117. package/dist/electron-build/agents/workflow-pivot-loop.js.map +1 -1
  118. package/dist/electron-build/llm/config-store.js +146 -6
  119. package/dist/electron-build/llm/config-store.js.map +1 -1
  120. package/dist/electron-build/llm/connection-probe.js +764 -0
  121. package/dist/electron-build/llm/connection-probe.js.map +1 -0
  122. package/dist/electron-build/llm/custom-provider-store.js +272 -0
  123. package/dist/electron-build/llm/custom-provider-store.js.map +1 -0
  124. package/dist/electron-build/llm/data/provider-catalog-baked.js +23 -0
  125. package/dist/electron-build/llm/data/provider-catalog-baked.js.map +1 -0
  126. package/dist/electron-build/llm/model-catalog.js +778 -0
  127. package/dist/electron-build/llm/model-catalog.js.map +1 -0
  128. package/dist/electron-build/llm/model-discovery.js +1202 -0
  129. package/dist/electron-build/llm/model-discovery.js.map +1 -0
  130. package/dist/electron-build/llm/model-selection.js +1315 -0
  131. package/dist/electron-build/llm/model-selection.js.map +1 -0
  132. package/dist/electron-build/llm/pi-ai.js +70 -11
  133. package/dist/electron-build/llm/pi-ai.js.map +1 -1
  134. package/dist/electron-build/llm/provider-catalog.js +969 -0
  135. package/dist/electron-build/llm/provider-catalog.js.map +1 -0
  136. package/dist/electron-build/llm/provider-registry.js +950 -0
  137. package/dist/electron-build/llm/provider-registry.js.map +1 -0
  138. package/dist/electron-build/llm/tool-name.js +266 -0
  139. package/dist/electron-build/llm/tool-name.js.map +1 -0
  140. package/dist/electron-build/orbitdb/cid-database.js +83 -17
  141. package/dist/electron-build/orbitdb/cid-database.js.map +1 -1
  142. package/dist/electron-build/orbitdb/ipfs-node.js +31 -2
  143. package/dist/electron-build/orbitdb/ipfs-node.js.map +1 -1
  144. package/dist/electron-build/utils/dual-source-facts.js +236 -0
  145. package/dist/electron-build/utils/dual-source-facts.js.map +1 -0
  146. package/dist/electron-build/utils/dual-source.js +347 -0
  147. package/dist/electron-build/utils/dual-source.js.map +1 -0
  148. package/dist/electron-build/utils/update-manager.js +328 -104
  149. package/dist/electron-build/utils/update-manager.js.map +1 -1
  150. package/dist/electron-build/utils/update-state.js +9 -11
  151. package/dist/electron-build/utils/update-state.js.map +1 -1
  152. package/dist/electron-build/utils/version-identity.js +193 -0
  153. package/dist/electron-build/utils/version-identity.js.map +1 -0
  154. package/dist/electron-build/utils/version-info.js +57 -31
  155. package/dist/electron-build/utils/version-info.js.map +1 -1
  156. package/dist/index.js +385 -108
  157. package/dist/ios/bolloon-web.json +10 -0
  158. package/dist/ios/chain-explorer.js +617 -0
  159. package/dist/ios/explorer.html +163 -0
  160. package/dist/ios/index.html +96 -0
  161. package/dist/ios/mobile-agent.js +2 -2
  162. package/dist/ios/mobile-contacts.js +362 -0
  163. package/dist/ios/mobile-core.js +3102 -310
  164. package/dist/ios/mobile-privacy.js +185 -0
  165. package/dist/ios/mobile-tasks.js +339 -0
  166. package/dist/ios/mobile-update.js +1442 -0
  167. package/dist/ios/mobile.css +19 -0
  168. package/dist/ios/mobile.html +96 -0
  169. package/dist/ios/mobile.js +1013 -41
  170. package/dist/ios/routes-chain-index.js +108 -0
  171. package/dist/ios/routes-contacts.js +420 -0
  172. package/dist/ios/routes-llm-config.js +344 -52
  173. package/dist/ios/routes-mobile-tasks.js +353 -0
  174. package/dist/ios/routes-onchain-trade.js +99 -0
  175. package/dist/ios/routes-x402-info.js +1 -0
  176. package/dist/ios/server.js +921 -0
  177. package/dist/ios/sw.js +1 -1
  178. package/dist/ios/task-frame-server.js +211 -0
  179. package/dist/llm/config-store.js +146 -6
  180. package/dist/llm/connection-probe.js +744 -0
  181. package/dist/llm/custom-provider-store.js +224 -0
  182. package/dist/llm/data/provider-catalog-baked.js +19 -0
  183. package/dist/llm/model-catalog.js +717 -0
  184. package/dist/llm/model-discovery.js +1137 -0
  185. package/dist/llm/model-selection.js +1241 -0
  186. package/dist/llm/pi-ai.js +70 -11
  187. package/dist/llm/provider-catalog.js +898 -0
  188. package/dist/llm/provider-registry.js +917 -0
  189. package/dist/llm/tool-name.js +220 -0
  190. package/dist/orbitdb/cid-database.js +81 -16
  191. package/dist/orbitdb/ipfs-node.js +31 -2
  192. package/dist/setup/onboard.js +34 -8
  193. package/dist/utils/dual-source-facts.js +223 -0
  194. package/dist/utils/dual-source.js +297 -0
  195. package/dist/utils/update-cli.js +16 -21
  196. package/dist/utils/update-health.js +35 -5
  197. package/dist/utils/update-manager.js +325 -96
  198. package/dist/utils/update-state.js +13 -10
  199. package/dist/utils/version-identity.js +176 -0
  200. package/dist/utils/version-info.js +27 -22
  201. package/dist/web/bolloon-web.json +10 -0
  202. package/dist/web/mobile-contacts.js +44 -11
  203. package/dist/web/mobile-core.js +2901 -580
  204. package/dist/web/mobile-tasks.js +339 -0
  205. package/dist/web/mobile-update.js +1442 -0
  206. package/dist/web/mobile.css +19 -0
  207. package/dist/web/mobile.html +64 -0
  208. package/dist/web/mobile.js +838 -35
  209. package/dist/web/routes-llm-config.js +344 -52
  210. package/dist/web/routes-mobile-tasks.js +353 -0
  211. package/dist/web/server.js +163 -4
  212. package/dist/web/sw.js +1 -1
  213. package/package.json +3 -1
  214. package/scripts/build-web.ts +36 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "schema": "bolloon-mobile-web/1",
3
+ "version": "0.5.1",
4
+ "channel": "stable",
5
+ "build": "stable",
6
+ "commit": "40006e67bf87e892569dd0b02cf2456fddcbbfaf",
7
+ "builtAt": "2026-09-27T05:37:37.788Z",
8
+ "layer": "web",
9
+ "note": "web 资源层构建戳 (手机端 OTA 的身份第 ③ 级回退; 装机后以 OTA state.json 为准)"
10
+ }
@@ -0,0 +1,617 @@
1
+ // src/web/chain-explorer.ts
2
+ var DEFAULT_PAGE_LIMIT = 25;
3
+ var POLL_INTERVAL_MS = 2e4;
4
+ var REQUEST_TIMEOUT_MS = 4e3;
5
+ var BACKOFF_BASE_MS = 1e3;
6
+ var BACKOFF_CAP_MS = 3e4;
7
+ var FINALITY_TIERS = ["observed", "confirmed", "finalized"];
8
+ var HEX40 = /^0x[0-9a-fA-F]{40}$/;
9
+ var HEX64 = /^0x[0-9a-fA-F]{64}$/;
10
+ function isFullAddress(v) {
11
+ return typeof v === "string" && HEX40.test(v);
12
+ }
13
+ function isFullHash(v) {
14
+ return typeof v === "string" && HEX64.test(v);
15
+ }
16
+ function shortHex(v, head = 4, tail = 4) {
17
+ const s = String(v ?? "");
18
+ if (HEX40.test(s) || HEX64.test(s)) return `${s.slice(0, 2 + head)}…${s.slice(-tail)}`;
19
+ if (s.length > 24) return `${s.slice(0, 12)}…${s.slice(-6)}`;
20
+ return s;
21
+ }
22
+ var ARG_ORDER = [
23
+ "taskKey",
24
+ "taskId",
25
+ "buyer",
26
+ "agent",
27
+ "caller",
28
+ "to",
29
+ "refundedTo",
30
+ "amount",
31
+ "by",
32
+ "deadline",
33
+ "window",
34
+ "feeBps",
35
+ "proofHash",
36
+ "resultHash",
37
+ "resultDigest",
38
+ "reason"
39
+ ];
40
+ function summarizeArgs(args) {
41
+ const a = args || {};
42
+ const keys = Object.keys(a);
43
+ const ordered = [...ARG_ORDER.filter((k) => keys.includes(k)), ...keys.filter((k) => !ARG_ORDER.includes(k)).sort()];
44
+ return ordered.map((k) => ({ key: k, value: shortHex(a[k]) }));
45
+ }
46
+ function argsText(args) {
47
+ return summarizeArgs(args).map(({ key, value }) => `${key}=${value}`).join(" · ");
48
+ }
49
+ function finalityOf(e) {
50
+ const f = String(e?.finality ?? "");
51
+ return FINALITY_TIERS.includes(f) ? f : "unknown";
52
+ }
53
+ function eventTone(eventName) {
54
+ switch (String(eventName || "")) {
55
+ case "EscrowCreatedV2":
56
+ return "created";
57
+ case "ProofSubmittedV2":
58
+ return "proof";
59
+ case "ReleasedV2":
60
+ return "released";
61
+ case "RefundedV2":
62
+ return "refunded";
63
+ case "DisputedV2":
64
+ return "disputed";
65
+ case "ExpiredV2":
66
+ return "expired";
67
+ default:
68
+ return "other";
69
+ }
70
+ }
71
+ function compareEntries(a, b) {
72
+ if (a.blockNumber !== b.blockNumber) return a.blockNumber - b.blockNumber;
73
+ if (a.logIndex !== b.logIndex) return a.logIndex - b.logIndex;
74
+ return String(a.txHash).localeCompare(String(b.txHash));
75
+ }
76
+ function sortEntries(list) {
77
+ return [...list].sort(compareEntries);
78
+ }
79
+ function cursorKey(e) {
80
+ return `${e.blockNumber}:${e.logIndex}`;
81
+ }
82
+ function mergeEntries(prev, next) {
83
+ const map = /* @__PURE__ */ new Map();
84
+ for (const e of [...prev, ...next]) {
85
+ if (!e || !Number.isInteger(e.blockNumber)) continue;
86
+ map.set(cursorKey(e), e);
87
+ }
88
+ return [...map.values()].sort(compareEntries);
89
+ }
90
+ function pageBoundaryIsExact(prev, next) {
91
+ const last = prev.events[prev.events.length - 1];
92
+ const first = next.events[0];
93
+ if (!last || !first) return false;
94
+ return first.blockNumber > last.blockNumber || first.blockNumber === last.blockNumber && first.logIndex > last.logIndex;
95
+ }
96
+ function finalityLabel(f, lang) {
97
+ const zh = { observed: "观测中", confirmed: "已确认", finalized: "已最终", unknown: "未知" };
98
+ const en = { observed: "observed", confirmed: "confirmed", finalized: "finalized", unknown: "unknown" };
99
+ return (lang === "zh" ? zh : en)[f] || String(f);
100
+ }
101
+ function eventLabel(eventName, lang) {
102
+ const tone = eventTone(eventName);
103
+ const zh = {
104
+ created: "托管创建",
105
+ proof: "提交证明",
106
+ released: "已放款",
107
+ refunded: "已退款",
108
+ disputed: "争议",
109
+ expired: "已过期",
110
+ other: "其它"
111
+ };
112
+ const en = {
113
+ created: "escrow created",
114
+ proof: "proof submitted",
115
+ released: "released",
116
+ refunded: "refunded",
117
+ disputed: "disputed",
118
+ expired: "expired",
119
+ other: "other"
120
+ };
121
+ return (lang === "zh" ? zh : en)[tone];
122
+ }
123
+ function formatAgo(ms, lang) {
124
+ if (ms === null || ms === void 0 || !Number.isFinite(ms)) return lang === "zh" ? "未知" : "unknown";
125
+ const s = Math.max(0, Math.floor(ms / 1e3));
126
+ if (s < 60) return lang === "zh" ? `${s} 秒前` : `${s}s ago`;
127
+ const m = Math.floor(s / 60);
128
+ if (m < 60) return lang === "zh" ? `${m} 分钟前` : `${m}m ago`;
129
+ const h = Math.floor(m / 60);
130
+ if (h < 24) return lang === "zh" ? `${h} 小时前` : `${h}h ago`;
131
+ const d = Math.floor(h / 24);
132
+ return lang === "zh" ? `${d} 天前` : `${d}d ago`;
133
+ }
134
+ function formatTime(ts) {
135
+ if (ts === null || ts === void 0 || !Number.isFinite(ts)) return "—";
136
+ const d = new Date(ts);
137
+ const p = (n) => String(n).padStart(2, "0");
138
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`;
139
+ }
140
+ function num(v) {
141
+ return typeof v === "number" && Number.isFinite(v) ? String(v) : v === null || v === void 0 ? "—" : String(v);
142
+ }
143
+ function renderEntryText(e) {
144
+ return [
145
+ `#${num(e.blockNumber)}`,
146
+ `log${num(e.logIndex)}`,
147
+ shortHex(e.txHash),
148
+ String(e.eventName || ""),
149
+ argsText(e.args),
150
+ `${num(e.confirmations)} conf`,
151
+ finalityLabel(finalityOf(e), "en")
152
+ ].join(" | ");
153
+ }
154
+ function emptyTimelineText(taskKeyShort, lang) {
155
+ return lang === "zh" ? `${taskKeyShort}: 索引里没有这个 taskKey 的事件 (不是"状态未知", 是"从未出现")` : `${taskKeyShort}: no events for this taskKey in the index (not "unknown state" — never seen)`;
156
+ }
157
+ function noIndexText(lang) {
158
+ return lang === "zh" ? "索引为空或尚未同步 (索引器还没扫过链上事件)" : "index is empty or not synced yet (indexer has not scanned any events)";
159
+ }
160
+ function backoffDelayMs(attempt, base = BACKOFF_BASE_MS, cap = BACKOFF_CAP_MS) {
161
+ const a = Math.max(0, Math.min(20, Math.floor(Number.isFinite(attempt) ? attempt : 0)));
162
+ return Math.min(cap, base * 2 ** a);
163
+ }
164
+ function joinBase(apiBase) {
165
+ return String(apiBase || "").replace(/\/+$/, "");
166
+ }
167
+ async function getJSON(url, fetchImpl, timeoutMs = REQUEST_TIMEOUT_MS) {
168
+ const ctl = typeof AbortController !== "undefined" ? new AbortController() : null;
169
+ let timer = null;
170
+ if (ctl) timer = setTimeout(() => {
171
+ try {
172
+ ctl.abort();
173
+ } catch {
174
+ }
175
+ }, timeoutMs);
176
+ try {
177
+ const r = await fetchImpl(url, ctl ? { signal: ctl.signal, headers: { accept: "application/json" } } : { headers: { accept: "application/json" } });
178
+ if (!r || typeof r.ok !== "boolean") throw new Error("无响应");
179
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
180
+ return await r.json();
181
+ } finally {
182
+ if (timer) clearTimeout(timer);
183
+ }
184
+ }
185
+ async function fetchSnapshot(apiBase, opts = {}) {
186
+ const f = opts.fetchImpl || globalThis.fetch;
187
+ const t = opts.timeoutMs ?? REQUEST_TIMEOUT_MS;
188
+ const base = joinBase(apiBase);
189
+ const fetchedAt = Date.now();
190
+ if (typeof f !== "function") return { status: "degraded", indexStatus: null, stats: null, error: "当前环境没有 fetch", fetchedAt };
191
+ try {
192
+ const [s, st] = await Promise.all([
193
+ getJSON(`${base}/api/chain/index/status`, f, t),
194
+ getJSON(`${base}/api/chain/index/stats`, f, t)
195
+ ]);
196
+ if (!s?.ok || !st?.ok) throw new Error("接口返回 ok=false");
197
+ return { status: "live", indexStatus: s.status || {}, stats: st.stats || {}, error: null, fetchedAt };
198
+ } catch (e) {
199
+ return { status: "degraded", indexStatus: null, stats: null, error: String(e?.message || e).slice(0, 200), fetchedAt };
200
+ }
201
+ }
202
+ async function fetchPage(apiBase, cursor, opts = {}) {
203
+ const f = opts.fetchImpl || globalThis.fetch;
204
+ const q = new URLSearchParams();
205
+ if (cursor) {
206
+ q.set("blockNumber", String(cursor.blockNumber));
207
+ q.set("logIndex", String(cursor.logIndex));
208
+ }
209
+ q.set("limit", String(opts.limit ?? DEFAULT_PAGE_LIMIT));
210
+ const j = await getJSON(`${joinBase(apiBase)}/api/chain/index/events?${q.toString()}`, f, opts.timeoutMs ?? REQUEST_TIMEOUT_MS);
211
+ if (!j?.ok) throw new Error("接口返回 ok=false");
212
+ const p = j.page || {};
213
+ return {
214
+ events: Array.isArray(p.events) ? p.events : [],
215
+ nextCursor: p.nextCursor || null,
216
+ hasMore: !!p.hasMore,
217
+ remaining: Number(p.remaining ?? 0)
218
+ };
219
+ }
220
+ async function fetchTaskTimeline(apiBase, taskKey, opts = {}) {
221
+ const f = opts.fetchImpl || globalThis.fetch;
222
+ const j = await getJSON(`${joinBase(apiBase)}/api/chain/index/timeline?taskKey=${encodeURIComponent(taskKey)}`, f, opts.timeoutMs ?? REQUEST_TIMEOUT_MS);
223
+ if (!j?.ok) throw new Error("接口返回 ok=false");
224
+ const t = j.timeline || {};
225
+ return {
226
+ taskKey: String(t.taskKey || taskKey),
227
+ state: t.state ?? null,
228
+ events: Array.isArray(t.events) ? t.events : [],
229
+ count: Number(t.count ?? 0),
230
+ hasSuspect: !!t.hasSuspect
231
+ };
232
+ }
233
+ var isTaskKey = (v) => typeof v === "string" && /^0x[0-9a-fA-F]{64}$/.test(v);
234
+ function mountChainExplorer(opts = {}) {
235
+ const win = opts.win || globalThis;
236
+ const doc = win.document;
237
+ if (!doc) return () => {
238
+ };
239
+ const root = doc.getElementById("chain-explorer-root");
240
+ if (!root) return () => {
241
+ };
242
+ const fetchImpl = opts.fetchImpl || win.fetch?.bind(win);
243
+ const pollMs = opts.pollMs ?? POLL_INTERVAL_MS;
244
+ const pageLimit = opts.pageLimit ?? DEFAULT_PAGE_LIMIT;
245
+ const timeoutMs = opts.timeoutMs ?? REQUEST_TIMEOUT_MS;
246
+ let query = null;
247
+ try {
248
+ query = new URLSearchParams(win.location?.search || "");
249
+ } catch {
250
+ query = null;
251
+ }
252
+ const apiBase = String(opts.apiBase ?? query?.get("apiBase") ?? root.getAttribute("data-api-base") ?? "");
253
+ const base = apiBase.replace(/\/+$/, "");
254
+ let lang = "zh";
255
+ try {
256
+ const stored = win.localStorage?.getItem("bolloon-lang");
257
+ const nav = String(win.navigator?.language || "");
258
+ lang = stored === "en" || stored === "zh" ? stored : nav.toLowerCase().startsWith("en") ? "en" : "zh";
259
+ } catch {
260
+ lang = "zh";
261
+ }
262
+ const state = {
263
+ snapshot: null,
264
+ entries: [],
265
+ cursor: null,
266
+ hasMore: false,
267
+ loading: false,
268
+ failStreak: 0,
269
+ taskKey: null,
270
+ taskEvents: [],
271
+ taskState: null,
272
+ taskLoaded: false,
273
+ remaining: 0,
274
+ timer: null,
275
+ disposed: false
276
+ };
277
+ const el = (id) => doc.getElementById(id);
278
+ const put = (id, text) => {
279
+ const n = el(id);
280
+ if (n) n.textContent = text;
281
+ };
282
+ const show = (id, on) => {
283
+ const n = el(id);
284
+ if (n) n.style.display = on ? "" : "none";
285
+ };
286
+ const t = (zh, en) => lang === "zh" ? zh : en;
287
+ function applyLang() {
288
+ doc.documentElement?.setAttribute?.("lang", lang === "zh" ? "zh-CN" : "en");
289
+ const nodes = doc.querySelectorAll("[data-zh]");
290
+ for (let i = 0; i < nodes.length; i++) {
291
+ const n = nodes[i];
292
+ const v = n.getAttribute(lang === "zh" ? "data-zh" : "data-en");
293
+ if (v !== null) n.textContent = v;
294
+ }
295
+ put("cx-lang", lang === "zh" ? "中文 / EN" : "EN / 中文");
296
+ renderHeader();
297
+ renderRows();
298
+ renderTask();
299
+ }
300
+ function setPageState(kind, detail) {
301
+ const n = el("cx-state");
302
+ if (n) n.textContent = detail;
303
+ doc.documentElement?.setAttribute?.("data-cx-status", kind);
304
+ const dot = el("cx-state-dot");
305
+ if (dot) dot.className = `cx-dot cx-dot-${kind}`;
306
+ }
307
+ function renderHeader() {
308
+ const s = state.snapshot;
309
+ const live = s?.status === "live";
310
+ show("cx-degraded", !live && !!s);
311
+ if (!s) {
312
+ setPageState("loading", t("加载中…", "loading…"));
313
+ return;
314
+ }
315
+ if (!live) {
316
+ setPageState("degraded", t("后端不可用 (降级态)", "backend unavailable (degraded)"));
317
+ put("cx-degraded-reason", t("原因: ", "reason: ") + String(s.error || t("未知", "unknown")));
318
+ for (const id of [
319
+ "cx-height",
320
+ "cx-head",
321
+ "cx-lastsync",
322
+ "cx-entries",
323
+ "cx-tasks",
324
+ "cx-created",
325
+ "cx-proof",
326
+ "cx-released",
327
+ "cx-refunded",
328
+ "cx-disputed",
329
+ "cx-expired",
330
+ "cx-final-observed",
331
+ "cx-final-confirmed",
332
+ "cx-final-finalized",
333
+ "cx-suspects",
334
+ "cx-escrow",
335
+ "cx-chain"
336
+ ]) {
337
+ put(id, "—");
338
+ }
339
+ put("cx-lastsync", t("— (后端不可用)", "— (backend down)"));
340
+ return;
341
+ }
342
+ const st = s.indexStatus || {};
343
+ const stats = s.stats || {};
344
+ put("cx-height", num(st.lastSyncedBlock));
345
+ put("cx-head", num(st.headBlock));
346
+ put("cx-lastsync", st.lastSyncedAt == null ? t("从未同步", "never synced") : `${formatAgo(st.lastSyncedAgoMs ?? Date.now() - Number(st.lastSyncedAt), lang)} (${formatTime(Number(st.lastSyncedAt))})`);
347
+ put("cx-entries", num(stats.entries));
348
+ put("cx-tasks", num(stats.tasks));
349
+ put("cx-created", num(stats.created));
350
+ put("cx-proof", num(stats.proofSubmitted));
351
+ put("cx-released", num(stats.released));
352
+ put("cx-refunded", num(stats.refunded));
353
+ put("cx-disputed", num(stats.disputed));
354
+ put("cx-expired", num(stats.expired));
355
+ const bf = stats.byFinality || {};
356
+ for (const f of FINALITY_TIERS) put(`cx-final-${f}`, num(bf[f]));
357
+ put("cx-suspects", num(stats.suspects));
358
+ put("cx-escrow", shortHex(st.escrowAddress));
359
+ put("cx-chain", `${st.networkName || "—"} · chainId ${num(st.chainId)}`);
360
+ const empty = (stats.entries ?? 0) === 0;
361
+ setPageState("live", empty ? t("后端在线 · 索引为空", "backend live · index empty") : `${t("后端在线 · 索引高度", "backend live · height")} ${num(st.lastSyncedBlock)} · ${num(stats.entries)} ${t("条事件", "events")}`);
362
+ }
363
+ function rowNode(e, idx) {
364
+ const li = doc.createElement("li");
365
+ li.className = "cx-row";
366
+ li.setAttribute("data-idx", String(idx));
367
+ li.setAttribute("data-finality", finalityOf(e));
368
+ li.setAttribute("data-tone", eventTone(e.eventName));
369
+ const mk = (cls, text) => {
370
+ const s = doc.createElement("span");
371
+ s.className = cls;
372
+ s.textContent = text;
373
+ return s;
374
+ };
375
+ li.appendChild(mk("cx-block", `#${num(e.blockNumber)}:${num(e.logIndex)}`));
376
+ li.appendChild(mk("cx-tx", shortHex(e.txHash)));
377
+ li.appendChild(mk("cx-task", shortHex(e.args?.taskKey)));
378
+ li.appendChild(mk("cx-event", `${String(e.eventName || "")} · ${eventLabel(e.eventName, lang)}`));
379
+ li.appendChild(mk("cx-args", argsText(e.args)));
380
+ li.appendChild(mk("cx-conf", `${num(e.confirmations)} conf`));
381
+ const badge = mk(`cx-badge cx-badge-${finalityOf(e)}`, finalityLabel(finalityOf(e), lang));
382
+ li.appendChild(badge);
383
+ if (e.suspect) li.appendChild(mk("cx-suspect", t("被回退", "reverted")));
384
+ const btn = doc.createElement("button");
385
+ btn.type = "button";
386
+ btn.className = "cx-link";
387
+ btn.textContent = t("看时间线", "timeline");
388
+ if (isTaskKey(e.args?.taskKey)) {
389
+ btn.addEventListener("click", () => {
390
+ void openTask(String(e.args.taskKey));
391
+ });
392
+ } else {
393
+ btn.disabled = true;
394
+ }
395
+ li.appendChild(btn);
396
+ return li;
397
+ }
398
+ function renderRows() {
399
+ const body = el("cx-timeline-body");
400
+ if (!body) return;
401
+ body.textContent = "";
402
+ if (!state.entries.length) {
403
+ const li = doc.createElement("li");
404
+ li.className = "cx-empty";
405
+ li.id = "cx-timeline-empty";
406
+ li.textContent = state.snapshot?.status === "degraded" ? t("后端不可用, 未加载任何事件 (降级态, 不显示假数据)", "backend unavailable — nothing loaded (degraded, no fabricated rows)") : noIndexText(lang);
407
+ body.appendChild(li);
408
+ } else {
409
+ state.entries.forEach((e, i) => body.appendChild(rowNode(e, i)));
410
+ }
411
+ put("cx-timeline-count", t(`已加载 ${state.entries.length} 条`, `${state.entries.length} loaded`));
412
+ const more2 = el("cx-more");
413
+ if (more2) {
414
+ more2.disabled = state.loading || !state.hasMore;
415
+ more2.textContent = state.loading ? t("加载中…", "loading…") : state.hasMore ? t("加载更多 (按 cursor 取下一段)", "load more (next cursor)") : t("已到底 (无更多)", "end of index");
416
+ more2.style.display = state.entries.length || state.hasMore ? "" : "none";
417
+ }
418
+ const rem = el("cx-remaining");
419
+ if (rem) rem.textContent = state.hasMore ? t(`还有 ${state.remaining - state.entries.length} 条`, `${state.remaining - state.entries.length} more`) : "";
420
+ }
421
+ function renderTask() {
422
+ const body = el("cx-task-body");
423
+ if (!body) return;
424
+ body.textContent = "";
425
+ const title = el("cx-task-title");
426
+ if (!state.taskKey) {
427
+ if (title) title.textContent = t("(未选择)", "(none selected)");
428
+ const hint = doc.createElement("li");
429
+ hint.className = "cx-empty";
430
+ hint.textContent = t("点上面任意一行的「看时间线」, 或粘贴一个 taskKey", 'Click "timeline" on any row, or paste a taskKey');
431
+ body.appendChild(hint);
432
+ return;
433
+ }
434
+ const short = shortHex(state.taskKey);
435
+ if (title) title.textContent = `${short} · ${state.taskState ? state.taskState : t("状态未知", "state unknown")}`;
436
+ if (!state.taskLoaded) {
437
+ const li = doc.createElement("li");
438
+ li.className = "cx-empty";
439
+ li.textContent = t("加载中…", "loading…");
440
+ body.appendChild(li);
441
+ return;
442
+ }
443
+ if (!state.taskEvents.length) {
444
+ const li = doc.createElement("li");
445
+ li.className = "cx-empty";
446
+ li.id = "cx-task-empty";
447
+ li.textContent = emptyTimelineText(short, lang);
448
+ body.appendChild(li);
449
+ return;
450
+ }
451
+ state.taskEvents.forEach((e, i) => body.appendChild(rowNode(e, i)));
452
+ }
453
+ async function refreshHeader() {
454
+ const snap = await fetchSnapshot(base, { fetchImpl, timeoutMs });
455
+ state.snapshot = snap;
456
+ if (snap.status === "live") state.failStreak = 0;
457
+ else state.failStreak++;
458
+ renderHeader();
459
+ return snap.status === "live";
460
+ }
461
+ async function loadMore() {
462
+ if (state.loading) return;
463
+ state.loading = true;
464
+ renderRows();
465
+ try {
466
+ const page = await fetchPage(base, state.cursor, { limit: pageLimit, fetchImpl, timeoutMs });
467
+ state.entries = mergeEntries(state.entries, page.events);
468
+ state.cursor = page.nextCursor || state.cursor;
469
+ state.hasMore = page.hasMore;
470
+ state.remaining = page.remaining;
471
+ state.failStreak = 0;
472
+ } catch (e) {
473
+ state.failStreak++;
474
+ const lede = el("cx-timeline-lede");
475
+ if (lede) lede.textContent = t("加载失败: ", "load failed: ") + shortHex(String(e?.message || e));
476
+ } finally {
477
+ state.loading = false;
478
+ renderRows();
479
+ }
480
+ }
481
+ async function openTask(taskKey) {
482
+ state.taskKey = taskKey;
483
+ state.taskLoaded = false;
484
+ state.taskEvents = [];
485
+ state.taskState = null;
486
+ renderTask();
487
+ try {
488
+ const tl = await fetchTaskTimeline(base, taskKey, { fetchImpl, timeoutMs });
489
+ state.taskEvents = sortEntries(tl.events);
490
+ state.taskState = tl.state;
491
+ state.taskLoaded = true;
492
+ } catch (e) {
493
+ state.taskLoaded = true;
494
+ state.taskEvents = [];
495
+ const body = el("cx-task-body");
496
+ if (body) {
497
+ body.textContent = "";
498
+ const li = doc.createElement("li");
499
+ li.className = "cx-empty";
500
+ li.textContent = t("取时间线失败: ", "timeline fetch failed: ") + String(e?.message || e).slice(0, 120);
501
+ body.appendChild(li);
502
+ }
503
+ return;
504
+ }
505
+ renderTask();
506
+ }
507
+ function schedule(next) {
508
+ if (state.disposed) return;
509
+ if (state.timer) clearTimeout(state.timer);
510
+ state.timer = setTimeout(async () => {
511
+ if (state.disposed) return;
512
+ const ok = await refreshHeader();
513
+ schedule(ok ? pollMs : backoffDelayMs(state.failStreak - 1));
514
+ }, next);
515
+ }
516
+ const more = el("cx-more");
517
+ if (more) more.addEventListener("click", () => {
518
+ void loadMore();
519
+ });
520
+ const reload = el("cx-reload");
521
+ if (reload) reload.addEventListener("click", () => {
522
+ void refreshHeader();
523
+ void loadMore();
524
+ });
525
+ const langBtn = el("cx-lang");
526
+ if (langBtn) langBtn.addEventListener("click", () => {
527
+ lang = lang === "zh" ? "en" : "zh";
528
+ try {
529
+ win.localStorage?.setItem("bolloon-lang", lang);
530
+ } catch {
531
+ }
532
+ applyLang();
533
+ });
534
+ const form = el("cx-task-form");
535
+ if (form) form.addEventListener("submit", (ev) => {
536
+ ev?.preventDefault?.();
537
+ const input = el("cx-task-input");
538
+ const v = String(input?.value || "").trim().toLowerCase();
539
+ if (input) input.value = "";
540
+ if (!isTaskKey(v)) {
541
+ const body = el("cx-task-body");
542
+ if (body) {
543
+ body.textContent = "";
544
+ const li = doc.createElement("li");
545
+ li.className = "cx-empty";
546
+ li.textContent = t("taskKey 必须是 0x + 64 位 hex", "taskKey must be 0x + 64 hex");
547
+ body.appendChild(li);
548
+ }
549
+ state.taskKey = null;
550
+ return;
551
+ }
552
+ void openTask(v);
553
+ });
554
+ try {
555
+ const mq = win.matchMedia?.("(prefers-reduced-motion: reduce)");
556
+ const apply = () => root.classList.toggle("cx-reduce", !!mq?.matches);
557
+ apply();
558
+ mq?.addEventListener?.("change", apply);
559
+ } catch {
560
+ }
561
+ applyLang();
562
+ void (async () => {
563
+ const ok = await refreshHeader();
564
+ await loadMore();
565
+ doc.documentElement?.setAttribute?.("data-cx-ready", "1");
566
+ schedule(ok ? pollMs : backoffDelayMs(Math.max(0, state.failStreak - 1)));
567
+ })();
568
+ return () => {
569
+ state.disposed = true;
570
+ if (state.timer) clearTimeout(state.timer);
571
+ };
572
+ }
573
+ if (typeof document !== "undefined" && typeof window !== "undefined") {
574
+ const boot = () => {
575
+ try {
576
+ mountChainExplorer({ win: window });
577
+ } catch (e) {
578
+ console.error("[chain-explorer] 挂载失败", e);
579
+ }
580
+ };
581
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", boot);
582
+ else boot();
583
+ }
584
+ export {
585
+ BACKOFF_BASE_MS,
586
+ BACKOFF_CAP_MS,
587
+ DEFAULT_PAGE_LIMIT,
588
+ FINALITY_TIERS,
589
+ POLL_INTERVAL_MS,
590
+ REQUEST_TIMEOUT_MS,
591
+ argsText,
592
+ backoffDelayMs,
593
+ compareEntries,
594
+ cursorKey,
595
+ emptyTimelineText,
596
+ eventLabel,
597
+ eventTone,
598
+ fetchPage,
599
+ fetchSnapshot,
600
+ fetchTaskTimeline,
601
+ finalityLabel,
602
+ finalityOf,
603
+ formatAgo,
604
+ formatTime,
605
+ getJSON,
606
+ isFullAddress,
607
+ isFullHash,
608
+ mergeEntries,
609
+ mountChainExplorer,
610
+ noIndexText,
611
+ num,
612
+ pageBoundaryIsExact,
613
+ renderEntryText,
614
+ shortHex,
615
+ sortEntries,
616
+ summarizeArgs
617
+ };