codex-autorunner 0.1.2__py3-none-any.whl → 1.1.0__py3-none-any.whl

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. codex_autorunner/__init__.py +12 -1
  2. codex_autorunner/__main__.py +4 -0
  3. codex_autorunner/agents/codex/harness.py +1 -1
  4. codex_autorunner/agents/opencode/client.py +68 -35
  5. codex_autorunner/agents/opencode/constants.py +3 -0
  6. codex_autorunner/agents/opencode/harness.py +6 -1
  7. codex_autorunner/agents/opencode/logging.py +21 -5
  8. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  9. codex_autorunner/agents/opencode/runtime.py +176 -47
  10. codex_autorunner/agents/opencode/supervisor.py +36 -48
  11. codex_autorunner/agents/registry.py +155 -8
  12. codex_autorunner/api.py +25 -0
  13. codex_autorunner/bootstrap.py +22 -37
  14. codex_autorunner/cli.py +5 -1156
  15. codex_autorunner/codex_cli.py +20 -84
  16. codex_autorunner/core/__init__.py +4 -0
  17. codex_autorunner/core/about_car.py +49 -32
  18. codex_autorunner/core/adapter_utils.py +21 -0
  19. codex_autorunner/core/app_server_ids.py +59 -0
  20. codex_autorunner/core/app_server_logging.py +7 -3
  21. codex_autorunner/core/app_server_prompts.py +27 -260
  22. codex_autorunner/core/app_server_threads.py +26 -28
  23. codex_autorunner/core/app_server_utils.py +165 -0
  24. codex_autorunner/core/archive.py +349 -0
  25. codex_autorunner/core/codex_runner.py +12 -2
  26. codex_autorunner/core/config.py +587 -103
  27. codex_autorunner/core/docs.py +10 -2
  28. codex_autorunner/core/drafts.py +136 -0
  29. codex_autorunner/core/engine.py +1531 -866
  30. codex_autorunner/core/exceptions.py +4 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +202 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +88 -0
  35. codex_autorunner/core/flows/reasons.py +52 -0
  36. codex_autorunner/core/flows/reconciler.py +131 -0
  37. codex_autorunner/core/flows/runtime.py +382 -0
  38. codex_autorunner/core/flows/store.py +568 -0
  39. codex_autorunner/core/flows/transition.py +138 -0
  40. codex_autorunner/core/flows/ux_helpers.py +257 -0
  41. codex_autorunner/core/flows/worker_process.py +242 -0
  42. codex_autorunner/core/git_utils.py +62 -0
  43. codex_autorunner/core/hub.py +136 -16
  44. codex_autorunner/core/locks.py +4 -0
  45. codex_autorunner/core/notifications.py +14 -2
  46. codex_autorunner/core/ports/__init__.py +28 -0
  47. codex_autorunner/core/ports/agent_backend.py +150 -0
  48. codex_autorunner/core/ports/backend_orchestrator.py +41 -0
  49. codex_autorunner/core/ports/run_event.py +91 -0
  50. codex_autorunner/core/prompt.py +15 -7
  51. codex_autorunner/core/redaction.py +29 -0
  52. codex_autorunner/core/review_context.py +5 -8
  53. codex_autorunner/core/run_index.py +6 -0
  54. codex_autorunner/core/runner_process.py +5 -2
  55. codex_autorunner/core/state.py +0 -88
  56. codex_autorunner/core/state_roots.py +57 -0
  57. codex_autorunner/core/supervisor_protocol.py +15 -0
  58. codex_autorunner/core/supervisor_utils.py +67 -0
  59. codex_autorunner/core/text_delta_coalescer.py +54 -0
  60. codex_autorunner/core/ticket_linter_cli.py +201 -0
  61. codex_autorunner/core/ticket_manager_cli.py +432 -0
  62. codex_autorunner/core/update.py +24 -16
  63. codex_autorunner/core/update_paths.py +28 -0
  64. codex_autorunner/core/update_runner.py +2 -0
  65. codex_autorunner/core/usage.py +164 -12
  66. codex_autorunner/core/utils.py +120 -11
  67. codex_autorunner/discovery.py +2 -4
  68. codex_autorunner/flows/review/__init__.py +17 -0
  69. codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
  70. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  71. codex_autorunner/flows/ticket_flow/definition.py +98 -0
  72. codex_autorunner/integrations/agents/__init__.py +17 -0
  73. codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
  74. codex_autorunner/integrations/agents/codex_adapter.py +90 -0
  75. codex_autorunner/integrations/agents/codex_backend.py +448 -0
  76. codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
  77. codex_autorunner/integrations/agents/opencode_backend.py +598 -0
  78. codex_autorunner/integrations/agents/runner.py +91 -0
  79. codex_autorunner/integrations/agents/wiring.py +271 -0
  80. codex_autorunner/integrations/app_server/client.py +583 -152
  81. codex_autorunner/integrations/app_server/env.py +2 -107
  82. codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
  83. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  84. codex_autorunner/integrations/telegram/adapter.py +204 -165
  85. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  86. codex_autorunner/integrations/telegram/config.py +221 -0
  87. codex_autorunner/integrations/telegram/constants.py +17 -2
  88. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  89. codex_autorunner/integrations/telegram/doctor.py +47 -0
  90. codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
  91. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  92. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  93. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  94. codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
  95. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  96. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  97. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  98. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
  99. codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
  100. codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
  101. codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
  102. codex_autorunner/integrations/telegram/helpers.py +111 -16
  103. codex_autorunner/integrations/telegram/outbox.py +208 -37
  104. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  105. codex_autorunner/integrations/telegram/service.py +221 -42
  106. codex_autorunner/integrations/telegram/state.py +100 -2
  107. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
  108. codex_autorunner/integrations/telegram/transport.py +39 -4
  109. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  110. codex_autorunner/manifest.py +2 -0
  111. codex_autorunner/plugin_api.py +22 -0
  112. codex_autorunner/routes/__init__.py +37 -67
  113. codex_autorunner/routes/agents.py +2 -137
  114. codex_autorunner/routes/analytics.py +3 -0
  115. codex_autorunner/routes/app_server.py +2 -131
  116. codex_autorunner/routes/base.py +2 -624
  117. codex_autorunner/routes/file_chat.py +7 -0
  118. codex_autorunner/routes/flows.py +7 -0
  119. codex_autorunner/routes/messages.py +7 -0
  120. codex_autorunner/routes/repos.py +2 -196
  121. codex_autorunner/routes/review.py +2 -147
  122. codex_autorunner/routes/sessions.py +2 -175
  123. codex_autorunner/routes/settings.py +2 -168
  124. codex_autorunner/routes/shared.py +2 -275
  125. codex_autorunner/routes/system.py +4 -188
  126. codex_autorunner/routes/usage.py +3 -0
  127. codex_autorunner/routes/voice.py +2 -119
  128. codex_autorunner/routes/workspace.py +3 -0
  129. codex_autorunner/server.py +3 -2
  130. codex_autorunner/static/agentControls.js +41 -11
  131. codex_autorunner/static/agentEvents.js +248 -0
  132. codex_autorunner/static/app.js +35 -24
  133. codex_autorunner/static/archive.js +826 -0
  134. codex_autorunner/static/archiveApi.js +37 -0
  135. codex_autorunner/static/autoRefresh.js +36 -8
  136. codex_autorunner/static/bootstrap.js +1 -0
  137. codex_autorunner/static/bus.js +1 -0
  138. codex_autorunner/static/cache.js +1 -0
  139. codex_autorunner/static/constants.js +20 -4
  140. codex_autorunner/static/dashboard.js +344 -325
  141. codex_autorunner/static/diffRenderer.js +37 -0
  142. codex_autorunner/static/docChatCore.js +324 -0
  143. codex_autorunner/static/docChatStorage.js +65 -0
  144. codex_autorunner/static/docChatVoice.js +65 -0
  145. codex_autorunner/static/docEditor.js +133 -0
  146. codex_autorunner/static/env.js +1 -0
  147. codex_autorunner/static/eventSummarizer.js +166 -0
  148. codex_autorunner/static/fileChat.js +182 -0
  149. codex_autorunner/static/health.js +155 -0
  150. codex_autorunner/static/hub.js +126 -185
  151. codex_autorunner/static/index.html +839 -863
  152. codex_autorunner/static/liveUpdates.js +1 -0
  153. codex_autorunner/static/loader.js +1 -0
  154. codex_autorunner/static/messages.js +873 -0
  155. codex_autorunner/static/mobileCompact.js +2 -1
  156. codex_autorunner/static/preserve.js +17 -0
  157. codex_autorunner/static/settings.js +149 -217
  158. codex_autorunner/static/smartRefresh.js +52 -0
  159. codex_autorunner/static/styles.css +8850 -3876
  160. codex_autorunner/static/tabs.js +175 -11
  161. codex_autorunner/static/terminal.js +32 -0
  162. codex_autorunner/static/terminalManager.js +34 -59
  163. codex_autorunner/static/ticketChatActions.js +333 -0
  164. codex_autorunner/static/ticketChatEvents.js +16 -0
  165. codex_autorunner/static/ticketChatStorage.js +16 -0
  166. codex_autorunner/static/ticketChatStream.js +264 -0
  167. codex_autorunner/static/ticketEditor.js +844 -0
  168. codex_autorunner/static/ticketVoice.js +9 -0
  169. codex_autorunner/static/tickets.js +1988 -0
  170. codex_autorunner/static/utils.js +43 -3
  171. codex_autorunner/static/voice.js +1 -0
  172. codex_autorunner/static/workspace.js +765 -0
  173. codex_autorunner/static/workspaceApi.js +53 -0
  174. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  175. codex_autorunner/surfaces/__init__.py +5 -0
  176. codex_autorunner/surfaces/cli/__init__.py +6 -0
  177. codex_autorunner/surfaces/cli/cli.py +1224 -0
  178. codex_autorunner/surfaces/cli/codex_cli.py +20 -0
  179. codex_autorunner/surfaces/telegram/__init__.py +3 -0
  180. codex_autorunner/surfaces/web/__init__.py +1 -0
  181. codex_autorunner/surfaces/web/app.py +2019 -0
  182. codex_autorunner/surfaces/web/hub_jobs.py +192 -0
  183. codex_autorunner/surfaces/web/middleware.py +587 -0
  184. codex_autorunner/surfaces/web/pty_session.py +370 -0
  185. codex_autorunner/surfaces/web/review.py +6 -0
  186. codex_autorunner/surfaces/web/routes/__init__.py +78 -0
  187. codex_autorunner/surfaces/web/routes/agents.py +138 -0
  188. codex_autorunner/surfaces/web/routes/analytics.py +277 -0
  189. codex_autorunner/surfaces/web/routes/app_server.py +132 -0
  190. codex_autorunner/surfaces/web/routes/archive.py +357 -0
  191. codex_autorunner/surfaces/web/routes/base.py +615 -0
  192. codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
  193. codex_autorunner/surfaces/web/routes/flows.py +1164 -0
  194. codex_autorunner/surfaces/web/routes/messages.py +459 -0
  195. codex_autorunner/surfaces/web/routes/repos.py +197 -0
  196. codex_autorunner/surfaces/web/routes/review.py +148 -0
  197. codex_autorunner/surfaces/web/routes/sessions.py +176 -0
  198. codex_autorunner/surfaces/web/routes/settings.py +169 -0
  199. codex_autorunner/surfaces/web/routes/shared.py +280 -0
  200. codex_autorunner/surfaces/web/routes/system.py +196 -0
  201. codex_autorunner/surfaces/web/routes/usage.py +89 -0
  202. codex_autorunner/surfaces/web/routes/voice.py +120 -0
  203. codex_autorunner/surfaces/web/routes/workspace.py +271 -0
  204. codex_autorunner/surfaces/web/runner_manager.py +25 -0
  205. codex_autorunner/surfaces/web/schemas.py +417 -0
  206. codex_autorunner/surfaces/web/static_assets.py +490 -0
  207. codex_autorunner/surfaces/web/static_refresh.py +86 -0
  208. codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
  209. codex_autorunner/tickets/__init__.py +27 -0
  210. codex_autorunner/tickets/agent_pool.py +399 -0
  211. codex_autorunner/tickets/files.py +89 -0
  212. codex_autorunner/tickets/frontmatter.py +55 -0
  213. codex_autorunner/tickets/lint.py +102 -0
  214. codex_autorunner/tickets/models.py +97 -0
  215. codex_autorunner/tickets/outbox.py +244 -0
  216. codex_autorunner/tickets/replies.py +179 -0
  217. codex_autorunner/tickets/runner.py +881 -0
  218. codex_autorunner/tickets/spec_ingest.py +77 -0
  219. codex_autorunner/web/__init__.py +5 -1
  220. codex_autorunner/web/app.py +2 -1771
  221. codex_autorunner/web/hub_jobs.py +2 -191
  222. codex_autorunner/web/middleware.py +2 -587
  223. codex_autorunner/web/pty_session.py +2 -369
  224. codex_autorunner/web/runner_manager.py +2 -24
  225. codex_autorunner/web/schemas.py +2 -396
  226. codex_autorunner/web/static_assets.py +4 -484
  227. codex_autorunner/web/static_refresh.py +2 -85
  228. codex_autorunner/web/terminal_sessions.py +2 -77
  229. codex_autorunner/workspace/__init__.py +40 -0
  230. codex_autorunner/workspace/paths.py +335 -0
  231. codex_autorunner-1.1.0.dist-info/METADATA +154 -0
  232. codex_autorunner-1.1.0.dist-info/RECORD +308 -0
  233. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
  234. codex_autorunner/agents/execution/policy.py +0 -292
  235. codex_autorunner/agents/factory.py +0 -52
  236. codex_autorunner/agents/orchestrator.py +0 -358
  237. codex_autorunner/core/doc_chat.py +0 -1446
  238. codex_autorunner/core/snapshot.py +0 -580
  239. codex_autorunner/integrations/github/chatops.py +0 -268
  240. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  241. codex_autorunner/routes/docs.py +0 -381
  242. codex_autorunner/routes/github.py +0 -327
  243. codex_autorunner/routes/runs.py +0 -250
  244. codex_autorunner/spec_ingest.py +0 -812
  245. codex_autorunner/static/docChatActions.js +0 -287
  246. codex_autorunner/static/docChatEvents.js +0 -300
  247. codex_autorunner/static/docChatRender.js +0 -205
  248. codex_autorunner/static/docChatStream.js +0 -361
  249. codex_autorunner/static/docs.js +0 -20
  250. codex_autorunner/static/docsClipboard.js +0 -69
  251. codex_autorunner/static/docsCrud.js +0 -257
  252. codex_autorunner/static/docsDocUpdates.js +0 -62
  253. codex_autorunner/static/docsDrafts.js +0 -16
  254. codex_autorunner/static/docsElements.js +0 -69
  255. codex_autorunner/static/docsInit.js +0 -285
  256. codex_autorunner/static/docsParse.js +0 -160
  257. codex_autorunner/static/docsSnapshot.js +0 -87
  258. codex_autorunner/static/docsSpecIngest.js +0 -263
  259. codex_autorunner/static/docsState.js +0 -127
  260. codex_autorunner/static/docsThreadRegistry.js +0 -44
  261. codex_autorunner/static/docsUi.js +0 -153
  262. codex_autorunner/static/docsVoice.js +0 -56
  263. codex_autorunner/static/github.js +0 -504
  264. codex_autorunner/static/logs.js +0 -678
  265. codex_autorunner/static/review.js +0 -157
  266. codex_autorunner/static/runs.js +0 -418
  267. codex_autorunner/static/snapshot.js +0 -124
  268. codex_autorunner/static/state.js +0 -94
  269. codex_autorunner/static/todoPreview.js +0 -27
  270. codex_autorunner/workspace.py +0 -16
  271. codex_autorunner-0.1.2.dist-info/METADATA +0 -249
  272. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  273. /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
  274. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
  275. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
  276. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,12 @@
1
- import { api, flash, statusPill, confirmModal, openModal } from "./utils.js";
2
- import { subscribe } from "./bus.js";
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ import { api, flash, statusPill } from "./utils.js";
3
3
  import { saveToCache, loadFromCache } from "./cache.js";
4
- import { renderTodoPreview } from "./todoPreview.js";
5
- import { loadState, startRun, stopRun, killRun, resetRun, clearLock, startStatePolling, } from "./state.js";
6
4
  import { registerAutoRefresh } from "./autoRefresh.js";
7
5
  import { CONSTANTS } from "./constants.js";
8
- import { initAgentControls } from "./agentControls.js";
9
- import { initReview } from "./review.js";
6
+ import { preserveScroll } from "./preserve.js";
7
+ import { createSmartRefresh } from "./smartRefresh.js";
10
8
  const UPDATE_STATUS_SEEN_KEY = "car_update_status_seen";
11
- let pendingSummaryOpen = false;
9
+ const ANALYTICS_SUMMARY_CACHE_KEY = "analytics-summary";
12
10
  const usageChartState = {
13
11
  segment: "none",
14
12
  bucket: "day",
@@ -16,119 +14,16 @@ const usageChartState = {
16
14
  };
17
15
  let usageSeriesRetryTimer = null;
18
16
  let usageSummaryRetryTimer = null;
19
- function renderState(state) {
20
- if (!state)
21
- return;
22
- saveToCache("state", state);
23
- const statusEl = document.getElementById("runner-status");
24
- if (statusEl) {
25
- statusPill(statusEl, state.status);
26
- }
27
- const lastRunIdEl = document.getElementById("last-run-id");
28
- if (lastRunIdEl) {
29
- lastRunIdEl.textContent = String(state.last_run_id ?? "–");
30
- }
31
- const lastExitCodeEl = document.getElementById("last-exit-code");
32
- if (lastExitCodeEl) {
33
- lastExitCodeEl.textContent = String(state.last_exit_code ?? "–");
34
- }
35
- const lastStartEl = document.getElementById("last-start");
36
- if (lastStartEl) {
37
- lastStartEl.textContent = state.last_run_started_at ?? "–";
38
- }
39
- const lastFinishEl = document.getElementById("last-finish");
40
- if (lastFinishEl) {
41
- lastFinishEl.textContent = state.last_run_finished_at ?? "–";
42
- }
43
- const todoCountEl = document.getElementById("todo-count");
44
- if (todoCountEl) {
45
- todoCountEl.textContent = String(state.outstanding_count ?? "–");
46
- }
47
- const doneCountEl = document.getElementById("done-count");
48
- if (doneCountEl) {
49
- doneCountEl.textContent = String(state.done_count ?? "–");
50
- }
51
- const runnerPidEl = document.getElementById("runner-pid");
52
- if (runnerPidEl) {
53
- runnerPidEl.textContent = `Runner pid: ${state.runner_pid ?? "–"}`;
54
- }
55
- const summaryBtn = document.getElementById("open-summary");
56
- if (summaryBtn) {
57
- const done = Number(state.outstanding_count ?? NaN) === 0;
58
- summaryBtn.classList.toggle("hidden", !done);
59
- }
60
- const clearLockBtn = document.getElementById("clear-lock");
61
- if (clearLockBtn) {
62
- const freeable = Boolean(state.lock_freeable);
63
- clearLockBtn.classList.toggle("hidden", !freeable);
64
- if (state.lock_freeable_reason) {
65
- clearLockBtn.title = state.lock_freeable_reason;
66
- }
67
- }
68
- const status = state.status || "idle";
69
- const startBtn = document.getElementById("start-run");
70
- const stopBtn = document.getElementById("stop-run");
71
- const killBtn = document.getElementById("kill-run");
72
- const resetBtn = document.getElementById("reset-runner");
73
- if (status === "idle" || status === "stopped" || status === "completed") {
74
- if (startBtn)
75
- startBtn.classList.remove("hidden");
76
- if (stopBtn)
77
- stopBtn.classList.add("hidden");
78
- if (killBtn)
79
- killBtn.classList.add("hidden");
80
- if (resetBtn)
81
- resetBtn.classList.remove("hidden");
82
- }
83
- else if (status === "running") {
84
- if (startBtn)
85
- startBtn.classList.add("hidden");
86
- if (stopBtn)
87
- stopBtn.classList.remove("hidden");
88
- if (killBtn)
89
- killBtn.classList.remove("hidden");
90
- if (resetBtn)
91
- resetBtn.classList.add("hidden");
92
- }
93
- else if (status === "error") {
94
- if (startBtn)
95
- startBtn.classList.remove("hidden");
96
- if (stopBtn)
97
- stopBtn.classList.add("hidden");
98
- if (killBtn)
99
- killBtn.classList.remove("hidden");
100
- if (resetBtn)
101
- resetBtn.classList.remove("hidden");
102
- }
103
- }
104
- function updateTodoPreview(content) {
105
- renderTodoPreview(content || "");
106
- if (content !== undefined) {
107
- saveToCache("todo-doc", content || "");
108
- }
109
- }
110
- function handleDocsEvent(payload) {
111
- if (!payload)
112
- return;
113
- if (payload.kind === "todo") {
114
- updateTodoPreview(payload.content || "");
115
- return;
116
- }
117
- if (typeof payload.todo === "string") {
118
- updateTodoPreview(payload.todo);
119
- }
17
+ const DASHBOARD_REFRESH_REASONS = ["initial", "background", "manual"];
18
+ function normalizeRefreshReason(reason) {
19
+ if (reason && DASHBOARD_REFRESH_REASONS.includes(reason))
20
+ return reason;
21
+ return "manual";
120
22
  }
121
- async function loadTodoPreview(options = {}) {
122
- const { silent = false } = options;
123
- try {
124
- const data = await api("/api/docs");
125
- updateTodoPreview(data?.todo || "");
126
- }
127
- catch (err) {
128
- if (!silent) {
129
- flash(err.message || "Failed to load TODO preview", "error");
130
- }
131
- }
23
+ function mapAutoRefreshReason(ctx) {
24
+ if (ctx.reason === "manual")
25
+ return "manual";
26
+ return "background";
132
27
  }
133
28
  function setUsageLoading(loading) {
134
29
  const btn = document.getElementById("usage-refresh");
@@ -178,6 +73,27 @@ function renderUsageProgressBar(container, percent, windowMinutes) {
178
73
  <span class="usage-progress-label">${hasData ? `${pct}%` : "–"}${windowMinutes ? `/${windowMinutes}m` : ""}</span>
179
74
  `;
180
75
  }
76
+ function usageSummarySignature(data) {
77
+ if (!data)
78
+ return "none";
79
+ const totals = data.totals || {};
80
+ const primary = data.latest_rate_limits?.primary || {};
81
+ const secondary = data.latest_rate_limits?.secondary || {};
82
+ return [
83
+ data.status || "",
84
+ data.events ?? "",
85
+ data.codex_home ?? "",
86
+ totals.total_tokens ?? "",
87
+ totals.input_tokens ?? "",
88
+ totals.cached_input_tokens ?? "",
89
+ totals.output_tokens ?? "",
90
+ totals.reasoning_output_tokens ?? "",
91
+ primary.used_percent ?? "",
92
+ primary.window_minutes ?? "",
93
+ secondary.used_percent ?? "",
94
+ secondary.window_minutes ?? "",
95
+ ].join("|");
96
+ }
181
97
  function renderUsage(data) {
182
98
  if (data)
183
99
  saveToCache("usage", data);
@@ -226,6 +142,235 @@ function renderUsage(data) {
226
142
  if (metaEl)
227
143
  metaEl.textContent = codexHome;
228
144
  }
145
+ function analyticsSummarySignature(data) {
146
+ if (!data)
147
+ return "none";
148
+ const run = data.run;
149
+ const tickets = data.tickets;
150
+ const turns = data.turns;
151
+ const agent = data.agent;
152
+ return [
153
+ run?.id ?? "",
154
+ run?.short_id ?? "",
155
+ run?.status ?? "",
156
+ run?.started_at ?? "",
157
+ run?.finished_at ?? "",
158
+ run?.duration_seconds ?? "",
159
+ run?.current_step ?? "",
160
+ tickets?.todo_count ?? "",
161
+ tickets?.done_count ?? "",
162
+ tickets?.total_count ?? "",
163
+ tickets?.current_ticket ?? "",
164
+ turns?.total ?? "",
165
+ turns?.current_ticket ?? "",
166
+ turns?.dispatches ?? "",
167
+ turns?.replies ?? "",
168
+ turns?.diff_stats?.insertions ?? "",
169
+ turns?.diff_stats?.deletions ?? "",
170
+ agent?.id ?? "",
171
+ agent?.model ?? "",
172
+ ].join("|");
173
+ }
174
+ const usageSummaryRefresh = createSmartRefresh({
175
+ getSignature: (payload) => usageSummarySignature(payload),
176
+ render: (payload) => {
177
+ renderUsage(payload);
178
+ },
179
+ });
180
+ const analyticsSummaryRefresh = createSmartRefresh({
181
+ getSignature: (payload) => analyticsSummarySignature(payload),
182
+ render: (payload) => {
183
+ renderTicketAnalytics(payload);
184
+ },
185
+ });
186
+ function runHistorySignature(runs) {
187
+ if (!runs.length)
188
+ return "none";
189
+ return runs
190
+ .map((run) => [
191
+ run.id,
192
+ run.status || "",
193
+ run.current_step || "",
194
+ run.created_at || "",
195
+ run.started_at || "",
196
+ run.finished_at || "",
197
+ ].join(":"))
198
+ .join("|");
199
+ }
200
+ const runHistoryRefresh = createSmartRefresh({
201
+ getSignature: (payload) => runHistorySignature(payload),
202
+ render: (payload) => {
203
+ renderRunHistory(payload);
204
+ },
205
+ });
206
+ async function fetchTicketAnalytics() {
207
+ try {
208
+ const data = (await api("/api/analytics/summary"));
209
+ saveToCache(ANALYTICS_SUMMARY_CACHE_KEY, data);
210
+ return data;
211
+ }
212
+ catch (err) {
213
+ const cached = loadFromCache(ANALYTICS_SUMMARY_CACHE_KEY);
214
+ flash(err.message || "Failed to load analytics", "error");
215
+ if (cached)
216
+ return cached;
217
+ return null;
218
+ }
219
+ }
220
+ async function loadTicketAnalytics(reason = "manual") {
221
+ const resolvedReason = normalizeRefreshReason(reason);
222
+ try {
223
+ await analyticsSummaryRefresh.refresh(fetchTicketAnalytics, { reason: resolvedReason });
224
+ }
225
+ catch (err) {
226
+ flash(err.message || "Failed to load analytics", "error");
227
+ }
228
+ }
229
+ function formatDuration(seconds) {
230
+ if (seconds === null || Number.isNaN(seconds))
231
+ return "–";
232
+ if (seconds < 60)
233
+ return `${Math.round(seconds)}s`;
234
+ const mins = seconds / 60;
235
+ if (mins < 60)
236
+ return `${Math.round(mins)}m`;
237
+ const hours = mins / 60;
238
+ return `${hours.toFixed(1)}h`;
239
+ }
240
+ function renderTicketAnalytics(data) {
241
+ const run = data?.run;
242
+ const tickets = data?.tickets;
243
+ const turns = data?.turns;
244
+ const agent = data?.agent;
245
+ const statusEl = document.getElementById("runner-status");
246
+ if (statusEl && run) {
247
+ statusPill(statusEl, run.status || "idle");
248
+ statusEl.textContent = run.status || "idle";
249
+ }
250
+ const lastStart = document.getElementById("last-start");
251
+ const lastFinish = document.getElementById("last-finish");
252
+ const lastDuration = document.getElementById("last-duration");
253
+ const todoCount = document.getElementById("todo-count");
254
+ const doneCount = document.getElementById("done-count");
255
+ const ticketActive = document.getElementById("ticket-active");
256
+ const ticketTurns = document.getElementById("ticket-turns");
257
+ const totalTurns = document.getElementById("total-turns");
258
+ const dispatchesEl = document.getElementById("message-dispatches");
259
+ const repliesEl = document.getElementById("message-replies");
260
+ const runIdEl = document.getElementById("last-run-id");
261
+ if (lastStart)
262
+ lastStart.textContent = formatIso(run?.started_at || null);
263
+ if (lastFinish)
264
+ lastFinish.textContent = formatIso(run?.finished_at || null);
265
+ if (lastDuration)
266
+ lastDuration.textContent = formatDuration(run?.duration_seconds ?? null);
267
+ if (todoCount)
268
+ todoCount.textContent = tickets ? String(tickets.todo_count) : "–";
269
+ if (doneCount)
270
+ doneCount.textContent = tickets ? String(tickets.done_count) : "–";
271
+ if (ticketActive) {
272
+ const ticket = tickets?.current_ticket || null;
273
+ ticketActive.textContent = ticket ? ticket.split("/").pop() || ticket : "–";
274
+ }
275
+ if (ticketTurns)
276
+ ticketTurns.textContent = turns?.current_ticket != null ? String(turns.current_ticket) : "–";
277
+ if (totalTurns)
278
+ totalTurns.textContent = turns?.total != null ? String(turns.total) : "–";
279
+ const dispatchCount = turns?.dispatches ?? 0;
280
+ if (dispatchesEl)
281
+ dispatchesEl.textContent = String(dispatchCount);
282
+ if (repliesEl)
283
+ repliesEl.textContent = turns?.replies != null ? String(turns.replies) : "0";
284
+ if (runIdEl)
285
+ runIdEl.textContent = run?.short_id || run?.id || "–";
286
+ // Diff stats (lines changed)
287
+ const diffStatsEl = document.getElementById("lines-changed");
288
+ if (diffStatsEl) {
289
+ const diffStats = turns?.diff_stats;
290
+ if (diffStats && (diffStats.insertions > 0 || diffStats.deletions > 0)) {
291
+ const ins = diffStats.insertions || 0;
292
+ const del = diffStats.deletions || 0;
293
+ diffStatsEl.innerHTML = `<span class="diff-add">+${formatTokensCompact(ins)}</span> <span class="diff-del">-${formatTokensCompact(del)}</span>`;
294
+ diffStatsEl.title = `${ins} insertions, ${del} deletions, ${diffStats.files_changed || 0} files changed`;
295
+ }
296
+ else {
297
+ diffStatsEl.textContent = "–";
298
+ diffStatsEl.title = "";
299
+ }
300
+ }
301
+ // Agent chip (optional future use)
302
+ const agentEl = document.getElementById("ticket-agent");
303
+ if (agentEl) {
304
+ agentEl.textContent = agent?.id || "–";
305
+ }
306
+ }
307
+ async function fetchRunHistory() {
308
+ const runs = (await api("/api/flows/runs?flow_type=ticket_flow"));
309
+ return Array.isArray(runs) ? runs.slice(0, 10) : [];
310
+ }
311
+ async function loadRunHistory(reason = "manual") {
312
+ const resolvedReason = normalizeRefreshReason(reason);
313
+ try {
314
+ await runHistoryRefresh.refresh(fetchRunHistory, { reason: resolvedReason });
315
+ }
316
+ catch (err) {
317
+ flash(err.message || "Failed to load run history", "error");
318
+ }
319
+ }
320
+ function formatIso(iso) {
321
+ if (!iso)
322
+ return "–";
323
+ const dt = new Date(iso);
324
+ if (Number.isNaN(dt.getTime()))
325
+ return iso;
326
+ return dt.toLocaleString();
327
+ }
328
+ function calcDurationFromRun(run) {
329
+ const started = run.started_at;
330
+ const finished = run.finished_at;
331
+ if (!started)
332
+ return "–";
333
+ const start = new Date(started).getTime();
334
+ const end = finished && !Number.isNaN(new Date(finished).getTime())
335
+ ? new Date(finished).getTime()
336
+ : Date.now();
337
+ if (Number.isNaN(start) || Number.isNaN(end))
338
+ return "–";
339
+ return formatDuration((end - start) / 1000);
340
+ }
341
+ function renderRunHistory(runs) {
342
+ const container = document.getElementById("run-history-list");
343
+ if (!container)
344
+ return;
345
+ preserveScroll(container, () => {
346
+ if (!runs || !runs.length) {
347
+ container.innerHTML = '<div class="muted">No runs yet.</div>';
348
+ return;
349
+ }
350
+ const items = runs.map((run) => {
351
+ const shortId = run.id ? run.id.split("-")[0] : "–";
352
+ const status = run.status || "unknown";
353
+ const duration = calcDurationFromRun(run);
354
+ const started = formatIso(run.started_at);
355
+ const current = run.current_step || "–";
356
+ return `
357
+ <div class="run-history-row">
358
+ <div class="run-history-id">${shortId}</div>
359
+ <div class="run-history-status">${status}</div>
360
+ <div class="run-history-duration">${duration}</div>
361
+ <div class="run-history-start">${started}</div>
362
+ <div class="run-history-step">${current}</div>
363
+ </div>
364
+ `;
365
+ });
366
+ container.innerHTML = `
367
+ <div class="run-history-head">
368
+ <div>ID</div><div>Status</div><div>Duration</div><div>Started</div><div>Step</div>
369
+ </div>
370
+ ${items.join("")}
371
+ `;
372
+ }, { restoreOnNextFrame: true });
373
+ }
229
374
  function buildUsageSeriesQuery() {
230
375
  const params = new URLSearchParams();
231
376
  const now = new Date();
@@ -237,6 +382,31 @@ function buildUsageSeriesQuery() {
237
382
  params.set("segment", usageChartState.segment);
238
383
  return params.toString();
239
384
  }
385
+ function usageSeriesSignature(data) {
386
+ if (!data)
387
+ return "none";
388
+ const buckets = data.buckets || [];
389
+ const series = data.series || [];
390
+ const seriesSig = series
391
+ .map((entry) => {
392
+ const values = entry.values || [];
393
+ return [
394
+ entry.key ?? "",
395
+ entry.model ?? "",
396
+ entry.token_type ?? "",
397
+ entry.total ?? "",
398
+ values.join(","),
399
+ ].join(":");
400
+ })
401
+ .join("|");
402
+ return `${data.status || ""}::${buckets.join(",")}::${seriesSig}`;
403
+ }
404
+ const usageSeriesRefresh = createSmartRefresh({
405
+ getSignature: (payload) => usageSeriesSignature(payload),
406
+ render: (payload) => {
407
+ renderUsageChart(payload);
408
+ },
409
+ });
240
410
  function renderUsageChart(data) {
241
411
  const container = document.getElementById("usage-chart-canvas");
242
412
  if (!container)
@@ -263,6 +433,7 @@ function renderUsageChart(data) {
263
433
  "#84d1ff",
264
434
  "#9be26f",
265
435
  "#f2a0c5",
436
+ "#373",
266
437
  ];
267
438
  const { series: displaySeries } = normalizeSeries(limitSeries(series, 4, "rest").series, buckets.length);
268
439
  let scaleMax = 1;
@@ -445,12 +616,13 @@ function attachUsageChartInteraction(container, state) {
445
616
  tooltip.style.opacity = "0";
446
617
  });
447
618
  }
448
- async function loadUsageSeries() {
619
+ async function loadUsageSeries(reason = "manual") {
620
+ const resolvedReason = normalizeRefreshReason(reason);
449
621
  const container = document.getElementById("usage-chart-canvas");
450
622
  try {
451
623
  const data = await api(`/api/usage/series?${buildUsageSeriesQuery()}`);
452
624
  setChartLoading(container, data?.status === "loading");
453
- renderUsageChart(data);
625
+ await usageSeriesRefresh.refresh(async () => data, { reason: resolvedReason });
454
626
  if (data?.status === "loading") {
455
627
  scheduleUsageSeriesRetry();
456
628
  }
@@ -460,7 +632,7 @@ async function loadUsageSeries() {
460
632
  }
461
633
  catch (err) {
462
634
  setChartLoading(container, false);
463
- renderUsageChart(null);
635
+ await usageSeriesRefresh.refresh(async () => null, { reason: resolvedReason });
464
636
  clearUsageSeriesRetry();
465
637
  }
466
638
  }
@@ -488,153 +660,52 @@ function clearUsageSummaryRetry() {
488
660
  usageSummaryRetryTimer = null;
489
661
  }
490
662
  }
491
- async function loadUsage() {
492
- setUsageLoading(true);
663
+ async function fetchUsageSummary() {
493
664
  try {
494
665
  const data = await api("/api/usage");
495
666
  const cachedUsage = loadFromCache("usage");
496
667
  const hasSummary = data && data.totals && typeof data.events === "number";
497
668
  if (data?.status === "loading") {
498
669
  if (hasSummary) {
499
- renderUsage(data);
500
- }
501
- else if (cachedUsage) {
502
- renderUsage(cachedUsage);
670
+ scheduleUsageSummaryRetry();
671
+ return data;
503
672
  }
504
- else {
505
- renderUsage(data);
673
+ if (cachedUsage) {
674
+ scheduleUsageSummaryRetry();
675
+ return cachedUsage;
506
676
  }
507
677
  scheduleUsageSummaryRetry();
678
+ return data;
508
679
  }
509
- else {
510
- renderUsage(data);
511
- clearUsageSummaryRetry();
512
- }
513
- loadUsageSeries();
680
+ clearUsageSummaryRetry();
681
+ return data;
514
682
  }
515
683
  catch (err) {
516
684
  const cachedUsage = loadFromCache("usage");
517
- if (cachedUsage) {
518
- renderUsage(cachedUsage);
519
- }
520
- else {
521
- renderUsage(null);
522
- }
523
685
  flash(err.message || "Failed to load usage", "error");
524
686
  clearUsageSummaryRetry();
525
- }
526
- finally {
527
- setUsageLoading(false);
687
+ if (cachedUsage) {
688
+ return cachedUsage;
689
+ }
690
+ return null;
528
691
  }
529
692
  }
530
- const UPDATE_TARGET_LABELS = {
531
- both: "web + Telegram",
532
- web: "web only",
533
- telegram: "Telegram only",
534
- };
535
- function normalizeUpdateTarget(value) {
536
- if (!value)
537
- return "both";
538
- if (value === "both" || value === "web" || value === "telegram")
539
- return value;
540
- return "both";
541
- }
542
- function getUpdateTarget(selectId) {
543
- const select = selectId ? document.getElementById(selectId) : null;
544
- return normalizeUpdateTarget(select ? select.value : "both");
545
- }
546
- function describeUpdateTarget(target) {
547
- return UPDATE_TARGET_LABELS[target] || UPDATE_TARGET_LABELS.both;
548
- }
549
- async function handleSystemUpdate(btnId, targetSelectId) {
550
- const btn = document.getElementById(btnId);
551
- if (!btn)
552
- return;
553
- const originalText = btn.textContent;
554
- btn.disabled = true;
555
- btn.textContent = "Checking...";
556
- const updateTarget = getUpdateTarget(targetSelectId);
557
- const targetLabel = describeUpdateTarget(updateTarget);
558
- let check;
693
+ async function loadUsage(reason = "manual") {
694
+ const resolvedReason = normalizeRefreshReason(reason);
695
+ const showLoading = resolvedReason !== "background";
696
+ if (showLoading)
697
+ setUsageLoading(true);
559
698
  try {
560
- check = await api("/system/update/check");
699
+ await usageSummaryRefresh.refresh(fetchUsageSummary, { reason: resolvedReason });
561
700
  }
562
701
  catch (err) {
563
- check = { update_available: true, message: err.message || "Unable to check for updates." };
564
- }
565
- if (!check?.update_available) {
566
- flash(check?.message || "No update available.", "info");
567
- btn.disabled = false;
568
- btn.textContent = originalText;
569
- return;
570
- }
571
- const restartNotice = updateTarget === "telegram"
572
- ? "The Telegram bot will restart."
573
- : "The service will restart.";
574
- const confirmed = await confirmModal(`${check?.message || "Update available."} Update Codex Autorunner (${targetLabel})? ${restartNotice}`);
575
- if (!confirmed) {
576
- btn.disabled = false;
577
- btn.textContent = originalText;
578
- return;
579
- }
580
- btn.textContent = "Updating...";
581
- try {
582
- const res = await api("/system/update", {
583
- method: "POST",
584
- body: { target: updateTarget },
585
- });
586
- flash(res.message || `Update started (${targetLabel}).`, "success");
587
- if (updateTarget === "telegram") {
588
- btn.disabled = false;
589
- btn.textContent = originalText;
590
- return;
591
- }
592
- document.body.style.pointerEvents = "none";
593
- setTimeout(() => {
594
- const url = new URL(window.location.href);
595
- url.searchParams.set("v", String(Date.now()));
596
- window.location.replace(url.toString());
597
- }, 8000);
598
- }
599
- catch (err) {
600
- flash(err.message || "Update failed", "error");
601
- btn.disabled = false;
602
- btn.textContent = originalText;
603
- }
604
- }
605
- function initSettings() {
606
- const settingsBtn = document.getElementById("repo-settings");
607
- const modal = document.getElementById("repo-settings-modal");
608
- const closeBtn = document.getElementById("repo-settings-close");
609
- const updateBtn = document.getElementById("repo-update-btn");
610
- const updateTarget = document.getElementById("repo-update-target");
611
- let closeModal = null;
612
- const hideModal = () => {
613
- if (closeModal) {
614
- const close = closeModal;
615
- closeModal = null;
616
- close();
617
- }
618
- };
619
- if (settingsBtn && modal) {
620
- settingsBtn.addEventListener("click", () => {
621
- const triggerEl = document.activeElement;
622
- hideModal();
623
- closeModal = openModal(modal, {
624
- initialFocus: closeBtn || updateBtn || modal,
625
- returnFocusTo: triggerEl,
626
- onRequestClose: hideModal,
627
- });
628
- });
629
- }
630
- if (closeBtn && modal) {
631
- closeBtn.addEventListener("click", () => {
632
- hideModal();
633
- });
702
+ flash(err.message || "Failed to load usage", "error");
634
703
  }
635
- if (updateBtn) {
636
- updateBtn.addEventListener("click", () => handleSystemUpdate("repo-update-btn", updateTarget ? updateTarget.id : null));
704
+ finally {
705
+ if (showLoading)
706
+ setUsageLoading(false);
637
707
  }
708
+ await loadUsageSeries(resolvedReason);
638
709
  }
639
710
  function initUsageChartControls() {
640
711
  const segmentSelect = document.getElementById("usage-chart-segment");
@@ -676,97 +747,45 @@ function bindAction(buttonId, action) {
676
747
  }
677
748
  });
678
749
  }
679
- function isDocsReady() {
680
- return document.body?.dataset?.docsReady === "true";
681
- }
682
- function openSummaryDoc() {
683
- const summaryChip = document.querySelector('.chip[data-doc="summary"]');
684
- if (summaryChip)
685
- summaryChip.click();
686
- }
687
750
  export function initDashboard() {
688
- initSettings();
689
751
  initUsageChartControls();
690
- initReview();
691
- const agentSelect = document.getElementById("dashboard-agent-select");
692
- const modelSelect = document.getElementById("dashboard-model-select");
693
- const reasoningSelect = document.getElementById("dashboard-reasoning-select");
694
- initAgentControls({ agentSelect, modelSelect, reasoningSelect });
695
- const buildRunOverrides = () => {
696
- const overrides = {};
697
- if (agentSelect)
698
- overrides.agent = agentSelect.value;
699
- if (modelSelect)
700
- overrides.model = modelSelect.value;
701
- if (reasoningSelect)
702
- overrides.reasoning = reasoningSelect.value;
703
- return overrides;
704
- };
705
- subscribe("state:update", renderState);
706
- subscribe("todo:invalidate", () => {
707
- void loadTodoPreview({ silent: true });
708
- });
709
- subscribe("docs:updated", handleDocsEvent);
710
- subscribe("docs:loaded", handleDocsEvent);
711
- subscribe("docs:ready", () => {
712
- if (!isDocsReady()) {
713
- if (document.body) {
714
- document.body.dataset.docsReady = "true";
715
- }
716
- }
717
- if (pendingSummaryOpen) {
718
- pendingSummaryOpen = false;
719
- openSummaryDoc();
720
- }
721
- });
722
- bindAction("start-run", () => startRun(false, buildRunOverrides()));
723
- bindAction("stop-run", stopRun);
724
- bindAction("kill-run", killRun);
725
- bindAction("clear-lock", clearLock);
726
- bindAction("reset-runner", async () => {
727
- const confirmed = await confirmModal("Reset runner? This will clear all logs and reset run ID to 1.");
728
- if (confirmed)
729
- await resetRun();
752
+ // initReview(); // Removed - review.ts was deleted
753
+ bindAction("usage-refresh", () => loadUsage("manual"));
754
+ bindAction("analytics-refresh", async () => {
755
+ await loadTicketAnalytics("manual");
756
+ await loadRunHistory("manual");
730
757
  });
731
- bindAction("refresh-state", async () => { await loadState(); });
732
- bindAction("usage-refresh", loadUsage);
733
- bindAction("refresh-preview", loadTodoPreview);
734
- const cachedState = loadFromCache("state");
735
- if (cachedState)
736
- renderState(cachedState);
737
758
  const cachedUsage = loadFromCache("usage");
738
759
  if (cachedUsage)
739
760
  renderUsage(cachedUsage);
740
- const cachedTodo = loadFromCache("todo-doc");
741
- if (typeof cachedTodo === "string") {
742
- updateTodoPreview(cachedTodo);
743
- }
744
- const summaryBtn = document.getElementById("open-summary");
745
- if (summaryBtn) {
746
- summaryBtn.addEventListener("click", () => {
747
- const docsTab = document.querySelector('.tab[data-target="docs"]');
748
- if (docsTab)
749
- docsTab.click();
750
- if (isDocsReady()) {
751
- requestAnimationFrame(openSummaryDoc);
752
- }
753
- else {
754
- pendingSummaryOpen = true;
755
- }
756
- });
757
- }
758
- loadUsage();
759
- loadTodoPreview();
761
+ const cachedAnalytics = loadFromCache(ANALYTICS_SUMMARY_CACHE_KEY);
762
+ if (cachedAnalytics)
763
+ renderTicketAnalytics(cachedAnalytics);
764
+ loadUsage("initial");
765
+ loadTicketAnalytics("initial");
766
+ loadRunHistory("initial");
760
767
  loadVersion();
761
768
  checkUpdateStatus();
762
- startStatePolling();
763
769
  registerAutoRefresh("dashboard-usage", {
764
- callback: async () => { await loadUsage(); },
765
- tabId: "dashboard",
770
+ callback: async (ctx) => {
771
+ await loadUsage(mapAutoRefreshReason(ctx));
772
+ },
773
+ tabId: "analytics",
766
774
  interval: CONSTANTS.UI.AUTO_REFRESH_USAGE_INTERVAL,
767
775
  refreshOnActivation: true,
768
776
  immediate: false,
769
777
  });
778
+ registerAutoRefresh("dashboard-analytics", {
779
+ callback: async (ctx) => {
780
+ const reason = mapAutoRefreshReason(ctx);
781
+ await loadTicketAnalytics(reason);
782
+ await loadRunHistory(reason);
783
+ },
784
+ tabId: "analytics",
785
+ interval: CONSTANTS.UI.AUTO_REFRESH_INTERVAL,
786
+ refreshOnActivation: true,
787
+ immediate: true,
788
+ });
770
789
  }
771
790
  async function loadVersion() {
772
791
  const versionEl = document.getElementById("repo-version");