Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,25 @@ public nonisolated struct AgentChatDetail: Hashable, Sendable {
public var laneSinceMs: UInt64?
/// The session's folder (`cwd`).
public var cwd: String?
/// What each model call carries now and the window it fits in, in tokens, and what the
/// session spent in the last 24 hours, in US dollars (acpmux `usageStats`: `contextTokens`,
/// `contextWindow`, `costUsd24h`); nil when acpmux has not read them.
public var contextTokens: Int?
public var contextWindow: Int?
public var costUsd24h: Double?

public init(model: String? = nil, turnStartedAtMs: UInt64? = nil, prompt: String? = nil,
laneState: String? = nil, laneSinceMs: UInt64? = nil, cwd: String? = nil) {
laneState: String? = nil, laneSinceMs: UInt64? = nil, cwd: String? = nil,
contextTokens: Int? = nil, contextWindow: Int? = nil, costUsd24h: Double? = nil) {
self.model = model
self.turnStartedAtMs = turnStartedAtMs
self.prompt = prompt
self.laneState = laneState
self.laneSinceMs = laneSinceMs
self.cwd = cwd
self.contextTokens = contextTokens
self.contextWindow = contextWindow
self.costUsd24h = costUsd24h
}

/// A summary's detail; nil when it names none of them.
Expand All @@ -69,9 +79,20 @@ public nonisolated struct AgentChatDetail: Hashable, Sendable {
return text
}
let lane = summary["lane"] as? [String: Any]
let usage = summary["usageStats"] as? [String: Any]
// A count acpmux could not read is null; a negative or non-finite one is no count.
let amount = { (value: Any?) -> Double? in
guard let number = value as? NSNumber, CFGetTypeID(number) != CFBooleanGetTypeID() else { return nil }
let amount = number.doubleValue
return amount.isFinite && amount >= 0 ? amount : nil
}
let tokens = { (value: Any?) -> Int? in amount(value).flatMap { $0 < Double(Int.max) ? Int($0) : nil } }
let detail = AgentChatDetail(model: text(summary["model"]), turnStartedAtMs: (turn?["startedAt"] as? NSNumber)?.uint64Value,
prompt: text(turn?["prompt"]), laneState: text(lane?["state"]),
laneSinceMs: (lane?["since"] as? NSNumber)?.uint64Value, cwd: text(summary["cwd"]))
laneSinceMs: (lane?["since"] as? NSNumber)?.uint64Value, cwd: text(summary["cwd"]),
contextTokens: tokens(usage?["contextTokens"]),
contextWindow: tokens(usage?["contextWindow"]).flatMap { $0 > 0 ? $0 : nil },
costUsd24h: amount(usage?["costUsd24h"]))
return detail == AgentChatDetail() ? nil : detail
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ extension SidebarMapping {
detail.activitySince = Self.activitySince(tabs: tabs, indicator: indicator, turns: turns)
let chat = agentTab?.agentSession.flatMap(turns.detail(for:))
detail.model = chat?.model
detail.contextTokens = chat?.contextTokens
detail.contextWindow = chat?.contextWindow
detail.costUsd24h = chat?.costUsd24h
// The task: the chat's running prompt, else the terminal agent's last prompt (memory
// only in the daemon), else what a working program says it does (OSC 7501 title, msg).
detail.task = chat?.prompt ?? agentTab?.agent?.lastPrompt ?? tabs.lazy.filter { ProgramStatusRecord.strongest($0.programStatus)?.state == .working }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,23 @@ struct WorkspaceHoverCardContent: Equatable {
Calendar.current.isDate(date, inSameDayAs: now) ? clockFormatter.string(from: date) : dayFormatter.string(from: date)
}

/// What the row's agent chat carries per call and spent in the last 24 hours ("312K context ·
/// $1,421 in 24h"), with the context's share of its window as a ring; nil when acpmux reported
/// neither (Leo 2026-10-30: "that visibility into huge-blue guys was (and is?) pretty bad").
static func usageFact(_ detail: SidebarWorkspaceDetail) -> Fact? {
let context = detail.contextTokens.map { Strings.usageContext($0.formatted(.number.notation(.compactName))) }
let cost = detail.costUsd24h.map {
// Half away from zero, as the page's Intl rounds it ($1,420.50 is $1,421 in both).
Strings.usageCost($0.formatted(.currency(code: "USD").precision(.fractionLength($0 >= 100 ? 0 : 2))
.rounded(rule: .toNearestOrAwayFromZero)))
}
let parts = [context, cost].compactMap { $0 }
guard !parts.isEmpty else { return nil }
let share = detail.contextTokens.flatMap { tokens in detail.contextWindow.map { min(1, Double(tokens) / Double($0)) } }
return Fact(icon: .contextUsage, text: parts.joined(separator: " · "), kind: "usage",
accessory: share.map { .ring($0, tone: .secondary) })
}

/// The facts card (the classic layout): one row per meaningful fact. Without `showsGit` the
/// place reads as the folder alone (no branch, no repository from a pull request).
private static func facts(_ workspace: SidebarWorkspace, machine: SidebarMachine?, now: Date,
Expand Down Expand Up @@ -214,6 +231,7 @@ struct WorkspaceHoverCardContent: Equatable {
if let agent = workspace.agentName {
facts.append(Fact(icon: .agentChat, text: [agent, detail.model].compactMap { $0 }.joined(separator: " · "), kind: "agent"))
}
if let usage = usageFact(detail) { facts.append(usage) }
if let task = detail.task { facts.append(Fact(icon: .textDescription, text: task, lines: 3, kind: "task")) }
for presence in workspace.presence.prefix(2) {
facts.append(Fact(icon: toolIcon(presence.activity), text: Strings.presenceLine(presence), kind: "presence"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,25 @@ public nonisolated struct SidebarWorkspaceDetail: Hashable, Sendable {
/// when it started (the daemon's OSC 133 fact).
public var command: String?
public var commandSince: Date?
/// What the row's agent chat carries per model call and the window it fits in, in tokens,
/// and what it spent in the last 24 hours, in US dollars (acpmux `usageStats`); nil when
/// acpmux has not read them. Leo 2026-10-30: "that visibility into huge-blue guys was (and
/// is?) pretty bad".
public var contextTokens: Int?
public var contextWindow: Int?
public var costUsd24h: Double?

public init(activitySince: Date? = nil, model: String? = nil, task: String? = nil, needsInput: String? = nil,
command: String? = nil, commandSince: Date? = nil) {
command: String? = nil, commandSince: Date? = nil, contextTokens: Int? = nil, contextWindow: Int? = nil,
costUsd24h: Double? = nil) {
self.activitySince = activitySince
self.model = model
self.task = task
self.needsInput = needsInput
self.command = command
self.commandSince = commandSince
self.contextTokens = contextTokens
self.contextWindow = contextWindow
self.costUsd24h = costUsd24h
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,270 @@
{
"sourceLanguage": "en",
"strings": {
"sidebar.hoverCard.usageContext": {
"comment": "Workspace hover card: the agent chat's usage row (acpmux usageStats)",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ context"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "コンテキスト %@"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "سياق %@"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "%@ konteksta"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "%@ kontekst"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ Kontext"
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "%@ de contexto"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "%@ de contexte"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "%@ di contesto"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "បរិបទ %@"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "컨텍스트 %@"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "%@ kontekst"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "%@ kontekstu"
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "%@ de contexto"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "Контекст %@"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "บริบท %@"
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "%@ bağlam"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "Контекст %@"
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "%@ ngữ cảnh"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "上下文 %@"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "上下文 %@"
}
}
}
},
"sidebar.hoverCard.usageCost": {
"comment": "Workspace hover card: the agent chat's usage row (acpmux usageStats)",
"extractionState": "manual",
"localizations": {
"en": {
"stringUnit": {
"state": "translated",
"value": "%@ in 24h"
}
},
"ja": {
"stringUnit": {
"state": "translated",
"value": "24時間で %@"
}
},
"ar": {
"stringUnit": {
"state": "translated",
"value": "%@ خلال 24 س"
}
},
"bs": {
"stringUnit": {
"state": "translated",
"value": "%@ za 24 h"
}
},
"da": {
"stringUnit": {
"state": "translated",
"value": "%@ på 24 t"
}
},
"de": {
"stringUnit": {
"state": "translated",
"value": "%@ in 24 Std."
}
},
"es": {
"stringUnit": {
"state": "translated",
"value": "%@ en 24 h"
}
},
"fr": {
"stringUnit": {
"state": "translated",
"value": "%@ en 24 h"
}
},
"it": {
"stringUnit": {
"state": "translated",
"value": "%@ in 24 h"
}
},
"km": {
"stringUnit": {
"state": "translated",
"value": "%@ ក្នុង 24 ម៉ោង"
}
},
"ko": {
"stringUnit": {
"state": "translated",
"value": "24시간 %@"
}
},
"nb": {
"stringUnit": {
"state": "translated",
"value": "%@ på 24 t"
}
},
"pl": {
"stringUnit": {
"state": "translated",
"value": "%@ w 24 godz."
}
},
"pt-BR": {
"stringUnit": {
"state": "translated",
"value": "%@ em 24 h"
}
},
"ru": {
"stringUnit": {
"state": "translated",
"value": "%@ за 24 ч"
}
},
"th": {
"stringUnit": {
"state": "translated",
"value": "%@ ใน 24 ชม."
}
},
"tr": {
"stringUnit": {
"state": "translated",
"value": "24 saatte %@"
}
},
"uk": {
"stringUnit": {
"state": "translated",
"value": "%@ за 24 год"
}
},
"vi": {
"stringUnit": {
"state": "translated",
"value": "%@ trong 24 giờ"
}
},
"zh-Hans": {
"stringUnit": {
"state": "translated",
"value": "24 小时 %@"
}
},
"zh-Hant": {
"stringUnit": {
"state": "translated",
"value": "24 小時 %@"
}
}
}
},
"autoArchive.undoTitle": {
"extractionState": "manual",
"localizations": {
Expand Down
Loading
Loading