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,157 +0,0 @@
1
- import { api, flash, statusPill } from "./utils.js";
2
- function $(id) {
3
- return document.getElementById(id);
4
- }
5
- function setText(el, text) {
6
- if (!el)
7
- return;
8
- el.textContent = text ?? "–";
9
- }
10
- function setLink(el, { href, text, title } = {}) {
11
- if (!el)
12
- return;
13
- if (href) {
14
- el.href = href;
15
- el.target = "_blank";
16
- el.rel = "noopener noreferrer";
17
- el.classList.remove("muted");
18
- el.textContent = text || href;
19
- if (title)
20
- el.title = title;
21
- }
22
- else {
23
- el.removeAttribute("href");
24
- el.removeAttribute("target");
25
- el.removeAttribute("rel");
26
- el.classList.add("muted");
27
- el.textContent = text || "–";
28
- if (title)
29
- el.title = title;
30
- }
31
- }
32
- let reviewInterval = null;
33
- async function loadReviewStatus() {
34
- try {
35
- const data = (await api("/api/review/status"));
36
- const review = data.review || {};
37
- const statusPillEl = $("review-status-pill");
38
- const runIdEl = $("review-run-id");
39
- const startedEl = $("review-started");
40
- const finishedEl = $("review-finished");
41
- const startBtn = $("review-start");
42
- const stopBtn = $("review-stop");
43
- const resetBtn = $("review-reset");
44
- const status = review.status || "idle";
45
- const running = review.running || false;
46
- statusPill(statusPillEl, status);
47
- setText(runIdEl, review.id);
48
- setText(startedEl, review.started_at);
49
- setText(finishedEl, review.finished_at);
50
- if (startBtn) {
51
- startBtn.disabled = running;
52
- }
53
- if (stopBtn) {
54
- stopBtn.disabled = !running || status === "stopped";
55
- }
56
- if (resetBtn) {
57
- resetBtn.disabled = running;
58
- }
59
- const finalLink = $("review-final-link");
60
- const logLink = $("review-log-link");
61
- const bundleLink = $("review-scratchpad-link");
62
- setLink(finalLink, {
63
- href: review.final_output_path ? "/api/review/artifact?kind=final_report" : null,
64
- text: "Final report",
65
- title: "Open the final review report",
66
- });
67
- setLink(logLink, {
68
- href: review.run_dir ? "/api/review/artifact?kind=workflow_log" : null,
69
- text: "Log",
70
- title: "Open the review workflow log",
71
- });
72
- setLink(bundleLink, {
73
- href: review.scratchpad_bundle_path
74
- ? "/api/review/artifact?kind=scratchpad_bundle"
75
- : null,
76
- text: "Scratchpad",
77
- title: "Download scratchpad files as zip",
78
- });
79
- }
80
- catch (err) {
81
- console.error("Failed to load review status:", err);
82
- }
83
- }
84
- async function startReview() {
85
- try {
86
- const agentEl = $("review-agent");
87
- const modelEl = $("review-model");
88
- const reasoningEl = $("review-reasoning");
89
- const timeoutEl = $("review-timeout");
90
- const payload = {
91
- agent: agentEl?.value || "opencode",
92
- model: modelEl?.value || "zai-coding-plan/glm-4.7",
93
- reasoning: reasoningEl?.value || null,
94
- max_wallclock_seconds: timeoutEl?.value
95
- ? parseInt(timeoutEl.value, 10) || null
96
- : null,
97
- };
98
- const data = (await api("/api/review/start", {
99
- method: "POST",
100
- body: payload,
101
- }));
102
- if (data.status === "ok" || data.review) {
103
- flash("Review started");
104
- await loadReviewStatus();
105
- }
106
- }
107
- catch (err) {
108
- console.error("Failed to start review:", err);
109
- const message = err instanceof Error ? err.message : "Failed to start review";
110
- flash(message);
111
- }
112
- }
113
- async function stopReview() {
114
- try {
115
- const data = (await api("/api/review/stop", {
116
- method: "POST",
117
- }));
118
- if (data.status === "ok" || data.review) {
119
- flash("Review stopped");
120
- await loadReviewStatus();
121
- }
122
- }
123
- catch (err) {
124
- console.error("Failed to stop review:", err);
125
- const message = err instanceof Error ? err.message : "Failed to stop review";
126
- flash(message);
127
- }
128
- }
129
- async function resetReview() {
130
- try {
131
- const data = (await api("/api/review/reset", {
132
- method: "POST",
133
- }));
134
- if (data.status === "ok" || data.review) {
135
- flash("Review state reset");
136
- await loadReviewStatus();
137
- }
138
- }
139
- catch (err) {
140
- console.error("Failed to reset review:", err);
141
- const message = err instanceof Error ? err.message : "Failed to reset review";
142
- flash(message);
143
- }
144
- }
145
- export function initReview() {
146
- const startBtn = $("review-start");
147
- const stopBtn = $("review-stop");
148
- const resetBtn = $("review-reset");
149
- startBtn?.addEventListener("click", startReview);
150
- stopBtn?.addEventListener("click", stopReview);
151
- resetBtn?.addEventListener("click", resetReview);
152
- loadReviewStatus();
153
- if (reviewInterval) {
154
- clearInterval(reviewInterval);
155
- }
156
- reviewInterval = setInterval(loadReviewStatus, 5000);
157
- }
@@ -1,418 +0,0 @@
1
- import { api, flash, getAuthToken, openModal, resolvePath } from "./utils.js";
2
- import { registerAutoRefresh, triggerRefresh } from "./autoRefresh.js";
3
- import { subscribe } from "./bus.js";
4
- let initialized = false;
5
- const RUNS_AUTO_REFRESH_INTERVAL = 15000;
6
- const runsState = {
7
- runs: [],
8
- attributionMode: "split",
9
- todoSearch: "",
10
- };
11
- let closeDetailsModal = null;
12
- const RUN_OUTPUT_MAX_LINES = 160;
13
- const RUN_DIFF_MAX_LINES = 400;
14
- const RUN_PLAN_MAX_LINES = 120;
15
- const ui = {
16
- refresh: document.getElementById("runs-refresh"),
17
- tableBody: document.getElementById("runs-table-body"),
18
- todoList: document.getElementById("runs-todo-list"),
19
- todoSummary: document.getElementById("runs-todo-summary"),
20
- todoSearch: document.getElementById("runs-todo-search"),
21
- attribution: document.getElementById("runs-attribution-mode"),
22
- modal: document.getElementById("run-details-modal"),
23
- modalClose: document.getElementById("run-details-close"),
24
- modalTitle: document.getElementById("run-details-title"),
25
- modalMeta: document.getElementById("run-details-meta"),
26
- modalTodos: document.getElementById("run-details-todos"),
27
- modalTokens: document.getElementById("run-details-tokens"),
28
- modalPlan: document.getElementById("run-details-plan"),
29
- modalDiff: document.getElementById("run-details-diff"),
30
- modalOutput: document.getElementById("run-details-output"),
31
- modalLog: document.getElementById("run-details-log"),
32
- };
33
- const STATUS_LABELS = {
34
- ok: "ok",
35
- error: "error",
36
- running: "running",
37
- };
38
- function formatTimestamp(ts) {
39
- if (!ts)
40
- return "–";
41
- const date = new Date(ts);
42
- if (Number.isNaN(date.getTime()))
43
- return ts;
44
- return date.toLocaleString();
45
- }
46
- function formatDuration(seconds) {
47
- if (typeof seconds !== "number" || Number.isNaN(seconds))
48
- return "–";
49
- if (seconds < 60)
50
- return `${seconds.toFixed(1)}s`;
51
- const mins = Math.floor(seconds / 60);
52
- const secs = Math.round(seconds % 60);
53
- return `${mins}m ${secs}s`;
54
- }
55
- function formatNumber(value) {
56
- if (typeof value !== "number" || Number.isNaN(value))
57
- return "–";
58
- return value.toLocaleString();
59
- }
60
- function truncateLines(text, maxLines) {
61
- if (!text)
62
- return "";
63
- const lines = text.split("\n");
64
- if (lines.length <= maxLines)
65
- return text;
66
- const trimmed = lines.slice(0, maxLines);
67
- trimmed.push(`… (${lines.length - maxLines} more lines)`);
68
- return trimmed.join("\n");
69
- }
70
- function runStatus(entry) {
71
- if (entry.exit_code === 0)
72
- return "ok";
73
- if (entry.exit_code != null)
74
- return "error";
75
- return entry.finished_at ? "error" : "running";
76
- }
77
- function renderRunsTable() {
78
- if (!ui.tableBody)
79
- return;
80
- ui.tableBody.innerHTML = "";
81
- if (!runsState.runs.length) {
82
- const row = document.createElement("tr");
83
- row.innerHTML =
84
- '<td colspan="6" class="runs-empty">No runs recorded yet.</td>';
85
- ui.tableBody.appendChild(row);
86
- return;
87
- }
88
- runsState.runs.forEach((run) => {
89
- const status = runStatus(run);
90
- const row = document.createElement("tr");
91
- row.className = "runs-row";
92
- row.dataset.runId = String(run.run_id);
93
- row.innerHTML = `
94
- <td>#${run.run_id}</td>
95
- <td><span class="runs-pill runs-pill-${status}">${STATUS_LABELS[status]}</span></td>
96
- <td>${formatTimestamp(run.started_at)}</td>
97
- <td>${run.app_server?.model || "–"}</td>
98
- <td>${formatNumber(run.token_total)}</td>
99
- <td>${formatNumber(run.completed_todo_count || 0)}</td>
100
- `;
101
- row.addEventListener("click", () => openRunDetails(run));
102
- ui.tableBody.appendChild(row);
103
- });
104
- }
105
- function computeTodoAttribution() {
106
- const map = new Map();
107
- runsState.runs.forEach((run) => {
108
- const completed = run.todo?.completed;
109
- if (!Array.isArray(completed) || completed.length === 0)
110
- return;
111
- const runTokens = typeof run.token_total === "number" ? run.token_total : 0;
112
- const perTodo = runsState.attributionMode === "split"
113
- ? runTokens / completed.length
114
- : runTokens;
115
- completed.forEach((item) => {
116
- if (!item)
117
- return;
118
- const key = String(item);
119
- if (!map.has(key)) {
120
- map.set(key, {
121
- text: key,
122
- tokens: 0,
123
- count: 0,
124
- runs: new Set(),
125
- });
126
- }
127
- const entry = map.get(key);
128
- entry.tokens += perTodo;
129
- entry.count += 1;
130
- entry.runs.add(run.run_id);
131
- });
132
- });
133
- const list = Array.from(map.values()).map((entry) => ({
134
- text: entry.text,
135
- tokens: entry.tokens,
136
- count: entry.count,
137
- runs: Array.from(entry.runs).sort((a, b) => b - a),
138
- }));
139
- list.sort((a, b) => b.tokens - a.tokens);
140
- return list;
141
- }
142
- function renderTodoAnalytics() {
143
- if (!ui.todoList)
144
- return;
145
- const list = computeTodoAttribution();
146
- const filtered = list.filter((entry) => entry.text.toLowerCase().includes(runsState.todoSearch.toLowerCase()));
147
- ui.todoList.innerHTML = "";
148
- if (!filtered.length) {
149
- ui.todoList.textContent = "No TODOs yet.";
150
- }
151
- else {
152
- filtered.forEach((entry) => {
153
- const item = document.createElement("div");
154
- item.className = "runs-todo-item";
155
- const runsMarkup = entry.runs
156
- .map((runId) => `<button class="runs-chip" data-run-id="${runId}" title="Open run #${runId}">#${runId}</button>`)
157
- .join("");
158
- item.innerHTML = `
159
- <div class="runs-todo-text">${entry.text}</div>
160
- <div class="runs-todo-meta">
161
- <span>${formatNumber(entry.tokens)} tokens</span>
162
- <span>${entry.count} completions</span>
163
- <span class="runs-todo-runs">${runsMarkup}</span>
164
- </div>
165
- `;
166
- item.querySelectorAll(".runs-chip").forEach((btn) => {
167
- btn.addEventListener("click", (event) => {
168
- event.stopPropagation();
169
- const id = Number(btn.dataset.runId);
170
- const run = runsState.runs.find((candidate) => candidate.run_id === id);
171
- if (run)
172
- openRunDetails(run);
173
- });
174
- });
175
- ui.todoList.appendChild(item);
176
- });
177
- }
178
- if (ui.todoSummary) {
179
- ui.todoSummary.textContent = `${filtered.length} TODOs`;
180
- }
181
- }
182
- function formatPlan(plan) {
183
- if (Array.isArray(plan)) {
184
- const lines = plan
185
- .map((item, index) => {
186
- if (item && typeof item === "object") {
187
- const step = item.step || item.task || item.title || "";
188
- const status = item.status || item.state || "";
189
- const label = `${index + 1}. ${step || "Step"}`;
190
- return status ? `${label} [${status}]` : label;
191
- }
192
- return `${index + 1}. ${item}`;
193
- })
194
- .filter(Boolean);
195
- return lines.join("\n");
196
- }
197
- if (plan && typeof plan === "object") {
198
- if (Array.isArray(plan.steps)) {
199
- return formatPlan(plan.steps);
200
- }
201
- if (Array.isArray(plan.plan)) {
202
- return formatPlan(plan.plan);
203
- }
204
- return JSON.stringify(plan, null, 2);
205
- }
206
- if (typeof plan === "string")
207
- return plan;
208
- return "";
209
- }
210
- async function fetchArtifactText(path) {
211
- const target = resolvePath(path);
212
- const headers = {};
213
- const token = getAuthToken();
214
- if (token) {
215
- headers.Authorization = `Bearer ${token}`;
216
- }
217
- const res = await fetch(target, { headers });
218
- if (!res.ok) {
219
- const message = await res.text();
220
- const error = message?.trim() || `Request failed with ${res.status}`;
221
- throw new Error(error);
222
- }
223
- return res.text();
224
- }
225
- async function loadPlan(runId) {
226
- if (!ui.modalPlan)
227
- return;
228
- ui.modalPlan.textContent = "Loading…";
229
- try {
230
- const planRaw = await fetchArtifactText(`/api/runs/${runId}/plan`);
231
- let planData = planRaw;
232
- try {
233
- planData = JSON.parse(planRaw);
234
- }
235
- catch (_err) {
236
- planData = planRaw;
237
- }
238
- const formatted = formatPlan(planData);
239
- ui.modalPlan.textContent = formatted ? truncateLines(formatted, RUN_PLAN_MAX_LINES) : "Not available.";
240
- }
241
- catch (err) {
242
- const message = err instanceof Error ? err.message : "";
243
- ui.modalPlan.textContent =
244
- message.includes("401") || message.includes("403") || message.includes("404")
245
- ? "Not available."
246
- : "Unable to load.";
247
- }
248
- }
249
- async function loadFinalOutput(runId) {
250
- if (!ui.modalOutput)
251
- return;
252
- ui.modalOutput.textContent = "Loading…";
253
- try {
254
- const text = await api(`/api/runs/${runId}/output`);
255
- if (typeof text === "string" && text.trim()) {
256
- ui.modalOutput.textContent = truncateLines(text, RUN_OUTPUT_MAX_LINES);
257
- return;
258
- }
259
- if (text !== null && text !== undefined) {
260
- const formatted = JSON.stringify(text, null, 2);
261
- ui.modalOutput.textContent = truncateLines(formatted, RUN_OUTPUT_MAX_LINES);
262
- return;
263
- }
264
- ui.modalOutput.textContent = "Not available.";
265
- }
266
- catch (err) {
267
- const message = err instanceof Error ? err.message : "";
268
- ui.modalOutput.textContent =
269
- message.includes("401") || message.includes("403") || message.includes("404")
270
- ? "Not available."
271
- : "Unable to load.";
272
- }
273
- }
274
- async function loadDiff(runId) {
275
- if (!ui.modalDiff)
276
- return;
277
- ui.modalDiff.textContent = "Loading…";
278
- try {
279
- const text = await api(`/api/runs/${runId}/diff`);
280
- if (typeof text === "string" && text.trim()) {
281
- ui.modalDiff.textContent = truncateLines(text, RUN_DIFF_MAX_LINES);
282
- return;
283
- }
284
- if (text !== null && text !== undefined) {
285
- const formatted = JSON.stringify(text, null, 2);
286
- ui.modalDiff.textContent = truncateLines(formatted, RUN_DIFF_MAX_LINES);
287
- return;
288
- }
289
- ui.modalDiff.textContent = "Not available.";
290
- }
291
- catch (err) {
292
- const message = err instanceof Error ? err.message : "";
293
- ui.modalDiff.textContent =
294
- message.includes("401") || message.includes("403") || message.includes("404")
295
- ? "Not available."
296
- : "Unable to load.";
297
- }
298
- }
299
- function renderTokenBreakdown(run) {
300
- if (!ui.modalTokens)
301
- return;
302
- const usage = run.token_usage?.delta;
303
- if (!usage || typeof usage !== "object") {
304
- ui.modalTokens.textContent = "Token data unavailable.";
305
- return;
306
- }
307
- ui.modalTokens.innerHTML = `
308
- <div>Input: ${formatNumber(usage.input_tokens || usage.inputTokens)}</div>
309
- <div>Cached input: ${formatNumber(usage.cached_input_tokens || usage.cachedInputTokens)}</div>
310
- <div>Output: ${formatNumber(usage.output_tokens || usage.outputTokens)}</div>
311
- <div>Reasoning: ${formatNumber(usage.reasoning_output_tokens || usage.reasoningOutputTokens)}</div>
312
- <div>Total: ${formatNumber(usage.total_tokens || usage.totalTokens || usage.total)}</div>
313
- `;
314
- }
315
- function renderCompletedTodos(run) {
316
- if (!ui.modalTodos)
317
- return;
318
- const completed = Array.isArray(run.todo?.completed) ? run.todo.completed : [];
319
- ui.modalTodos.innerHTML = "";
320
- if (!completed.length) {
321
- ui.modalTodos.textContent = "No TODOs completed in this run.";
322
- return;
323
- }
324
- const list = document.createElement("ul");
325
- completed.forEach((item) => {
326
- const li = document.createElement("li");
327
- li.textContent = item;
328
- li.addEventListener("click", () => {
329
- if (ui.todoSearch) {
330
- ui.todoSearch.value = item;
331
- runsState.todoSearch = item;
332
- renderTodoAnalytics();
333
- }
334
- });
335
- list.appendChild(li);
336
- });
337
- ui.modalTodos.appendChild(list);
338
- }
339
- function openRunDetails(run) {
340
- if (!ui.modal)
341
- return;
342
- if (closeDetailsModal) {
343
- closeDetailsModal();
344
- }
345
- ui.modalTitle.textContent = `Run #${run.run_id}`;
346
- ui.modalMeta.textContent = `Started ${formatTimestamp(run.started_at)} · Duration ${formatDuration(run.duration_seconds)} · Status ${runStatus(run) || "–"}`;
347
- if (ui.modalLog) {
348
- const params = new URLSearchParams({ run_id: String(run.run_id), raw: "1" });
349
- const token = getAuthToken();
350
- if (token) {
351
- params.set("token", token);
352
- }
353
- ui.modalLog.href = resolvePath(`/api/logs?${params.toString()}`);
354
- }
355
- renderTokenBreakdown(run);
356
- renderCompletedTodos(run);
357
- if (ui.modalPlan)
358
- loadPlan(run.run_id);
359
- if (ui.modalDiff)
360
- loadDiff(run.run_id);
361
- if (ui.modalOutput)
362
- loadFinalOutput(run.run_id);
363
- const triggerEl = document.activeElement;
364
- closeDetailsModal = openModal(ui.modal, {
365
- initialFocus: ui.modalClose || ui.modal,
366
- returnFocusTo: triggerEl,
367
- });
368
- }
369
- async function loadRuns() {
370
- try {
371
- const data = await api("/api/runs?limit=200");
372
- runsState.runs = Array.isArray(data?.runs) ? data.runs : [];
373
- renderRunsTable();
374
- renderTodoAnalytics();
375
- }
376
- catch (err) {
377
- const message = err instanceof Error ? err.message : "Failed to load runs";
378
- flash(message, "error");
379
- }
380
- }
381
- export function initRuns() {
382
- if (initialized)
383
- return;
384
- initialized = true;
385
- if (ui.refresh) {
386
- ui.refresh.addEventListener("click", () => loadRuns());
387
- }
388
- if (ui.todoSearch) {
389
- ui.todoSearch.addEventListener("input", () => {
390
- runsState.todoSearch = ui.todoSearch.value || "";
391
- renderTodoAnalytics();
392
- });
393
- }
394
- if (ui.attribution) {
395
- ui.attribution.value = runsState.attributionMode;
396
- ui.attribution.addEventListener("change", () => {
397
- runsState.attributionMode = (ui.attribution?.value || "split");
398
- renderTodoAnalytics();
399
- });
400
- }
401
- if (ui.modalClose) {
402
- ui.modalClose.addEventListener("click", () => {
403
- if (closeDetailsModal)
404
- closeDetailsModal();
405
- });
406
- }
407
- registerAutoRefresh("runs-list", {
408
- callback: loadRuns,
409
- tabId: "runs",
410
- interval: RUNS_AUTO_REFRESH_INTERVAL,
411
- refreshOnActivation: true,
412
- immediate: false,
413
- });
414
- subscribe("runs:invalidate", () => {
415
- triggerRefresh("runs-list");
416
- });
417
- loadRuns();
418
- }
@@ -1,124 +0,0 @@
1
- import { api, flash } from "./utils.js";
2
- const UI = {
3
- status: document.getElementById("snapshot-status"),
4
- content: document.getElementById("snapshot-content"),
5
- generate: document.getElementById("snapshot-generate"),
6
- update: document.getElementById("snapshot-update"),
7
- regenerate: document.getElementById("snapshot-regenerate"),
8
- copy: document.getElementById("snapshot-copy"),
9
- refresh: document.getElementById("snapshot-refresh"),
10
- };
11
- let latest = { exists: false, content: "", state: {} };
12
- let busy = false;
13
- function setBusy(on) {
14
- busy = on;
15
- const disabled = !!on;
16
- for (const btn of [UI.generate, UI.update, UI.regenerate, UI.copy, UI.refresh]) {
17
- if (!btn)
18
- continue;
19
- btn.disabled = disabled;
20
- }
21
- if (UI.status)
22
- UI.status.textContent = on ? "Working…" : "";
23
- }
24
- function render() {
25
- if (!UI.content)
26
- return;
27
- UI.content.value = latest.content || "";
28
- if (UI.generate)
29
- UI.generate.classList.toggle("hidden", false);
30
- if (UI.update)
31
- UI.update.classList.toggle("hidden", true);
32
- if (UI.regenerate)
33
- UI.regenerate.classList.toggle("hidden", true);
34
- if (UI.copy)
35
- UI.copy.disabled = busy || !(latest.content || "").trim();
36
- }
37
- async function loadSnapshot({ notify = false } = {}) {
38
- if (busy)
39
- return;
40
- try {
41
- setBusy(true);
42
- const data = await api("/api/snapshot");
43
- latest = {
44
- exists: !!data?.exists,
45
- content: data?.content || "",
46
- state: data?.state || {},
47
- };
48
- render();
49
- if (notify)
50
- flash(latest.exists ? "Snapshot loaded" : "No snapshot yet");
51
- }
52
- catch (err) {
53
- flash(err?.message || "Failed to load snapshot");
54
- }
55
- finally {
56
- setBusy(false);
57
- }
58
- }
59
- async function runSnapshot() {
60
- if (busy)
61
- return;
62
- try {
63
- setBusy(true);
64
- const data = await api("/api/snapshot", {
65
- method: "POST",
66
- body: {},
67
- });
68
- latest = {
69
- exists: true,
70
- content: data?.content || "",
71
- state: data?.state || {},
72
- };
73
- render();
74
- flash("Snapshot generated");
75
- }
76
- catch (err) {
77
- flash(err?.message || "Snapshot generation failed");
78
- }
79
- finally {
80
- setBusy(false);
81
- }
82
- }
83
- async function copyToClipboard() {
84
- const text = UI.content?.value || "";
85
- if (!text.trim())
86
- return;
87
- try {
88
- if (navigator.clipboard?.writeText) {
89
- await navigator.clipboard.writeText(text);
90
- flash("Copied to clipboard");
91
- return;
92
- }
93
- }
94
- catch {
95
- // fall through
96
- }
97
- try {
98
- UI.content?.focus();
99
- UI.content?.select();
100
- const ok = document.execCommand("copy");
101
- flash(ok ? "Copied to clipboard" : "Copy failed");
102
- }
103
- catch {
104
- flash("Copy failed");
105
- }
106
- finally {
107
- try {
108
- UI.content?.setSelectionRange(0, 0);
109
- }
110
- catch {
111
- // ignore
112
- }
113
- }
114
- }
115
- export function initSnapshot() {
116
- if (!UI.content)
117
- return;
118
- UI.generate?.addEventListener("click", () => runSnapshot());
119
- UI.update?.addEventListener("click", () => runSnapshot());
120
- UI.regenerate?.addEventListener("click", () => runSnapshot());
121
- UI.copy?.addEventListener("click", copyToClipboard);
122
- UI.refresh?.addEventListener("click", () => loadSnapshot({ notify: true }));
123
- loadSnapshot().catch(() => { });
124
- }