From 5842b093cc502b46076423e4f8efdbbc568f86aa Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Thu, 5 Nov 2026 19:21:43 +0200 Subject: pi: a failed run says so: a 'failed' alert with the error, and the error in red until seen --- menubar/model.swift | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'menubar/model.swift') diff --git a/menubar/model.swift b/menubar/model.swift index 4117ba5..fb83105 100644 --- a/menubar/model.swift +++ b/menubar/model.swift @@ -105,6 +105,11 @@ struct Row: Equatable { /// How full its context window is, in percent (`@dingger_context`), as /// pi's footer shows it; nil when unknown. var context: Double? = nil + /// Why its run failed (`@dingger_error`), while it is done; "" if not. + var error: String = "" + + /// Done, but its run failed. + var failed: Bool { status == .done && !error.isEmpty } var location: String { "\(session) · \(window)" } /// A draft left behind: where the user looks, they may be typing it. @@ -134,15 +139,25 @@ struct Row: Equatable { } /// The panel's second line, before its cost and context columns. - var place: String { + var place: String { [placeBase, failure].compactMap { $0 }.joined(separator: " · ") } + + /// Where it runs, which harness, whether it is low, and how long it + /// waits, unless its run failed (see `failure`). + var placeBase: String { var parts = [location, agent] if importance == .low { parts.append("low") } - if waiting, since != Int.max { + if waiting, since != Int.max, !failed { parts.append("\(status.rawValue) \(elapsed(sinceMs: since))") } return parts.joined(separator: " · ") } + /// "failed 3m · 401: invalid x-api-key", for a failed run. + var failure: String? { + guard failed else { return nil } + return (since != Int.max ? "failed \(elapsed(sinceMs: since))" : "failed") + " · " + error + } + /// The panel's row as text: its columns, then cost and context. var panelText: String { let right = [cost >= 0.005 ? dollars(cost) : nil, context.map(contextText)].compactMap { $0 } @@ -356,19 +371,19 @@ func fetchRows() -> [Row] { let format = [ "#{pane_id}", "#{@agent}", "#{@agent_state}", "#{@dingger_since}", "#{@agent_name}", "#{session_name}", "#{window_name}", "#{window_panes}", "#{pane_index}", "#{pane_current_path}", - "#{@dingger_importance}", "#{window_active}", "#{session_attached}", "#{@dingger_draft}", "#{@dingger_cost}", "#{@dingger_context}", + "#{@dingger_importance}", "#{window_active}", "#{session_attached}", "#{@dingger_draft}", "#{@dingger_cost}", "#{@dingger_context}", "#{@dingger_error}", ].joined(separator: "\t") guard let out = tmux(["list-panes", "-a", "-F", format]) else { return [] } let lines = out.split(separator: "\n").map { $0.split(separator: "\t", omittingEmptySubsequences: false).map(String.init) } // Looked at in any of its sessions, as the daemon counts it. - let attended = Set(lines.filter { $0.count >= 16 && $0[11] == "1" && $0[12] != "0" }.map { $0[0] }) + let attended = Set(lines.filter { $0.count >= 17 && $0[11] == "1" && $0[12] != "0" }.map { $0[0] }) // A pane in several sessions (a session group, a linked window) is // listed once, under its first session, as the daemon counts it. var listed = Set() let rows: [Row] = lines.compactMap { f in - guard f.count >= 16, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil } + guard f.count >= 17, !f[1].isEmpty, listed.insert(f[0]).inserted else { return nil } let status = Status(rawValue: f[2]) ?? .unknown guard status != .unknown else { return nil } let window = (Int(f[7]) ?? 1) > 1 ? "\(f[6]).\(f[8])" : f[6] @@ -376,7 +391,7 @@ func fetchRows() -> [Row] { session: f[5], window: window, since: Int(f[3]) ?? Int.max, path: f[9].hasPrefix(home) ? "~" + f[9].dropFirst(home.count) : f[9], importance: Importance(option: f[10]), draft: f[13], attended: attended.contains(f[0]), - cost: Double(f[14]) ?? 0, context: Double(f[15])) + cost: Double(f[14]) ?? 0, context: Double(f[15]), error: f[16]) } return rows.sorted(by: listedBefore) } -- cgit v1.2.3