feat(web): expose agent quota queries in settings and context display - #1788
techotaku39 wants to merge 41 commits into
Conversation
There was a problem hiding this comment.
Findings
- [Major] Restrict quota templates to read-only requests —
UsageQueryHttpMethodSchemaacceptsPOST, the template also controls the path/body/headers, andtest()executes it immediately with the Runner-resolved API key. The same-origin check prevents exfiltration but does not prevent a Web credential holder from invoking a mutating provider endpoint with a secret that is intentionally withheld from Web. Evidenceshared/src/usageQuery.ts:17,shared/src/usageQuery.ts:42,cli/src/usageQuery/service.ts:193.
Suggested fix:If a provider truly requires POST, keep that method/path/body fixed inside a reviewed adapter rather than editable in a generic template.export const UsageQueryHttpMethodSchema = z.literal("GET")
- [Minor] Do not let an obsolete in-flight request replace the current cache — after template A starts, saving/querying template B can complete first; when A later finishes, both success and error paths unconditionally overwrite B in
this.cache. The fingerprint check prevents displaying A as B, but the next B query misses the five-minute cache and hits the provider again. The existing race test stops before checking the subsequent query. Evidencecli/src/usageQuery/service.ts:257,cli/src/usageQuery/service.ts:269,cli/src/usageQuery/service.test.ts:136.
Suggested fix:Apply the guard to both cache writes and extend the test with another B query asserting the fetch count remains 2.const fingerprint = templateFingerprint(configured.template) if (this.inFlight.get(agent)?.fingerprint === fingerprint) { this.cache.set(agent, nextCacheEntry) }
Summary
Review mode: initial. Found 1 Major security issue and 1 Minor cache-race regression in the fresh full diff.
HAPI Bot
| ]) | ||
| export type UsageQueryAdapter = z.infer<typeof UsageQueryAdapterSchema> | ||
|
|
||
| export const UsageQueryHttpMethodSchema = z.enum(['GET', 'POST']) |
There was a problem hiding this comment.
[MAJOR] Restrict quota templates to read-only requests
This schema makes the feature a credentialed arbitrary same-origin POST primitive: the template controls path/body/headers, and the uncached Test endpoint executes it immediately with the Runner-resolved API key. Origin pinning prevents exfiltration, but it does not prevent mutating provider actions using a secret deliberately withheld from Web. All built-in templates are already GET-only.
Suggested fix:
export const UsageQueryHttpMethodSchema = z.literal("GET")If a provider needs POST, hard-code its method/path/body in a reviewed adapter rather than exposing them through the generic template.
| fetchImpl: this.fetchImpl, | ||
| now: startedAt | ||
| }) | ||
| this.cache.set(agent, { |
There was a problem hiding this comment.
[MINOR] Prevent obsolete requests from replacing the current cache
Template A can remain in flight while a save and query for template B completes. When A finishes later, this unconditional write replaces B in this.cache; the fingerprint check avoids showing the wrong result, but the next B query bypasses the promised five-minute cache and calls the provider again. The current race test ends before that next query.
Suggested fix (apply to both success and error writes):
const fingerprint = templateFingerprint(configured.template)
if (this.inFlight.get(agent)?.fingerprint === fingerprint) {
this.cache.set(agent, nextCacheEntry)
}Then query B once more in the test and assert the fetch count remains 2.
There was a problem hiding this comment.
Findings
- [Minor] Retry failed forced refreshes after the 30-second cooldown — a forced refresh can fail while the previous success is still inside its five-minute TTL. The error entry preserves the old
cachedAt, then the TTL check runs before the error cooldown check, so every normal query keeps returning the failed/stale result until the original five-minute TTL expires. Evidencecli/src/usageQuery/service.ts:225.
Suggested fix:if (!force && usableCached) { if (usableCached.lastError) { if (now - usableCached.lastAttemptAt < USAGE_QUERY_RETRY_COOLDOWN_MS) return usableCached.result } else if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) { return usableCached.result } }
- [Minor] Preserve Home/End keyboard highlighting when the menu opens — the trigger sets the first/last enabled option, but the open effect immediately resets
highlightedValueto the selected value, so Home and End do not land on the requested boundary. Evidenceweb/src/components/ui/select-menu.tsx:89.
Suggested fix:// setOpenAndHighlight already initializes pointer-open state. // Keep the boundary selected by the trigger key handler. useEffect(() => { if (!open || !highlighted) return optionRefs.current.get(highlighted.value)?.focus() }, [open, highlighted?.value])
Summary
Review mode: follow-up after new commits. Found 2 Minor correctness/accessibility regressions in the full fresh-head diff.
Testing
- Not run (automation; PR code not executed). Add a forced-failure-before-TTL cache test and Home/End focus assertions.
HAPI Bot
| if (!configured.enabled) { | ||
| return emptyResult(agent, configured.templateId, now, 'Usage query is disabled', usableCached?.result) | ||
| } | ||
| if (!force && usableCached) { |
There was a problem hiding this comment.
[MINOR] Retry failed forced refreshes after the cooldown
If a previous success is still inside the five-minute TTL and force=true refreshes it unsuccessfully, this entry keeps the old cachedAt. The TTL branch then wins before lastError is checked, so normal queries return the failed/stale result for the rest of the original TTL instead of retrying after 30 seconds.
Suggested fix:
if (!force && usableCached) {
if (usableCached.lastError) {
if (now - usableCached.lastAttemptAt < USAGE_QUERY_RETRY_COOLDOWN_MS) return usableCached.result
} else if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) {
return usableCached.result
}
}Add a regression test: successful query, forced failure before TTL expiry, advance 31 seconds, then assert a normal query calls the provider again.
| const highlighted = props.options.find((option) => option.value === highlightedValue && !option.disabled) | ||
| ?? firstEnabled(props.options) | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
[MINOR] Do not reset the Home/End highlight after opening
The trigger handler selects the first/last enabled option for Home/End, but this effect runs after the popup mounts and overwrites that value with props.value. Keyboard users therefore remain on the currently selected option instead of reaching the requested boundary.
Suggested fix:
// setOpenAndHighlight already initializes pointer-open state.
// Keep the boundary selected by the trigger key handler.
useEffect(() => {
if (!open || !highlighted) return
optionRefs.current.get(highlighted.value)?.focus()
}, [open, highlighted?.value])Then assert that Home focuses the first option and End focuses the last option when invoked from the closed trigger.
There was a problem hiding this comment.
Findings
- [Major] Keep async save results keyed to the original machine and agent —
onSuccessreads the component's currentmachineId/agent, while the request may have started for a different selection. Switching targets during a slow save can therefore cache machine A's settings under machine B's key; the UI can then display and later save the wrong template to B. Evidenceweb/src/routes/settings/usage-query.tsx:140.
Suggested fix:type SaveInput = { machineId: string agent: UsageQueryAgent template: UsageQueryTemplate enabled: boolean } const saveMutation = useMutation({ mutationFn: async (input: SaveInput) => await api.saveMachineUsageQuerySettings(input.machineId, input.agent, { enabled: input.enabled, templateId: input.template.id, template: input.template }), onSuccess: (data, input) => { queryClient.setQueryData(queryKeys.machineUsageQuery(input.machineId, input.agent), data) queryClient.removeQueries({ queryKey: queryKeys.machineUsageQueryResult(input.machineId, input.agent) }) } })
- [Minor] Retry failed forced refreshes after the cooldown — when a forced refresh fails while an older success is still within the five-minute TTL, the error entry retains the old
cachedAt. The TTL branch runs beforelastError, so normal requests keep returning the failed/stale result until the full old TTL expires instead of retrying after 30 seconds. Evidencecli/src/usageQuery/service.ts:236.
Suggested fix:if (!force && usableCached) { if (usableCached.lastError) { if (now - usableCached.lastAttemptAt < USAGE_QUERY_RETRY_COOLDOWN_MS) return usableCached.result } else if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) { return usableCached.result } }
- [Minor] Preserve Home/End highlighting when opening the menu — the trigger sets the first or last option, but the open effect immediately overwrites that state with
props.value. Keyboard users land on the selected option instead of the requested boundary. Evidenceweb/src/components/ui/select-menu.tsx:91.
Suggested fix:// Remove the effect that resets highlightedValue on open. useEffect(() => { if (!open || !highlighted) return optionRefs.current.get(highlighted.value)?.focus() }, [open, highlighted?.value])
Summary
Review mode: follow-up after new commits. Fresh-head full-diff review found 1 Major and 2 Minor issues; the two findings from the prior HAPI Bot pass remain unresolved.
Testing
- Not run (automation; PR code not executed).
- Missing regression coverage: switching machine/agent during an in-flight save; forced failure before TTL followed by a normal retry after 31 seconds; closed-trigger Home/End focus behavior.
HAPI Bot
| template: input.template | ||
| }), | ||
| onSuccess: (data) => { | ||
| queryClient.setQueryData(queryKeys.machineUsageQuery(machineId, agent), data) |
There was a problem hiding this comment.
[MAJOR] Keep this cache update tied to the mutation target
This callback uses the component's current machineId and agent, but the completed request may have started before the user switched either selector. A slow save for machine A can then be written under machine B's query key, making B display A's template and risking a later overwrite of B's settings.
Suggested fix:
type SaveInput = {
machineId: string
agent: UsageQueryAgent
template: UsageQueryTemplate
enabled: boolean
}
const saveMutation = useMutation({
mutationFn: async (input: SaveInput) =>
await api.saveMachineUsageQuerySettings(input.machineId, input.agent, {
enabled: input.enabled,
templateId: input.template.id,
template: input.template
}),
onSuccess: (data, input) => {
queryClient.setQueryData(queryKeys.machineUsageQuery(input.machineId, input.agent), data)
queryClient.removeQueries({ queryKey: queryKeys.machineUsageQueryResult(input.machineId, input.agent) })
}
})Pass the current machineId and agent in every mutate call, and add a deferred-save test that switches targets before resolution.
| ? cached | ||
| : undefined | ||
| if (!force && usableCached) { | ||
| if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) return usableCached.result |
There was a problem hiding this comment.
[MINOR] Check the failed-attempt cooldown before the success TTL
If a previous success is still inside the five-minute TTL and force=true refreshes it unsuccessfully, the error cache entry retains the old cachedAt. This TTL branch therefore wins before lastError is considered, suppressing normal retries for the rest of the old TTL rather than only 30 seconds.
Suggested fix:
if (!force && usableCached) {
if (usableCached.lastError) {
if (now - usableCached.lastAttemptAt < USAGE_QUERY_RETRY_COOLDOWN_MS) return usableCached.result
} else if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) {
return usableCached.result
}
}Add a regression test: success, forced failure before TTL expiry, advance 31 seconds, then verify a normal query reaches the provider.
|
|
||
| useEffect(() => { | ||
| if (!open) return | ||
| setHighlightedValue(props.value) |
There was a problem hiding this comment.
[MINOR] Do not overwrite Home/End highlighting after opening
The trigger handler sets the first or last enabled option, but this effect runs after the popup mounts and resets highlightedValue to the selected value. Home and End therefore fail to land on their requested boundary.
Suggested fix:
// Remove this reset effect; setOpenAndHighlight already initializes normal opens.
useEffect(() => {
if (!open || !highlighted) return
optionRefs.current.get(highlighted.value)?.focus()
}, [open, highlighted?.value])Add closed-trigger assertions that Home focuses the first option and End focuses the last.
There was a problem hiding this comment.
Findings
- [Major] Respect an explicit Kimi home during credential resolution — inline finding on
cli/src/usageQuery/credentials.ts:170.
Questions
- None.
Summary
- Review mode: follow-up after new commits.
- The prior incomplete-current-config finding is addressed, but the expanded fallback can route an explicit-profile API key to the default profile's endpoint.
- Residual testing risk: provider responses and profile migration states remain covered only by synthetic fixtures; live provider/config drift is not covered.
Testing
- Not run (automation; PR content was not executed).
- GitHub checks observed: drift-gate, integration, test, and windows-codex-mcp passed.
HAPI Bot
| async function readKimiConfig(env: NodeJS.ProcessEnv, userHome = homedir()): Promise<KimiConfigSnapshot> { | ||
| const explicitHome = nonEmptyString(env.KIMI_CODE_HOME) ?? nonEmptyString(env.KIMI_SHARE_DIR) | ||
| const homes = [ | ||
| explicitHome, |
There was a problem hiding this comment.
[MAJOR] Do not fall through from an explicit Kimi home to the default profile
KIMI_CODE_HOME / KIMI_SHARE_DIR selects the current Kimi profile, but this list still appends the default ~/.kimi-code home. With the new “skip incomplete config” behavior, a readable-but-incomplete explicit profile can therefore fall through to the default profile. If KIMI_API_KEY belongs to the explicit profile, resolveKimiCredentials combines that key with the default profile's base_url and sends it to the wrong origin. The existing Kimi resolver treats the override as the current home and separately considers only the legacy ~/.kimi fallback (cli/src/kimi/utils/config.ts:19-28).
Suggested fix:
const homes = explicitHome
? [explicitHome, join(userHome, '.kimi')]
: [join(userHome, '.kimi-code'), join(userHome, '.kimi')]Add a regression test with an incomplete explicit home, a configured default ~/.kimi-code profile, and KIMI_API_KEY; assert that the default current profile is not selected.
There was a problem hiding this comment.
Findings
- No findings.
Questions
- None.
Summary
- Review mode: follow-up after new commits.
- No issues found in the latest full diff. The prior explicit Kimi-profile fallback finding is addressed at cli/src/usageQuery/credentials.ts:169.
- Residual risk: provider response parsing and credential-config compatibility are covered by synthetic fixtures; live provider/config drift remains unverified.
Testing
- Not run locally (PR content was not executed).
- GitHub checks observed: drift-gate, integration, test, and windows-codex-mcp passed; pr-review was pending.
HAPI Bot
There was a problem hiding this comment.
Findings
- [Major] Inactive Codex profiles override the root quota provider —
cli/src/usageQuery/credentials.ts:229. Details and suggested fix in the attached inline comment.
Summary
Review mode: follow-up after new commits
Reviewed the latest full 40-file diff, with the prior bot review and commit comparison as context. One credential-selection issue found. Live provider compatibility remains unverified.
Testing
- Not run (automation; PR code was not executed).
- Existing credential tests omit inactive profile tables. Add the regression described inline.
HAPI Bot
| } | ||
|
|
||
| const providerMatch = /^model_provider\s*=\s*(.+)$/.exec(line) | ||
| if (providerMatch && currentProvider === null) { |
There was a problem hiding this comment.
[Major] Ignore inactive profiles when resolving the Codex provider
cli/src/usageQuery/credentials.ts:229: every unrelated TOML table resets currentProvider to null, so model_provider inside [profiles.other] overwrites the root selection. For example, root model_provider = "Gateway" followed by an unused profile selecting "Other" makes resolveCodexCredentials choose Other's endpoint/key instead of Gateway's. Merely defining that profile can therefore show another provider's quota or make a working configuration fail with missing credentials. The new parser scans the whole file, including profiles after the provider definitions.
Track root-table scope separately and only read the root provider selector there.
Suggested fix:
let inRoot = true // alongside activeProvider/currentProvider
// Inside the loop, before the existing section matching:
if (line.startsWith('[')) inRoot = false
// Replace the currentProvider === null condition:
if (providerMatch && inRoot) {
activeProvider = unquoteTomlString(providerMatch[1])
continue
}Suggested regression: define Gateway and Other provider tables plus an unused profile selecting Other; assert the root Gateway URL and credential remain selected, including when the profile appears after both provider tables.
There was a problem hiding this comment.
Findings
- [Minor] Pending saves can discard newer template edits —
web/src/routes/settings/usage-query.tsx:135. Save/Enable responses reset the still-editable JSON draft. Details in the inline comment.
Suggested fix: add this prop to both the template SelectMenu and the JSON textarea so the submitted draft cannot change while its save is pending:
disabled={saveMutation.isPending}Summary
Review mode: follow-up after new commits
Reviewed the full 40-file diff at e220a5b936c8ba45cf5be4b5e6d8e906d11d2550, with the previous bot review and commit comparison as context. One Minor finding; no Blocker or Major findings. Live provider compatibility remains unverified.
Testing
- Not run (automation; PR code was not executed).
- Existing settings tests cover changing Agent during a pending save, but omit editing the same target during Save/Enable. Add a deferred-response regression for that case.
HAPI Bot
|
|
||
| useEffect(() => { | ||
| if (!settingsQuery.data) return | ||
| setTemplateText(JSON.stringify(settingsQuery.data.template, null, 2)) |
There was a problem hiding this comment.
[Minor] Preserve template edits made while a save is pending
web/src/routes/settings/usage-query.tsx:135: click Enable (which saves immediately), then edit the JSON or select another template before the RPC completes. Both editor controls remain enabled. The save response updates settingsQuery.data at line 152, and this effect unconditionally replaces the newer draft with the submitted template, silently discarding those edits. Saving a changed template and continuing to edit has the same problem.
Suggested fix: add this prop to both the template SelectMenu and the JSON textarea so the submitted draft cannot change while its save is pending:
disabled={saveMutation.isPending}Suggested regression: defer the save response after toggling Enable; verify both editor controls are disabled until settlement, or, if editing remains supported, verify subsequent edits survive the response.
There was a problem hiding this comment.
Findings
- [Minor] Returning to an Agent with an outstanding save bypasses the editor lock, allowing the response to discard newer JSON edits —
web/src/routes/settings/usage-query.tsx:282, with the pending-state reset at line 159. Details in the inline comment.
Suggested fix: import useIsMutating, add mutationKey: saveKey to the save mutation, and replace its observer-based pending checks with savePending:
const saveKey = ['usage-query-save', machineId, agent] as const
const savePending = useIsMutating({
mutationKey: saveKey,
exact: true
}) > 0Summary
Review mode: follow-up after new commits
Reviewed the full 40-file diff at 1f00b185243b63a320e4e9400f06e3e202cd4e1d, using the previous review and commit comparison as context. One remaining Minor finding; no Blocker or Major findings. Live provider compatibility remains unverified.
Testing
Not run (automation; PR code was not executed). Add a deferred Save/Enable regression covering A → B → A before settlement; the current test covers switching away only.
HAPI Bot
| onChange={(event) => { setTemplateText(event.target.value); setEditorError(null) }} | ||
| spellCheck={false} | ||
| rows={18} | ||
| disabled={saveMutation.isPending} |
There was a problem hiding this comment.
[Minor] Keep the editor locked when returning to a target with a pending save
web/src/routes/settings/usage-query.tsx:282: click Enable for Agent A, switch to B, then return to A before the save completes. The selection effect calls saveMutation.reset() at line 159, which clears the observer's pending state without cancelling the request. Both editor controls unlock. Editing now is still unsafe: the original response updates A's query at line 152, and the effect at line 135 replaces the newer JSON draft.
Suggested fix: track pending saves in the mutation cache per target. Import useIsMutating, add mutationKey: saveKey to the existing save mutation, and use savePending for both editor controls and the Save/Enable pending checks:
const saveKey = ['usage-query-save', machineId, agent] as const
const savePending = useIsMutating({
mutationKey: saveKey,
exact: true
}) > 0Regression: defer Enable's response, switch A → B → A, and verify A's editor stays disabled until its original save settles. The existing test switches away once, so it does not cover returning to the pending target.
|
Addressed the latest Minor review in 6f225d9: save mutations now use target-specific mutation keys and |
There was a problem hiding this comment.
Findings
No reportable issues found in the full latest diff.
Summary
Review mode: follow-up after new commits
Reviewed all 40 changed files at 6f225d9bde5f9e37464a84309c307c2e2494abcc, with the prior review and commit comparison as context. No remaining findings. Residual risks: live provider compatibility and browser focus behavior remain unverified.
Testing
Not run (automation; PR code was not executed). Inspected the updated pending-save regression and existing credential, cache, request-boundary, API, and UI tests. CI test and windows-codex-mcp checks were still running when checked. Suggested additional coverage: popover reopen after the retry cooldown, plus real-browser Tab/Escape focus behavior.
HAPI Bot
There was a problem hiding this comment.
Findings
-
[Minor] Selector dismissal steals editor focus — clicking the JSON editor while the Template menu is open closes the menu and forces focus back to its trigger (
web/src/components/ui/select-menu.tsx:185-187). Selection and Escape already restore focus explicitly.Suggested fix:
onCloseAutoFocus={(event) => event.preventDefault()}
Summary
Review mode: follow-up after new commits
Reviewed the full latest diff (40 files) at 6a115a61274bc4a03190a23b889201cd4e0dc528, using the previous bot review and comparison as context. One minor finding; no blocker or major issues found. Live provider compatibility remains unverified.
Testing
Not run (automation; PR code was not executed). Inspected credential, request-boundary, cache, API, and UI tests. Add a real-browser outside-click focus regression and popover reopen coverage after the Runner retry cooldown; existing tests do not cover these cases.
HAPI Bot
| onOpenAutoFocus={(event) => event.preventDefault()} | ||
| onCloseAutoFocus={(event) => { | ||
| event.preventDefault() | ||
| triggerRef.current?.focus() |
There was a problem hiding this comment.
[Minor] Preserve focus when dismissing the selector outside
Opening the Template menu and then clicking the JSON editor closes the menu, but this callback unconditionally focuses the selector trigger again. The editor loses focus, so the user must click it a second time before typing. This also overrides focus after Tab dismissal. Evidence: web/src/components/ui/select-menu.tsx:185-187; choose() and the Escape handler already restore trigger focus explicitly.
Suggested fix: suppress automatic restoration here without moving focus; retain the existing explicit restoration for selection and Escape.
onCloseAutoFocus={(event) => event.preventDefault()}Add a browser regression that opens the selector, clicks the editor, waits for popup teardown, and verifies that typing still edits the textarea.
|
Resolved the PR #1788 merge conflict by merging the latest upstream |
There was a problem hiding this comment.
Findings
No reportable issues found.
Summary
Review mode: follow-up after new commits
Reviewed the full 40-file diff at 9deaf42716996a7492ae676ebbf3992c49713aef, with the previous review and commit comparison as context. The prior selector-focus finding is addressed. Live provider compatibility and browser focus behavior remain unverified.
Testing
Not run (automation; PR code was not executed). Inspected credential, request-boundary, cache, API, and UI tests. Suggested coverage: browser outside-click focus after popup teardown, Tab/Escape dismissal, and quota-popover reopening after the retry cooldown. CI test and Windows jobs were still running when checked.
HAPI Bot
|
Addressed the latest Minor review in 9deaf42: SelectMenu outside dismissal now prevents Radix from overriding the editor's focus. Explicit option selection and Escape continue to restore trigger focus themselves. Added an outside-dismissal focus regression; Web targeted tests pass 36/36, with typecheck, docs build, and full build passing. |
# Conflicts: # shared/src/rpcMethods.ts
There was a problem hiding this comment.
Findings
No reportable issues found.
Summary
Review mode: follow-up after new commits
Reviewed the full 40-file diff at b76e0dc1d0a90d5ff76e40b390ccdfc988ea4518, using the previous bot review and commit comparison as context. Live provider compatibility and browser focus behavior remain unverified.
Testing
Not run (automation; PR code was not executed). Inspected credential, request-boundary, cache, API, and UI tests; diff whitespace checks passed. Suggested coverage: quota-popover reopening after the retry cooldown and browser Tab/Escape dismissal. CI test and Windows jobs were still running when checked.
HAPI Bot
|
Resolved the merge conflict reported against |
Problem / Motivation HAPI needs a user-facing surface for configuring Runner-side quota queries and viewing sanitized quota windows from an existing session context popover. ## Summary - Add a Settings → Agent quota queries page. - Filter the Agent selector using the selected machine's reported availability, matching the Create Session behavior. - Add a HAPI-themed custom SelectMenu with touch, keyboard, Escape, and focus support. - Add template selection, JSON editing, Test, Save, credential status placeholders, and localized English/Chinese copy. - Display sanitized 5-hour and 7-day quota percentages and reset countdowns in the session context popover. - Query only when the popover is opened; Runner caching remains authoritative. - Add the Agent quota query guide, sidebar entry, and REST contract documentation. ## Dependency This PR is stacked on PR #1787 (
feat/usage-query-runner-core), currently at8199cd3e. The Web branch contains the core commits through8199cd3eso it can be tested as one complete feature and includes latest upstreammainsync atb76e0dc1; reviewers should evaluate this UI/documentation layer after the core PR is merged. ## Security and compatibility - The browser receives credential status only, never credential values. - Quota results are sanitized Runner responses. - Existing sessions and unsupported Agent flavors remain unaffected. - The context popover remains usable when quota querying is disabled or unavailable. - User-authored templates are read-only GET requests without a body; mutating provider calls require a reviewed Runner adapter. - Save mutation results are keyed by the machine and Agent captured at submission time.templateIdandtemplate.idpairs. - Core Codex provider selection ignores unselected profile tables, and early Runner HTTP/error exits abort unread response bodies. ## Validation -bun typecheck-bun run --cwd web test -- src/components/AssistantChat/StatusBar.popover.test.tsx src/components/AssistantChat/StatusBar.usage-query.test.tsx src/routes/settings/usage-query.test.tsx src/components/ui/select-menu.test.tsx— 36/36 -bun run --cwd docs docs:build-bun run test:e2e -- terminal-wrap-fidelity.spec.ts— 2/2 -bun run build- Isolated task smoke checks: Agent availability filtering, context quota query, successful adapter result, and repeated-query cache hit. ## Related Issues Refs 能不能增加显示token用量的功能,比如claudecode 5h余量 周余量 #1764 ## AI disclosure AI assistance: OpenAI GPT-5.6.