Skip to content

feat(web): expose agent quota queries in settings and context display - #1788

Open
techotaku39 wants to merge 41 commits into
tiann:mainfrom
techotaku39:feat/usage-query-web
Open

techotaku39 wants to merge 41 commits into
tiann:mainfrom
techotaku39:feat/usage-query-web

Conversation

@techotaku39

@techotaku39 techotaku39 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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 at 8199cd3e. The Web branch contains the core commits through 8199cd3e so it can be tested as one complete feature and includes latest upstream main sync at b76e0dc1; 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.

  • Save mutations are tracked by target-specific mutation keys, so returning to a target with a pending save keeps its editor locked until settlement. - Machine-list failures show a retry action instead of an empty-state false positive.
  • SelectMenu dismissal preserves focus on the newly targeted editor instead of forcing focus back to the trigger. - Test, Save, and Enable validate the editor contents on demand, including malformed JSON. - The HTTP boundary rejects mismatched templateId and template.id pairs. - 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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings

  • [Major] Restrict quota templates to read-only requests — UsageQueryHttpMethodSchema accepts POST, the template also controls the path/body/headers, and test() 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. Evidence shared/src/usageQuery.ts:17, shared/src/usageQuery.ts:42, cli/src/usageQuery/service.ts:193.
    Suggested fix:
    export const UsageQueryHttpMethodSchema = z.literal("GET")
    If a provider truly requires POST, keep that method/path/body fixed inside a reviewed adapter rather than editable in a generic template.
  • [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. Evidence cli/src/usageQuery/service.ts:257, cli/src/usageQuery/service.ts:269, cli/src/usageQuery/service.test.ts:136.
    Suggested fix:
    const fingerprint = templateFingerprint(configured.template)
    if (this.inFlight.get(agent)?.fingerprint === fingerprint) {
        this.cache.set(agent, nextCacheEntry)
    }
    Apply the guard to both cache writes and extend the test with another B query asserting the fetch count remains 2.

Summary
Review mode: initial. Found 1 Major security issue and 1 Minor cache-race regression in the fresh full diff.

HAPI Bot

Comment thread shared/src/usageQuery.ts Outdated
])
export type UsageQueryAdapter = z.infer<typeof UsageQueryAdapterSchema>

export const UsageQueryHttpMethodSchema = z.enum(['GET', 'POST'])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

Comment thread cli/src/usageQuery/service.ts Outdated
fetchImpl: this.fetchImpl,
now: startedAt
})
this.cache.set(agent, {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with the latest core fixes from PR #1787 (core 31a3eac; web branch d61608b). The UI/docs layer remains unchanged by these Runner-only fixes and is ready for stacked review after the core PR.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core 9c2f1bd and web tip 8bf1a90. The docs now state that user quota templates are read-only GET requests; mutating providers require a reviewed adapter.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 now tracks core 6adddf2 and web tip 1733aa0. Documentation explicitly describes the read-only, bodyless GET template boundary.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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. Evidence cli/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 highlightedValue to the selected value, so Home and End do not land on the requested boundary. Evidence web/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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

Comment thread web/src/components/ui/select-menu.tsx Outdated
const highlighted = props.options.find((option) => option.value === highlightedValue && !option.disabled)
?? firstEnabled(props.options)

useEffect(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core 9bbe9e2 and web tip 4384395. No UI behavior changed in this round; the stacked branch includes the credential-bound Runner cache fix.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Findings

  • [Major] Keep async save results keyed to the original machine and agent — onSuccess reads the component's current machineId/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. Evidence web/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 before lastError, so normal requests keep returning the failed/stale result until the full old TTL expires instead of retrying after 30 seconds. Evidence cli/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. Evidence web/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

Comment thread web/src/routes/settings/usage-query.tsx Outdated
template: input.template
}),
onSuccess: (data) => {
queryClient.setQueryData(queryKeys.machineUsageQuery(machineId, agent), data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

Comment thread cli/src/usageQuery/service.ts Outdated
? cached
: undefined
if (!force && usableCached) {
if (now - usableCached.cachedAt < USAGE_QUERY_CACHE_TTL_MS) return usableCached.result

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

Comment thread web/src/components/ui/select-menu.tsx Outdated

useEffect(() => {
if (!open) return
setHighlightedValue(props.value)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@heavygee heavygee added enhancement New feature or request area:web Web PWA / React client area:cli CLI, runner, agent wrappers area:hub Hub server (API, sync, store) community-pr PR from non-collaborator contributor labels Sep 8, 2026
@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core f265ee0 and web tip fb91e0a. The latest Kimi migration fallback fix is included in the stacked branch; Web validations remain green.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread cli/src/usageQuery/credentials.ts Outdated
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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core e41a4c8 and web tip 53cc7c8. The explicit Kimi profile isolation fix is included in the stacked branch; Web validations remain green.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread cli/src/usageQuery/credentials.ts Outdated
}

const providerMatch = /^model_provider\s*=\s*(.+)$/.exec(line)
if (providerMatch && currentProvider === null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core 710c732 and web tip 8c33d91. The latest Codex/Kimi credential-boundary and literal-key URL fixes are included in the stacked branch; Web validations remain green.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core 8199cd3 and web tip e220a5b. The latest Codex profile-scope and response-cleanup fixes are included in the stacked branch; Web validations remain green.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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
}) > 0

Summary

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

Comment thread web/src/routes/settings/usage-query.tsx Outdated
onChange={(event) => { setTemplateText(event.target.value); setEditorError(null) }}
spellCheck={false}
rows={18}
disabled={saveMutation.isPending}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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
}) > 0

Regression: 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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

PR #1788 is synced with core 8199cd3 and Web tip e220a5b. The latest Codex profile-scope and response-cleanup fixes are included in the stacked branch; Web validations remain green.

@techotaku39

Copy link
Copy Markdown
Contributor Author

Addressed the latest Minor review in 6f225d9: save mutations now use target-specific mutation keys and useIsMutating so an outstanding save remains visible after switching away and back to the same machine/Agent. The template selector and JSON editor stay disabled until that target's save settles. Extended the deferred-save regression to cover A → B → A; Web quota tests pass 17/17, with typecheck, docs build, and full build passing.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

Comment thread web/src/components/ui/select-menu.tsx Outdated
onOpenAutoFocus={(event) => event.preventDefault()}
onCloseAutoFocus={(event) => {
event.preventDefault()
triggerRef.current?.focus()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[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.

@techotaku39

Copy link
Copy Markdown
Contributor Author

Resolved the PR #1788 merge conflict by merging the latest upstream main into the Web branch at 6a115a61. The sole conflict in web/src/api/client.test.ts retains both the usage-query client coverage and upstream AGY refresh coverage. Web targeted tests pass 26/26; typecheck, docs build, and full build pass.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@techotaku39

Copy link
Copy Markdown
Contributor Author

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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

@techotaku39

Copy link
Copy Markdown
Contributor Author

Resolved the merge conflict reported against main by merging upstream main at 092a2259c into the Web branch. The conflict in shared/src/rpcMethods.ts retains both the usage-query RPC methods and upstream ClearConversation; pushed merge commit is b76e0dc1. Validation completed locally: bun typecheck, Web usage-query/UI tests 36/36, bun run --cwd docs docs:build, and bun run build. PR #1788 is now mergeable; CI has restarted for the new head.

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

Labels

area:cli CLI, runner, agent wrappers area:hub Hub server (API, sync, store) area:web Web PWA / React client community-pr PR from non-collaborator contributor enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants