Skip to content

Add Hermes, Antigravity, Z.ai, Ollama Cloud, MiniMax, OpenRouter, and DeepSeek to the agents panel - #14039

Open
dhh wants to merge 19 commits into
quattrofrom
agents-hermes-antigravity-providers
Open

dhh wants to merge 19 commits into
quattrofrom
agents-hermes-antigravity-providers

Conversation

@dhh

@dhh dhh commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Brings in seven community collectors for the agents panel, each as its author's commit followed by adaptations to the current panel and record contract.

Agent From Shows
Hermes #13505 (Mena, with Scott Cashman), profiles from #6647 (@okurmustafa) Local stats from state.db, now honoring HERMES_HOME and every named profile
Antigravity (agy) #11989 (@Steph4n) Quota and plan from Google Cloud Code's loadCodeAssist / retrieveUserQuotaSummary, with the token from the keyring
Z.ai GLM Coding Plan #7566 (@FelipeMayerDev) 5-hour and weekly meters and tier, plus pi/omp sessions on glm-*
Ollama Cloud #10172 (Dante Loi) 5-hour and weekly fractions and tier
MiniMax Token Plan #9597 (Michael D) 5-hour and weekly windows
OpenRouter #10824 (@Azonnali) Prepaid balance and key-limit meter, with history from opencode or the /activity API
DeepSeek #10683 (Alexander Holbreich) Prepaid balance

Adaptations

  • Every collector with limits keeps its last good limits through a failed check and marks them limitsStale/limitsFetchedAt, so the panel dims them with their age, like Claude and Grok. Cached windows past their reset are dropped, and day-bound caches are only reused on their day.
  • A provider the machine doesn't use produces a record the panel skips (DeepSeek and MiniMax previously always showed).
  • Antigravity: id agy, matching the default-agent id; dropped the limits invented from prompt counts, the assumed plan, the made-up token counts, and its Panel.qml change.
  • Ollama Cloud: dropped the computed reset times, since the endpoint returns none and the alignment was a guess.
  • MiniMax: collector only; dropped the mmx install, the Setup menu entry, and the migration.
  • OpenRouter: 2-space indentation, limit_reset labels the meter's period instead of posing as a reset time, read timeouts fall back to local stats and the last meter, and a management key alone is enough for history and balance.
  • Hermes: 2-space indentation, recentDays carries tokens like the other collectors, and the --write mode is gone.
  • The manifest description no longer lists every agent by name.

Not verified

The collectors were only run against fakes and with --help, never against the real services or anyone's credentials. The Antigravity endpoint and keyring entry, and the Ollama, MiniMax, Z.ai, DeepSeek, and OpenRouter response shapes, come from the original PRs. The panel wasn't checked visually with these records.

Tests

Each collector's tests, plus new ones for stale limits, skipped records, HERMES_HOME and profiles, OpenRouter timeouts and management-only keys, Ollama's day-bound cache, and MiniMax's reset windows. All agent tests and ./test/cli pass, apart from vscode generated theme references current theme file, which fails the same way on quattro. Reviewed with codex review --base quattro; its four findings are fixed.

🤖 Generated with Claude Code

mbot11 and others added 19 commits October 1, 2026 21:52
Co-authored-by: Scott Cashman <scottc2789@gmail.com>
Profile discovery follows #6647 by Mustafa Okur. Drops the --write mode,
reports the week's tokens by day like the other collectors, and lists
Hermes in the panel's defaults, README, and the manual.

Co-authored-by: Mustafa Okur <mustafaokur2005@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the omarchy-agent-usage-antigravity collector to monitor Google
Antigravity usage alongside the other agents in the agents bar panel.

Authoritative subscription tier and quota limits (rolling 5-hour session,
weekly limits, and scoped 3P model allowances) are probed from the Google
Cloud Code API via Secret Service keyring credentials with rolling local
history estimation as fallback.
The record shares the id omarchy-default-agent and omarchy-agent use for
the CLI. Limits now come only from Google's quota answer, kept from the
last good check and marked stale (limitsStale, limitsFetchedAt) after a
failed one, instead of being estimated from prompt counts. The plan is
no longer assumed to be Pro, and transcript steps without reported usage
no longer get estimated tokens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Felipe Mayer <mayer.felipe@outlook.com>
Limits kept from an earlier check after a failed one now carry
limitsStale and limitsFetchedAt, so the panel dims them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ship omarchy-agent-usage-ollama, which reads the account's 5-hour
session and 7-day weekly limits from Ollama's usage endpoint and local
token stats from pi and omp sessions on the ollama-cloud provider.
Ollama's usage endpoint names no reset times, and the epoch-aligned
windows the collector computed were an unproven guess, so the limits now
carry none. Limits kept after a failed check carry limitsStale and
limitsFetchedAt, a record without local stats no longer claims some, and
the manual mentions Ollama Cloud.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The collector part of #9597: the Token Plan's 5-hour and weekly quota
from MiniMax's API, with the key from MINIMAX_API_KEY, mmx, or opencode.
A failed check now keeps the last good limits, marked with limitsStale
and limitsFetchedAt, instead of dropping them. Without a key the record
has no plan, so the panel skips it. Help text no longer points at the
mmx CLI, which Omarchy doesn't install, and the manual mentions MiniMax.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed /key lookup no longer throws away the token stats and balance:
the last good key-limit meter stays, marked with limitsStale and
limitsFetchedAt. limit_reset names a period (daily, weekly, monthly),
not a time, so it labels the meter instead of filling resetsAt. Without
a key the record has no plan, so the panel skips it. The manual mentions
OpenRouter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Counts shared DeepSeek days once and keeps refreshes quiet.
The record claimed to be ready with a Prepaid plan for everyone, which
put a DeepSeek section in every panel. Ready and the plan now follow
the key. The test no longer reaches DeepSeek when DEEPSEEK_API_KEY is
set, and the manual mentions DeepSeek.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A server stalling on headers or body raised TimeoutError past the
OpenRouterError handlers, so the record went blank instead of keeping
its stats and last key-limit meter and asking for a retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A management key without an inference key returned an empty record
before /activity was asked. Now either key collects: the management key
alone brings the 30-day history and the balance, with no key-limit
meter, since that belongs to an inference key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The cached scan was reused by age alone, so just after midnight
yesterday's tokens showed as today's, and a changed modelPrefix kept
showing excluded models for up to 15 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
During an outage the fallback republished cached windows even after they
had reset, so an exhausted session could stay on screen. Windows past
their reset are now dropped; open ones and those without a reset time
stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 2/5

[Medium risk] Adds seven new AI agent collectors to the agents panel.

The PR is not ready to merge while account switches can show the wrong quotas and Hermes errors can corrupt displayed totals.

Findings

  1. P1 Limits leak across accounts ▶
  2. P1 Failed scans leave partial totals ▶
  3. P1 Expired quotas remain visible ▶
  4. P2 Security Activity caches become world-readable ▶

Summary

The PR adds seven usage collectors, enables their records in the agents panel, and documents their credentials, limits, and local-history sources.

  • Account-independent limits caches can display another account’s quota.
  • Hermes can publish partial totals after a database error.
  • Quotas without reset times can remain visible past their windows during an outage.
  • Some new activity caches are made world-readable.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Seven collectors] --> B[Local sessions and provider APIs]
  B --> C[Usage records and limits caches]
  C --> D[usage-update]
  D --> E[Agents panel]
  C --> F[Optional synced statistics]
  F --> E
Loading

Reviews (1) · Last reviewed commit: "Drop cached MiniMax windows that have re..."

Comment on lines +227 to +229
if kept and not force and time.time() - fetched_at / 1000 < PROBE_REUSE_SECONDS:
result.update(limits=kept, live=True)
return result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Limits leak across accounts If a user switches Antigravity accounts and opens the panel within 60 seconds, this code shows the previous account’s quota and plan as fresh without checking the new account. Z.ai and Ollama also reuse limits across keys; MiniMax and OpenRouter can show another account’s limits when a probe fails. The cache needs to distinguish the credential or account that produced each result.

Comment on lines +103 to +108
for path in paths:
try:
scan(path, totals, today, week)
read += 1
except sqlite3.Error:
status = "Hermes state unreadable"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Failed scans leave partial totals If a Hermes profile database fails a query after earlier queries have added counts, the error is caught but those counts remain. When another profile scans successfully, the panel receives a mix of complete and partial results, so its session and prompt totals are wrong. Merge a profile’s counts only after its entire scan succeeds.

Comment on lines +385 to +386
if fallback:
result["limits"] = fallback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Expired quotas remain visible If Ollama’s usage endpoint stays unavailable past a five-hour or weekly rollover, this fallback keeps showing the previous window’s percentage because the cached limits have no reset time or age limit. The panel may display that old quota indefinitely, even though it is dimmed. OpenRouter’s cached key-limit meter has the same problem. Bound how long these cached meters can stand in for current limits.

Comment on lines +151 to +153
# mkstemp opens at 0600; these caches were world-readable before.
tmp.chmod(0o644)
tmp.replace(path)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security Activity caches become world-readable This changes the Ollama session cache from owner-only mode to 0644; the Z.ai and OpenRouter cache writers do the same. If the home and cache directories are traversable, another local user can read cached model names, activity dates, and token counts. Keeping these usage caches owner-readable avoids that disclosure. How this was verified: The session-statistics writer stores activity summaries in a cache file and explicitly changes that file’s mode to 0644.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for bringing this in, @dhh!

This is specifically about bin/omarchy-agent-usage-agy, carried over from my #11989. Some later fixes didn’t make it into this version.

My final revision, f008cf6, uses version-gated CLI quota checks, removes quota caching to avoid cross-account reuse, preserves unused Claude/GPT allowances, and merges history/transcript prompt counts.

Could you carry over the final bin/omarchy-agent-usage-antigravity implementation and its regression tests, adapting the collector id to agy?

That should also address the Antigravity portion of Greptile’s account-switch finding.

@ChaseC-130

Copy link
Copy Markdown

Heads up on bin/omarchy-agent-usage-agy line 85:

Running the collector against real ~/.gemini/antigravity-cli/ data currently crashes on startup in any timezone west of UTC (negative offset):

Traceback (most recent call last):
  File "/usr/lib/omarchy/bin/omarchy-agent-usage-agy", line 399, in <module>
  File "/usr/lib/omarchy/bin/omarchy-agent-usage-agy", line 394, in main
  File "/usr/lib/omarchy/bin/omarchy-agent-usage-agy", line 360, in cached_local_stats
  File "/usr/lib/omarchy/bin/omarchy-agent-usage-agy", line 305, in local_stats
  File "/usr/lib/omarchy/bin/omarchy-agent-usage-agy", line 85, in local_day
OverflowError: date value out of range

Root Cause

Antigravity's conversation_summaries.db records Go zero timestamps (time.Time{} serializes to '0001-01-01 00:00:00+00:00') for uninitialized summaries.

In Python:

parsed = datetime.fromisoformat('0001-01-01 00:00:00+00:00')
parsed.astimezone()  # In UTC-5 (or any negative offset), rolls into year 0000 -> OverflowError

Recommended Fix

In local_day (or parse_time), catch OverflowError or filter out uninitialized timestamps:

def local_day(value):
  try:
    parsed = parse_time(value)
    if not parsed or parsed.year < 1970:
      return ""
    return parsed.astimezone().date().isoformat()
  except (ValueError, OverflowError):
    return ""

@jandrusk

jandrusk commented Oct 6, 2026

Copy link
Copy Markdown

+1 to @ChaseC-130's report on omarchy-agent-usage-agy. I can reproduce it with plain Python in any zone west of UTC:

$ TZ=America/New_York python3 -c "from datetime import datetime; datetime.fromisoformat('0001-01-01 00:00:00+00:00').astimezone()"
OverflowError: date value out of range

Nothing catches the error between local_day() and main(), so one Go zero-time row in conversation_summaries.db takes down the whole collector. That makes this a blocker for most US users. A minimal guard:

def local_day(value):
  parsed = parse_time(value)
  if not parsed or parsed.year < 1970:
    return ""
  try:
    return parsed.astimezone().date().isoformat()
  except (OverflowError, ValueError, OSError):
    return ""

A smaller point: the Ollama, Z.ai and OpenRouter cache writers chmod 0o644 after mkstemp. The comment says this matches the old behavior, but these hold model names and activity dates, and 0600 (the mkstemp default) seems the safer default unless something outside the user needs to read them.

Also +1 to @steph4n-gh's request to carry over the final Antigravity revision (f008cf6). It drops the quota cache, which also covers the Antigravity part of Greptile's account-switch finding.

@ivanvan08

Copy link
Copy Markdown

The Antigravity part of this PR is split out in #14704, rebased onto current quattro, keeping steph4n's and your commits with the conflicts in the README, manifest, and manual resolved. It includes the fix for the crash @ChaseC-130 and @jandrusk reported above (Go's year-0001 zero time west of UTC, from #14098, covered in an EST5 test), so it can land on its own or be cherry-picked back here. On top of that, it reads the Antigravity sign-in omp, pi, and opencode keep, so the panel shows the plan and limits without agy installed.

@JohnNiang JohnNiang Oct 20, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.