diff --git a/my-tools/show_blender_web_scheduler.py b/my-tools/show_blender_web_scheduler.py index 49f523c13..2959ab059 100755 --- a/my-tools/show_blender_web_scheduler.py +++ b/my-tools/show_blender_web_scheduler.py @@ -188,6 +188,7 @@ def fast_path_status(status: dict) -> dict: latest_batch = scheduler.get("latestBatch") if isinstance(scheduler.get("latestBatch"), dict) else {} cache = _read_json(CONTROL_ROOT / "context-cache.json") ledger = _read_json(CONTROL_ROOT / "failure-ledger.json") + planner = _read_json(CONTROL_ROOT / "auto-planner.json") return { "batch": { "batchId": latest_batch.get("batchId"), @@ -204,6 +205,12 @@ def fast_path_status(status: dict) -> dict: "entryCount": len(ledger.get("entries", [])) if isinstance(ledger.get("entries"), list) else 0, "hydratedLogs": len(ledger.get("hydratedLogs", [])) if isinstance(ledger.get("hydratedLogs"), list) else 0, }, + "autoPlanner": { + "status": planner.get("status", "MISSING"), + "reason": planner.get("reason"), + "pending": planner.get("pending", {}).get("count") if isinstance(planner.get("pending"), dict) else None, + "nextAction": planner.get("nextAction"), + }, "commandBudget": {"maxCommands": 48, "maxReadCommands": 16}, "verificationPolicy": "incremental-context-only-until-final-audit", } @@ -219,6 +226,7 @@ def format_progress(progress: dict, fast_path: dict | None = None) -> list[str]: batch = fast_path.get("batch", {}) context_cache = fast_path.get("contextCache", {}) ledger = fast_path.get("failureLedger", {}) + planner = fast_path.get("autoPlanner", {}) return [ "", "Project migration progress", @@ -233,6 +241,7 @@ def format_progress(progress: dict, fast_path: dict | None = None) -> list[str]: f" fast path: batch={batch.get('batchId') or 'none'} selected={batch.get('selectedRecordCount', 0)} completed={batch.get('completed', False)}", f" context cache: {context_cache.get('status', 'MISSING')} task={context_cache.get('task') or 'none'} inputs={context_cache.get('inputCount', 0)}; failure ledger entries={ledger.get('entryCount', 0)} hydratedLogs={ledger.get('hydratedLogs', 0)}", f" command budget: max={fast_path.get('commandBudget', {}).get('maxCommands', 48)}, read/exploration max={fast_path.get('commandBudget', {}).get('maxReadCommands', 16)}; verification={fast_path.get('verificationPolicy', 'n/a')}", + f" auto planner: {planner.get('status', 'MISSING')} pending={planner.get('pending', 'n/a')} next={planner.get('nextAction') or 'none'}", ] diff --git a/tests/golden/WBV2-P1162-001/manifest.json b/tests/golden/WBV2-P1162-001/manifest.json index 5dfccf707..d89700399 100644 --- a/tests/golden/WBV2-P1162-001/manifest.json +++ b/tests/golden/WBV2-P1162-001/manifest.json @@ -60,7 +60,7 @@ }, "taskContext": { "path": "tests/golden/WBV2-P1162-001/task-context.json", - "sha256": "376f89e31e0d34a413c9aff68780cf005f26d6cf2a5ebe51e38673cc8b9a5af5" + "sha256": "296e19f4147f8d9fa324f118c02ebb08af98c787e52fdc0670c76e475f93820f" } }, "assertions": { diff --git a/tools/web/print-wbv2-live-status.mjs b/tools/web/print-wbv2-live-status.mjs index 9aa833248..9ca74aec7 100644 --- a/tools/web/print-wbv2-live-status.mjs +++ b/tools/web/print-wbv2-live-status.mjs @@ -90,7 +90,17 @@ const collect = () => { const supervisorLock = lockInfo(supervisorLockPath); const handoff = latestHandoff(); const active = schedulerLock.alive || (guardianLock.alive && supervisorLock.alive); - const blocked = scheduler?.state === "blocked" || guardian?.state === "blocked" || supervisor?.state === "blocked"; + let terminalQueue = false; + try { + const manifest = queue.task ? readJson(`tests/golden/${queue.task}/manifest.json`) : null; + const index = readJson("tests/golden/WBV2/task-index.json"); + terminalQueue = !active && index?.activeTask === null && manifest?.status === "done" && manifest?.nextTask === null; + } catch { /* A malformed pointer remains visible as a normal checkpoint failure. */ } + const checkpointBlocked = scheduler?.state === "blocked" || guardian?.state === "blocked" || supervisor?.state === "blocked"; + // A terminal task can leave a historical blocked checkpoint behind (for + // example, after a push retry). The watchdog owns the next planner attempt; + // do not report that stale checkpoint as a live execution block. + const blocked = checkpointBlocked && !terminalQueue; const state = blocked ? "BLOCKED" : active ? "RUNNING" : "IDLE"; const phase = blocked ? "blocked" : scheduler?.state === "running" ? "scheduler-task" : supervisor?.state === "running" ? "task-or-gate" : scheduler?.state ?? supervisor?.state ?? "unknown"; const observed = new Date().toISOString();