Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
981a7b0
Document platform history design and CodeCaps channel routing.
jaywedgeworth22 Oct 4, 2026
80c6625
Clarify provider probes and native companion architecture.
jaywedgeworth22 Oct 4, 2026
53bff65
Validate quota history before anomaly comparisons
jaywedgeworth22 Oct 4, 2026
d54f707
Render native platform UI fixtures in hosted validation.
jaywedgeworth22 Oct 4, 2026
3571e90
Reserve account-bound passive quota reader work.
jaywedgeworth22 Oct 4, 2026
9d2611b
Show per-platform quota history and route alerts to matching windows
jaywedgeworth22 Oct 4, 2026
95c9d31
Keep notification links aligned with saved alert observations.
jaywedgeworth22 Oct 4, 2026
6aa934d
Read account-bound Codex session quotas passively
jaywedgeworth22 Oct 4, 2026
d3a5f7e
Keep bounded read data immutable
jaywedgeworth22 Oct 4, 2026
c368028
Retain the first appended quota event at a known line boundary.
jaywedgeworth22 Oct 4, 2026
15e690a
Merge remote-tracking branch 'origin/main' into codex/platform-usage-…
jaywedgeworth22 Oct 4, 2026
ad1c321
Validate saved read credentials before changing shared endpoints.
jaywedgeworth22 Oct 4, 2026
be203ae
Collapse queued Swift CI duplicates for the same pull request.
jaywedgeworth22 Oct 4, 2026
78fa3ae
Scope Infisical settings reads to managed keys
jaywedgeworth22 Oct 4, 2026
60691fa
Merge branch 'codex/platform-usage-history' into codex/independent-so…
jaywedgeworth22 Oct 4, 2026
6ab1f53
Merge branch 'codex/platform-usage-history' into codex/independent-so…
jaywedgeworth22 Oct 4, 2026
76ee030
Use canonical nonsymlink directories in passive reader fixtures.
jaywedgeworth22 Oct 4, 2026
9ab8eb7
Show stale history observation time
jaywedgeworth22 Oct 4, 2026
5664758
Merge remote-tracking branch 'origin/main' into codex/platform-usage-…
jaywedgeworth22 Oct 4, 2026
8bed693
Match normalized local readings when selecting usage history.
jaywedgeworth22 Oct 4, 2026
59d2076
Render chart history in native UI fixtures
jaywedgeworth22 Oct 4, 2026
4120a1d
Separate provider and Codex session refresh cycles
jaywedgeworth22 Oct 4, 2026
39249a0
Apply display pool normalization to local history provenance.
jaywedgeworth22 Oct 4, 2026
d0f27bc
Merge branch 'codex/platform-usage-history' into codex/independent-so…
jaywedgeworth22 Oct 4, 2026
1aa051b
Document provider and passive file refresh frequency and data flow.
jaywedgeworth22 Oct 4, 2026
77faedd
Add Alerts and Alarms history screenshot fixture
jaywedgeworth22 Oct 4, 2026
a37d3ee
Guard account changes and publish passive refresh snapshots
jaywedgeworth22 Oct 4, 2026
2e4095f
Skip Codex auth reads for server-only and injected refreshes
jaywedgeworth22 Oct 4, 2026
e5ecf05
fix: address Kodus review findings (PR #142)
Oct 4, 2026
f4aafcc
Merge branch 'main' into ag/independent-source-refresh
jaywedgeworth22 Oct 4, 2026
8f005f2
Merge branch 'main' into ag/independent-source-refresh and address Ko…
jaywedgeworth22 Oct 4, 2026
b41a1ec
fix(model): preserve provider issue reporting when resolving session …
jaywedgeworth22 Oct 4, 2026
4ef4570
Merge branch 'main' into ag/independent-source-refresh
jaywedgeworth22 Oct 4, 2026
7f30b4c
Identify the platform in settings history and clarify interval values.
jaywedgeworth22 Oct 4, 2026
6c55832
Merge remote-tracking branch 'origin/main' into codex/independent-sou…
jaywedgeworth22 Oct 4, 2026
6eb7166
docs: mirror PR #142 effort row in paired effort ledgers
cursoragent Oct 5, 2026
fe4b872
fix: load usage history samples on appear, not in view init
cursoragent Oct 5, 2026
008b5c7
docs: align platform usage history validation with CI review rule
cursoragent Oct 5, 2026
82dc236
fix: restore explicit UsageHistoryView init and add pre-work claim re…
jaywedgeworth22 Oct 5, 2026
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
11 changes: 11 additions & 0 deletions EFFORT-LOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CodeCaps — Effort Log

## 2026-10-03 — Independent Provider and File Refresh [CODEX, PR #142]

repo: CodeCaps; pre-work claim: posted to #agent-sync after reading AGENT-SYNC.md; Lane: `codex/independent-source-refresh`; Board `4bcf84f1`; GitHub #137; PR #142 open on this branch (depends on #139; refs #136).

- Added `CodexSessionQuotaReader` for bounded passive Codex session JSONL reads with exact account identity, incremental append tracking, symlink-safe paths, and original event timestamps. Unchanged files do not invoke provider, Fleet, upload, or download work.
- Split Settings → Sources & Fleet into independent **Provider Checks** and **Codex Session File Checks** toggles with separate 1-, 3-, 5-, and 15-minute intervals (five-minute provider default, one-minute file default). Manual refresh runs both enabled paths; disabling one source preserves the other when possible.
- Integrated scheduling, merge rules, and cancellation in `MonitorModel` / `SourceRefreshSettings`; documented behavior in `docs/REFRESH.md`.
- Verification: `CodexSessionQuotaReaderTests`, `SourceRefreshTests`, and related refresh tests on the branch; hosted Swift CI green. Native Mac UI claims rely on code review and CI fixture rendering in `DocsScreenshotTests`, not supplied or manually captured screenshots.

---

## 2026-10-03 — Audit 9 Residual Reconciliation [CODEX, in progress]

Lane: `codex/audit-residuals`. Board `1ac04ba99f13478da0564f802d3af3e8`; GitHub #19.
Expand Down
1 change: 0 additions & 1 deletion Sources/CodeCaps/ConsoleViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ final class ConsoleState: ObservableObject {
unavailableAlert = nil
pendingAlert = nil
}

func clearHistoryFocus() {
selectedWindowId = nil
selectedTimestamp = nil
Expand Down
321 changes: 290 additions & 31 deletions Sources/CodeCaps/MonitorModel.swift

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions Sources/CodeCaps/SettingsViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,24 @@ struct SettingsSourcesFleetPage: View {

Toggle("Read Quotas From This Mac",
isOn: Binding(get: { model.localEnabled }, set: { model.setLocalEnabled($0) }))
Toggle("Provider Checks", isOn: Binding(
get: { model.providerChecksEnabled }, set: { model.setProviderChecksEnabled($0) }))
.disabled(!model.localEnabled)
Picker("Provider Check Interval", selection: $model.providerCheckCadence) {
ForEach(SourceRefreshCadence.allCases) { cadence in
Text(cadence.title).tag(cadence)
}
}
.disabled(!model.localEnabled || !model.providerChecksEnabled)
Toggle("Codex Session File Checks", isOn: Binding(
get: { model.sessionFileChecksEnabled }, set: { model.setSessionFileChecksEnabled($0) }))
.disabled(!model.localEnabled)
Picker("Session File Check Interval", selection: $model.sessionFileCadence) {
ForEach(SourceRefreshCadence.allCases) { cadence in
Text(cadence.title).tag(cadence)
}
}
.disabled(!model.localEnabled || !model.sessionFileChecksEnabled)
ForEach(ReaderStatus.all, id: \.providerKey) { reader in
readerRow(reader)
}
Expand All @@ -304,6 +322,10 @@ struct SettingsSourcesFleetPage: View {
VStack(alignment: .leading, spacing: 4) {
Text("CodeCaps reads each CLI's own saved credentials in place." + sentenceGap
+ "It never asks you for a provider API key.")
Text("Provider Checks: 7 HTTP paths and 3 local helpers across 8 AI plan families." + sentenceGap
+ "These are source capabilities, not a request count per check.")
Text("Codex Session File Checks: 1 local quota source." + sentenceGap
+ "File checks do not upload or download on their own.")
Text("A snapshot is written to ~/Library/Application Support/Usage Monitor/quota-windows.json for BotFleet.")
if let widgetSharingError = model.widgetSharingError {
Text(widgetSharingError).foregroundStyle(Theme.warning)
Expand Down Expand Up @@ -1136,6 +1158,15 @@ struct SettingsNotificationsPage: View {

var body: some View {
SettingsPage {
if let row = model.displaySections.first(where: {
$0.providerKey == model.runawayAlertHistory.first?.providerKey
}) ?? model.displaySections.first {
Section {
UsageHistoryView(model: model, state: state, row: row)
} header: {
Eyebrow("RECENT USAGE HISTORY")
}
}
Section {
Toggle("Reset Alarms For All Providers", isOn: $model.alarmsAll)
.help("The same switch as the All bell at the top of the Docked Bar.")
Expand Down
28 changes: 28 additions & 0 deletions Sources/CodeCaps/SourceRefreshSettings.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

enum SourceRefreshCadence: Int, CaseIterable, Identifiable {
case one = 1
case three = 3
case five = 5
case fifteen = 15

var id: Int { rawValue }
var seconds: TimeInterval { TimeInterval(rawValue * 60) }
var title: String { "Every \(rawValue) minute\(rawValue == 1 ? "" : "s")" }
}

enum SourceRefreshPreference {
static let providerEnabled = "providerChecksEnabled"
static let sessionEnabled = "sessionFileChecksEnabled"
static let providerMinutes = "providerCheckMinutes"
static let sessionMinutes = "sessionFileCheckMinutes"

static func enabled(_ key: String, defaults: UserDefaults) -> Bool {
defaults.object(forKey: key) as? Bool ?? true
}

static func cadence(_ key: String, fallback: SourceRefreshCadence,
defaults: UserDefaults) -> SourceRefreshCadence {
SourceRefreshCadence(rawValue: defaults.integer(forKey: key)) ?? fallback
}
}
4 changes: 1 addition & 3 deletions Sources/CodeCaps/UsageHistoryViews.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ struct UsageHistoryView: View {
self.model = model
self.state = state
self.row = row
// Offscreen AppKit snapshots can draw before SwiftUI calls onAppear.
_samples = State(initialValue: model.historySamples())
}

private var now: Date { model.now }
Expand Down Expand Up @@ -128,7 +126,7 @@ struct UsageHistoryView: View {
VStack(alignment: .leading, spacing: 2) {
Text("Usage History")
.font(.system(size: 16, weight: .semibold))
Text("Quota remaining · local readings")
Text("\(row.title) · Quota remaining · local readings")
.font(.system(size: 11))
.foregroundStyle(.secondary)
}
Expand Down
36 changes: 30 additions & 6 deletions Sources/QuotaCore/AnomalyDetector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,13 @@ public struct AnomalyDetector: Sendable {
encoder.dateEncodingStrategy = .iso8601
encoder.outputFormatting = [.withoutEscapingSlashes]
let fm = FileManager.default
let existing = try load()
var seen = Set(existing.map {
SampleKey(pair: PairKey(provider: $0.providerKey, window: $0.windowId), time: $0.observedAt)
})
// Dedup against a tail read only: decoding the whole file here ran
// a multi-megabyte parse on the main thread on every recorded
// refresh. Duplicates can only come from re-appending a recent
// batch (identical observedAt timestamps), so the tail is
// sufficient; load() dedups by key on read anyway, making a missed
// older duplicate harmless.
var seen = tailSampleKeys(maxBytes: 64 * 1024)
let fresh = samples.filter { sample in
guard sample.observedAt.timeIntervalSinceReferenceDate.isFinite,
let percent = sample.remainingPercent,
Expand Down Expand Up @@ -285,9 +288,30 @@ public struct AnomalyDetector: Sendable {
}
}

/// SampleKeys decoded from the last `maxBytes` of the history file, for
/// append-time dedup without a full parse. The first line of the
/// slice may be cut mid-line and is skipped.
private func tailSampleKeys(maxBytes: Int) -> Set<SampleKey> {
guard let full = try? Data(contentsOf: url, options: .mappedIfSafe), !full.isEmpty else { return [] }
let sliced = full.count > maxBytes
let tail: Data = sliced ? full.suffix(maxBytes) : full
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
var seen = Set<SampleKey>()
var lines = tail.split(separator: 0x0A, omittingEmptySubsequences: true)
if sliced { lines = Array(lines.dropFirst()) }
for line in lines {
if let sample = try? decoder.decode(Sample.self, from: Data(line)) {
seen.insert(SampleKey(pair: PairKey(provider: sample.providerKey,
window: sample.windowId),
time: sample.observedAt))
}
}
return seen
}

/// Load every sample currently on disk.
public func load() throws -> [Sample] {
guard FileManager.default.fileExists(atPath: url.path) else { return [] }
public func load() throws -> [Sample] { guard FileManager.default.fileExists(atPath: url.path) else { return [] }
let data = try Data(contentsOf: url)
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
Expand Down
Loading
Loading