codex-autorunner 0.1.1__py3-none-any.whl → 1.0.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 (226) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/__init__.py +20 -0
  3. codex_autorunner/agents/base.py +2 -2
  4. codex_autorunner/agents/codex/harness.py +1 -1
  5. codex_autorunner/agents/opencode/__init__.py +4 -0
  6. codex_autorunner/agents/opencode/agent_config.py +104 -0
  7. codex_autorunner/agents/opencode/client.py +305 -28
  8. codex_autorunner/agents/opencode/harness.py +71 -20
  9. codex_autorunner/agents/opencode/logging.py +225 -0
  10. codex_autorunner/agents/opencode/run_prompt.py +261 -0
  11. codex_autorunner/agents/opencode/runtime.py +1202 -132
  12. codex_autorunner/agents/opencode/supervisor.py +194 -68
  13. codex_autorunner/agents/registry.py +258 -0
  14. codex_autorunner/agents/types.py +2 -2
  15. codex_autorunner/api.py +25 -0
  16. codex_autorunner/bootstrap.py +19 -40
  17. codex_autorunner/cli.py +234 -151
  18. codex_autorunner/core/about_car.py +44 -32
  19. codex_autorunner/core/adapter_utils.py +21 -0
  20. codex_autorunner/core/app_server_events.py +15 -6
  21. codex_autorunner/core/app_server_logging.py +55 -15
  22. codex_autorunner/core/app_server_prompts.py +28 -259
  23. codex_autorunner/core/app_server_threads.py +15 -26
  24. codex_autorunner/core/circuit_breaker.py +183 -0
  25. codex_autorunner/core/codex_runner.py +6 -0
  26. codex_autorunner/core/config.py +555 -133
  27. codex_autorunner/core/docs.py +54 -9
  28. codex_autorunner/core/drafts.py +82 -0
  29. codex_autorunner/core/engine.py +828 -274
  30. codex_autorunner/core/exceptions.py +60 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +178 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +75 -0
  35. codex_autorunner/core/flows/runtime.py +351 -0
  36. codex_autorunner/core/flows/store.py +485 -0
  37. codex_autorunner/core/flows/transition.py +133 -0
  38. codex_autorunner/core/flows/worker_process.py +242 -0
  39. codex_autorunner/core/hub.py +21 -13
  40. codex_autorunner/core/locks.py +118 -1
  41. codex_autorunner/core/logging_utils.py +9 -6
  42. codex_autorunner/core/path_utils.py +123 -0
  43. codex_autorunner/core/prompt.py +15 -7
  44. codex_autorunner/core/redaction.py +29 -0
  45. codex_autorunner/core/retry.py +61 -0
  46. codex_autorunner/core/review.py +888 -0
  47. codex_autorunner/core/review_context.py +161 -0
  48. codex_autorunner/core/run_index.py +223 -0
  49. codex_autorunner/core/runner_controller.py +44 -1
  50. codex_autorunner/core/runner_process.py +30 -1
  51. codex_autorunner/core/sqlite_utils.py +32 -0
  52. codex_autorunner/core/state.py +273 -44
  53. codex_autorunner/core/static_assets.py +55 -0
  54. codex_autorunner/core/supervisor_utils.py +67 -0
  55. codex_autorunner/core/text_delta_coalescer.py +43 -0
  56. codex_autorunner/core/update.py +20 -11
  57. codex_autorunner/core/update_runner.py +2 -0
  58. codex_autorunner/core/usage.py +107 -75
  59. codex_autorunner/core/utils.py +167 -3
  60. codex_autorunner/discovery.py +3 -3
  61. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  62. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  63. codex_autorunner/integrations/agents/__init__.py +27 -0
  64. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  65. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  66. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  67. codex_autorunner/integrations/agents/run_event.py +71 -0
  68. codex_autorunner/integrations/app_server/client.py +708 -153
  69. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  70. codex_autorunner/integrations/telegram/adapter.py +474 -185
  71. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  72. codex_autorunner/integrations/telegram/config.py +239 -1
  73. codex_autorunner/integrations/telegram/constants.py +19 -1
  74. codex_autorunner/integrations/telegram/dispatch.py +44 -8
  75. codex_autorunner/integrations/telegram/doctor.py +47 -0
  76. codex_autorunner/integrations/telegram/handlers/approvals.py +12 -10
  77. codex_autorunner/integrations/telegram/handlers/callbacks.py +15 -1
  78. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +29 -0
  79. codex_autorunner/integrations/telegram/handlers/commands/approvals.py +173 -0
  80. codex_autorunner/integrations/telegram/handlers/commands/execution.py +2595 -0
  81. codex_autorunner/integrations/telegram/handlers/commands/files.py +1408 -0
  82. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  83. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +81 -0
  84. codex_autorunner/integrations/telegram/handlers/commands/github.py +1688 -0
  85. codex_autorunner/integrations/telegram/handlers/commands/shared.py +190 -0
  86. codex_autorunner/integrations/telegram/handlers/commands/voice.py +112 -0
  87. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +2043 -0
  88. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +954 -5689
  89. codex_autorunner/integrations/telegram/handlers/{commands.py → commands_spec.py} +11 -4
  90. codex_autorunner/integrations/telegram/handlers/messages.py +374 -49
  91. codex_autorunner/integrations/telegram/handlers/questions.py +389 -0
  92. codex_autorunner/integrations/telegram/handlers/selections.py +6 -4
  93. codex_autorunner/integrations/telegram/handlers/utils.py +171 -0
  94. codex_autorunner/integrations/telegram/helpers.py +90 -18
  95. codex_autorunner/integrations/telegram/notifications.py +126 -35
  96. codex_autorunner/integrations/telegram/outbox.py +214 -43
  97. codex_autorunner/integrations/telegram/progress_stream.py +42 -19
  98. codex_autorunner/integrations/telegram/runtime.py +24 -13
  99. codex_autorunner/integrations/telegram/service.py +500 -129
  100. codex_autorunner/integrations/telegram/state.py +1278 -330
  101. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  102. codex_autorunner/integrations/telegram/transport.py +37 -4
  103. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  104. codex_autorunner/integrations/telegram/types.py +22 -2
  105. codex_autorunner/integrations/telegram/voice.py +14 -15
  106. codex_autorunner/manifest.py +2 -0
  107. codex_autorunner/plugin_api.py +22 -0
  108. codex_autorunner/routes/__init__.py +25 -14
  109. codex_autorunner/routes/agents.py +18 -78
  110. codex_autorunner/routes/analytics.py +239 -0
  111. codex_autorunner/routes/base.py +142 -113
  112. codex_autorunner/routes/file_chat.py +836 -0
  113. codex_autorunner/routes/flows.py +980 -0
  114. codex_autorunner/routes/messages.py +459 -0
  115. codex_autorunner/routes/repos.py +17 -0
  116. codex_autorunner/routes/review.py +148 -0
  117. codex_autorunner/routes/sessions.py +16 -8
  118. codex_autorunner/routes/settings.py +22 -0
  119. codex_autorunner/routes/shared.py +33 -3
  120. codex_autorunner/routes/system.py +22 -1
  121. codex_autorunner/routes/usage.py +87 -0
  122. codex_autorunner/routes/voice.py +5 -13
  123. codex_autorunner/routes/workspace.py +271 -0
  124. codex_autorunner/server.py +2 -1
  125. codex_autorunner/static/agentControls.js +9 -1
  126. codex_autorunner/static/agentEvents.js +248 -0
  127. codex_autorunner/static/app.js +27 -22
  128. codex_autorunner/static/autoRefresh.js +29 -1
  129. codex_autorunner/static/bootstrap.js +1 -0
  130. codex_autorunner/static/bus.js +1 -0
  131. codex_autorunner/static/cache.js +1 -0
  132. codex_autorunner/static/constants.js +20 -4
  133. codex_autorunner/static/dashboard.js +162 -150
  134. codex_autorunner/static/diffRenderer.js +37 -0
  135. codex_autorunner/static/docChatCore.js +324 -0
  136. codex_autorunner/static/docChatStorage.js +65 -0
  137. codex_autorunner/static/docChatVoice.js +65 -0
  138. codex_autorunner/static/docEditor.js +133 -0
  139. codex_autorunner/static/env.js +1 -0
  140. codex_autorunner/static/eventSummarizer.js +166 -0
  141. codex_autorunner/static/fileChat.js +182 -0
  142. codex_autorunner/static/health.js +155 -0
  143. codex_autorunner/static/hub.js +67 -126
  144. codex_autorunner/static/index.html +788 -807
  145. codex_autorunner/static/liveUpdates.js +59 -0
  146. codex_autorunner/static/loader.js +1 -0
  147. codex_autorunner/static/messages.js +470 -0
  148. codex_autorunner/static/mobileCompact.js +2 -1
  149. codex_autorunner/static/settings.js +24 -205
  150. codex_autorunner/static/styles.css +7577 -3758
  151. codex_autorunner/static/tabs.js +28 -5
  152. codex_autorunner/static/terminal.js +14 -0
  153. codex_autorunner/static/terminalManager.js +53 -59
  154. codex_autorunner/static/ticketChatActions.js +333 -0
  155. codex_autorunner/static/ticketChatEvents.js +16 -0
  156. codex_autorunner/static/ticketChatStorage.js +16 -0
  157. codex_autorunner/static/ticketChatStream.js +264 -0
  158. codex_autorunner/static/ticketEditor.js +750 -0
  159. codex_autorunner/static/ticketVoice.js +9 -0
  160. codex_autorunner/static/tickets.js +1315 -0
  161. codex_autorunner/static/utils.js +32 -3
  162. codex_autorunner/static/voice.js +21 -7
  163. codex_autorunner/static/workspace.js +672 -0
  164. codex_autorunner/static/workspaceApi.js +53 -0
  165. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  166. codex_autorunner/tickets/__init__.py +20 -0
  167. codex_autorunner/tickets/agent_pool.py +377 -0
  168. codex_autorunner/tickets/files.py +85 -0
  169. codex_autorunner/tickets/frontmatter.py +55 -0
  170. codex_autorunner/tickets/lint.py +102 -0
  171. codex_autorunner/tickets/models.py +95 -0
  172. codex_autorunner/tickets/outbox.py +232 -0
  173. codex_autorunner/tickets/replies.py +179 -0
  174. codex_autorunner/tickets/runner.py +823 -0
  175. codex_autorunner/tickets/spec_ingest.py +77 -0
  176. codex_autorunner/voice/capture.py +7 -7
  177. codex_autorunner/voice/service.py +51 -9
  178. codex_autorunner/web/app.py +419 -199
  179. codex_autorunner/web/hub_jobs.py +13 -2
  180. codex_autorunner/web/middleware.py +47 -13
  181. codex_autorunner/web/pty_session.py +26 -13
  182. codex_autorunner/web/schemas.py +114 -109
  183. codex_autorunner/web/static_assets.py +55 -42
  184. codex_autorunner/web/static_refresh.py +86 -0
  185. codex_autorunner/workspace/__init__.py +40 -0
  186. codex_autorunner/workspace/paths.py +319 -0
  187. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +20 -21
  188. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  189. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  190. codex_autorunner/core/doc_chat.py +0 -1415
  191. codex_autorunner/core/snapshot.py +0 -580
  192. codex_autorunner/integrations/github/chatops.py +0 -268
  193. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  194. codex_autorunner/routes/docs.py +0 -381
  195. codex_autorunner/routes/github.py +0 -327
  196. codex_autorunner/routes/runs.py +0 -118
  197. codex_autorunner/spec_ingest.py +0 -788
  198. codex_autorunner/static/docChatActions.js +0 -279
  199. codex_autorunner/static/docChatEvents.js +0 -300
  200. codex_autorunner/static/docChatRender.js +0 -205
  201. codex_autorunner/static/docChatStream.js +0 -361
  202. codex_autorunner/static/docs.js +0 -20
  203. codex_autorunner/static/docsClipboard.js +0 -69
  204. codex_autorunner/static/docsCrud.js +0 -257
  205. codex_autorunner/static/docsDocUpdates.js +0 -62
  206. codex_autorunner/static/docsDrafts.js +0 -16
  207. codex_autorunner/static/docsElements.js +0 -69
  208. codex_autorunner/static/docsInit.js +0 -274
  209. codex_autorunner/static/docsParse.js +0 -160
  210. codex_autorunner/static/docsSnapshot.js +0 -87
  211. codex_autorunner/static/docsSpecIngest.js +0 -263
  212. codex_autorunner/static/docsState.js +0 -127
  213. codex_autorunner/static/docsThreadRegistry.js +0 -44
  214. codex_autorunner/static/docsUi.js +0 -153
  215. codex_autorunner/static/docsVoice.js +0 -56
  216. codex_autorunner/static/github.js +0 -442
  217. codex_autorunner/static/logs.js +0 -640
  218. codex_autorunner/static/runs.js +0 -409
  219. codex_autorunner/static/snapshot.js +0 -124
  220. codex_autorunner/static/state.js +0 -86
  221. codex_autorunner/static/todoPreview.js +0 -27
  222. codex_autorunner/workspace.py +0 -16
  223. codex_autorunner-0.1.1.dist-info/RECORD +0 -191
  224. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  225. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  226. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,18 +1,15 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { api, flash, statusPill, resolvePath, escapeHtml, confirmModal, inputModal, openModal, } from "./utils.js";
2
3
  import { registerAutoRefresh } from "./autoRefresh.js";
3
- import { CONSTANTS } from "./constants.js";
4
4
  import { HUB_BASE } from "./env.js";
5
5
  let hubData = { repos: [], last_scan_at: null };
6
- const repoPrCache = new Map();
7
- const repoPrFetches = new Set();
8
6
  const prefetchedUrls = new Set();
9
7
  const HUB_CACHE_TTL_MS = 30000;
10
8
  const HUB_CACHE_KEY = `car:hub:${HUB_BASE || "/"}`;
11
9
  const HUB_USAGE_CACHE_KEY = `car:hub-usage:${HUB_BASE || "/"}`;
12
- const PR_CACHE_TTL_MS = 120000;
13
- const PR_FAILURE_TTL_MS = 15000;
14
- const PR_FETCH_CONCURRENCY = 3;
15
- const PR_PREFETCH_MARGIN = "200px";
10
+ const HUB_REFRESH_ACTIVE_MS = 5000;
11
+ const HUB_REFRESH_IDLE_MS = 30000;
12
+ let lastHubAutoRefreshAt = 0;
16
13
  const repoListEl = document.getElementById("hub-repo-list");
17
14
  const lastScanEl = document.getElementById("hub-last-scan");
18
15
  const totalEl = document.getElementById("hub-count-total");
@@ -25,6 +22,8 @@ const hubUsageChartCanvas = document.getElementById("hub-usage-chart-canvas");
25
22
  const hubUsageChartRange = document.getElementById("hub-usage-chart-range");
26
23
  const hubUsageChartSegment = document.getElementById("hub-usage-chart-segment");
27
24
  const hubVersionEl = document.getElementById("hub-version");
25
+ const hubInboxList = document.getElementById("hub-inbox-list");
26
+ const hubInboxRefresh = document.getElementById("hub-inbox-refresh");
28
27
  const UPDATE_STATUS_SEEN_KEY = "car_update_status_seen";
29
28
  const HUB_JOB_POLL_INTERVAL_MS = 1200;
30
29
  const HUB_JOB_TIMEOUT_MS = 180000;
@@ -35,10 +34,6 @@ const hubUsageChartState = {
35
34
  };
36
35
  let hubUsageSeriesRetryTimer = null;
37
36
  let hubUsageSummaryRetryTimer = null;
38
- const repoPrPending = new Set();
39
- const repoPrQueue = [];
40
- let repoPrActive = 0;
41
- let repoPrObserver = null;
42
37
  function saveSessionCache(key, value) {
43
38
  try {
44
39
  const payload = { at: Date.now(), value };
@@ -65,7 +60,7 @@ function loadSessionCache(key, maxAgeMs) {
65
60
  }
66
61
  }
67
62
  function formatRunSummary(repo) {
68
- if (!repo.initialzed)
63
+ if (!repo.initialized)
69
64
  return "Not initialized";
70
65
  if (!repo.exists_on_disk)
71
66
  return "Missing on disk";
@@ -77,7 +72,7 @@ function formatRunSummary(repo) {
77
72
  return `#${repo.last_run_id}${exit}`;
78
73
  }
79
74
  function formatLastActivity(repo) {
80
- if (!repo.initialzed)
75
+ if (!repo.initialized)
81
76
  return "";
82
77
  const time = repo.last_run_finished_at || repo.last_run_started_at;
83
78
  if (!time)
@@ -683,11 +678,11 @@ function buildActions(repo) {
683
678
  else if (!missing && repo.init_error) {
684
679
  actions.push({
685
680
  key: "init",
686
- label: repo.initialzed ? "Re-init" : "Init",
681
+ label: repo.initialized ? "Re-init" : "Init",
687
682
  kind: "primary",
688
683
  });
689
684
  }
690
- else if (!missing && !repo.initialzed) {
685
+ else if (!missing && !repo.initialized) {
691
686
  actions.push({ key: "init", label: "Init", kind: "primary" });
692
687
  }
693
688
  if (!missing && kind === "base") {
@@ -756,76 +751,9 @@ function inferBaseId(repo) {
756
751
  }
757
752
  return null;
758
753
  }
759
- function initRepoPrObserver() {
760
- if (!("IntersectionObserver" in window))
761
- return null;
762
- if (repoPrObserver)
763
- return repoPrObserver;
764
- repoPrObserver = new IntersectionObserver((entries) => {
765
- entries.forEach((entry) => {
766
- if (!entry.isIntersecting)
767
- return;
768
- const target = entry.target;
769
- const repoId = target?.dataset?.repoId;
770
- if (repoId) {
771
- const repo = (hubData.repos || []).find((item) => item.id === repoId);
772
- if (repo)
773
- scheduleRepoPrFetch(repo);
774
- }
775
- if (target)
776
- repoPrObserver?.unobserve(target);
777
- });
778
- }, { rootMargin: PR_PREFETCH_MARGIN });
779
- return repoPrObserver;
780
- }
781
- function scheduleRepoPrFetch(repo) {
782
- if (!repo || repo.mounted !== true)
783
- return;
784
- const cached = repoPrCache.get(repo.id);
785
- if (cached &&
786
- typeof cached.fetchedAt === "number" &&
787
- Date.now() - cached.fetchedAt <
788
- (cached.failed ? PR_FAILURE_TTL_MS : PR_CACHE_TTL_MS)) {
789
- return;
790
- }
791
- if (repoPrFetches.has(repo.id) || repoPrPending.has(repo.id))
792
- return;
793
- repoPrPending.add(repo.id);
794
- repoPrQueue.push(repo);
795
- pumpRepoPrQueue();
796
- }
797
- function pumpRepoPrQueue() {
798
- while (repoPrActive < PR_FETCH_CONCURRENCY && repoPrQueue.length) {
799
- const repo = repoPrQueue.shift();
800
- if (!repo || repoPrFetches.has(repo.id))
801
- continue;
802
- repoPrPending.delete(repo.id);
803
- repoPrActive += 1;
804
- repoPrFetches.add(repo.id);
805
- api(`/repos/${repo.id}/api/github/pr`, { method: "GET" })
806
- .then((pr) => {
807
- repoPrCache.set(repo.id, { data: pr, fetchedAt: Date.now() });
808
- })
809
- .catch(() => {
810
- repoPrCache.set(repo.id, {
811
- data: null,
812
- fetchedAt: Date.now(),
813
- failed: true,
814
- });
815
- })
816
- .finally(() => {
817
- repoPrFetches.delete(repo.id);
818
- repoPrActive -= 1;
819
- pumpRepoPrQueue();
820
- renderRepos(hubData.repos || []);
821
- });
822
- }
823
- }
824
754
  function renderRepos(repos) {
825
755
  if (!repoListEl)
826
756
  return;
827
- if (repoPrObserver)
828
- repoPrObserver.disconnect();
829
757
  repoListEl.innerHTML = "";
830
758
  if (!repos.length) {
831
759
  repoListEl.innerHTML =
@@ -867,7 +795,7 @@ function renderRepos(repos) {
867
795
  const lockBadge = repo.lock_status && repo.lock_status !== "unlocked"
868
796
  ? `<span class="pill pill-small pill-warn">${escapeHtml(repo.lock_status.replace("_", " "))}</span>`
869
797
  : "";
870
- const initBadge = !repo.initialzed
798
+ const initBadge = !repo.initialized
871
799
  ? '<span class="pill pill-small pill-warn">uninit</span>'
872
800
  : "";
873
801
  let noteText = "";
@@ -900,12 +828,6 @@ function renderRepos(repos) {
900
828
  const infoLine = infoItems.length > 0
901
829
  ? `<span class="hub-repo-info-line">${escapeHtml(infoItems.join(" · "))}</span>`
902
830
  : "";
903
- const prInfo = repoPrCache.get(repo.id)?.data;
904
- const prPill = prInfo?.links?.files
905
- ? `<a class="pill pill-small hub-pr-pill" href="${escapeHtml(prInfo.links.files)}" target="_blank" rel="noopener noreferrer" title="${escapeHtml(prInfo.pr?.title || "Open PR files")}">PR${prInfo.pr?.number
906
- ? ` #${escapeHtml(prInfo.pr.number)}`
907
- : ""}</a>`
908
- : "";
909
831
  card.innerHTML = `
910
832
  <div class="hub-repo-row">
911
833
  <div class="hub-repo-left">
@@ -918,7 +840,6 @@ function renderRepos(repos) {
918
840
  <span class="hub-repo-title">${escapeHtml(repo.display_name)}</span>
919
841
  <div class="hub-repo-subline">
920
842
  ${infoLine}
921
- ${prPill}
922
843
  </div>
923
844
  </div>
924
845
  <div class="hub-repo-right">
@@ -933,15 +854,6 @@ function renderRepos(repos) {
933
854
  statusPill(statusEl, repo.status);
934
855
  }
935
856
  repoListEl.appendChild(card);
936
- if (repo.mounted === true) {
937
- const observer = initRepoPrObserver();
938
- if (observer) {
939
- observer.observe(card);
940
- }
941
- else {
942
- scheduleRepoPrFetch(repo);
943
- }
944
- }
945
857
  };
946
858
  orderedGroups.forEach((group) => {
947
859
  const repo = group.base;
@@ -978,34 +890,16 @@ function renderRepos(repos) {
978
890
  .forEach((wt) => renderRepoCard(wt, { isWorktreeRow: true }));
979
891
  }
980
892
  }
981
- async function refreshRepoPrCache(repos) {
982
- const mounted = repos.filter((r) => r && r.mounted === true);
983
- if (!mounted.length)
984
- return;
985
- const observer = initRepoPrObserver();
986
- if (observer && repoListEl) {
987
- mounted.forEach((repo) => {
988
- const card = repoListEl.querySelector(`[data-repo-id="${repo.id}"]`);
989
- if (card) {
990
- observer.observe(card);
991
- }
992
- else {
993
- scheduleRepoPrFetch(repo);
994
- }
995
- });
996
- return;
997
- }
998
- mounted.forEach((repo) => scheduleRepoPrFetch(repo));
999
- }
1000
893
  async function refreshHub() {
1001
894
  setButtonLoading(true);
1002
895
  try {
1003
896
  const data = await api("/hub/repos", { method: "GET" });
1004
897
  hubData = data;
898
+ markHubRefreshed();
1005
899
  saveSessionCache(HUB_CACHE_KEY, hubData);
1006
900
  renderSummary(data.repos || []);
1007
901
  renderRepos(data.repos || []);
1008
- await refreshRepoPrCache(data.repos || []).catch(() => { });
902
+ await loadHubInbox().catch(() => { });
1009
903
  await loadHubUsage().catch(() => { });
1010
904
  }
1011
905
  catch (err) {
@@ -1015,6 +909,40 @@ async function refreshHub() {
1015
909
  setButtonLoading(false);
1016
910
  }
1017
911
  }
912
+ async function loadHubInbox() {
913
+ if (!hubInboxList)
914
+ return;
915
+ hubInboxList.innerHTML = "Loading…";
916
+ try {
917
+ const payload = (await api("/hub/messages", { method: "GET" }));
918
+ const items = payload?.items || [];
919
+ if (!items.length) {
920
+ hubInboxList.innerHTML = '<div class="muted">No paused runs</div>';
921
+ return;
922
+ }
923
+ hubInboxList.innerHTML = items
924
+ .map((item) => {
925
+ const title = item.message?.title || item.message?.mode || "Message";
926
+ const excerpt = item.message?.body ? item.message.body.slice(0, 160) : "";
927
+ const repoLabel = item.repo_display_name || item.repo_id;
928
+ const href = item.open_url || `/repos/${item.repo_id}/?tab=messages&run_id=${item.run_id}`;
929
+ return `
930
+ <a class="hub-inbox-item" href="${escapeHtml(resolvePath(href))}">
931
+ <div class="hub-inbox-item-header">
932
+ <span class="hub-inbox-repo">${escapeHtml(repoLabel)}</span>
933
+ <span class="pill pill-small pill-warn">paused</span>
934
+ </div>
935
+ <div class="hub-inbox-title">${escapeHtml(title)}</div>
936
+ <div class="hub-inbox-excerpt muted small">${escapeHtml(excerpt)}</div>
937
+ </a>
938
+ `;
939
+ })
940
+ .join("");
941
+ }
942
+ catch (_err) {
943
+ hubInboxList.innerHTML = '';
944
+ }
945
+ }
1018
946
  async function triggerHubScan() {
1019
947
  setButtonLoading(true);
1020
948
  try {
@@ -1263,11 +1191,6 @@ function attachHubHandlers() {
1263
1191
  if (repoListEl) {
1264
1192
  repoListEl.addEventListener("click", (event) => {
1265
1193
  const target = event.target;
1266
- const prLink = target instanceof HTMLElement && target.closest("a.hub-pr-pill");
1267
- if (prLink) {
1268
- event.stopPropagation();
1269
- return;
1270
- }
1271
1194
  const btn = target instanceof HTMLElement && target.closest("button[data-action]");
1272
1195
  if (btn) {
1273
1196
  event.stopPropagation();
@@ -1319,6 +1242,7 @@ async function silentRefreshHub() {
1319
1242
  try {
1320
1243
  const data = await api("/hub/repos", { method: "GET" });
1321
1244
  hubData = data;
1245
+ markHubRefreshed();
1322
1246
  saveSessionCache(HUB_CACHE_KEY, hubData);
1323
1247
  renderSummary(data.repos || []);
1324
1248
  renderRepos(data.repos || []);
@@ -1328,6 +1252,20 @@ async function silentRefreshHub() {
1328
1252
  console.error("Auto-refresh hub failed:", err);
1329
1253
  }
1330
1254
  }
1255
+ function markHubRefreshed() {
1256
+ lastHubAutoRefreshAt = Date.now();
1257
+ }
1258
+ function hasActiveRuns(repos) {
1259
+ return repos.some((repo) => repo.status === "running");
1260
+ }
1261
+ async function dynamicRefreshHub() {
1262
+ const now = Date.now();
1263
+ const running = hasActiveRuns(hubData.repos || []);
1264
+ const minInterval = running ? HUB_REFRESH_ACTIVE_MS : HUB_REFRESH_IDLE_MS;
1265
+ if (now - lastHubAutoRefreshAt < minInterval)
1266
+ return;
1267
+ await silentRefreshHub();
1268
+ }
1331
1269
  async function loadHubVersion() {
1332
1270
  if (!hubVersionEl)
1333
1271
  return;
@@ -1369,6 +1307,9 @@ export function initHub() {
1369
1307
  return;
1370
1308
  attachHubHandlers();
1371
1309
  initHubUsageChartControls();
1310
+ hubInboxRefresh?.addEventListener("click", () => {
1311
+ void loadHubInbox();
1312
+ });
1372
1313
  const cachedHub = loadSessionCache(HUB_CACHE_KEY, HUB_CACHE_TTL_MS);
1373
1314
  if (cachedHub) {
1374
1315
  hubData = cachedHub;
@@ -1384,9 +1325,9 @@ export function initHub() {
1384
1325
  loadHubVersion();
1385
1326
  checkUpdateStatus();
1386
1327
  registerAutoRefresh("hub-repos", {
1387
- callback: async () => { await silentRefreshHub(); },
1328
+ callback: async () => { await dynamicRefreshHub(); },
1388
1329
  tabId: null,
1389
- interval: CONSTANTS.UI.AUTO_REFRESH_INTERVAL,
1330
+ interval: HUB_REFRESH_ACTIVE_MS,
1390
1331
  refreshOnActivation: true,
1391
1332
  immediate: false,
1392
1333
  });