Repository navigation
Conversation
b8d82e5 to
6fcc909
Compare
yetone
left a comment
There was a problem hiding this comment.
Thanks for this. The promptUsage fix is right and well guarded, and reading gen_metadata / steps is a real improvement over estimates. Reviewed at 6fcc909, every line, and run on Linux (WSL, Bun 1.4.2 / Node 22.23) and Windows. A few things have to change before it can merge.
Must fix
-
Rebase on main and take 0.1.19. main now has agy-cli 0.1.18 (9ec6962, #980:
cloak.mjs). This PR also says 0.1.18, andindex.mjsconflicts on the import line. Merged by hand (both imports kept),bun test packages/agy-clipasses 14/14 on Linux andbun scripts/check.mjsis clean. The version still has to be 0.1.19. -
The read-write fallback in
query()rewrites agy's store (step-usage.mjs:160-178, and thequeryOnlyFallbackopeners at:29-46). Every error except "no such table" (an unknown column, SQLITE_BUSY, a corrupt page) retries with a normal read-write handle.PRAGMA query_onlystops statements from writing, but it does not stop SQLite's checkpoint when the last connection closes. Probe: a WAL store with uncheckpointed frames left behind by an agy that has exited, and astepstable withoutstatus. ThenlastModelStep()gives:before {"s.db":"…:4096","s.db-wal":"…:416152"} after {"s.db":"…:110592"} MAIN DB REWRITTEN: true WAL GONE: trueSame on Bun and Node. The plugin must never write agy's files.
The fallback also isn't needed for the case the PR gives as its reason. A plainreadOnly: trueopen reads a checkpointed store with no-shm(and a crashed one with-waland no-shm) fine on both runtimes. Removing the fallback ([false, true]→[false]) leaves every test green, including "WAL-mode stores stay readable after writer close removes -shm". So that test doesn't guard the fallback, and SQLITE_CANTOPEN didn't reproduce here. If you hit it on macOS, please post the SQLite version and the exact files in the folder. Either way, an unreadable store should benull(unknown), never a read-write open. -
A late row from the previous turn is billed to the next turn's first tool_use response.
turn.stepCursorisMAX(idx)when the turn starts (index.mjs:1499). Mid-turn responses haveexpected === null, so nothing reconciles them. Any model call agy commits after that point is counted in this turn. That covers a cancelled turn's generation (cancelled turns skip measurement entirely) and a turn whose final settlement gave up after its 1s wait. Probe (fake agy through the testkit): turn 1 is cancelled and its generation{input 50000, output 900}lands 150ms later. Turn 2 writes{input 1000, read 49000, output 20}and calls a tool:AssertionError: turn 2's first response billed the cancelled turn's 900 output tokens 920 !== 20Please make a turn whose rows were never consumed (cancelled, or fell back) keep the next turn from trusting rows it can't attribute. Add a test for it. I couldn't check whether agy 1.5.x writes a
gen_metadatarow for a cancelled generation. If you can show it never does, say so with a store. But a fallen-back final turn hits this anyway. -
Every read leaves agy's store open until GC, and on Windows that locks the file. The plugin host runs under Bun, so
loadOpenerpicksnode:sqlite.query()doesdb.prepare(sql).all(...)and thendb.close(). With Bun'snode:sqlite, the statement that is never finalized keeps the file handle open afterclose(). Probe (Bun 1.4.2, fixture store, no plugin code except the opener):Windows: node:sqlite open/close rm ok node:sqlite prepare.all/close rm FAIL EBUSY bun:sqlite query.all/close rm ok bun:sqlite prepare/finalize/close rm ok Linux: 50 × lastModelStep() -> 50 fds open on s.db, 0 after Bun.gc(true)measuredUsagepolls every 25ms for up to 1s, so one turn can hold dozens of handles on agy's conversation file. On Windows, agy can't delete or replace that file while they're open. The same thing is why the PR's own tests are red on Windows. Eachfs.rmSync(dir)in afinallythrows EBUSY:test/step-usage.mjs: 4 of 6 red. Three are EBUSY. The fourth, "a missing or unreadable store…", asserts/^\/x\/f6faf504/, butpath.joingives\x\f6faf504-….dbon Windows, so the assertion needspath.jointoo.test/usage-settlement.mjs: the 3 SQLite tests are red, all with EBUSY.
On main, Windows has only the existing smoke red (#973, the same 1 of 333 checks in both trees). Fix: under Bun, prefer
bun:sqlite(or finalize the statement beforeclose()), and test on Windows that the directory can be removed right after a read.
Should fix
-
The event loop is blocked while the store is locked. Every
query()is synchronous, with a 50ms busy timeout per open and two opens per query. On a store held under an EXCLUSIVE lock,modelUsageCursor()andmodelUsageAfter()each took about 215ms (Bun and Node). The poll loop inmeasuredUsage(index.mjs:4341-4350) repeats that until 1s has passed, so the plugin host is blocked for most of that second. In WAL mode this is rare, and #2 removes half of it (and #4 the handles). Please also make aSQLITE_BUSYstop the measurement for the turn, not poll again. A large store is fine: 550MB with 40k steps and 13k generations, each cursor or tail read 0.1–1ms with agy's indexes. -
A store that never answers costs 1s on every turn. When the
.dbexists but gives no usable row (the.pbcold start, or a future agy whose blob layout changes), the first response of every turn waits the fullSTEP_USAGE_WAIT_MSbeforetool_usereaches the client. After one turn like that, remember it on theProcand stop waiting. -
The same-generation heuristic bills a slow commit as 0 and drops its prompt. If a new generation's row isn't committed within 75ms (
STEP_USAGE_SAME_GEN_WAIT_MS), that response is billed 0. The next response then reports only the latest call's prompt, so the delayed call's input/cache-read is never reported. agy 1.5.5's binary writesgen_metadatathrough a background cache (dbtrajectory.newGenMetadataCache.func1/gowrap1), so commit timing isn't guaranteed to come before the tool call. Your macOS numbers (54/54) are good evidence, but please add a test where the row lands after 75ms and pin what should happen.
Not guarded by any test
Each of these was broken by hand in turn, and test/step-usage.mjs and test/usage-settlement.mjs stayed green:
pendingignored inmodelUsageAfter(step-usage.mjs:237);- the over-count guard removed (
index.mjs:4374,prompt > expected + max(16, 1%)); - "latest call's prompt" changed to the sum of calls (
index.mjs:4390-4394), the multi-call case your body describes; - the
queryOnlyFallbackretry removed (see #2).
These ones did go red, so the tests hold them: promptUsage forced inclusive (3 red) or exclusive (5 red), settleUsage without measuredUsage (2), the same-generation branch removed (1), omittedHistoryTokens held at 0 (1), a gen_metadata cursor falling through to steps (2), and the cursor kept after a fallback (1).
Smaller
- The PR body and the cold-start test's title say
HISTORY_BLOCK_CLIP_BYTESis 112 KiB. It is 12 KiB (per block). 112 KiB ismaxInitialPromptBytes(). The gate atindex.mjs:4841works only as a necessary condition. Please fix the wording. step-usage.mjs's header credits lili-luo/dsh-plugin-bundle, and the body says it was adapted from DylanLIiii/opencode-antigravity-bridge (MIT). Credit both in the file.- The
promptUsagefix could ship on its own now if you split it out. It's independent of the store reader.
What ran
- PR head 6fcc909:
HOME=$(mktemp -d) bun test packages/agy-cli12/12 andbun scripts/check.mjsok (WSL). - Merged with origin/main 9ec6962 (import conflict resolved by keeping both): agy-cli 14/14 on WSL, check.mjs ok.
- Schema checked against agy 1.5.5's binary (strings only, nothing run): GORM tags
primaryKey;autoIncrement:false;column:idx,index;column:step_type,index;column:status,column:metadata,column:data, plussetPageSizeAndWAL. Sosteps(idx, step_type, status, metadata)andgen_metadata(idx, data)in WAL mode match your queries. I had no real conversation store to read. - Old-schema store (no
statuscolumn, nogen_metadata): cursornull. A truncatedgen_metadatablob:pending: true. Missing store:null/ empty, as intended. - Merged tree, root
HOME=$(mktemp -d) bun teston WSL: 8199 pass, 19 skip, 0 fail (580 files). - Windows 11 (Bun 1.4.2, fresh HOME and USERPROFILE),
bun test packages/agy-cli. On main: 12 pass, 1 fail, which is smoke's #973. Merged: 11 pass, 3 fail, the extra two beingtest/step-usage.mjsandtest/usage-settlement.mjs(see #4). Smoke fails the same single check in both trees. - Broken by hand, one at a time, with a touch after each restore. The results are listed above.
- No network and no real accounts. Every probe ran on fixtures under the testkit's fake agy.
|
Heads-up on the version: main now has agy-cli 0.1.19 too (5341ac1, #989: client tools are listed with |
6fcc909 to
c742d08
Compare
|
Thanks for the thorough review and probes — especially catching the Must fix
Should fix & Not guarded by any test
Smaller
|
c742d08 to
6aab6a4
Compare
yetone
left a comment
There was a problem hiding this comment.
Thanks, this round fixes almost everything from the last review. I re-reviewed every changed line at 6aab6a4 and ran it on macOS (mote), Linux (WSL Ubuntu 24.04) and Windows 11, all on Bun 1.4.2. One thing still blocks the merge: the new ?immutable=1 path only works on macOS. On Linux and Windows the PR's own new tests are red.
Must fix
-
?immutable=1is never opened as a URI bybun:sqliteon Linux or Windows (step-usage.mjs:34-35,:225). Only Apple's systemlibsqlite3has URI filenames on by default. Bun's bundled SQLite on Linux and Windows doesn't, sonew Database("file:///…?immutable=1", { readonly: true })fails with "unable to open database file",withStore()returnsnull, and every read of a checkpointed store (the WAL-flagged file with no-wal/-shmthat you described) gives no measurement. The turn then falls back andstepUsageTrustedturns off for the whole process. I opened a checkpointed WAL store (one row, no sidecars) in each of these ways:plain {readonly:true} "file:…?immutable=1" {readonly:true} READONLY|URI flags darwin bun 1.4.2 ERR unable to open (as you saw) [{"x":42}], no sidecars [{"x":42}], no sidecars linux bun 1.4.2 [{"x":42}], leaves -wal/-shm ERR unable to open database file [{"x":42}], no sidecars win32 bun 1.4.2 [{"x":42}], leaves -wal/-shm ERR unable to open database file [{"x":42}], no sidecars, strict rm okThe PR's own tests at this head, run with
bun testper file:- Linux:
test/step-usage.mjs7/8. "checkpointed WAL stores without -wal/-shm read cleanly…" getsnullattest/step-usage.mjs:169.test/usage-settlement.mjs31/33: "单次生成串行派发多工具 + WAL 关库…" (:1128) and "异步延迟 >75ms 落盘…" (:1423) are red. Their fake writers close the store after every write, so each read finds a checkpointed file. (node --test step-usage.mjspasses 8/8 on Linux, because Node's SQLite has URIs on. The plugin host runs Bun, though.) - Windows: the same three, plus the timing bar in item 2.
- macOS: everything green, 14/14.
Fix: pass the raw open flags.
const { Database, constants } = require("bun:sqlite")andnew Database(target, constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI). With only that change (two lines),test/step-usage.mjsis 8/8 andtest/usage-settlement.mjs33/33 on Linux. On Windows usage-settlement is 33/33, and step-usage is 7/8, the one red being item 2's bar. A strictfs.rmSync(dir, { recursive: true })right after the read passes on all three. Please run the package on Linux and Windows before pushing. CI didn't run on this PR, and the macOS run alone can't show this. - Linux:
-
test/step-usage.mjs:230'selapsed < 50is a fixed bar that Windows misses. Two non-blocking reads of an EXCLUSIVE-locked store took 58.5, 50.4 and 58.5 ms in three runs on Windows (Linux 18 ms, macOS under 1 ms). SQLite's Windows lock path costs that much without any busy timeout. The old cost was a 50 ms busy timeout per open, about 200 ms per pair. So a bar like< 150still catches a busy timeout coming back, and it leaves room on a loaded or slower machine. I addedPRAGMA busy_timeout = 200and the test went red at 481 ms, so a wider bar keeps it meaningful.
Verified fixed
- Rebased on main 4b0aef5.
cloak.mjsimport and #989'sALWAYS_LOADlistTools()change are both kept. The version is 0.1.20.origin/disthasopencode-agy-cli-auth-0.1.19.tgzas its newest, so 0.1.20 is the right next number. - The read-write fallback is gone. A crashed store (uncheckpointed
-wal, with and without-shm, old schema and new) leavess.dbands.db-walbyte-identical afterlastModelStep,modelUsageCursorandmodelUsageAfter, on Bun and Node. The new-schema row is read correctly from the WAL. The only file a reader touches is-shm(the WAL index): it's created when missing and updated when present. That's ordinary SQLite reader behaviour, and agy rebuilds it. The body's "zero sidecar writes" holds only for the checkpointed case, though, so please word it that way. - Handles: with
bun:sqlitepreferred, 50 reads leave 0 fds open on macOS. On Windows the strictrmSyncin the tests passes. When I forced the oldnode:sqliteopener on Windows, three step-usage tests went red with EBUSY, so the strict remove guards the leak. - Cross-turn late rows, the cancelled/fallen-back turn and the store that never answers are all guarded now (see below). Your note that a real cancelled agy 1.5.5 store had no
gen_metadatarow is useful. - The >75 ms commit cases are pinned. Reporting only the latest call's prompt for a multi-call response, so its context window isn't doubled, is a stated choice now, and the body's measured -2,582 / -83,270 delta matches it.
- Credits and the 12 KiB / 112 KiB wording are fixed.
Broken by hand (macOS, one at a time, restored and touched after each)
Red: immutable path off (step-usage), busy_timeout back (step-usage), pending ignored, finalizeTurn's trust reset removed, both trust resets removed, stepUsageTrusted ignored at turn start, polling on a null batch, the over-count guard removed, prompts summed in place of the latest call's, sawOutput ignored, promptUsage forced inclusive, omittedHistoryTokens held at 0 (all in usage-settlement unless named).
Green, and why that's acceptable:
settleUsage's reset alone, the cursor-null reset alone and measuring a cancelled turn: each is covered byfinalizeTurn's unreconciled check.- Leaving
stmt.finalize()out underbun:sqlite: Bun closes those statements itself. tableQueryswallowing every error: the result is the samenull.- Removing the re-read when
-walappears after an immutable read: no test covers it. It's a narrow race, and a test that writes a-walmid-read would be worth adding, but it doesn't block.
Smaller
- The
node:sqliteopener (step-usage.mjs:57) still doesn't finalize. Only plain Node reaches it, and Node finalizes onclose(), so this is fine. A one-line comment would save the next reader the question.
What ran
- macOS (mote, Bun 1.4.2, Node 22.22):
HOME=$(mktemp -d) bun test packages/agy-cli14/14.bun scripts/check.mjsok.node --test test/step-usage.mjs8/8. - Linux (WSL, Bun 1.4.2): step-usage 7/8, usage-settlement 31/33, as above. Node 8/8. With the flags fix: 8/8 and 33/33.
- Windows 11 (Bun 1.4.2, fresh HOME/USERPROFILE):
bun test packages/agy-cli11 pass, 3 fail. Those are step-usage, usage-settlement and smoke. Smoke is the known #973, 1 of 334 checks. With the flags fix, usage-settlement is 33/33 and step-usage 7/8 (item 2). - I didn't run the root suite this round, because item 1 blocks the merge anyway. I'll run it on the next head.
- No network and no real agy accounts. Every probe ran on fixture stores and the testkit's fake agy.
6aab6a4 to
262145f
Compare
|
Thanks for catching the Must fix
Wording, Race Test & Smaller
What ran (3-OS GitHub Actions run
|
…e semantics
agy 1.5.1+ (verified on agy 1.5.5) changed two usage surfaces:
1. ACP `session/prompt` usage now reports `inputTokens` as the uncached prompt
tokens alone, while `cachedReadTokens` and `cachedWriteTokens` are reported
separately and `totalTokens` sums all prompt and completion buckets. Treating
`inputTokens` as inclusive of `cachedReadTokens` truncated `cache_read` to
`inputTokens` (e.g. 2,133 uncached + 28,610 cached reported as 0 input and
2,133 cached) and clamped `recordPromptUsage`'s learned ratio to 1.0 whenever
`cachedReadTokens > inputTokens`. `promptUsage()` uses `totalTokens` to tell
the two shapes apart while preserving pre-1.5.1 inclusive payloads.
2. Each live ACP session writes per-generation Google model usage rows to
`~/.gemini/antigravity-cli/conversations/<sessionId>.db` (`gen_metadata` in
agy 1.5.1+, `steps` type 15 in 1.4.x-1.5.0). `step-usage.mjs` (Protobuf
layout decoded by lili-luo/dsh-plugin-bundle, MIT; SQLite reader adapted from
DylanLIiii/opencode-antigravity-bridge, MIT) reads those rows strictly
read-only:
- Prefer `bun:sqlite` under Bun with `SQLITE_OPEN_READONLY | SQLITE_OPEN_URI`
and explicit `stmt.finalize()` before `db.close()` so URI parameters work
across macOS/Linux/Windows and Windows file handles release immediately
without waiting on GC; fall back to `node:sqlite` (`{ readOnly: true,
timeout: 0 }`) with no busy timeout and no read-write open.
- Normalize store paths with `path.resolve()` so relative `file:...` paths
are never misinterpreted as SQLite URIs under `SQLITE_OPEN_URI`, and format
Windows `\\...` UNC / extended-length paths without a non-empty authority
segment when opening `${uri}?immutable=1`.
- When a checkpointed WAL store (`header[18..19] = [2, 2]`) has neither
`-wal` nor `-shm` on disk, open `${uri}?immutable=1` so Apple's system
`libsqlite3` on macOS does not fail with `SQLITE_CANTOPEN` and upstream
SQLite does not create `-wal`/`-shm` sidecar files on checkpointed stores,
re-reading once against the resolved live path if `-wal` appears
concurrently.
- Stop polling immediately on `SQLITE_BUSY` / unreadable stores, and mark
`proc.stepUsageTrusted = false` whenever a turn cancels or falls back so
unconsumed late rows cannot leak into a subsequent turn and non-answering
(`.pb` / incompatible) stores never cost 1s again on later turns.
- Settle sequential MCP `tools/call` continuations emitted by one LLM
generation at 0 tokens (`agy_same_generation`) after a 75ms poll when no
new message/thought output arrived and no `pending` row exists, while
waiting up to 1,000ms whenever text/thinking streamed or a `pending`
pre-send row is present.
- Bound fallback window estimates via `Proc.omittedHistoryTokens` when an
initial replay exceeds `maxInitialPromptBytes()` (112 KiB) /
`HISTORY_BLOCK_CLIP_BYTES` (12 KiB per block).
Tests:
- `HOME=$(mktemp -d) bun test packages/agy-cli`: 14 pass, 0 fail (Linux/macOS;
all `step-usage` 10/10 and `usage-settlement` 33/33 pass on Windows)
- `node --test packages/agy-cli/test/step-usage.mjs`: 10 pass, 0 fail
- `bun scripts/check.mjs`: all packages and `registry.json` ok
- Broken by hand (13 checks across `test/step-usage.mjs` and
`test/usage-settlement.mjs`): `pending = true` removed, over-count guard
removed, latest-call prompt changed to sum of calls, checkpointed-WAL
`?immutable=1` disabled (both Bun and Node), concurrent `-wal` re-read
removed, no-`-wal` catch guard removed, `path.resolve()` removed on relative
`file:` path, `stepUsageTrusted = false` removed (both cross-turn late row and
`.pb` Turn 2 latency), `!batch` break removed on `SQLITE_BUSY`, and
`!sawOutput` removed on `>75ms` commits all turn tests red.
262145f to
9054f47
Compare
What
Fixes two usage-reporting mismatches observed on
agy 1.5.1+(verified onagy 1.5.5):ACP
session/promptinputTokensshape (agy 1.5.1+):agy 1.5.1+reportsresult.usage.inputTokensas the uncached prompt tokens alone, whilecachedReadTokensandcachedWriteTokensare separate andtotalTokenssums all prompt and completion buckets (e.g.2,133uncached input +28,610cache read +6output =30,749total). TreatinginputTokensas inclusive ofcachedReadTokenstruncatedcache_read_input_tokenstoMath.min(inputTokens, cachedReadTokens)(0uncached input,2,133cache read) and clampedrecordPromptUsage's learnedlastCacheRatioto1.0whenevercachedReadTokens > inputTokens.Per-step model usage across multi-step tool loops:
ACP reports accumulated usage only when
session/promptends (agy 1.5.xemits nousage_updatenotifications), while each Google model call is recorded in~/.gemini/antigravity-cli/conversations/<sessionId>.db(gen_metadatainagy 1.5.1+,stepswithstep_type = 15in1.4.x–1.5.0). Reading those completed rows settles intermediate and final HTTP steps fromagy's actual model calls instead of character-based approximations, with these real-world invariants enforced:step-usage.mjsprefersbun:sqliteunder Bun (constants.SQLITE_OPEN_READONLY | constants.SQLITE_OPEN_URI) withstmt.finalize()beforedb.close()(releasing Windows file locks immediately without waiting on GC) andtimeout: 0(no synchronous busy sleep), never opening a read-write handle, and resolves paths viapath.resolve()so relativefile:...paths are never misinterpreted as SQLite URIs underSQLITE_OPEN_URI. When a WAL-flagged store (header[18..19] = [2, 2]) has neither-walnor-shmon disk afteragyexits, opening${immutableUri(resolved)}(?immutable=1, formatting Windows\\...UNC / extended-length paths without a non-empty URI authority) keeps checkpointed reads sidecar-free across macOS, Linux, and Windows (avoidingSQLITE_CANTOPENunder Apple's systemlibsqlite3and-wal/-shmcreation under upstream SQLite), re-reading against the resolved live file path if-walappears concurrently; on uncheckpointed WAL stores,s.dbands.db-walremain byte-identical while ordinary SQLite reader-shmWAL-index behaviour applies.SQLITE_BUSY, or falls back without reconciling against ACP's total (including.pbcold starts or incompatible schemas),ProcsetsstepUsageTrusted = falseso late-arriving rows from an earlier turn cannot be billed to a later turn and subsequent turns do not spend1,000mspolling a store that never answers.agydispatches MCPtools/callsequentially without invoking the model between them.measuredUsage()settles same-generation continuations at0tokens (agy_same_generation) after75msonly when an earlier step in the turn already measured a generation, no new message/thought output arrived, and nopendingpre-send row exists; otherwise it waits up to1,000ms(stopping immediately onSQLITE_BUSY/ unreadable store)..pbfallback on clipped initial replays: when a freshagyprocess starts a session before its SQLite trajectory flag initializes and writes<sessionId>.pb(leaving.dbwith0model rows) on an initial replay clipped bymaxInitialPromptBytes()(112 KiB) /HISTORY_BLOCK_CLIP_BYTES(12 KiBper block),Proc.omittedHistoryTokenssubtracts the clipped-out history tokens from fallback window estimates soresponseUsage()stays bounded by what actually reachedagy.Measured
Across a continuous
agy 1.5.5workload on macOS (39conversations,210/v1/messagesresponses,0request errors),38conversations (193HTTP responses =139model-backed +54same-generation continuations) recorded SQLite generation rows (140generations serving those193responses):140generations)0.1.20) HTTP usage (193responses)HTTP − SQLite)input_tokens1,847,9731,845,391-2,582cache_read_input_tokens8,192,4208,109,150-83,270output_tokens(incl. thinking)134,291134,2910read / (in + read))81.59%81.46%-0.13%138 / 139model-backed HTTP responses match their single SQLite generation(input_tokens, cache_read_input_tokens, output_tokens)tuple with0token difference.1 / 139response spanned two internalagygenerations inside one HTTP turn (in: 2,582, read: 83,270, out: 453followed byin: 2,355, read: 83,803, out: 115);measuredUsage()reports the latest call's context window (2,355/83,803, accounting for the exact-2,582/-83,270prompt delta) and the exact sum of both outputs (568), giving0output_tokensdelta across all193responses.54same-generation sequential MCP tool continuations settled at0tokens (agy_same_generation).1 / 39cold-start conversation (17tool_useresponses on a~375K-token request body clipped tomaxInitialPromptBytes()112 KiB) wrote<sessionId>.pbwith0SQLite rows beforeagy's trajectory flag initialized, motivating theomittedHistoryTokensfallback bound.The change
packages/agy-cli/step-usage.mjs: strictly read-only reader (bun:sqlitepreferred withSQLITE_OPEN_READONLY | SQLITE_OPEN_URIand finalized statements,node:sqlitefallback withtimeout: 0,path.resolve()normalization, and?immutable=1for closed checkpointed WAL stores including Windows\\...UNC/extended paths and live re-read if-walappears concurrently) forgen_metadata(agy 1.5.1+) andstepsstep_type = 15(1.4.x–1.5.0); Protobuf layout credited tolili-luo/dsh-plugin-bundle(MIT) and SQLite reader adapted fromDylanLIiii/opencode-antigravity-bridge(MIT). Controlled byAGY_BRIDGE_STEP_USAGE(default on;0/false/offdisables) andAGY_BRIDGE_CONVERSATIONS_DIR.packages/agy-cli/index.mjs:promptUsage(usage): distinguishes exclusive (agy 1.5.1+) vs inclusive (< 1.5.1)inputTokensviatotalTokens, used acrosstranslateUsage,responseUsage,recordPromptUsage, andmeasuredUsage.measuredUsage(turn, step, content, result)&settleUsage(...): settles per-response usage fromstep-usage.mjswhen reconcilable with ACP's total, handlesagy_same_generation0-token continuations vspending/ text-bearing slow commits, aborts immediately onSQLITE_BUSY, and marksproc.stepUsageTrusted = falseon cancelled or fallen-back turns.Proc.omittedHistoryTokens/Proc.estimatePromptTokens(body): bounds fallback estimates when initial history replay is clipped bymaxInitialPromptBytes()/HISTORY_BLOCK_CLIP_BYTES.packages/agy-cli/package.json: bump version to0.1.20(rebased ontomainafter#980and#989) and includestep-usage.mjsinfiles.Tests
ubuntu-latest, Bun1.4.4, Nodev24.22.2):HOME=$(mktemp -d) bun test packages/agy-cli14 pass, 0 fail(test/step-usage.mjs10/10,test/usage-settlement.mjs33/33),node --test packages/agy-cli/test/step-usage.mjs10/10, full repoHOME=$(mktemp -d) bun test8386 pass, 6 skip, 0 fail,bun scripts/check.mjsok.windows-latest, Bun1.4.4, Nodev24.22.2, fresh tempHOME/USERPROFILE):bun test ./packages/agy-cli/test/step-usage.mjs10/10(70.21msonEXCLUSIVE-locked store,\\?\extended/UNC +#/%20paths & strictfs.rmSyncok),node --test ./packages/agy-cli/test/step-usage.mjs10/10,bun test ./packages/agy-cli/test/usage-settlement.mjs33/33, all otheragy-clisuites green (smoke.mjsonly has the known#973check,333/334),bun scripts/check.mjsok.macos-latestarm64, Bun1.4.4, Nodev24.22.2+ local arm64 Bun1.4.2, Nodev26.2.0):HOME=$(mktemp -d) bun test packages/agy-cli14 pass, 0 fail,node --test packages/agy-cli/test/step-usage.mjs10/10,bun scripts/check.mjsok,npm pack --dry-runincludesstep-usage.mjsin0.1.20tarball.pending = trueremoved instep-usage.mjs-> red in bothtest/step-usage.mjsandtest/usage-settlement.mjs;prompt > expected + max(16, 1%)) removed inindex.mjs-> red intest/usage-settlement.mjs;"latest call's prompt"(last.input/last.read) changed to sum acrosssteps-> red intest/usage-settlement.mjs;isCheckpointedWal(?immutable=1) forcedfalse-> red intest/step-usage.mjsunder both Bun (SQLITE_CANTOPENon macOS, sidecars on Linux/Windows) and Node (-wal/-shmsidecars created);-walre-read removed inwithStore-> red intest/step-usage.mjsunder both Bun and Node;-walcatch guard removed inwithStore-> red intest/step-usage.mjsunder both Bun and Node;path.resolve()removed inresolveStorePath-> red on relativefile:...path under both Bun and Node;stepUsageTrusted = falseremoved -> red on both cross-turn late-row isolation and.pbTurn 2 latency;!batchbreak removed inmeasuredUsage-> red onEXCLUSIVE-lockedSQLITE_BUSYlatency (>= 1000ms);!sawOutputremoved fromcanShareGeneration-> red when text precedestool_usewith a>75msgen_metadatacommit.Existing work reviewed
step-usageitem from agy-cli: sync upstream bridge modules (prompt-guard, tool-schema, step-usage, output-limit, upstream-error) and fix #856 #892 as a standalone atomic PR (per review feedback on feat(agy-cli): sync upstream bridge modules (prompt-guard, tool-schema, step-usage, output-limit, upstream-error) and fix #856 #895).step-usagedraft in feat(agy-cli): sync upstream bridge modules (prompt-guard, tool-schema, step-usage, output-limit, upstream-error) and fix #856 #895: addsgen_metadata(agy 1.5.1+) support alongsidestepstype 15, strictly read-only?immutable=1closed-WAL handling across macOS/Linux/Windows,bun:sqlitefinalized statements for Windows handle release,stepUsageTrustedcross-turn isolation,agy_same_generationhandling,agy 1.5.1+exclusiveinputTokenshandling inpromptUsage, the clipped-replay fallback bound, and comprehensive hand-break-verified regressions.0.1.11/0.1.12)lastCacheRatiocalibration semantics (including a calibrated0) when SQLite rows are absent.