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.
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +118 -30
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +136 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +16 -35
- codex_autorunner/cli.py +157 -139
- codex_autorunner/core/about_car.py +44 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +15 -26
- codex_autorunner/core/codex_runner.py +6 -0
- codex_autorunner/core/config.py +390 -100
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +82 -0
- codex_autorunner/core/engine.py +278 -262
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +178 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +75 -0
- codex_autorunner/core/flows/runtime.py +351 -0
- codex_autorunner/core/flows/store.py +485 -0
- codex_autorunner/core/flows/transition.py +133 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/hub.py +15 -9
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/static_assets.py +55 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/update.py +20 -11
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/utils.py +29 -2
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +91 -0
- codex_autorunner/integrations/agents/__init__.py +27 -0
- codex_autorunner/integrations/agents/agent_backend.py +142 -0
- codex_autorunner/integrations/agents/codex_backend.py +307 -0
- codex_autorunner/integrations/agents/opencode_backend.py +325 -0
- codex_autorunner/integrations/agents/run_event.py +71 -0
- codex_autorunner/integrations/app_server/client.py +576 -92
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +141 -167
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +175 -0
- codex_autorunner/integrations/telegram/constants.py +16 -1
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +0 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +227 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +133 -475
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +11 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +120 -9
- codex_autorunner/integrations/telegram/helpers.py +88 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +214 -40
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +322 -0
- codex_autorunner/integrations/telegram/transport.py +36 -3
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +23 -14
- codex_autorunner/routes/analytics.py +239 -0
- codex_autorunner/routes/base.py +81 -109
- codex_autorunner/routes/file_chat.py +836 -0
- codex_autorunner/routes/flows.py +980 -0
- codex_autorunner/routes/messages.py +459 -0
- codex_autorunner/routes/system.py +6 -1
- codex_autorunner/routes/usage.py +87 -0
- codex_autorunner/routes/workspace.py +271 -0
- codex_autorunner/server.py +2 -1
- codex_autorunner/static/agentControls.js +1 -0
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +25 -22
- codex_autorunner/static/autoRefresh.js +29 -1
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +162 -196
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +41 -118
- codex_autorunner/static/index.html +787 -858
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +470 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/settings.js +24 -211
- codex_autorunner/static/styles.css +7567 -3865
- codex_autorunner/static/tabs.js +28 -5
- codex_autorunner/static/terminal.js +14 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +750 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1315 -0
- codex_autorunner/static/utils.js +32 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +672 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/tickets/__init__.py +20 -0
- codex_autorunner/tickets/agent_pool.py +377 -0
- codex_autorunner/tickets/files.py +85 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +95 -0
- codex_autorunner/tickets/outbox.py +232 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +823 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/app.py +269 -91
- codex_autorunner/web/middleware.py +3 -4
- codex_autorunner/web/schemas.py +89 -109
- codex_autorunner/web/static_assets.py +1 -44
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +319 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/METADATA +18 -21
- codex_autorunner-1.0.0.dist-info/RECORD +251 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
export const COMPACT_MAX_ACTIONS = 10;
|
|
3
|
+
export const COMPACT_MAX_TEXT_LENGTH = 80;
|
|
4
|
+
const STATUS_ICONS = {
|
|
5
|
+
done: "✓",
|
|
6
|
+
fail: "✗",
|
|
7
|
+
warn: "⚠",
|
|
8
|
+
running: "▸",
|
|
9
|
+
update: "↻",
|
|
10
|
+
thinking: "🧠",
|
|
11
|
+
};
|
|
12
|
+
function normalizeText(value) {
|
|
13
|
+
return value.replace(/\s+/g, " ").trim();
|
|
14
|
+
}
|
|
15
|
+
function truncateText(value, maxLength) {
|
|
16
|
+
if (value.length <= maxLength)
|
|
17
|
+
return value;
|
|
18
|
+
return `${value.slice(0, maxLength).trim()}…`;
|
|
19
|
+
}
|
|
20
|
+
function formatElapsed(seconds) {
|
|
21
|
+
const total = Math.max(Math.floor(seconds), 0);
|
|
22
|
+
if (total < 60)
|
|
23
|
+
return `${total}s`;
|
|
24
|
+
const minutes = Math.floor(total / 60);
|
|
25
|
+
const secs = total % 60;
|
|
26
|
+
if (minutes < 60)
|
|
27
|
+
return `${minutes}m ${secs}s`;
|
|
28
|
+
const hours = Math.floor(minutes / 60);
|
|
29
|
+
const remaining = minutes % 60;
|
|
30
|
+
return `${hours}h ${remaining}m`;
|
|
31
|
+
}
|
|
32
|
+
function deriveHeaderLabel(events) {
|
|
33
|
+
const lastError = [...events].reverse().find((evt) => evt.kind === "error");
|
|
34
|
+
if (lastError)
|
|
35
|
+
return "error";
|
|
36
|
+
const lastStatus = [...events].reverse().find((evt) => evt.kind === "status" && evt.summary);
|
|
37
|
+
if (lastStatus?.summary)
|
|
38
|
+
return lastStatus.summary;
|
|
39
|
+
return "working";
|
|
40
|
+
}
|
|
41
|
+
function eventToAction(event, maxTextLength) {
|
|
42
|
+
if (event.isSignificant === false && event.kind !== "thinking") {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const rawText = normalizeText(event.summary || event.detail || "");
|
|
46
|
+
if (!rawText)
|
|
47
|
+
return null;
|
|
48
|
+
let label = event.kind;
|
|
49
|
+
let status = "update";
|
|
50
|
+
if (event.kind === "command") {
|
|
51
|
+
label = "command";
|
|
52
|
+
status = event.method.includes("requestApproval") ? "warn" : "done";
|
|
53
|
+
}
|
|
54
|
+
else if (event.kind === "tool") {
|
|
55
|
+
label = "tool";
|
|
56
|
+
status = "done";
|
|
57
|
+
}
|
|
58
|
+
else if (event.kind === "file") {
|
|
59
|
+
label = "files";
|
|
60
|
+
status = event.method.includes("requestApproval") ? "warn" : "done";
|
|
61
|
+
}
|
|
62
|
+
else if (event.kind === "output") {
|
|
63
|
+
label = "output";
|
|
64
|
+
status = "update";
|
|
65
|
+
}
|
|
66
|
+
else if (event.kind === "thinking") {
|
|
67
|
+
label = "thinking";
|
|
68
|
+
status = "update";
|
|
69
|
+
}
|
|
70
|
+
else if (event.kind === "error") {
|
|
71
|
+
label = "error";
|
|
72
|
+
status = "fail";
|
|
73
|
+
}
|
|
74
|
+
else if (event.kind === "status") {
|
|
75
|
+
label = "status";
|
|
76
|
+
status = "running";
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
label = "event";
|
|
80
|
+
status = "update";
|
|
81
|
+
}
|
|
82
|
+
const text = truncateText(rawText, maxTextLength);
|
|
83
|
+
const icon = label === "thinking" ? STATUS_ICONS.thinking : STATUS_ICONS[status] || STATUS_ICONS.running;
|
|
84
|
+
return { icon, label, text, status };
|
|
85
|
+
}
|
|
86
|
+
export function summarizeEvents(events, options = {}) {
|
|
87
|
+
const maxActions = options.maxActions ?? COMPACT_MAX_ACTIONS;
|
|
88
|
+
const maxTextLength = options.maxTextLength ?? COMPACT_MAX_TEXT_LENGTH;
|
|
89
|
+
const actions = [];
|
|
90
|
+
let lastOutputIndex = null;
|
|
91
|
+
let lastThinkingIndex = null;
|
|
92
|
+
events.forEach((event) => {
|
|
93
|
+
const action = eventToAction(event, maxTextLength);
|
|
94
|
+
if (!action)
|
|
95
|
+
return;
|
|
96
|
+
if (action.label === "output") {
|
|
97
|
+
if (lastOutputIndex != null) {
|
|
98
|
+
actions[lastOutputIndex] = action;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
actions.push(action);
|
|
102
|
+
lastOutputIndex = actions.length - 1;
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (action.label === "thinking") {
|
|
107
|
+
if (lastThinkingIndex != null) {
|
|
108
|
+
actions[lastThinkingIndex] = action;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
actions.push(action);
|
|
112
|
+
lastThinkingIndex = actions.length - 1;
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
actions.push(action);
|
|
117
|
+
lastOutputIndex = null;
|
|
118
|
+
});
|
|
119
|
+
let thinkingAction = null;
|
|
120
|
+
if (lastThinkingIndex != null && actions[lastThinkingIndex]) {
|
|
121
|
+
thinkingAction = actions[lastThinkingIndex];
|
|
122
|
+
}
|
|
123
|
+
let trimmedActions = actions.slice(-maxActions);
|
|
124
|
+
if (thinkingAction) {
|
|
125
|
+
trimmedActions = trimmedActions.filter((action) => action !== thinkingAction);
|
|
126
|
+
trimmedActions.push(thinkingAction);
|
|
127
|
+
if (maxActions > 0 && trimmedActions.length > maxActions) {
|
|
128
|
+
trimmedActions = trimmedActions.slice(-maxActions);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const step = actions.length;
|
|
132
|
+
const headerParts = [deriveHeaderLabel(events)];
|
|
133
|
+
const now = options.now ?? Date.now();
|
|
134
|
+
const startTime = options.startTime ?? (events.length ? Math.min(...events.map((evt) => evt.time || now)) : null);
|
|
135
|
+
if (startTime != null) {
|
|
136
|
+
const elapsed = (now - startTime) / 1000;
|
|
137
|
+
headerParts.push(formatElapsed(elapsed));
|
|
138
|
+
}
|
|
139
|
+
if (step) {
|
|
140
|
+
headerParts.push(`step ${step}`);
|
|
141
|
+
}
|
|
142
|
+
if (options.contextUsagePercent != null) {
|
|
143
|
+
headerParts.push(`ctx ${options.contextUsagePercent}%`);
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
header: headerParts.join(" · "),
|
|
147
|
+
actions: trimmedActions,
|
|
148
|
+
thinkingText: thinkingAction?.text,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
export function renderCompactSummary(summary) {
|
|
152
|
+
const lines = [];
|
|
153
|
+
if (summary.header) {
|
|
154
|
+
lines.push(summary.header);
|
|
155
|
+
}
|
|
156
|
+
summary.actions.forEach((action) => {
|
|
157
|
+
if (!action.text)
|
|
158
|
+
return;
|
|
159
|
+
if (action.label === "thinking") {
|
|
160
|
+
lines.push(`${STATUS_ICONS.thinking} ${action.text}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
lines.push(`${action.icon} ${action.label}: ${action.text}`);
|
|
164
|
+
});
|
|
165
|
+
return lines.join("\n");
|
|
166
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { resolvePath, getAuthToken, api } from "./utils.js";
|
|
3
|
+
const decoder = new TextDecoder();
|
|
4
|
+
function parseMaybeJson(data) {
|
|
5
|
+
try {
|
|
6
|
+
return JSON.parse(data);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return data;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function sendFileChat(target, message, controller, handlers = {}, options = {}) {
|
|
13
|
+
const endpoint = resolvePath("/api/file-chat");
|
|
14
|
+
const headers = {
|
|
15
|
+
"Content-Type": "application/json",
|
|
16
|
+
};
|
|
17
|
+
const token = getAuthToken();
|
|
18
|
+
if (token)
|
|
19
|
+
headers.Authorization = `Bearer ${token}`;
|
|
20
|
+
const payload = {
|
|
21
|
+
target,
|
|
22
|
+
message,
|
|
23
|
+
stream: true,
|
|
24
|
+
};
|
|
25
|
+
if (options.agent)
|
|
26
|
+
payload.agent = options.agent;
|
|
27
|
+
if (options.model)
|
|
28
|
+
payload.model = options.model;
|
|
29
|
+
if (options.reasoning)
|
|
30
|
+
payload.reasoning = options.reasoning;
|
|
31
|
+
const res = await fetch(endpoint, {
|
|
32
|
+
method: "POST",
|
|
33
|
+
headers,
|
|
34
|
+
body: JSON.stringify(payload),
|
|
35
|
+
signal: controller.signal,
|
|
36
|
+
});
|
|
37
|
+
if (!res.ok) {
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
let detail = text;
|
|
40
|
+
try {
|
|
41
|
+
const parsed = JSON.parse(text);
|
|
42
|
+
detail =
|
|
43
|
+
parsed.detail || parsed.error || parsed.message || text;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// ignore
|
|
47
|
+
}
|
|
48
|
+
throw new Error(detail || `Request failed (${res.status})`);
|
|
49
|
+
}
|
|
50
|
+
const contentType = res.headers.get("content-type") || "";
|
|
51
|
+
if (contentType.includes("text/event-stream")) {
|
|
52
|
+
await readFileChatStream(res, handlers);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const responsePayload = contentType.includes("application/json") ? await res.json() : await res.text();
|
|
56
|
+
handlers.onUpdate?.(responsePayload);
|
|
57
|
+
handlers.onDone?.();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function readFileChatStream(res, handlers) {
|
|
61
|
+
if (!res.body)
|
|
62
|
+
throw new Error("Streaming not supported in this browser");
|
|
63
|
+
const reader = res.body.getReader();
|
|
64
|
+
let buffer = "";
|
|
65
|
+
for (;;) {
|
|
66
|
+
const { value, done } = await reader.read();
|
|
67
|
+
if (done)
|
|
68
|
+
break;
|
|
69
|
+
buffer += decoder.decode(value, { stream: true });
|
|
70
|
+
const chunks = buffer.split("\n\n");
|
|
71
|
+
buffer = chunks.pop() || "";
|
|
72
|
+
for (const chunk of chunks) {
|
|
73
|
+
if (!chunk.trim())
|
|
74
|
+
continue;
|
|
75
|
+
let event = "message";
|
|
76
|
+
const dataLines = [];
|
|
77
|
+
chunk.split("\n").forEach((line) => {
|
|
78
|
+
if (line.startsWith("event:")) {
|
|
79
|
+
event = line.slice(6).trim();
|
|
80
|
+
}
|
|
81
|
+
else if (line.startsWith("data:")) {
|
|
82
|
+
dataLines.push(line.slice(5).trimStart());
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (!dataLines.length)
|
|
86
|
+
continue;
|
|
87
|
+
const rawData = dataLines.join("\n");
|
|
88
|
+
handleStreamEvent(event, rawData, handlers);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function handleStreamEvent(event, rawData, handlers) {
|
|
93
|
+
const parsed = parseMaybeJson(rawData);
|
|
94
|
+
switch (event) {
|
|
95
|
+
case "status": {
|
|
96
|
+
const status = typeof parsed === "string" ? parsed : parsed.status || "";
|
|
97
|
+
handlers.onStatus?.(status);
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "token": {
|
|
101
|
+
const token = typeof parsed === "string"
|
|
102
|
+
? parsed
|
|
103
|
+
: parsed.token || parsed.text || rawData || "";
|
|
104
|
+
handlers.onToken?.(token);
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
case "update": {
|
|
108
|
+
handlers.onUpdate?.(parsed);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case "event":
|
|
112
|
+
case "app-server": {
|
|
113
|
+
handlers.onEvent?.(parsed);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case "error": {
|
|
117
|
+
const msg = typeof parsed === "object" && parsed !== null
|
|
118
|
+
? (parsed.detail || parsed.error || rawData || "File chat failed")
|
|
119
|
+
: rawData || "File chat failed";
|
|
120
|
+
handlers.onError?.(msg);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case "interrupted": {
|
|
124
|
+
const msg = typeof parsed === "object" && parsed !== null
|
|
125
|
+
? (parsed.detail || rawData || "File chat interrupted")
|
|
126
|
+
: rawData || "File chat interrupted";
|
|
127
|
+
handlers.onInterrupted?.(msg);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
case "done":
|
|
131
|
+
case "finish": {
|
|
132
|
+
handlers.onDone?.();
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
default:
|
|
136
|
+
// treat unknown as event for visibility
|
|
137
|
+
handlers.onEvent?.(parsed);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
export async function fetchPendingDraft(target) {
|
|
142
|
+
try {
|
|
143
|
+
const res = (await api(`/api/file-chat/pending?target=${encodeURIComponent(target)}`));
|
|
144
|
+
if (!res || typeof res !== "object")
|
|
145
|
+
return null;
|
|
146
|
+
return {
|
|
147
|
+
target: res.target || target,
|
|
148
|
+
content: res.content || "",
|
|
149
|
+
patch: res.patch || "",
|
|
150
|
+
agent_message: res.agent_message || undefined,
|
|
151
|
+
created_at: res.created_at || undefined,
|
|
152
|
+
base_hash: res.base_hash || undefined,
|
|
153
|
+
current_hash: res.current_hash || undefined,
|
|
154
|
+
is_stale: Boolean(res.is_stale),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
export async function applyDraft(target, options = {}) {
|
|
162
|
+
const res = (await api("/api/file-chat/apply", {
|
|
163
|
+
method: "POST",
|
|
164
|
+
body: { target, force: Boolean(options.force) },
|
|
165
|
+
}));
|
|
166
|
+
return {
|
|
167
|
+
content: res.content || "",
|
|
168
|
+
agent_message: res.agent_message || undefined,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
export async function discardDraft(target) {
|
|
172
|
+
const res = (await api("/api/file-chat/discard", {
|
|
173
|
+
method: "POST",
|
|
174
|
+
body: { target },
|
|
175
|
+
}));
|
|
176
|
+
return {
|
|
177
|
+
content: res.content || "",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
export async function interruptFileChat(target) {
|
|
181
|
+
await api("/api/file-chat/interrupt", { method: "POST", body: { target } });
|
|
182
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { publish } from "./bus.js";
|
|
3
|
+
import { setAutoRefreshEnabled } from "./autoRefresh.js";
|
|
4
|
+
import { getAuthToken, resolvePath } from "./utils.js";
|
|
5
|
+
let initialized = false;
|
|
6
|
+
let healthState = "unknown";
|
|
7
|
+
let lastDetail = null;
|
|
8
|
+
let retryTimer = null;
|
|
9
|
+
function bannerEls() {
|
|
10
|
+
return {
|
|
11
|
+
banner: document.getElementById("repo-health-banner"),
|
|
12
|
+
title: document.getElementById("repo-health-title"),
|
|
13
|
+
detail: document.getElementById("repo-health-detail"),
|
|
14
|
+
retry: document.getElementById("repo-health-retry"),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function renderBanner(status, detail) {
|
|
18
|
+
const { banner, title, detail: detailEl, retry } = bannerEls();
|
|
19
|
+
if (!banner || !title)
|
|
20
|
+
return;
|
|
21
|
+
if (status === "ok") {
|
|
22
|
+
banner.classList.add("hidden");
|
|
23
|
+
banner.classList.remove("error", "warn");
|
|
24
|
+
if (retry)
|
|
25
|
+
retry.disabled = false;
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const isOffline = status === "offline";
|
|
29
|
+
banner.classList.remove("hidden");
|
|
30
|
+
banner.classList.toggle("error", isOffline);
|
|
31
|
+
banner.classList.toggle("warn", !isOffline);
|
|
32
|
+
title.textContent = isOffline
|
|
33
|
+
? "Repo server offline or unreachable"
|
|
34
|
+
: "Repo server uninitialized";
|
|
35
|
+
if (detailEl) {
|
|
36
|
+
detailEl.textContent =
|
|
37
|
+
detail ||
|
|
38
|
+
(isOffline
|
|
39
|
+
? "Check that the repo server is running."
|
|
40
|
+
: "Create .codex-autorunner/tickets/ to initialize this repo.");
|
|
41
|
+
}
|
|
42
|
+
if (retry)
|
|
43
|
+
retry.disabled = false;
|
|
44
|
+
}
|
|
45
|
+
function setHealth(status, detail) {
|
|
46
|
+
const changed = status !== healthState || detail !== lastDetail;
|
|
47
|
+
healthState = status;
|
|
48
|
+
lastDetail = detail || null;
|
|
49
|
+
renderBanner(status, detail);
|
|
50
|
+
setAutoRefreshEnabled(status !== "offline");
|
|
51
|
+
if (changed) {
|
|
52
|
+
publish("repo:health", { status, detail });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function tryFetch(path) {
|
|
56
|
+
const target = resolvePath(path);
|
|
57
|
+
const headers = {};
|
|
58
|
+
const token = getAuthToken();
|
|
59
|
+
if (token)
|
|
60
|
+
headers.Authorization = `Bearer ${token}`;
|
|
61
|
+
const res = await fetch(target, { headers });
|
|
62
|
+
const text = await res.text();
|
|
63
|
+
let payload = null;
|
|
64
|
+
try {
|
|
65
|
+
payload = JSON.parse(text);
|
|
66
|
+
}
|
|
67
|
+
catch (_err) {
|
|
68
|
+
payload = null;
|
|
69
|
+
}
|
|
70
|
+
return { ok: res.ok, status: res.status, payload, text };
|
|
71
|
+
}
|
|
72
|
+
function deriveHealthFromPayload(payload) {
|
|
73
|
+
if (!payload || typeof payload !== "object") {
|
|
74
|
+
return { status: "offline", detail: "Empty health response" };
|
|
75
|
+
}
|
|
76
|
+
const p = payload;
|
|
77
|
+
const payloadStatus = String(p.status || "ok").toLowerCase();
|
|
78
|
+
if (payloadStatus !== "ok" && payloadStatus !== "degraded") {
|
|
79
|
+
return { status: "offline", detail: String(p.detail || payloadStatus) };
|
|
80
|
+
}
|
|
81
|
+
// Ticket-first: the only initialization requirement is `.codex-autorunner/tickets/`.
|
|
82
|
+
const tickets = p.tickets;
|
|
83
|
+
const ticketsStatus = String(tickets?.status || "").toLowerCase();
|
|
84
|
+
if (ticketsStatus && ticketsStatus !== "ok") {
|
|
85
|
+
return {
|
|
86
|
+
status: "degraded",
|
|
87
|
+
detail: "Tickets directory missing; create .codex-autorunner/tickets/.",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Flows DB is lazily created. Only treat truly unavailable storage as degraded.
|
|
91
|
+
const flows = p.flows;
|
|
92
|
+
const flowsStatus = String(flows?.status || "").toLowerCase();
|
|
93
|
+
if (flowsStatus && flowsStatus !== "ok" && flowsStatus !== "missing") {
|
|
94
|
+
return { status: "degraded", detail: `Flows unavailable: ${flowsStatus}` };
|
|
95
|
+
}
|
|
96
|
+
return { status: "ok" };
|
|
97
|
+
}
|
|
98
|
+
async function probeHealth() {
|
|
99
|
+
const paths = ["/api/repo/health", "/health"];
|
|
100
|
+
let lastError = null;
|
|
101
|
+
for (const path of paths) {
|
|
102
|
+
try {
|
|
103
|
+
const res = await tryFetch(path);
|
|
104
|
+
if (res.ok) {
|
|
105
|
+
return deriveHealthFromPayload(res.payload);
|
|
106
|
+
}
|
|
107
|
+
lastError = `${path} → ${res.status}`;
|
|
108
|
+
if (res.status === 404) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
lastError = err.message || String(err);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { status: "offline", detail: lastError };
|
|
118
|
+
}
|
|
119
|
+
function scheduleNext(delayMs) {
|
|
120
|
+
if (retryTimer) {
|
|
121
|
+
clearTimeout(retryTimer);
|
|
122
|
+
retryTimer = null;
|
|
123
|
+
}
|
|
124
|
+
retryTimer = setTimeout(() => {
|
|
125
|
+
void refreshRepoHealth();
|
|
126
|
+
}, delayMs);
|
|
127
|
+
}
|
|
128
|
+
export async function refreshRepoHealth() {
|
|
129
|
+
const result = await probeHealth();
|
|
130
|
+
setHealth(result.status, result.detail);
|
|
131
|
+
const nextDelay = result.status === "ok" ? 20000 : Math.min(60000, result.status === "degraded" ? 20000 : 10000);
|
|
132
|
+
scheduleNext(nextDelay);
|
|
133
|
+
}
|
|
134
|
+
export async function initHealthGate() {
|
|
135
|
+
if (initialized)
|
|
136
|
+
return;
|
|
137
|
+
initialized = true;
|
|
138
|
+
const { retry } = bannerEls();
|
|
139
|
+
if (retry) {
|
|
140
|
+
retry.addEventListener("click", () => {
|
|
141
|
+
if (retryTimer) {
|
|
142
|
+
clearTimeout(retryTimer);
|
|
143
|
+
retryTimer = null;
|
|
144
|
+
}
|
|
145
|
+
void refreshRepoHealth();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
await refreshRepoHealth();
|
|
149
|
+
}
|
|
150
|
+
export function isRepoHealthy() {
|
|
151
|
+
return healthState === "ok" || healthState === "degraded";
|
|
152
|
+
}
|
|
153
|
+
export function currentHealthDetail() {
|
|
154
|
+
return lastDetail;
|
|
155
|
+
}
|