codex-autorunner 1.1.0__py3-none-any.whl → 1.2.1__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 (134) hide show
  1. codex_autorunner/agents/opencode/client.py +113 -4
  2. codex_autorunner/agents/opencode/supervisor.py +4 -0
  3. codex_autorunner/agents/registry.py +17 -7
  4. codex_autorunner/bootstrap.py +219 -1
  5. codex_autorunner/core/__init__.py +17 -1
  6. codex_autorunner/core/about_car.py +124 -11
  7. codex_autorunner/core/app_server_threads.py +6 -0
  8. codex_autorunner/core/config.py +238 -3
  9. codex_autorunner/core/context_awareness.py +39 -0
  10. codex_autorunner/core/docs.py +0 -122
  11. codex_autorunner/core/filebox.py +265 -0
  12. codex_autorunner/core/flows/controller.py +71 -1
  13. codex_autorunner/core/flows/reconciler.py +4 -1
  14. codex_autorunner/core/flows/runtime.py +22 -0
  15. codex_autorunner/core/flows/store.py +61 -9
  16. codex_autorunner/core/flows/transition.py +23 -16
  17. codex_autorunner/core/flows/ux_helpers.py +18 -3
  18. codex_autorunner/core/flows/worker_process.py +32 -6
  19. codex_autorunner/core/hub.py +198 -41
  20. codex_autorunner/core/lifecycle_events.py +253 -0
  21. codex_autorunner/core/path_utils.py +2 -1
  22. codex_autorunner/core/pma_audit.py +224 -0
  23. codex_autorunner/core/pma_context.py +683 -0
  24. codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
  25. codex_autorunner/core/pma_lifecycle.py +527 -0
  26. codex_autorunner/core/pma_queue.py +367 -0
  27. codex_autorunner/core/pma_safety.py +221 -0
  28. codex_autorunner/core/pma_state.py +115 -0
  29. codex_autorunner/core/ports/agent_backend.py +2 -5
  30. codex_autorunner/core/ports/run_event.py +1 -4
  31. codex_autorunner/core/prompt.py +0 -80
  32. codex_autorunner/core/prompts.py +56 -172
  33. codex_autorunner/core/redaction.py +0 -4
  34. codex_autorunner/core/review_context.py +11 -9
  35. codex_autorunner/core/runner_controller.py +35 -33
  36. codex_autorunner/core/runner_state.py +147 -0
  37. codex_autorunner/core/runtime.py +829 -0
  38. codex_autorunner/core/sqlite_utils.py +13 -4
  39. codex_autorunner/core/state.py +7 -10
  40. codex_autorunner/core/state_roots.py +5 -0
  41. codex_autorunner/core/templates/__init__.py +39 -0
  42. codex_autorunner/core/templates/git_mirror.py +234 -0
  43. codex_autorunner/core/templates/provenance.py +56 -0
  44. codex_autorunner/core/templates/scan_cache.py +120 -0
  45. codex_autorunner/core/ticket_linter_cli.py +17 -0
  46. codex_autorunner/core/ticket_manager_cli.py +154 -92
  47. codex_autorunner/core/time_utils.py +11 -0
  48. codex_autorunner/core/types.py +18 -0
  49. codex_autorunner/core/utils.py +34 -6
  50. codex_autorunner/flows/review/service.py +23 -25
  51. codex_autorunner/flows/ticket_flow/definition.py +43 -1
  52. codex_autorunner/integrations/agents/__init__.py +2 -0
  53. codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
  54. codex_autorunner/integrations/agents/codex_backend.py +19 -8
  55. codex_autorunner/integrations/agents/runner.py +3 -8
  56. codex_autorunner/integrations/agents/wiring.py +8 -0
  57. codex_autorunner/integrations/telegram/adapter.py +1 -1
  58. codex_autorunner/integrations/telegram/config.py +1 -1
  59. codex_autorunner/integrations/telegram/doctor.py +228 -6
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
  63. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
  64. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
  65. codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
  66. codex_autorunner/integrations/telegram/handlers/messages.py +34 -3
  67. codex_autorunner/integrations/telegram/helpers.py +1 -3
  68. codex_autorunner/integrations/telegram/runtime.py +9 -4
  69. codex_autorunner/integrations/telegram/service.py +30 -0
  70. codex_autorunner/integrations/telegram/state.py +38 -0
  71. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
  72. codex_autorunner/integrations/telegram/transport.py +10 -3
  73. codex_autorunner/integrations/templates/__init__.py +27 -0
  74. codex_autorunner/integrations/templates/scan_agent.py +312 -0
  75. codex_autorunner/server.py +2 -2
  76. codex_autorunner/static/agentControls.js +21 -5
  77. codex_autorunner/static/app.js +115 -11
  78. codex_autorunner/static/archive.js +274 -81
  79. codex_autorunner/static/archiveApi.js +21 -0
  80. codex_autorunner/static/chatUploads.js +137 -0
  81. codex_autorunner/static/constants.js +1 -1
  82. codex_autorunner/static/docChatCore.js +185 -13
  83. codex_autorunner/static/fileChat.js +68 -40
  84. codex_autorunner/static/fileboxUi.js +159 -0
  85. codex_autorunner/static/hub.js +46 -81
  86. codex_autorunner/static/index.html +303 -24
  87. codex_autorunner/static/messages.js +82 -4
  88. codex_autorunner/static/notifications.js +288 -0
  89. codex_autorunner/static/pma.js +1167 -0
  90. codex_autorunner/static/settings.js +3 -0
  91. codex_autorunner/static/streamUtils.js +57 -0
  92. codex_autorunner/static/styles.css +9141 -6742
  93. codex_autorunner/static/templateReposSettings.js +225 -0
  94. codex_autorunner/static/terminalManager.js +22 -3
  95. codex_autorunner/static/ticketChatActions.js +165 -3
  96. codex_autorunner/static/ticketChatStream.js +17 -119
  97. codex_autorunner/static/ticketEditor.js +41 -13
  98. codex_autorunner/static/ticketTemplates.js +798 -0
  99. codex_autorunner/static/tickets.js +69 -19
  100. codex_autorunner/static/turnEvents.js +27 -0
  101. codex_autorunner/static/turnResume.js +33 -0
  102. codex_autorunner/static/utils.js +28 -0
  103. codex_autorunner/static/workspace.js +258 -44
  104. codex_autorunner/static/workspaceFileBrowser.js +6 -4
  105. codex_autorunner/surfaces/cli/cli.py +1465 -155
  106. codex_autorunner/surfaces/cli/pma_cli.py +817 -0
  107. codex_autorunner/surfaces/web/app.py +253 -49
  108. codex_autorunner/surfaces/web/routes/__init__.py +4 -0
  109. codex_autorunner/surfaces/web/routes/analytics.py +29 -22
  110. codex_autorunner/surfaces/web/routes/archive.py +197 -0
  111. codex_autorunner/surfaces/web/routes/file_chat.py +297 -36
  112. codex_autorunner/surfaces/web/routes/filebox.py +227 -0
  113. codex_autorunner/surfaces/web/routes/flows.py +219 -29
  114. codex_autorunner/surfaces/web/routes/messages.py +70 -39
  115. codex_autorunner/surfaces/web/routes/pma.py +1652 -0
  116. codex_autorunner/surfaces/web/routes/repos.py +1 -1
  117. codex_autorunner/surfaces/web/routes/shared.py +0 -3
  118. codex_autorunner/surfaces/web/routes/templates.py +634 -0
  119. codex_autorunner/surfaces/web/runner_manager.py +2 -2
  120. codex_autorunner/surfaces/web/schemas.py +81 -18
  121. codex_autorunner/tickets/agent_pool.py +27 -0
  122. codex_autorunner/tickets/files.py +33 -16
  123. codex_autorunner/tickets/lint.py +50 -0
  124. codex_autorunner/tickets/models.py +3 -0
  125. codex_autorunner/tickets/outbox.py +41 -5
  126. codex_autorunner/tickets/runner.py +350 -69
  127. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/METADATA +15 -19
  128. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/RECORD +132 -101
  129. codex_autorunner/core/adapter_utils.py +0 -21
  130. codex_autorunner/core/engine.py +0 -3302
  131. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/WHEEL +0 -0
  132. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/entry_points.txt +0 -0
  133. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/licenses/LICENSE +0 -0
  134. {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  // GENERATED FILE - do not edit directly. Source: static_src/
2
- import { api, flash, getUrlParams, resolvePath, statusPill, getAuthToken, openModal, inputModal, setButtonLoading, } from "./utils.js";
2
+ import { api, confirmModal, flash, getUrlParams, resolvePath, statusPill, getAuthToken, openModal, inputModal, setButtonLoading, } from "./utils.js";
3
3
  // Note: activateTab removed - header now used for collapse, not inbox navigation
4
4
  import { registerAutoRefresh } from "./autoRefresh.js";
5
5
  import { CONSTANTS } from "./constants.js";
@@ -837,6 +837,17 @@ function renderTickets(data) {
837
837
  if (!tickets)
838
838
  return;
839
839
  tickets.innerHTML = "";
840
+ // Display lint errors if present
841
+ if (data?.lint_errors && data.lint_errors.length > 0) {
842
+ const lintBanner = document.createElement("div");
843
+ lintBanner.className = "ticket-lint-errors";
844
+ data.lint_errors.forEach((error) => {
845
+ const errorLine = document.createElement("div");
846
+ errorLine.textContent = error;
847
+ lintBanner.appendChild(errorLine);
848
+ });
849
+ tickets.appendChild(lintBanner);
850
+ }
840
851
  const list = (data?.tickets || []);
841
852
  ticketsExist = list.length > 0;
842
853
  // Update progress bar
@@ -870,9 +881,19 @@ function renderTickets(data) {
870
881
  item.title = "Click to edit";
871
882
  item.setAttribute("data-ticket-path", ticket.path || "");
872
883
  // Make ticket item clickable to open editor
873
- item.addEventListener("click", () => {
884
+ item.addEventListener("click", async () => {
874
885
  updateSelectedTicket(ticket.path || null);
875
- openTicketEditor(ticket);
886
+ try {
887
+ if (ticket.index == null) {
888
+ flash("Invalid ticket: missing index", "error");
889
+ return;
890
+ }
891
+ const data = (await api(`/api/flows/ticket_flow/tickets/${ticket.index}`));
892
+ openTicketEditor(data);
893
+ }
894
+ catch (err) {
895
+ flash(`Failed to load ticket: ${err.message}`, "error");
896
+ }
876
897
  });
877
898
  const head = document.createElement("div");
878
899
  head.className = "ticket-item-head";
@@ -928,6 +949,17 @@ function renderTickets(data) {
928
949
  agent.className = "ticket-agent";
929
950
  agent.textContent = fm?.agent || "codex";
930
951
  badges.appendChild(agent);
952
+ // Cumulative diff stats (from FlowStore DIFF_UPDATED aggregation).
953
+ const diffStats = ticket.diff_stats || null;
954
+ if (diffStats && (diffStats.insertions > 0 || diffStats.deletions > 0)) {
955
+ const statsEl = document.createElement("span");
956
+ statsEl.className = "ticket-diff-stats";
957
+ const ins = diffStats.insertions || 0;
958
+ const del = diffStats.deletions || 0;
959
+ statsEl.innerHTML = `<span class="diff-add">+${formatNumber(ins)}</span><span class="diff-del">-${formatNumber(del)}</span>`;
960
+ statsEl.title = `${ins} insertions, ${del} deletions${diffStats.files_changed ? `, ${diffStats.files_changed} files` : ""}`;
961
+ badges.appendChild(statsEl);
962
+ }
931
963
  head.appendChild(badges);
932
964
  item.appendChild(head);
933
965
  if (ticket.errors && ticket.errors.length) {
@@ -1051,7 +1083,10 @@ function renderDispatchHistory(runId, data) {
1051
1083
  contentWrapper.appendChild(header);
1052
1084
  container.append(collapseBar, contentWrapper);
1053
1085
  // Add diff stats if present (for turn summaries)
1054
- const diffStats = dispatch?.extra?.diff_stats;
1086
+ // New path: dispatch.diff_stats (from FlowStore DIFF_UPDATED merge)
1087
+ // Legacy fallback: dispatch.extra.diff_stats (DISPATCH.md frontmatter)
1088
+ const diffStats = (dispatch?.diff_stats ||
1089
+ dispatch?.extra?.diff_stats);
1055
1090
  if (diffStats && (diffStats.insertions || diffStats.deletions)) {
1056
1091
  const statsEl = document.createElement("span");
1057
1092
  statsEl.className = "dispatch-diff-stats";
@@ -1113,10 +1148,18 @@ function renderDispatchHistory(runId, data) {
1113
1148
  if (!att.url)
1114
1149
  return;
1115
1150
  const link = document.createElement("a");
1116
- link.href = resolvePath(att.url);
1151
+ const resolved = new URL(resolvePath(att.url), window.location.origin);
1152
+ link.href = resolved.toString();
1117
1153
  link.textContent = att.name || att.rel_path || "attachment";
1118
- link.target = "_blank";
1119
- link.rel = "noreferrer noopener";
1154
+ // Prefer direct downloads for same-origin attachments.
1155
+ if (resolved.origin === window.location.origin) {
1156
+ link.download = "";
1157
+ link.rel = "noopener";
1158
+ }
1159
+ else {
1160
+ link.target = "_blank";
1161
+ link.rel = "noreferrer noopener";
1162
+ }
1120
1163
  link.title = att.path || "";
1121
1164
  wrap.appendChild(link);
1122
1165
  });
@@ -1185,6 +1228,7 @@ async function loadTicketFiles(ctx) {
1185
1228
  return {
1186
1229
  ticket_dir: data.ticket_dir,
1187
1230
  tickets: data.tickets,
1231
+ lint_errors: data.lint_errors,
1188
1232
  activeTicket: currentActiveTicket,
1189
1233
  flowStatus: currentFlowStatus,
1190
1234
  };
@@ -1204,10 +1248,9 @@ async function loadTicketFiles(ctx) {
1204
1248
  */
1205
1249
  async function openTicketByIndex(index) {
1206
1250
  try {
1207
- const data = (await api("/api/flows/ticket_flow/tickets"));
1208
- const ticket = data.tickets?.find((t) => t.index === index);
1209
- if (ticket) {
1210
- openTicketEditor(ticket);
1251
+ const data = (await api(`/api/flows/ticket_flow/tickets/${index}`));
1252
+ if (data) {
1253
+ openTicketEditor(data);
1211
1254
  }
1212
1255
  else {
1213
1256
  flash(`Ticket TICKET-${String(index).padStart(3, "0")} not found`, "error");
@@ -1714,7 +1757,8 @@ async function restartTicketFlow() {
1714
1757
  flash("Create a ticket first before restarting the flow.", "error");
1715
1758
  return;
1716
1759
  }
1717
- if (!confirm("Restart ticket flow? This will stop the current run and start a new one.")) {
1760
+ const confirmed = await confirmModal("Restart ticket flow? This will stop the current run and start a new one.");
1761
+ if (!confirmed) {
1718
1762
  return;
1719
1763
  }
1720
1764
  setButtonsDisabled(true);
@@ -1754,7 +1798,8 @@ async function archiveTicketFlow() {
1754
1798
  flash("No ticket flow run to archive", "info");
1755
1799
  return;
1756
1800
  }
1757
- if (!confirm("Archive all tickets from this flow? They will be moved to the run's artifact directory.")) {
1801
+ const confirmed = await confirmModal("Archive all tickets from this flow? They will be moved to the run's artifact directory.");
1802
+ if (!confirmed) {
1758
1803
  return;
1759
1804
  }
1760
1805
  setButtonsDisabled(true);
@@ -1869,21 +1914,26 @@ export function initTicketFlow() {
1869
1914
  const { overflowToggle, overflowDropdown, overflowNew, overflowRestart, overflowArchive } = els();
1870
1915
  if (overflowToggle && overflowDropdown) {
1871
1916
  const toggleMenu = (e) => {
1917
+ e.preventDefault();
1872
1918
  e.stopPropagation();
1873
1919
  const isHidden = overflowDropdown.classList.contains("hidden");
1874
1920
  overflowDropdown.classList.toggle("hidden", !isHidden);
1875
1921
  };
1876
- overflowToggle.addEventListener("click", toggleMenu);
1877
- overflowToggle.addEventListener("touchend", (e) => {
1878
- e.preventDefault(); // Prevent ghost click
1879
- toggleMenu(e);
1922
+ const closeMenu = () => overflowDropdown.classList.add("hidden");
1923
+ overflowToggle.addEventListener("pointerdown", toggleMenu);
1924
+ overflowToggle.addEventListener("click", (e) => {
1925
+ e.preventDefault(); // swallow synthetic click after pointerdown
1926
+ });
1927
+ overflowToggle.addEventListener("keydown", (e) => {
1928
+ if (e.key === "Enter" || e.key === " ")
1929
+ toggleMenu(e);
1880
1930
  });
1881
1931
  // Close on outside click
1882
- document.addEventListener("click", (e) => {
1932
+ document.addEventListener("pointerdown", (e) => {
1883
1933
  if (!overflowDropdown.classList.contains("hidden") &&
1884
1934
  !overflowToggle.contains(e.target) &&
1885
1935
  !overflowDropdown.contains(e.target)) {
1886
- overflowDropdown.classList.add("hidden");
1936
+ closeMenu();
1887
1937
  }
1888
1938
  });
1889
1939
  }
@@ -0,0 +1,27 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ import { fetchActiveFileChat, streamTurnEvents } from "./fileChat.js";
3
+ export async function resumeFileChatTurn(clientTurnId, opts = {}) {
4
+ const active = await fetchActiveFileChat(clientTurnId, opts.basePath || "/api/file-chat/active");
5
+ const current = (active.current || {});
6
+ const lastResult = (active.last_result || {});
7
+ if (lastResult.status && opts.onResult) {
8
+ opts.onResult(lastResult);
9
+ }
10
+ const threadId = typeof current.thread_id === "string" ? current.thread_id : "";
11
+ const turnId = typeof current.turn_id === "string" ? current.turn_id : "";
12
+ const agent = typeof current.agent === "string" ? current.agent : "codex";
13
+ if (threadId && turnId) {
14
+ const meta = {
15
+ agent,
16
+ threadId,
17
+ turnId,
18
+ basePath: opts.eventsBasePath || "/api/file-chat/turns",
19
+ };
20
+ const controller = streamTurnEvents(meta, {
21
+ onEvent: opts.onEvent,
22
+ onError: opts.onError,
23
+ });
24
+ return { controller, lastResult };
25
+ }
26
+ return { controller: null, lastResult };
27
+ }
@@ -0,0 +1,33 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ export function loadPendingTurn(key) {
3
+ try {
4
+ const raw = localStorage.getItem(key);
5
+ if (!raw)
6
+ return null;
7
+ const parsed = JSON.parse(raw);
8
+ if (!parsed || typeof parsed !== "object")
9
+ return null;
10
+ if (!parsed.clientTurnId || !parsed.message || !parsed.startedAtMs)
11
+ return null;
12
+ return parsed;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ export function savePendingTurn(key, turn) {
19
+ try {
20
+ localStorage.setItem(key, JSON.stringify(turn));
21
+ }
22
+ catch {
23
+ // ignore
24
+ }
25
+ }
26
+ export function clearPendingTurn(key) {
27
+ try {
28
+ localStorage.removeItem(key);
29
+ }
30
+ catch {
31
+ // ignore
32
+ }
33
+ }
@@ -355,6 +355,34 @@ const FOCUSABLE_SELECTOR = [
355
355
  "[tabindex]:not([tabindex=\"-1\"])",
356
356
  ].join(",");
357
357
  let modalOpenCount = 0;
358
+ export function repairModalBackgroundIfStuck() {
359
+ // Dev reloads / unexpected errors can leave the app background `inert` even when
360
+ // no modal is visible. This makes the whole UI feel "unclickable".
361
+ const openModals = document.querySelectorAll(".modal-overlay:not([hidden])");
362
+ if (openModals.length > 0)
363
+ return false;
364
+ let repaired = false;
365
+ MODAL_BACKGROUND_IDS.forEach((id) => {
366
+ const el = document.getElementById(id);
367
+ if (!el)
368
+ return;
369
+ if (el.hasAttribute("inert") || el.getAttribute("aria-hidden") === "true") {
370
+ repaired = true;
371
+ }
372
+ el.removeAttribute("aria-hidden");
373
+ try {
374
+ el.inert = false;
375
+ }
376
+ catch (_err) {
377
+ // ignore
378
+ }
379
+ el.removeAttribute("inert");
380
+ });
381
+ if (repaired) {
382
+ modalOpenCount = 0;
383
+ }
384
+ return repaired;
385
+ }
358
386
  function getFocusableElements(container) {
359
387
  if (!container || !container.querySelectorAll)
360
388
  return [];