@0xmaxma/claude-gateway 2.0.4 → 2.0.5

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 (228) 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 +109 -9
  7. package/dist/agent/runner.d.ts.map +1 -1
  8. package/dist/agent/runner.js +148 -119
  9. package/dist/agent/runner.js.map +1 -1
  10. package/dist/agent/skill-learning/index.d.ts.map +1 -1
  11. package/dist/agent/skill-learning/index.js +27 -8
  12. package/dist/agent/skill-learning/index.js.map +1 -1
  13. package/dist/agent/skill-learning/reviewer.d.ts +3 -1
  14. package/dist/agent/skill-learning/reviewer.d.ts.map +1 -1
  15. package/dist/agent/skill-learning/reviewer.js +6 -2
  16. package/dist/agent/skill-learning/reviewer.js.map +1 -1
  17. package/dist/agent/turn-stream.d.ts +3 -1
  18. package/dist/agent/turn-stream.d.ts.map +1 -1
  19. package/dist/agent/turn-stream.js +8 -4
  20. package/dist/agent/turn-stream.js.map +1 -1
  21. package/dist/api/gateway-router.d.ts +2 -0
  22. package/dist/api/gateway-router.d.ts.map +1 -1
  23. package/dist/api/gateway-router.js +229 -12
  24. package/dist/api/gateway-router.js.map +1 -1
  25. package/dist/api/router.js +11 -11
  26. package/dist/api/router.js.map +1 -1
  27. package/dist/api/slack-webhook-router.js +1 -1
  28. package/dist/api/slack-webhook-router.js.map +1 -1
  29. package/dist/api/voice-router.d.ts +1 -0
  30. package/dist/api/voice-router.d.ts.map +1 -1
  31. package/dist/api/voice-router.js +129 -7
  32. package/dist/api/voice-router.js.map +1 -1
  33. package/dist/config/claude-settings.d.ts +14 -0
  34. package/dist/config/claude-settings.d.ts.map +1 -1
  35. package/dist/config/claude-settings.js +20 -0
  36. package/dist/config/claude-settings.js.map +1 -1
  37. package/dist/orchestration/agent-cli-session.d.ts +46 -0
  38. package/dist/orchestration/agent-cli-session.d.ts.map +1 -0
  39. package/dist/orchestration/agent-cli-session.js +121 -0
  40. package/dist/orchestration/agent-cli-session.js.map +1 -0
  41. package/dist/orchestration/agent-tool-schemas.d.ts +6 -0
  42. package/dist/orchestration/agent-tool-schemas.d.ts.map +1 -0
  43. package/dist/orchestration/agent-tool-schemas.js +23 -0
  44. package/dist/orchestration/agent-tool-schemas.js.map +1 -0
  45. package/dist/orchestration/bridge.d.ts +6 -3
  46. package/dist/orchestration/bridge.d.ts.map +1 -1
  47. package/dist/orchestration/bridge.js +31 -14
  48. package/dist/orchestration/bridge.js.map +1 -1
  49. package/dist/orchestration/browser-voice.d.ts +14 -0
  50. package/dist/orchestration/browser-voice.d.ts.map +1 -0
  51. package/dist/orchestration/browser-voice.js +30 -0
  52. package/dist/orchestration/browser-voice.js.map +1 -0
  53. package/dist/orchestration/context-footprint.d.ts +14 -0
  54. package/dist/orchestration/context-footprint.d.ts.map +1 -0
  55. package/dist/orchestration/context-footprint.js +57 -0
  56. package/dist/orchestration/context-footprint.js.map +1 -0
  57. package/dist/orchestration/dashboard-reader-worker.d.ts +2 -0
  58. package/dist/orchestration/dashboard-reader-worker.d.ts.map +1 -0
  59. package/dist/orchestration/dashboard-reader-worker.js +155 -0
  60. package/dist/orchestration/dashboard-reader-worker.js.map +1 -0
  61. package/dist/orchestration/dashboard-reader.d.ts +15 -0
  62. package/dist/orchestration/dashboard-reader.d.ts.map +1 -0
  63. package/dist/orchestration/dashboard-reader.js +87 -0
  64. package/dist/orchestration/dashboard-reader.js.map +1 -0
  65. package/dist/orchestration/decision-context.d.ts +15 -0
  66. package/dist/orchestration/decision-context.d.ts.map +1 -0
  67. package/dist/orchestration/decision-context.js +57 -0
  68. package/dist/orchestration/decision-context.js.map +1 -0
  69. package/dist/orchestration/display-stream.d.ts +21 -2
  70. package/dist/orchestration/display-stream.d.ts.map +1 -1
  71. package/dist/orchestration/display-stream.js +61 -8
  72. package/dist/orchestration/display-stream.js.map +1 -1
  73. package/dist/orchestration/inference-errors.d.ts.map +1 -1
  74. package/dist/orchestration/inference-errors.js +19 -21
  75. package/dist/orchestration/inference-errors.js.map +1 -1
  76. package/dist/orchestration/mutation-recovery.d.ts +12 -0
  77. package/dist/orchestration/mutation-recovery.d.ts.map +1 -0
  78. package/dist/orchestration/mutation-recovery.js +41 -0
  79. package/dist/orchestration/mutation-recovery.js.map +1 -0
  80. package/dist/orchestration/native-compact.d.ts +5 -0
  81. package/dist/orchestration/native-compact.d.ts.map +1 -0
  82. package/dist/orchestration/native-compact.js +29 -0
  83. package/dist/orchestration/native-compact.js.map +1 -0
  84. package/dist/orchestration/notification-mailbox.d.ts +3 -1
  85. package/dist/orchestration/notification-mailbox.d.ts.map +1 -1
  86. package/dist/orchestration/notification-mailbox.js +4 -2
  87. package/dist/orchestration/notification-mailbox.js.map +1 -1
  88. package/dist/orchestration/process-turn.d.ts +15 -2
  89. package/dist/orchestration/process-turn.d.ts.map +1 -1
  90. package/dist/orchestration/process-turn.js +78 -14
  91. package/dist/orchestration/process-turn.js.map +1 -1
  92. package/dist/orchestration/progress-review.d.ts +8 -18
  93. package/dist/orchestration/progress-review.d.ts.map +1 -1
  94. package/dist/orchestration/progress-review.js +12 -14
  95. package/dist/orchestration/progress-review.js.map +1 -1
  96. package/dist/orchestration/provider-message.d.ts +7 -0
  97. package/dist/orchestration/provider-message.d.ts.map +1 -0
  98. package/dist/orchestration/provider-message.js +63 -0
  99. package/dist/orchestration/provider-message.js.map +1 -0
  100. package/dist/orchestration/response-errors.d.ts +6 -0
  101. package/dist/orchestration/response-errors.d.ts.map +1 -0
  102. package/dist/orchestration/response-errors.js +88 -0
  103. package/dist/orchestration/response-errors.js.map +1 -0
  104. package/dist/orchestration/response-schema.d.ts +33 -0
  105. package/dist/orchestration/response-schema.d.ts.map +1 -0
  106. package/dist/orchestration/response-schema.js +61 -0
  107. package/dist/orchestration/response-schema.js.map +1 -0
  108. package/dist/orchestration/runtime.d.ts +67 -0
  109. package/dist/orchestration/runtime.d.ts.map +1 -1
  110. package/dist/orchestration/runtime.js +266 -62
  111. package/dist/orchestration/runtime.js.map +1 -1
  112. package/dist/orchestration/skills.d.ts.map +1 -1
  113. package/dist/orchestration/skills.js +3 -2
  114. package/dist/orchestration/skills.js.map +1 -1
  115. package/dist/orchestration/source-policy.d.ts +1 -1
  116. package/dist/orchestration/source-policy.d.ts.map +1 -1
  117. package/dist/orchestration/source-policy.js +1 -1
  118. package/dist/orchestration/speech.d.ts +15 -17
  119. package/dist/orchestration/speech.d.ts.map +1 -1
  120. package/dist/orchestration/speech.js +90 -30
  121. package/dist/orchestration/speech.js.map +1 -1
  122. package/dist/orchestration/store.d.ts +2 -0
  123. package/dist/orchestration/store.d.ts.map +1 -1
  124. package/dist/orchestration/store.js +16 -1
  125. package/dist/orchestration/store.js.map +1 -1
  126. package/dist/orchestration/tasks/driver.d.ts.map +1 -1
  127. package/dist/orchestration/tasks/driver.js +8 -3
  128. package/dist/orchestration/tasks/driver.js.map +1 -1
  129. package/dist/orchestration/tasks/pool.d.ts.map +1 -1
  130. package/dist/orchestration/tasks/pool.js +8 -4
  131. package/dist/orchestration/tasks/pool.js.map +1 -1
  132. package/dist/orchestration/tasks/service.d.ts +48 -1
  133. package/dist/orchestration/tasks/service.d.ts.map +1 -1
  134. package/dist/orchestration/tasks/service.js +41 -17
  135. package/dist/orchestration/tasks/service.js.map +1 -1
  136. package/dist/orchestration/token-ledger.d.ts +125 -0
  137. package/dist/orchestration/token-ledger.d.ts.map +1 -0
  138. package/dist/orchestration/token-ledger.js +232 -0
  139. package/dist/orchestration/token-ledger.js.map +1 -0
  140. package/dist/orchestration/token-usage.d.ts +46 -0
  141. package/dist/orchestration/token-usage.d.ts.map +1 -0
  142. package/dist/orchestration/token-usage.js +102 -0
  143. package/dist/orchestration/token-usage.js.map +1 -0
  144. package/dist/orchestration/tool-activity.d.ts.map +1 -1
  145. package/dist/orchestration/tool-activity.js +5 -3
  146. package/dist/orchestration/tool-activity.js.map +1 -1
  147. package/dist/orchestration/tool-name.d.ts +10 -0
  148. package/dist/orchestration/tool-name.d.ts.map +1 -0
  149. package/dist/orchestration/tool-name.js +14 -0
  150. package/dist/orchestration/tool-name.js.map +1 -0
  151. package/dist/orchestration/transcript-checkpoint.d.ts +14 -0
  152. package/dist/orchestration/transcript-checkpoint.d.ts.map +1 -0
  153. package/dist/orchestration/transcript-checkpoint.js +83 -0
  154. package/dist/orchestration/transcript-checkpoint.js.map +1 -0
  155. package/dist/orchestration/unanswered-inputs.d.ts +6 -0
  156. package/dist/orchestration/unanswered-inputs.d.ts.map +1 -0
  157. package/dist/orchestration/unanswered-inputs.js +18 -0
  158. package/dist/orchestration/unanswered-inputs.js.map +1 -0
  159. package/dist/session/process.d.ts +10 -0
  160. package/dist/session/process.d.ts.map +1 -1
  161. package/dist/session/process.js +91 -9
  162. package/dist/session/process.js.map +1 -1
  163. package/dist/session/request-tool-capture.d.ts +31 -0
  164. package/dist/session/request-tool-capture.d.ts.map +1 -0
  165. package/dist/session/request-tool-capture.js +194 -0
  166. package/dist/session/request-tool-capture.js.map +1 -0
  167. package/dist/session/runtime-profile.d.ts +7 -3
  168. package/dist/session/runtime-profile.d.ts.map +1 -1
  169. package/dist/session/runtime-profile.js +3 -1
  170. package/dist/session/runtime-profile.js.map +1 -1
  171. package/dist/session/store.d.ts +7 -0
  172. package/dist/session/store.d.ts.map +1 -1
  173. package/dist/session/store.js +27 -0
  174. package/dist/session/store.js.map +1 -1
  175. package/dist/shell/tailer.d.ts +7 -5
  176. package/dist/shell/tailer.d.ts.map +1 -1
  177. package/dist/shell/tailer.js +6 -17
  178. package/dist/shell/tailer.js.map +1 -1
  179. package/dist/ui/cli-viewer-ui.js +1 -1
  180. package/dist/ui/dashboard-client.d.ts +3 -0
  181. package/dist/ui/dashboard-client.d.ts.map +1 -0
  182. package/dist/ui/dashboard-client.js +188 -0
  183. package/dist/ui/dashboard-client.js.map +1 -0
  184. package/dist/ui/dashboard-presentation.d.ts +9 -0
  185. package/dist/ui/dashboard-presentation.d.ts.map +1 -0
  186. package/dist/ui/dashboard-presentation.js +56 -0
  187. package/dist/ui/dashboard-presentation.js.map +1 -0
  188. package/dist/ui/dashboard-range.d.ts +5 -0
  189. package/dist/ui/dashboard-range.d.ts.map +1 -0
  190. package/dist/ui/dashboard-range.js +20 -0
  191. package/dist/ui/dashboard-range.js.map +1 -0
  192. package/dist/ui/dashboard-theme.d.ts +4 -0
  193. package/dist/ui/dashboard-theme.d.ts.map +1 -0
  194. package/dist/ui/dashboard-theme.js +485 -0
  195. package/dist/ui/dashboard-theme.js.map +1 -0
  196. package/dist/ui/token-report-client.d.ts +3 -0
  197. package/dist/ui/token-report-client.d.ts.map +1 -0
  198. package/dist/ui/token-report-client.js +34 -0
  199. package/dist/ui/token-report-client.js.map +1 -0
  200. package/dist/ui/token-report.d.ts +89 -0
  201. package/dist/ui/token-report.d.ts.map +1 -0
  202. package/dist/ui/token-report.js +200 -0
  203. package/dist/ui/token-report.js.map +1 -0
  204. package/dist/ui/web-ui.d.ts.map +1 -1
  205. package/dist/ui/web-ui.js +124 -181
  206. package/dist/ui/web-ui.js.map +1 -1
  207. package/dist/voice/replay.d.ts +8 -0
  208. package/dist/voice/replay.d.ts.map +1 -0
  209. package/dist/voice/replay.js +48 -0
  210. package/dist/voice/replay.js.map +1 -0
  211. package/dist/voice/session.d.ts +9 -0
  212. package/dist/voice/session.d.ts.map +1 -1
  213. package/dist/voice/session.js +72 -14
  214. package/dist/voice/session.js.map +1 -1
  215. package/mcp/lazy-connector.test.ts +58 -0
  216. package/mcp/lazy-connector.ts +67 -0
  217. package/mcp/lazy-tools.test.ts +152 -0
  218. package/mcp/lazy-tools.ts +41 -0
  219. package/mcp/server.ts +53 -34
  220. package/mcp/tools/discord/commands.ts +2 -0
  221. package/mcp/tools/tasks/module.ts +1 -12
  222. package/mcp/tools/telegram/receiver-server.ts +14 -14
  223. package/package.json +2 -1
  224. package/resource/dashboard/OFL.txt +93 -0
  225. package/resource/dashboard/poppins-300.woff2 +0 -0
  226. package/resource/dashboard/poppins-400.woff2 +0 -0
  227. package/resource/dashboard/poppins-500.woff2 +0 -0
  228. package/resource/dashboard/poppins-600.woff2 +0 -0
package/dist/ui/web-ui.js CHANGED
@@ -2,13 +2,16 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.generateDashboardHtml = generateDashboardHtml;
4
4
  exports.generateLoginHtml = generateLoginHtml;
5
+ const dashboard_range_1 = require("./dashboard-range");
6
+ const dashboard_theme_1 = require("./dashboard-theme");
7
+ const dashboard_client_1 = require("./dashboard-client");
5
8
  /**
6
9
  * Generates a self-contained HTML dashboard page for the gateway status UI.
7
10
  * No external dependencies except xterm.js CDN for PTY viewer.
8
11
  */
9
12
  function generateDashboardHtml() {
10
13
  return `<!DOCTYPE html>
11
- <html lang="en">
14
+ <html lang="en" data-theme="light">
12
15
  <head>
13
16
  <meta charset="UTF-8">
14
17
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -341,97 +344,78 @@ function generateDashboardHtml() {
341
344
  .dream-accept-all:disabled { opacity: 0.55; cursor: default; }
342
345
  /* Note detail — full-width card below the graph. Shows the whole file. */
343
346
  .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;
347
+ margin-top: 12px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px;
348
+ padding: 16px 18px; font-size: 0.85rem; color: var(--text);
346
349
  }
347
350
  .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; }
351
+ .kb-note-head h3 { margin: 0; font-size: 1.02rem; color: var(--text); flex: 1; word-break: break-word; }
349
352
  .kb-note-close {
350
- cursor: pointer; color: #718096; background: #1a202c; border: 1px solid #2d3748;
353
+ cursor: pointer; color: var(--muted); background: var(--panel); border: 1px solid var(--line);
351
354
  border-radius: 4px; padding: 1px 8px; font-size: 0.9rem; line-height: 1.4;
352
355
  }
353
- .kb-note-close:hover { color: #e2e8f0; background: #2d3748; }
356
+ .kb-note-close:hover { color: var(--text); background: var(--line); }
354
357
  /* Metadata chips row. */
355
358
  .kb-note-meta { display: flex; flex-wrap: wrap; gap: 6px; margin: 10px 0 4px; }
356
359
  .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;
360
+ display: inline-flex; align-items: center; gap: 5px; background: var(--raised);
361
+ border: 1px solid var(--line); border-radius: 999px; padding: 2px 10px; font-size: 0.72rem; color: var(--muted);
359
362
  }
360
- .kb-note-chip b { color: #cbd5e0; font-weight: 600; }
363
+ .kb-note-chip b { color: var(--text); font-weight: 600; }
361
364
  .kb-note-chip i { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
362
365
  /* File path line under the chips — full width, monospace, wraps on long paths. */
363
366
  .kb-note-path {
364
367
  margin: 6px 0 2px; font-size: 0.72rem; font-family: ui-monospace, monospace;
365
- color: #718096; word-break: break-all;
368
+ color: var(--muted); word-break: break-all;
366
369
  }
367
- .kb-note-path b { color: #a0aec0; font-weight: 600; }
370
+ .kb-note-path b { color: var(--muted); font-weight: 600; }
368
371
  /* Rendered Markdown body. */
369
372
  .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;
373
+ margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line);
374
+ line-height: 1.62; color: var(--text); word-break: break-word;
372
375
  }
373
376
  .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; }
377
+ .kb-md h1, .kb-md h2, .kb-md h3, .kb-md h4 { color: var(--text); line-height: 1.3; margin: 18px 0 8px; }
378
+ .kb-md h1 { font-size: 1.25rem; border-bottom: 1px solid var(--line); padding-bottom: 5px; }
379
+ .kb-md h2 { font-size: 1.1rem; border-bottom: 1px solid var(--line); padding-bottom: 4px; }
380
+ .kb-md h3 { font-size: 1rem; } .kb-md h4 { font-size: 0.9rem; color: var(--muted); }
378
381
  .kb-md p { margin: 8px 0; }
379
382
  .kb-md ul, .kb-md ol { margin: 8px 0; padding-left: 22px; }
380
383
  .kb-md li { margin: 3px 0; }
381
- .kb-md a { color: #63b3ed; text-decoration: none; } .kb-md a:hover { text-decoration: underline; }
384
+ .kb-md a { color: var(--accent); text-decoration: none; } .kb-md a:hover { text-decoration: underline; }
382
385
  .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;
386
+ background: var(--raised); border: 1px solid var(--line); border-radius: 4px;
387
+ padding: 1px 5px; font-size: 0.82em; font-family: ui-monospace, monospace; color: var(--text);
385
388
  }
386
389
  .kb-md pre {
387
- background: #0a0e17; border: 1px solid #1e2740; border-radius: 6px; padding: 10px 12px;
390
+ background: var(--raised); border: 1px solid var(--line); border-radius: 6px; padding: 10px 12px;
388
391
  overflow-x: auto; margin: 10px 0;
389
392
  }
390
393
  .kb-md pre code { background: none; border: none; padding: 0; font-size: 0.8rem; line-height: 1.5; }
391
394
  .kb-md blockquote {
392
- margin: 10px 0; padding: 2px 14px; border-left: 3px solid #2d3748; color: #a0aec0;
395
+ margin: 10px 0; padding: 2px 14px; border-left: 3px solid var(--line); color: var(--muted);
393
396
  }
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; }
397
+ .kb-md hr { border: none; border-top: 1px solid var(--line); margin: 14px 0; }
398
+ .kb-md strong { color: var(--text); } .kb-md table { border-collapse: collapse; margin: 0; width: 100%; font-size: 0.82rem; }
399
+ .kb-md th, .kb-md td { border: 1px solid var(--line); padding: 5px 10px; text-align: left; }
400
+ .kb-md th { background: var(--raised); color: var(--text); font-weight: 600; }
401
+ .kb-md tbody tr:nth-child(even) { background: var(--raised); }
399
402
  /* Wide tables scroll horizontally instead of breaking the panel. */
400
403
  .kb-table-wrap { overflow-x: auto; margin: 10px 0; border-radius: 6px; }
401
- .kb-md del { color: #718096; }
404
+ .kb-md del { color: var(--muted); }
402
405
  /* Task-list items: bullet replaced by a real (disabled) checkbox. */
403
406
  .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; }
407
+ .kb-md li.kb-task input { margin-right: 7px; vertical-align: middle; accent-color: var(--accent); }
408
+ .kb-note-loading { color: var(--muted); font-size: 0.82rem; margin-top: 12px; }
409
+ ${dashboard_theme_1.dashboardTheme}
406
410
  </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>
411
+ ${dashboard_theme_1.dashboardFontLink}<script>try{document.documentElement.dataset.sidebarCollapsed=localStorage.getItem("gateway-sidebar-collapsed")==="true"?"true":"false";}catch{}</script></head>
412
+ <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">✳</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><button class="theme-toggle" id="dash-theme" aria-label="Toggle light and dark theme">◐</button><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>
413
+ <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><div class="columns"><section class="dash-panel"><h2>Token activity</h2><p class="live-note">Today · UTC · Workers (violet) / Agent (blue)</p><div id="overview-chart"></div><p class="live-note">Recorded token volume, including cache. Not billing cost.</p></section><section class="dash-panel"><h2>Needs your attention</h2><div class="dash-mini-list" id="overview-attention"></div></section></div><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>
414
+ <div class="dash-filter"><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")}<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>
415
+ <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>
416
+ <div id="view-system" class="view" style="display:none">
417
+ <h2>Processes</h2>
418
+ <div class="proc-tree" id="proc-tree">Loading...</div>
435
419
 
436
420
  <!-- PTY viewer — full width so the native 200-col terminal has room.
437
421
  Placed above the Sessions table so the live mirror is the first thing
@@ -450,32 +434,7 @@ function generateDashboardHtml() {
450
434
  <div id="pty-terminal" tabindex="0"></div>
451
435
  </div>
452
436
 
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
-
437
+ </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
438
  <!-- Knowledge base — a 3D force-directed graph over the shared vault / an agent's memory.
480
439
  Zero external deps: the spherical layout + canvas render + rotation are hand-rolled. -->
481
440
  <div id="view-kb" class="view" style="display:none;">
@@ -530,7 +489,7 @@ function generateDashboardHtml() {
530
489
  <div id="dreams-empty" class="dreams-empty" style="display:none;"></div>
531
490
  </div><!-- /view-dreams -->
532
491
 
533
- <div id="error-msg" class="error" style="display:none;"></div>
492
+ <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
493
 
535
494
  <script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@6.0.0/lib/xterm.min.js"
536
495
  integrity="sha384-pELe6ZHtFxFcuYBq3gMkqvmnNIqUWnAYjBG5gThqQQCjWp8PJ/65MLK4lMIfEK1e" crossorigin="anonymous"></script>
@@ -540,7 +499,7 @@ function generateDashboardHtml() {
540
499
  // page (nothing for view-source/XSS to steal). If any read returns 401 the
541
500
  // session has expired or is missing, so bounce to /dashboard (login page).
542
501
  function onUnauthorized() {
543
- window.location.href = apiUrl('/dashboard');
502
+ window.location.reload();
544
503
  }
545
504
 
546
505
  // Must match the server PTY size (src/shell/screen.ts ScreenModel defaults).
@@ -861,7 +820,7 @@ function generateDashboardHtml() {
861
820
 
862
821
  const expandedSessionRows = new Set();
863
822
  // Event delegation for Live buttons (avoids inline onclick + HTML injection)
864
- document.getElementById('sessions-tbody').addEventListener('click', function(e) {
823
+ document.getElementById('session-results').addEventListener('click', function(e) {
865
824
  const toggle = e.target.closest('.btn-workers');
866
825
  if (toggle) {
867
826
  const key=toggle.getAttribute('data-expand-key'), open=!expandedSessionRows.has(key);
@@ -871,8 +830,8 @@ function generateDashboardHtml() {
871
830
  const detail=document.getElementById(toggle.getAttribute('aria-controls'));if(detail)detail.hidden=!open;
872
831
  return;
873
832
  }
874
- const btn = e.target.closest('.btn-stream');
875
- if (btn) void openPtyViewer(btn.getAttribute('data-agent-id'), btn.getAttribute('data-session-id'));
833
+ const btn = e.target.closest('button.btn-stream[data-agent-id][data-session-id]');
834
+ if (btn) { document.getElementById('tab-system').click(); void openPtyViewer(btn.getAttribute('data-agent-id'), btn.getAttribute('data-session-id')); }
876
835
  });
877
836
 
878
837
  function escHtml(s) {
@@ -916,22 +875,25 @@ function generateDashboardHtml() {
916
875
 
917
876
  // Format a context-window token count compactly: 1234 -> "1.2k", 45000 -> "45k".
918
877
  // Full value is kept in the tooltip. 0/unknown renders as a dash.
878
+ function toolInventory(loaded, used, contextTools) {
879
+ function list(label, names) {
880
+ return '<div title="'+(Array.isArray(names)?escHtml(names.join(', ')||'None'):'Not recorded')+'">'+label+': '+(Array.isArray(names)?compactNumber(names.length):'—')+'</div>';
881
+ }
882
+ return list('Loaded', contextTools)+list('Used', used);
883
+ }
884
+
885
+ function fmtRecordedTokens(n) {
886
+ return n == null ? '—' : n === 0 ? '0' : fmtTokens(n);
887
+ }
888
+
919
889
  function fmtTokens(n) {
920
890
  const v = Number(n) || 0;
921
891
  if (v <= 0) return '<span class="ts">&mdash;</span>';
922
- const label = v >= 1000 ? (v / 1000).toFixed(v >= 10000 ? 0 : 1) + 'k' : String(v);
892
+ const label = compactNumber(v);
923
893
  return '<span title="' + v.toLocaleString() + ' tokens">' + label + '</span>';
924
894
  }
925
895
 
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
-
896
+ function applyDashboardSnapshot(data) {
935
897
  document.getElementById('uptime').textContent = fmtUptime(data.uptime || 0);
936
898
  document.getElementById('started-at').textContent = data.startedAt
937
899
  ? new Date(data.startedAt).toLocaleString() : '\\u2014';
@@ -949,70 +911,33 @@ function generateDashboardHtml() {
949
911
  const badges = agents.map(function(a) {
950
912
  const ok = a.hasChannel ? a.isRunning : true;
951
913
  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>';
914
+ return agentBadge(a.id);
953
915
  });
954
916
  document.getElementById('agents-bar').innerHTML = badges.join('') || '<span class="ts">No agents</span>';
955
917
 
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
- });
918
+ renderDashboard(data);
1006
919
 
1007
- document.getElementById('sessions-tbody').innerHTML =
1008
- rows.length ? rows.join('') : '<tr><td colspan="11" class="ts">No active sessions</td></tr>';
920
+ document.getElementById('refresh-indicator').textContent = 'Live · updated';
921
+ }
1009
922
 
1010
- document.getElementById('refresh-indicator').textContent = 'auto-refresh 3s';
923
+ // ── Status Refresh ────────────────────────────────────────────────────────
924
+ async function refresh() {
925
+ if (dashboardBusy || document.hidden) return;
926
+ dashboardBusy = true;
927
+ const requestedOffset = dashboardOffset, requestedScope = dashboardScope;
928
+ document.getElementById('refresh-indicator').textContent = 'Updating…';
929
+ try {
930
+ const res = await fetch(apiUrl('/status')+'?offset='+requestedOffset+'&scope='+requestedScope);
931
+ if (res.status === 401) { onUnauthorized(); return; }
932
+ if (!res.ok) throw new Error('HTTP ' + res.status);
933
+ const data = await res.json();
934
+
935
+ if (requestedOffset === dashboardOffset && requestedScope === dashboardScope) applyDashboardSnapshot(data);
1011
936
  } catch(e) {
1012
937
  document.getElementById('error-msg').textContent = 'Error fetching status: ' + e.message;
1013
938
  document.getElementById('error-msg').style.display = 'block';
1014
- document.getElementById('refresh-indicator').textContent = 'error';
1015
- }
939
+ document.getElementById('refresh-indicator').textContent = 'Reconnecting…';
940
+ } finally {dashboardBusy=false;}
1016
941
  }
1017
942
 
1018
943
  // ── Process Tree ─────────────────────────────────────────────────────────
@@ -1166,14 +1091,15 @@ function generateDashboardHtml() {
1166
1091
  try {
1167
1092
  await fetch(apiUrl('/dashboard/logout'), { method: 'POST' });
1168
1093
  } catch (e) { /* best-effort; navigate regardless */ }
1169
- window.location.href = apiUrl('/dashboard');
1094
+ window.location.reload();
1170
1095
  });
1171
1096
 
1097
+ ${dashboard_client_1.dashboardClient}
1172
1098
  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);
1099
+ connectDashboardStream();
1100
+ setInterval(function(){if(!dashboardStream || dashboardStream.readyState!==1)refresh();},15000);
1101
+ setInterval(function(){if(!document.hidden && document.getElementById('view-system').style.display!=='none')refreshProcesses();},10000);
1102
+ document.addEventListener('visibilitychange',function(){if(document.hidden){dashboardStream?.close();dashboardStream=null;}else{refresh();connectDashboardStream();}});
1177
1103
  </script>
1178
1104
 
1179
1105
  <!-- Knowledge base graph: tab switching + hand-rolled force-directed renderer.
@@ -1400,7 +1326,11 @@ function generateDashboardHtml() {
1400
1326
  // Labels: hubs always; hovered / selected / search-hit reveal on demand.
1401
1327
  var showLabel = (o.deg >= LABEL_MIN_DEG) || isHov || isSel || (searching && o.hit);
1402
1328
  if (showLabel && !dim){
1403
- ctx.fillStyle = (isHov || isSel) ? '#e6edf3' : 'rgba(213,222,236,' + (0.25 + 0.6 * near) + ')';
1329
+ // White, not var(--text): .kb-stage paints a fixed deep-space backdrop in BOTH
1330
+ // themes, but --text flips to near-black under the light theme (the page default),
1331
+ // which left labels unreadable on the dark canvas. Matches the white the hover /
1332
+ // selection rings above already use.
1333
+ ctx.fillStyle = '#fff';
1404
1334
  ctx.font = (isHov || isSel ? '12px ' : '11px ') + 'ui-sans-serif, system-ui, sans-serif';
1405
1335
  ctx.fillText(nd.title, p.sx + p.r + 4, p.sy + 3);
1406
1336
  }
@@ -1530,7 +1460,7 @@ function generateDashboardHtml() {
1530
1460
  o.hit = hay.indexOf(q) >= 0;
1531
1461
  if (o.hit) hits.push(o);
1532
1462
  });
1533
- if (elSearchCount) elSearchCount.textContent = hits.length + ' / ' + G.nodes.length;
1463
+ if (elSearchCount) elSearchCount.textContent = compactNumber(hits.length) + ' / ' + compactNumber(G.nodes.length);
1534
1464
  invalidate();
1535
1465
  return hits;
1536
1466
  }
@@ -1812,7 +1742,7 @@ function generateDashboardHtml() {
1812
1742
  sourceSel.textContent = '';
1813
1743
  (data.sources || [{ id: 'shared', label: 'Shared Knowledge Base', count: 0 }]).forEach(function(s){
1814
1744
  var o = document.createElement('option');
1815
- o.value = s.id; o.textContent = s.label + (typeof s.count === 'number' ? ' (' + s.count + ')' : '');
1745
+ o.value = s.id; o.textContent = s.label + (typeof s.count === 'number' ? ' (' + compactNumber(s.count) + ')' : '');
1816
1746
  sourceSel.appendChild(o);
1817
1747
  });
1818
1748
  // Restore prior selection if it still exists, else fall back to Shared KB.
@@ -1855,7 +1785,7 @@ function generateDashboardHtml() {
1855
1785
  }
1856
1786
  buildModel(data);
1857
1787
  renderLegend();
1858
- elStats.textContent = data.nodes.length + ' notes, ' + data.edges.length + ' links';
1788
+ elStats.textContent = compactNumber(data.nodes.length) + ' notes, ' + compactNumber(data.edges.length) + ' links';
1859
1789
  }).catch(function(err){
1860
1790
  elStats.textContent = ''; elEmpty.textContent = 'Failed to load graph: ' + err.message; elEmpty.style.display = '';
1861
1791
  });
@@ -1938,11 +1868,11 @@ function generateDashboardHtml() {
1938
1868
  function renderRun(run){
1939
1869
  var box = el('div', 'dream-run');
1940
1870
  var head = el('div', 'dream-run-head');
1941
- if (run.agent) head.appendChild(el('span', 'agent', run.agent));
1871
+ if (run.agent) { var agentEl=el('span'); agentEl.innerHTML=agentBadge(run.agent); head.appendChild(agentEl); }
1942
1872
  var modeCls = run.mode === 'auto' ? 'auto' : 'propose';
1943
1873
  head.appendChild(el('span', 'dream-badge ' + modeCls, run.mode));
1944
1874
  head.appendChild(el('span', 'dream-badge outcome', run.outcome));
1945
- head.appendChild(el('span', 'when', run.iso));
1875
+ head.appendChild(el('span', 'when', new Date(run.iso).toLocaleString()));
1946
1876
 
1947
1877
  // Pending = a propose-run proposal not yet manually accepted. Accept-all
1948
1878
  // shows only when there is something to apply.
@@ -1994,13 +1924,14 @@ function generateDashboardHtml() {
1994
1924
  }
1995
1925
 
1996
1926
  var applied = (run.applied != null) ? (', applied ' + run.applied) : '';
1997
- box.appendChild(el('div', 'dream-meta', 'tokens ' + (run.tokens||0) + ' · sessions ' + (run.sessions||0) + applied));
1927
+ box.appendChild(el('div', 'dream-meta', 'tokens ' + compactNumber(run.tokens||0) + ' · sessions ' + compactNumber(run.sessions||0) + applied));
1998
1928
  return box;
1999
1929
  }
2000
1930
 
2001
1931
  function render(){
2002
1932
  var filter = agentSel ? agentSel.value : '';
2003
1933
  var runs = filter ? allRuns.filter(function(r){ return r.agent === filter; }) : allRuns;
1934
+ runs=runs.filter(function(r){return Date.parse(r.iso)>=dashboardSince(dashboardScope);}).sort(function(a,b){return Date.parse(b.iso)-Date.parse(a.iso);});
2004
1935
  listEl.textContent = '';
2005
1936
  if (!runs.length){
2006
1937
  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).';
@@ -2014,8 +1945,8 @@ function generateDashboardHtml() {
2014
1945
  if (r.mode === 'auto') return;
2015
1946
  (r.proposals || []).forEach(function(p){ if (!p.accepted) pendingTotal++; });
2016
1947
  });
2017
- statsEl.textContent = runs.length + ' run' + (runs.length === 1 ? '' : 's')
2018
- + (pendingTotal ? ' · ' + pendingTotal + ' pending' : '');
1948
+ statsEl.textContent = compactNumber(runs.length) + ' run' + (runs.length === 1 ? '' : 's')
1949
+ + (pendingTotal ? ' · ' + compactNumber(pendingTotal) + ' pending' : '');
2019
1950
  if (pendingNotice){
2020
1951
  statsEl.textContent += ' — ' + pendingNotice;
2021
1952
  pendingNotice = '';
@@ -2072,15 +2003,15 @@ function generateLoginHtml(disabledReason = '') {
2072
2003
  ? ` <h1>Claude Gateway</h1>
2073
2004
  <div class="sub">${safeReason}</div>`
2074
2005
  : ` <h1>Claude Gateway</h1>
2075
- <div class="sub">Enter an API key to access the dashboard.</div>
2006
+ <div class="sub">Sign in with your admin API key to inspect agents, workers and usage.</div>
2076
2007
  <form id="login-form" autocomplete="off">
2077
- <label for="key">API key</label>
2078
- <input id="key" type="password" placeholder="sk-gateway-..." autofocus>
2008
+ <label for="key">Admin API key</label>
2009
+ <input id="key" type="password" placeholder="Enter your admin API key" required autocomplete="current-password" autofocus>
2079
2010
  <button type="submit">Sign in</button>
2080
- <div id="err"></div>
2011
+ <div id="err" role="alert" aria-live="polite"></div>
2081
2012
  </form>`;
2082
2013
  return `<!DOCTYPE html>
2083
- <html lang="en">
2014
+ <html lang="en" data-theme="light">
2084
2015
  <head>
2085
2016
  <meta charset="UTF-8">
2086
2017
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -2110,12 +2041,12 @@ function generateLoginHtml(disabledReason = '') {
2110
2041
  }
2111
2042
  button:hover { background: #2b6cb0; }
2112
2043
  #err { color: #fc8181; font-size: 0.8rem; margin-top: 12px; min-height: 1em; }
2044
+ ${dashboard_theme_1.dashboardTheme}
2045
+ 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
2046
  </style>
2114
- </head>
2047
+ ${dashboard_theme_1.dashboardFontLink}</head>
2115
2048
  <body>
2116
- <div class="card">
2117
- ${bodyInner}
2118
- </div>
2049
+ <main class="login-layout"><section class="login-intro"><div class="brand row" style="padding:0"><div class="brandmark">✳</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
2050
  <script>
2120
2051
  function basePath() {
2121
2052
  var p = window.location.pathname;
@@ -2129,17 +2060,29 @@ ${bodyInner}
2129
2060
  var err = document.getElementById('err');
2130
2061
  err.textContent = '';
2131
2062
  var key = document.getElementById('key').value;
2063
+ var button = loginForm.querySelector('button'); button.disabled = true;
2132
2064
  try {
2133
2065
  var res = await fetch(basePath() + '/dashboard/login', {
2134
2066
  method: 'POST',
2135
2067
  headers: { 'Content-Type': 'application/json' },
2136
2068
  body: JSON.stringify({ key: key }),
2137
2069
  });
2138
- if (res.ok) { window.location.href = basePath() + '/dashboard'; return; }
2070
+ if (res.ok) {
2071
+ var root = basePath() + '/dashboard';
2072
+ var target = new URL(window.location.href);
2073
+ var requested = target.searchParams.get('returnTo');
2074
+ if (requested) {
2075
+ var candidate = new URL(requested, window.location.origin + root + '/');
2076
+ if (candidate.origin === window.location.origin && candidate.pathname === root + '/token-report') target = candidate;
2077
+ else target = new URL(root, window.location.origin);
2078
+ }
2079
+ target.searchParams.delete('returnTo');
2080
+ window.location.replace(target.pathname + target.search + target.hash); return;
2081
+ }
2139
2082
  err.textContent = res.status === 401 ? 'Invalid API key.' : ('Login failed (HTTP ' + res.status + ').');
2140
2083
  } catch (e2) {
2141
- err.textContent = 'Network error.';
2142
- }
2084
+ err.textContent = 'Unable to connect. Please try again.';
2085
+ } finally { button.disabled = false; }
2143
2086
  });
2144
2087
  </script>
2145
2088
  </body>
@@ -1 +1 @@
1
- {"version":3,"file":"web-ui.js","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web-ui.js","sourceRoot":"","sources":["../../src/ui/web-ui.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { TtsProvider } from './types';
2
+ /** Bounded, shared synthesis for explicit replay requests, independent of a microphone lease. */
3
+ export declare class VoiceReplays {
4
+ private readonly pending;
5
+ generate(key: string, provider: TtsProvider, voiceId: string, text: string): Promise<Buffer>;
6
+ close(): void;
7
+ }
8
+ //# sourceMappingURL=replay.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../src/voice/replay.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,WAAW,EAAc,MAAM,SAAS,CAAC;AAGzD,iGAAiG;AACjG,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+E;IACvG,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwB5F,KAAK,IAAI,IAAI;CACd"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VoiceReplays = void 0;
4
+ const types_1 = require("./types");
5
+ const wav_1 = require("./wav");
6
+ /** Bounded, shared synthesis for explicit replay requests, independent of a microphone lease. */
7
+ class VoiceReplays {
8
+ constructor() {
9
+ this.pending = new Map();
10
+ }
11
+ generate(key, provider, voiceId, text) {
12
+ const existing = this.pending.get(key);
13
+ if (existing)
14
+ return existing.result;
15
+ if (this.pending.size >= 4)
16
+ return Promise.reject(new types_1.VoiceError('VOICE_REPLAY_BUSY'));
17
+ const controller = new AbortController();
18
+ const timer = setTimeout(() => controller.abort(), 60000);
19
+ const result = (async () => {
20
+ const chunks = [];
21
+ let size = 0, sequence = 0;
22
+ for await (const chunk of provider.synthesize({ text: (async function* () { yield text; })(), voiceId, outputFormat: types_1.PCM16, signal: controller.signal })) {
23
+ if (controller.signal.aborted)
24
+ throw new types_1.VoiceError('TTS_UNAVAILABLE');
25
+ if (chunk.chunkSeq !== sequence++ || chunk.format.encoding !== types_1.PCM16.encoding || chunk.format.sampleRate !== types_1.PCM16.sampleRate || chunk.format.channels !== 1 || chunk.bytes.length % 2)
26
+ throw new types_1.VoiceError('INVALID_AUDIO_CHUNK');
27
+ size += chunk.bytes.length;
28
+ if (size > 16 * 1024 * 1024 - 44) {
29
+ controller.abort();
30
+ throw new types_1.VoiceError('VOICE_REPLAY_TOO_LARGE');
31
+ }
32
+ chunks.push(Buffer.from(chunk.bytes));
33
+ }
34
+ if (!size || controller.signal.aborted)
35
+ throw new types_1.VoiceError('TTS_UNAVAILABLE');
36
+ return (0, wav_1.pcmToWav)(Buffer.concat(chunks));
37
+ })();
38
+ // A provider must not leave the HTTP request hanging if it ignores cancellation.
39
+ const aborted = new Promise((_, reject) => controller.signal.addEventListener('abort', () => reject(new types_1.VoiceError('TTS_UNAVAILABLE')), { once: true }));
40
+ const bounded = Promise.race([result, aborted]).finally(() => { clearTimeout(timer); this.pending.delete(key); });
41
+ this.pending.set(key, { result: bounded, controller });
42
+ return bounded;
43
+ }
44
+ close() { for (const job of this.pending.values())
45
+ job.controller.abort(); }
46
+ }
47
+ exports.VoiceReplays = VoiceReplays;
48
+ //# sourceMappingURL=replay.js.map