53b4f60da1
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
3.0 KiB
Swift
70 lines
3.0 KiB
Swift
import Foundation
|
|
|
|
// What the iPhone's Live Activity (Lock Screen and Dynamic Island) shows:
|
|
// the most urgent agent session, while the Mac is locked. The Mac builds it,
|
|
// the relay forwards it as the Live Activity's "content-state", ActivityKit
|
|
// decodes it on the iPhone. It passes through the relay and Apple's push
|
|
// service, so it holds no project name, step text, command or path: only the
|
|
// agent, its state and counts.
|
|
|
|
struct MochiActivityState: Codable, Hashable, Sendable {
|
|
var pillId: String
|
|
var agent: String // "VS Code", "Codex"…
|
|
var color: String // agent color, hex
|
|
var state: String // BotState raw value
|
|
var statusText: String // "working · 3/7", "waiting for your OK"…
|
|
var tone: String // waiting, question, error, working, done, idle
|
|
var stepIndex: Int
|
|
var stepCount: Int
|
|
var others: Int // other sessions still going
|
|
/// When Mochi left for the iPhone (Unix seconds): the iPhone counts the
|
|
/// time from it, live. Optional so older pushes still decode.
|
|
var since: Int? = nil
|
|
/// The fingerprint of the command waiting for your OK (an opaque hash,
|
|
/// never the command): the Lock Screen's Allow and Deny answer this one.
|
|
var approval: String? = nil
|
|
|
|
var botState: BotState { BotState(rawValue: state) ?? .idle }
|
|
var isActive: Bool { ["waiting", "question", "working", "error"].contains(tone) }
|
|
|
|
/// Lower = more urgent, same order as the iPhone list.
|
|
static func urgency(state: BotState, waitingForOK: Bool, hasQuestion: Bool) -> Int {
|
|
if waitingForOK || state == .approval { return 0 }
|
|
if hasQuestion || state == .question { return 1 }
|
|
switch state {
|
|
case .error, .ratelimit, .dizzy: return 2
|
|
case .working, .thinking, .searching: return 3
|
|
case .finished: return 4
|
|
default: return 5
|
|
}
|
|
}
|
|
|
|
static func tone(urgency: Int) -> String {
|
|
["waiting", "question", "error", "working", "done"].indices.contains(urgency)
|
|
? ["waiting", "question", "error", "working", "done"][urgency] : "idle"
|
|
}
|
|
|
|
static func statusText(state: BotState, urgency: Int, stepIndex: Int, stepCount: Int) -> String {
|
|
switch urgency {
|
|
case 0: return "waiting for your OK"
|
|
case 1: return "has a question"
|
|
default: break
|
|
}
|
|
switch state {
|
|
case .working, .thinking, .searching:
|
|
return stepCount == 0 ? "working" : "working · \(min(stepIndex + 1, stepCount))/\(stepCount)"
|
|
case .finished: return "✓ done"
|
|
case .error: return "error"
|
|
case .ratelimit: return "rate limited"
|
|
case .sleeping: return "asleep"
|
|
case .dizzy: return "dizzy"
|
|
default: return "idle"
|
|
}
|
|
}
|
|
|
|
static let placeholder = MochiActivityState(
|
|
pillId: "integration_claude", agent: "VS Code", color: "#4A86E8",
|
|
state: "working", statusText: "working · 3/7", tone: "working",
|
|
stepIndex: 2, stepCount: 7, others: 1)
|
|
}
|