mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
af9b0c7616
* fix(macos): keep the node channel alive and visible when the node-host worker cannot start A node-host worker that exited before its ready manifest never notified the retry policy, so the coordinator respawned the broken CLI forever and the whole node channel silently never dialed the gateway. Startup exits now consume the crash retry budget and carry the worker's stderr into the start error; every non-transient worker failure degrades the connect to native capabilities instead of aborting it; and the menu bar surfaces the recorded node-channel state with the concrete reason. * fix(macos): render node-channel status as a top-level menu view The native-menu extra style flattens multi-view Toggle labels to their first Text, so status sublines inside the label never rendered — the original 'zero indication' report. Top-level menu views render (exec-approval error pattern).
51 lines
1.9 KiB
Swift
51 lines
1.9 KiB
Swift
import Foundation
|
|
import Observation
|
|
|
|
/// Recorded fact for the Mac node channel, written by MacNodeModeCoordinator at
|
|
/// the connect boundary. The menu bar reads this instead of inferring node
|
|
/// health from gateway node listings, so a node channel that never dials still
|
|
/// surfaces its reason to the operator.
|
|
enum MacNodeChannelState: Equatable, Sendable {
|
|
/// Node mode is paused, stopped, or not configured to run.
|
|
case idle
|
|
/// The channel connected. A non-nil reason means the node-host worker is
|
|
/// unavailable and only native capabilities are advertised.
|
|
case connected(workerUnavailableReason: String?)
|
|
/// The last connect attempt failed; the coordinator keeps retrying.
|
|
case unavailable(reason: String)
|
|
|
|
var operatorStatusLine: (label: String, isDegraded: Bool)? {
|
|
switch self {
|
|
case .idle, .connected(workerUnavailableReason: nil):
|
|
nil
|
|
case let .connected(workerUnavailableReason: .some(reason)):
|
|
("Mac node degraded — \(Self.condense(reason))", true)
|
|
case let .unavailable(reason):
|
|
("Mac node unavailable — \(Self.condense(reason))", false)
|
|
}
|
|
}
|
|
|
|
/// Menu status lines are single-line; keep the leading reason sentence and
|
|
/// bound it so a CLI stack trace cannot flood the menu.
|
|
private static func condense(_ reason: String) -> String {
|
|
let firstLine = reason
|
|
.split(separator: "\n", omittingEmptySubsequences: true)
|
|
.first
|
|
.map { $0.trimmingCharacters(in: .whitespaces) } ?? reason
|
|
return firstLine.count > 220 ? firstLine.prefix(220) + "…" : firstLine
|
|
}
|
|
}
|
|
|
|
@MainActor
|
|
@Observable
|
|
final class MacNodeChannelStatusStore {
|
|
static let shared = MacNodeChannelStatusStore()
|
|
|
|
private(set) var state: MacNodeChannelState = .idle
|
|
|
|
func record(_ state: MacNodeChannelState) {
|
|
guard self.state != state else { return }
|
|
self.state = state
|
|
}
|
|
}
|