Repository navigation
cmux NEXT: Session Info's Usage and the sidebar hover card show what each session spends (carry #27923) #28773
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
33 commits
Select commit
Hold shift + click to select a range
e4d77b1
test: Session Info Usage and the hover card usage row read acpmux usa…
teamleaderleo ef9b192
cmux NEXT: Session Info's Usage and the hover card show what each ses…
teamleaderleo a72f7a8
Session Info Usage: no redraw per token while the numbers hold
teamleaderleo 6bc2ccc
Session Info Usage: unknown is an em dash, and each number has its ne…
teamleaderleo e0e80cb
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo 27f6e66
test: Session Info's section order includes Usage after Context
teamleaderleo 5c6aea2
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo 5199abf
Hover card usage row: round the day's cost half up, as Session Info does
teamleaderleo 2339585
Session Info Usage: no dead clicks, no arrow on its buttons
teamleaderleo fab65d8
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo d1eba87
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo 0b628f1
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo 393db11
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo cd4225c
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/ao-vis-ui
teamleaderleo 99e7ca0
Merge origin/feat-cmux-next into the carry of #27923
teamleaderleo e0319f0
Gallery: Session Info Usage sorted by 24h cost with a session whose c…
teamleaderleo 9de6ae6
Session Info Usage: an unknown 24h cost is an em dash in the cost-sor…
teamleaderleo 741a00a
Merge origin/feat-cmux-next into the carry of #27923
teamleaderleo b58c8b1
test: Session Info's Usage asks acpmux for every chat's usage
teamleaderleo 1c55a89
Merge origin/feat-cmux-next into the carry of #27923
teamleaderleo dacf261
Session Info's Usage asks acpmux for every chat's usage when it shows
teamleaderleo 221177f
Session Info's Usage: format sessionPages.ts and its test (vp fmt)
teamleaderleo f687496
test: the hover card's usage comes from a usage read, not the watch's…
teamleaderleo 0039cf9
The sidebar hover card asks acpmux for each chat's usage when the wat…
teamleaderleo d881860
Merge origin/feat-cmux-next into the carry of #27923
teamleaderleo b8eadc6
test(usage): read base's section order after the merge (groupOrder is…
teamleaderleo d05e09b
ChatInspector: pass onCompact once (the merge kept base's and the car…
teamleaderleo e0c9b7c
Merge branch 'feat-cmux-next' into lane/carry-27923
cmux-bot[bot] a501cbe
Merge branch 'feat-cmux-next' into lane/carry-27923
teamleaderleo cc55f24
Merge commit '5a273091bbef97cbeaaee8346088a3499b0a1acc' into cw-28773
lawrencecchen 47381de
Session Info Usage: the carried Usage section, its strings, CSS and t…
lawrencecchen f38ca14
test(hover-card usage): import CmuxNextAgentActivity and CmuxNextDaem…
lawrencecchen a8f49cb
Merge remote-tracking branch 'origin/feat-cmux-next' into lane/carry-…
teamleaderleo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,6 +50,12 @@ 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? | ||
| /// The running turn's prompt is acpmux's notice of other agents' messages ("[inbox] New | ||
| /// messages from other agents…", `turn.notice`), not anyone's words: the hover card's | ||
| /// History leaves it out. | ||
|
|
@@ -62,14 +68,18 @@ public nonisolated struct AgentChatDetail: Hashable, Sendable { | |
| public var commandsRunning = 0 | ||
|
|
||
| public init(model: String? = nil, turnStartedAtMs: UInt64? = nil, prompt: String? = nil, | ||
| laneState: String? = nil, laneSinceMs: UInt64? = nil, cwd: String? = nil, promptIsNotice: Bool = false, | ||
| laneState: String? = nil, laneSinceMs: UInt64? = nil, cwd: String? = nil, | ||
| contextTokens: Int? = nil, contextWindow: Int? = nil, costUsd24h: Double? = nil, promptIsNotice: Bool = false, | ||
| subagentsRunning: Int = 0, subagentNames: [String] = [], commandsRunning: Int = 0) { | ||
| 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 | ||
| self.promptIsNotice = promptIsNotice | ||
| self.subagentsRunning = subagentsRunning | ||
| self.subagentNames = subagentNames | ||
|
|
@@ -84,17 +94,40 @@ 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 count = { (value: Any?) -> Int in max(0, (value as? NSNumber)?.intValue ?? 0) } | ||
| 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"]), | ||
| contextTokens: tokens(usage?["contextTokens"]), | ||
| contextWindow: tokens(usage?["contextWindow"]).flatMap { $0 > 0 ? $0 : nil }, | ||
| costUsd24h: amount(usage?["costUsd24h"]), | ||
| promptIsNotice: turn?["notice"] as? Bool == true, | ||
| subagentsRunning: count(summary["subagentsRunning"]), | ||
| subagentNames: (summary["subagentNames"] as? [Any] ?? []).compactMap(text), | ||
| commandsRunning: count(summary["commandsRunning"])) | ||
| return detail == AgentChatDetail() ? nil : detail | ||
| } | ||
|
|
||
| /// Whether acpmux read any of the chat's usage (`usageStats`). | ||
| public var hasUsage: Bool { contextTokens != nil || contextWindow != nil || costUsd24h != nil } | ||
|
|
||
| /// This detail with `usage`'s usage numbers in place of its own; the rest stays. | ||
| public func withUsage(of usage: AgentChatDetail) -> AgentChatDetail { | ||
| var detail = self | ||
| detail.contextTokens = usage.contextTokens | ||
| detail.contextWindow = usage.contextWindow | ||
| detail.costUsd24h = usage.costUsd24h | ||
| return detail | ||
| } | ||
|
|
||
| /// The detail of every session of a watch result that has one. | ||
| public static func details(_ result: [String: Any]) -> [String: AgentChatDetail] { | ||
| var details: [String: AgentChatDetail] = [:] | ||
|
|
@@ -376,6 +409,17 @@ public final class AgentTurnStateStore { | |
| } | ||
| } | ||
|
|
||
| /// Lays a usage read's numbers (`_acpmux/sessions` with `usage: true`, by session id) over | ||
| /// `source`'s sessions, leaving every other fact as it is; writes only what changed. A session | ||
| /// the store does not hold from `source` is skipped: the watch has not listed it. | ||
| public func applyUsage(_ usage: [String: AgentChatDetail], source: String = AgentTurnStateStore.localSource) { | ||
| for (id, numbers) in usage { | ||
| guard let record = sessions[id], record.source == source else { continue } | ||
| let detail = (record.detail ?? AgentChatDetail()).withUsage(of: numbers) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: Prompt for AI agents |
||
| if record.detail != detail { record.detail = detail } | ||
| } | ||
| } | ||
|
|
||
| /// `source`'s watch closed: nothing live is known of its sessions. Titles stay: a tab keeps | ||
| /// reading as its chat while the watch reconnects. | ||
| public func clearLive(source: String = AgentTurnStateStore.localSource) { | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: The
tokensparser truncates fractional values, so malformed or non-integralcontextTokens/contextWindowvalues are displayed as smaller valid counts instead of an em dash. Reject non-integral numbers with an exactIntconversion.Prompt for AI agents