mirror of
https://github.com/FBukovina/burn-baby-burn.git
synced 2026-10-29 08:04:31 +00:00
Handle burn backend failures in the TUI
This commit is contained in:
parent
12c36ae23d
commit
24b606abcf
2 changed files with 89 additions and 24 deletions
45
bin/burn
45
bin/burn
|
|
@ -345,6 +345,7 @@ total_cost=0.000000
|
||||||
BAR_WIDTH=30
|
BAR_WIDTH=30
|
||||||
cli_pid=""
|
cli_pid=""
|
||||||
last_output=""
|
last_output=""
|
||||||
|
last_error=""
|
||||||
|
|
||||||
# Build CLI command for the chosen backend.
|
# Build CLI command for the chosen backend.
|
||||||
case "$backend" in
|
case "$backend" in
|
||||||
|
|
@ -464,10 +465,11 @@ trap on_interrupt INT
|
||||||
# Run one CLI call in the background while animating flames. On success
|
# Run one CLI call in the background while animating flames. On success
|
||||||
# leaves the parsed stdout in $last_output. Returns the CLI exit status.
|
# leaves the parsed stdout in $last_output. Returns the CLI exit status.
|
||||||
run_cli() {
|
run_cli() {
|
||||||
local output_file
|
local output_file error_file
|
||||||
output_file=$(mktemp)
|
output_file=$(mktemp)
|
||||||
|
error_file=$(mktemp)
|
||||||
# </dev/null prevents codex from waiting on stdin when run in background.
|
# </dev/null prevents codex from waiting on stdin when run in background.
|
||||||
"${cli_cmd[@]}" "$prompt" >"$output_file" 2>/dev/null </dev/null &
|
"${cli_cmd[@]}" "$prompt" >"$output_file" 2>"$error_file" </dev/null &
|
||||||
cli_pid=$!
|
cli_pid=$!
|
||||||
|
|
||||||
local frame=0
|
local frame=0
|
||||||
|
|
@ -481,16 +483,49 @@ run_cli() {
|
||||||
|
|
||||||
if wait "$cli_pid" 2>/dev/null; then
|
if wait "$cli_pid" 2>/dev/null; then
|
||||||
last_output=$(<"$output_file")
|
last_output=$(<"$output_file")
|
||||||
rm -f "$output_file"
|
last_error=""
|
||||||
|
rm -f "$output_file" "$error_file"
|
||||||
cli_pid=""
|
cli_pid=""
|
||||||
return 0
|
return 0
|
||||||
else
|
else
|
||||||
rm -f "$output_file"
|
last_error=$(<"$error_file")
|
||||||
|
rm -f "$output_file" "$error_file"
|
||||||
cli_pid=""
|
cli_pid=""
|
||||||
return 1
|
return 1
|
||||||
fi
|
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 "<tokens> <cost>" (tab-separated).
|
# Parse one CLI call's output into "<tokens> <cost>" (tab-separated).
|
||||||
parse_usage() {
|
parse_usage() {
|
||||||
case "$backend" in
|
case "$backend" in
|
||||||
|
|
@ -540,7 +575,7 @@ while ((burned < tokens)); do
|
||||||
if ! run_cli; then
|
if ! run_cli; then
|
||||||
draw_bar " (retrying)"
|
draw_bar " (retrying)"
|
||||||
if ! run_cli; then
|
if ! run_cli; then
|
||||||
printf "\nerror: two consecutive %s calls failed; aborting.\n" "$backend" >&2
|
report_backend_failure
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
68
bin/tui.js
68
bin/tui.js
|
|
@ -84,6 +84,8 @@ let iterationCalls = 0;
|
||||||
let burnSpeed = 0;
|
let burnSpeed = 0;
|
||||||
let telemetryLogs = [];
|
let telemetryLogs = [];
|
||||||
let burnCompletedSuccessfully = true;
|
let burnCompletedSuccessfully = true;
|
||||||
|
let burnFailureSticky = false;
|
||||||
|
let burnFailureMessage = '';
|
||||||
|
|
||||||
// Timing metrics for speed calculations
|
// Timing metrics for speed calculations
|
||||||
let lastTime = 0;
|
let lastTime = 0;
|
||||||
|
|
@ -382,6 +384,15 @@ function parseProgressLine(rawLine) {
|
||||||
const cleanLine = stripAnsi(rawLine).trim();
|
const cleanLine = stripAnsi(rawLine).trim();
|
||||||
if (!cleanLine) return false;
|
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')) {
|
if (cleanLine.startsWith('BURN_PROGRESS\t')) {
|
||||||
const parts = cleanLine.split('\t');
|
const parts = cleanLine.split('\t');
|
||||||
const parsed = updateProgress(
|
const parsed = updateProgress(
|
||||||
|
|
@ -831,6 +842,14 @@ function handleBurnKey(str, key) {
|
||||||
const isSpace = (key && key.name === 'space') || str === ' ';
|
const isSpace = (key && key.name === 'space') || str === ' ';
|
||||||
const isEscape = (key && key.name === 'escape') || str === '\u001b';
|
const isEscape = (key && key.name === 'escape') || str === '\u001b';
|
||||||
|
|
||||||
|
if (burnFailureSticky) {
|
||||||
|
burnFailureSticky = false;
|
||||||
|
setScreen('config');
|
||||||
|
focusedItem = 'backend';
|
||||||
|
isWarningNihilist = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isSpace || isEscape) {
|
if (isSpace || isEscape) {
|
||||||
appendTelemetry(`${RED}⚠️ Interrupted by user. Extinguishing core...${RESET}`);
|
appendTelemetry(`${RED}⚠️ Interrupted by user. Extinguishing core...${RESET}`);
|
||||||
abortBurn();
|
abortBurn();
|
||||||
|
|
@ -896,6 +915,8 @@ function startCombustion() {
|
||||||
setScreen('burn');
|
setScreen('burn');
|
||||||
|
|
||||||
burnWasAborted = false;
|
burnWasAborted = false;
|
||||||
|
burnFailureSticky = false;
|
||||||
|
burnFailureMessage = '';
|
||||||
targetTokens = selectedPresetIdx === 4 ? customTokens : PRESETS[selectedPresetIdx];
|
targetTokens = selectedPresetIdx === 4 ? customTokens : PRESETS[selectedPresetIdx];
|
||||||
burnedTokens = 0;
|
burnedTokens = 0;
|
||||||
burnPercentage = 0;
|
burnPercentage = 0;
|
||||||
|
|
@ -913,11 +934,10 @@ function startCombustion() {
|
||||||
|
|
||||||
const scriptPath = path.join(__dirname, 'burn');
|
const scriptPath = path.join(__dirname, 'burn');
|
||||||
if (!fs.existsSync(scriptPath)) {
|
if (!fs.existsSync(scriptPath)) {
|
||||||
appendTelemetry(`❌ [system] Burn script not found: ${scriptPath}`);
|
burnFailureMessage = `Burn script not found: ${scriptPath}`;
|
||||||
|
appendTelemetry(`❌ [system] ${burnFailureMessage}`);
|
||||||
burnCompletedSuccessfully = false;
|
burnCompletedSuccessfully = false;
|
||||||
returnToConfigTimer = setTimeout(() => {
|
burnFailureSticky = true;
|
||||||
if (activeScreen === 'burn') setScreen('config');
|
|
||||||
}, 2500);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -963,10 +983,9 @@ function startCombustion() {
|
||||||
activeChild = null;
|
activeChild = null;
|
||||||
stopAudioFeed();
|
stopAudioFeed();
|
||||||
burnCompletedSuccessfully = false;
|
burnCompletedSuccessfully = false;
|
||||||
appendTelemetry(`❌ [system] Could not start burn process: ${error.message}`);
|
burnFailureSticky = true;
|
||||||
returnToConfigTimer = setTimeout(() => {
|
burnFailureMessage = `Could not start burn process: ${error.message}`;
|
||||||
if (activeScreen === 'burn') setScreen('config');
|
appendTelemetry(`❌ [system] ${burnFailureMessage}`);
|
||||||
}, 3000);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
activeChild.on('close', (code) => {
|
activeChild.on('close', (code) => {
|
||||||
|
|
@ -978,18 +997,22 @@ function startCombustion() {
|
||||||
}
|
}
|
||||||
if (code === 0 && !burnWasAborted) {
|
if (code === 0 && !burnWasAborted) {
|
||||||
burnCompletedSuccessfully = true;
|
burnCompletedSuccessfully = true;
|
||||||
|
burnFailureSticky = false;
|
||||||
setScreen('victory');
|
setScreen('victory');
|
||||||
} else {
|
} else if (burnWasAborted) {
|
||||||
burnCompletedSuccessfully = false;
|
burnCompletedSuccessfully = false;
|
||||||
appendTelemetry(
|
appendTelemetry('🛑 [system] Combustion aborted.');
|
||||||
burnWasAborted
|
|
||||||
? '🛑 [system] Combustion aborted.'
|
|
||||||
: `❌ [system] Process terminated with exit code: ${code}`
|
|
||||||
);
|
|
||||||
returnToConfigTimer = setTimeout(() => {
|
returnToConfigTimer = setTimeout(() => {
|
||||||
returnToConfigTimer = null;
|
returnToConfigTimer = null;
|
||||||
if (activeScreen === 'burn') setScreen('config');
|
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
|
// 2. Compile Screen: Active Burn
|
||||||
function compileBurnScreen() {
|
function compileBurnScreen() {
|
||||||
let s = '';
|
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
|
// Two Column Layout
|
||||||
const dancerLines = DANCER_POSES[dancerFrame];
|
const dancerLines = failed ? DANCER_POSES[0] : DANCER_POSES[dancerFrame];
|
||||||
|
|
||||||
// Left Column (Disco Dancer + Fire)
|
// Left Column (Disco Dancer + Fire)
|
||||||
const leftCol = [];
|
const leftCol = [];
|
||||||
dancerLines.forEach(l => leftCol.push(l));
|
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)
|
// Right Column (Metrics & Odometer)
|
||||||
const pct = Math.min(100, burnPercentage);
|
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(`${BOLD}${YELLOW}OKRs:${RESET} Ladder: ${okrDraw(pct, 1.4)}`);
|
||||||
rightCol.push(` Manager: ${okrDraw(pct, 2.5)}`);
|
rightCol.push(` Manager: ${okrDraw(pct, 2.5)}`);
|
||||||
rightCol.push(` VC Depth:${okrDraw(pct, 4.0)}`);
|
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
|
// Merge columns
|
||||||
const maxLines = Math.max(leftCol.length, rightCol.length);
|
const maxLines = Math.max(leftCol.length, rightCol.length);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue