No error is a dead end: crash, click, and your agent is already looking

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
Gabriel Brown
2026-08-25 12:50:09 -04:00
parent ada0faf1d1
commit cc7d91d09c
43 changed files with 4648 additions and 327 deletions
+71
View File
@@ -710,6 +710,77 @@ ShellRoot {
function onReloadFailed(error: string): void {
console.warn("Panama shell reload failed:", error);
Quickshell.inhibitReloadPopup();
root.offerReloadDiagnosis(error);
}
}
// ── The reload-failure rung of the escalation ladder ─────────────────────
//
// This is the failure most likely to happen while somebody is customizing
// the desktop, and the one they are least equipped to read: a QML parse
// error in the journal, and a shell that silently keeps running the old
// configuration. That "keeps running" is what makes the rung possible --
// the shell that failed to load the change is not the shell answering
// here, so it can still say so and still offer the log to an agent.
//
// Sent through notify-send rather than constructed in-process on purpose:
// it takes the same delivery path, the same per-application rules and the
// same `panama-exec` click as every other rung, so there is one mechanism
// to keep working rather than two.
//
// Everything below is guarded. A handler that throws on a failed reload
// turns one bad save into a broken desktop, so a fault in the offer must
// cost nothing beyond the offer.
function offerReloadDiagnosis(error: string): void {
try {
if (DesktopPreferences.get("reloadFailureOffer") !== true)
return;
// No agent chosen is the shipped default, and it means exactly
// what it says: the desktop stays quiet rather than volunteering a
// tool nobody asked for. The old actionless behaviour, verbatim.
const agent = String(DesktopPreferences.get("preferredAgent") ?? "none");
if (agent === "" || agent === "none")
return;
const spec = PreferenceSchema.spec("preferredAgent");
const option = (spec?.options ?? []).find(entry => entry.value === agent);
const label = option ? option.label : agent;
// The failing message goes to the launcher as ONE single-quoted
// argument, so nothing a QML parse error can contain -- and they
// contain plenty of punctuation -- ends the quoting and starts a
// command of its own. `panama-agent-reload` reads the rest of the
// context out of the journal itself.
const summary = String(error ?? "").replace(/\s+/g, " ").trim().slice(0, 500);
// The launcher is reached by path rather than by name: the shell
// is started by systemd, whose environment does not carry the
// repo's bin directory on PATH. Same expansion the
// panama-crash-watch unit uses.
const command = '"${PANAMA_PATH:-$HOME/.local/share/Panama}/bin/panama-agent-reload" '
+ root.shellQuote(summary);
// Ordinary urgency, the same as the crash rung's. A critical
// notification never expires and can break through Do Not Disturb,
// which is a louder desktop than anybody asked for in exchange for
// an offer that keeps in history until it is read anyway.
Quickshell.execDetached([
"notify-send", "--app-name=Panama",
"--icon=dialog-error-symbolic",
"--hint=string:panama-exec:" + command,
"The shell could not reload your change",
"The previous configuration is still running. Click to hand the failure to "
+ label + "."
]);
} catch (problem) {
console.warn("Panama shell reload failure offer skipped:", problem);
}
}
// POSIX single-quoting: everything between the quotes is literal, and the
// only character that needs care is the quote itself.
function shellQuote(text: string): string {
return "'" + String(text).replace(/'/g, "'\\''") + "'";
}
}