Commit graph

351 commits

Author SHA1 Message Date
iamtoruk
f0f0ed8874 fix(menubar): keep the wordmark filled under the shimmer and slow the sweep
The letters were only visible through the animated gradient's mask, so any
frame where the mask or gradient was not laid out yet blanked the name at
random. The text now carries a static flame fill under the sweep, which can
only brighten it, and the sweep runs 4 s each way instead of 1.5 s.
2026-09-02 10:34:11 -07:00
iamtoruk
58c6cce6db fix(menubar): the selection fallback showed another provider's totals under the selected tab
Switching providers stashed the outgoing payload and rendered it under the new
tab until that tab's own fetch landed. That is not continuity, it is a wrong
number: opening the Hermes tab printed the all-provider total, and the test
suite pinned that behaviour by asserting $12 under Hermes and $12.34 under
Cursor.

The fallback is gone. With no cached data for the selection, `payload` is
empty, `hasCachedData` is false, and the popover renders the loading state it
already has for a cold key, so no dollar figure is shown for a provider whose
data has not arrived. AgentTabStrip and MenuBarContent take the same
hasCachedData gate for the same reason.
2026-09-02 07:55:43 -07:00
iamtoruk
cd7de30bc4 fix(menubar): a twice-contradicting scoped payload threw instead of being served
When a scoped payload disagreed with its all-provider slice, the code re-ran
the fetch bypassing the resident child and, if it STILL disagreed, threw. That
verified payload is what the CLI actually reports for the provider, so
throwing traded a real number for an error banner and left the tab with
nothing at all.

The payload is now cached with a contradictsAll flag on CachedPayload
(add-only) and served, and the popover carries one subdued secondary line
saying the total may be incomplete. The refresh paths treat an
already-verified contradiction as usable, so a poll cannot re-run the one-shot
parse forever.
2026-09-02 07:54:55 -07:00
iamtoruk
68f6d1ced3 fix(menubar): the all-provider and scoped fetches were serialized on every tab click
To guarantee the all-provider slice was cached before a scoped result could be
accepted, the code simply awaited the all-provider fetch BEFORE starting the
scoped one. That is two full parses back to back on every tab click, and the
ordering it bought is only needed at acceptance time, not at start time.

Both fetches now start together. fetchPayload awaits the all-provider task
after its own fetch has already returned and before the consistency check
reads it, so the evidence is still present when the scoped result is judged.

The resident-bypassing cold fetch was already conditional on a contradiction
actually being detected; it is unchanged.
2026-09-02 07:54:03 -07:00
iamtoruk
6eab24960c fix(menubar): an evicted in-flight fetch cleared its successor's slot
inFlightKeys held only a timestamp, so the slot had no notion of WHICH fetch
owned it. The watchdog and the display-sleep reset both evict an occupant, and
a later fetch then legitimately claims the same key. When the evicted fetch
finally returned, its defer cleared the new owner's slot and resumed every
task parked on it, so a waiter woke on an empty cache and reported failure
while the real fetch was still running.

The slot now carries a token. A fetch releases it only if it still owns it;
the watchdog and the resets take an explicit force path, because they are
declaring the slot dead rather than reporting a fetch that ended.
2026-09-02 07:53:03 -07:00
iamtoruk
216ead74ae fix(menubar): refresh(key:) reported failure instead of joining an in-flight fetch
The interactive refresh path bailed with false whenever the key was already
being fetched. recoverFromStuckLoading() reads that return value, so a healthy
fetch that was simply still running was announced as a failed recovery, and a
second interactive refresh threw away a result that was moments from landing.

Join the in-flight fetch and report on its outcome, which is what the quiet
path already did.

Adds a DataClient fetch hook under DEBUG: slot ownership and acceptance
ordering are only observable when two REAL fetches overlap, which needs a
fetch whose completion the test controls rather than a spawned CLI.
2026-09-02 07:52:18 -07:00
iamtoruk
b0ee9cc7d4 Merge branch 'wt1234' into wt1235 2026-09-02 07:33:03 -07:00
iamtoruk
9c432bb105 fix(menubar,app): absent hasUsage hid every provider with no period spend
hasUsage is a new payload field, so every RELEASED CLI omits it. Both decoders
treated the absent case as "derive from cost", which is the strict reading of
a field that is not there: a provider with $0 spend in the period vanished
from the picker even though the user has it and is actively using it on an
included subscription.

Absent now means visible on both surfaces. The strict signal still applies
wherever the CLI actually emits the field, so an explicit false still hides an
idle discovery row. Settings.tsx carried the same fallback and is fixed with
it.
2026-09-02 07:32:02 -07:00
iamtoruk
6fa5dfbeb7 Merge remote-tracking branch 'origin/main' into wt1234 2026-09-02 07:06:47 -07:00
Aditya Vikram Singh
c9c5bc00f8 join provider evidence refreshes 2026-09-02 19:16:08 +05:30
Aditya Vikram Singh
5ef7387a33 close provider reconciliation races 2026-09-02 19:16:08 +05:30
Aditya Vikram Singh
e9a55bb2f8 prevent provider-scoped false zeros 2026-09-02 19:16:08 +05:30
iamtoruk
53c5c7b370 feat(dock): glance popover with hero window, sessions and today strip
The Capacity Dock hover panel becomes a glance: a provider header, the
sessions running right now as pills with their context rings, a Today
strip, and a row of quota-window percentages, separated by hairlines.
Window percentages stay plain text until 70 percent so a healthy row
reads as text rather than a wall of colour. The panel stays 350pt wide;
every block height is a whole point and is summed into the panel frame,
since the hosting view does not size itself.

Running sessions are new CLI surface. `liveSessions` is add-only on the
menubar payload: sessions whose transcript was appended inside a 120s
window, carrying the branch, the model that answered last, and the
context that turn reported. Absence means "unknown", so the section
hides rather than claiming nothing is running. The block names projects
and branches, so sharing and MCP output strip it.

Today's totals needed no CLI change; the app simply was not decoding the
cache token counts the payload already carried.
2026-09-02 06:09:06 -07:00
Aditya Vikram Singh
38e6e19f29 harden provider period and cache contracts 2026-09-02 16:18:05 +05:30
Aditya Vikram Singh
7106db2c0e fix menubar provider usage contracts 2026-09-02 16:17:28 +05:30
Aditya Vikram Singh
3664152d37 feat(mac): add native Grok Build provider 2026-09-01 18:46:02 +05:30
Resham Joshi
043ffd4822
Merge pull request #1214 from MiguelMachado-dev/feat/zero-cost-provider-visibility
feat(menubar): keep token-only and flat-rate providers visible in agent tabs
2026-09-01 05:08:32 -07:00
Resham Joshi
fecbcd6e1c
Merge pull request #1212 from MiguelMachado-dev/fix/menubar-presence-deadlock
fix(mac): avoid provider presence deadlock
2026-09-01 04:58:38 -07:00
iamtoruk
c436787239 fix(mac): document the main-queue serialization and harden the deadlock test cleanup
The lock no longer covers the defaults write, so the main-queue hop is
what keeps the read-modify-write atomic; say so where the next reader
would be tempted to make it async. The regression test now cleans up its
observer and defaults domain on every exit and waits up to 5 seconds, so
a loaded runner cannot fake a deadlock.
2026-09-01 04:58:16 -07:00
Aditya Vikram Singh
5fee00ab9c Fix Capacity Dock pointer coalescing and dismissal 2026-09-01 16:51:00 +05:30
iamtoruk
fdd18e7bb4 fix(menubar): rebuild the popover content on every open and drop it on close
A popover content view that outlived its window kept re-laying itself out
at display cadence once the popover had been shown during the first load
(loading overlay 520pt tall, then the real height): 6 to 7 percent idle
CPU until relaunch, reproduced on demand. Limiting the hosting controller
to preferredContentSize was not enough on its own. Building the 360pt view
fresh per open and releasing it on close removes the survivor entirely;
the dock's hidden bubble panel gets the same treatment.

Reproduction on the same machine (minute logs, popover opened during the
first load and closed): before 6.1 to 7.1 percent sustained; after 0.1
percent sustained through the data load.
2026-09-01 02:49:01 -07:00
iamtoruk
80330ca03e fix(menubar): round the docked flare padding so the rail length stays integral
At 60 percent the docked pad was 12 + 18.6 points, putting the rail body at
111.2 against a 111 point panel; the same fractional mismatch as #1210,
hidden inside the attachment compensation instead of the base metrics.
2026-09-01 02:00:01 -07:00
iamtoruk
926fe640ba fix(menubar): round the Capacity Dock bubble height to whole points
The detail panel is created on the first hover and kept alive hidden, and
its height was the one dock dimension still fractional (186 at 60 percent
is 111.6), so from the first hover on it re-laid itself out at display
cadence forever: 0.1 percent idle CPU before the hover, 5.7 to 6.1 percent
after, measured on the release build. Same bug as #1210, second window.
2026-09-01 01:43:56 -07:00
Miguel Machado
fee0f63fd0 feat(menubar): keep token-only and flat-rate providers visible in agent tabs
Agent tabs filtered strictly on cost > 0, so a provider whose sessions
carry tokens but no recorded dollar cost (Hermes on a flat subscription,
free-tier models, unpriced models) vanished from the strip entirely.

The menubar payload now carries an activity signal per provider, and the
strip keys visibility off it: providers with real usage stay listed, while
providers merely discovered on disk stay hidden. Older payloads without
the signal keep the previous detected-provider behavior.

Applies consistently across the mac menubar, the Electron renderer, and
the Windows payload types.
2026-08-31 22:43:13 -03:00
Miguel Machado
da20fd0f42 fix(mac): avoid provider presence deadlock
A background credential read held the provider-presence lock while UserDefaults synchronously notified SwiftUI. SwiftUI could re-read presence while holding its own update lock, leaving each thread waiting on the other. Serialize presence writes on the main queue and release the presence lock before publishing defaults changes.
2026-08-31 22:40:50 -03:00
iamtoruk
1a74f0d3ac fix(menubar): round Capacity Dock metrics to whole points to stop perpetual relayout
At any scale except 100% the dock's dimensions were fractional (85% of 88
is 74.8), so SwiftUI's fitted content never agreed with the pixel-aligned
panel frame and the hosting view re-laid itself out at display cadence
forever: 5 to 7 percent idle CPU and hundreds of wakeups a second once
quota data existed, on every release since the dock shipped. Measured on
the same machine: 60% and 85% scale held 5 to 6 percent for an hour;
100% scale idled at 0.1 to 0.2 percent. Rounding every scaled metric to a
whole point makes all scales behave like 100%.
2026-08-31 17:57:04 -07:00
iamtoruk
86f9d6c065 feat(menubar): default the Capacity Dock size to 60 percent 2026-08-31 10:56:59 -07:00
iamtoruk
6100fce4af feat(menubar): let the Capacity Dock size go down to 60 percent 2026-08-31 10:52:59 -07:00
Resham Joshi
764326438a
Merge pull request #1208 from getagentseal/fix/menubar-idle-render
fix(menubar): stop rendering the closed popover around the clock
2026-08-31 10:48:19 -07:00
iamtoruk
c19d5621e3 fix(menubar): stop rendering the closed popover around the clock
The status-item popover's hosting view is created once and lives for the
app's lifetime, and the flame wordmark inside it ran a repeat-forever
shimmer, so SwiftUI re-rendered the closed popover at display cadence
continuously: 5 to 7 percent idle CPU and roughly 650 wakeups a second
(Activity Monitor showed a 12-hour energy figure near two thirds of
Chrome's). The wordmark sweep and the loading flame pulse now start and
stop with popover visibility via a store flag set by the popover
delegate.
2026-08-31 10:46:37 -07:00
Miguel Machado
1416cdd4c1 fix(mac): stop Capacity Dock hover drift 2026-08-31 14:22:20 -03:00
Resham Joshi
f54329e686
Merge pull request #1204 from MiguelMachado-dev/fix/zai-body-auth-errors
fix(mac): map Z.ai body authentication errors
2026-08-31 09:59:49 -07:00
Resham Joshi
7b79a4ba5e
Merge pull request #1197 from getagentseal/fix/dock-hover-any-frontmost
fix(menubar): Capacity Dock hover works over any frontmost app
2026-08-31 09:56:46 -07:00
Miguel Machado
b39133941f fix(mac): map Z.ai body auth errors 2026-08-31 13:51:46 -03:00
Resham Joshi
0c71cdcf31
Merge pull request #1202 from MiguelMachado-dev/feat/zai-menubar-quota
feat(mac): add Z.ai Coding Plan quota
2026-08-31 09:44:15 -07:00
Miguel Machado
069a163e26 feat(mac): add Z.ai quota provider 2026-08-31 12:11:55 -03:00
iamtoruk
ac6cfc045f fix(quota): skip zero-entitlement and unlimited Copilot windows instead of showing 100% used
A Free or Individual plan reports premium_interactions with entitlement 0
and 0% remaining, which both the menubar and the desktop decoder turned
into a full 100% used ring. Verified against a live quota payload.
2026-08-31 03:26:19 -07:00
iamtoruk
c2524308bf fix(menubar): drop the copilot-cli Keychain rung from Copilot discovery (#1198)
The Copilot CLI's Keychain item is written by a Node keyring library, so
/usr/bin/security is not in its partition list. Unlike the Claude Code item,
every read would raise the macOS "allow access?" dialog, and with the probe
cache a user who clicks Deny would be asked again every five minutes.

`gh auth token` already reaches the same users, because the Copilot CLI itself
falls back to gh, so the rung was risk without benefit.

The chain is now legacy plugin files, ~/.copilot/config.json and settings.json,
the environment variables, `gh auth token`, and a pasted token. No `security`
subprocess remains in the adapter, and the probe cache collapses to the single
gh answer.
2026-08-31 02:09:44 -07:00
iamtoruk
7e37d33acc fix(menubar): keep the Copilot credential chain private to the adapter 2026-08-31 02:05:38 -07:00
iamtoruk
25c1218874 fix(menubar): find Copilot credentials from the Copilot CLI keychain, gh, env, and a pasted token (#1198)
Modern Copilot clients no longer write ~/.config/github-copilot/hosts.json or
apps.json, so the menubar reported "No GitHub Copilot credentials found" even
after signing in through VS Code and the Copilot CLI.

Discovery now walks an ordered read-only chain, first hit wins, every rung
tolerating an absent, locked, or malformed source: the legacy plugin files,
the Copilot CLI keychain item (service copilot-cli, read via /usr/bin/security
so no partition-list prompt can fire), ~/.copilot/config.json and
settings.json, COPILOT_GITHUB_TOKEN / GH_TOKEN / GITHUB_TOKEN, `gh auth token`,
and finally a token pasted into Settings. The two subprocess rungs are cached
for five minutes so eligibility checks and the 30s refresh cadence never spawn
per call, and hasCredential stays spawn-free.

VS Code's encrypted secret storage is deliberately untouched.

The no-credentials message now says usage tracking still works and names every
way to connect, and the Copilot settings pane gains a token field.
2026-08-31 02:05:03 -07:00
iamtoruk
286c1daf0d fix(menubar): poll the pointer so Capacity Dock hover works over any frontmost app
The #1170 hover synthesis rides on a global .mouseMoved monitor, but
macOS only delivers global mouse-moved events while the frontmost app
itself requests them. Chrome does, so hover appeared fixed there;
Finder and Terminal do not, so the dock went hover-dead over the
desktop and most native apps.

Add a 10 Hz pointer poll feeding the same passthrough + hover sync
path. A same-position diff guard makes idle ticks free, and the timer
lives and dies with the existing monitor lifecycle.
2026-08-30 15:12:20 -07:00
Resham Joshi
fcbe978206
Merge pull request #1172 from avs-io/codex/fix-claude-keychain-test-isolation
fix: isolate overridden Claude credential homes
2026-08-29 15:24:04 -07:00
iamtoruk
03b6cc4857 fix(menubar): harden the Cursor quota adapter
Require every Dependencies field explicitly so a test can never fall
through to the live adapter and read the local Cursor session. Surface
an unreadable local store as its own honest error instead of a network
failure. Read the state database off the main actor so a lock held by
Cursor cannot stall the UI. Report the tighter of the Auto and API
pools when Cursor omits the total, since averaging hides an exhausted
pool behind an idle one.
2026-08-29 14:17:15 -07:00
iamtoruk
1f1954f257 fix(menubar): disconnect drops the provider from the persisted dock selection
A credential-less adapter left in the dock selection was silently
reconnected by the next scheduled refresh, undoing the user's explicit
disconnect. Cursor is the first adapter that needs no saved credential,
so the disconnect intent now persists: the provider leaves the stored
selection without latching manual-selection mode.
2026-08-29 14:17:15 -07:00
Aditya Vikram Singh
bce49926c3 feat: add native Cursor quota connection 2026-08-29 23:48:42 +05:30
Aditya Vikram Singh
6ac0db4604 fix: isolate overridden Claude credential homes 2026-08-29 23:38:37 +05:30
Resham Joshi
91b4ad87b6
fix(menubar): keep Capacity Dock hover alive after app deactivation (#1170)
SwiftUI onHover rides on NSTrackingAreas that only deliver while the
app is active. The dock lives in a nonactivating panel inside an
accessory app, so the first click into another app killed every hover
interaction until relaunch.

Hover enter/exit is now synthesized in CapacityDockController from the
existing local and global mouse monitors, which observe the pointer
regardless of activation. Provider rows are resolved by inverting the
layoutDetail row-midpoint math (CapacityDockPlacement.providerRowIndex,
unit-tested), and the SwiftUI onHover plumbing is removed so the
controller is the single hover source. Row exits no longer cancel the
rail's pending collapse, and a drag drop re-derives hover so a
stationary pointer resting on a row still opens its bubble.
2026-08-29 18:42:27 +02:00
iamtoruk
5958294f67 fix(menubar): only list providers with a live quota adapter in settings
The Capacity Dock settings sidebar listed the whole 69-provider catalog, but
only 7 have a live CodeBurn quota adapter; the rest just read 'coming soon'.
Filter the settings provider list to live-adapter providers so users only see
what actually works.
2026-08-29 02:26:33 -07:00
Aditya Vikram Singh
84fea9d550
feat(menubar): add native Capacity Dock (#1167)
* feat(menubar): add native capacity dock

* fix(menubar): harden dock interaction boundaries

* feat(menubar): polish native Capacity Dock for launch

- Flush top-edge docking via NSHostingView safe-area opt-out; shielding
  window level so the rail necks into the physical top like the system notch
- Liquid Glass legibility scrim so labels stay readable on light wallpapers
- Weekly-else-monthly headline window; unified severity colour bands
  (green <50 / yellow / orange / red) across rings and detail bars
- Squircle gauge + graphite theme defaults; auto-show connected
  subscriptions (up to 5) until the user edits the set, then respect it
- Capacity Dock Settings menu item; re-centre the settings window; info
  glyph for the About row
- Trim dead provider-credential override fields down to apiKey only
- Reconcile and extend tests for all of the above

---------

Co-authored-by: iamtoruk <hello@agentseal.org>
2026-08-29 00:37:30 -07:00
Aditya Vikram Singh
daafb593ea
fix(menubar): recover parked status item (#1161) 2026-08-28 13:20:48 -07:00