codex-autorunner 1.1.0__py3-none-any.whl → 1.2.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/agents/opencode/client.py +113 -4
- codex_autorunner/agents/opencode/supervisor.py +4 -0
- codex_autorunner/agents/registry.py +17 -7
- codex_autorunner/bootstrap.py +219 -1
- codex_autorunner/core/__init__.py +17 -1
- codex_autorunner/core/about_car.py +114 -1
- codex_autorunner/core/app_server_threads.py +6 -0
- codex_autorunner/core/config.py +236 -1
- codex_autorunner/core/context_awareness.py +38 -0
- codex_autorunner/core/docs.py +0 -122
- codex_autorunner/core/filebox.py +265 -0
- codex_autorunner/core/flows/controller.py +71 -1
- codex_autorunner/core/flows/reconciler.py +4 -1
- codex_autorunner/core/flows/runtime.py +22 -0
- codex_autorunner/core/flows/store.py +61 -9
- codex_autorunner/core/flows/transition.py +23 -16
- codex_autorunner/core/flows/ux_helpers.py +18 -3
- codex_autorunner/core/flows/worker_process.py +32 -6
- codex_autorunner/core/hub.py +198 -41
- codex_autorunner/core/lifecycle_events.py +253 -0
- codex_autorunner/core/path_utils.py +2 -1
- codex_autorunner/core/pma_audit.py +224 -0
- codex_autorunner/core/pma_context.py +496 -0
- codex_autorunner/core/pma_dispatch_interceptor.py +284 -0
- codex_autorunner/core/pma_lifecycle.py +527 -0
- codex_autorunner/core/pma_queue.py +367 -0
- codex_autorunner/core/pma_safety.py +221 -0
- codex_autorunner/core/pma_state.py +115 -0
- codex_autorunner/core/ports/agent_backend.py +2 -5
- codex_autorunner/core/ports/run_event.py +1 -4
- codex_autorunner/core/prompt.py +0 -80
- codex_autorunner/core/prompts.py +56 -172
- codex_autorunner/core/redaction.py +0 -4
- codex_autorunner/core/review_context.py +11 -9
- codex_autorunner/core/runner_controller.py +35 -33
- codex_autorunner/core/runner_state.py +147 -0
- codex_autorunner/core/runtime.py +829 -0
- codex_autorunner/core/sqlite_utils.py +13 -4
- codex_autorunner/core/state.py +7 -10
- codex_autorunner/core/state_roots.py +5 -0
- codex_autorunner/core/templates/__init__.py +39 -0
- codex_autorunner/core/templates/git_mirror.py +234 -0
- codex_autorunner/core/templates/provenance.py +56 -0
- codex_autorunner/core/templates/scan_cache.py +120 -0
- codex_autorunner/core/ticket_linter_cli.py +17 -0
- codex_autorunner/core/ticket_manager_cli.py +154 -92
- codex_autorunner/core/time_utils.py +11 -0
- codex_autorunner/core/types.py +18 -0
- codex_autorunner/core/utils.py +34 -6
- codex_autorunner/flows/review/service.py +23 -25
- codex_autorunner/flows/ticket_flow/definition.py +43 -1
- codex_autorunner/integrations/agents/__init__.py +2 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +18 -0
- codex_autorunner/integrations/agents/codex_backend.py +19 -8
- codex_autorunner/integrations/agents/runner.py +3 -8
- codex_autorunner/integrations/agents/wiring.py +8 -0
- codex_autorunner/integrations/telegram/doctor.py +228 -6
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +236 -74
- codex_autorunner/integrations/telegram/handlers/commands/files.py +314 -75
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +346 -58
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +498 -37
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +202 -45
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +18 -7
- codex_autorunner/integrations/telegram/handlers/messages.py +26 -1
- codex_autorunner/integrations/telegram/helpers.py +1 -3
- codex_autorunner/integrations/telegram/runtime.py +9 -4
- codex_autorunner/integrations/telegram/service.py +30 -0
- codex_autorunner/integrations/telegram/state.py +38 -0
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +10 -4
- codex_autorunner/integrations/telegram/transport.py +10 -3
- codex_autorunner/integrations/templates/__init__.py +27 -0
- codex_autorunner/integrations/templates/scan_agent.py +312 -0
- codex_autorunner/server.py +2 -2
- codex_autorunner/static/agentControls.js +21 -5
- codex_autorunner/static/app.js +115 -11
- codex_autorunner/static/chatUploads.js +137 -0
- codex_autorunner/static/docChatCore.js +185 -13
- codex_autorunner/static/fileChat.js +68 -40
- codex_autorunner/static/fileboxUi.js +159 -0
- codex_autorunner/static/hub.js +46 -81
- codex_autorunner/static/index.html +303 -24
- codex_autorunner/static/messages.js +82 -4
- codex_autorunner/static/notifications.js +255 -0
- codex_autorunner/static/pma.js +1167 -0
- codex_autorunner/static/settings.js +3 -0
- codex_autorunner/static/streamUtils.js +57 -0
- codex_autorunner/static/styles.css +9125 -6742
- codex_autorunner/static/templateReposSettings.js +225 -0
- codex_autorunner/static/ticketChatActions.js +165 -3
- codex_autorunner/static/ticketChatStream.js +17 -119
- codex_autorunner/static/ticketEditor.js +41 -13
- codex_autorunner/static/ticketTemplates.js +798 -0
- codex_autorunner/static/tickets.js +69 -19
- codex_autorunner/static/turnEvents.js +27 -0
- codex_autorunner/static/turnResume.js +33 -0
- codex_autorunner/static/utils.js +28 -0
- codex_autorunner/static/workspace.js +258 -44
- codex_autorunner/static/workspaceFileBrowser.js +6 -4
- codex_autorunner/surfaces/cli/cli.py +1465 -155
- codex_autorunner/surfaces/cli/pma_cli.py +817 -0
- codex_autorunner/surfaces/web/app.py +253 -49
- codex_autorunner/surfaces/web/routes/__init__.py +4 -0
- codex_autorunner/surfaces/web/routes/analytics.py +29 -22
- codex_autorunner/surfaces/web/routes/file_chat.py +317 -36
- codex_autorunner/surfaces/web/routes/filebox.py +227 -0
- codex_autorunner/surfaces/web/routes/flows.py +219 -29
- codex_autorunner/surfaces/web/routes/messages.py +70 -39
- codex_autorunner/surfaces/web/routes/pma.py +1652 -0
- codex_autorunner/surfaces/web/routes/repos.py +1 -1
- codex_autorunner/surfaces/web/routes/shared.py +0 -3
- codex_autorunner/surfaces/web/routes/templates.py +634 -0
- codex_autorunner/surfaces/web/runner_manager.py +2 -2
- codex_autorunner/surfaces/web/schemas.py +70 -18
- codex_autorunner/tickets/agent_pool.py +27 -0
- codex_autorunner/tickets/files.py +33 -16
- codex_autorunner/tickets/lint.py +50 -0
- codex_autorunner/tickets/models.py +3 -0
- codex_autorunner/tickets/outbox.py +41 -5
- codex_autorunner/tickets/runner.py +350 -69
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/METADATA +15 -19
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/RECORD +125 -94
- codex_autorunner/core/adapter_utils.py +0 -21
- codex_autorunner/core/engine.py +0 -3302
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/WHEEL +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-1.1.0.dist-info → codex_autorunner-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// GENERATED FILE - do not edit directly. Source: static_src/
|
|
2
|
+
import { api, confirmModal, escapeHtml, flash, resolvePath } from "./utils.js";
|
|
3
|
+
function formatBytes(size) {
|
|
4
|
+
if (!size && size !== 0)
|
|
5
|
+
return "";
|
|
6
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
7
|
+
let val = size;
|
|
8
|
+
let idx = 0;
|
|
9
|
+
while (val >= 1024 && idx < units.length - 1) {
|
|
10
|
+
val /= 1024;
|
|
11
|
+
idx += 1;
|
|
12
|
+
}
|
|
13
|
+
const formatted = idx === 0 ? String(val) : val.toFixed(1).replace(/\.0$/, "");
|
|
14
|
+
return `${formatted}${units[idx]}`;
|
|
15
|
+
}
|
|
16
|
+
function pathPrefix(config) {
|
|
17
|
+
if (config.scope === "repo") {
|
|
18
|
+
return config.basePath || "/api/filebox";
|
|
19
|
+
}
|
|
20
|
+
if (config.scope === "pma") {
|
|
21
|
+
return config.basePath || "/hub/pma/files";
|
|
22
|
+
}
|
|
23
|
+
if (!config.repoId) {
|
|
24
|
+
throw new Error("repoId is required for hub filebox");
|
|
25
|
+
}
|
|
26
|
+
const base = config.basePath || "/hub/filebox";
|
|
27
|
+
return `${base}/${encodeURIComponent(config.repoId)}`;
|
|
28
|
+
}
|
|
29
|
+
async function listFileBox(config) {
|
|
30
|
+
const prefix = pathPrefix(config);
|
|
31
|
+
const res = (await api(prefix));
|
|
32
|
+
return {
|
|
33
|
+
inbox: Array.isArray(res?.inbox) ? res.inbox : [],
|
|
34
|
+
outbox: Array.isArray(res?.outbox) ? res.outbox : [],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
async function uploadFiles(config, box, files) {
|
|
38
|
+
const prefix = pathPrefix(config);
|
|
39
|
+
const form = new FormData();
|
|
40
|
+
const names = [];
|
|
41
|
+
Array.from(files).forEach((file) => {
|
|
42
|
+
form.append(file.name, file);
|
|
43
|
+
names.push(file.name);
|
|
44
|
+
});
|
|
45
|
+
await api(`${prefix}/${box}`, {
|
|
46
|
+
method: "POST",
|
|
47
|
+
body: form,
|
|
48
|
+
});
|
|
49
|
+
return names;
|
|
50
|
+
}
|
|
51
|
+
async function deleteFile(config, box, name) {
|
|
52
|
+
const prefix = pathPrefix(config);
|
|
53
|
+
await api(`${prefix}/${box}/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
54
|
+
}
|
|
55
|
+
export function createFileBoxWidget(opts) {
|
|
56
|
+
const uploadBox = opts.uploadBox || "inbox";
|
|
57
|
+
let listing = { inbox: [], outbox: [] };
|
|
58
|
+
const renderList = (box, el) => {
|
|
59
|
+
if (!el)
|
|
60
|
+
return;
|
|
61
|
+
const files = listing[box] || [];
|
|
62
|
+
if (!files.length) {
|
|
63
|
+
el.innerHTML = opts.emptyMessage
|
|
64
|
+
? `<div class="filebox-empty muted small">${escapeHtml(opts.emptyMessage)}</div>`
|
|
65
|
+
: "";
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
el.innerHTML = files
|
|
69
|
+
.map((entry) => {
|
|
70
|
+
const href = entry.url ? resolvePath(entry.url) : "#";
|
|
71
|
+
const meta = entry.modified_at ? new Date(entry.modified_at).toLocaleString() : "";
|
|
72
|
+
const size = formatBytes(entry.size);
|
|
73
|
+
const source = entry.source && entry.source !== "filebox" ? ` • ${escapeHtml(entry.source || "")}` : "";
|
|
74
|
+
return `
|
|
75
|
+
<div class="filebox-item">
|
|
76
|
+
<div class="filebox-row">
|
|
77
|
+
<a class="filebox-link" href="${escapeHtml(href)}" download>${escapeHtml(entry.name)}</a>
|
|
78
|
+
<button class="ghost sm icon-btn filebox-delete" data-box="${box}" data-file="${escapeHtml(entry.name)}" title="Delete">×</button>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="filebox-meta muted small">${escapeHtml(size || "")}${source}${meta ? ` • ${escapeHtml(meta)}` : ""}</div>
|
|
81
|
+
</div>
|
|
82
|
+
`;
|
|
83
|
+
})
|
|
84
|
+
.join("");
|
|
85
|
+
el.querySelectorAll(".filebox-delete").forEach((btn) => {
|
|
86
|
+
btn.addEventListener("click", async (evt) => {
|
|
87
|
+
const target = evt.currentTarget;
|
|
88
|
+
const boxName = (target.dataset.box || "");
|
|
89
|
+
const file = target.dataset.file || "";
|
|
90
|
+
if (!boxName || !file)
|
|
91
|
+
return;
|
|
92
|
+
const confirmed = await confirmModal(`Delete ${file}?`);
|
|
93
|
+
if (!confirmed)
|
|
94
|
+
return;
|
|
95
|
+
try {
|
|
96
|
+
await deleteFile(opts, boxName, file);
|
|
97
|
+
await refresh();
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const msg = err.message || "Delete failed";
|
|
101
|
+
flash(msg, "error");
|
|
102
|
+
opts.onError?.(msg);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
};
|
|
107
|
+
const render = () => {
|
|
108
|
+
renderList("inbox", opts.inboxEl);
|
|
109
|
+
renderList("outbox", opts.outboxEl);
|
|
110
|
+
};
|
|
111
|
+
async function refresh() {
|
|
112
|
+
try {
|
|
113
|
+
listing = await listFileBox(opts);
|
|
114
|
+
render();
|
|
115
|
+
opts.onChange?.(listing);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
const msg = err.message || "Failed to load FileBox";
|
|
119
|
+
flash(msg, "error");
|
|
120
|
+
opts.onError?.(msg);
|
|
121
|
+
}
|
|
122
|
+
return listing;
|
|
123
|
+
}
|
|
124
|
+
const handleUpload = async (files) => {
|
|
125
|
+
if (!files || !files.length)
|
|
126
|
+
return;
|
|
127
|
+
const names = Array.from(files).map((f) => f.name);
|
|
128
|
+
try {
|
|
129
|
+
await uploadFiles(opts, uploadBox, files);
|
|
130
|
+
opts.onUpload?.(names);
|
|
131
|
+
await refresh();
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
const msg = err.message || "Upload failed";
|
|
135
|
+
flash(msg, "error");
|
|
136
|
+
opts.onError?.(msg);
|
|
137
|
+
}
|
|
138
|
+
finally {
|
|
139
|
+
if (opts.uploadInput)
|
|
140
|
+
opts.uploadInput.value = "";
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
if (opts.uploadBtn && opts.uploadInput) {
|
|
144
|
+
opts.uploadBtn.addEventListener("click", () => opts.uploadInput?.click());
|
|
145
|
+
opts.uploadInput.addEventListener("change", () => void handleUpload(opts.uploadInput?.files));
|
|
146
|
+
}
|
|
147
|
+
if (opts.refreshBtn) {
|
|
148
|
+
opts.refreshBtn.addEventListener("click", () => void refresh());
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
refresh,
|
|
152
|
+
snapshot() {
|
|
153
|
+
return {
|
|
154
|
+
inbox: [...listing.inbox],
|
|
155
|
+
outbox: [...listing.outbox],
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
codex_autorunner/static/hub.js
CHANGED
|
@@ -5,7 +5,6 @@ import { HUB_BASE } from "./env.js";
|
|
|
5
5
|
import { preserveScroll } from "./preserve.js";
|
|
6
6
|
let hubData = { repos: [], last_scan_at: null };
|
|
7
7
|
const prefetchedUrls = new Set();
|
|
8
|
-
let hubInboxHydrated = false;
|
|
9
8
|
const HUB_CACHE_TTL_MS = 30000;
|
|
10
9
|
const HUB_CACHE_KEY = `car:hub:${HUB_BASE || "/"}`;
|
|
11
10
|
const HUB_USAGE_CACHE_KEY = `car:hub-usage:${HUB_BASE || "/"}`;
|
|
@@ -14,6 +13,7 @@ const HUB_REFRESH_IDLE_MS = 30000;
|
|
|
14
13
|
let lastHubAutoRefreshAt = 0;
|
|
15
14
|
const repoListEl = document.getElementById("hub-repo-list");
|
|
16
15
|
const lastScanEl = document.getElementById("hub-last-scan");
|
|
16
|
+
const pmaLastScanEl = document.getElementById("pma-last-scan");
|
|
17
17
|
const totalEl = document.getElementById("hub-count-total");
|
|
18
18
|
const runningEl = document.getElementById("hub-count-running");
|
|
19
19
|
const missingEl = document.getElementById("hub-count-missing");
|
|
@@ -23,8 +23,7 @@ const hubUsageChartCanvas = document.getElementById("hub-usage-chart-canvas");
|
|
|
23
23
|
const hubUsageChartRange = document.getElementById("hub-usage-chart-range");
|
|
24
24
|
const hubUsageChartSegment = document.getElementById("hub-usage-chart-segment");
|
|
25
25
|
const hubVersionEl = document.getElementById("hub-version");
|
|
26
|
-
const
|
|
27
|
-
const hubInboxRefresh = document.getElementById("hub-inbox-refresh");
|
|
26
|
+
const pmaVersionEl = document.getElementById("pma-version");
|
|
28
27
|
const UPDATE_STATUS_SEEN_KEY = "car_update_status_seen";
|
|
29
28
|
const HUB_JOB_POLL_INTERVAL_MS = 1200;
|
|
30
29
|
const HUB_JOB_TIMEOUT_MS = 180000;
|
|
@@ -84,9 +83,7 @@ function formatLastActivity(repo) {
|
|
|
84
83
|
}
|
|
85
84
|
function setButtonLoading(scanning) {
|
|
86
85
|
const buttons = [
|
|
87
|
-
document.getElementById("hub-scan"),
|
|
88
86
|
document.getElementById("hub-quick-scan"),
|
|
89
|
-
document.getElementById("hub-refresh"),
|
|
90
87
|
];
|
|
91
88
|
buttons.forEach((btn) => {
|
|
92
89
|
if (!btn)
|
|
@@ -156,6 +153,9 @@ function renderSummary(repos) {
|
|
|
156
153
|
if (lastScanEl) {
|
|
157
154
|
lastScanEl.textContent = formatTimeCompact(hubData.last_scan_at);
|
|
158
155
|
}
|
|
156
|
+
if (pmaLastScanEl) {
|
|
157
|
+
pmaLastScanEl.textContent = formatTimeCompact(hubData.last_scan_at);
|
|
158
|
+
}
|
|
159
159
|
}
|
|
160
160
|
function formatTokensCompact(val) {
|
|
161
161
|
if (val === null || val === undefined)
|
|
@@ -182,17 +182,10 @@ function formatTokensAxis(val) {
|
|
|
182
182
|
function getRepoUsage(repoId) {
|
|
183
183
|
const usage = hubUsageIndex[repoId];
|
|
184
184
|
if (!usage)
|
|
185
|
-
return { label: "—",
|
|
185
|
+
return { label: "—", hasData: false };
|
|
186
186
|
const totals = usage.totals || {};
|
|
187
|
-
const cached = totals.cached_input_tokens || 0;
|
|
188
|
-
const input = totals.input_tokens || 0;
|
|
189
|
-
const cachePercent = input ? Math.round((cached / input) * 100) : 0;
|
|
190
|
-
const meta = usage.events === undefined
|
|
191
|
-
? ""
|
|
192
|
-
: `${usage.events}ev${input ? ` · ${cachePercent}%↻` : ""}`;
|
|
193
187
|
return {
|
|
194
188
|
label: formatTokensCompact(totals.total_tokens),
|
|
195
|
-
meta,
|
|
196
189
|
hasData: true,
|
|
197
190
|
};
|
|
198
191
|
}
|
|
@@ -616,7 +609,7 @@ async function handleSystemUpdate(btnId, targetSelectId) {
|
|
|
616
609
|
}
|
|
617
610
|
}
|
|
618
611
|
function initHubSettings() {
|
|
619
|
-
const
|
|
612
|
+
const settingsBtns = Array.from(document.querySelectorAll("#hub-settings, #pma-settings"));
|
|
620
613
|
const modal = document.getElementById("hub-settings-modal");
|
|
621
614
|
const closeBtn = document.getElementById("hub-settings-close");
|
|
622
615
|
const updateBtn = document.getElementById("hub-update-btn");
|
|
@@ -629,14 +622,16 @@ function initHubSettings() {
|
|
|
629
622
|
close();
|
|
630
623
|
}
|
|
631
624
|
};
|
|
632
|
-
if (
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
625
|
+
if (modal && settingsBtns.length > 0) {
|
|
626
|
+
settingsBtns.forEach((settingsBtn) => {
|
|
627
|
+
settingsBtn.addEventListener("click", () => {
|
|
628
|
+
const triggerEl = document.activeElement;
|
|
629
|
+
hideModal();
|
|
630
|
+
closeModal = openModal(modal, {
|
|
631
|
+
initialFocus: closeBtn || updateBtn || modal,
|
|
632
|
+
returnFocusTo: triggerEl,
|
|
633
|
+
onRequestClose: hideModal,
|
|
634
|
+
});
|
|
640
635
|
});
|
|
641
636
|
});
|
|
642
637
|
}
|
|
@@ -815,10 +810,26 @@ function renderRepos(repos) {
|
|
|
815
810
|
<span class="pill pill-small hub-usage-pill">
|
|
816
811
|
${escapeHtml(usageInfo.label)}
|
|
817
812
|
</span>
|
|
818
|
-
${usageInfo.meta
|
|
819
|
-
? `<span class="hub-usage-pill-meta">${escapeHtml(usageInfo.meta)}</span>`
|
|
820
|
-
: ""}
|
|
821
813
|
</div>`;
|
|
814
|
+
// Ticket flow progress line
|
|
815
|
+
let ticketFlowLine = "";
|
|
816
|
+
const tf = repo.ticket_flow;
|
|
817
|
+
if (tf && tf.total_count > 0) {
|
|
818
|
+
const percent = Math.round((tf.done_count / tf.total_count) * 100);
|
|
819
|
+
const isActive = tf.status === "running" || tf.status === "paused";
|
|
820
|
+
const statusSuffix = tf.status === "paused"
|
|
821
|
+
? " · paused"
|
|
822
|
+
: tf.current_step
|
|
823
|
+
? ` · step ${tf.current_step}`
|
|
824
|
+
: "";
|
|
825
|
+
ticketFlowLine = `
|
|
826
|
+
<div class="hub-repo-flow-line${isActive ? " active" : ""}">
|
|
827
|
+
<div class="hub-flow-bar">
|
|
828
|
+
<div class="hub-flow-fill" style="width:${percent}%"></div>
|
|
829
|
+
</div>
|
|
830
|
+
<span class="hub-flow-text">${tf.done_count}/${tf.total_count}${statusSuffix}</span>
|
|
831
|
+
</div>`;
|
|
832
|
+
}
|
|
822
833
|
card.innerHTML = `
|
|
823
834
|
<div class="hub-repo-row">
|
|
824
835
|
<div class="hub-repo-left">
|
|
@@ -833,6 +844,7 @@ function renderRepos(repos) {
|
|
|
833
844
|
${infoLine}
|
|
834
845
|
</div>
|
|
835
846
|
${usageLine}
|
|
847
|
+
${ticketFlowLine}
|
|
836
848
|
</div>
|
|
837
849
|
<div class="hub-repo-right">
|
|
838
850
|
${actions || ""}
|
|
@@ -903,7 +915,6 @@ async function refreshHub() {
|
|
|
903
915
|
saveSessionCache(HUB_CACHE_KEY, hubData);
|
|
904
916
|
renderSummary(data.repos || []);
|
|
905
917
|
renderReposWithScroll(data.repos || []);
|
|
906
|
-
await loadHubInbox().catch(() => { });
|
|
907
918
|
loadHubUsage({ silent: true }).catch(() => { });
|
|
908
919
|
}
|
|
909
920
|
catch (err) {
|
|
@@ -913,46 +924,6 @@ async function refreshHub() {
|
|
|
913
924
|
setButtonLoading(false);
|
|
914
925
|
}
|
|
915
926
|
}
|
|
916
|
-
async function loadHubInbox(ctx) {
|
|
917
|
-
if (!hubInboxList)
|
|
918
|
-
return;
|
|
919
|
-
if (!hubInboxHydrated || ctx?.reason === "manual") {
|
|
920
|
-
hubInboxList.textContent = "Loading…";
|
|
921
|
-
}
|
|
922
|
-
try {
|
|
923
|
-
const payload = (await api("/hub/messages", { method: "GET" }));
|
|
924
|
-
const items = payload?.items || [];
|
|
925
|
-
const html = !items.length
|
|
926
|
-
? '<div class="muted">No paused runs</div>'
|
|
927
|
-
: items
|
|
928
|
-
.map((item) => {
|
|
929
|
-
const title = item.message?.title || item.message?.mode || "Message";
|
|
930
|
-
const excerpt = item.message?.body ? item.message.body.slice(0, 160) : "";
|
|
931
|
-
const repoLabel = item.repo_display_name || item.repo_id;
|
|
932
|
-
const href = item.open_url || `/repos/${item.repo_id}/?tab=messages&run_id=${item.run_id}`;
|
|
933
|
-
return `
|
|
934
|
-
<a class="hub-inbox-item" href="${escapeHtml(resolvePath(href))}">
|
|
935
|
-
<div class="hub-inbox-item-header">
|
|
936
|
-
<span class="hub-inbox-repo">${escapeHtml(repoLabel)}</span>
|
|
937
|
-
<span class="pill pill-small pill-warn">paused</span>
|
|
938
|
-
</div>
|
|
939
|
-
<div class="hub-inbox-title">${escapeHtml(title)}</div>
|
|
940
|
-
<div class="hub-inbox-excerpt muted small">${escapeHtml(excerpt)}</div>
|
|
941
|
-
</a>
|
|
942
|
-
`;
|
|
943
|
-
})
|
|
944
|
-
.join("");
|
|
945
|
-
preserveScroll(hubInboxList, () => {
|
|
946
|
-
hubInboxList.innerHTML = html;
|
|
947
|
-
}, { restoreOnNextFrame: true });
|
|
948
|
-
hubInboxHydrated = true;
|
|
949
|
-
}
|
|
950
|
-
catch (_err) {
|
|
951
|
-
preserveScroll(hubInboxList, () => {
|
|
952
|
-
hubInboxList.innerHTML = "";
|
|
953
|
-
}, { restoreOnNextFrame: true });
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
927
|
async function triggerHubScan() {
|
|
957
928
|
setButtonLoading(true);
|
|
958
929
|
try {
|
|
@@ -1167,22 +1138,14 @@ async function handleRepoAction(repoId, action) {
|
|
|
1167
1138
|
}
|
|
1168
1139
|
function attachHubHandlers() {
|
|
1169
1140
|
initHubSettings();
|
|
1170
|
-
const scanBtn = document.getElementById("hub-scan");
|
|
1171
|
-
const refreshBtn = document.getElementById("hub-refresh");
|
|
1172
1141
|
const quickScanBtn = document.getElementById("hub-quick-scan");
|
|
1173
1142
|
const newRepoBtn = document.getElementById("hub-new-repo");
|
|
1174
1143
|
const createCancelBtn = document.getElementById("create-repo-cancel");
|
|
1175
1144
|
const createSubmitBtn = document.getElementById("create-repo-submit");
|
|
1176
1145
|
const createRepoId = document.getElementById("create-repo-id");
|
|
1177
|
-
if (scanBtn) {
|
|
1178
|
-
scanBtn.addEventListener("click", () => triggerHubScan());
|
|
1179
|
-
}
|
|
1180
1146
|
if (quickScanBtn) {
|
|
1181
1147
|
quickScanBtn.addEventListener("click", () => triggerHubScan());
|
|
1182
1148
|
}
|
|
1183
|
-
if (refreshBtn) {
|
|
1184
|
-
refreshBtn.addEventListener("click", () => refreshHub());
|
|
1185
|
-
}
|
|
1186
1149
|
if (hubUsageRefresh) {
|
|
1187
1150
|
hubUsageRefresh.addEventListener("click", () => loadHubUsage());
|
|
1188
1151
|
}
|
|
@@ -1282,15 +1245,20 @@ async function dynamicRefreshHub() {
|
|
|
1282
1245
|
await silentRefreshHub();
|
|
1283
1246
|
}
|
|
1284
1247
|
async function loadHubVersion() {
|
|
1285
|
-
if (!hubVersionEl)
|
|
1286
|
-
return;
|
|
1287
1248
|
try {
|
|
1288
1249
|
const data = await api("/hub/version", { method: "GET" });
|
|
1289
1250
|
const version = data.asset_version || "";
|
|
1290
|
-
|
|
1251
|
+
const formatted = version ? `v${version}` : "v–";
|
|
1252
|
+
if (hubVersionEl)
|
|
1253
|
+
hubVersionEl.textContent = formatted;
|
|
1254
|
+
if (pmaVersionEl)
|
|
1255
|
+
pmaVersionEl.textContent = formatted;
|
|
1291
1256
|
}
|
|
1292
1257
|
catch (_err) {
|
|
1293
|
-
hubVersionEl
|
|
1258
|
+
if (hubVersionEl)
|
|
1259
|
+
hubVersionEl.textContent = "v–";
|
|
1260
|
+
if (pmaVersionEl)
|
|
1261
|
+
pmaVersionEl.textContent = "v–";
|
|
1294
1262
|
}
|
|
1295
1263
|
}
|
|
1296
1264
|
async function checkUpdateStatus() {
|
|
@@ -1322,9 +1290,6 @@ export function initHub() {
|
|
|
1322
1290
|
return;
|
|
1323
1291
|
attachHubHandlers();
|
|
1324
1292
|
initHubUsageChartControls();
|
|
1325
|
-
hubInboxRefresh?.addEventListener("click", () => {
|
|
1326
|
-
void loadHubInbox({ reason: "manual" });
|
|
1327
|
-
});
|
|
1328
1293
|
const cachedHub = loadSessionCache(HUB_CACHE_KEY, HUB_CACHE_TTL_MS);
|
|
1329
1294
|
if (cachedHub) {
|
|
1330
1295
|
hubData = cachedHub;
|