From 24b606abcf4a79d2b2b07faf65f2117cb59a1ce8 Mon Sep 17 00:00:00 2001 From: FBukovina Date: Wed, 20 May 2026 11:49:37 +0200 Subject: [PATCH] Handle burn backend failures in the TUI --- bin/burn | 45 ++++++++++++++++++++++++++++++++---- bin/tui.js | 68 +++++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 89 insertions(+), 24 deletions(-) diff --git a/bin/burn b/bin/burn index 0b58026..c9d6b42 100755 --- a/bin/burn +++ b/bin/burn @@ -345,6 +345,7 @@ total_cost=0.000000 BAR_WIDTH=30 cli_pid="" last_output="" +last_error="" # Build CLI command for the chosen backend. case "$backend" in @@ -464,10 +465,11 @@ trap on_interrupt INT # Run one CLI call in the background while animating flames. On success # leaves the parsed stdout in $last_output. Returns the CLI exit status. run_cli() { - local output_file + local output_file error_file output_file=$(mktemp) + error_file=$(mktemp) # "$output_file" 2>/dev/null "$output_file" 2>"$error_file" /dev/null; then last_output=$(<"$output_file") - rm -f "$output_file" + last_error="" + rm -f "$output_file" "$error_file" cli_pid="" return 0 else - rm -f "$output_file" + last_error=$(<"$error_file") + rm -f "$output_file" "$error_file" cli_pid="" return 1 fi } +last_error_summary() { + awk ' + NF { + line = $0 + } + END { + gsub(/\t/, " ", line) + gsub(/\r/, "", line) + print line + } + ' <<<"$last_error" +} + +report_backend_failure() { + local err message + err=$(last_error_summary) + message="two consecutive ${backend} calls failed; aborting." + if [[ -n "$err" ]]; then + message="${message} Last error: ${err}" + fi + + if ((TUI_CHILD)); then + printf 'BURN_ERROR\t%s\n' "$message" + fi + + printf "\nerror: two consecutive %s calls failed; aborting.\n" "$backend" >&2 + if [[ -n "$err" ]]; then + printf "last %s error: %s\n" "$backend" "$err" >&2 + fi +} + # Parse one CLI call's output into " " (tab-separated). parse_usage() { case "$backend" in @@ -540,7 +575,7 @@ while ((burned < tokens)); do if ! run_cli; then draw_bar " (retrying)" if ! run_cli; then - printf "\nerror: two consecutive %s calls failed; aborting.\n" "$backend" >&2 + report_backend_failure exit 1 fi fi diff --git a/bin/tui.js b/bin/tui.js index ec8abd4..cb00514 100755 --- a/bin/tui.js +++ b/bin/tui.js @@ -84,6 +84,8 @@ let iterationCalls = 0; let burnSpeed = 0; let telemetryLogs = []; let burnCompletedSuccessfully = true; +let burnFailureSticky = false; +let burnFailureMessage = ''; // Timing metrics for speed calculations let lastTime = 0; @@ -382,6 +384,15 @@ function parseProgressLine(rawLine) { const cleanLine = stripAnsi(rawLine).trim(); if (!cleanLine) return false; + if (cleanLine.startsWith('BURN_ERROR\t')) { + const message = cleanLine.split('\t').slice(1).join('\t').trim(); + burnCompletedSuccessfully = false; + burnFailureSticky = true; + burnFailureMessage = message || 'Burn process failed.'; + appendTelemetry(`❌ ${burnFailureMessage}`); + return true; + } + if (cleanLine.startsWith('BURN_PROGRESS\t')) { const parts = cleanLine.split('\t'); const parsed = updateProgress( @@ -830,6 +841,14 @@ function handleConfigKey(str, key) { function handleBurnKey(str, key) { const isSpace = (key && key.name === 'space') || str === ' '; const isEscape = (key && key.name === 'escape') || str === '\u001b'; + + if (burnFailureSticky) { + burnFailureSticky = false; + setScreen('config'); + focusedItem = 'backend'; + isWarningNihilist = false; + return; + } if (isSpace || isEscape) { appendTelemetry(`${RED}⚠️ Interrupted by user. Extinguishing core...${RESET}`); @@ -896,6 +915,8 @@ function startCombustion() { setScreen('burn'); burnWasAborted = false; + burnFailureSticky = false; + burnFailureMessage = ''; targetTokens = selectedPresetIdx === 4 ? customTokens : PRESETS[selectedPresetIdx]; burnedTokens = 0; burnPercentage = 0; @@ -913,11 +934,10 @@ function startCombustion() { const scriptPath = path.join(__dirname, 'burn'); if (!fs.existsSync(scriptPath)) { - appendTelemetry(`❌ [system] Burn script not found: ${scriptPath}`); + burnFailureMessage = `Burn script not found: ${scriptPath}`; + appendTelemetry(`❌ [system] ${burnFailureMessage}`); burnCompletedSuccessfully = false; - returnToConfigTimer = setTimeout(() => { - if (activeScreen === 'burn') setScreen('config'); - }, 2500); + burnFailureSticky = true; return; } @@ -963,10 +983,9 @@ function startCombustion() { activeChild = null; stopAudioFeed(); burnCompletedSuccessfully = false; - appendTelemetry(`❌ [system] Could not start burn process: ${error.message}`); - returnToConfigTimer = setTimeout(() => { - if (activeScreen === 'burn') setScreen('config'); - }, 3000); + burnFailureSticky = true; + burnFailureMessage = `Could not start burn process: ${error.message}`; + appendTelemetry(`❌ [system] ${burnFailureMessage}`); }); activeChild.on('close', (code) => { @@ -978,18 +997,22 @@ function startCombustion() { } if (code === 0 && !burnWasAborted) { burnCompletedSuccessfully = true; + burnFailureSticky = false; setScreen('victory'); - } else { + } else if (burnWasAborted) { burnCompletedSuccessfully = false; - appendTelemetry( - burnWasAborted - ? '🛑 [system] Combustion aborted.' - : `❌ [system] Process terminated with exit code: ${code}` - ); + appendTelemetry('🛑 [system] Combustion aborted.'); returnToConfigTimer = setTimeout(() => { returnToConfigTimer = null; if (activeScreen === 'burn') setScreen('config'); - }, burnWasAborted ? 900 : 3000); + }, 900); + } else { + burnCompletedSuccessfully = false; + burnFailureSticky = true; + if (!burnFailureMessage) { + burnFailureMessage = `Process terminated with exit code: ${code}`; + } + appendTelemetry(`❌ [system] ${burnFailureMessage}`); } }); } @@ -1166,16 +1189,18 @@ function compileConfigScreen() { // 2. Compile Screen: Active Burn function compileBurnScreen() { let s = ''; + const failed = burnFailureSticky && !activeChild; - s += `${RED}🔥 ${BOLD}${YELLOW}ACTIVE COMBUSTION ARENA${RESET} ${RED}•${RESET} ${CYAN}${backend.toUpperCase()} (${BACKEND_MODELS[backend][activeModelIdx].id})${RESET}\n`; + const title = failed ? `${BOLD}${RED}COMBUSTION FAILED${RESET}` : `${BOLD}${YELLOW}ACTIVE COMBUSTION ARENA${RESET}`; + s += `${RED}🔥 ${title} ${RED}•${RESET} ${CYAN}${backend.toUpperCase()} (${BACKEND_MODELS[backend][activeModelIdx].id})${RESET}\n`; // Two Column Layout - const dancerLines = DANCER_POSES[dancerFrame]; + const dancerLines = failed ? DANCER_POSES[0] : DANCER_POSES[dancerFrame]; // Left Column (Disco Dancer + Fire) const leftCol = []; dancerLines.forEach(l => leftCol.push(l)); - leftCol.push(getFlickeringFlames(17, pulseTimerCount)); // ground fire + leftCol.push(failed ? `${DIM}░░░░░░░░░░░░░░░░░${RESET}` : getFlickeringFlames(17, pulseTimerCount)); // ground fire // Right Column (Metrics & Odometer) const pct = Math.min(100, burnPercentage); @@ -1201,7 +1226,12 @@ function compileBurnScreen() { rightCol.push(`${BOLD}${YELLOW}OKRs:${RESET} Ladder: ${okrDraw(pct, 1.4)}`); rightCol.push(` Manager: ${okrDraw(pct, 2.5)}`); rightCol.push(` VC Depth:${okrDraw(pct, 4.0)}`); - rightCol.push(`${DIM}Press [Space] to Extinguish Flames (Abort)${RESET}`); + if (failed) { + rightCol.push(`${BOLD}${RED}Reason:${RESET} ${truncateVisible(burnFailureMessage || 'Burn process failed.', 48)}`); + rightCol.push(`${RED}${BOLD}Burn failed.${RESET} ${DIM}Press any key to return to Control Deck${RESET}`); + } else { + rightCol.push(`${DIM}Press [Space] to Extinguish Flames (Abort)${RESET}`); + } // Merge columns const maxLines = Math.max(leftCol.length, rightCol.length);