agent-control-plane 0.1.13 → 0.1.16
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.
- package/README.md +250 -355
- package/SKILL.md +1 -1
- package/hooks/heartbeat-hooks.sh +16 -9
- package/npm/bin/agent-control-plane.js +117 -8
- package/package.json +3 -1
- package/references/commands.md +2 -2
- package/references/control-plane-map.md +1 -1
- package/tools/bin/agent-project-reconcile-issue-session +23 -0
- package/tools/bin/agent-project-reconcile-pr-session +191 -22
- package/tools/bin/agent-project-run-codex-resilient +57 -2
- package/tools/bin/agent-project-run-openclaw-session +46 -0
- package/tools/bin/agent-project-worker-status +37 -0
- package/tools/bin/flow-config-lib.sh +7 -0
- package/tools/bin/flow-shell-lib.sh +2 -0
- package/tools/bin/heartbeat-safe-auto.sh +20 -10
- package/tools/bin/project-runtimectl.sh +1 -1
- package/tools/bin/provider-cooldown-state.sh +39 -1
- package/tools/bin/start-issue-worker.sh +35 -0
- package/tools/bin/start-pr-fix-worker.sh +3 -0
- package/tools/bin/start-pr-review-worker.sh +3 -0
- package/tools/bin/start-resident-issue-loop.sh +1 -0
- package/tools/dashboard/app.js +136 -0
- package/tools/dashboard/dashboard_snapshot.py +253 -3
- package/tools/dashboard/index.html +5 -1
- package/tools/dashboard/styles.css +97 -20
- package/tools/templates/pr-fix-template.md +6 -6
- package/tools/templates/pr-merge-repair-template.md +6 -6
- package/tools/vendor/codex-quota-manager/scripts/auto-switch.sh +8 -6
- package/tools/bin/render-dashboard-snapshot.py +0 -16
- package/tools/templates/legacy/issue-prompt-template-pre-slim.md +0 -109
|
@@ -361,7 +361,7 @@ print_status() {
|
|
|
361
361
|
if [[ -n "${heartbeat}" || -n "${shared_loop}" || -n "${supervisor}" || "${controller_count}" != "0" || "${active_session_count}" != "0" ]]; then
|
|
362
362
|
runtime_status="running"
|
|
363
363
|
fi
|
|
364
|
-
if [[ -z "${heartbeat}" && -z "${supervisor}" &&
|
|
364
|
+
if [[ -z "${heartbeat}" && -z "${supervisor}" && "${active_session_count}" == "0" && ( -n "${shared_loop}" || "${controller_count}" != "0" ) ]]; then
|
|
365
365
|
runtime_status="partial"
|
|
366
366
|
fi
|
|
367
367
|
|
|
@@ -20,6 +20,7 @@ backend=""
|
|
|
20
20
|
model=""
|
|
21
21
|
action=""
|
|
22
22
|
reason=""
|
|
23
|
+
label=""
|
|
23
24
|
|
|
24
25
|
case "$#" in
|
|
25
26
|
1)
|
|
@@ -77,6 +78,33 @@ resolve_backend() {
|
|
|
77
78
|
flow_config_get "${CONFIG_YAML}" "execution.coding_worker"
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
resolve_codex_label() {
|
|
82
|
+
local configured_label="${ACP_ACTIVE_PROVIDER_LABEL:-${F_LOSNING_ACTIVE_PROVIDER_LABEL:-}}"
|
|
83
|
+
local codex_quota_bin=""
|
|
84
|
+
local active_label=""
|
|
85
|
+
|
|
86
|
+
if [[ -n "${configured_label}" ]]; then
|
|
87
|
+
printf '%s\n' "${configured_label}"
|
|
88
|
+
return 0
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [[ -n "${ACP_CODEX_QUOTA_LABEL:-${F_LOSNING_CODEX_QUOTA_LABEL:-}}" ]]; then
|
|
92
|
+
printf '%s\n' "${ACP_CODEX_QUOTA_LABEL:-${F_LOSNING_CODEX_QUOTA_LABEL:-}}"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
codex_quota_bin="$(flow_resolve_codex_quota_bin "${SCRIPT_DIR}")"
|
|
97
|
+
if [[ -n "${codex_quota_bin}" && -x "${codex_quota_bin}" ]]; then
|
|
98
|
+
active_label="$("${codex_quota_bin}" codex list --json 2>/dev/null | jq -r '.activeInfo.trackedLabel // .activeInfo.activeLabel // empty' 2>/dev/null || true)"
|
|
99
|
+
if [[ -n "${active_label}" ]]; then
|
|
100
|
+
printf '%s\n' "${active_label}"
|
|
101
|
+
return 0
|
|
102
|
+
fi
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
return 1
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
resolve_model() {
|
|
81
109
|
local resolved_backend="${1:?backend required}"
|
|
82
110
|
local raw_model="${2:-}"
|
|
@@ -147,7 +175,16 @@ case "${action}" in
|
|
|
147
175
|
;;
|
|
148
176
|
esac
|
|
149
177
|
|
|
150
|
-
|
|
178
|
+
if [[ "${backend}" == "codex" ]]; then
|
|
179
|
+
label="$(resolve_codex_label || true)"
|
|
180
|
+
fi
|
|
181
|
+
|
|
182
|
+
provider_key_source="${backend}-${model}"
|
|
183
|
+
if [[ "${backend}" == "codex" && -n "${label}" ]]; then
|
|
184
|
+
provider_key_source="${provider_key_source}-${label}"
|
|
185
|
+
fi
|
|
186
|
+
|
|
187
|
+
provider_key="$(flow_sanitize_provider_key "${provider_key_source}")"
|
|
151
188
|
out="$(
|
|
152
189
|
ACP_STATE_ROOT="${STATE_ROOT}" \
|
|
153
190
|
ACP_PROVIDER_QUOTA_COOLDOWNS="${COOLDOWNS}" \
|
|
@@ -162,5 +199,6 @@ out="$(
|
|
|
162
199
|
|
|
163
200
|
printf 'BACKEND=%s\n' "${backend}"
|
|
164
201
|
printf 'MODEL=%s\n' "${model}"
|
|
202
|
+
printf 'LABEL=%s\n' "${label}"
|
|
165
203
|
printf 'PROVIDER_KEY=%s\n' "${provider_key}"
|
|
166
204
|
printf '%s\n' "${out}"
|
|
@@ -54,6 +54,9 @@ rollback_labels_on_failure() {
|
|
|
54
54
|
if [[ "${label_rollback_armed}" != "yes" || "${launch_success}" == "yes" ]]; then
|
|
55
55
|
return 0
|
|
56
56
|
fi
|
|
57
|
+
if [[ -d "${RUN_DIR}" && ! -f "${RUN_DIR}/run.env" && ! -f "${RUN_DIR}/runner.env" && ! -f "${RUN_DIR}/result.env" ]]; then
|
|
58
|
+
rm -rf "${RUN_DIR}" >/dev/null 2>&1 || true
|
|
59
|
+
fi
|
|
57
60
|
if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
|
|
58
61
|
bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${ISSUE_ID}" --remove agent-running >/dev/null 2>&1 || true
|
|
59
62
|
fi
|
|
@@ -377,6 +380,30 @@ const recentPrs = recentNumbers.map((number) => {
|
|
|
377
380
|
const activePrs = recentPrs.filter((pr) => pr.state === 'open' || pr.state === 'draft');
|
|
378
381
|
const completedPrs = recentPrs.filter((pr) => pr.state !== 'open' && pr.state !== 'draft');
|
|
379
382
|
|
|
383
|
+
const recentCycleNotes = [];
|
|
384
|
+
for (const comment of [...(issue.comments || [])].reverse()) {
|
|
385
|
+
const body = String(comment?.body || '').trim();
|
|
386
|
+
if (!body) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (!/^(Completed|Blocked on|# Blocker:|Host-side publish blocked|Host-side publish failed)/im.test(body)) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const summaryLines = body
|
|
393
|
+
.split(/\r?\n/)
|
|
394
|
+
.map((line) => line.trim())
|
|
395
|
+
.filter(Boolean)
|
|
396
|
+
.slice(0, 6);
|
|
397
|
+
if (summaryLines.length === 0) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const summary = summaryLines.join(' | ');
|
|
401
|
+
recentCycleNotes.push(summary.length > 420 ? `${summary.slice(0, 417)}...` : summary);
|
|
402
|
+
if (recentCycleNotes.length >= 3) {
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
380
407
|
const formatPr = (pr) => {
|
|
381
408
|
const suffix = pr.url ? ` ${pr.url}` : '';
|
|
382
409
|
return `- #${pr.number} (${pr.state}): ${pr.title}${suffix}`;
|
|
@@ -389,6 +416,7 @@ const lines = [
|
|
|
389
416
|
'- Before editing, choose exactly one concrete target module, screen, or flow and keep the cycle limited to that target.',
|
|
390
417
|
'- Do not work on a target already covered by an open or draft PR for this issue, or by the most recent completed cycles listed below, unless you are explicitly fixing a regression introduced there.',
|
|
391
418
|
'- If you cannot identify a small non-overlapping target after reviewing recent cycle history, stop blocked using the blocker contract instead of forcing another PR.',
|
|
419
|
+
'- Prefer the recent cycle notes below over repeating broad web research; only fetch outside context when the local baseline or linked advisories materially changed.',
|
|
392
420
|
'- In your final worker output, start with `Target:` and `Why now:` lines before the changed-files list.',
|
|
393
421
|
];
|
|
394
422
|
|
|
@@ -406,6 +434,13 @@ if (completedPrs.length > 0) {
|
|
|
406
434
|
}
|
|
407
435
|
}
|
|
408
436
|
|
|
437
|
+
if (recentCycleNotes.length > 0) {
|
|
438
|
+
lines.push('', '### Recent cycle notes from issue comments');
|
|
439
|
+
for (const note of recentCycleNotes) {
|
|
440
|
+
lines.push(`- ${note}`);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
409
444
|
process.stdout.write(`${lines.join('\n')}\n`);
|
|
410
445
|
EOF
|
|
411
446
|
ISSUE_RECURRING_CONTEXT="$(cat "$ISSUE_RECURRING_CONTEXT_FILE")"
|
|
@@ -33,6 +33,9 @@ rollback_labels_on_failure() {
|
|
|
33
33
|
if [[ "${label_rollback_armed}" != "yes" || "${launch_success}" == "yes" ]]; then
|
|
34
34
|
return 0
|
|
35
35
|
fi
|
|
36
|
+
if [[ -d "${RUN_DIR}" && ! -f "${RUN_DIR}/run.env" && ! -f "${RUN_DIR}/runner.env" && ! -f "${RUN_DIR}/result.env" ]]; then
|
|
37
|
+
rm -rf "${RUN_DIR}" >/dev/null 2>&1 || true
|
|
38
|
+
fi
|
|
36
39
|
if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
|
|
37
40
|
bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${PR_NUMBER}" --remove agent-running >/dev/null 2>&1 || true
|
|
38
41
|
fi
|
|
@@ -32,6 +32,9 @@ rollback_labels_on_failure() {
|
|
|
32
32
|
if [[ "${label_rollback_armed}" != "yes" || "${launch_success}" == "yes" ]]; then
|
|
33
33
|
return 0
|
|
34
34
|
fi
|
|
35
|
+
if [[ -d "${RUN_DIR}" && ! -f "${RUN_DIR}/run.env" && ! -f "${RUN_DIR}/runner.env" && ! -f "${RUN_DIR}/result.env" ]]; then
|
|
36
|
+
rm -rf "${RUN_DIR}" >/dev/null 2>&1 || true
|
|
37
|
+
fi
|
|
35
38
|
if [[ -x "${UPDATE_LABELS_BIN}" ]]; then
|
|
36
39
|
bash "${UPDATE_LABELS_BIN}" --repo-slug "${REPO_SLUG}" --number "${PR_NUMBER}" --remove agent-running >/dev/null 2>&1 || true
|
|
37
40
|
fi
|
|
@@ -204,6 +204,7 @@ controller_refresh_execution_context() {
|
|
|
204
204
|
ACP_OPENCLAW_MODEL F_LOSNING_OPENCLAW_MODEL \
|
|
205
205
|
ACP_OPENCLAW_THINKING F_LOSNING_OPENCLAW_THINKING \
|
|
206
206
|
ACP_OPENCLAW_TIMEOUT_SECONDS F_LOSNING_OPENCLAW_TIMEOUT_SECONDS \
|
|
207
|
+
ACP_OPENCLAW_STALL_SECONDS F_LOSNING_OPENCLAW_STALL_SECONDS \
|
|
207
208
|
ACP_ACTIVE_PROVIDER_POOL_NAME F_LOSNING_ACTIVE_PROVIDER_POOL_NAME \
|
|
208
209
|
ACP_ACTIVE_PROVIDER_BACKEND F_LOSNING_ACTIVE_PROVIDER_BACKEND \
|
|
209
210
|
ACP_ACTIVE_PROVIDER_MODEL F_LOSNING_ACTIVE_PROVIDER_MODEL \
|
package/tools/dashboard/app.js
CHANGED
|
@@ -1,9 +1,56 @@
|
|
|
1
1
|
const refreshButton = document.querySelector("#refresh-button");
|
|
2
|
+
const themeToggleButton = document.querySelector("#theme-toggle");
|
|
2
3
|
const generatedAtNode = document.querySelector("#generated-at");
|
|
3
4
|
const overviewNode = document.querySelector("#overview");
|
|
4
5
|
const profilesNode = document.querySelector("#profiles");
|
|
5
6
|
const seenAlertIds = new Set();
|
|
6
7
|
let notificationPermissionRequested = false;
|
|
8
|
+
const THEME_STORAGE_KEY = "acp-dashboard-theme";
|
|
9
|
+
|
|
10
|
+
function systemPrefersDark() {
|
|
11
|
+
return typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function currentThemePreference() {
|
|
15
|
+
try {
|
|
16
|
+
const stored = window.localStorage.getItem(THEME_STORAGE_KEY);
|
|
17
|
+
if (stored === "light" || stored === "dark") return stored;
|
|
18
|
+
} catch (_error) {
|
|
19
|
+
// Ignore storage access issues and fall back to system preference.
|
|
20
|
+
}
|
|
21
|
+
return systemPrefersDark() ? "dark" : "light";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function updateThemeToggleLabel(theme) {
|
|
25
|
+
if (!themeToggleButton) return;
|
|
26
|
+
const nextTheme = theme === "dark" ? "light" : "dark";
|
|
27
|
+
const label = nextTheme === "dark" ? "Dark mode" : "Light mode";
|
|
28
|
+
themeToggleButton.textContent = label;
|
|
29
|
+
themeToggleButton.setAttribute("aria-label", `Switch to ${label.toLowerCase()}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function applyTheme(theme) {
|
|
33
|
+
document.documentElement.dataset.theme = theme;
|
|
34
|
+
updateThemeToggleLabel(theme);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function persistTheme(theme) {
|
|
38
|
+
try {
|
|
39
|
+
window.localStorage.setItem(THEME_STORAGE_KEY, theme);
|
|
40
|
+
} catch (_error) {
|
|
41
|
+
// Ignore storage access issues.
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function initializeTheme() {
|
|
46
|
+
applyTheme(currentThemePreference());
|
|
47
|
+
if (!themeToggleButton) return;
|
|
48
|
+
themeToggleButton.addEventListener("click", () => {
|
|
49
|
+
const nextTheme = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
|
|
50
|
+
applyTheme(nextTheme);
|
|
51
|
+
persistTheme(nextTheme);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
7
54
|
|
|
8
55
|
function relativeTime(input) {
|
|
9
56
|
if (!input) return "n/a";
|
|
@@ -138,6 +185,33 @@ function renderAlerts(alerts) {
|
|
|
138
185
|
`;
|
|
139
186
|
}
|
|
140
187
|
|
|
188
|
+
function renderCodexRotation(rotation) {
|
|
189
|
+
if (!rotation || !rotation.active_label) {
|
|
190
|
+
return `<div class="empty-state">Codex rotation data is not available yet for this Codex profile.</div>`;
|
|
191
|
+
}
|
|
192
|
+
const candidates = (rotation.candidate_labels || []).length ? rotation.candidate_labels.join(", ") : "n/a";
|
|
193
|
+
const ready = (rotation.ready_candidates || []).length ? rotation.ready_candidates.join(", ") : "none";
|
|
194
|
+
const nextRetry = rotation.next_retry_at
|
|
195
|
+
? `${rotation.next_retry_label || "n/a"} · ${relativeTime(rotation.next_retry_at)}<div class="muted">${rotation.next_retry_at}</div>`
|
|
196
|
+
: "n/a";
|
|
197
|
+
const lastSwitch = rotation.last_switch_label
|
|
198
|
+
? `${rotation.last_switch_label}${rotation.last_switch_reason ? ` · ${rotation.last_switch_reason}` : ""}`
|
|
199
|
+
: "n/a";
|
|
200
|
+
|
|
201
|
+
return renderTable(
|
|
202
|
+
[
|
|
203
|
+
{ label: "Current", render: () => `<div class="mono">${rotation.active_label}</div>` },
|
|
204
|
+
{ label: "Decision", render: () => `<span class="status-pill ${statusClass(rotation.switch_decision || "unknown")}">${rotation.switch_decision || "unknown"}</span>` },
|
|
205
|
+
{ label: "Candidates", render: () => `<div class="mono">${candidates}</div>` },
|
|
206
|
+
{ label: "Ready now", render: () => `<div class="mono">${ready}</div>` },
|
|
207
|
+
{ label: "Next retry", render: () => nextRetry },
|
|
208
|
+
{ label: "Last switch", render: () => `<div class="mono">${lastSwitch}</div>` },
|
|
209
|
+
],
|
|
210
|
+
[{}],
|
|
211
|
+
"No Codex rotation data for this profile.",
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
141
215
|
function renderProfile(profile) {
|
|
142
216
|
const providerBadges = [
|
|
143
217
|
profile.coding_worker ? `<span class="badge good">${profile.coding_worker}</span>` : "",
|
|
@@ -153,6 +227,7 @@ function renderProfile(profile) {
|
|
|
153
227
|
const summaryCards = [
|
|
154
228
|
["Run sessions", profile.counts.active_runs],
|
|
155
229
|
["Running", profile.counts.running_runs],
|
|
230
|
+
["Recent completed", profile.counts.recent_history_runs || 0],
|
|
156
231
|
["Implemented", profile.counts.implemented_runs],
|
|
157
232
|
["Reported", profile.counts.reported_runs],
|
|
158
233
|
["Blocked", profile.counts.blocked_runs],
|
|
@@ -188,6 +263,19 @@ function renderProfile(profile) {
|
|
|
188
263
|
"No active run directories for this profile.",
|
|
189
264
|
);
|
|
190
265
|
|
|
266
|
+
const recentHistoryTable = renderTable(
|
|
267
|
+
[
|
|
268
|
+
{ label: "Session", render: (row) => `<div class="mono">${row.session}</div>` },
|
|
269
|
+
{ label: "Task", render: (row) => `${row.task_kind || "n/a"} ${row.task_id || ""}`.trim() },
|
|
270
|
+
{ label: "Lifecycle", render: renderLifecycle },
|
|
271
|
+
{ label: "Worker", key: "coding_worker" },
|
|
272
|
+
{ label: "Result", render: renderResult },
|
|
273
|
+
{ label: "Updated", render: (row) => row.updated_at ? `${relativeTime(row.updated_at)}<div class="muted">${row.updated_at}</div>` : "n/a" },
|
|
274
|
+
],
|
|
275
|
+
profile.recent_history || [],
|
|
276
|
+
"No recently archived runs.",
|
|
277
|
+
);
|
|
278
|
+
|
|
191
279
|
const controllerTable = renderTable(
|
|
192
280
|
[
|
|
193
281
|
{ label: "Issue", key: "issue_id" },
|
|
@@ -214,6 +302,18 @@ function renderProfile(profile) {
|
|
|
214
302
|
"No issue retries recorded.",
|
|
215
303
|
);
|
|
216
304
|
|
|
305
|
+
const prRetryTable = renderTable(
|
|
306
|
+
[
|
|
307
|
+
{ label: "PR", key: "pr_number" },
|
|
308
|
+
{ label: "Status", render: (row) => `<span class="status-pill ${row.ready ? "" : "waiting-provider"}">${row.ready ? "ready" : "retrying"}</span>` },
|
|
309
|
+
{ label: "Reason", render: (row) => row.last_reason || "n/a" },
|
|
310
|
+
{ label: "Attempts", key: "attempts" },
|
|
311
|
+
{ label: "Next attempt", render: (row) => row.next_attempt_at ? `${relativeTime(row.next_attempt_at)}<div class="muted">${row.next_attempt_at}</div>` : "n/a" },
|
|
312
|
+
],
|
|
313
|
+
profile.pr_retries || [],
|
|
314
|
+
"No PR retries recorded.",
|
|
315
|
+
);
|
|
316
|
+
|
|
217
317
|
const workerTable = renderTable(
|
|
218
318
|
[
|
|
219
319
|
{ label: "Key", render: (row) => `<div class="mono">${row.key}</div>` },
|
|
@@ -261,6 +361,27 @@ function renderProfile(profile) {
|
|
|
261
361
|
"No pending leased issues.",
|
|
262
362
|
);
|
|
263
363
|
|
|
364
|
+
const claimsTable = renderTable(
|
|
365
|
+
[
|
|
366
|
+
{ label: "Issue", key: "issue_id" },
|
|
367
|
+
{ label: "Session", render: (row) => row.session ? `<div class="mono">${row.session}</div>` : "n/a" },
|
|
368
|
+
{ label: "Updated", render: (row) => row.updated_at ? `${relativeTime(row.updated_at)}<div class="muted">${row.updated_at}</div>` : "n/a" },
|
|
369
|
+
],
|
|
370
|
+
profile.issue_queue.claims || [],
|
|
371
|
+
"No claimed issues.",
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
const codexRotationPanel =
|
|
375
|
+
profile.coding_worker === "codex"
|
|
376
|
+
? `
|
|
377
|
+
<section class="panel">
|
|
378
|
+
<h3>Codex Rotation</h3>
|
|
379
|
+
<p class="panel-subtitle">Shows the active Codex label, candidate labels, and whether failover is ready or deferred.</p>
|
|
380
|
+
${renderCodexRotation(profile.codex_rotation)}
|
|
381
|
+
</section>
|
|
382
|
+
`
|
|
383
|
+
: "";
|
|
384
|
+
|
|
264
385
|
return `
|
|
265
386
|
<article class="profile">
|
|
266
387
|
<header class="profile-header">
|
|
@@ -285,15 +406,25 @@ function renderProfile(profile) {
|
|
|
285
406
|
<p class="panel-subtitle">Lifecycle shows technical session completion. Result shows what the run achieved: implemented, reported, or blocked.</p>
|
|
286
407
|
${runsTable}
|
|
287
408
|
</section>
|
|
409
|
+
<section class="panel">
|
|
410
|
+
<h3>Recent Completed Runs</h3>
|
|
411
|
+
<p class="panel-subtitle">Recently archived runs so they do not disappear from the dashboard immediately after completion.</p>
|
|
412
|
+
${recentHistoryTable}
|
|
413
|
+
</section>
|
|
288
414
|
<section class="panel">
|
|
289
415
|
<h3>Resident Controllers</h3>
|
|
290
416
|
<p class="panel-subtitle">Includes provider wait and failover telemetry. Stale controllers show a warning.</p>
|
|
291
417
|
${controllerTable}
|
|
292
418
|
</section>
|
|
419
|
+
${codexRotationPanel}
|
|
293
420
|
<section class="panel half">
|
|
294
421
|
<h3>Issue Retries</h3>
|
|
295
422
|
${retryTable}
|
|
296
423
|
</section>
|
|
424
|
+
<section class="panel half">
|
|
425
|
+
<h3>PR Retries</h3>
|
|
426
|
+
${prRetryTable}
|
|
427
|
+
</section>
|
|
297
428
|
<section class="panel">
|
|
298
429
|
<h3>Resident Worker Metadata</h3>
|
|
299
430
|
${workerTable}
|
|
@@ -310,6 +441,10 @@ function renderProfile(profile) {
|
|
|
310
441
|
<h3>Pending Issue Queue</h3>
|
|
311
442
|
${queueTable}
|
|
312
443
|
</section>
|
|
444
|
+
<section class="panel half">
|
|
445
|
+
<h3>Claimed Issues</h3>
|
|
446
|
+
${claimsTable}
|
|
447
|
+
</section>
|
|
313
448
|
</section>
|
|
314
449
|
</article>
|
|
315
450
|
`;
|
|
@@ -368,6 +503,7 @@ refreshButton.addEventListener("click", () => {
|
|
|
368
503
|
void loadSnapshot();
|
|
369
504
|
});
|
|
370
505
|
|
|
506
|
+
initializeTheme();
|
|
371
507
|
void loadSnapshot();
|
|
372
508
|
window.setInterval(() => {
|
|
373
509
|
void loadSnapshot();
|