@0xmaxma/claude-gateway 2.0.4 → 2.0.6

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 (276) hide show
  1. package/README.md +10 -1
  2. package/dist/agent/builtin-commands.d.ts +1 -1
  3. package/dist/agent/builtin-commands.d.ts.map +1 -1
  4. package/dist/agent/builtin-commands.js +3 -2
  5. package/dist/agent/builtin-commands.js.map +1 -1
  6. package/dist/agent/runner.d.ts +123 -9
  7. package/dist/agent/runner.d.ts.map +1 -1
  8. package/dist/agent/runner.js +215 -182
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/agent/session-compaction-scheduler.d.ts +22 -0
  11. package/dist/agent/session-compaction-scheduler.d.ts.map +1 -0
  12. package/dist/agent/session-compaction-scheduler.js +40 -0
  13. package/dist/agent/session-compaction-scheduler.js.map +1 -0
  14. package/dist/agent/skill-learning/index.d.ts.map +1 -1
  15. package/dist/agent/skill-learning/index.js +27 -8
  16. package/dist/agent/skill-learning/index.js.map +1 -1
  17. package/dist/agent/skill-learning/reviewer.d.ts +3 -1
  18. package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
  19. package/dist/agent/skill-learning/reviewer.js +6 -2
  20. package/dist/agent/skill-learning/reviewer.js.map +1 -1
  21. package/dist/agent/turn-stream.d.ts +3 -1
  22. package/dist/agent/turn-stream.d.ts.map +1 -1
  23. package/dist/agent/turn-stream.js +8 -4
  24. package/dist/agent/turn-stream.js.map +1 -1
  25. package/dist/api/gateway-router.d.ts +2 -0
  26. package/dist/api/gateway-router.d.ts.map +1 -1
  27. package/dist/api/gateway-router.js +323 -16
  28. package/dist/api/gateway-router.js.map +1 -1
  29. package/dist/api/memory-activity.d.ts +18 -0
  30. package/dist/api/memory-activity.d.ts.map +1 -0
  31. package/dist/api/memory-activity.js +97 -0
  32. package/dist/api/memory-activity.js.map +1 -0
  33. package/dist/api/router.js +11 -11
  34. package/dist/api/router.js.map +1 -1
  35. package/dist/api/slack-webhook-router.js +1 -1
  36. package/dist/api/slack-webhook-router.js.map +1 -1
  37. package/dist/api/voice-router.d.ts +1 -0
  38. package/dist/api/voice-router.d.ts.map +1 -1
  39. package/dist/api/voice-router.js +129 -7
  40. package/dist/api/voice-router.js.map +1 -1
  41. package/dist/config/claude-settings.d.ts +14 -0
  42. package/dist/config/claude-settings.d.ts.map +1 -1
  43. package/dist/config/claude-settings.js +20 -0
  44. package/dist/config/claude-settings.js.map +1 -1
  45. package/dist/orchestration/agent-cli-session.d.ts +46 -0
  46. package/dist/orchestration/agent-cli-session.d.ts.map +1 -0
  47. package/dist/orchestration/agent-cli-session.js +121 -0
  48. package/dist/orchestration/agent-cli-session.js.map +1 -0
  49. package/dist/orchestration/agent-tool-schemas.d.ts +6 -0
  50. package/dist/orchestration/agent-tool-schemas.d.ts.map +1 -0
  51. package/dist/orchestration/agent-tool-schemas.js +23 -0
  52. package/dist/orchestration/agent-tool-schemas.js.map +1 -0
  53. package/dist/orchestration/bridge.d.ts +6 -3
  54. package/dist/orchestration/bridge.d.ts.map +1 -1
  55. package/dist/orchestration/bridge.js +31 -14
  56. package/dist/orchestration/bridge.js.map +1 -1
  57. package/dist/orchestration/browser-voice.d.ts +14 -0
  58. package/dist/orchestration/browser-voice.d.ts.map +1 -0
  59. package/dist/orchestration/browser-voice.js +30 -0
  60. package/dist/orchestration/browser-voice.js.map +1 -0
  61. package/dist/orchestration/compact-measurements.d.ts +21 -0
  62. package/dist/orchestration/compact-measurements.d.ts.map +1 -0
  63. package/dist/orchestration/compact-measurements.js +62 -0
  64. package/dist/orchestration/compact-measurements.js.map +1 -0
  65. package/dist/orchestration/config.d.ts +4 -0
  66. package/dist/orchestration/config.d.ts.map +1 -1
  67. package/dist/orchestration/config.js +1 -1
  68. package/dist/orchestration/config.js.map +1 -1
  69. package/dist/orchestration/context-delivery.d.ts +32 -0
  70. package/dist/orchestration/context-delivery.d.ts.map +1 -0
  71. package/dist/orchestration/context-delivery.js +101 -0
  72. package/dist/orchestration/context-delivery.js.map +1 -0
  73. package/dist/orchestration/context-footprint.d.ts +14 -0
  74. package/dist/orchestration/context-footprint.d.ts.map +1 -0
  75. package/dist/orchestration/context-footprint.js +57 -0
  76. package/dist/orchestration/context-footprint.js.map +1 -0
  77. package/dist/orchestration/conversation-intake.d.ts +10 -6
  78. package/dist/orchestration/conversation-intake.d.ts.map +1 -1
  79. package/dist/orchestration/conversation-intake.js +22 -8
  80. package/dist/orchestration/conversation-intake.js.map +1 -1
  81. package/dist/orchestration/dashboard-charts.d.ts +32 -0
  82. package/dist/orchestration/dashboard-charts.d.ts.map +1 -0
  83. package/dist/orchestration/dashboard-charts.js +77 -0
  84. package/dist/orchestration/dashboard-charts.js.map +1 -0
  85. package/dist/orchestration/dashboard-reader-worker.d.ts +2 -0
  86. package/dist/orchestration/dashboard-reader-worker.d.ts.map +1 -0
  87. package/dist/orchestration/dashboard-reader-worker.js +173 -0
  88. package/dist/orchestration/dashboard-reader-worker.js.map +1 -0
  89. package/dist/orchestration/dashboard-reader.d.ts +15 -0
  90. package/dist/orchestration/dashboard-reader.d.ts.map +1 -0
  91. package/dist/orchestration/dashboard-reader.js +87 -0
  92. package/dist/orchestration/dashboard-reader.js.map +1 -0
  93. package/dist/orchestration/decision-context.d.ts +15 -0
  94. package/dist/orchestration/decision-context.d.ts.map +1 -0
  95. package/dist/orchestration/decision-context.js +57 -0
  96. package/dist/orchestration/decision-context.js.map +1 -0
  97. package/dist/orchestration/display-stream.d.ts +21 -2
  98. package/dist/orchestration/display-stream.d.ts.map +1 -1
  99. package/dist/orchestration/display-stream.js +61 -8
  100. package/dist/orchestration/display-stream.js.map +1 -1
  101. package/dist/orchestration/inference-errors.d.ts.map +1 -1
  102. package/dist/orchestration/inference-errors.js +19 -21
  103. package/dist/orchestration/inference-errors.js.map +1 -1
  104. package/dist/orchestration/mutation-recovery.d.ts +13 -0
  105. package/dist/orchestration/mutation-recovery.d.ts.map +1 -0
  106. package/dist/orchestration/mutation-recovery.js +49 -0
  107. package/dist/orchestration/mutation-recovery.js.map +1 -0
  108. package/dist/orchestration/native-compact.d.ts +8 -0
  109. package/dist/orchestration/native-compact.d.ts.map +1 -0
  110. package/dist/orchestration/native-compact.js +33 -0
  111. package/dist/orchestration/native-compact.js.map +1 -0
  112. package/dist/orchestration/notification-mailbox.d.ts +3 -1
  113. package/dist/orchestration/notification-mailbox.d.ts.map +1 -1
  114. package/dist/orchestration/notification-mailbox.js +4 -2
  115. package/dist/orchestration/notification-mailbox.js.map +1 -1
  116. package/dist/orchestration/process-turn.d.ts +16 -2
  117. package/dist/orchestration/process-turn.d.ts.map +1 -1
  118. package/dist/orchestration/process-turn.js +78 -14
  119. package/dist/orchestration/process-turn.js.map +1 -1
  120. package/dist/orchestration/progress-review.d.ts +8 -18
  121. package/dist/orchestration/progress-review.d.ts.map +1 -1
  122. package/dist/orchestration/progress-review.js +12 -14
  123. package/dist/orchestration/progress-review.js.map +1 -1
  124. package/dist/orchestration/provider-message.d.ts +7 -0
  125. package/dist/orchestration/provider-message.d.ts.map +1 -0
  126. package/dist/orchestration/provider-message.js +63 -0
  127. package/dist/orchestration/provider-message.js.map +1 -0
  128. package/dist/orchestration/response-errors.d.ts +6 -0
  129. package/dist/orchestration/response-errors.d.ts.map +1 -0
  130. package/dist/orchestration/response-errors.js +88 -0
  131. package/dist/orchestration/response-errors.js.map +1 -0
  132. package/dist/orchestration/response-schema.d.ts +33 -0
  133. package/dist/orchestration/response-schema.d.ts.map +1 -0
  134. package/dist/orchestration/response-schema.js +61 -0
  135. package/dist/orchestration/response-schema.js.map +1 -0
  136. package/dist/orchestration/runtime.d.ts +79 -0
  137. package/dist/orchestration/runtime.d.ts.map +1 -1
  138. package/dist/orchestration/runtime.js +415 -68
  139. package/dist/orchestration/runtime.js.map +1 -1
  140. package/dist/orchestration/session-compaction.d.ts +59 -0
  141. package/dist/orchestration/session-compaction.d.ts.map +1 -0
  142. package/dist/orchestration/session-compaction.js +212 -0
  143. package/dist/orchestration/session-compaction.js.map +1 -0
  144. package/dist/orchestration/skills.d.ts.map +1 -1
  145. package/dist/orchestration/skills.js +3 -2
  146. package/dist/orchestration/skills.js.map +1 -1
  147. package/dist/orchestration/source-policy.d.ts +1 -1
  148. package/dist/orchestration/source-policy.d.ts.map +1 -1
  149. package/dist/orchestration/source-policy.js +1 -1
  150. package/dist/orchestration/speech.d.ts +15 -17
  151. package/dist/orchestration/speech.d.ts.map +1 -1
  152. package/dist/orchestration/speech.js +90 -30
  153. package/dist/orchestration/speech.js.map +1 -1
  154. package/dist/orchestration/store.d.ts +2 -0
  155. package/dist/orchestration/store.d.ts.map +1 -1
  156. package/dist/orchestration/store.js +16 -1
  157. package/dist/orchestration/store.js.map +1 -1
  158. package/dist/orchestration/tasks/driver.d.ts.map +1 -1
  159. package/dist/orchestration/tasks/driver.js +8 -3
  160. package/dist/orchestration/tasks/driver.js.map +1 -1
  161. package/dist/orchestration/tasks/pool.d.ts.map +1 -1
  162. package/dist/orchestration/tasks/pool.js +8 -4
  163. package/dist/orchestration/tasks/pool.js.map +1 -1
  164. package/dist/orchestration/tasks/service.d.ts +48 -1
  165. package/dist/orchestration/tasks/service.d.ts.map +1 -1
  166. package/dist/orchestration/tasks/service.js +41 -17
  167. package/dist/orchestration/tasks/service.js.map +1 -1
  168. package/dist/orchestration/token-ledger.d.ts +132 -0
  169. package/dist/orchestration/token-ledger.d.ts.map +1 -0
  170. package/dist/orchestration/token-ledger.js +233 -0
  171. package/dist/orchestration/token-ledger.js.map +1 -0
  172. package/dist/orchestration/token-usage.d.ts +46 -0
  173. package/dist/orchestration/token-usage.d.ts.map +1 -0
  174. package/dist/orchestration/token-usage.js +102 -0
  175. package/dist/orchestration/token-usage.js.map +1 -0
  176. package/dist/orchestration/tool-activity.d.ts.map +1 -1
  177. package/dist/orchestration/tool-activity.js +5 -3
  178. package/dist/orchestration/tool-activity.js.map +1 -1
  179. package/dist/orchestration/tool-name.d.ts +10 -0
  180. package/dist/orchestration/tool-name.d.ts.map +1 -0
  181. package/dist/orchestration/tool-name.js +14 -0
  182. package/dist/orchestration/tool-name.js.map +1 -0
  183. package/dist/orchestration/transcript-checkpoint.d.ts +14 -0
  184. package/dist/orchestration/transcript-checkpoint.d.ts.map +1 -0
  185. package/dist/orchestration/transcript-checkpoint.js +83 -0
  186. package/dist/orchestration/transcript-checkpoint.js.map +1 -0
  187. package/dist/orchestration/unanswered-inputs.d.ts +6 -0
  188. package/dist/orchestration/unanswered-inputs.d.ts.map +1 -0
  189. package/dist/orchestration/unanswered-inputs.js +18 -0
  190. package/dist/orchestration/unanswered-inputs.js.map +1 -0
  191. package/dist/session/process.d.ts +10 -0
  192. package/dist/session/process.d.ts.map +1 -1
  193. package/dist/session/process.js +91 -9
  194. package/dist/session/process.js.map +1 -1
  195. package/dist/session/request-tool-capture.d.ts +31 -0
  196. package/dist/session/request-tool-capture.d.ts.map +1 -0
  197. package/dist/session/request-tool-capture.js +194 -0
  198. package/dist/session/request-tool-capture.js.map +1 -0
  199. package/dist/session/runtime-profile.d.ts +7 -3
  200. package/dist/session/runtime-profile.d.ts.map +1 -1
  201. package/dist/session/runtime-profile.js +3 -1
  202. package/dist/session/runtime-profile.js.map +1 -1
  203. package/dist/session/store.d.ts +7 -0
  204. package/dist/session/store.d.ts.map +1 -1
  205. package/dist/session/store.js +27 -0
  206. package/dist/session/store.js.map +1 -1
  207. package/dist/shell/tailer.d.ts +7 -5
  208. package/dist/shell/tailer.d.ts.map +1 -1
  209. package/dist/shell/tailer.js +6 -17
  210. package/dist/shell/tailer.js.map +1 -1
  211. package/dist/types.d.ts +3 -0
  212. package/dist/types.d.ts.map +1 -1
  213. package/dist/ui/cli-viewer-ui.js +1 -1
  214. package/dist/ui/dashboard-charts.d.ts +3 -0
  215. package/dist/ui/dashboard-charts.d.ts.map +1 -0
  216. package/dist/ui/dashboard-charts.js +130 -0
  217. package/dist/ui/dashboard-charts.js.map +1 -0
  218. package/dist/ui/dashboard-client.d.ts +3 -0
  219. package/dist/ui/dashboard-client.d.ts.map +1 -0
  220. package/dist/ui/dashboard-client.js +184 -0
  221. package/dist/ui/dashboard-client.js.map +1 -0
  222. package/dist/ui/dashboard-controls.d.ts +4 -0
  223. package/dist/ui/dashboard-controls.d.ts.map +1 -0
  224. package/dist/ui/dashboard-controls.js +27 -0
  225. package/dist/ui/dashboard-controls.js.map +1 -0
  226. package/dist/ui/dashboard-presentation.d.ts +12 -0
  227. package/dist/ui/dashboard-presentation.d.ts.map +1 -0
  228. package/dist/ui/dashboard-presentation.js +62 -0
  229. package/dist/ui/dashboard-presentation.js.map +1 -0
  230. package/dist/ui/dashboard-range.d.ts +5 -0
  231. package/dist/ui/dashboard-range.d.ts.map +1 -0
  232. package/dist/ui/dashboard-range.js +36 -0
  233. package/dist/ui/dashboard-range.js.map +1 -0
  234. package/dist/ui/dashboard-theme.d.ts +6 -0
  235. package/dist/ui/dashboard-theme.d.ts.map +1 -0
  236. package/dist/ui/dashboard-theme.js +562 -0
  237. package/dist/ui/dashboard-theme.js.map +1 -0
  238. package/dist/ui/memory-activity.d.ts +4 -0
  239. package/dist/ui/memory-activity.d.ts.map +1 -0
  240. package/dist/ui/memory-activity.js +115 -0
  241. package/dist/ui/memory-activity.js.map +1 -0
  242. package/dist/ui/token-report-client.d.ts +3 -0
  243. package/dist/ui/token-report-client.d.ts.map +1 -0
  244. package/dist/ui/token-report-client.js +34 -0
  245. package/dist/ui/token-report-client.js.map +1 -0
  246. package/dist/ui/token-report.d.ts +90 -0
  247. package/dist/ui/token-report.d.ts.map +1 -0
  248. package/dist/ui/token-report.js +203 -0
  249. package/dist/ui/token-report.js.map +1 -0
  250. package/dist/ui/web-ui.d.ts +1 -1
  251. package/dist/ui/web-ui.d.ts.map +1 -1
  252. package/dist/ui/web-ui.js +129 -409
  253. package/dist/ui/web-ui.js.map +1 -1
  254. package/dist/voice/replay.d.ts +8 -0
  255. package/dist/voice/replay.d.ts.map +1 -0
  256. package/dist/voice/replay.js +48 -0
  257. package/dist/voice/replay.js.map +1 -0
  258. package/dist/voice/session.d.ts +9 -0
  259. package/dist/voice/session.d.ts.map +1 -1
  260. package/dist/voice/session.js +72 -14
  261. package/dist/voice/session.js.map +1 -1
  262. package/mcp/lazy-connector.test.ts +58 -0
  263. package/mcp/lazy-connector.ts +67 -0
  264. package/mcp/lazy-tools.test.ts +152 -0
  265. package/mcp/lazy-tools.ts +41 -0
  266. package/mcp/server.ts +53 -34
  267. package/mcp/tools/discord/commands.ts +2 -0
  268. package/mcp/tools/tasks/module.ts +1 -12
  269. package/mcp/tools/telegram/receiver-server.ts +45 -31
  270. package/mcp/tools/telegram/session-browser.ts +10 -0
  271. package/package.json +2 -1
  272. package/resource/dashboard/OFL.txt +93 -0
  273. package/resource/dashboard/poppins-300.woff2 +0 -0
  274. package/resource/dashboard/poppins-400.woff2 +0 -0
  275. package/resource/dashboard/poppins-500.woff2 +0 -0
  276. package/resource/dashboard/poppins-600.woff2 +0 -0
package/dist/ui/web-ui.js CHANGED
@@ -2,13 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateDashboardHtml = generateDashboardHtml;
4
4
  exports.generateLoginHtml = generateLoginHtml;
5
+ const dashboard_charts_1 = require("./dashboard-charts");
6
+ const dashboard_controls_1 = require("./dashboard-controls");
7
+ const memory_activity_1 = require("./memory-activity");
8
+ const dashboard_range_1 = require("./dashboard-range");
9
+ const dashboard_theme_1 = require("./dashboard-theme");
10
+ const dashboard_client_1 = require("./dashboard-client");
5
11
  /**
6
12
  * Generates a self-contained HTML dashboard page for the gateway status UI.
7
13
  * No external dependencies except xterm.js CDN for PTY viewer.
8
14
  */
9
- function generateDashboardHtml() {
15
+ function generateDashboardHtml(timezone = 'UTC') {
10
16
  return `<!DOCTYPE html>
11
- <html lang="en">
17
+ <html lang="en" data-theme="light">
12
18
  <head>
13
19
  <meta charset="UTF-8">
14
20
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -297,141 +303,80 @@ function generateDashboardHtml() {
297
303
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
298
304
  color: #718096; font-size: 0.9rem; text-align: center; padding: 20px; pointer-events: none;
299
305
  }
300
- /* --- Nightly dreaming report --- */
301
- .dreams-list { display: flex; flex-direction: column; gap: 12px; padding: 4px 2px 20px; }
302
- .dreams-empty { color: #718096; font-size: 0.9rem; text-align: center; padding: 40px 20px; }
303
- .dream-run {
304
- background: #131a2b; border: 1px solid #222c40; border-radius: 8px; padding: 12px 14px;
305
- }
306
- .dream-run-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
307
- .dream-run-head .agent { color: #63b3ed; font-weight: 600; }
308
- .dream-run-head .when { color: #718096; font-size: 0.78rem; font-family: monospace; }
309
- .dream-badge { border-radius: 4px; padding: 1px 7px; font-size: 0.7rem; font-weight: 600; }
310
- .dream-badge.auto { background: #22543d; color: #9ae6b4; border: 1px solid #2f855a; }
311
- .dream-badge.propose { background: #2a4365; color: #90cdf4; border: 1px solid #2b6cb0; }
312
- .dream-badge.outcome { background: #2d3748; color: #cbd5e0; border: 1px solid #4a5568; }
313
- .dream-summary { color: #cbd5e0; font-size: 0.86rem; margin: 4px 0 8px; }
314
- .dream-meta { color: #718096; font-size: 0.75rem; margin-top: 8px; }
315
- .dream-props { display: flex; flex-direction: column; gap: 6px; }
316
- .dream-prop {
317
- background: #0e1420; border: 1px solid #1e2740; border-radius: 6px; padding: 7px 9px; font-size: 0.82rem;
318
- }
319
- .dream-prop .op { font-weight: 600; text-transform: uppercase; font-size: 0.7rem; margin-right: 6px; }
320
- .dream-prop .op.add { color: #68d391; } .dream-prop .op.replace { color: #f6ad55; } .dream-prop .op.remove { color: #fc8181; }
321
- .dream-prop .file { color: #90cdf4; font-family: monospace; font-size: 0.76rem; }
322
- .dream-prop .score { color: #718096; font-size: 0.72rem; float: right; }
323
- .dream-prop .reason { color: #a0aec0; margin-top: 3px; }
324
- .dream-prop .content { color: #cbd5e0; margin-top: 5px; white-space: pre-wrap; word-break: break-word;
325
- background: #070a12; border-radius: 4px; padding: 6px 8px; font-size: 0.78rem; max-height: 140px; overflow: auto; }
326
- .dream-prop .anchor { color: #f6ad55; font-family: monospace; font-size: 0.72rem; margin-top: 3px; }
327
- .dream-prop.accepted { border-color: #2f855a; }
328
- .dream-prop-actions { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
329
- .dream-accept-btn { background: #22543d; color: #9ae6b4; border: 1px solid #2f855a; border-radius: 4px;
330
- padding: 2px 10px; font-size: 0.74rem; font-weight: 600; cursor: pointer; }
331
- .dream-accept-btn:hover { background: #276749; }
332
- .dream-accept-btn:disabled { opacity: 0.55; cursor: default; }
333
- .prop-status { font-size: 0.72rem; font-weight: 600; }
334
- .prop-status.applied { color: #9ae6b4; }
335
- .prop-status.applied-auto { color: #68d391; }
336
- .prop-status.pending { color: #90cdf4; }
337
- .prop-status.failed { color: #fc8181; }
338
- .dream-accept-all { background: #22543d; color: #9ae6b4; border: 1px solid #2f855a; border-radius: 4px;
339
- padding: 1px 9px; font-size: 0.7rem; font-weight: 600; cursor: pointer; }
340
- .dream-accept-all:hover { background: #276749; }
341
- .dream-accept-all:disabled { opacity: 0.55; cursor: default; }
342
306
  /* Note detail — full-width card below the graph. Shows the whole file. */
343
307
  .kb-note {
344
- margin-top: 12px; background: #131a2b; border: 1px solid #2d3748; border-radius: 8px;
345
- padding: 16px 18px; font-size: 0.85rem; color: #cbd5e0;
308
+ margin-top: 12px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
309
+ padding: 16px 18px; font-size: 0.85rem; color: var(--text);
346
310
  }
347
311
  .kb-note-head { display: flex; align-items: flex-start; gap: 10px; }
348
- .kb-note-head h3 { margin: 0; font-size: 1.02rem; color: #e2e8f0; flex: 1; word-break: break-word; }
312
+ .kb-note-head h3 { margin: 0; font-size: 1.02rem; color: var(--text); flex: 1; word-break: break-word; }
349
313
  .kb-note-close {
350
- cursor: pointer; color: #718096; background: #1a202c; border: 1px solid #2d3748;
314
+ cursor: pointer; color: var(--muted); background: var(--panel); border: 1px solid var(--line);
351
315
  border-radius: 4px; padding: 1px 8px; font-size: 0.9rem; line-height: 1.4;
352
316
  }
353
- .kb-note-close:hover { color: #e2e8f0; background: #2d3748; }
317
+ .kb-note-close:hover { color: var(--text); background: var(--line); }
354
318
  /* Metadata chips row. */
355
319
  .kb-note-meta { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
356
320
  .kb-note-chip {
357
- display: inline-flex; align-items: center; gap: 5px; background: #0e1420;
358
- border: 1px solid #1e2740; border-radius: 999px; padding: 2px 10px; font-size: 0.72rem; color: #a0aec0;
321
+ display: inline-flex; align-items: center; gap: 5px; background: var(--raised);
322
+ border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px; font-size: 0.72rem; color: var(--muted);
359
323
  }
360
- .kb-note-chip b { color: #cbd5e0; font-weight: 600; }
324
+ .kb-note-chip b { color: var(--text); font-weight: 600; }
361
325
  .kb-note-chip i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
362
326
  /* File path line under the chips — full width, monospace, wraps on long paths. */
363
327
  .kb-note-path {
364
328
  margin: 6px 0 2px; font-size: 0.72rem; font-family: ui-monospace, monospace;
365
- color: #718096; word-break: break-all;
329
+ color: var(--muted); word-break: break-all;
366
330
  }
367
- .kb-note-path b { color: #a0aec0; font-weight: 600; }
331
+ .kb-note-path b { color: var(--muted); font-weight: 600; }
368
332
  /* Rendered Markdown body. */
369
333
  .kb-md {
370
- margin-top: 12px; padding-top: 12px; border-top: 1px solid #222c40;
371
- line-height: 1.62; color: #cbd5e0; word-break: break-word;
334
+ margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
335
+ line-height: 1.62; color: var(--text); word-break: break-word;
372
336
  }
373
337
  .kb-md > :first-child { margin-top: 0; }
374
- .kb-md h1, .kb-md h2, .kb-md h3, .kb-md h4 { color: #e2e8f0; line-height: 1.3; margin: 18px 0 8px; }
375
- .kb-md h1 { font-size: 1.25rem; border-bottom: 1px solid #222c40; padding-bottom: 5px; }
376
- .kb-md h2 { font-size: 1.1rem; border-bottom: 1px solid #1e2740; padding-bottom: 4px; }
377
- .kb-md h3 { font-size: 1rem; } .kb-md h4 { font-size: 0.9rem; color: #a0aec0; }
338
+ .kb-md h1, .kb-md h2, .kb-md h3, .kb-md h4 { color: var(--text); line-height: 1.3; margin: 18px 0 8px; }
339
+ .kb-md h1 { font-size: 1.25rem; border-bottom: 1px solid var(--line); padding-bottom: 5px; }
340
+ .kb-md h2 { font-size: 1.1rem; border-bottom: 1px solid var(--line); padding-bottom: 4px; }
341
+ .kb-md h3 { font-size: 1rem; } .kb-md h4 { font-size: 0.9rem; color: var(--muted); }
378
342
  .kb-md p { margin: 8px 0; }
379
343
  .kb-md ul, .kb-md ol { margin: 8px 0; padding-left: 22px; }
380
344
  .kb-md li { margin: 3px 0; }
381
- .kb-md a { color: #63b3ed; text-decoration: none; } .kb-md a:hover { text-decoration: underline; }
345
+ .kb-md a { color: var(--accent); text-decoration: none; } .kb-md a:hover { text-decoration: underline; }
382
346
  .kb-md code {
383
- background: #0d1117; border: 1px solid #1e2740; border-radius: 4px;
384
- padding: 1px 5px; font-size: 0.82em; font-family: ui-monospace, monospace; color: #e2e8f0;
347
+ background: var(--raised); border: 1px solid var(--line); border-radius: 4px;
348
+ padding: 1px 5px; font-size: 0.82em; font-family: ui-monospace, monospace; color: var(--text);
385
349
  }
386
350
  .kb-md pre {
387
- background: #0a0e17; border: 1px solid #1e2740; border-radius: 6px; padding: 10px 12px;
351
+ background: var(--raised); border: 1px solid var(--line); border-radius: 6px; padding: 10px 12px;
388
352
  overflow-x: auto; margin: 10px 0;
389
353
  }
390
354
  .kb-md pre code { background: none; border: none; padding: 0; font-size: 0.8rem; line-height: 1.5; }
391
355
  .kb-md blockquote {
392
- margin: 10px 0; padding: 2px 14px; border-left: 3px solid #2d3748; color: #a0aec0;
356
+ margin: 10px 0; padding: 2px 14px; border-left: 3px solid var(--line); color: var(--muted);
393
357
  }
394
- .kb-md hr { border: none; border-top: 1px solid #222c40; margin: 14px 0; }
395
- .kb-md strong { color: #e2e8f0; } .kb-md table { border-collapse: collapse; margin: 0; width: 100%; font-size: 0.82rem; }
396
- .kb-md th, .kb-md td { border: 1px solid #222c40; padding: 5px 10px; text-align: left; }
397
- .kb-md th { background: #0e1420; color: #e2e8f0; font-weight: 600; }
398
- .kb-md tbody tr:nth-child(even) { background: #0e1420; }
358
+ .kb-md hr { border: none; border-top: 1px solid var(--line); margin: 14px 0; }
359
+ .kb-md strong { color: var(--text); } .kb-md table { border-collapse: collapse; margin: 0; width: 100%; font-size: 0.82rem; }
360
+ .kb-md th, .kb-md td { border: 1px solid var(--line); padding: 5px 10px; text-align: left; }
361
+ .kb-md th { background: var(--raised); color: var(--text); font-weight: 600; }
362
+ .kb-md tbody tr:nth-child(even) { background: var(--raised); }
399
363
  /* Wide tables scroll horizontally instead of breaking the panel. */
400
364
  .kb-table-wrap { overflow-x: auto; margin: 10px 0; border-radius: 6px; }
401
- .kb-md del { color: #718096; }
365
+ .kb-md del { color: var(--muted); }
402
366
  /* Task-list items: bullet replaced by a real (disabled) checkbox. */
403
367
  .kb-md li.kb-task { list-style: none; margin-left: -18px; }
404
- .kb-md li.kb-task input { margin-right: 7px; vertical-align: middle; accent-color: #63b3ed; }
405
- .kb-note-loading { color: #718096; font-size: 0.82rem; margin-top: 12px; }
368
+ .kb-md li.kb-task input { margin-right: 7px; vertical-align: middle; accent-color: var(--accent); }
369
+ .kb-note-loading { color: var(--muted); font-size: 0.82rem; margin-top: 12px; }
370
+ ${dashboard_theme_1.dashboardTheme}
406
371
  </style>
407
- </head>
408
- <body>
409
- <h1><span class="rainbow">Claude Gateway</span> <span id="gateway-version" style="font-size:0.75rem;color:#718096;"></span> <span id="top-right"><button id="logout-btn">Logout</button><span id="refresh-indicator">refreshing...</span></span></h1>
410
- <div class="meta">
411
- Uptime: <span id="uptime">&mdash;</span> &nbsp;|&nbsp;
412
- Started: <span id="started-at">&mdash;</span> &nbsp;|&nbsp;
413
- Last updated: <span id="last-updated">&mdash;</span>
414
- </div>
415
-
416
- <!-- Tab switcher: Sessions (live PTY + table) | Knowledge base (shared-KB graph) -->
417
- <div class="tabs">
418
- <button class="tab active" id="tab-sessions" data-view="view-sessions">Sessions</button>
419
- <button class="tab" id="tab-kb" data-view="view-kb">Knowledge base</button>
420
- <button class="tab" id="tab-dreams" data-view="view-dreams">Nightly dreaming</button>
421
- </div>
422
-
423
- <div id="view-sessions" class="view">
424
- <!-- Row: Processes 70% | Agent badges 30% (collapses on narrow screens) -->
425
- <div class="top-grid">
426
- <div>
427
- <h2>Processes</h2>
428
- <div class="proc-tree" id="proc-tree">Loading...</div>
429
- </div>
430
- <div>
431
- <h2>Agents</h2>
432
- <div class="agents-bar" id="agents-bar"></div>
433
- </div>
434
- </div>
372
+ ${dashboard_theme_1.dashboardFontLink}<script>try{document.documentElement.dataset.sidebarCollapsed=localStorage.getItem("gateway-sidebar-collapsed")==="true"?"true":"false";}catch{}</script></head>
373
+ <body><div class="layout"><aside class="sidebar" id="dashboard-sidebar"><div class="brand row" id="sidebar-brand" role="button" tabindex="0" aria-label="Collapse sidebar" aria-expanded="true" aria-controls="dashboard-sidebar"><div class="brandmark" aria-hidden="true">✳</div><div>Claude Gateway<small>OPERATIONS CONSOLE</small></div></div><div class="navlabel">WORKSPACE</div><nav class="tabs" aria-label="Main navigation"><button class="tab active" id="tab-overview" data-view="view-overview"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 3h7v7H3zM14 3h7v4h-7zM14 11h7v10h-7zM3 14h7v7H3z"/></svg>Overview</button><button class="tab " id="tab-sessions" data-view="view-sessions"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M21 11a8 8 0 0 1-8 8H6l-4 3 1-7a8 8 0 1 1 18-4Z"/></svg>Conversations</button><button class="tab " id="tab-tasks" data-view="view-tasks"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 5h12M9 12h12M9 19h12M3 5l1 1 2-3M3 12l1 1 2-3M3 19l1 1 2-3"/></svg>Tasks</button><button class="tab " id="tab-usage" data-view="view-usage"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 20V10m8 10V4m8 16v-7"/></svg>Usage & tokens</button><button class="tab " id="tab-kb" data-view="view-kb"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 3h7v18H4zM14 3h7v18h-7zM7 7h1m9 0h1"/></svg>Knowledge base</button><button class="tab " id="tab-dreams" data-view="view-dreams"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 15A9 9 0 0 1 9 4 9 9 0 1 0 20 15Z"/></svg>Nightly dreaming</button><button class="tab " id="tab-system" data-view="view-system"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><path d="M3 4h18v6H3zM3 14h18v6H3zM6 7h1M6 17h1"/></svg>System</button></nav><div class="sidebarfoot"><div class="instance">Claude Gateway<br><span id="gateway-version"></span></div></div></aside><div class="main"><header class="topbar"><div class="row"><button id="dash-menu" class="mobilemenu" aria-label="Collapse sidebar" aria-expanded="true" aria-controls="dashboard-sidebar" title="Collapse sidebar"><svg class="icon" viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16"/></svg></button><h1 id="dash-current-view">Overview</h1></div><div id="top-right"><span id="refresh-indicator">Connecting…</span>${(0, dashboard_controls_1.themeButtonHtml)('dash-theme')}<button id="logout-btn">Logout</button></div></header><main class="shell-content"><div class="meta">Uptime: <span id="uptime">—</span> · Started: <span id="started-at">—</span> · Updated: <span id="last-updated">—</span></div>
374
+ <div id="view-overview" class="view"><div class="heading" style="margin-top:24px"><div class="eyebrow">ONE WORKSPACE. EVERY CONVERSATION.</div><h1>Good work, <span class="gradient-title">in motion.</span></h1><p>Your agents, ongoing work, and what needs your attention.</p></div><div class="dash-grid" id="overview-stats"></div>${(0, dashboard_charts_1.dashboardChartsHtml)(timezone)}<section class="dash-panel" id="overview-attention-panel" hidden><h2>Needs your attention</h2><p class="live-note">Inspect the task here, then reply to your agent in the original chat.</p><div class="dash-mini-list" id="overview-attention"></div></section><section class="dash-panel"><h2>Agents</h2><div class="agents-bar" id="agents-bar"></div></section><section class="dash-panel"><h2>Active & recent work</h2><div id="overview-tasks"></div></section></div>
375
+ <div class="dash-filter turn-filters"><input id="dash-search" type="search" aria-label="Filter displayed sessions" placeholder="Filter this page by session, chat or model…">${(0, dashboard_range_1.rangeButtons)("dash-scope", "24h", timezone)}<select id="dash-agent-filter" aria-label="Filter agent"><option value="">All agents</option></select></div><div class="dash-pager"><span data-dash-page></span><div class="row"><button data-dash-prev disabled>← Previous</button><button data-dash-next disabled>Next →</button></div></div>
376
+ <div id="view-tasks" class="view" style="display:none"><h2>Tasks & workers</h2><p id="tasks-scope" class="live-note"></p><div id="task-results"></div></div><div id="view-usage" class="view" style="display:none"><h2>Usage & tokens</h2><p class="live-note">Session totals include every recorded attempt, including retries. Missing measurements are shown as —. Open a report for input, cache write, cache read, output and per-turn details.</p><div id="usage-results"></div></div>
377
+ <div id="view-system" class="view" style="display:none">
378
+ <h2>Processes</h2>
379
+ <div class="proc-tree" id="proc-tree">Loading...</div>
435
380
 
436
381
  <!-- PTY viewer — full width so the native 200-col terminal has room.
437
382
  Placed above the Sessions table so the live mirror is the first thing
@@ -450,32 +395,7 @@ function generateDashboardHtml() {
450
395
  <div id="pty-terminal" tabindex="0"></div>
451
396
  </div>
452
397
 
453
- <!-- Sessions full width (session-centric, flat list) -->
454
- <h2>Sessions</h2>
455
- <div class="table-wrap">
456
- <table id="sessions-table">
457
- <thead>
458
- <tr>
459
- <th>Agent</th>
460
- <th>Session ID</th>
461
- <th>Chat ID</th>
462
- <th>Source</th>
463
- <th>Mode</th>
464
- <th>Model</th>
465
- <th>Tokens</th>
466
- <th>Status</th>
467
- <th>Uptime</th>
468
- <th>Spawned</th>
469
- <th>Shell</th>
470
- </tr>
471
- </thead>
472
- <tbody id="sessions-tbody">
473
- <tr><td colspan="11" class="ts">Loading...</td></tr>
474
- </tbody>
475
- </table>
476
- </div>
477
- </div><!-- /view-sessions -->
478
-
398
+ </div><div id="view-sessions" class="view" style="display:none"><h2>Conversations</h2><p class="live-note">Inspect a session for its Agent turns and Worker attempts. Inspect the tools used in each session and worker attempt.</p><div id="session-results"></div></div>
479
399
  <!-- Knowledge base — a 3D force-directed graph over the shared vault / an agent's memory.
480
400
  Zero external deps: the spherical layout + canvas render + rotation are hand-rolled. -->
481
401
  <div id="view-kb" class="view" style="display:none;">
@@ -519,28 +439,21 @@ function generateDashboardHtml() {
519
439
 
520
440
  <!-- Nightly dreaming — the memory-consolidation audit trail (.dreaming/) per agent. -->
521
441
  <div id="view-dreams" class="view" style="display:none;">
522
- <div class="kb-toolbar">
523
- <button id="dreams-refresh" title="Reload the dreaming audit trail">&#x21bb; Refresh</button>
524
- <label id="dreams-agent-wrap">Agent
525
- <select id="dreams-agent"><option value="">All agents</option></select>
526
- </label>
527
- <span id="dreams-stats"></span>
528
- </div>
529
- <div id="dreams-list" class="dreams-list"></div>
530
- <div id="dreams-empty" class="dreams-empty" style="display:none;"></div>
442
+ ${memory_activity_1.memoryActivityHtml}
531
443
  </div><!-- /view-dreams -->
532
444
 
533
- <div id="error-msg" class="error" style="display:none;"></div>
445
+ <div id="error-msg" class="error" style="display:none;" role="status"></div><footer class="footer">Claude Gateway · Admin dashboard</footer></main></div></div><div id="dash-drawer-back" class="dash-drawer-back"><section id="dash-drawer" class="dash-drawer" role="dialog" aria-modal="true" aria-label="Recorded session and task details" tabindex="-1"></section></div>
534
446
 
535
447
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/lib/xterm.min.js"
536
448
  integrity="sha384-pELe6ZHtFxFcuYBq3gMkqvmnNIqUWnAYjBG5gThqQQCjWp8PJ/65MLK4lMIfEK1e" crossorigin="anonymous"></script>
537
449
  <script>
450
+ const dashboardTimezone = ${JSON.stringify(timezone).replace(/</g, '\\u003c')};
538
451
  // Auth is carried by the HttpOnly 'dash_session' cookie the browser sends
539
452
  // automatically on every same-origin request — no token is embedded in the
540
453
  // page (nothing for view-source/XSS to steal). If any read returns 401 the
541
454
  // session has expired or is missing, so bounce to /dashboard (login page).
542
455
  function onUnauthorized() {
543
- window.location.href = apiUrl('/dashboard');
456
+ window.location.reload();
544
457
  }
545
458
 
546
459
  // Must match the server PTY size (src/shell/screen.ts ScreenModel defaults).
@@ -561,7 +474,7 @@ function generateDashboardHtml() {
561
474
  if (!ts) return '<span class="ts">&mdash;</span>';
562
475
  try {
563
476
  const d = new Date(ts);
564
- return '<span class="ts">' + d.toLocaleString('en-US', {
477
+ return '<span class="ts">' + d.toLocaleString('en-US', {timeZone:dashboardTimezone,
565
478
  year: 'numeric', month: 'numeric', day: 'numeric',
566
479
  hour: 'numeric', minute: '2-digit', second: '2-digit', hour12: true
567
480
  }) + '</span>';
@@ -861,7 +774,7 @@ function generateDashboardHtml() {
861
774
 
862
775
  const expandedSessionRows = new Set();
863
776
  // Event delegation for Live buttons (avoids inline onclick + HTML injection)
864
- document.getElementById('sessions-tbody').addEventListener('click', function(e) {
777
+ document.getElementById('session-results').addEventListener('click', function(e) {
865
778
  const toggle = e.target.closest('.btn-workers');
866
779
  if (toggle) {
867
780
  const key=toggle.getAttribute('data-expand-key'), open=!expandedSessionRows.has(key);
@@ -871,8 +784,8 @@ function generateDashboardHtml() {
871
784
  const detail=document.getElementById(toggle.getAttribute('aria-controls'));if(detail)detail.hidden=!open;
872
785
  return;
873
786
  }
874
- const btn = e.target.closest('.btn-stream');
875
- if (btn) void openPtyViewer(btn.getAttribute('data-agent-id'), btn.getAttribute('data-session-id'));
787
+ const btn = e.target.closest('button.btn-stream[data-agent-id][data-session-id]');
788
+ if (btn) { document.getElementById('tab-system').click(); void openPtyViewer(btn.getAttribute('data-agent-id'), btn.getAttribute('data-session-id')); }
876
789
  });
877
790
 
878
791
  function escHtml(s) {
@@ -916,26 +829,29 @@ function generateDashboardHtml() {
916
829
 
917
830
  // Format a context-window token count compactly: 1234 -> "1.2k", 45000 -> "45k".
918
831
  // Full value is kept in the tooltip. 0/unknown renders as a dash.
832
+ function toolInventory(loaded, used, contextTools) {
833
+ function list(label, names) {
834
+ return '<div title="'+(Array.isArray(names)?escHtml(names.join(', ')||'None'):'Not recorded')+'">'+label+': '+(Array.isArray(names)?compactNumber(names.length):'—')+'</div>';
835
+ }
836
+ return list('Loaded', contextTools)+list('Used', used);
837
+ }
838
+
839
+ function fmtRecordedTokens(n) {
840
+ return n == null ? '—' : n === 0 ? '0' : fmtTokens(n);
841
+ }
842
+
919
843
  function fmtTokens(n) {
920
844
  const v = Number(n) || 0;
921
845
  if (v <= 0) return '<span class="ts">&mdash;</span>';
922
- const label = v >= 1000 ? (v / 1000).toFixed(v >= 10000 ? 0 : 1) + 'k' : String(v);
846
+ const label = compactNumber(v);
923
847
  return '<span title="' + v.toLocaleString() + ' tokens">' + label + '</span>';
924
848
  }
925
849
 
926
- // ── Status Refresh ────────────────────────────────────────────────────────
927
- async function refresh() {
928
- document.getElementById('refresh-indicator').textContent = 'refreshing...';
929
- try {
930
- const res = await fetch(apiUrl('/status'));
931
- if (res.status === 401) { onUnauthorized(); return; }
932
- if (!res.ok) throw new Error('HTTP ' + res.status);
933
- const data = await res.json();
934
-
850
+ function applyDashboardSnapshot(data) {
935
851
  document.getElementById('uptime').textContent = fmtUptime(data.uptime || 0);
936
852
  document.getElementById('started-at').textContent = data.startedAt
937
- ? new Date(data.startedAt).toLocaleString() : '\\u2014';
938
- document.getElementById('last-updated').textContent = new Date().toLocaleTimeString();
853
+ ? new Date(data.startedAt).toLocaleString('en-GB',{timeZone:dashboardTimezone}) : '\\u2014';
854
+ document.getElementById('last-updated').textContent = new Date().toLocaleTimeString('en-GB',{timeZone:dashboardTimezone});
939
855
  if (data.version) document.getElementById('gateway-version').textContent = 'v' + data.version;
940
856
  document.getElementById('error-msg').style.display = 'none';
941
857
 
@@ -949,70 +865,33 @@ function generateDashboardHtml() {
949
865
  const badges = agents.map(function(a) {
950
866
  const ok = a.hasChannel ? a.isRunning : true;
951
867
  const dot = ok ? '<span class="dot-green">&#x25CF;</span>' : '<span class="dot-red">&#x25CF;</span>';
952
- return '<span class="agent-badge">' + dot + ' <span class="agent-name">' + escHtml(a.id) + '</span></span>';
868
+ return agentBadge(a.id);
953
869
  });
954
870
  document.getElementById('agents-bar').innerHTML = badges.join('') || '<span class="ts">No agents</span>';
955
871
 
956
- // Sessions table — flat, session-centric. One row per real session across
957
- // all agents; agents with no session do not produce a row.
958
- const rows = [];
959
- agents.forEach(function(a) {
960
- (a.sessions || []).forEach(function(s) {
961
- const statusBadge = s.orchestration ? '<span class="badge '+(['thinking','working'].includes(s.status) ? 'badge-green' : 'badge-gray')+'">'+escHtml(s.status)+'</span>' : s.isRunning
962
- ? '<span class="badge badge-green">running</span>'
963
- : '<span class="badge badge-gray">stopped</span>';
964
- const uptime = s.isRunning ? fmtUptime(s.uptimeSec || 0) : '<span class="ts">&mdash;</span>';
965
- const sessId = s.sessionId
966
- ? '<span class="session-id">' + escHtml(s.sessionId) + '</span>'
967
- : '<span class="ts">&mdash;</span>';
968
- const chatCell = s.chatId
969
- ? '<span class="session-id">' + escHtml(String(s.chatId)) + '</span>'
970
- : '<span class="ts">&mdash;</span>';
971
- const liveBtn = (s.hasPtyStream && s.isRunning && s.mode === 'pty-shell')
972
- ? '<button class="btn-stream" data-agent-id="' + escHtml(a.id) + '" data-session-id="' + escHtml(s.sessionId || '') + '">💻 View</button>'
973
- : '<span class="ts">&mdash;</span>';
974
- const key=a.id+':'+s.sessionId, detailId='workers-'+encodeURIComponent(key), open=expandedSessionRows.has(key);
975
- const childTasks=s.tasks||[], count=childTasks.length;
976
- const label='Workers / tasks ('+count+')';
977
- const expand=s.orchestration ? '<br><button class="btn-stream btn-workers" data-expand-key="'+escHtml(key)+'" data-label="'+escHtml(label)+'" aria-expanded="'+open+'" aria-controls="'+escHtml(detailId)+'">'+(open?'▾ ':'▸ ')+escHtml(label)+'</button>' : '';
978
- rows.push(
979
- '<tr class="session-row" data-agent="'+escHtml(a.id)+'" data-session="'+escHtml(s.sessionId)+'">' +
980
- '<td><span style="color:#90cdf4;font-weight:600;">' + escHtml(a.id) + '</span></td>' +
981
- '<td>' + sessId + expand + '</td>' +
982
- '<td>' + chatCell + '</td>' +
983
- '<td>' + sourceBadge(s.source) + '</td>' +
984
- '<td><div class="session-modes">' + (s.orchestration ? '<span class="badge badge-purple">orchestration</span>' : '') + modeBadge(s.mode) + '</div></td>' +
985
- '<td>' + fmtModel(s.model) + '</td>' +
986
- '<td>' + fmtTokens(s.tokens) + '</td>' +
987
- '<td>' + statusBadge + '</td>' +
988
- '<td>' + uptime + '</td>' +
989
- '<td>' + fmtTs(s.spawnedAt ? new Date(s.spawnedAt).toISOString() : null) + '</td>' +
990
- '<td>' + liveBtn + '</td>' +
991
- '</tr>'
992
- );
993
- if (s.orchestration) {
994
- const pool=a.orchestration.workerPool, slots=pool ? pool.workers.filter(w => (s.workerIds||[]).includes(w.workerId)) : [];
995
- let content='<div style="padding:12px 20px;border-left:3px solid #805ad5"><div class="ts">'+escHtml(a.orchestration.workspaceMode)+(a.container ? ' · '+escHtml(a.container) : '')+(pool ? ' · Agent pool '+pool.workers.length+'/'+pool.maxWorkers+' · idle TTL '+Math.round(pool.idleTtlMs/60000)+' min' : '')+'</div>';
996
- slots.forEach(function(w){content+='<div class="worker-slot">'+escHtml(w.state)+' · Worker '+escHtml(w.workerId)+' · session '+escHtml(w.sessionId)+(w.expiresAt ? ' · expires '+escHtml(new Date(w.expiresAt).toLocaleTimeString()) : '')+'</div>';});
997
- if (count) {
998
- content+='<table class="worker-tasks"><thead><tr><th>Worker / Session</th><th>Task</th><th>Status</th><th>Tool</th><th>Process</th></tr></thead><tbody>';
999
- childTasks.forEach(function(t){content+='<tr data-task="'+escHtml(t.taskId)+'"><td>'+escHtml(t.workerId||'Unassigned')+'<br><span class="ts">'+escHtml(t.workerSessionId||'')+(t.resumed?' · resumed':'')+'</span></td><td>'+escHtml(t.title)+'<br><span class="ts">'+escHtml(t.taskId)+(t.continueTaskId?' · follows '+escHtml(t.continueTaskId):'')+'</span></td><td>'+escHtml(t.state)+'</td><td>'+escHtml(t.lastTool ? t.lastTool.name+' · '+t.lastTool.type+(t.lastTool.is_error?' (error)':'') : '—')+'</td><td>'+escHtml(t.hostProcessId ? String(t.hostProcessId)+(t.container?' (docker exec)':'') : '—')+'</td></tr>';});
1000
- content+='</tbody></table>';
1001
- } else content+='<div class="ts">No worker tasks in this session</div>';
1002
- rows.push('<tr class="session-workers" id="'+escHtml(detailId)+'"'+(open?'':' hidden')+'><td colspan="11">'+content+'</div></td></tr>');
1003
- }
1004
- });
1005
- });
872
+ renderDashboard(data);
873
+
874
+ document.getElementById('refresh-indicator').textContent = 'Live · updated';
875
+ }
1006
876
 
1007
- document.getElementById('sessions-tbody').innerHTML =
1008
- rows.length ? rows.join('') : '<tr><td colspan="11" class="ts">No active sessions</td></tr>';
877
+ // ── Status Refresh ────────────────────────────────────────────────────────
878
+ async function refresh() {
879
+ if (dashboardBusy || document.hidden) return;
880
+ dashboardBusy = true;
881
+ const requestedOffset = dashboardOffset, requestedScope = dashboardScope;
882
+ document.getElementById('refresh-indicator').textContent = 'Updating…';
883
+ try {
884
+ const res = await fetch(apiUrl('/status')+'?offset='+requestedOffset+'&scope='+requestedScope);
885
+ if (res.status === 401) { onUnauthorized(); return; }
886
+ if (!res.ok) throw new Error('HTTP ' + res.status);
887
+ const data = await res.json();
1009
888
 
1010
- document.getElementById('refresh-indicator').textContent = 'auto-refresh 3s';
889
+ if (requestedOffset === dashboardOffset && requestedScope === dashboardScope) applyDashboardSnapshot(data);
1011
890
  } catch(e) {
1012
891
  document.getElementById('error-msg').textContent = 'Error fetching status: ' + e.message;
1013
892
  document.getElementById('error-msg').style.display = 'block';
1014
- document.getElementById('refresh-indicator').textContent = 'error';
1015
- }
893
+ document.getElementById('refresh-indicator').textContent = 'Reconnecting…';
894
+ } finally {dashboardBusy=false;}
1016
895
  }
1017
896
 
1018
897
  // ── Process Tree ─────────────────────────────────────────────────────────
@@ -1166,14 +1045,16 @@ function generateDashboardHtml() {
1166
1045
  try {
1167
1046
  await fetch(apiUrl('/dashboard/logout'), { method: 'POST' });
1168
1047
  } catch (e) { /* best-effort; navigate regardless */ }
1169
- window.location.href = apiUrl('/dashboard');
1048
+ window.location.reload();
1170
1049
  });
1171
1050
 
1051
+ ${dashboard_controls_1.dashboardControlsClient}
1052
+ ${dashboard_client_1.dashboardClient}
1172
1053
  refresh();
1173
- refreshProcesses();
1174
- setInterval(refresh, 3000);
1175
- // Process tree (with CPU/mem) is heavier (spawns ps) — refresh a bit slower.
1176
- setInterval(refreshProcesses, 6000);
1054
+ connectDashboardStream();
1055
+ setInterval(function(){if(!dashboardStream || dashboardStream.readyState!==1)refresh();},15000);
1056
+ setInterval(function(){if(!document.hidden && document.getElementById('view-system').style.display!=='none')refreshProcesses();},10000);
1057
+ document.addEventListener('visibilitychange',function(){if(document.hidden){dashboardStream?.close();dashboardStream=null;}else{refresh();connectDashboardStream();}});
1177
1058
  </script>
1178
1059
 
1179
1060
  <!-- Knowledge base graph: tab switching + hand-rolled force-directed renderer.
@@ -1400,7 +1281,11 @@ function generateDashboardHtml() {
1400
1281
  // Labels: hubs always; hovered / selected / search-hit reveal on demand.
1401
1282
  var showLabel = (o.deg >= LABEL_MIN_DEG) || isHov || isSel || (searching && o.hit);
1402
1283
  if (showLabel && !dim){
1403
- ctx.fillStyle = (isHov || isSel) ? '#e6edf3' : 'rgba(213,222,236,' + (0.25 + 0.6 * near) + ')';
1284
+ // White, not var(--text): .kb-stage paints a fixed deep-space backdrop in BOTH
1285
+ // themes, but --text flips to near-black under the light theme (the page default),
1286
+ // which left labels unreadable on the dark canvas. Matches the white the hover /
1287
+ // selection rings above already use.
1288
+ ctx.fillStyle = '#fff';
1404
1289
  ctx.font = (isHov || isSel ? '12px ' : '11px ') + 'ui-sans-serif, system-ui, sans-serif';
1405
1290
  ctx.fillText(nd.title, p.sx + p.r + 4, p.sy + 3);
1406
1291
  }
@@ -1530,7 +1415,7 @@ function generateDashboardHtml() {
1530
1415
  o.hit = hay.indexOf(q) >= 0;
1531
1416
  if (o.hit) hits.push(o);
1532
1417
  });
1533
- if (elSearchCount) elSearchCount.textContent = hits.length + ' / ' + G.nodes.length;
1418
+ if (elSearchCount) elSearchCount.textContent = compactNumber(hits.length) + ' / ' + compactNumber(G.nodes.length);
1534
1419
  invalidate();
1535
1420
  return hits;
1536
1421
  }
@@ -1812,7 +1697,7 @@ function generateDashboardHtml() {
1812
1697
  sourceSel.textContent = '';
1813
1698
  (data.sources || [{ id: 'shared', label: 'Shared Knowledge Base', count: 0 }]).forEach(function(s){
1814
1699
  var o = document.createElement('option');
1815
- o.value = s.id; o.textContent = s.label + (typeof s.count === 'number' ? ' (' + s.count + ')' : '');
1700
+ o.value = s.id; o.textContent = s.label + (typeof s.count === 'number' ? ' (' + compactNumber(s.count) + ')' : '');
1816
1701
  sourceSel.appendChild(o);
1817
1702
  });
1818
1703
  // Restore prior selection if it still exists, else fall back to Shared KB.
@@ -1855,7 +1740,7 @@ function generateDashboardHtml() {
1855
1740
  }
1856
1741
  buildModel(data);
1857
1742
  renderLegend();
1858
- elStats.textContent = data.nodes.length + ' notes, ' + data.edges.length + ' links';
1743
+ elStats.textContent = compactNumber(data.nodes.length) + ' notes, ' + compactNumber(data.edges.length) + ' links';
1859
1744
  }).catch(function(err){
1860
1745
  elStats.textContent = ''; elEmpty.textContent = 'Failed to load graph: ' + err.message; elEmpty.style.display = '';
1861
1746
  });
@@ -1876,184 +1761,7 @@ function generateDashboardHtml() {
1876
1761
  })();
1877
1762
  </script>
1878
1763
 
1879
- <script id="kb-dreams">
1880
- // Nightly dreaming report renderer. Fetches /knowledge/dreams (parsed audit
1881
- // trail across all agents), renders a newest-first timeline with per-run
1882
- // proposals, and an agent filter. All text goes through textContent (no HTML
1883
- // injection). Uses the global apiUrl/onUnauthorized helpers.
1884
- (function(){
1885
- var listEl = document.getElementById('dreams-list');
1886
- var emptyEl = document.getElementById('dreams-empty');
1887
- var statsEl = document.getElementById('dreams-stats');
1888
- var agentSel = document.getElementById('dreams-agent');
1889
- var refreshBtn = document.getElementById('dreams-refresh');
1890
- var allRuns = [];
1891
- var loaded = false;
1892
- // Notice to show once, after the next render (a reload overwrites statsEl,
1893
- // so an accept result set directly on statsEl would be lost immediately).
1894
- var pendingNotice = '';
1895
-
1896
- function el(tag, cls, text){
1897
- var e = document.createElement(tag);
1898
- if (cls) e.className = cls;
1899
- if (text != null) e.textContent = text;
1900
- return e;
1901
- }
1902
-
1903
- // Apply one or more proposals of a run to the agent's memory via the K4
1904
- // applier. indexes === null ⇒ accept every pending proposal in the run.
1905
- function acceptDreams(agent, ts, indexes, btn){
1906
- var origText = btn ? btn.textContent : '';
1907
- if (btn){ btn.disabled = true; btn.textContent = 'Applying…'; }
1908
- var payload = { agentId: agent, ts: ts };
1909
- if (indexes) payload.indexes = indexes;
1910
- fetch(apiUrl('/knowledge/dreams/apply'), {
1911
- method: 'POST',
1912
- headers: { 'Content-Type': 'application/json' },
1913
- body: JSON.stringify(payload)
1914
- }).then(function(res){
1915
- if (res.status === 401){ onUnauthorized(); return null; }
1916
- return res.json().then(function(data){ return { ok: res.ok, data: data || {} }; });
1917
- }).then(function(r){
1918
- if (!r) return;
1919
- if (!r.ok){
1920
- if (btn){ btn.disabled = false; btn.textContent = origText || 'Accept'; }
1921
- statsEl.textContent = 'Apply failed: ' + (r.data.error || 'error');
1922
- return;
1923
- }
1924
- // Reload so applied proposals show ✓ and any anchor-stale skips stay
1925
- // pending; the memory files themselves were already updated on disk.
1926
- // The message is stashed in pendingNotice because loadDreams → render
1927
- // rewrites statsEl.
1928
- pendingNotice = 'Applied ' + (r.data.applied || 0)
1929
- + (r.data.skipped ? ', skipped ' + r.data.skipped + ' (anchor changed / net-negative)' : '')
1930
- + (r.data.alreadyAccepted ? ', ' + r.data.alreadyAccepted + ' already applied' : '');
1931
- loadDreams(true);
1932
- }).catch(function(e){
1933
- if (btn){ btn.disabled = false; btn.textContent = origText || 'Accept'; }
1934
- statsEl.textContent = 'Apply error: ' + e.message;
1935
- });
1936
- }
1937
-
1938
- function renderRun(run){
1939
- var box = el('div', 'dream-run');
1940
- var head = el('div', 'dream-run-head');
1941
- if (run.agent) head.appendChild(el('span', 'agent', run.agent));
1942
- var modeCls = run.mode === 'auto' ? 'auto' : 'propose';
1943
- head.appendChild(el('span', 'dream-badge ' + modeCls, run.mode));
1944
- head.appendChild(el('span', 'dream-badge outcome', run.outcome));
1945
- head.appendChild(el('span', 'when', run.iso));
1946
-
1947
- // Pending = a propose-run proposal not yet manually accepted. Accept-all
1948
- // shows only when there is something to apply.
1949
- var pending = (run.proposals || []).filter(function(p){ return run.mode !== 'auto' && !p.accepted; });
1950
- if (pending.length){
1951
- var allBtn = el('button', 'dream-accept-all', 'Accept all (' + pending.length + ')');
1952
- allBtn.title = 'Apply every pending proposal in this run to memory';
1953
- allBtn.addEventListener('click', function(){
1954
- acceptDreams(run.agent, run.ts, pending.map(function(p){ return p.index; }), allBtn);
1955
- });
1956
- head.appendChild(allBtn);
1957
- }
1958
- box.appendChild(head);
1959
-
1960
- if (run.summary) box.appendChild(el('div', 'dream-summary', run.summary));
1961
-
1962
- if (run.proposals && run.proposals.length){
1963
- var props = el('div', 'dream-props');
1964
- run.proposals.forEach(function(p){
1965
- var pe = el('div', 'dream-prop' + (p.accepted ? ' accepted' : ''));
1966
- var top = el('div');
1967
- top.appendChild(el('span', 'op ' + (p.op || ''), p.op || '?'));
1968
- top.appendChild(el('span', 'file', p.file || ''));
1969
- top.appendChild(el('span', 'score', 'score ' + (p.score != null ? p.score : '?') + ' · recall ' + (p.recallCount != null ? p.recallCount : '?')));
1970
- pe.appendChild(top);
1971
- if (p.reason) pe.appendChild(el('div', 'reason', p.reason));
1972
- if (p.target) pe.appendChild(el('div', 'anchor', 'anchor: ' + p.target));
1973
- if (p.content) pe.appendChild(el('div', 'content', p.content));
1974
-
1975
- // Per-proposal status + accept action.
1976
- var actions = el('div', 'dream-prop-actions');
1977
- if (p.accepted){
1978
- actions.appendChild(el('span', 'prop-status applied', '✓ applied'));
1979
- } else if (run.mode === 'auto'){
1980
- actions.appendChild(el('span', 'prop-status applied-auto', '✓ applied (auto)'));
1981
- } else {
1982
- actions.appendChild(el('span', 'prop-status pending', 'pending'));
1983
- var accBtn = el('button', 'dream-accept-btn', 'Accept');
1984
- accBtn.title = 'Apply this proposal to ' + (p.file || 'memory');
1985
- accBtn.addEventListener('click', function(){
1986
- acceptDreams(run.agent, run.ts, [p.index], accBtn);
1987
- });
1988
- actions.appendChild(accBtn);
1989
- }
1990
- pe.appendChild(actions);
1991
- props.appendChild(pe);
1992
- });
1993
- box.appendChild(props);
1994
- }
1995
-
1996
- var applied = (run.applied != null) ? (', applied ' + run.applied) : '';
1997
- box.appendChild(el('div', 'dream-meta', 'tokens ' + (run.tokens||0) + ' · sessions ' + (run.sessions||0) + applied));
1998
- return box;
1999
- }
2000
-
2001
- function render(){
2002
- var filter = agentSel ? agentSel.value : '';
2003
- var runs = filter ? allRuns.filter(function(r){ return r.agent === filter; }) : allRuns;
2004
- listEl.textContent = '';
2005
- if (!runs.length){
2006
- emptyEl.textContent = allRuns.length ? 'No dream runs for this agent.' : 'No dream runs yet. The nightly dreaming pass writes here after it first runs (auto or propose mode).';
2007
- emptyEl.style.display = '';
2008
- statsEl.textContent = '';
2009
- return;
2010
- }
2011
- emptyEl.style.display = 'none';
2012
- var pendingTotal = 0;
2013
- runs.forEach(function(r){
2014
- if (r.mode === 'auto') return;
2015
- (r.proposals || []).forEach(function(p){ if (!p.accepted) pendingTotal++; });
2016
- });
2017
- statsEl.textContent = runs.length + ' run' + (runs.length === 1 ? '' : 's')
2018
- + (pendingTotal ? ' · ' + pendingTotal + ' pending' : '');
2019
- if (pendingNotice){
2020
- statsEl.textContent += ' — ' + pendingNotice;
2021
- pendingNotice = '';
2022
- }
2023
- runs.forEach(function(r){ listEl.appendChild(renderRun(r)); });
2024
- }
2025
-
2026
- function loadDreams(force){
2027
- if (loaded && !force) return;
2028
- loaded = true;
2029
- statsEl.textContent = 'Loading…';
2030
- fetch(apiUrl('/knowledge/dreams'), { headers: { 'Accept': 'application/json' } }).then(function(res){
2031
- if (res.status === 401) { onUnauthorized(); return null; }
2032
- if (!res.ok) throw new Error('HTTP ' + res.status);
2033
- return res.json();
2034
- }).then(function(data){
2035
- if (!data) return;
2036
- allRuns = data.runs || [];
2037
- // Populate the agent filter, preserving the current pick.
2038
- if (agentSel){
2039
- var prev = agentSel.value;
2040
- agentSel.textContent = '';
2041
- agentSel.appendChild(el('option', null, 'All agents')); agentSel.lastChild.value = '';
2042
- (data.agents || []).forEach(function(a){ var o = el('option', null, a); o.value = a; agentSel.appendChild(o); });
2043
- agentSel.value = prev; if (agentSel.value !== prev) agentSel.value = '';
2044
- }
2045
- render();
2046
- }).catch(function(err){
2047
- statsEl.textContent = ''; emptyEl.textContent = 'Failed to load dreams: ' + err.message; emptyEl.style.display = '';
2048
- });
2049
- }
2050
-
2051
- if (agentSel) agentSel.addEventListener('change', render);
2052
- if (refreshBtn) refreshBtn.addEventListener('click', function(){ loadDreams(true); });
2053
- // Exposed so the tab switcher (in the kb-graph script) can lazy-load on first open.
2054
- window.__loadDreams = loadDreams;
2055
- })();
2056
- </script>
1764
+ <script id="kb-dreams">${memory_activity_1.memoryActivityClient}</script>
2057
1765
  </body>
2058
1766
  </html>`;
2059
1767
  }
@@ -2072,15 +1780,15 @@ function generateLoginHtml(disabledReason = '') {
2072
1780
  ? ` <h1>Claude Gateway</h1>
2073
1781
  <div class="sub">${safeReason}</div>`
2074
1782
  : ` <h1>Claude Gateway</h1>
2075
- <div class="sub">Enter an API key to access the dashboard.</div>
1783
+ <div class="sub">Sign in with your admin API key to inspect agents, workers and usage.</div>
2076
1784
  <form id="login-form" autocomplete="off">
2077
- <label for="key">API key</label>
2078
- <input id="key" type="password" placeholder="sk-gateway-..." autofocus>
1785
+ <label for="key">Admin API key</label>
1786
+ <input id="key" type="password" placeholder="Enter your admin API key" required autocomplete="current-password" autofocus>
2079
1787
  <button type="submit">Sign in</button>
2080
- <div id="err"></div>
1788
+ <div id="err" role="alert" aria-live="polite"></div>
2081
1789
  </form>`;
2082
1790
  return `<!DOCTYPE html>
2083
- <html lang="en">
1791
+ <html lang="en" data-theme="light">
2084
1792
  <head>
2085
1793
  <meta charset="UTF-8">
2086
1794
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -2110,12 +1818,12 @@ function generateLoginHtml(disabledReason = '') {
2110
1818
  }
2111
1819
  button:hover { background: #2b6cb0; }
2112
1820
  #err { color: #fc8181; font-size: 0.8rem; margin-top: 12px; min-height: 1em; }
1821
+ ${dashboard_theme_1.dashboardTheme}
1822
+ body{display:grid;place-items:center;min-height:100vh;padding:24px;background:var(--bg)}.login-layout{display:grid;grid-template-columns:1fr 1fr;width:min(980px,100%);background:var(--panel);border:1px solid var(--line);border-radius:28px;overflow:hidden;box-shadow:var(--shadow)}.login-intro{padding:48px;background:var(--lavender);display:flex;flex-direction:column;justify-content:space-between;gap:48px}.login-intro h2{font-size:36px;font-weight:400;line-height:1.25;color:var(--text);margin:0 0 20px}.login-intro p{font-size:13px;line-height:1.8;color:var(--muted)}.login-card{padding:52px 40px;align-self:center}.login-card h1{font-size:26px;margin-bottom:16px}.login-card .sub{color:var(--muted);font-size:13px;line-height:1.7;margin-bottom:28px}.login-card label{color:var(--text);font-size:12px;margin-bottom:10px}.login-card input{background:var(--panel);border-color:var(--line);padding:13px;font-size:13px}.login-card button{border-radius:160px;background:#6161ff;padding:13px;color:white;font-size:13px}.login-card button:disabled{opacity:.6}#err{color:var(--yellow);font-size:12px;line-height:1.6}.login-caption{font-size:11px;color:var(--muted);margin-top:22px;line-height:1.7}@media(max-width:760px){.login-layout{grid-template-columns:1fr;max-width:440px}.login-intro{padding:28px;gap:24px}.login-intro h2{font-size:28px}.login-card{padding:30px 26px}.login-intro .login-description{display:none}}
2113
1823
  </style>
2114
- </head>
1824
+ ${dashboard_theme_1.dashboardFontLink}</head>
2115
1825
  <body>
2116
- <div class="card">
2117
- ${bodyInner}
2118
- </div>
1826
+ <main class="login-layout"><section class="login-intro"><div class="brand row" style="padding:0"><div class="brandmark" aria-hidden="true">✳</div>Claude Gateway</div><div><h2>Your agents.<br>Your workspace.</h2><p class="login-description">A clear view of conversations, worker activity and token usage. All in one place.</p></div><p>Orchestration · Voice · Multi-channel</p></section><section class="login-card">${bodyInner}<p class="login-caption">Admin access only. Your key is exchanged for a secure session cookie and is not saved in browser storage.</p></section></main>
2119
1827
  <script>
2120
1828
  function basePath() {
2121
1829
  var p = window.location.pathname;
@@ -2129,17 +1837,29 @@ ${bodyInner}
2129
1837
  var err = document.getElementById('err');
2130
1838
  err.textContent = '';
2131
1839
  var key = document.getElementById('key').value;
1840
+ var button = loginForm.querySelector('button'); button.disabled = true;
2132
1841
  try {
2133
1842
  var res = await fetch(basePath() + '/dashboard/login', {
2134
1843
  method: 'POST',
2135
1844
  headers: { 'Content-Type': 'application/json' },
2136
1845
  body: JSON.stringify({ key: key }),
2137
1846
  });
2138
- if (res.ok) { window.location.href = basePath() + '/dashboard'; return; }
1847
+ if (res.ok) {
1848
+ var root = basePath() + '/dashboard';
1849
+ var target = new URL(window.location.href);
1850
+ var requested = target.searchParams.get('returnTo');
1851
+ if (requested) {
1852
+ var candidate = new URL(requested, window.location.origin + root + '/');
1853
+ if (candidate.origin === window.location.origin && candidate.pathname === root + '/token-report') target = candidate;
1854
+ else target = new URL(root, window.location.origin);
1855
+ }
1856
+ target.searchParams.delete('returnTo');
1857
+ window.location.replace(target.pathname + target.search + target.hash); return;
1858
+ }
2139
1859
  err.textContent = res.status === 401 ? 'Invalid API key.' : ('Login failed (HTTP ' + res.status + ').');
2140
1860
  } catch (e2) {
2141
- err.textContent = 'Network error.';
2142
- }
1861
+ err.textContent = 'Unable to connect. Please try again.';
1862
+ } finally { button.disabled = false; }
2143
1863
  });
2144
1864
  </script>
2145
1865
  </body>