Skip to content

Add Antigravity usage collector for the agents panel - #13553

Closed
atanas-chalakov wants to merge 7 commits into
omacom:quattrofrom
atanas-chalakov:add-antigravity-agent-usage
Closed

atanas-chalakov wants to merge 7 commits into
omacom:quattrofrom
atanas-chalakov:add-antigravity-agent-usage

Conversation

@atanas-chalakov

Copy link
Copy Markdown

Summary

Adds the omarchy-agent-usage-agy collector, icons, and test suite to enable tracking Antigravity (agy) in the Omarchy Agents panel.

Omarchy already supports agy as a default coding agent in omarchy-agent and omarchy-default-agent, but was missing a usage collector and brand marks in shell/plugins/agents/assets/.

Changes

  • bin/omarchy-agent-usage-agy: Implements the usage collector parsing prompt activity, sessions, and daily counts from ~/.gemini/antigravity-cli/history.jsonl and conversations/.
  • shell/plugins/agents/assets/agy.svg & agy-light.svg: Adds Antigravity SVGs for light and dark themes.
  • test/shell.d/agent-usage-agy-scanner-test.sh: Adds unit test suite testing collector schema version, empty state, prompt tracking, and session calculation.

Verification

  • Ran test/shell.d/agent-usage-agy-scanner-test.sh: all 8 assertions passed.
  • Ran test/shell.d/agent-usage-update-test.sh: all tests passed.
  • Verified on a live Omarchy desktop with active agy session logs.

@llstrk

llstrk commented Sep 29, 2026

Copy link
Copy Markdown

Automated AI review

Community review: Independent automated community review, unaffiliated with the Omarchy team, intended to help prepare PRs for their review.

Outcome: The collectors publish numbers their data does not support.

Quota meters without a quota

Both collectors add "Session Turns" and "Daily Quota" meters of prompts / 50; the files they read hold no quota. 45 prompts reach the 0.9 bar alarm.

Suggested change: return limits: [] and drop the tests' limits checks.

Reproducer (fails at 012b47e)

Invented quota meters (Antigravity; the Copilot collector uses the same code)

Test file test/shell.d/agent-usage-agy-limits-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

source "$(dirname "$0")/base-test.sh"

require_command jq
require_command python3

TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT

data_dir="$TEST_HOME/.gemini/antigravity-cli"
mkdir -p "$data_dir"

# 45 prompts today. Nothing in these files describes a quota.
now_ms=$(date +%s%3N)
for n in $(seq 45); do
  printf '{"display":"prompt %s","timestamp":%s,"workspace":"/home/user","conversationId":"c1"}\n' "$n" "$now_ms"
done >"$data_dir/history.jsonl"

result=$(HOME="$TEST_HOME" ANTIGRAVITY_DATA_DIR="$data_dir" "$ROOT/bin/omarchy-agent-usage-agy")
jq -c '.limits' <<<"$result"

# Panel.qml lights the bar icon (alarming) once any limit reaches 0.9.
jq -e '([.limits[].percent] | max // 0) < 0.9' <<<"$result" >/dev/null ||
  fail "Antigravity collector does not report a quota at the bar alarm threshold without quota data" "$(jq -c '.limits' <<<"$result")"
pass "Antigravity collector does not report a quota at the bar alarm threshold without quota data"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-usage-agy-limits-test.sh.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

[{"label":"Session","percent":0.9,"resetsAt":"","title":"Session Turns"},{"label":"Weekly (7-day)","percent":0.9,"resetsAt":"2026-09-30T00:00:00Z","title":"Daily Quota"}]
[{"label":"Session","percent":0.9,"resetsAt":"","title":"Session Turns"},{"label":"Weekly (7-day)","percent":0.9,"resetsAt":"2026-09-30T00:00:00Z","title":"Daily Quota"}]
not ok - Antigravity collector does not report a quota at the bar alarm threshold without quota data

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

[]
ok - Antigravity collector does not report a quota at the bar alarm threshold without quota data

Same test with TZ=America/Los_Angeles at 2026-09-28 17:54 local (2026-09-29 00:54 UTC): the daily reset is reported as 2026-09-29T00:00:00Z, which was 17:00 local and already past, so the panel shows no reset countdown:

[{"label":"Session","percent":0.9,"resetsAt":"","title":"Session Turns"},{"label":"Weekly (7-day)","percent":0.9,"resetsAt":"2026-09-29T00:00:00Z","title":"Daily Quota"}]
[{"label":"Session","percent":0.9,"resetsAt":"","title":"Session Turns"},{"label":"Weekly (7-day)","percent":0.9,"resetsAt":"2026-09-29T00:00:00Z","title":"Daily Quota"}]
not ok - Antigravity collector does not report a quota at the bar alarm threshold without quota data

shell/plugins/agents/Panel.qml turns the bar icon to its alarm state once any limit reaches 0.9 (alarming, line 47). The control patch left limits empty, as the Fireworks collector does when it has no real figure.

Copilot mixes UTC and local dates

Copilot's schema stamps rows in UTC; the collector matches substr(col, 1, 10) to the local date. When the dates differ, prompts land on the wrong day and the PR's test fails.

Suggested change: compare date(col, 'localtime') and test in a non-UTC TZ.

Reproducer (fails at 012b47e)

Copilot: UTC dates compared with the local date

Test file test/shell.d/agent-usage-copilot-timezone-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

source "$(dirname "$0")/base-test.sh"

require_command jq
require_command python3
require_command sqlite3

TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT

data_dir="$TEST_HOME/.copilot"
mkdir -p "$data_dir"

# Same tables and rows as agent-usage-copilot-scanner-test.sh. Copilot's own
# schema defaults these timestamps to datetime('now'), which is UTC.
sqlite3 "$data_dir/session-store.db" <<EOF
CREATE TABLE sessions (id TEXT PRIMARY KEY, cwd TEXT, repository TEXT, host_type TEXT, branch TEXT, summary TEXT, created_at TEXT, updated_at TEXT);
CREATE TABLE turns (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, turn_index INTEGER, user_message TEXT, assistant_response TEXT, timestamp TEXT);
CREATE TABLE assistant_usage_events (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, turn_index INTEGER, model TEXT, input_tokens INTEGER, output_tokens INTEGER, cache_read_tokens INTEGER, cache_write_tokens INTEGER, created_at TEXT);
INSERT INTO sessions VALUES ('s1', '/home/user/work', 'user/repo', 'github', 'main', 'Test feature', datetime('now'), datetime('now'));
INSERT INTO turns VALUES (1, 's1', 0, 'Hello world', 'Hi', datetime('now'));
INSERT INTO turns VALUES (2, 's1', 1, 'Second prompt', 'Sure', datetime('now'));
INSERT INTO assistant_usage_events (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, created_at)
  VALUES ('s1', 'gpt-5-mini', 100, 50, 0, 0, datetime('now'));
EOF

# Rows written a moment ago are "today" wherever the machine is. Pick a zone
# whose calendar date differs from UTC's right now: UTC+14 from 10:00 UTC,
# UTC-12 before then. (Etc/GMT-14 is UTC+14; the POSIX sign is inverted.)
if (( 10#$(date -u +%H) >= 10 )); then zone=Etc/GMT-14; else zone=Etc/GMT+12; fi
printf '# TZ=%s local=%s utc=%s\n' "$zone" "$(TZ=$zone date +%F)" "$(date -u +%F)"

result=$(TZ=$zone HOME="$TEST_HOME" COPILOT_HOME="$data_dir" "$ROOT/bin/omarchy-agent-usage-copilot")

[[ $(jq -r '.todayPrompts' <<<"$result") == "2" ]] ||
  fail "Copilot collector counts prompts from a moment ago as today in $zone" "$(jq -c '{todayPrompts, todaySessions, todayTotalTokens, recentDays}' <<<"$result")"
pass "Copilot collector counts prompts from a moment ago as today in $zone"

[[ $(jq -r '.recentDays[-1].messageCount' <<<"$result") == "150" ]] ||
  fail "Copilot collector puts tokens from a moment ago on today's row in $zone" "$(jq -c '.recentDays' <<<"$result")"
pass "Copilot collector puts tokens from a moment ago on today's row in $zone"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-usage-copilot-timezone-test.sh.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

# TZ=Etc/GMT+12 local=2026-09-28 utc=2026-09-29
{"todayPrompts":0,"todaySessions":0,"todayTotalTokens":0,"recentDays":[{"date":"2026-09-22","messageCount":0},{"date":"2026-09-23","messageCount":0},{"date":"2026-09-24","messageCount":0},{"date":"2026-09-25","messageCount":0},{"date":"2026-09-26","messageCount":0},{"date":"2026-09-27","messageCount":0},{"date":"2026-09-28","messageCount":0}]}
not ok - Copilot collector counts prompts from a moment ago as today in Etc/GMT+12

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

# TZ=Etc/GMT+12 local=2026-09-28 utc=2026-09-29
ok - Copilot collector counts prompts from a moment ago as today in Etc/GMT+12
ok - Copilot collector puts tokens from a moment ago on today's row in Etc/GMT+12

The PR's own test fails the same way when the local date is behind UTC. TZ=America/Los_Angeles bash test/shell.d/agent-usage-copilot-scanner-test.sh, run at local=2026-09-28 17:54:29 utc=2026-09-29 00:54:29:

ok - Copilot collector reports id copilot
ok - Copilot collector reports name GitHub Copilot
ok - Copilot collector handles empty state
ok - Copilot collector marks ready when prompts exist
ok - Copilot collector counts total prompts
{"activeDates":["2026-09-29"],"activeDays":1,"authHelpText":"","hasLocalStats":true,"hasPromptStats":true,"id":"copilot","limits":[{"label":"Session","percent":0.04,"resetsAt":"","title":"Session Turns"},{"label":"Weekly (7-day)","percent":0.02,"resetsAt":"2026-09-29T00:00:00Z","title":"Daily Quota"}],"modelUsage":{"gpt-5-mini":{"cacheCreationInputTokens":0,"cacheReadInputTokens":0,"inputTokens":100,"outputTokens":50}},"name":"GitHub Copilot","ready":true,"recentDays":[{"date":"2026-09-22","messageCount":0},{"date":"2026-09-23","messageCount":0},{"date":"2026-09-24","messageCount":0},{"date":"2026-09-25","messageCount":0},{"date":"2026-09-26","messageCount":0},{"date":"2026-09-27","messageCount":0},{"date":"2026-09-28","messageCount":0}],"schemaVersion":1,"tierLabel":"GitHub Copilot","todayPrompts":0,"todaySessions":0,"todayTokensByModel":{},"todayTotalTokens":0,"totalPrompts":2,"totalSessions":1,"updatedAt":"2026-09-29T00:54:29.696914+00:00","usageStatusText":"Current: Test feature"}
not ok - Copilot collector counts today prompts

Copilot CLI 1.0.89 creates these columns as TEXT DEFAULT (datetime('now')), which SQLite fills in UTC. The control patch replaced substr(<column>, 1, 10) with date(<column>, 'localtime') in the collector's queries.

Antigravity shows old estimates as today's tokens

Tokens are characters / 4 of the latest transcript, under a fixed gemini-3.8-flash. The panel shows it as today's usage even if that conversation is days old.

Suggested change: omit tokens until a real usage source exists.

Reproducer (fails at 012b47e)

Antigravity: estimated tokens of an old conversation reported as today's, and prompt counts in the tokens-by-day rows

Test file test/shell.d/agent-usage-agy-days-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

source "$(dirname "$0")/base-test.sh"

require_command jq
require_command python3

TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT

data_dir="$TEST_HOME/.gemini/antigravity-cli"
mkdir -p "$data_dir/conversations" "$data_dir/brain/old-conv/.system_generated/logs"

# Three prompts three days ago, nothing since.
old_ms=$(( ($(date +%s) - 3 * 86400) * 1000 ))
for n in 1 2 3; do
  printf '{"display":"prompt %s","timestamp":%s,"workspace":"/home/user","conversationId":"old-conv"}\n' "$n" "$old_ms"
done >"$data_dir/history.jsonl"

# That conversation is the latest one, last touched three days ago, with a
# 40,000-character model reply in its transcript.
python3 - "$data_dir" "$old_ms" <<'PY'
import json, sqlite3, sys
data_dir, old_ms = sys.argv[1], int(sys.argv[2])
db = sqlite3.connect(f"{data_dir}/conversation_summaries.db")
db.execute("CREATE TABLE conversation_summaries (conversation_id TEXT, title TEXT, step_count INTEGER, status TEXT, last_modified_time INTEGER)")
db.execute("INSERT INTO conversation_summaries VALUES ('old-conv', 'Refactor the parser', 6, 'IDLE', ?)", (old_ms,))
db.commit()
with open(f"{data_dir}/brain/old-conv/.system_generated/logs/transcript.jsonl", "w") as f:
  f.write(json.dumps({"source": "USER_EXPLICIT", "content": "x" * 400}) + "\n")
  f.write(json.dumps({"source": "MODEL", "content": "y" * 40000}) + "\n")
PY

result=$(HOME="$TEST_HOME" ANTIGRAVITY_DATA_DIR="$data_dir" "$ROOT/bin/omarchy-agent-usage-agy")
jq -c '{todayPrompts, todayTotalTokens, recentDays: [.recentDays[].messageCount], modelUsage, tierLabel, usageStatusText}' <<<"$result"

[[ $(jq -r '.todayPrompts' <<<"$result") == "0" ]] ||
  fail "fixture has no prompts today" "$result"
pass "fixture has no prompts today"

[[ $(jq -r '.todayTotalTokens' <<<"$result") == "0" ]] ||
  fail "Antigravity collector reports no tokens today when nothing ran today" "$(jq -c '{todayTotalTokens, todayTokensByModel}' <<<"$result")"
pass "Antigravity collector reports no tokens today when nothing ran today"

[[ $(jq -r '.recentDays[-1].messageCount' <<<"$result") == "0" ]] ||
  fail "Antigravity collector leaves today's row empty when nothing ran today" "$(jq -c '.recentDays' <<<"$result")"
pass "Antigravity collector leaves today's row empty when nothing ran today"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-usage-agy-days-test.sh.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

{"todayPrompts":0,"todayTotalTokens":10100,"recentDays":[0,0,0,3,0,0,10100],"modelUsage":{"gemini-3.8-flash":{"cacheCreationInputTokens":0,"cacheReadInputTokens":0,"inputTokens":100,"outputTokens":10000}},"tierLabel":"Gemini 3.8 Flash","usageStatusText":"Current: Refactor the parser"}
ok - fixture has no prompts today
{"todayTotalTokens":10100,"todayTokensByModel":{"gemini-3.8-flash":10100}}
not ok - Antigravity collector reports no tokens today when nothing ran today

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

{"todayPrompts":0,"todayTotalTokens":0,"recentDays":[0,0,0,3,0,0,0],"modelUsage":{"gemini-3.8-flash":{"cacheCreationInputTokens":0,"cacheReadInputTokens":0,"inputTokens":100,"outputTokens":10000}},"tierLabel":"Gemini 3.8 Flash","usageStatusText":"Current: Refactor the parser"}
ok - fixture has no prompts today
ok - Antigravity collector reports no tokens today when nothing ran today
ok - Antigravity collector leaves today's row empty when nothing ran today

The control patch stopped writing the transcript estimate into todayTotalTokens, todayTokensByModel and today's recentDays row. The earlier rows still carry prompt counts (3 above) under the panel's "TOKENS BY DAY" heading, which the control did not change.

More findings

omarchy agent --continue passes Codex an option it does not have

With Codex as the default agent, the launcher emits codex --approve-for-me --continue. Codex rust-v0.158.0 defines no --continue on the base command; resuming the latest session is codex resume --last, so Codex should exit with an unknown-argument error (inferred from source, Codex not run). For agents without a mapping, and for --plan with Codex, both flags are dropped silently, so --plan starts an ordinary auto-approving session.

Suggested change: map Codex to codex resume --last --approve-for-me and reject --continue/--plan with a message for agents that lack a mapping.

Launcher: --continue passed to Codex, and flags dropped for unmapped agents

Test file test/shell.d/agent-launch-continue-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

set -euo pipefail

source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"

test_tmp=$(mktemp -d)
trap 'rm -rf "$test_tmp"' EXIT

mock_bin="$test_tmp/bin"
launch_log="$test_tmp/launch"
mkdir -p "$mock_bin" "$test_tmp/home/.config/omarchy/defaults"

# Same stubs default-agent-test.sh uses: record the argv instead of launching.
cat >"$mock_bin/omarchy-launch-tui" <<'SH'
#!/bin/bash
printf '%s\0' "$@" >"$OMARCHY_TEST_AGENT_LAUNCH_LOG"
SH
cat >"$mock_bin/omarchy-cmd-missing" <<'SH'
#!/bin/bash
exit 1
SH
chmod +x "$mock_bin"/*

export HOME="$test_tmp/home" PATH="$mock_bin:$ROOT/bin:$PATH" OMARCHY_TEST_AGENT_LAUNCH_LOG="$launch_log"

launched() {
  printf '%s\n' "$1" >"$HOME/.config/omarchy/defaults/agent"
  shift
  omarchy-agent "$@"
  mapfile -d '' -t launch_args <"$launch_log"
  printf '%s\n' "${launch_args[*]:1}"
}

for agent in codex opencode pi; do
  printf '# %s --continue -> %s\n' "$agent" "$(launched "$agent" --continue)"
  printf '# %s --plan     -> %s\n' "$agent" "$(launched "$agent" --plan)"
done

# Codex has no --continue option; resuming the last session is `codex resume --last`.
[[ $(launched codex --continue) != *"--continue"* ]] ||
  fail "agent launcher passes Codex a resume request it accepts" "$(launched codex --continue)"
pass "agent launcher passes Codex a resume request it accepts"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-launch-continue-test.sh. It records the argv omarchy-agent hands to omarchy-launch-tui; it does not run Codex.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

# codex --continue -> codex --approve-for-me --continue
# codex --plan     -> codex --approve-for-me
# opencode --continue -> opencode --auto
# opencode --plan     -> opencode --auto
# pi --continue -> pi
# pi --plan     -> pi
codex --approve-for-me --continue
not ok - agent launcher passes Codex a resume request it accepts

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

# codex --continue -> codex resume --last --approve-for-me
# codex --plan     -> codex --approve-for-me
# opencode --continue -> opencode --auto
# opencode --plan     -> opencode --auto
# pi --continue -> pi
# pi --plan     -> pi
ok - agent launcher passes Codex a resume request it accepts

Not executed against Codex. The rejection comes from Codex's source at rust-v0.158.0 (released 2026-09-28): the base codex command is MultitoolCli in codex-rs/cli/src/main.rs, which flattens TuiCli (codex-rs/tui/src/cli.rs) and SharedCliOptions (codex-rs/utils/cli/src/shared_options.rs). None defines --continue; resuming the latest session is the resume subcommand with --last ("Continue the most recent session without showing the picker"), and --approve-for-me is accepted there through SessionTuiCli. The control patch replaced the Codex --continue line with command=(codex resume --last --approve-for-me).

Status text lands in the field reserved for problems

Both collectors set usageStatusText to "Current: <title>" or "N prompts today" whenever data exists. The panel shows that field in place of the plan line and opens its red-tinted status card whenever it is non-empty, here with an empty authHelpText (source only, UI not run).

Suggested change: leave usageStatusText empty when healthy.

Copilot model rows likely count cache tokens twice

Day totals are input_tokens + output_tokens, while each model row adds input, output, cache read and cache write. Copilot 1.0.89's own code treats input tokens as already including cache, so the model row can show far more tokens than the day row for the same request (stored semantics not checked against a live database).

Suggested change: subtract cache reads and writes from inputTokens, as omarchy-agent-usage-codex does.

Copilot: the day row and the model row disagree when cache tokens are present

Test file test/shell.d/agent-usage-copilot-cache-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

source "$(dirname "$0")/base-test.sh"

require_command jq
require_command python3
require_command sqlite3

TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT

data_dir="$TEST_HOME/.copilot"
mkdir -p "$data_dir"

# One request whose prompt was mostly served from cache. Local time strings so
# the day matches in any time zone.
now=$(date '+%F %T')
sqlite3 "$data_dir/session-store.db" <<EOF
CREATE TABLE sessions (id TEXT PRIMARY KEY, cwd TEXT, repository TEXT, host_type TEXT, branch TEXT, summary TEXT, created_at TEXT, updated_at TEXT);
CREATE TABLE turns (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, turn_index INTEGER, user_message TEXT, assistant_response TEXT, timestamp TEXT);
CREATE TABLE assistant_usage_events (id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT, turn_index INTEGER, model TEXT, input_tokens INTEGER, output_tokens INTEGER, cache_read_tokens INTEGER, cache_write_tokens INTEGER, created_at TEXT);
INSERT INTO sessions VALUES ('s1', '/w', 'u/r', 'github', 'main', 'Test', '$now', '$now');
INSERT INTO turns VALUES (1, 's1', 0, 'Hello', 'Hi', '$now');
INSERT INTO assistant_usage_events (session_id, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens, created_at)
  VALUES ('s1', 'claude-sonnet-4.6', 10000, 500, 9000, 0, '$now');
EOF

result=$(HOME="$TEST_HOME" COPILOT_HOME="$data_dir" "$ROOT/bin/omarchy-agent-usage-copilot")

# Panel.qml adds all four buckets for a model row; the day row is messageCount.
model_total=$(jq -r '.modelUsage["claude-sonnet-4.6"] | .inputTokens + .outputTokens + .cacheReadInputTokens + .cacheCreationInputTokens' <<<"$result")
day_total=$(jq -r '.recentDays[-1].messageCount' <<<"$result")
printf '# model row total=%s, today row=%s, todayTotalTokens=%s\n' "$model_total" "$day_total" "$(jq -r '.todayTotalTokens' <<<"$result")"

[[ $model_total == "$day_total" ]] ||
  fail "Copilot collector reports the same total for one request on the day row and the model row" "model=$model_total day=$day_total"
pass "Copilot collector reports the same total for one request on the day row and the model row"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-usage-copilot-cache-test.sh.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

# model row total=19500, today row=10500, todayTotalTokens=10500
model=19500 day=10500
not ok - Copilot collector reports the same total for one request on the day row and the model row

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

# model row total=10500, today row=10500, todayTotalTokens=10500
ok - Copilot collector reports the same total for one request on the day row and the model row

Why the input already contains the cache: Copilot CLI 1.0.89's bundled app.js derives the uncached input as inputTokens - cacheReadTokens - cacheWriteTokens when the per-type token details are missing, and reports a prompt-cache read ratio as cacheReadTokens / inputTokens. The assistant_usage_events columns mirror those event fields. This was not checked against a live session-store.db. The control patch subtracted cache reads and writes from inputTokens, as omarchy-agent-usage-codex already does for Codex.

Antigravity token figures mix units and change between runs

The tokens-by-day rows before today hold prompt counts (the 3 in the reproducer above), not tokens. Without conversation_summaries.db, the "active" conversation is list(conversation_ids)[-1] of a Python set, so the reported tokens change from run to run on identical data.

Suggested change: keep every day row in one unit, and pick the conversation deterministically (for example sorted(...)[-1]) or by its latest timestamp.

Antigravity: without conversation_summaries.db the "active" conversation is an arbitrary set element

Test file test/shell.d/agent-usage-agy-active-test.sh (same helpers and style as the PR's own tests):

#!/bin/bash

source "$(dirname "$0")/base-test.sh"

require_command jq
require_command python3

TEST_HOME=$(mktemp -d)
trap 'rm -rf "$TEST_HOME"' EXIT

data_dir="$TEST_HOME/.gemini/antigravity-cli"
mkdir -p "$data_dir/conversations"

# Five conversations with transcripts of different sizes, and no
# conversation_summaries.db (so the collector falls back to its own pick).
now_ms=$(date +%s%3N)
for n in 1 2 3 4 5; do
  printf '{"display":"prompt","timestamp":%s,"workspace":"/home/user","conversationId":"conv-%s"}\n' "$now_ms" "$n" >>"$data_dir/history.jsonl"
  mkdir -p "$data_dir/brain/conv-$n/.system_generated/logs"
  printf '{"source":"MODEL","content":"%s"}\n' "$(head -c $((n * 4000)) /dev/zero | tr '\0' y)" \
    >"$data_dir/brain/conv-$n/.system_generated/logs/transcript.jsonl"
done

seen=()
for seed in 1 2 3 4 5 6 7 8; do
  seen+=("$(PYTHONHASHSEED=$seed HOME="$TEST_HOME" ANTIGRAVITY_DATA_DIR="$data_dir" \
    "$ROOT/bin/omarchy-agent-usage-agy" | jq -r '[.modelUsage[].outputTokens] | add // 0')")
done
printf '# estimated output tokens over 8 runs of the same data: %s\n' "${seen[*]}"

[[ $(printf '%s\n' "${seen[@]}" | sort -u | wc -l) == 1 ]] ||
  fail "Antigravity collector reports the same tokens for the same data on every run" "${seen[*]}"
pass "Antigravity collector reports the same tokens for the same data on every run"

Run from the repository root at the reviewed head (012b47e) as bash test/shell.d/agent-usage-agy-active-test.sh. PYTHONHASHSEED is pinned per run only to make the variation visible; normal runs get a random seed each time.

Actual output at the reviewed head (run 2026-09-29 00:54 UTC, machine zone UTC+2):

# estimated output tokens over 8 runs of the same data: 2000 5000 4000 4000 3000 2000 1000 5000
2000 5000 4000 4000 3000 2000 1000 5000
not ok - Antigravity collector reports the same tokens for the same data on every run

Control: the same test against a copy of bin/ with a minimal candidate fix applied passes:

# estimated output tokens over 8 runs of the same data: 5000 5000 5000 5000 5000 5000 5000 5000
ok - Antigravity collector reports the same tokens for the same data on every run

The fallback is list(conversation_ids)[-1] on a Python set, whose order depends on string hashing. The control patch used sorted(conversation_ids)[-1].

Daily reset time is local midnight labelled UTC

resetsAt formats the next local date as T00:00:00Z, so it is off by the UTC offset; west of UTC in the evening it lies in the past and the countdown disappears (shown in the first reproducer with TZ=America/Los_Angeles).

Suggested change: covered by returning limits: []; if a reset time is kept, convert the next local midnight to UTC.

Details

Verified

  • omarchy-agent-usage-update picks up both collectors by its omarchy-agent-usage-* glob, and the panel resolves the new agy and copilot icons by record id.
  • --continue and --plan map to options the current Antigravity (1.2.12), Copilot (1.0.89) and Claude Code CLIs document.
  • The PR's new tests and the extended default-agent-test.sh pass at the head in the sandbox (the Copilot test only while local and UTC dates agree), and the existing agent tests pass at head and base.
  • Both collectors open their SQLite databases read-only.

Related open PRs

Optional notes

  • --limits-only is parsed but ignored by both collectors, so every panel open rescans the full history; the Claude and Codex collectors reuse a recent scan in that mode.
  • The agents README collector table, the plugin manifest description and the agents-panel paragraph of manual/17-ai.md still name only Claude Code, Codex and Fireworks.
  • The PR description covers only the Antigravity collector; splitting the Copilot collector and launcher flags into their own PRs would make the description match the diff and keep the feat(agents): add copilot usage #9945 and Intelligently fallback to available agent when default agent has exhausted usage #12105 conflicts out of this one.

Review information

Test scope: Source and synthetic sandbox tests at 012b47e9; agent CLIs and UI not run.

AI process: Opus 5.5 Medium coordination and synthesis, Opus 5.5 Xhigh technical review and final fact check, GPT 6 Sol Xhigh search for related issues, Opus 5.5 Medium editorial check.

Opt out: To stop receiving these reviews, reply to this comment saying so.

@omarchybot omarchybot added the enhancement New feature or request label Oct 1, 2026
@dhh

dhh commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Thanks! Antigravity is coming in through #14039 (from #11989) and Copilot through #14040 (from #9945). Closing in favor of those.

@dhh dhh closed this Oct 2, 2026
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.

4 participants