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:
@@ -109,6 +109,49 @@ Singleton {
|
||||
return at ? Qt.formatDateTime(at, Settings.use24Hour ? "HH:mm" : "h:mm AP") : "";
|
||||
}
|
||||
|
||||
// ── Commands carried as data ────────────────────────────────────────────
|
||||
//
|
||||
// A notification may name a shell command in the `panama-exec` hint, and
|
||||
// clicking its body runs it (modules/notifications/NotificationCard.qml).
|
||||
// That is how the escalation ladder works: `panama-crash-watch` sees a
|
||||
// coredump, sends "click to diagnose with your agent", and exits. The
|
||||
// sender does not have to stay alive to service a freedesktop action, and
|
||||
// the click keeps working across a shell restart, because the command is
|
||||
// the notification rather than a callback into a process that has gone.
|
||||
//
|
||||
// Kept beside `arrivals` and for the same reason: the protocol hands the
|
||||
// value over once, at delivery, and the card needs it long afterwards. One
|
||||
// read, one validation, one entry per notification, dropped by forget().
|
||||
//
|
||||
// SECURITY. Any process on this session bus can set this hint. That is not
|
||||
// an escalation: a process that can reach the session bus can already run
|
||||
// whatever it likes as this user, without asking a notification card
|
||||
// first, so the hint grants nothing a local process lacks. The property
|
||||
// this DOES keep is that nothing runs on arrival -- the command is stored,
|
||||
// never executed here, and only a deliberate click on the card runs it.
|
||||
readonly property var execCommands: ({})
|
||||
|
||||
// The command this notification carries, or "" for the overwhelming
|
||||
// majority that carry none.
|
||||
function execCommand(notification: var): string {
|
||||
if (!notification)
|
||||
return "";
|
||||
const stored = root.execCommands[notification.id];
|
||||
return typeof stored === "string" ? stored : "";
|
||||
}
|
||||
|
||||
// Stamped at delivery. An id being reused by a replacement notification
|
||||
// that carries no hint has to clear the old command rather than inherit
|
||||
// it, which is why the empty case deletes instead of returning early.
|
||||
function rememberExecCommand(notification: var): void {
|
||||
const hints = notification.hints ?? {};
|
||||
const command = String(hints["panama-exec"] ?? "").trim();
|
||||
if (command === "")
|
||||
delete root.execCommands[notification.id];
|
||||
else
|
||||
root.execCommands[notification.id] = command;
|
||||
}
|
||||
|
||||
// Freedesktop timeout resolution, shared by the toast countdown (Toast.qml)
|
||||
// and the no-display expiry a transient notification gets while Do Not
|
||||
// Disturb is on (below). Critical urgency and an explicit expireTimeout
|
||||
@@ -373,6 +416,7 @@ Singleton {
|
||||
// Without this the object is destroyed the instant this returns.
|
||||
notification.tracked = true;
|
||||
root.arrivals[notification.id] = new Date();
|
||||
root.rememberExecCommand(notification);
|
||||
|
||||
// The object may go away at any time (app-side close, dismiss()).
|
||||
// Drop our references synchronously when it does.
|
||||
@@ -610,6 +654,7 @@ Singleton {
|
||||
// only ever removes references, never touches the notification.
|
||||
function forget(n: var): void {
|
||||
delete root.arrivals[n.id];
|
||||
delete root.execCommands[n.id];
|
||||
if (root.history.indexOf(n) !== -1)
|
||||
root.history = root.history.filter(x => x !== n);
|
||||
if (root.popups.indexOf(n) !== -1)
|
||||
|
||||
Reference in New Issue
Block a user