codex-autorunner 0.1.2__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 (189) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/opencode/client.py +68 -35
  3. codex_autorunner/agents/opencode/logging.py +21 -5
  4. codex_autorunner/agents/opencode/run_prompt.py +1 -0
  5. codex_autorunner/agents/opencode/runtime.py +118 -30
  6. codex_autorunner/agents/opencode/supervisor.py +36 -48
  7. codex_autorunner/agents/registry.py +136 -8
  8. codex_autorunner/api.py +25 -0
  9. codex_autorunner/bootstrap.py +16 -35
  10. codex_autorunner/cli.py +157 -139
  11. codex_autorunner/core/about_car.py +44 -32
  12. codex_autorunner/core/adapter_utils.py +21 -0
  13. codex_autorunner/core/app_server_logging.py +7 -3
  14. codex_autorunner/core/app_server_prompts.py +27 -260
  15. codex_autorunner/core/app_server_threads.py +15 -26
  16. codex_autorunner/core/codex_runner.py +6 -0
  17. codex_autorunner/core/config.py +390 -100
  18. codex_autorunner/core/docs.py +10 -2
  19. codex_autorunner/core/drafts.py +82 -0
  20. codex_autorunner/core/engine.py +278 -262
  21. codex_autorunner/core/flows/__init__.py +25 -0
  22. codex_autorunner/core/flows/controller.py +178 -0
  23. codex_autorunner/core/flows/definition.py +82 -0
  24. codex_autorunner/core/flows/models.py +75 -0
  25. codex_autorunner/core/flows/runtime.py +351 -0
  26. codex_autorunner/core/flows/store.py +485 -0
  27. codex_autorunner/core/flows/transition.py +133 -0
  28. codex_autorunner/core/flows/worker_process.py +242 -0
  29. codex_autorunner/core/hub.py +15 -9
  30. codex_autorunner/core/locks.py +4 -0
  31. codex_autorunner/core/prompt.py +15 -7
  32. codex_autorunner/core/redaction.py +29 -0
  33. codex_autorunner/core/review_context.py +5 -8
  34. codex_autorunner/core/run_index.py +6 -0
  35. codex_autorunner/core/runner_process.py +5 -2
  36. codex_autorunner/core/state.py +0 -88
  37. codex_autorunner/core/static_assets.py +55 -0
  38. codex_autorunner/core/supervisor_utils.py +67 -0
  39. codex_autorunner/core/update.py +20 -11
  40. codex_autorunner/core/update_runner.py +2 -0
  41. codex_autorunner/core/utils.py +29 -2
  42. codex_autorunner/discovery.py +2 -4
  43. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  44. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  45. codex_autorunner/integrations/agents/__init__.py +27 -0
  46. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  47. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  48. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  49. codex_autorunner/integrations/agents/run_event.py +71 -0
  50. codex_autorunner/integrations/app_server/client.py +576 -92
  51. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  52. codex_autorunner/integrations/telegram/adapter.py +141 -167
  53. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  54. codex_autorunner/integrations/telegram/config.py +175 -0
  55. codex_autorunner/integrations/telegram/constants.py +16 -1
  56. codex_autorunner/integrations/telegram/dispatch.py +17 -0
  57. codex_autorunner/integrations/telegram/doctor.py +47 -0
  58. codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
  59. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
  60. codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
  61. codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
  62. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  63. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
  64. codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
  65. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
  66. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
  67. codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
  68. codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
  69. codex_autorunner/integrations/telegram/helpers.py +88 -16
  70. codex_autorunner/integrations/telegram/outbox.py +208 -37
  71. codex_autorunner/integrations/telegram/progress_stream.py +3 -10
  72. codex_autorunner/integrations/telegram/service.py +214 -40
  73. codex_autorunner/integrations/telegram/state.py +100 -2
  74. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  75. codex_autorunner/integrations/telegram/transport.py +36 -3
  76. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  77. codex_autorunner/manifest.py +2 -0
  78. codex_autorunner/plugin_api.py +22 -0
  79. codex_autorunner/routes/__init__.py +23 -14
  80. codex_autorunner/routes/analytics.py +239 -0
  81. codex_autorunner/routes/base.py +81 -109
  82. codex_autorunner/routes/file_chat.py +836 -0
  83. codex_autorunner/routes/flows.py +980 -0
  84. codex_autorunner/routes/messages.py +459 -0
  85. codex_autorunner/routes/system.py +6 -1
  86. codex_autorunner/routes/usage.py +87 -0
  87. codex_autorunner/routes/workspace.py +271 -0
  88. codex_autorunner/server.py +2 -1
  89. codex_autorunner/static/agentControls.js +1 -0
  90. codex_autorunner/static/agentEvents.js +248 -0
  91. codex_autorunner/static/app.js +25 -22
  92. codex_autorunner/static/autoRefresh.js +29 -1
  93. codex_autorunner/static/bootstrap.js +1 -0
  94. codex_autorunner/static/bus.js +1 -0
  95. codex_autorunner/static/cache.js +1 -0
  96. codex_autorunner/static/constants.js +20 -4
  97. codex_autorunner/static/dashboard.js +162 -196
  98. codex_autorunner/static/diffRenderer.js +37 -0
  99. codex_autorunner/static/docChatCore.js +324 -0
  100. codex_autorunner/static/docChatStorage.js +65 -0
  101. codex_autorunner/static/docChatVoice.js +65 -0
  102. codex_autorunner/static/docEditor.js +133 -0
  103. codex_autorunner/static/env.js +1 -0
  104. codex_autorunner/static/eventSummarizer.js +166 -0
  105. codex_autorunner/static/fileChat.js +182 -0
  106. codex_autorunner/static/health.js +155 -0
  107. codex_autorunner/static/hub.js +41 -118
  108. codex_autorunner/static/index.html +787 -858
  109. codex_autorunner/static/liveUpdates.js +1 -0
  110. codex_autorunner/static/loader.js +1 -0
  111. codex_autorunner/static/messages.js +470 -0
  112. codex_autorunner/static/mobileCompact.js +2 -1
  113. codex_autorunner/static/settings.js +24 -211
  114. codex_autorunner/static/styles.css +7567 -3865
  115. codex_autorunner/static/tabs.js +28 -5
  116. codex_autorunner/static/terminal.js +14 -0
  117. codex_autorunner/static/terminalManager.js +34 -59
  118. codex_autorunner/static/ticketChatActions.js +333 -0
  119. codex_autorunner/static/ticketChatEvents.js +16 -0
  120. codex_autorunner/static/ticketChatStorage.js +16 -0
  121. codex_autorunner/static/ticketChatStream.js +264 -0
  122. codex_autorunner/static/ticketEditor.js +750 -0
  123. codex_autorunner/static/ticketVoice.js +9 -0
  124. codex_autorunner/static/tickets.js +1315 -0
  125. codex_autorunner/static/utils.js +32 -3
  126. codex_autorunner/static/voice.js +1 -0
  127. codex_autorunner/static/workspace.js +672 -0
  128. codex_autorunner/static/workspaceApi.js +53 -0
  129. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  130. codex_autorunner/tickets/__init__.py +20 -0
  131. codex_autorunner/tickets/agent_pool.py +377 -0
  132. codex_autorunner/tickets/files.py +85 -0
  133. codex_autorunner/tickets/frontmatter.py +55 -0
  134. codex_autorunner/tickets/lint.py +102 -0
  135. codex_autorunner/tickets/models.py +95 -0
  136. codex_autorunner/tickets/outbox.py +232 -0
  137. codex_autorunner/tickets/replies.py +179 -0
  138. codex_autorunner/tickets/runner.py +823 -0
  139. codex_autorunner/tickets/spec_ingest.py +77 -0
  140. codex_autorunner/web/app.py +269 -91
  141. codex_autorunner/web/middleware.py +3 -4
  142. codex_autorunner/web/schemas.py +89 -109
  143. codex_autorunner/web/static_assets.py +1 -44
  144. codex_autorunner/workspace/__init__.py +40 -0
  145. codex_autorunner/workspace/paths.py +319 -0
  146. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
  147. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  148. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  149. codex_autorunner/agents/execution/policy.py +0 -292
  150. codex_autorunner/agents/factory.py +0 -52
  151. codex_autorunner/agents/orchestrator.py +0 -358
  152. codex_autorunner/core/doc_chat.py +0 -1446
  153. codex_autorunner/core/snapshot.py +0 -580
  154. codex_autorunner/integrations/github/chatops.py +0 -268
  155. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  156. codex_autorunner/routes/docs.py +0 -381
  157. codex_autorunner/routes/github.py +0 -327
  158. codex_autorunner/routes/runs.py +0 -250
  159. codex_autorunner/spec_ingest.py +0 -812
  160. codex_autorunner/static/docChatActions.js +0 -287
  161. codex_autorunner/static/docChatEvents.js +0 -300
  162. codex_autorunner/static/docChatRender.js +0 -205
  163. codex_autorunner/static/docChatStream.js +0 -361
  164. codex_autorunner/static/docs.js +0 -20
  165. codex_autorunner/static/docsClipboard.js +0 -69
  166. codex_autorunner/static/docsCrud.js +0 -257
  167. codex_autorunner/static/docsDocUpdates.js +0 -62
  168. codex_autorunner/static/docsDrafts.js +0 -16
  169. codex_autorunner/static/docsElements.js +0 -69
  170. codex_autorunner/static/docsInit.js +0 -285
  171. codex_autorunner/static/docsParse.js +0 -160
  172. codex_autorunner/static/docsSnapshot.js +0 -87
  173. codex_autorunner/static/docsSpecIngest.js +0 -263
  174. codex_autorunner/static/docsState.js +0 -127
  175. codex_autorunner/static/docsThreadRegistry.js +0 -44
  176. codex_autorunner/static/docsUi.js +0 -153
  177. codex_autorunner/static/docsVoice.js +0 -56
  178. codex_autorunner/static/github.js +0 -504
  179. codex_autorunner/static/logs.js +0 -678
  180. codex_autorunner/static/review.js +0 -157
  181. codex_autorunner/static/runs.js +0 -418
  182. codex_autorunner/static/snapshot.js +0 -124
  183. codex_autorunner/static/state.js +0 -94
  184. codex_autorunner/static/todoPreview.js +0 -27
  185. codex_autorunner/workspace.py +0 -16
  186. codex_autorunner-0.1.2.dist-info/RECORD +0 -222
  187. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  188. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  189. {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  /**
2
3
  * Auto-refresh utility for managing periodic data fetching.
3
4
  *
@@ -16,6 +17,9 @@ const refreshers = new Map();
16
17
  let activeTab = null;
17
18
  // Track page visibility
18
19
  let pageVisible = true;
20
+ // Global enable/disable toggle (e.g., when repo server is offline)
21
+ let globallyEnabled = true;
22
+ let globalPauseReason = null;
19
23
  /**
20
24
  * Register a component for auto-refresh.
21
25
  *
@@ -43,7 +47,7 @@ export function registerAutoRefresh(id, options) {
43
47
  // Start timer if applicable
44
48
  maybeStartTimer(id, refresher);
45
49
  // Immediate refresh if requested
46
- if (immediate) {
50
+ if (immediate && globallyEnabled) {
47
51
  doRefresh(id, refresher);
48
52
  }
49
53
  return () => unregisterAutoRefresh(id);
@@ -69,10 +73,32 @@ export function triggerRefresh(id) {
69
73
  doRefresh(id, refresher);
70
74
  }
71
75
  }
76
+ /**
77
+ * Globally enable/disable all auto-refresh timers.
78
+ */
79
+ export function setAutoRefreshEnabled(enabled, reason) {
80
+ globallyEnabled = enabled;
81
+ globalPauseReason = enabled ? null : reason || null;
82
+ refreshers.forEach((refresher, id) => {
83
+ if (!enabled) {
84
+ if (refresher.timerId) {
85
+ clearInterval(refresher.timerId);
86
+ refresher.timerId = null;
87
+ }
88
+ return;
89
+ }
90
+ maybeStartTimer(id, refresher);
91
+ });
92
+ }
93
+ export function getAutoRefreshPauseReason() {
94
+ return globalPauseReason;
95
+ }
72
96
  /**
73
97
  * Check if conditions allow refresh for this refresher.
74
98
  */
75
99
  function canRefresh(refresher) {
100
+ if (!globallyEnabled)
101
+ return false;
76
102
  // Don't refresh if page is hidden
77
103
  if (!pageVisible)
78
104
  return false;
@@ -112,6 +138,8 @@ function maybeStartTimer(id, refresher) {
112
138
  refresher.timerId = null;
113
139
  }
114
140
  // Only start timer if page is visible and tab is active (or global)
141
+ if (!globallyEnabled)
142
+ return;
115
143
  if (!pageVisible)
116
144
  return;
117
145
  if (refresher.tabId && refresher.tabId !== activeTab)
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  "use strict";
2
3
  (() => {
3
4
  const windowExt = window;
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  const listeners = new Map();
2
3
  export function subscribe(event, handler) {
3
4
  if (!listeners.has(event)) {
@@ -1,3 +1,4 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  import { BASE_PATH, REPO_ID } from "./env.js";
2
3
  function cachePrefix() {
3
4
  const scope = REPO_ID ? `repo:${REPO_ID}` : `base:${BASE_PATH || ""}`;
@@ -1,6 +1,7 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
1
2
  export const CONSTANTS = {
2
3
  UI: {
3
- TOAST_DURATION: 2200,
4
+ TOAST_DURATION: 5000,
4
5
  POLLING_INTERVAL: 15000,
5
6
  LOG_SCROLL_THRESHOLD: 50,
6
7
  MAX_LOG_LINES_IN_DOM: 2000,
@@ -36,11 +37,26 @@ export const CONSTANTS = {
36
37
  },
37
38
  PROMPTS: {
38
39
  VOICE_TRANSCRIPT_DISCLAIMER: "Note: transcribed from user voice. If confusing or possibly inaccurate and you cannot infer the intention please clarify before proceeding.",
40
+ CAR_CONTEXT_HINT: "Context: read .codex-autorunner/ABOUT_CAR.md for repo-specific rules.",
41
+ },
42
+ KEYWORDS: {
43
+ CAR_CONTEXT: [
44
+ "car",
45
+ "codex",
46
+ "spec",
47
+ "autorunner",
48
+ "workspace",
49
+ "ticket",
50
+ "tickets",
51
+ "context",
52
+ "decision",
53
+ "decisions",
54
+ "handoff",
55
+ "dispatch",
56
+ "inbox",
57
+ ],
39
58
  },
40
59
  API: {
41
- STATE_ENDPOINT: "/api/state",
42
- LOGS_ENDPOINT: "/api/logs",
43
- DOCS_ENDPOINT: "/api/docs",
44
60
  TERMINAL_ENDPOINT: "/api/terminal",
45
61
  TERMINAL_IMAGE_ENDPOINT: "/api/terminal/image",
46
62
  }
@@ -1,14 +1,10 @@
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, confirmModal, openModal, 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";
10
6
  const UPDATE_STATUS_SEEN_KEY = "car_update_status_seen";
11
- let pendingSummaryOpen = false;
7
+ const ANALYTICS_SUMMARY_CACHE_KEY = "analytics-summary";
12
8
  const usageChartState = {
13
9
  segment: "none",
14
10
  bucket: "day",
@@ -16,120 +12,6 @@ const usageChartState = {
16
12
  };
17
13
  let usageSeriesRetryTimer = null;
18
14
  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
- }
120
- }
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
- }
132
- }
133
15
  function setUsageLoading(loading) {
134
16
  const btn = document.getElementById("usage-refresh");
135
17
  if (!btn)
@@ -226,6 +108,144 @@ function renderUsage(data) {
226
108
  if (metaEl)
227
109
  metaEl.textContent = codexHome;
228
110
  }
111
+ async function loadTicketAnalytics() {
112
+ try {
113
+ const data = (await api("/api/analytics/summary"));
114
+ saveToCache(ANALYTICS_SUMMARY_CACHE_KEY, data);
115
+ renderTicketAnalytics(data);
116
+ }
117
+ catch (err) {
118
+ const cached = loadFromCache(ANALYTICS_SUMMARY_CACHE_KEY);
119
+ if (cached)
120
+ renderTicketAnalytics(cached);
121
+ flash(err.message || "Failed to load analytics", "error");
122
+ }
123
+ }
124
+ function formatDuration(seconds) {
125
+ if (seconds === null || Number.isNaN(seconds))
126
+ return "–";
127
+ if (seconds < 60)
128
+ return `${Math.round(seconds)}s`;
129
+ const mins = seconds / 60;
130
+ if (mins < 60)
131
+ return `${Math.round(mins)}m`;
132
+ const hours = mins / 60;
133
+ return `${hours.toFixed(1)}h`;
134
+ }
135
+ function renderTicketAnalytics(data) {
136
+ const run = data?.run;
137
+ const tickets = data?.tickets;
138
+ const turns = data?.turns;
139
+ const agent = data?.agent;
140
+ const statusEl = document.getElementById("runner-status");
141
+ if (statusEl && run) {
142
+ statusPill(statusEl, run.status || "idle");
143
+ statusEl.textContent = run.status || "idle";
144
+ }
145
+ const lastStart = document.getElementById("last-start");
146
+ const lastFinish = document.getElementById("last-finish");
147
+ const lastDuration = document.getElementById("last-duration");
148
+ const todoCount = document.getElementById("todo-count");
149
+ const doneCount = document.getElementById("done-count");
150
+ const ticketActive = document.getElementById("ticket-active");
151
+ const ticketTurns = document.getElementById("ticket-turns");
152
+ const totalTurns = document.getElementById("total-turns");
153
+ const dispatchesEl = document.getElementById("message-dispatches");
154
+ const repliesEl = document.getElementById("message-replies");
155
+ const runIdEl = document.getElementById("last-run-id");
156
+ if (lastStart)
157
+ lastStart.textContent = formatIso(run?.started_at || null);
158
+ if (lastFinish)
159
+ lastFinish.textContent = formatIso(run?.finished_at || null);
160
+ if (lastDuration)
161
+ lastDuration.textContent = formatDuration(run?.duration_seconds ?? null);
162
+ if (todoCount)
163
+ todoCount.textContent = tickets ? String(tickets.todo_count) : "–";
164
+ if (doneCount)
165
+ doneCount.textContent = tickets ? String(tickets.done_count) : "–";
166
+ if (ticketActive) {
167
+ const ticket = tickets?.current_ticket || null;
168
+ ticketActive.textContent = ticket ? ticket.split("/").pop() || ticket : "–";
169
+ }
170
+ if (ticketTurns)
171
+ ticketTurns.textContent = turns?.current_ticket != null ? String(turns.current_ticket) : "–";
172
+ if (totalTurns)
173
+ totalTurns.textContent = turns?.total != null ? String(turns.total) : "–";
174
+ const dispatchCount = turns?.dispatches ?? 0;
175
+ if (dispatchesEl)
176
+ dispatchesEl.textContent = String(dispatchCount);
177
+ if (repliesEl)
178
+ repliesEl.textContent = turns?.replies != null ? String(turns.replies) : "0";
179
+ if (runIdEl)
180
+ runIdEl.textContent = run?.short_id || run?.id || "–";
181
+ // Agent chip (optional future use)
182
+ const agentEl = document.getElementById("ticket-agent");
183
+ if (agentEl) {
184
+ agentEl.textContent = agent?.id || "–";
185
+ }
186
+ }
187
+ async function loadRunHistory() {
188
+ try {
189
+ const runs = (await api("/api/flows/runs?flow_type=ticket_flow"));
190
+ const runHistory = Array.isArray(runs) ? runs.slice(0, 10) : [];
191
+ renderRunHistory(runHistory);
192
+ }
193
+ catch (err) {
194
+ flash(err.message || "Failed to load run history", "error");
195
+ }
196
+ }
197
+ function formatIso(iso) {
198
+ if (!iso)
199
+ return "–";
200
+ const dt = new Date(iso);
201
+ if (Number.isNaN(dt.getTime()))
202
+ return iso;
203
+ return dt.toLocaleString();
204
+ }
205
+ function calcDurationFromRun(run) {
206
+ const started = run.started_at;
207
+ const finished = run.finished_at;
208
+ if (!started)
209
+ return "–";
210
+ const start = new Date(started).getTime();
211
+ const end = finished && !Number.isNaN(new Date(finished).getTime())
212
+ ? new Date(finished).getTime()
213
+ : Date.now();
214
+ if (Number.isNaN(start) || Number.isNaN(end))
215
+ return "–";
216
+ return formatDuration((end - start) / 1000);
217
+ }
218
+ function renderRunHistory(runs) {
219
+ const container = document.getElementById("run-history-list");
220
+ if (!container)
221
+ return;
222
+ if (!runs || !runs.length) {
223
+ container.innerHTML = '<div class="muted">No runs yet.</div>';
224
+ return;
225
+ }
226
+ const items = runs.map((run) => {
227
+ const shortId = run.id ? run.id.split("-")[0] : "–";
228
+ const status = run.status || "unknown";
229
+ const duration = calcDurationFromRun(run);
230
+ const started = formatIso(run.started_at);
231
+ const current = run.current_step || "–";
232
+ return `
233
+ <div class="run-history-row">
234
+ <div class="run-history-id">${shortId}</div>
235
+ <div class="run-history-status">${status}</div>
236
+ <div class="run-history-duration">${duration}</div>
237
+ <div class="run-history-start">${started}</div>
238
+ <div class="run-history-step">${current}</div>
239
+ </div>
240
+ `;
241
+ });
242
+ container.innerHTML = `
243
+ <div class="run-history-head">
244
+ <div>ID</div><div>Status</div><div>Duration</div><div>Started</div><div>Step</div>
245
+ </div>
246
+ ${items.join("")}
247
+ `;
248
+ }
229
249
  function buildUsageSeriesQuery() {
230
250
  const params = new URLSearchParams();
231
251
  const now = new Date();
@@ -676,97 +696,43 @@ function bindAction(buttonId, action) {
676
696
  }
677
697
  });
678
698
  }
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
699
  export function initDashboard() {
688
700
  initSettings();
689
701
  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();
730
- });
731
- bindAction("refresh-state", async () => { await loadState(); });
702
+ // initReview(); // Removed - review.ts was deleted
732
703
  bindAction("usage-refresh", loadUsage);
733
- bindAction("refresh-preview", loadTodoPreview);
734
- const cachedState = loadFromCache("state");
735
- if (cachedState)
736
- renderState(cachedState);
704
+ bindAction("analytics-refresh", async () => {
705
+ await loadTicketAnalytics();
706
+ await loadRunHistory();
707
+ });
737
708
  const cachedUsage = loadFromCache("usage");
738
709
  if (cachedUsage)
739
710
  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
- }
711
+ const cachedAnalytics = loadFromCache(ANALYTICS_SUMMARY_CACHE_KEY);
712
+ if (cachedAnalytics)
713
+ renderTicketAnalytics(cachedAnalytics);
758
714
  loadUsage();
759
- loadTodoPreview();
715
+ loadTicketAnalytics();
716
+ loadRunHistory();
760
717
  loadVersion();
761
718
  checkUpdateStatus();
762
- startStatePolling();
763
719
  registerAutoRefresh("dashboard-usage", {
764
720
  callback: async () => { await loadUsage(); },
765
- tabId: "dashboard",
721
+ tabId: "analytics",
766
722
  interval: CONSTANTS.UI.AUTO_REFRESH_USAGE_INTERVAL,
767
723
  refreshOnActivation: true,
768
724
  immediate: false,
769
725
  });
726
+ registerAutoRefresh("dashboard-analytics", {
727
+ callback: async () => {
728
+ await loadTicketAnalytics();
729
+ await loadRunHistory();
730
+ },
731
+ tabId: "analytics",
732
+ interval: CONSTANTS.UI.AUTO_REFRESH_INTERVAL,
733
+ refreshOnActivation: true,
734
+ immediate: true,
735
+ });
770
736
  }
771
737
  async function loadVersion() {
772
738
  const versionEl = document.getElementById("repo-version");
@@ -0,0 +1,37 @@
1
+ // GENERATED FILE - do not edit directly. Source: static_src/
2
+ export function renderDiff(patch, container) {
3
+ if (!container)
4
+ return;
5
+ container.innerHTML = "";
6
+ const lines = (patch || "").split("\n");
7
+ if (!lines.length)
8
+ return;
9
+ for (const line of lines) {
10
+ const row = document.createElement("div");
11
+ row.classList.add("diff-line");
12
+ if (line.startsWith("@@")) {
13
+ row.classList.add("diff-hunk");
14
+ }
15
+ else if (line.startsWith("+") && !line.startsWith("+++")) {
16
+ row.classList.add("diff-add");
17
+ }
18
+ else if (line.startsWith("-") && !line.startsWith("---")) {
19
+ row.classList.add("diff-del");
20
+ }
21
+ else if (line.startsWith("---") || line.startsWith("+++")) {
22
+ row.classList.add("diff-file");
23
+ }
24
+ else {
25
+ row.classList.add("diff-ctx");
26
+ }
27
+ const sign = document.createElement("span");
28
+ sign.classList.add("diff-sign");
29
+ sign.textContent = line.charAt(0) || " ";
30
+ const content = document.createElement("span");
31
+ content.classList.add("diff-content");
32
+ content.textContent = line.slice(1);
33
+ row.appendChild(sign);
34
+ row.appendChild(content);
35
+ container.appendChild(row);
36
+ }
37
+ }