codex-autorunner 0.1.1__py3-none-any.whl → 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. codex_autorunner/__main__.py +4 -0
  2. codex_autorunner/agents/__init__.py +20 -0
  3. codex_autorunner/agents/base.py +2 -2
  4. codex_autorunner/agents/codex/harness.py +1 -1
  5. codex_autorunner/agents/opencode/__init__.py +4 -0
  6. codex_autorunner/agents/opencode/agent_config.py +104 -0
  7. codex_autorunner/agents/opencode/client.py +305 -28
  8. codex_autorunner/agents/opencode/harness.py +71 -20
  9. codex_autorunner/agents/opencode/logging.py +225 -0
  10. codex_autorunner/agents/opencode/run_prompt.py +261 -0
  11. codex_autorunner/agents/opencode/runtime.py +1202 -132
  12. codex_autorunner/agents/opencode/supervisor.py +194 -68
  13. codex_autorunner/agents/registry.py +258 -0
  14. codex_autorunner/agents/types.py +2 -2
  15. codex_autorunner/api.py +25 -0
  16. codex_autorunner/bootstrap.py +19 -40
  17. codex_autorunner/cli.py +234 -151
  18. codex_autorunner/core/about_car.py +44 -32
  19. codex_autorunner/core/adapter_utils.py +21 -0
  20. codex_autorunner/core/app_server_events.py +15 -6
  21. codex_autorunner/core/app_server_logging.py +55 -15
  22. codex_autorunner/core/app_server_prompts.py +28 -259
  23. codex_autorunner/core/app_server_threads.py +15 -26
  24. codex_autorunner/core/circuit_breaker.py +183 -0
  25. codex_autorunner/core/codex_runner.py +6 -0
  26. codex_autorunner/core/config.py +555 -133
  27. codex_autorunner/core/docs.py +54 -9
  28. codex_autorunner/core/drafts.py +82 -0
  29. codex_autorunner/core/engine.py +828 -274
  30. codex_autorunner/core/exceptions.py +60 -0
  31. codex_autorunner/core/flows/__init__.py +25 -0
  32. codex_autorunner/core/flows/controller.py +178 -0
  33. codex_autorunner/core/flows/definition.py +82 -0
  34. codex_autorunner/core/flows/models.py +75 -0
  35. codex_autorunner/core/flows/runtime.py +351 -0
  36. codex_autorunner/core/flows/store.py +485 -0
  37. codex_autorunner/core/flows/transition.py +133 -0
  38. codex_autorunner/core/flows/worker_process.py +242 -0
  39. codex_autorunner/core/hub.py +21 -13
  40. codex_autorunner/core/locks.py +118 -1
  41. codex_autorunner/core/logging_utils.py +9 -6
  42. codex_autorunner/core/path_utils.py +123 -0
  43. codex_autorunner/core/prompt.py +15 -7
  44. codex_autorunner/core/redaction.py +29 -0
  45. codex_autorunner/core/retry.py +61 -0
  46. codex_autorunner/core/review.py +888 -0
  47. codex_autorunner/core/review_context.py +161 -0
  48. codex_autorunner/core/run_index.py +223 -0
  49. codex_autorunner/core/runner_controller.py +44 -1
  50. codex_autorunner/core/runner_process.py +30 -1
  51. codex_autorunner/core/sqlite_utils.py +32 -0
  52. codex_autorunner/core/state.py +273 -44
  53. codex_autorunner/core/static_assets.py +55 -0
  54. codex_autorunner/core/supervisor_utils.py +67 -0
  55. codex_autorunner/core/text_delta_coalescer.py +43 -0
  56. codex_autorunner/core/update.py +20 -11
  57. codex_autorunner/core/update_runner.py +2 -0
  58. codex_autorunner/core/usage.py +107 -75
  59. codex_autorunner/core/utils.py +167 -3
  60. codex_autorunner/discovery.py +3 -3
  61. codex_autorunner/flows/ticket_flow/__init__.py +3 -0
  62. codex_autorunner/flows/ticket_flow/definition.py +91 -0
  63. codex_autorunner/integrations/agents/__init__.py +27 -0
  64. codex_autorunner/integrations/agents/agent_backend.py +142 -0
  65. codex_autorunner/integrations/agents/codex_backend.py +307 -0
  66. codex_autorunner/integrations/agents/opencode_backend.py +325 -0
  67. codex_autorunner/integrations/agents/run_event.py +71 -0
  68. codex_autorunner/integrations/app_server/client.py +708 -153
  69. codex_autorunner/integrations/app_server/supervisor.py +59 -33
  70. codex_autorunner/integrations/telegram/adapter.py +474 -185
  71. codex_autorunner/integrations/telegram/api_schemas.py +120 -0
  72. codex_autorunner/integrations/telegram/config.py +239 -1
  73. codex_autorunner/integrations/telegram/constants.py +19 -1
  74. codex_autorunner/integrations/telegram/dispatch.py +44 -8
  75. codex_autorunner/integrations/telegram/doctor.py +47 -0
  76. codex_autorunner/integrations/telegram/handlers/approvals.py +12 -10
  77. codex_autorunner/integrations/telegram/handlers/callbacks.py +15 -1
  78. codex_autorunner/integrations/telegram/handlers/commands/__init__.py +29 -0
  79. codex_autorunner/integrations/telegram/handlers/commands/approvals.py +173 -0
  80. codex_autorunner/integrations/telegram/handlers/commands/execution.py +2595 -0
  81. codex_autorunner/integrations/telegram/handlers/commands/files.py +1408 -0
  82. codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
  83. codex_autorunner/integrations/telegram/handlers/commands/formatting.py +81 -0
  84. codex_autorunner/integrations/telegram/handlers/commands/github.py +1688 -0
  85. codex_autorunner/integrations/telegram/handlers/commands/shared.py +190 -0
  86. codex_autorunner/integrations/telegram/handlers/commands/voice.py +112 -0
  87. codex_autorunner/integrations/telegram/handlers/commands/workspace.py +2043 -0
  88. codex_autorunner/integrations/telegram/handlers/commands_runtime.py +954 -5689
  89. codex_autorunner/integrations/telegram/handlers/{commands.py → commands_spec.py} +11 -4
  90. codex_autorunner/integrations/telegram/handlers/messages.py +374 -49
  91. codex_autorunner/integrations/telegram/handlers/questions.py +389 -0
  92. codex_autorunner/integrations/telegram/handlers/selections.py +6 -4
  93. codex_autorunner/integrations/telegram/handlers/utils.py +171 -0
  94. codex_autorunner/integrations/telegram/helpers.py +90 -18
  95. codex_autorunner/integrations/telegram/notifications.py +126 -35
  96. codex_autorunner/integrations/telegram/outbox.py +214 -43
  97. codex_autorunner/integrations/telegram/progress_stream.py +42 -19
  98. codex_autorunner/integrations/telegram/runtime.py +24 -13
  99. codex_autorunner/integrations/telegram/service.py +500 -129
  100. codex_autorunner/integrations/telegram/state.py +1278 -330
  101. codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
  102. codex_autorunner/integrations/telegram/transport.py +37 -4
  103. codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
  104. codex_autorunner/integrations/telegram/types.py +22 -2
  105. codex_autorunner/integrations/telegram/voice.py +14 -15
  106. codex_autorunner/manifest.py +2 -0
  107. codex_autorunner/plugin_api.py +22 -0
  108. codex_autorunner/routes/__init__.py +25 -14
  109. codex_autorunner/routes/agents.py +18 -78
  110. codex_autorunner/routes/analytics.py +239 -0
  111. codex_autorunner/routes/base.py +142 -113
  112. codex_autorunner/routes/file_chat.py +836 -0
  113. codex_autorunner/routes/flows.py +980 -0
  114. codex_autorunner/routes/messages.py +459 -0
  115. codex_autorunner/routes/repos.py +17 -0
  116. codex_autorunner/routes/review.py +148 -0
  117. codex_autorunner/routes/sessions.py +16 -8
  118. codex_autorunner/routes/settings.py +22 -0
  119. codex_autorunner/routes/shared.py +33 -3
  120. codex_autorunner/routes/system.py +22 -1
  121. codex_autorunner/routes/usage.py +87 -0
  122. codex_autorunner/routes/voice.py +5 -13
  123. codex_autorunner/routes/workspace.py +271 -0
  124. codex_autorunner/server.py +2 -1
  125. codex_autorunner/static/agentControls.js +9 -1
  126. codex_autorunner/static/agentEvents.js +248 -0
  127. codex_autorunner/static/app.js +27 -22
  128. codex_autorunner/static/autoRefresh.js +29 -1
  129. codex_autorunner/static/bootstrap.js +1 -0
  130. codex_autorunner/static/bus.js +1 -0
  131. codex_autorunner/static/cache.js +1 -0
  132. codex_autorunner/static/constants.js +20 -4
  133. codex_autorunner/static/dashboard.js +162 -150
  134. codex_autorunner/static/diffRenderer.js +37 -0
  135. codex_autorunner/static/docChatCore.js +324 -0
  136. codex_autorunner/static/docChatStorage.js +65 -0
  137. codex_autorunner/static/docChatVoice.js +65 -0
  138. codex_autorunner/static/docEditor.js +133 -0
  139. codex_autorunner/static/env.js +1 -0
  140. codex_autorunner/static/eventSummarizer.js +166 -0
  141. codex_autorunner/static/fileChat.js +182 -0
  142. codex_autorunner/static/health.js +155 -0
  143. codex_autorunner/static/hub.js +67 -126
  144. codex_autorunner/static/index.html +788 -807
  145. codex_autorunner/static/liveUpdates.js +59 -0
  146. codex_autorunner/static/loader.js +1 -0
  147. codex_autorunner/static/messages.js +470 -0
  148. codex_autorunner/static/mobileCompact.js +2 -1
  149. codex_autorunner/static/settings.js +24 -205
  150. codex_autorunner/static/styles.css +7577 -3758
  151. codex_autorunner/static/tabs.js +28 -5
  152. codex_autorunner/static/terminal.js +14 -0
  153. codex_autorunner/static/terminalManager.js +53 -59
  154. codex_autorunner/static/ticketChatActions.js +333 -0
  155. codex_autorunner/static/ticketChatEvents.js +16 -0
  156. codex_autorunner/static/ticketChatStorage.js +16 -0
  157. codex_autorunner/static/ticketChatStream.js +264 -0
  158. codex_autorunner/static/ticketEditor.js +750 -0
  159. codex_autorunner/static/ticketVoice.js +9 -0
  160. codex_autorunner/static/tickets.js +1315 -0
  161. codex_autorunner/static/utils.js +32 -3
  162. codex_autorunner/static/voice.js +21 -7
  163. codex_autorunner/static/workspace.js +672 -0
  164. codex_autorunner/static/workspaceApi.js +53 -0
  165. codex_autorunner/static/workspaceFileBrowser.js +504 -0
  166. codex_autorunner/tickets/__init__.py +20 -0
  167. codex_autorunner/tickets/agent_pool.py +377 -0
  168. codex_autorunner/tickets/files.py +85 -0
  169. codex_autorunner/tickets/frontmatter.py +55 -0
  170. codex_autorunner/tickets/lint.py +102 -0
  171. codex_autorunner/tickets/models.py +95 -0
  172. codex_autorunner/tickets/outbox.py +232 -0
  173. codex_autorunner/tickets/replies.py +179 -0
  174. codex_autorunner/tickets/runner.py +823 -0
  175. codex_autorunner/tickets/spec_ingest.py +77 -0
  176. codex_autorunner/voice/capture.py +7 -7
  177. codex_autorunner/voice/service.py +51 -9
  178. codex_autorunner/web/app.py +419 -199
  179. codex_autorunner/web/hub_jobs.py +13 -2
  180. codex_autorunner/web/middleware.py +47 -13
  181. codex_autorunner/web/pty_session.py +26 -13
  182. codex_autorunner/web/schemas.py +114 -109
  183. codex_autorunner/web/static_assets.py +55 -42
  184. codex_autorunner/web/static_refresh.py +86 -0
  185. codex_autorunner/workspace/__init__.py +40 -0
  186. codex_autorunner/workspace/paths.py +319 -0
  187. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +20 -21
  188. codex_autorunner-1.0.0.dist-info/RECORD +251 -0
  189. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
  190. codex_autorunner/core/doc_chat.py +0 -1415
  191. codex_autorunner/core/snapshot.py +0 -580
  192. codex_autorunner/integrations/github/chatops.py +0 -268
  193. codex_autorunner/integrations/github/pr_flow.py +0 -1314
  194. codex_autorunner/routes/docs.py +0 -381
  195. codex_autorunner/routes/github.py +0 -327
  196. codex_autorunner/routes/runs.py +0 -118
  197. codex_autorunner/spec_ingest.py +0 -788
  198. codex_autorunner/static/docChatActions.js +0 -279
  199. codex_autorunner/static/docChatEvents.js +0 -300
  200. codex_autorunner/static/docChatRender.js +0 -205
  201. codex_autorunner/static/docChatStream.js +0 -361
  202. codex_autorunner/static/docs.js +0 -20
  203. codex_autorunner/static/docsClipboard.js +0 -69
  204. codex_autorunner/static/docsCrud.js +0 -257
  205. codex_autorunner/static/docsDocUpdates.js +0 -62
  206. codex_autorunner/static/docsDrafts.js +0 -16
  207. codex_autorunner/static/docsElements.js +0 -69
  208. codex_autorunner/static/docsInit.js +0 -274
  209. codex_autorunner/static/docsParse.js +0 -160
  210. codex_autorunner/static/docsSnapshot.js +0 -87
  211. codex_autorunner/static/docsSpecIngest.js +0 -263
  212. codex_autorunner/static/docsState.js +0 -127
  213. codex_autorunner/static/docsThreadRegistry.js +0 -44
  214. codex_autorunner/static/docsUi.js +0 -153
  215. codex_autorunner/static/docsVoice.js +0 -56
  216. codex_autorunner/static/github.js +0 -442
  217. codex_autorunner/static/logs.js +0 -640
  218. codex_autorunner/static/runs.js +0 -409
  219. codex_autorunner/static/snapshot.js +0 -124
  220. codex_autorunner/static/state.js +0 -86
  221. codex_autorunner/static/todoPreview.js +0 -27
  222. codex_autorunner/workspace.py +0 -16
  223. codex_autorunner-0.1.1.dist-info/RECORD +0 -191
  224. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
  225. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
  226. {codex_autorunner-0.1.1.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
@@ -1,640 +0,0 @@
1
- import { api, flash, streamEvents, getUrlParams, updateUrlParams } from "./utils.js";
2
- import { publish, subscribe } from "./bus.js";
3
- import { saveToCache, loadFromCache } from "./cache.js";
4
- import { CONSTANTS } from "./constants.js";
5
- const logRunIdInput = document.getElementById("log-run-id");
6
- const logTailInput = document.getElementById("log-tail");
7
- const toggleLogStreamButton = document.getElementById("toggle-log-stream");
8
- const showTimestampToggle = document.getElementById("log-show-timestamp");
9
- const showRunToggle = document.getElementById("log-show-run");
10
- const showSummaryToggle = document.getElementById("log-show-summary");
11
- const jumpBottomButton = document.getElementById("log-jump-bottom");
12
- const loadOlderButton = document.getElementById("log-load-older");
13
- let stopLogStream = null;
14
- let lastKnownRunId = null;
15
- let rawLogLines = [];
16
- let autoScrollEnabled = true;
17
- let renderedStartIndex = 0;
18
- let renderedEndIndex = 0;
19
- let isViewingTail = true;
20
- let renderState = null;
21
- let logContexts = [];
22
- let logContextState = { inPromptBlock: false, inDiffBlock: false };
23
- const DOC_CHAT_META_RE = /doc-chat id=[a-f0-9]+ (result=|exit_code=)/i;
24
- const LINE_PATTERNS = {
25
- runStart: /^=== run \d+ start/,
26
- runEnd: /^=== run \d+ end/,
27
- thinking: /^thinking$/i,
28
- thinkingContent: /^\*\*.+\*\*$/,
29
- thinkingMultiline: /^I'm (preparing|planning|considering|reviewing|analyzing|checking|looking|reading|searching)/i,
30
- execStart: /^exec$/i,
31
- execCommand: /^\/bin\/(zsh|bash|sh)\s+-[a-z]+\s+['"]?.+in\s+\//i,
32
- applyPatch: /^apply_patch\(/i,
33
- fileUpdate: /^file update:?$/i,
34
- fileModified: /^M\s+[\w./]/,
35
- diffGitHeader: /^diff --git /,
36
- diffFileHeader: /^(---|\+\+\+)\s+[ab]\//,
37
- diffIndex: /^index [a-f0-9]+\.\.[a-f0-9]+/,
38
- diffHunk: /^@@\s+-\d+,?\d*\s+\+\d+,?\d*\s+@@/,
39
- promptMarker: /^<(SPEC|WORK_DOCS|TODO|PROGRESS|OPINIONS|TARGET_DOC|RECENT_RUN|SYSTEM|USER|ASSISTANT)>$/,
40
- promptMarkerEnd: /^<\/(SPEC|WORK_DOCS|TODO|PROGRESS|OPINIONS|TARGET_DOC|RECENT_RUN|SYSTEM|USER|ASSISTANT)>$/,
41
- mcpStartup: /^mcp startup:/i,
42
- tokensUsed: /^tokens used/i,
43
- agentOutput: /^Agent:\s*/i,
44
- success: /succeeded in \d+ms/i,
45
- exitCode: /exited \d+ in \d+ms/i,
46
- testOutput: /^(={3,}\s*(test session|.*passed|.*failed)|PASSED|FAILED|ERROR)/i,
47
- pythonTraceback: /^(Traceback \(most recent|File ".*", line \d+|.*Error:)/i,
48
- markdownList: /^- (\[[ x]\]\s)?[A-Z]/,
49
- };
50
- function classifyLine(line, context = { inPromptBlock: false, inDiffBlock: false }) {
51
- const stripped = line
52
- .replace(/^\[[^\]]*]\s*/, "")
53
- .replace(/^(run=\d+\s*)?(stdout|stderr):\s*/, "")
54
- .replace(/^doc-chat id=[a-f0-9]+ stdout:\s*/i, "")
55
- .trim();
56
- if (LINE_PATTERNS.runStart.test(stripped))
57
- return { type: "run-start", priority: 1, resetDiff: true };
58
- if (LINE_PATTERNS.runEnd.test(stripped))
59
- return { type: "run-end", priority: 1, resetDiff: true };
60
- if (LINE_PATTERNS.agentOutput.test(stripped))
61
- return { type: "agent-output", priority: 1 };
62
- if (LINE_PATTERNS.thinking.test(stripped))
63
- return { type: "thinking-label", priority: 2 };
64
- if (LINE_PATTERNS.thinkingContent.test(stripped))
65
- return { type: "thinking", priority: 2 };
66
- if (LINE_PATTERNS.thinkingMultiline.test(stripped))
67
- return { type: "thinking", priority: 2 };
68
- if (LINE_PATTERNS.execStart.test(stripped))
69
- return { type: "exec-label", priority: 3 };
70
- if (LINE_PATTERNS.execCommand.test(stripped))
71
- return { type: "exec-command", priority: 3 };
72
- if (LINE_PATTERNS.applyPatch.test(stripped))
73
- return { type: "exec-command", priority: 3 };
74
- if (LINE_PATTERNS.fileUpdate.test(stripped))
75
- return { type: "file-update-label", priority: 3, startDiff: true };
76
- if (LINE_PATTERNS.fileModified.test(stripped))
77
- return { type: "file-modified", priority: 3 };
78
- if (LINE_PATTERNS.testOutput.test(stripped))
79
- return { type: "test-output", priority: 3 };
80
- if (LINE_PATTERNS.pythonTraceback.test(stripped))
81
- return { type: "error-output", priority: 2 };
82
- if (LINE_PATTERNS.diffGitHeader.test(stripped))
83
- return { type: "diff-header", priority: 4, startDiff: true };
84
- if (LINE_PATTERNS.diffFileHeader.test(stripped))
85
- return { type: "diff-header", priority: 4 };
86
- if (LINE_PATTERNS.diffIndex.test(stripped))
87
- return { type: "diff-header", priority: 4 };
88
- if (LINE_PATTERNS.diffHunk.test(stripped))
89
- return { type: "diff-hunk", priority: 4 };
90
- if (context.inDiffBlock) {
91
- if (/^\+[^+]/.test(stripped) && !LINE_PATTERNS.markdownList.test(stripped))
92
- return { type: "diff-add", priority: 4 };
93
- if (/^-[^-]/.test(stripped) && !LINE_PATTERNS.markdownList.test(stripped))
94
- return { type: "diff-del", priority: 4 };
95
- }
96
- if (LINE_PATTERNS.promptMarker.test(stripped))
97
- return { type: "prompt-marker", priority: 5 };
98
- if (LINE_PATTERNS.promptMarkerEnd.test(stripped))
99
- return { type: "prompt-marker-end", priority: 5 };
100
- if (LINE_PATTERNS.mcpStartup.test(stripped))
101
- return { type: "system", priority: 6 };
102
- if (LINE_PATTERNS.tokensUsed.test(stripped))
103
- return { type: "tokens", priority: 6 };
104
- if (LINE_PATTERNS.success.test(stripped))
105
- return { type: "success", priority: 3 };
106
- if (LINE_PATTERNS.exitCode.test(stripped))
107
- return { type: "exit-code", priority: 3 };
108
- if (context.inPromptBlock)
109
- return { type: "prompt-context", priority: 5 };
110
- return { type: "output", priority: 4 };
111
- }
112
- function isSummaryMode() {
113
- return !showSummaryToggle || showSummaryToggle.checked;
114
- }
115
- function processLine(line) {
116
- let next = line;
117
- next = next.replace(/^=== run (\d+)\s+chat(\s|$)/, "=== run $1$2");
118
- if (showTimestampToggle && !showTimestampToggle.checked) {
119
- next = next.replace(/^\[[^\]]*]\s*/, "");
120
- }
121
- if (showRunToggle && !showRunToggle.checked) {
122
- if (next.startsWith("[")) {
123
- next = next.replace(/^(\[[^\]]+]\s*)run=\d+\s*/, "$1");
124
- }
125
- else {
126
- next = next.replace(/^run=\d+\s*/, "");
127
- }
128
- }
129
- next = next.replace(/^(\[[^\]]+]\s*)?(run=\d+\s*)?chat:\s*/, "$1$2");
130
- next = next.replace(/^(\[[^\]]+]\s*)?(run=\d+\s*)?(stdout|stderr):\s*/, "$1$2");
131
- next = next.replace(/^(\[[^\]]+]\s*)?(run=\d+\s*)?doc-chat id=[a-f0-9]+ stdout:\s*/i, "$1$2");
132
- return next.trimEnd();
133
- }
134
- function shouldOmitLine(line) {
135
- if (showRunToggle && !showRunToggle.checked && DOC_CHAT_META_RE.test(line)) {
136
- return true;
137
- }
138
- return false;
139
- }
140
- function resetRenderState() {
141
- renderState = {
142
- inPromptBlock: false,
143
- promptBlockDetails: null,
144
- promptBlockContent: null,
145
- promptBlockType: null,
146
- promptLineCount: 0,
147
- inDiffBlock: false,
148
- };
149
- }
150
- function resetLogContexts() {
151
- logContexts = [];
152
- logContextState = { inPromptBlock: false, inDiffBlock: false };
153
- }
154
- function updateLogContextForLine(line) {
155
- logContexts.push({ ...logContextState });
156
- const classification = classifyLine(line, logContextState);
157
- if (classification.startDiff) {
158
- logContextState.inDiffBlock = true;
159
- }
160
- if (classification.resetDiff) {
161
- logContextState.inDiffBlock = false;
162
- }
163
- if (classification.type === "prompt-marker") {
164
- logContextState.inPromptBlock = true;
165
- logContextState.inDiffBlock = false;
166
- }
167
- else if (classification.type === "prompt-marker-end") {
168
- logContextState.inPromptBlock = false;
169
- }
170
- }
171
- function rebuildLogContexts() {
172
- resetLogContexts();
173
- rawLogLines.forEach((line) => updateLogContextForLine(line));
174
- }
175
- function finalizePromptBlock() {
176
- if (!renderState || !renderState.promptBlockDetails)
177
- return;
178
- const countEl = renderState.promptBlockDetails.querySelector(".log-context-count");
179
- if (countEl) {
180
- countEl.textContent = `(${renderState.promptLineCount} lines)`;
181
- }
182
- }
183
- function startPromptBlock(output, label) {
184
- if (!renderState)
185
- return;
186
- renderState.promptBlockType = label;
187
- renderState.promptBlockDetails = document.createElement("details");
188
- renderState.promptBlockDetails.className = "log-context-block";
189
- const summary = document.createElement("summary");
190
- summary.className = "log-context-summary";
191
- summary.innerHTML = `<span class="log-context-icon">▶</span> ${label} <span class="log-context-count"></span>`;
192
- renderState.promptBlockDetails.appendChild(summary);
193
- renderState.promptBlockContent = document.createElement("div");
194
- renderState.promptBlockContent.className = "log-context-content";
195
- renderState.promptBlockDetails.appendChild(renderState.promptBlockContent);
196
- renderState.promptLineCount = 0;
197
- output.appendChild(renderState.promptBlockDetails);
198
- }
199
- function appendRawLine(line, output) {
200
- const isBlank = line.trim() === "";
201
- const div = document.createElement("div");
202
- div.textContent = line;
203
- div.className = isBlank ? "log-line log-blank" : "log-line log-raw";
204
- output.appendChild(div);
205
- }
206
- function appendRenderedLine(line, output) {
207
- if (!renderState)
208
- resetRenderState();
209
- if (shouldOmitLine(line))
210
- return;
211
- const processed = processLine(line).trimEnd();
212
- const classification = classifyLine(line, renderState);
213
- if (classification.startDiff) {
214
- renderState.inDiffBlock = true;
215
- }
216
- if (classification.resetDiff) {
217
- renderState.inDiffBlock = false;
218
- }
219
- if (classification.type === "prompt-marker") {
220
- renderState.inPromptBlock = true;
221
- renderState.inDiffBlock = false;
222
- const match = processed.match(/<(\w+)>/);
223
- const blockLabel = match ? match[1] : "CONTEXT";
224
- startPromptBlock(output, blockLabel);
225
- return;
226
- }
227
- if (classification.type === "prompt-marker-end") {
228
- finalizePromptBlock();
229
- if (renderState) {
230
- renderState.promptBlockDetails = null;
231
- renderState.promptBlockContent = null;
232
- renderState.promptBlockType = null;
233
- renderState.promptLineCount = 0;
234
- renderState.inPromptBlock = false;
235
- }
236
- return;
237
- }
238
- if (renderState &&
239
- renderState.promptBlockContent &&
240
- renderState.inPromptBlock &&
241
- (classification.type === "prompt-context" || classification.type === "output")) {
242
- const div = document.createElement("div");
243
- div.textContent = processed;
244
- div.className = "log-line log-prompt-context";
245
- renderState.promptBlockContent.appendChild(div);
246
- renderState.promptLineCount++;
247
- return;
248
- }
249
- const isBlank = processed.trim() === "";
250
- const div = document.createElement("div");
251
- div.textContent = processed;
252
- if (isBlank) {
253
- div.className = "log-line log-blank";
254
- }
255
- else {
256
- div.className = `log-line log-${classification.type}`;
257
- div.dataset.logType = classification.type;
258
- div.dataset.priority = String(classification.priority);
259
- }
260
- if (classification.type === "thinking-label" || classification.type === "thinking") {
261
- div.dataset.icon = "💭";
262
- }
263
- else if (classification.type === "exec-label" || classification.type === "exec-command") {
264
- div.dataset.icon = "⚡";
265
- }
266
- else if (classification.type === "file-update-label" ||
267
- classification.type === "file-modified") {
268
- div.dataset.icon = "📝";
269
- }
270
- else if (classification.type === "agent-output") {
271
- div.dataset.icon = "✨";
272
- }
273
- else if (classification.type === "run-start" || classification.type === "run-end") {
274
- div.dataset.icon = "🔄";
275
- }
276
- else if (classification.type === "success") {
277
- div.dataset.icon = "✓";
278
- }
279
- else if (classification.type === "tokens") {
280
- div.dataset.icon = "📊";
281
- }
282
- output.appendChild(div);
283
- }
284
- function trimLogBuffer() {
285
- const maxLines = CONSTANTS.UI.MAX_LOG_LINES_IN_MEMORY;
286
- if (!maxLines || rawLogLines.length <= maxLines)
287
- return;
288
- const overflow = rawLogLines.length - maxLines;
289
- rawLogLines = rawLogLines.slice(overflow);
290
- if (logContexts.length > overflow) {
291
- logContexts = logContexts.slice(overflow);
292
- }
293
- else {
294
- logContexts = [];
295
- }
296
- renderedStartIndex = Math.max(0, renderedStartIndex - overflow);
297
- renderedEndIndex = Math.max(0, renderedEndIndex - overflow);
298
- }
299
- function updateLoadOlderButton() {
300
- if (!loadOlderButton)
301
- return;
302
- if (renderedStartIndex > 0) {
303
- loadOlderButton.classList.remove("hidden");
304
- }
305
- else {
306
- loadOlderButton.classList.add("hidden");
307
- }
308
- }
309
- function applyLogUrlState() {
310
- const params = getUrlParams();
311
- const runId = params.get("run");
312
- const tail = params.get("tail");
313
- const summary = params.get("summary");
314
- if (runId !== null && logRunIdInput) {
315
- logRunIdInput.value = runId;
316
- }
317
- if (tail !== null && logTailInput) {
318
- logTailInput.value = tail;
319
- }
320
- if (summary !== null && showSummaryToggle) {
321
- showSummaryToggle.checked = !(summary === "0" || summary.toLowerCase() === "false");
322
- }
323
- if (runId) {
324
- isViewingTail = false;
325
- }
326
- }
327
- function syncLogUrlState() {
328
- const runId = logRunIdInput?.value?.trim() || "";
329
- const tail = logTailInput?.value?.trim() || "";
330
- updateUrlParams({
331
- run: runId || null,
332
- tail: runId ? null : tail || null,
333
- summary: showSummaryToggle?.checked ? null : "0",
334
- });
335
- }
336
- function renderLogWindow({ startIndex = null, followTail = true } = {}) {
337
- const output = document.getElementById("log-output");
338
- if (!output)
339
- return;
340
- if (rawLogLines.length === 0) {
341
- output.innerHTML = "";
342
- output.textContent = "(empty log)";
343
- output.dataset.isPlaceholder = "true";
344
- renderedStartIndex = 0;
345
- renderedEndIndex = 0;
346
- isViewingTail = true;
347
- updateLoadOlderButton();
348
- return;
349
- }
350
- const endIndex = rawLogLines.length;
351
- let windowStart = startIndex;
352
- if (followTail || windowStart === null) {
353
- windowStart = Math.max(0, endIndex - CONSTANTS.UI.MAX_LOG_LINES_IN_DOM);
354
- }
355
- const windowEnd = Math.min(endIndex, windowStart + CONSTANTS.UI.MAX_LOG_LINES_IN_DOM);
356
- if (!isSummaryMode()) {
357
- output.innerHTML = "";
358
- delete output.dataset.isPlaceholder;
359
- for (let i = windowStart; i < windowEnd; i += 1) {
360
- appendRawLine(rawLogLines[i], output);
361
- }
362
- renderedStartIndex = windowStart;
363
- renderedEndIndex = windowEnd;
364
- isViewingTail = followTail && windowEnd === endIndex;
365
- updateLoadOlderButton();
366
- if (isViewingTail) {
367
- scrollLogsToBottom(true);
368
- }
369
- return;
370
- }
371
- output.innerHTML = "";
372
- delete output.dataset.isPlaceholder;
373
- resetRenderState();
374
- const startContext = logContexts[windowStart];
375
- if (startContext && renderState) {
376
- renderState.inPromptBlock = startContext.inPromptBlock;
377
- renderState.inDiffBlock = startContext.inDiffBlock;
378
- if (renderState.inPromptBlock) {
379
- startPromptBlock(output, "CONTEXT (continued)");
380
- }
381
- }
382
- for (let i = windowStart; i < windowEnd; i += 1) {
383
- appendRenderedLine(rawLogLines[i], output);
384
- }
385
- finalizePromptBlock();
386
- renderedStartIndex = windowStart;
387
- renderedEndIndex = windowEnd;
388
- isViewingTail = followTail && windowEnd === endIndex;
389
- updateLoadOlderButton();
390
- if (isViewingTail) {
391
- scrollLogsToBottom(true);
392
- }
393
- }
394
- function appendLogLine(line) {
395
- const output = document.getElementById("log-output");
396
- if (!output)
397
- return;
398
- if (output.dataset.isPlaceholder === "true") {
399
- output.innerHTML = "";
400
- delete output.dataset.isPlaceholder;
401
- rawLogLines = [];
402
- resetRenderState();
403
- resetLogContexts();
404
- renderedStartIndex = 0;
405
- renderedEndIndex = 0;
406
- isViewingTail = true;
407
- }
408
- rawLogLines.push(line);
409
- updateLogContextForLine(line);
410
- trimLogBuffer();
411
- if (!isViewingTail) {
412
- publish("logs:line", line);
413
- updateLoadOlderButton();
414
- return;
415
- }
416
- if (!isSummaryMode()) {
417
- appendRawLine(line, output);
418
- }
419
- else {
420
- appendRenderedLine(line, output);
421
- }
422
- renderedEndIndex = rawLogLines.length;
423
- if (output.childElementCount > CONSTANTS.UI.MAX_LOG_LINES_IN_DOM) {
424
- output.firstElementChild?.remove();
425
- }
426
- renderedStartIndex = Math.max(0, renderedEndIndex - output.childElementCount);
427
- updateLoadOlderButton();
428
- publish("logs:line", line);
429
- scrollLogsToBottom();
430
- }
431
- function scrollLogsToBottom(force = false) {
432
- const output = document.getElementById("log-output");
433
- if (!output)
434
- return;
435
- if (!autoScrollEnabled && !force)
436
- return;
437
- requestAnimationFrame(() => {
438
- output.scrollTop = output.scrollHeight;
439
- });
440
- }
441
- function updateJumpButtonVisibility() {
442
- const output = document.getElementById("log-output");
443
- if (!output || !jumpBottomButton)
444
- return;
445
- const isNearBottom = output.scrollHeight - output.scrollTop - output.clientHeight < 100;
446
- if (isNearBottom) {
447
- jumpBottomButton.classList.add("hidden");
448
- autoScrollEnabled = true;
449
- }
450
- else {
451
- jumpBottomButton.classList.remove("hidden");
452
- autoScrollEnabled = false;
453
- }
454
- }
455
- function setLogStreamButton(active) {
456
- if (toggleLogStreamButton) {
457
- toggleLogStreamButton.textContent = active ? "Stop stream" : "Start stream";
458
- }
459
- }
460
- async function loadLogs() {
461
- syncLogUrlState();
462
- const runId = logRunIdInput?.value || "";
463
- const tail = logTailInput?.value || "200";
464
- const params = new URLSearchParams();
465
- if (runId) {
466
- params.set("run_id", runId);
467
- }
468
- else if (tail) {
469
- params.set("tail", tail);
470
- }
471
- const path = params.toString()
472
- ? `/api/logs?${params.toString()}`
473
- : "/api/logs";
474
- try {
475
- const data = await api(path);
476
- const text = typeof data === "string" ? data : data.log || "";
477
- const output = document.getElementById("log-output");
478
- if (!output)
479
- return;
480
- if (text) {
481
- rawLogLines = text.split("\n");
482
- trimLogBuffer();
483
- rebuildLogContexts();
484
- delete output.dataset.isPlaceholder;
485
- isViewingTail = true;
486
- renderLogs();
487
- if (!runId) {
488
- const lines = rawLogLines.slice(-200);
489
- saveToCache("logs:tail", lines.join("\n"));
490
- }
491
- }
492
- else {
493
- output.textContent = "(empty log)";
494
- output.dataset.isPlaceholder = "true";
495
- rawLogLines = [];
496
- resetRenderState();
497
- resetLogContexts();
498
- renderedStartIndex = 0;
499
- renderedEndIndex = 0;
500
- isViewingTail = true;
501
- updateLoadOlderButton();
502
- if (!runId) {
503
- saveToCache("logs:tail", "");
504
- }
505
- }
506
- flash("Logs loaded", "success");
507
- publish("logs:loaded", { runId, tail, text });
508
- }
509
- catch (err) {
510
- flash(err.message || "Failed to load logs", "error");
511
- }
512
- }
513
- function stopLogStreaming() {
514
- if (stopLogStream) {
515
- stopLogStream();
516
- stopLogStream = null;
517
- }
518
- setLogStreamButton(false);
519
- publish("logs:streaming", false);
520
- }
521
- function startLogStreaming() {
522
- if (stopLogStream)
523
- return;
524
- const output = document.getElementById("log-output");
525
- if (!output)
526
- return;
527
- output.textContent = "(listening...)";
528
- output.dataset.isPlaceholder = "true";
529
- rawLogLines = [];
530
- resetRenderState();
531
- resetLogContexts();
532
- renderedStartIndex = 0;
533
- renderedEndIndex = 0;
534
- isViewingTail = true;
535
- updateLoadOlderButton();
536
- stopLogStream = streamEvents("/api/logs/stream", {
537
- onMessage: (data) => {
538
- appendLogLine(data || "");
539
- },
540
- onError: (err) => {
541
- flash(err.message || "Stream error", "error");
542
- stopLogStreaming();
543
- },
544
- onFinish: () => {
545
- stopLogStream = null;
546
- setLogStreamButton(false);
547
- publish("logs:streaming", false);
548
- },
549
- });
550
- setLogStreamButton(true);
551
- publish("logs:streaming", true);
552
- flash("Streaming logs…", "info");
553
- }
554
- function syncRunIdPlaceholder(state) {
555
- lastKnownRunId = state?.last_run_id ?? null;
556
- if (logRunIdInput) {
557
- logRunIdInput.placeholder = lastKnownRunId
558
- ? `latest (${lastKnownRunId})`
559
- : "latest";
560
- }
561
- }
562
- function renderLogs() {
563
- renderLogWindow({ followTail: isViewingTail });
564
- }
565
- export function initLogs() {
566
- applyLogUrlState();
567
- const loadLogsButton = document.getElementById("load-logs");
568
- if (loadLogsButton) {
569
- loadLogsButton.addEventListener("click", loadLogs);
570
- }
571
- if (toggleLogStreamButton) {
572
- toggleLogStreamButton.addEventListener("click", () => {
573
- if (stopLogStream) {
574
- stopLogStreaming();
575
- }
576
- else {
577
- startLogStreaming();
578
- }
579
- });
580
- }
581
- subscribe("state:update", syncRunIdPlaceholder);
582
- subscribe("tab:change", (tab) => {
583
- if (tab !== "logs" && stopLogStream) {
584
- stopLogStreaming();
585
- }
586
- });
587
- if (showTimestampToggle) {
588
- showTimestampToggle.addEventListener("change", renderLogs);
589
- }
590
- if (showRunToggle) {
591
- showRunToggle.addEventListener("change", renderLogs);
592
- }
593
- if (showSummaryToggle) {
594
- showSummaryToggle.addEventListener("change", () => {
595
- syncLogUrlState();
596
- renderLogs();
597
- });
598
- }
599
- if (jumpBottomButton) {
600
- jumpBottomButton.addEventListener("click", () => {
601
- if (!isViewingTail) {
602
- isViewingTail = true;
603
- renderLogs();
604
- }
605
- autoScrollEnabled = true;
606
- scrollLogsToBottom(true);
607
- jumpBottomButton.classList.add("hidden");
608
- });
609
- }
610
- if (loadOlderButton) {
611
- loadOlderButton.addEventListener("click", () => {
612
- if (renderedStartIndex <= 0)
613
- return;
614
- const nextStart = Math.max(0, renderedStartIndex - CONSTANTS.UI.LOG_PAGE_SIZE);
615
- isViewingTail = false;
616
- autoScrollEnabled = false;
617
- renderLogWindow({ startIndex: nextStart, followTail: false });
618
- });
619
- }
620
- const output = document.getElementById("log-output");
621
- if (output) {
622
- output.addEventListener("scroll", updateJumpButtonVisibility);
623
- }
624
- const cachedLogs = loadFromCache("logs:tail");
625
- if (cachedLogs) {
626
- const output = document.getElementById("log-output");
627
- if (output) {
628
- rawLogLines = cachedLogs.split("\n");
629
- if (rawLogLines.length > 0) {
630
- trimLogBuffer();
631
- rebuildLogContexts();
632
- delete output.dataset.isPlaceholder;
633
- isViewingTail = true;
634
- renderLogs();
635
- scrollLogsToBottom(true);
636
- }
637
- }
638
- }
639
- loadLogs();
640
- }