agent-control-plane 0.1.14 → 0.2.0
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 +323 -349
- package/bin/pr-risk.sh +28 -6
- package/hooks/heartbeat-hooks.sh +62 -22
- package/npm/bin/agent-control-plane.js +434 -12
- package/package.json +1 -1
- package/references/architecture.md +8 -0
- package/references/control-plane-map.md +6 -2
- package/references/release-checklist.md +0 -2
- package/tools/bin/agent-github-update-labels +6 -1
- package/tools/bin/agent-project-catch-up-issue-pr-links +118 -0
- package/tools/bin/agent-project-catch-up-merged-prs +77 -21
- package/tools/bin/agent-project-catch-up-scheduled-issue-retries +123 -0
- package/tools/bin/agent-project-cleanup-session +84 -0
- package/tools/bin/agent-project-heartbeat-loop +10 -3
- package/tools/bin/agent-project-reconcile-issue-session +45 -12
- package/tools/bin/agent-project-reconcile-pr-session +25 -0
- package/tools/bin/agent-project-run-claude-session +2 -2
- package/tools/bin/agent-project-run-codex-resilient +57 -2
- package/tools/bin/agent-project-run-kilo-session +346 -14
- package/tools/bin/agent-project-run-ollama-session +658 -0
- package/tools/bin/agent-project-run-openclaw-session +73 -25
- package/tools/bin/agent-project-run-opencode-session +354 -14
- package/tools/bin/agent-project-run-pi-session +479 -0
- package/tools/bin/agent-project-worker-status +38 -1
- package/tools/bin/flow-config-lib.sh +123 -3
- package/tools/bin/flow-resident-worker-lib.sh +1 -1
- package/tools/bin/flow-shell-lib.sh +7 -2
- package/tools/bin/heartbeat-recovery-preflight.sh +1 -0
- package/tools/bin/heartbeat-safe-auto.sh +105 -17
- package/tools/bin/install-project-launchd.sh +19 -2
- package/tools/bin/prepare-worktree.sh +4 -4
- package/tools/bin/profile-activate.sh +2 -2
- package/tools/bin/profile-adopt.sh +2 -2
- package/tools/bin/project-init.sh +1 -1
- package/tools/bin/project-runtimectl.sh +90 -7
- package/tools/bin/provider-cooldown-state.sh +14 -14
- package/tools/bin/render-flow-config.sh +30 -33
- package/tools/bin/run-codex-task.sh +53 -4
- package/tools/bin/scaffold-profile.sh +18 -3
- package/tools/bin/start-issue-worker.sh +4 -1
- package/tools/bin/start-pr-fix-worker.sh +33 -0
- package/tools/bin/start-pr-review-worker.sh +34 -0
- package/tools/bin/start-resident-issue-loop.sh +5 -4
- package/tools/bin/sync-agent-repo.sh +2 -2
- package/tools/bin/sync-dependency-baseline.sh +3 -3
- package/tools/bin/sync-shared-agent-home.sh +4 -1
- package/tools/dashboard/app.js +62 -0
- package/tools/dashboard/dashboard_snapshot.py +53 -4
- package/tools/dashboard/index.html +5 -1
- package/tools/dashboard/styles.css +97 -20
- package/tools/templates/pr-fix-template.md +4 -8
- package/tools/templates/pr-merge-repair-template.md +4 -8
- package/tools/templates/pr-review-template.md +2 -1
|
@@ -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
|
|
@@ -120,6 +123,35 @@ PR_FILES_TEXT="$(jq -r '.files[] | "- " + .' <<<"$RISK_JSON")"
|
|
|
120
123
|
PR_REPO_ROOT="$(flow_resolve_repo_root "${CONFIG_YAML}")"
|
|
121
124
|
PR_DEPENDENCY_SOURCE_ROOT="${ACP_DEPENDENCY_SOURCE_ROOT:-${F_LOSNING_DEPENDENCY_SOURCE_ROOT:-$PR_REPO_ROOT}}"
|
|
122
125
|
|
|
126
|
+
render_pr_context_reads_text() {
|
|
127
|
+
local repo_root="${1:?repo root required}"
|
|
128
|
+
local -a candidate_paths=(
|
|
129
|
+
"${repo_root}/AGENTS.md"
|
|
130
|
+
"${repo_root}/openspec/AGENT_RULES.md"
|
|
131
|
+
"${repo_root}/openspec/AGENTS.md"
|
|
132
|
+
"${repo_root}/openspec/project.md"
|
|
133
|
+
"${repo_root}/openspec/CONVENTIONS.md"
|
|
134
|
+
"${repo_root}/docs/TESTING_AND_SEED_POLICY.md"
|
|
135
|
+
)
|
|
136
|
+
local -a existing_paths=()
|
|
137
|
+
local candidate_path=""
|
|
138
|
+
|
|
139
|
+
for candidate_path in "${candidate_paths[@]}"; do
|
|
140
|
+
if [[ -f "${candidate_path}" ]]; then
|
|
141
|
+
existing_paths+=("${candidate_path}")
|
|
142
|
+
fi
|
|
143
|
+
done
|
|
144
|
+
|
|
145
|
+
if [[ "${#existing_paths[@]}" -eq 0 ]]; then
|
|
146
|
+
printf '%s\n' '- No repo-specific context files were found under the expected AGENTS/OpenSpec/testing-doc locations; rely on the current diff and nearby source.'
|
|
147
|
+
return 0
|
|
148
|
+
fi
|
|
149
|
+
|
|
150
|
+
printf '%s\n' "${existing_paths[@]}" | sed 's/^/- `/' | sed 's/$/`/'
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
PR_CONTEXT_READS_TEXT="$(render_pr_context_reads_text "${PR_REPO_ROOT}")"
|
|
154
|
+
|
|
123
155
|
case "$PR_AGENT_LANE" in
|
|
124
156
|
double-check-1)
|
|
125
157
|
PR_REVIEW_STAGE_TEXT="Independent agent double-check 1 of 2. A clean pass should advance this PR to the second review pass, not merge it yet."
|
|
@@ -154,6 +186,7 @@ PR_CHECKS_BYPASSED="$PR_CHECKS_BYPASSED" \
|
|
|
154
186
|
PR_MERGE_STATE_STATUS="$PR_MERGE_STATE_STATUS" \
|
|
155
187
|
PR_CHECKS_TEXT="$PR_CHECKS_TEXT" \
|
|
156
188
|
PR_FILES_TEXT="$PR_FILES_TEXT" \
|
|
189
|
+
PR_CONTEXT_READS_TEXT="$PR_CONTEXT_READS_TEXT" \
|
|
157
190
|
PR_REPO_ROOT="$PR_REPO_ROOT" \
|
|
158
191
|
PR_DEPENDENCY_SOURCE_ROOT="$PR_DEPENDENCY_SOURCE_ROOT" \
|
|
159
192
|
REPO_SLUG="$REPO_SLUG" \
|
|
@@ -180,6 +213,7 @@ const replacements = {
|
|
|
180
213
|
'{PR_MERGE_STATE_STATUS}': process.env.PR_MERGE_STATE_STATUS || '',
|
|
181
214
|
'{PR_CHECKS_TEXT}': process.env.PR_CHECKS_TEXT || '',
|
|
182
215
|
'{PR_FILES_TEXT}': process.env.PR_FILES_TEXT || '',
|
|
216
|
+
'{PR_CONTEXT_READS_TEXT}': process.env.PR_CONTEXT_READS_TEXT || '',
|
|
183
217
|
'{REPO_ROOT}': process.env.PR_REPO_ROOT || '',
|
|
184
218
|
'{DEPENDENCY_SOURCE_ROOT}': process.env.PR_DEPENDENCY_SOURCE_ROOT || '',
|
|
185
219
|
};
|
|
@@ -29,7 +29,7 @@ PENDING_LAUNCH_DIR="${ACP_PENDING_LAUNCH_DIR:-${F_LOSNING_PENDING_LAUNCH_DIR:-${
|
|
|
29
29
|
SCHEDULED_STATE_DIR="${STATE_ROOT}/scheduled-issues"
|
|
30
30
|
CONTROLLER_FILE="$(flow_resident_issue_controller_file "${CONFIG_YAML}" "${ISSUE_ID}")"
|
|
31
31
|
RESIDENT_META_FILE="$(flow_resident_issue_meta_file "${CONFIG_YAML}" "${ISSUE_ID}")"
|
|
32
|
-
CODING_WORKER="${ACP_CODING_WORKER
|
|
32
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
33
33
|
MAX_IMMEDIATE_CYCLES="$(flow_resident_issue_controller_max_immediate_cycles "${CONFIG_YAML}")"
|
|
34
34
|
POLL_SECONDS="$(flow_resident_issue_controller_poll_seconds "${CONFIG_YAML}")"
|
|
35
35
|
IDLE_TIMEOUT_SECONDS="$(flow_resident_issue_controller_idle_timeout_seconds "${CONFIG_YAML}")"
|
|
@@ -192,7 +192,7 @@ issue_id_is_scheduled() {
|
|
|
192
192
|
|
|
193
193
|
controller_refresh_execution_context() {
|
|
194
194
|
unset \
|
|
195
|
-
ACP_CODING_WORKER
|
|
195
|
+
ACP_CODING_WORKER \
|
|
196
196
|
ACP_CODEX_PROFILE_SAFE F_LOSNING_CODEX_PROFILE_SAFE \
|
|
197
197
|
ACP_CODEX_PROFILE_BYPASS F_LOSNING_CODEX_PROFILE_BYPASS \
|
|
198
198
|
ACP_CLAUDE_MODEL F_LOSNING_CLAUDE_MODEL \
|
|
@@ -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 \
|
|
@@ -215,7 +216,7 @@ controller_refresh_execution_context() {
|
|
|
215
216
|
ACP_PROVIDER_POOL_LAST_REASON F_LOSNING_PROVIDER_POOL_LAST_REASON
|
|
216
217
|
flow_export_execution_env "${CONFIG_YAML}"
|
|
217
218
|
flow_export_project_env_aliases
|
|
218
|
-
CODING_WORKER="${ACP_CODING_WORKER
|
|
219
|
+
CODING_WORKER="${ACP_CODING_WORKER:-codex}"
|
|
219
220
|
controller_capture_active_provider_context
|
|
220
221
|
}
|
|
221
222
|
|
|
@@ -543,7 +544,7 @@ controller_provider_state() {
|
|
|
543
544
|
|
|
544
545
|
provider_state="$(
|
|
545
546
|
env \
|
|
546
|
-
-u ACP_CODING_WORKER
|
|
547
|
+
-u ACP_CODING_WORKER \
|
|
547
548
|
-u ACP_CODEX_PROFILE_SAFE -u F_LOSNING_CODEX_PROFILE_SAFE \
|
|
548
549
|
-u ACP_CODEX_PROFILE_BYPASS -u F_LOSNING_CODEX_PROFILE_BYPASS \
|
|
549
550
|
-u ACP_CLAUDE_MODEL -u F_LOSNING_CLAUDE_MODEL \
|
|
@@ -6,11 +6,11 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
6
6
|
source "${SCRIPT_DIR}/flow-config-lib.sh"
|
|
7
7
|
|
|
8
8
|
CONFIG_YAML="$(resolve_flow_config_yaml "${BASH_SOURCE[0]}")"
|
|
9
|
-
SOURCE_REPO_ROOT="${ACP_SOURCE_REPO_ROOT:-$
|
|
9
|
+
SOURCE_REPO_ROOT="${ACP_SOURCE_REPO_ROOT:-$(flow_resolve_retained_repo_root "${CONFIG_YAML}")}"
|
|
10
10
|
CANONICAL_REPO_ROOT="$(flow_resolve_repo_root "${CONFIG_YAML}")"
|
|
11
11
|
AGENT_REPO_ROOT="$(flow_resolve_agent_repo_root "${CONFIG_YAML}")"
|
|
12
12
|
DEFAULT_BRANCH="$(flow_resolve_default_branch "${CONFIG_YAML}")"
|
|
13
|
-
REMOTE_NAME="${ACP_REMOTE_NAME
|
|
13
|
+
REMOTE_NAME="${ACP_REMOTE_NAME:-origin}"
|
|
14
14
|
FLOW_SKILL_DIR="$(resolve_flow_skill_dir "${BASH_SOURCE[0]}")"
|
|
15
15
|
FLOW_TOOLS_DIR="${FLOW_SKILL_DIR}/tools/bin"
|
|
16
16
|
|
|
@@ -13,9 +13,9 @@ STATE_ROOT="$(flow_resolve_state_root "${CONFIG_YAML}")"
|
|
|
13
13
|
LOCK_DIR="${STATE_ROOT}/dependency-baseline.lock"
|
|
14
14
|
PID_FILE="${LOCK_DIR}/pid"
|
|
15
15
|
HASH_FILE="${STATE_ROOT}/dependency-baseline.sha256"
|
|
16
|
-
PACKAGE_MANAGER_BIN="${ACP_PACKAGE_MANAGER_BIN
|
|
17
|
-
WORKSPACE_BUILD_PACKAGES_RAW="${ACP_WORKSPACE_BUILD_PACKAGES
|
|
18
|
-
WORKSPACE_BUILD_ARTIFACTS_RAW="${ACP_WORKSPACE_BUILD_ARTIFACTS
|
|
16
|
+
PACKAGE_MANAGER_BIN="${ACP_PACKAGE_MANAGER_BIN:-pnpm}"
|
|
17
|
+
WORKSPACE_BUILD_PACKAGES_RAW="${ACP_WORKSPACE_BUILD_PACKAGES:-}"
|
|
18
|
+
WORKSPACE_BUILD_ARTIFACTS_RAW="${ACP_WORKSPACE_BUILD_ARTIFACTS:-}"
|
|
19
19
|
declare -a WORKSPACE_BUILD_PACKAGES=()
|
|
20
20
|
declare -a WORKSPACE_BUILD_ARTIFACTS=()
|
|
21
21
|
|
|
@@ -207,7 +207,10 @@ sync_tree_rsync() {
|
|
|
207
207
|
local target_dir="${2:?target dir required}"
|
|
208
208
|
[[ -d "${source_dir}" ]] || return 0
|
|
209
209
|
mkdir -p "${target_dir}"
|
|
210
|
-
rsync -a --delete --exclude='.git/' "${source_dir}/" "${target_dir}/"
|
|
210
|
+
if rsync -a --delete --exclude='.git/' "${source_dir}/" "${target_dir}/"; then
|
|
211
|
+
return 0
|
|
212
|
+
fi
|
|
213
|
+
sync_tree_copy_mode "${source_dir}" "${target_dir}"
|
|
211
214
|
}
|
|
212
215
|
|
|
213
216
|
reset_runtime_skill_targets() {
|
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";
|
|
@@ -314,6 +361,16 @@ function renderProfile(profile) {
|
|
|
314
361
|
"No pending leased issues.",
|
|
315
362
|
);
|
|
316
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
|
+
|
|
317
374
|
const codexRotationPanel =
|
|
318
375
|
profile.coding_worker === "codex"
|
|
319
376
|
? `
|
|
@@ -384,6 +441,10 @@ function renderProfile(profile) {
|
|
|
384
441
|
<h3>Pending Issue Queue</h3>
|
|
385
442
|
${queueTable}
|
|
386
443
|
</section>
|
|
444
|
+
<section class="panel half">
|
|
445
|
+
<h3>Claimed Issues</h3>
|
|
446
|
+
${claimsTable}
|
|
447
|
+
</section>
|
|
387
448
|
</section>
|
|
388
449
|
</article>
|
|
389
450
|
`;
|
|
@@ -442,6 +503,7 @@ refreshButton.addEventListener("click", () => {
|
|
|
442
503
|
void loadSnapshot();
|
|
443
504
|
});
|
|
444
505
|
|
|
506
|
+
initializeTheme();
|
|
445
507
|
void loadSnapshot();
|
|
446
508
|
window.setInterval(() => {
|
|
447
509
|
void loadSnapshot();
|
|
@@ -200,6 +200,11 @@ GITHUB_RATE_LIMIT_PATTERNS = [
|
|
|
200
200
|
),
|
|
201
201
|
]
|
|
202
202
|
|
|
203
|
+
WORKER_PREFLIGHT_NETWORK_BLOCKED_PATTERN = re.compile(
|
|
204
|
+
r"Blocked on external network access.*?What I ran:\s*-\s*`(?P<command>[^`]+)`.*?Exact failure:\s*`(?P<failure>[^`]+)`",
|
|
205
|
+
re.IGNORECASE | re.DOTALL,
|
|
206
|
+
)
|
|
207
|
+
|
|
203
208
|
|
|
204
209
|
def summarize_whitespace(text: str) -> str:
|
|
205
210
|
return re.sub(r"\s+", " ", text).strip()
|
|
@@ -242,6 +247,52 @@ def extract_github_rate_limit_alert(run_dir: Path, run: dict[str, Any]) -> dict[
|
|
|
242
247
|
return None
|
|
243
248
|
|
|
244
249
|
|
|
250
|
+
def extract_worker_preflight_network_blocked_alert(run_dir: Path, run: dict[str, Any]) -> dict[str, Any] | None:
|
|
251
|
+
candidate_files = [
|
|
252
|
+
run_dir / "issue-comment.md",
|
|
253
|
+
run_dir / "pr-comment.md",
|
|
254
|
+
]
|
|
255
|
+
for path in candidate_files:
|
|
256
|
+
text = read_tail_text(path)
|
|
257
|
+
if not text:
|
|
258
|
+
continue
|
|
259
|
+
match = WORKER_PREFLIGHT_NETWORK_BLOCKED_PATTERN.search(text)
|
|
260
|
+
if not match:
|
|
261
|
+
continue
|
|
262
|
+
command = summarize_whitespace(match.group("command"))
|
|
263
|
+
failure = summarize_whitespace(match.group("failure"))
|
|
264
|
+
message = f"Worker preflight `{command or 'unknown command'}` failed before implementation."
|
|
265
|
+
if failure:
|
|
266
|
+
message = f"{message} {failure}"
|
|
267
|
+
message = f"{message} Verify from the host if the same command succeeds; worker and host environment can diverge."
|
|
268
|
+
return {
|
|
269
|
+
"id": f"worker-preflight-network-blocked:{run.get('session', '')}:{command}:{failure}",
|
|
270
|
+
"kind": "worker-preflight-network-blocked",
|
|
271
|
+
"severity": "warn",
|
|
272
|
+
"title": "Worker preflight blocked by network",
|
|
273
|
+
"message": message,
|
|
274
|
+
"session": run.get("session", ""),
|
|
275
|
+
"task_kind": run.get("task_kind", ""),
|
|
276
|
+
"task_id": run.get("task_id", ""),
|
|
277
|
+
"reset_at": "",
|
|
278
|
+
"updated_at": run.get("updated_at", "") or file_mtime_iso(path),
|
|
279
|
+
"source_file": str(path),
|
|
280
|
+
}
|
|
281
|
+
return None
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def extract_run_alerts(run_dir: Path, run: dict[str, Any]) -> list[dict[str, Any]]:
|
|
285
|
+
alerts: list[dict[str, Any]] = []
|
|
286
|
+
for extractor in (
|
|
287
|
+
extract_github_rate_limit_alert,
|
|
288
|
+
extract_worker_preflight_network_blocked_alert,
|
|
289
|
+
):
|
|
290
|
+
alert = extractor(run_dir, run)
|
|
291
|
+
if alert:
|
|
292
|
+
alerts.append(alert)
|
|
293
|
+
return alerts
|
|
294
|
+
|
|
295
|
+
|
|
245
296
|
def collect_runs(runs_root: Path) -> list[dict[str, Any]]:
|
|
246
297
|
if not runs_root.is_dir():
|
|
247
298
|
return []
|
|
@@ -296,8 +347,7 @@ def collect_runs(runs_root: Path) -> list[dict[str, Any]]:
|
|
|
296
347
|
"provider_pool_name": run_env.get("ACTIVE_PROVIDER_POOL_NAME", ""),
|
|
297
348
|
"run_dir": str(run_dir),
|
|
298
349
|
}
|
|
299
|
-
|
|
300
|
-
item["alerts"] = [alert] if alert else []
|
|
350
|
+
item["alerts"] = extract_run_alerts(run_dir, item)
|
|
301
351
|
runs.append(item)
|
|
302
352
|
return runs
|
|
303
353
|
|
|
@@ -351,8 +401,7 @@ def collect_recent_history(history_root: Path, limit: int = 8) -> list[dict[str,
|
|
|
351
401
|
"run_dir": str(run_dir),
|
|
352
402
|
"archived": True,
|
|
353
403
|
}
|
|
354
|
-
|
|
355
|
-
item["alerts"] = [alert] if alert else []
|
|
404
|
+
item["alerts"] = extract_run_alerts(run_dir, item)
|
|
356
405
|
items.append(item)
|
|
357
406
|
seen_sessions.add(session)
|
|
358
407
|
if len(items) >= limit:
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="light dark" />
|
|
6
7
|
<title>ACP Worker Dashboard</title>
|
|
7
8
|
<link rel="stylesheet" href="./styles.css" />
|
|
8
9
|
</head>
|
|
@@ -20,7 +21,10 @@
|
|
|
20
21
|
</p>
|
|
21
22
|
</div>
|
|
22
23
|
<div class="hero-actions">
|
|
23
|
-
<
|
|
24
|
+
<div class="hero-controls">
|
|
25
|
+
<button id="theme-toggle" type="button" aria-label="Toggle dark mode">Dark mode</button>
|
|
26
|
+
<button id="refresh-button" type="button">Refresh now</button>
|
|
27
|
+
</div>
|
|
24
28
|
<div class="meta">
|
|
25
29
|
<div>Auto refresh: <strong>5s</strong></div>
|
|
26
30
|
<div id="generated-at">Loading snapshot...</div>
|
|
@@ -12,6 +12,56 @@
|
|
|
12
12
|
--danger: #b42318;
|
|
13
13
|
--danger-soft: #fdd8d2;
|
|
14
14
|
--shadow: 0 18px 50px rgba(25, 33, 38, 0.08);
|
|
15
|
+
--button-bg: var(--ink);
|
|
16
|
+
--button-ink: #ffffff;
|
|
17
|
+
--button-hover: #0f1720;
|
|
18
|
+
--hero-bg: rgba(255, 253, 247, 0.92);
|
|
19
|
+
--profile-bg: rgba(255, 253, 247, 0.94);
|
|
20
|
+
--body-gradient-top: rgba(15, 118, 110, 0.08);
|
|
21
|
+
--body-gradient-bottom: #faf7ef;
|
|
22
|
+
--theme-toggle-bg: var(--panel);
|
|
23
|
+
--theme-toggle-ink: var(--ink);
|
|
24
|
+
--theme-toggle-line: var(--line);
|
|
25
|
+
--theme-toggle-hover: var(--panel-strong);
|
|
26
|
+
--reported-soft: #dbeafe;
|
|
27
|
+
--reported-ink: #1d4ed8;
|
|
28
|
+
--implemented-soft: #dcfce7;
|
|
29
|
+
--implemented-ink: #166534;
|
|
30
|
+
--blocked-soft: #fef3c7;
|
|
31
|
+
--blocked-ink: #92400e;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
:root[data-theme="dark"] {
|
|
35
|
+
--bg: #0d1418;
|
|
36
|
+
--panel: #142026;
|
|
37
|
+
--panel-strong: #1a2a31;
|
|
38
|
+
--ink: #ebf1f3;
|
|
39
|
+
--muted: #9ab0bb;
|
|
40
|
+
--line: #2a3c44;
|
|
41
|
+
--accent: #5ad4c7;
|
|
42
|
+
--accent-soft: #183d3a;
|
|
43
|
+
--warn: #f4c35f;
|
|
44
|
+
--warn-soft: #4d3a12;
|
|
45
|
+
--danger: #ff8a80;
|
|
46
|
+
--danger-soft: #4a2220;
|
|
47
|
+
--shadow: 0 24px 60px rgba(0, 0, 0, 0.32);
|
|
48
|
+
--button-bg: #ebf1f3;
|
|
49
|
+
--button-ink: #0d1418;
|
|
50
|
+
--button-hover: #d7e2e6;
|
|
51
|
+
--hero-bg: rgba(20, 32, 38, 0.92);
|
|
52
|
+
--profile-bg: rgba(20, 32, 38, 0.94);
|
|
53
|
+
--body-gradient-top: rgba(90, 212, 199, 0.12);
|
|
54
|
+
--body-gradient-bottom: #10181d;
|
|
55
|
+
--theme-toggle-bg: #1d2d35;
|
|
56
|
+
--theme-toggle-ink: #ebf1f3;
|
|
57
|
+
--theme-toggle-line: #35505b;
|
|
58
|
+
--theme-toggle-hover: #23363f;
|
|
59
|
+
--reported-soft: #1b3552;
|
|
60
|
+
--reported-ink: #9fc8ff;
|
|
61
|
+
--implemented-soft: #173a2b;
|
|
62
|
+
--implemented-ink: #85ddb1;
|
|
63
|
+
--blocked-soft: #4a3711;
|
|
64
|
+
--blocked-ink: #f4c35f;
|
|
15
65
|
}
|
|
16
66
|
|
|
17
67
|
* {
|
|
@@ -22,8 +72,8 @@ body {
|
|
|
22
72
|
margin: 0;
|
|
23
73
|
font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
|
|
24
74
|
background:
|
|
25
|
-
radial-gradient(circle at top right,
|
|
26
|
-
linear-gradient(180deg,
|
|
75
|
+
radial-gradient(circle at top right, var(--body-gradient-top), transparent 34%),
|
|
76
|
+
linear-gradient(180deg, var(--body-gradient-bottom) 0%, var(--bg) 100%);
|
|
27
77
|
color: var(--ink);
|
|
28
78
|
}
|
|
29
79
|
|
|
@@ -41,7 +91,7 @@ body {
|
|
|
41
91
|
padding: 24px;
|
|
42
92
|
border: 1px solid var(--line);
|
|
43
93
|
border-radius: 28px;
|
|
44
|
-
background:
|
|
94
|
+
background: var(--hero-bg);
|
|
45
95
|
box-shadow: var(--shadow);
|
|
46
96
|
}
|
|
47
97
|
|
|
@@ -75,19 +125,42 @@ body {
|
|
|
75
125
|
align-items: flex-end;
|
|
76
126
|
}
|
|
77
127
|
|
|
128
|
+
.hero-controls {
|
|
129
|
+
display: flex;
|
|
130
|
+
gap: 10px;
|
|
131
|
+
align-items: center;
|
|
132
|
+
flex-wrap: wrap;
|
|
133
|
+
justify-content: flex-end;
|
|
134
|
+
}
|
|
135
|
+
|
|
78
136
|
button {
|
|
79
137
|
appearance: none;
|
|
80
138
|
border: 0;
|
|
81
139
|
border-radius: 999px;
|
|
82
140
|
padding: 12px 18px;
|
|
83
141
|
font: inherit;
|
|
84
|
-
background: var(--
|
|
85
|
-
color:
|
|
142
|
+
background: var(--button-bg);
|
|
143
|
+
color: var(--button-ink);
|
|
86
144
|
cursor: pointer;
|
|
145
|
+
transition: background 140ms ease, color 140ms ease, border-color 140ms ease, transform 140ms ease;
|
|
87
146
|
}
|
|
88
147
|
|
|
89
148
|
button:hover {
|
|
90
|
-
background:
|
|
149
|
+
background: var(--button-hover);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
button:active {
|
|
153
|
+
transform: translateY(1px);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
#theme-toggle {
|
|
157
|
+
background: var(--theme-toggle-bg);
|
|
158
|
+
color: var(--theme-toggle-ink);
|
|
159
|
+
border: 1px solid var(--theme-toggle-line);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#theme-toggle:hover {
|
|
163
|
+
background: var(--theme-toggle-hover);
|
|
91
164
|
}
|
|
92
165
|
|
|
93
166
|
.meta {
|
|
@@ -135,7 +208,7 @@ button:hover {
|
|
|
135
208
|
padding: 22px;
|
|
136
209
|
border-radius: 28px;
|
|
137
210
|
border: 1px solid var(--line);
|
|
138
|
-
background:
|
|
211
|
+
background: var(--profile-bg);
|
|
139
212
|
box-shadow: var(--shadow);
|
|
140
213
|
}
|
|
141
214
|
|
|
@@ -183,12 +256,12 @@ button:hover {
|
|
|
183
256
|
|
|
184
257
|
.badge.good {
|
|
185
258
|
background: var(--accent-soft);
|
|
186
|
-
color:
|
|
259
|
+
color: var(--accent);
|
|
187
260
|
}
|
|
188
261
|
|
|
189
262
|
.badge.warn {
|
|
190
263
|
background: var(--warn-soft);
|
|
191
|
-
color:
|
|
264
|
+
color: var(--warn);
|
|
192
265
|
}
|
|
193
266
|
|
|
194
267
|
.badge.danger {
|
|
@@ -243,8 +316,8 @@ button:hover {
|
|
|
243
316
|
}
|
|
244
317
|
|
|
245
318
|
.alert-card.warn {
|
|
246
|
-
border-color:
|
|
247
|
-
background:
|
|
319
|
+
border-color: color-mix(in srgb, var(--warn) 45%, var(--line));
|
|
320
|
+
background: color-mix(in srgb, var(--warn-soft) 82%, var(--panel) 18%);
|
|
248
321
|
}
|
|
249
322
|
|
|
250
323
|
.alert-card h4 {
|
|
@@ -323,7 +396,7 @@ th {
|
|
|
323
396
|
.status-pill.launching,
|
|
324
397
|
.status-pill.reconciling {
|
|
325
398
|
background: var(--accent-soft);
|
|
326
|
-
color:
|
|
399
|
+
color: var(--accent);
|
|
327
400
|
}
|
|
328
401
|
|
|
329
402
|
.status-pill.waiting-provider,
|
|
@@ -332,7 +405,7 @@ th {
|
|
|
332
405
|
.status-pill.idle,
|
|
333
406
|
.status-pill.sleeping {
|
|
334
407
|
background: var(--warn-soft);
|
|
335
|
-
color:
|
|
408
|
+
color: var(--warn);
|
|
336
409
|
}
|
|
337
410
|
|
|
338
411
|
.status-pill.FAILED,
|
|
@@ -343,19 +416,19 @@ th {
|
|
|
343
416
|
}
|
|
344
417
|
|
|
345
418
|
.status-pill.implemented {
|
|
346
|
-
background:
|
|
347
|
-
color:
|
|
419
|
+
background: var(--implemented-soft);
|
|
420
|
+
color: var(--implemented-ink);
|
|
348
421
|
}
|
|
349
422
|
|
|
350
423
|
.status-pill.reported,
|
|
351
424
|
.status-pill.completed {
|
|
352
|
-
background:
|
|
353
|
-
color:
|
|
425
|
+
background: var(--reported-soft);
|
|
426
|
+
color: var(--reported-ink);
|
|
354
427
|
}
|
|
355
428
|
|
|
356
429
|
.status-pill.blocked {
|
|
357
|
-
background:
|
|
358
|
-
color:
|
|
430
|
+
background: var(--blocked-soft);
|
|
431
|
+
color: var(--blocked-ink);
|
|
359
432
|
}
|
|
360
433
|
|
|
361
434
|
.status-pill.failed,
|
|
@@ -366,7 +439,7 @@ th {
|
|
|
366
439
|
|
|
367
440
|
.status-pill.running {
|
|
368
441
|
background: var(--accent-soft);
|
|
369
|
-
color:
|
|
442
|
+
color: var(--accent);
|
|
370
443
|
}
|
|
371
444
|
|
|
372
445
|
.empty-state {
|
|
@@ -387,6 +460,10 @@ th {
|
|
|
387
460
|
text-align: left;
|
|
388
461
|
}
|
|
389
462
|
|
|
463
|
+
.hero-controls {
|
|
464
|
+
justify-content: flex-start;
|
|
465
|
+
}
|
|
466
|
+
|
|
390
467
|
.panel.half,
|
|
391
468
|
.panel.third {
|
|
392
469
|
grid-column: span 12;
|
|
@@ -2,13 +2,9 @@ You are the PR repair worker for `{REPO_SLUG}`.
|
|
|
2
2
|
|
|
3
3
|
Before making any change:
|
|
4
4
|
|
|
5
|
-
1. Read
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
4. Read `{REPO_ROOT}/openspec/project.md`.
|
|
9
|
-
5. Read `{REPO_ROOT}/openspec/CONVENTIONS.md`.
|
|
10
|
-
6. Read `{REPO_ROOT}/docs/TESTING_AND_SEED_POLICY.md`.
|
|
11
|
-
7. Stay on this PR branch worktree. Do not push or mutate GitHub from inside the worker.
|
|
5
|
+
1. Read the following repo context before changing code:
|
|
6
|
+
{PR_CONTEXT_READS_TEXT}
|
|
7
|
+
2. Stay on this PR branch worktree. Do not push or mutate GitHub from inside the worker.
|
|
12
8
|
|
|
13
9
|
PR metadata:
|
|
14
10
|
|
|
@@ -58,7 +54,7 @@ PR body:
|
|
|
58
54
|
Required flow:
|
|
59
55
|
|
|
60
56
|
1. Inspect the current diff and the failing/pending CI signals first:
|
|
61
|
-
- `openspec list`
|
|
57
|
+
- `openspec list` if the repo uses OpenSpec
|
|
62
58
|
- `git diff --stat origin/main...HEAD`
|
|
63
59
|
- `git status --short`
|
|
64
60
|
- if `Merge state` is not `CLEAN` or `Mergeable` is `FALSE`, treat branch drift/conflicts as the concrete blocker first
|
|
@@ -2,13 +2,9 @@ You are the PR merge-repair worker for `{REPO_SLUG}`.
|
|
|
2
2
|
|
|
3
3
|
Before making any change:
|
|
4
4
|
|
|
5
|
-
1. Read
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
4. Read `{REPO_ROOT}/openspec/project.md`.
|
|
9
|
-
5. Read `{REPO_ROOT}/openspec/CONVENTIONS.md`.
|
|
10
|
-
6. Read `{REPO_ROOT}/docs/TESTING_AND_SEED_POLICY.md`.
|
|
11
|
-
7. Stay on this PR branch worktree. Do not push or mutate GitHub from inside the worker.
|
|
5
|
+
1. Read the following repo context before changing code:
|
|
6
|
+
{PR_CONTEXT_READS_TEXT}
|
|
7
|
+
2. Stay on this PR branch worktree. Do not push or mutate GitHub from inside the worker.
|
|
12
8
|
|
|
13
9
|
PR metadata:
|
|
14
10
|
|
|
@@ -53,7 +49,7 @@ Required flow:
|
|
|
53
49
|
- do not run `git fetch`, `git pull`, `git merge`, `git rebase`, `git commit`, `git push`, or any command that writes Git metadata
|
|
54
50
|
- do not abort or restart the prepared merge state
|
|
55
51
|
3. Inspect only the concrete branch-repair state you were given:
|
|
56
|
-
- `openspec list`
|
|
52
|
+
- `openspec list` if the repo uses OpenSpec
|
|
57
53
|
- `git status --short`
|
|
58
54
|
- `git diff --check`
|
|
59
55
|
- `git diff --name-only --diff-filter=U`
|
|
@@ -2,7 +2,8 @@ You are the PR review and final-merge worker for `{REPO_SLUG}`.
|
|
|
2
2
|
|
|
3
3
|
Before making any decision:
|
|
4
4
|
|
|
5
|
-
1. Read
|
|
5
|
+
1. Read the following repo context before deciding:
|
|
6
|
+
{PR_CONTEXT_READS_TEXT}
|
|
6
7
|
2. Do not edit product code in this worktree. This is review and final-review only.
|
|
7
8
|
3. Never run dependency bootstrap or workspace-mutating commands here.
|
|
8
9
|
|