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,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
- }