codex-autorunner 0.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.
- codex_autorunner/__init__.py +3 -0
- codex_autorunner/bootstrap.py +151 -0
- codex_autorunner/cli.py +886 -0
- codex_autorunner/codex_cli.py +79 -0
- codex_autorunner/codex_runner.py +17 -0
- codex_autorunner/core/__init__.py +1 -0
- codex_autorunner/core/about_car.py +125 -0
- codex_autorunner/core/codex_runner.py +100 -0
- codex_autorunner/core/config.py +1465 -0
- codex_autorunner/core/doc_chat.py +547 -0
- codex_autorunner/core/docs.py +37 -0
- codex_autorunner/core/engine.py +720 -0
- codex_autorunner/core/git_utils.py +206 -0
- codex_autorunner/core/hub.py +756 -0
- codex_autorunner/core/injected_context.py +9 -0
- codex_autorunner/core/locks.py +57 -0
- codex_autorunner/core/logging_utils.py +158 -0
- codex_autorunner/core/notifications.py +465 -0
- codex_autorunner/core/optional_dependencies.py +41 -0
- codex_autorunner/core/prompt.py +107 -0
- codex_autorunner/core/prompts.py +275 -0
- codex_autorunner/core/request_context.py +21 -0
- codex_autorunner/core/runner_controller.py +116 -0
- codex_autorunner/core/runner_process.py +29 -0
- codex_autorunner/core/snapshot.py +576 -0
- codex_autorunner/core/state.py +156 -0
- codex_autorunner/core/update.py +567 -0
- codex_autorunner/core/update_runner.py +44 -0
- codex_autorunner/core/usage.py +1221 -0
- codex_autorunner/core/utils.py +108 -0
- codex_autorunner/discovery.py +102 -0
- codex_autorunner/housekeeping.py +423 -0
- codex_autorunner/integrations/__init__.py +1 -0
- codex_autorunner/integrations/app_server/__init__.py +6 -0
- codex_autorunner/integrations/app_server/client.py +1386 -0
- codex_autorunner/integrations/app_server/supervisor.py +206 -0
- codex_autorunner/integrations/github/__init__.py +10 -0
- codex_autorunner/integrations/github/service.py +889 -0
- codex_autorunner/integrations/telegram/__init__.py +1 -0
- codex_autorunner/integrations/telegram/adapter.py +1401 -0
- codex_autorunner/integrations/telegram/commands_registry.py +104 -0
- codex_autorunner/integrations/telegram/config.py +450 -0
- codex_autorunner/integrations/telegram/constants.py +154 -0
- codex_autorunner/integrations/telegram/dispatch.py +162 -0
- codex_autorunner/integrations/telegram/handlers/__init__.py +0 -0
- codex_autorunner/integrations/telegram/handlers/approvals.py +241 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +72 -0
- codex_autorunner/integrations/telegram/handlers/commands.py +160 -0
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +5262 -0
- codex_autorunner/integrations/telegram/handlers/messages.py +477 -0
- codex_autorunner/integrations/telegram/handlers/selections.py +545 -0
- codex_autorunner/integrations/telegram/helpers.py +2084 -0
- codex_autorunner/integrations/telegram/notifications.py +164 -0
- codex_autorunner/integrations/telegram/outbox.py +174 -0
- codex_autorunner/integrations/telegram/rendering.py +102 -0
- codex_autorunner/integrations/telegram/retry.py +37 -0
- codex_autorunner/integrations/telegram/runtime.py +270 -0
- codex_autorunner/integrations/telegram/service.py +921 -0
- codex_autorunner/integrations/telegram/state.py +1223 -0
- codex_autorunner/integrations/telegram/transport.py +318 -0
- codex_autorunner/integrations/telegram/types.py +57 -0
- codex_autorunner/integrations/telegram/voice.py +413 -0
- codex_autorunner/manifest.py +150 -0
- codex_autorunner/routes/__init__.py +53 -0
- codex_autorunner/routes/base.py +470 -0
- codex_autorunner/routes/docs.py +275 -0
- codex_autorunner/routes/github.py +197 -0
- codex_autorunner/routes/repos.py +121 -0
- codex_autorunner/routes/sessions.py +137 -0
- codex_autorunner/routes/shared.py +137 -0
- codex_autorunner/routes/system.py +175 -0
- codex_autorunner/routes/terminal_images.py +107 -0
- codex_autorunner/routes/voice.py +128 -0
- codex_autorunner/server.py +23 -0
- codex_autorunner/spec_ingest.py +113 -0
- codex_autorunner/static/app.js +95 -0
- codex_autorunner/static/autoRefresh.js +209 -0
- codex_autorunner/static/bootstrap.js +105 -0
- codex_autorunner/static/bus.js +23 -0
- codex_autorunner/static/cache.js +52 -0
- codex_autorunner/static/constants.js +48 -0
- codex_autorunner/static/dashboard.js +795 -0
- codex_autorunner/static/docs.js +1514 -0
- codex_autorunner/static/env.js +99 -0
- codex_autorunner/static/github.js +168 -0
- codex_autorunner/static/hub.js +1511 -0
- codex_autorunner/static/index.html +622 -0
- codex_autorunner/static/loader.js +28 -0
- codex_autorunner/static/logs.js +690 -0
- codex_autorunner/static/mobileCompact.js +300 -0
- codex_autorunner/static/snapshot.js +116 -0
- codex_autorunner/static/state.js +87 -0
- codex_autorunner/static/styles.css +4966 -0
- codex_autorunner/static/tabs.js +50 -0
- codex_autorunner/static/terminal.js +21 -0
- codex_autorunner/static/terminalManager.js +3535 -0
- codex_autorunner/static/todoPreview.js +25 -0
- codex_autorunner/static/types.d.ts +8 -0
- codex_autorunner/static/utils.js +597 -0
- codex_autorunner/static/vendor/LICENSE.xterm +24 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-400-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-500-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-cyrillic.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-greek.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin-ext.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-latin.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/JetBrainsMono-600-vietnamese.woff2 +0 -0
- codex_autorunner/static/vendor/fonts/jetbrains-mono/OFL.txt +93 -0
- codex_autorunner/static/vendor/xterm-addon-fit.js +2 -0
- codex_autorunner/static/vendor/xterm.css +209 -0
- codex_autorunner/static/vendor/xterm.js +2 -0
- codex_autorunner/static/voice.js +591 -0
- codex_autorunner/voice/__init__.py +39 -0
- codex_autorunner/voice/capture.py +349 -0
- codex_autorunner/voice/config.py +167 -0
- codex_autorunner/voice/provider.py +66 -0
- codex_autorunner/voice/providers/__init__.py +7 -0
- codex_autorunner/voice/providers/openai_whisper.py +345 -0
- codex_autorunner/voice/resolver.py +36 -0
- codex_autorunner/voice/service.py +210 -0
- codex_autorunner/web/__init__.py +1 -0
- codex_autorunner/web/app.py +1037 -0
- codex_autorunner/web/hub_jobs.py +181 -0
- codex_autorunner/web/middleware.py +552 -0
- codex_autorunner/web/pty_session.py +357 -0
- codex_autorunner/web/runner_manager.py +25 -0
- codex_autorunner/web/schemas.py +253 -0
- codex_autorunner/web/static_assets.py +430 -0
- codex_autorunner/web/terminal_sessions.py +78 -0
- codex_autorunner/workspace.py +16 -0
- codex_autorunner-0.1.0.dist-info/METADATA +240 -0
- codex_autorunner-0.1.0.dist-info/RECORD +147 -0
- codex_autorunner-0.1.0.dist-info/WHEEL +5 -0
- codex_autorunner-0.1.0.dist-info/entry_points.txt +3 -0
- codex_autorunner-0.1.0.dist-info/licenses/LICENSE +21 -0
- codex_autorunner-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,690 @@
|
|
|
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
|
+
|
|
6
|
+
const logRunIdInput = document.getElementById("log-run-id");
|
|
7
|
+
const logTailInput = document.getElementById("log-tail");
|
|
8
|
+
const toggleLogStreamButton = document.getElementById("toggle-log-stream");
|
|
9
|
+
const showTimestampToggle = document.getElementById("log-show-timestamp");
|
|
10
|
+
const showRunToggle = document.getElementById("log-show-run");
|
|
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
|
+
// Matches doc-chat metadata lines (start/result) that we might want to hide for cleaner view
|
|
24
|
+
const DOC_CHAT_META_RE = /doc-chat id=[a-f0-9]+ (result=|exit_code=)/i;
|
|
25
|
+
|
|
26
|
+
// Log line classification patterns
|
|
27
|
+
const LINE_PATTERNS = {
|
|
28
|
+
// Run boundaries
|
|
29
|
+
runStart: /^=== run \d+ start/,
|
|
30
|
+
runEnd: /^=== run \d+ end/,
|
|
31
|
+
|
|
32
|
+
// Agent thinking/reasoning
|
|
33
|
+
thinking: /^thinking$/i,
|
|
34
|
+
thinkingContent: /^\*\*.+\*\*$/,
|
|
35
|
+
thinkingMultiline:
|
|
36
|
+
/^I'm (preparing|planning|considering|reviewing|analyzing|checking|looking|reading|searching)/i,
|
|
37
|
+
|
|
38
|
+
// Tool execution
|
|
39
|
+
execStart: /^exec$/i,
|
|
40
|
+
execCommand: /^\/bin\/(zsh|bash|sh)\s+-[a-z]+\s+['"]?.+in\s+\//i,
|
|
41
|
+
applyPatch: /^apply_patch\(/i,
|
|
42
|
+
fileUpdate: /^file update:?$/i,
|
|
43
|
+
fileModified: /^M\s+[\w./]/,
|
|
44
|
+
|
|
45
|
+
// Diff patterns - need context tracking to avoid false positives
|
|
46
|
+
// These patterns identify the START of a diff block
|
|
47
|
+
diffGitHeader: /^diff --git /,
|
|
48
|
+
diffFileHeader: /^(---|\+\+\+)\s+[ab]\//,
|
|
49
|
+
diffIndex: /^index [a-f0-9]+\.\.[a-f0-9]+/,
|
|
50
|
+
diffHunk: /^@@\s+-\d+,?\d*\s+\+\d+,?\d*\s+@@/,
|
|
51
|
+
|
|
52
|
+
// Prompt/context markers (verbose)
|
|
53
|
+
promptMarker:
|
|
54
|
+
/^<(SPEC|WORK_DOCS|TODO|PROGRESS|OPINIONS|TARGET_DOC|RECENT_RUN|SYSTEM|USER|ASSISTANT)>$/,
|
|
55
|
+
promptMarkerEnd:
|
|
56
|
+
/^<\/(SPEC|WORK_DOCS|TODO|PROGRESS|OPINIONS|TARGET_DOC|RECENT_RUN|SYSTEM|USER|ASSISTANT)>$/,
|
|
57
|
+
|
|
58
|
+
// System messages
|
|
59
|
+
mcpStartup: /^mcp startup:/i,
|
|
60
|
+
tokensUsed: /^tokens used/i,
|
|
61
|
+
|
|
62
|
+
// Agent summary/output (lines after tokens used)
|
|
63
|
+
agentOutput: /^Agent:\s*/i,
|
|
64
|
+
|
|
65
|
+
// Success/error indicators
|
|
66
|
+
success: /succeeded in \d+ms/i,
|
|
67
|
+
exitCode: /exited \d+ in \d+ms/i,
|
|
68
|
+
|
|
69
|
+
// Additional patterns for better classification
|
|
70
|
+
testOutput:
|
|
71
|
+
/^(={3,}\s*(test session|.*passed|.*failed)|PASSED|FAILED|ERROR)/i,
|
|
72
|
+
pythonTraceback: /^(Traceback \(most recent|File ".*", line \d+|.*Error:)/i,
|
|
73
|
+
|
|
74
|
+
// Markdown list items - explicitly NOT diff lines
|
|
75
|
+
markdownList: /^- (\[[ x]\]\s)?[A-Z]/,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Determine the type of a log line
|
|
79
|
+
function classifyLine(line, context = {}) {
|
|
80
|
+
const stripped = line
|
|
81
|
+
.replace(/^\[[^\]]*]\s*/, "")
|
|
82
|
+
.replace(/^(run=\d+\s*)?(stdout|stderr):\s*/, "")
|
|
83
|
+
.replace(/^doc-chat id=[a-f0-9]+ stdout:\s*/i, "")
|
|
84
|
+
.trim();
|
|
85
|
+
|
|
86
|
+
// Run boundaries - highest priority (also resets diff context)
|
|
87
|
+
if (LINE_PATTERNS.runStart.test(stripped))
|
|
88
|
+
return { type: "run-start", priority: 1, resetDiff: true };
|
|
89
|
+
if (LINE_PATTERNS.runEnd.test(stripped))
|
|
90
|
+
return { type: "run-end", priority: 1, resetDiff: true };
|
|
91
|
+
|
|
92
|
+
// Agent output (summary) - also high priority as this is final output for user
|
|
93
|
+
if (LINE_PATTERNS.agentOutput.test(stripped))
|
|
94
|
+
return { type: "agent-output", priority: 1 };
|
|
95
|
+
|
|
96
|
+
// Thinking/reasoning
|
|
97
|
+
if (LINE_PATTERNS.thinking.test(stripped))
|
|
98
|
+
return { type: "thinking-label", priority: 2 };
|
|
99
|
+
if (LINE_PATTERNS.thinkingContent.test(stripped))
|
|
100
|
+
return { type: "thinking", priority: 2 };
|
|
101
|
+
if (LINE_PATTERNS.thinkingMultiline.test(stripped))
|
|
102
|
+
return { type: "thinking", priority: 2 };
|
|
103
|
+
|
|
104
|
+
// Tool execution
|
|
105
|
+
if (LINE_PATTERNS.execStart.test(stripped))
|
|
106
|
+
return { type: "exec-label", priority: 3 };
|
|
107
|
+
if (LINE_PATTERNS.execCommand.test(stripped))
|
|
108
|
+
return { type: "exec-command", priority: 3 };
|
|
109
|
+
if (LINE_PATTERNS.applyPatch.test(stripped))
|
|
110
|
+
return { type: "exec-command", priority: 3 };
|
|
111
|
+
if (LINE_PATTERNS.fileUpdate.test(stripped))
|
|
112
|
+
return { type: "file-update-label", priority: 3, startDiff: true };
|
|
113
|
+
if (LINE_PATTERNS.fileModified.test(stripped))
|
|
114
|
+
return { type: "file-modified", priority: 3 };
|
|
115
|
+
|
|
116
|
+
// Test output
|
|
117
|
+
if (LINE_PATTERNS.testOutput.test(stripped))
|
|
118
|
+
return { type: "test-output", priority: 3 };
|
|
119
|
+
|
|
120
|
+
// Error/traceback
|
|
121
|
+
if (LINE_PATTERNS.pythonTraceback.test(stripped))
|
|
122
|
+
return { type: "error-output", priority: 2 };
|
|
123
|
+
|
|
124
|
+
// Diff headers - mark start of diff context
|
|
125
|
+
if (LINE_PATTERNS.diffGitHeader.test(stripped))
|
|
126
|
+
return { type: "diff-header", priority: 4, startDiff: true };
|
|
127
|
+
if (LINE_PATTERNS.diffFileHeader.test(stripped))
|
|
128
|
+
return { type: "diff-header", priority: 4 };
|
|
129
|
+
if (LINE_PATTERNS.diffIndex.test(stripped))
|
|
130
|
+
return { type: "diff-header", priority: 4 };
|
|
131
|
+
if (LINE_PATTERNS.diffHunk.test(stripped))
|
|
132
|
+
return { type: "diff-hunk", priority: 4 };
|
|
133
|
+
|
|
134
|
+
// Diff add/del lines - ONLY if we're in diff context
|
|
135
|
+
if (context.inDiffBlock) {
|
|
136
|
+
// Check for actual diff lines (not markdown lists)
|
|
137
|
+
if (/^\+[^+]/.test(stripped) && !LINE_PATTERNS.markdownList.test(stripped))
|
|
138
|
+
return { type: "diff-add", priority: 4 };
|
|
139
|
+
if (/^-[^-]/.test(stripped) && !LINE_PATTERNS.markdownList.test(stripped))
|
|
140
|
+
return { type: "diff-del", priority: 4 };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Prompt/context (verbose - collapsible)
|
|
144
|
+
if (LINE_PATTERNS.promptMarker.test(stripped))
|
|
145
|
+
return { type: "prompt-marker", priority: 5 };
|
|
146
|
+
if (LINE_PATTERNS.promptMarkerEnd.test(stripped))
|
|
147
|
+
return { type: "prompt-marker-end", priority: 5 };
|
|
148
|
+
|
|
149
|
+
// System messages
|
|
150
|
+
if (LINE_PATTERNS.mcpStartup.test(stripped))
|
|
151
|
+
return { type: "system", priority: 6 };
|
|
152
|
+
if (LINE_PATTERNS.tokensUsed.test(stripped))
|
|
153
|
+
return { type: "tokens", priority: 6 };
|
|
154
|
+
|
|
155
|
+
// Success/error in command output
|
|
156
|
+
if (LINE_PATTERNS.success.test(stripped))
|
|
157
|
+
return { type: "success", priority: 3 };
|
|
158
|
+
if (LINE_PATTERNS.exitCode.test(stripped))
|
|
159
|
+
return { type: "exit-code", priority: 3 };
|
|
160
|
+
|
|
161
|
+
// If we're in a context block, mark as context
|
|
162
|
+
if (context.inPromptBlock) return { type: "prompt-context", priority: 5 };
|
|
163
|
+
|
|
164
|
+
// Default: regular output
|
|
165
|
+
return { type: "output", priority: 4 };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function processLine(line) {
|
|
169
|
+
let next = line;
|
|
170
|
+
// Normalize run markers that include "chat"
|
|
171
|
+
next = next.replace(/^=== run (\d+)\s+chat(\s|$)/, "=== run $1$2");
|
|
172
|
+
|
|
173
|
+
if (!showTimestampToggle.checked) {
|
|
174
|
+
next = next.replace(/^\[[^\]]*]\s*/, "");
|
|
175
|
+
}
|
|
176
|
+
if (!showRunToggle.checked) {
|
|
177
|
+
if (next.startsWith("[")) {
|
|
178
|
+
next = next.replace(/^(\[[^\]]+]\s*)run=\d+\s*/, "$1");
|
|
179
|
+
} else {
|
|
180
|
+
next = next.replace(/^run=\d+\s*/, "");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
// Remove redundant channel prefix
|
|
184
|
+
next = next.replace(/^(\[[^\]]+]\s*)?(run=\d+\s*)?chat:\s*/, "$1$2");
|
|
185
|
+
// Strip stdout/stderr markers that make logs noisy
|
|
186
|
+
next = next.replace(
|
|
187
|
+
/^(\[[^\]]+]\s*)?(run=\d+\s*)?(stdout|stderr):\s*/,
|
|
188
|
+
"$1$2"
|
|
189
|
+
);
|
|
190
|
+
// Strip doc-chat id prefix for cleaner display
|
|
191
|
+
next = next.replace(
|
|
192
|
+
/^(\[[^\]]+]\s*)?(run=\d+\s*)?doc-chat id=[a-f0-9]+ stdout:\s*/i,
|
|
193
|
+
"$1$2"
|
|
194
|
+
);
|
|
195
|
+
return next.trimEnd();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function shouldOmitLine(line) {
|
|
199
|
+
// Only omit doc-chat metadata lines (result=, exit_code=) when Run toggle is off
|
|
200
|
+
// We still want to show the actual content from doc-chat
|
|
201
|
+
if (!showRunToggle.checked && DOC_CHAT_META_RE.test(line)) {
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function resetRenderState() {
|
|
208
|
+
renderState = {
|
|
209
|
+
inPromptBlock: false,
|
|
210
|
+
promptBlockDetails: null,
|
|
211
|
+
promptBlockContent: null,
|
|
212
|
+
promptBlockType: null,
|
|
213
|
+
promptLineCount: 0,
|
|
214
|
+
inDiffBlock: false,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function resetLogContexts() {
|
|
219
|
+
logContexts = [];
|
|
220
|
+
logContextState = { inPromptBlock: false, inDiffBlock: false };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function updateLogContextForLine(line) {
|
|
224
|
+
logContexts.push({ ...logContextState });
|
|
225
|
+
const classification = classifyLine(line, logContextState);
|
|
226
|
+
if (classification.startDiff) {
|
|
227
|
+
logContextState.inDiffBlock = true;
|
|
228
|
+
}
|
|
229
|
+
if (classification.resetDiff) {
|
|
230
|
+
logContextState.inDiffBlock = false;
|
|
231
|
+
}
|
|
232
|
+
if (classification.type === "prompt-marker") {
|
|
233
|
+
logContextState.inPromptBlock = true;
|
|
234
|
+
logContextState.inDiffBlock = false;
|
|
235
|
+
} else if (classification.type === "prompt-marker-end") {
|
|
236
|
+
logContextState.inPromptBlock = false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function rebuildLogContexts() {
|
|
241
|
+
resetLogContexts();
|
|
242
|
+
rawLogLines.forEach((line) => updateLogContextForLine(line));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function finalizePromptBlock() {
|
|
246
|
+
if (!renderState || !renderState.promptBlockDetails) return;
|
|
247
|
+
const countEl = renderState.promptBlockDetails.querySelector(
|
|
248
|
+
".log-context-count"
|
|
249
|
+
);
|
|
250
|
+
if (countEl) {
|
|
251
|
+
countEl.textContent = `(${renderState.promptLineCount} lines)`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function startPromptBlock(output, label) {
|
|
256
|
+
renderState.promptBlockType = label;
|
|
257
|
+
renderState.promptBlockDetails = document.createElement("details");
|
|
258
|
+
renderState.promptBlockDetails.className = "log-context-block";
|
|
259
|
+
const summary = document.createElement("summary");
|
|
260
|
+
summary.className = "log-context-summary";
|
|
261
|
+
summary.innerHTML = `<span class="log-context-icon">▶</span> ${label} <span class="log-context-count"></span>`;
|
|
262
|
+
renderState.promptBlockDetails.appendChild(summary);
|
|
263
|
+
renderState.promptBlockContent = document.createElement("div");
|
|
264
|
+
renderState.promptBlockContent.className = "log-context-content";
|
|
265
|
+
renderState.promptBlockDetails.appendChild(renderState.promptBlockContent);
|
|
266
|
+
renderState.promptLineCount = 0;
|
|
267
|
+
output.appendChild(renderState.promptBlockDetails);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function appendRenderedLine(line, output) {
|
|
271
|
+
if (!renderState) resetRenderState();
|
|
272
|
+
if (shouldOmitLine(line)) return;
|
|
273
|
+
|
|
274
|
+
const processed = processLine(line).trimEnd();
|
|
275
|
+
const classification = classifyLine(line, renderState);
|
|
276
|
+
|
|
277
|
+
if (classification.startDiff) {
|
|
278
|
+
renderState.inDiffBlock = true;
|
|
279
|
+
}
|
|
280
|
+
if (classification.resetDiff) {
|
|
281
|
+
renderState.inDiffBlock = false;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (classification.type === "prompt-marker") {
|
|
285
|
+
renderState.inPromptBlock = true;
|
|
286
|
+
renderState.inDiffBlock = false;
|
|
287
|
+
const match = processed.match(/<(\w+)>/);
|
|
288
|
+
const blockLabel = match ? match[1] : "CONTEXT";
|
|
289
|
+
startPromptBlock(output, blockLabel);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (classification.type === "prompt-marker-end") {
|
|
294
|
+
finalizePromptBlock();
|
|
295
|
+
renderState.promptBlockDetails = null;
|
|
296
|
+
renderState.promptBlockContent = null;
|
|
297
|
+
renderState.promptBlockType = null;
|
|
298
|
+
renderState.promptLineCount = 0;
|
|
299
|
+
renderState.inPromptBlock = false;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (
|
|
304
|
+
renderState.promptBlockContent &&
|
|
305
|
+
renderState.inPromptBlock &&
|
|
306
|
+
(classification.type === "prompt-context" || classification.type === "output")
|
|
307
|
+
) {
|
|
308
|
+
const div = document.createElement("div");
|
|
309
|
+
div.textContent = processed;
|
|
310
|
+
div.className = "log-line log-prompt-context";
|
|
311
|
+
renderState.promptBlockContent.appendChild(div);
|
|
312
|
+
renderState.promptLineCount++;
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const isBlank = processed.trim() === "";
|
|
317
|
+
const div = document.createElement("div");
|
|
318
|
+
div.textContent = processed;
|
|
319
|
+
|
|
320
|
+
if (isBlank) {
|
|
321
|
+
div.className = "log-line log-blank";
|
|
322
|
+
} else {
|
|
323
|
+
div.className = `log-line log-${classification.type}`;
|
|
324
|
+
div.dataset.logType = classification.type;
|
|
325
|
+
div.dataset.priority = classification.priority;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (classification.type === "thinking-label" || classification.type === "thinking") {
|
|
329
|
+
div.dataset.icon = "💭";
|
|
330
|
+
} else if (classification.type === "exec-label" || classification.type === "exec-command") {
|
|
331
|
+
div.dataset.icon = "⚡";
|
|
332
|
+
} else if (
|
|
333
|
+
classification.type === "file-update-label" ||
|
|
334
|
+
classification.type === "file-modified"
|
|
335
|
+
) {
|
|
336
|
+
div.dataset.icon = "📝";
|
|
337
|
+
} else if (classification.type === "agent-output") {
|
|
338
|
+
div.dataset.icon = "✨";
|
|
339
|
+
} else if (classification.type === "run-start" || classification.type === "run-end") {
|
|
340
|
+
div.dataset.icon = "🔄";
|
|
341
|
+
} else if (classification.type === "success") {
|
|
342
|
+
div.dataset.icon = "✓";
|
|
343
|
+
} else if (classification.type === "tokens") {
|
|
344
|
+
div.dataset.icon = "📊";
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
output.appendChild(div);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function trimLogBuffer() {
|
|
351
|
+
const maxLines = CONSTANTS.UI.MAX_LOG_LINES_IN_MEMORY;
|
|
352
|
+
if (!maxLines || rawLogLines.length <= maxLines) return;
|
|
353
|
+
const overflow = rawLogLines.length - maxLines;
|
|
354
|
+
rawLogLines = rawLogLines.slice(overflow);
|
|
355
|
+
if (logContexts.length > overflow) {
|
|
356
|
+
logContexts = logContexts.slice(overflow);
|
|
357
|
+
} else {
|
|
358
|
+
logContexts = [];
|
|
359
|
+
}
|
|
360
|
+
renderedStartIndex = Math.max(0, renderedStartIndex - overflow);
|
|
361
|
+
renderedEndIndex = Math.max(0, renderedEndIndex - overflow);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function updateLoadOlderButton() {
|
|
365
|
+
if (!loadOlderButton) return;
|
|
366
|
+
if (renderedStartIndex > 0) {
|
|
367
|
+
loadOlderButton.classList.remove("hidden");
|
|
368
|
+
} else {
|
|
369
|
+
loadOlderButton.classList.add("hidden");
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function applyLogUrlState() {
|
|
374
|
+
const params = getUrlParams();
|
|
375
|
+
const runId = params.get("run");
|
|
376
|
+
const tail = params.get("tail");
|
|
377
|
+
if (runId !== null && logRunIdInput) {
|
|
378
|
+
logRunIdInput.value = runId;
|
|
379
|
+
}
|
|
380
|
+
if (tail !== null && logTailInput) {
|
|
381
|
+
logTailInput.value = tail;
|
|
382
|
+
}
|
|
383
|
+
if (runId) {
|
|
384
|
+
isViewingTail = false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function syncLogUrlState() {
|
|
389
|
+
const runId = logRunIdInput?.value?.trim() || "";
|
|
390
|
+
const tail = logTailInput?.value?.trim() || "";
|
|
391
|
+
updateUrlParams({
|
|
392
|
+
run: runId || null,
|
|
393
|
+
tail: runId ? null : tail || null,
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function renderLogWindow({ startIndex = null, followTail = true } = {}) {
|
|
398
|
+
const output = document.getElementById("log-output");
|
|
399
|
+
|
|
400
|
+
if (rawLogLines.length === 0) {
|
|
401
|
+
output.innerHTML = "";
|
|
402
|
+
output.textContent = "(empty log)";
|
|
403
|
+
output.dataset.isPlaceholder = "true";
|
|
404
|
+
renderedStartIndex = 0;
|
|
405
|
+
renderedEndIndex = 0;
|
|
406
|
+
isViewingTail = true;
|
|
407
|
+
updateLoadOlderButton();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const endIndex = rawLogLines.length;
|
|
412
|
+
let windowStart = startIndex;
|
|
413
|
+
if (followTail || windowStart === null) {
|
|
414
|
+
windowStart = Math.max(0, endIndex - CONSTANTS.UI.MAX_LOG_LINES_IN_DOM);
|
|
415
|
+
}
|
|
416
|
+
const windowEnd = Math.min(
|
|
417
|
+
endIndex,
|
|
418
|
+
windowStart + CONSTANTS.UI.MAX_LOG_LINES_IN_DOM
|
|
419
|
+
);
|
|
420
|
+
|
|
421
|
+
output.innerHTML = "";
|
|
422
|
+
delete output.dataset.isPlaceholder;
|
|
423
|
+
resetRenderState();
|
|
424
|
+
const startContext = logContexts[windowStart];
|
|
425
|
+
if (startContext) {
|
|
426
|
+
renderState.inPromptBlock = startContext.inPromptBlock;
|
|
427
|
+
renderState.inDiffBlock = startContext.inDiffBlock;
|
|
428
|
+
if (renderState.inPromptBlock) {
|
|
429
|
+
startPromptBlock(output, "CONTEXT (continued)");
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
for (let i = windowStart; i < windowEnd; i += 1) {
|
|
434
|
+
appendRenderedLine(rawLogLines[i], output);
|
|
435
|
+
}
|
|
436
|
+
finalizePromptBlock();
|
|
437
|
+
|
|
438
|
+
renderedStartIndex = windowStart;
|
|
439
|
+
renderedEndIndex = windowEnd;
|
|
440
|
+
isViewingTail = followTail && windowEnd === endIndex;
|
|
441
|
+
updateLoadOlderButton();
|
|
442
|
+
|
|
443
|
+
if (isViewingTail) {
|
|
444
|
+
scrollLogsToBottom(true);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function appendLogLine(line) {
|
|
449
|
+
const output = document.getElementById("log-output");
|
|
450
|
+
if (output.dataset.isPlaceholder === "true") {
|
|
451
|
+
output.innerHTML = "";
|
|
452
|
+
delete output.dataset.isPlaceholder;
|
|
453
|
+
rawLogLines = [];
|
|
454
|
+
resetRenderState();
|
|
455
|
+
resetLogContexts();
|
|
456
|
+
renderedStartIndex = 0;
|
|
457
|
+
renderedEndIndex = 0;
|
|
458
|
+
isViewingTail = true;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
rawLogLines.push(line);
|
|
462
|
+
updateLogContextForLine(line);
|
|
463
|
+
trimLogBuffer();
|
|
464
|
+
|
|
465
|
+
if (!isViewingTail) {
|
|
466
|
+
publish("logs:line", line);
|
|
467
|
+
updateLoadOlderButton();
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
appendRenderedLine(line, output);
|
|
472
|
+
renderedEndIndex = rawLogLines.length;
|
|
473
|
+
if (output.childElementCount > CONSTANTS.UI.MAX_LOG_LINES_IN_DOM) {
|
|
474
|
+
output.firstElementChild.remove();
|
|
475
|
+
}
|
|
476
|
+
renderedStartIndex = Math.max(
|
|
477
|
+
0,
|
|
478
|
+
renderedEndIndex - output.childElementCount
|
|
479
|
+
);
|
|
480
|
+
updateLoadOlderButton();
|
|
481
|
+
publish("logs:line", line);
|
|
482
|
+
scrollLogsToBottom();
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function scrollLogsToBottom(force = false) {
|
|
486
|
+
const output = document.getElementById("log-output");
|
|
487
|
+
if (!output) return;
|
|
488
|
+
if (!autoScrollEnabled && !force) return;
|
|
489
|
+
|
|
490
|
+
requestAnimationFrame(() => {
|
|
491
|
+
output.scrollTop = output.scrollHeight;
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function updateJumpButtonVisibility() {
|
|
496
|
+
const output = document.getElementById("log-output");
|
|
497
|
+
if (!output || !jumpBottomButton) return;
|
|
498
|
+
|
|
499
|
+
const isNearBottom =
|
|
500
|
+
output.scrollHeight - output.scrollTop - output.clientHeight < 100;
|
|
501
|
+
|
|
502
|
+
if (isNearBottom) {
|
|
503
|
+
jumpBottomButton.classList.add("hidden");
|
|
504
|
+
autoScrollEnabled = true;
|
|
505
|
+
} else {
|
|
506
|
+
jumpBottomButton.classList.remove("hidden");
|
|
507
|
+
autoScrollEnabled = false;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function setLogStreamButton(active) {
|
|
512
|
+
toggleLogStreamButton.textContent = active ? "Stop stream" : "Start stream";
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function loadLogs() {
|
|
516
|
+
syncLogUrlState();
|
|
517
|
+
const runId = logRunIdInput.value;
|
|
518
|
+
const tail = logTailInput.value || "200";
|
|
519
|
+
const params = new URLSearchParams();
|
|
520
|
+
if (runId) {
|
|
521
|
+
params.set("run_id", runId);
|
|
522
|
+
} else if (tail) {
|
|
523
|
+
params.set("tail", tail);
|
|
524
|
+
}
|
|
525
|
+
const path = params.toString()
|
|
526
|
+
? `/api/logs?${params.toString()}`
|
|
527
|
+
: "/api/logs";
|
|
528
|
+
try {
|
|
529
|
+
const data = await api(path);
|
|
530
|
+
const text = typeof data === "string" ? data : data.log || "";
|
|
531
|
+
const output = document.getElementById("log-output");
|
|
532
|
+
|
|
533
|
+
if (text) {
|
|
534
|
+
rawLogLines = text.split("\n");
|
|
535
|
+
trimLogBuffer();
|
|
536
|
+
rebuildLogContexts();
|
|
537
|
+
delete output.dataset.isPlaceholder;
|
|
538
|
+
isViewingTail = true;
|
|
539
|
+
renderLogs();
|
|
540
|
+
|
|
541
|
+
// Update cache if we are looking at the latest logs (no specific run ID)
|
|
542
|
+
if (!runId) {
|
|
543
|
+
// Limit to last 200 lines to avoid localStorage quota issues
|
|
544
|
+
const lines = rawLogLines.slice(-200);
|
|
545
|
+
saveToCache("logs:tail", lines.join("\n"));
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
output.textContent = "(empty log)";
|
|
549
|
+
output.dataset.isPlaceholder = "true";
|
|
550
|
+
rawLogLines = [];
|
|
551
|
+
resetRenderState();
|
|
552
|
+
resetLogContexts();
|
|
553
|
+
renderedStartIndex = 0;
|
|
554
|
+
renderedEndIndex = 0;
|
|
555
|
+
isViewingTail = true;
|
|
556
|
+
updateLoadOlderButton();
|
|
557
|
+
if (!runId) {
|
|
558
|
+
saveToCache("logs:tail", "");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
flash("Logs loaded");
|
|
563
|
+
publish("logs:loaded", { runId, tail, text });
|
|
564
|
+
} catch (err) {
|
|
565
|
+
flash(err.message);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function stopLogStreaming() {
|
|
570
|
+
if (stopLogStream) {
|
|
571
|
+
stopLogStream();
|
|
572
|
+
stopLogStream = null;
|
|
573
|
+
}
|
|
574
|
+
setLogStreamButton(false);
|
|
575
|
+
publish("logs:streaming", false);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function startLogStreaming() {
|
|
579
|
+
if (stopLogStream) return;
|
|
580
|
+
const output = document.getElementById("log-output");
|
|
581
|
+
output.textContent = "(listening...)";
|
|
582
|
+
output.dataset.isPlaceholder = "true";
|
|
583
|
+
rawLogLines = [];
|
|
584
|
+
resetRenderState();
|
|
585
|
+
resetLogContexts();
|
|
586
|
+
renderedStartIndex = 0;
|
|
587
|
+
renderedEndIndex = 0;
|
|
588
|
+
isViewingTail = true;
|
|
589
|
+
updateLoadOlderButton();
|
|
590
|
+
|
|
591
|
+
stopLogStream = streamEvents("/api/logs/stream", {
|
|
592
|
+
onMessage: (data) => {
|
|
593
|
+
appendLogLine(data || "");
|
|
594
|
+
},
|
|
595
|
+
onError: (err) => {
|
|
596
|
+
flash(err.message);
|
|
597
|
+
stopLogStreaming();
|
|
598
|
+
},
|
|
599
|
+
onFinish: () => {
|
|
600
|
+
stopLogStream = null;
|
|
601
|
+
setLogStreamButton(false);
|
|
602
|
+
publish("logs:streaming", false);
|
|
603
|
+
},
|
|
604
|
+
});
|
|
605
|
+
setLogStreamButton(true);
|
|
606
|
+
publish("logs:streaming", true);
|
|
607
|
+
flash("Streaming logs…");
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function syncRunIdPlaceholder(state) {
|
|
611
|
+
lastKnownRunId = state?.last_run_id ?? null;
|
|
612
|
+
logRunIdInput.placeholder = lastKnownRunId
|
|
613
|
+
? `latest (${lastKnownRunId})`
|
|
614
|
+
: "latest";
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function renderLogs() {
|
|
618
|
+
renderLogWindow({ followTail: isViewingTail });
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function initLogs() {
|
|
622
|
+
applyLogUrlState();
|
|
623
|
+
document.getElementById("load-logs").addEventListener("click", loadLogs);
|
|
624
|
+
toggleLogStreamButton.addEventListener("click", () => {
|
|
625
|
+
if (stopLogStream) {
|
|
626
|
+
stopLogStreaming();
|
|
627
|
+
} else {
|
|
628
|
+
startLogStreaming();
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
subscribe("state:update", syncRunIdPlaceholder);
|
|
633
|
+
subscribe("tab:change", (tab) => {
|
|
634
|
+
if (tab !== "logs" && stopLogStream) {
|
|
635
|
+
stopLogStreaming();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
showTimestampToggle.addEventListener("change", renderLogs);
|
|
640
|
+
showRunToggle.addEventListener("change", renderLogs);
|
|
641
|
+
|
|
642
|
+
// Jump to bottom button
|
|
643
|
+
if (jumpBottomButton) {
|
|
644
|
+
jumpBottomButton.addEventListener("click", () => {
|
|
645
|
+
if (!isViewingTail) {
|
|
646
|
+
isViewingTail = true;
|
|
647
|
+
renderLogs();
|
|
648
|
+
}
|
|
649
|
+
autoScrollEnabled = true;
|
|
650
|
+
scrollLogsToBottom(true);
|
|
651
|
+
jumpBottomButton.classList.add("hidden");
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
if (loadOlderButton) {
|
|
656
|
+
loadOlderButton.addEventListener("click", () => {
|
|
657
|
+
if (renderedStartIndex <= 0) return;
|
|
658
|
+
const nextStart = Math.max(
|
|
659
|
+
0,
|
|
660
|
+
renderedStartIndex - CONSTANTS.UI.LOG_PAGE_SIZE
|
|
661
|
+
);
|
|
662
|
+
isViewingTail = false;
|
|
663
|
+
autoScrollEnabled = false;
|
|
664
|
+
renderLogWindow({ startIndex: nextStart, followTail: false });
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Track scroll position to show/hide jump button
|
|
669
|
+
const output = document.getElementById("log-output");
|
|
670
|
+
if (output) {
|
|
671
|
+
output.addEventListener("scroll", updateJumpButtonVisibility);
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// Try loading from cache first
|
|
675
|
+
const cachedLogs = loadFromCache("logs:tail");
|
|
676
|
+
if (cachedLogs) {
|
|
677
|
+
const output = document.getElementById("log-output");
|
|
678
|
+
rawLogLines = cachedLogs.split("\n");
|
|
679
|
+
if (rawLogLines.length > 0) {
|
|
680
|
+
trimLogBuffer();
|
|
681
|
+
rebuildLogContexts();
|
|
682
|
+
delete output.dataset.isPlaceholder;
|
|
683
|
+
isViewingTail = true;
|
|
684
|
+
renderLogs();
|
|
685
|
+
scrollLogsToBottom(true);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
loadLogs();
|
|
690
|
+
}
|