Skip to content

Feat/lazy account mode#154

Merged
james-6-23 merged 5 commits into
james-6-23:mainfrom
abwuge:feat/lazy-account-mode
May 23, 2026
Merged

Feat/lazy account mode#154
james-6-23 merged 5 commits into
james-6-23:mainfrom
abwuge:feat/lazy-account-mode

Conversation

@abwuge
Copy link
Copy Markdown
Contributor

@abwuge abwuge commented May 22, 2026

新增惰性模式,只被动获取账号信息,或许可以降低封禁概率?

Summary by CodeRabbit

  • New Features
    • Introduced "Lazy Mode" system setting (default: off) to control background refresh and account maintenance behavior
    • Account list shows both record and usage "last updated" timestamps when lazy mode is active
    • Settings UI includes a persisted Lazy Mode toggle and locks related scheduler/cleanup controls when enabled
    • Added English and Chinese translations for new UI text and timestamps

Review Change Stack

Copilot AI review requested due to automatic review settings May 22, 2026 23:26
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 22, 2026

📝 Walkthrough

Walkthrough

Adds a persistent system setting "lazy_mode" and implements lazy-mode behavior across database migrations, auth store/scheduler logic (selection, refresh gating), admin settings API, and frontend UI and labels.

Changes

Lazy Mode Feature

Layer / File(s) Summary
Database schema and frontend types
database/postgres.go, database/sqlite.go, frontend/src/types.ts, frontend/src/locales/*.json
Adds lazy_mode column to SQLite/Postgres migrations and SystemSettings.LazyMode in Go; frontend types add lazy_mode and codex_usage_updated_at; locale keys for timestamps and lazy mode added.
Defaults and bootstrap initialization
auth/store.go, admin/bootstrap.go, main.go
Initialization paths set LazyMode: false by default when creating or falling back to system settings.
Store accessors and background gating
auth/store.go
Adds GetLazyMode()/SetLazyMode(); background refresh, usage/recovery probes, and available counting are gated/no-op under lazy mode.
Lazy selectability & readiness helpers
auth/store.go
Introduces accountLazySelectable, dispatch-readiness helpers (ensureLazyDispatchReady, lazyNeedsDispatchRefresh, triggerLazyRefreshAsync) and metadata-refresh candidate checks.
Lazy-mode selection loop and acquisition
auth/store.go
Implements nextExcludingWithFilterLazy with bounded attempts, scoring, optional metadata refresh, readiness enforcement, and concurrency-limited acquisition.
Request-time acquisition & checks
auth/store.go
takeByIDExcluding, hasDispatchCandidateWithFilter, and AvailableCount use lazy-selectable predicates and lazy acquisition flows when enabled.
Admin account fields and refresh gating
admin/handler.go
accountResponse exposes codex_usage_updated_at; ListAccounts populates it; AddAccount and import path skip async refresh when lazy mode is enabled.
Settings API persistence and propagation
admin/handler.go, database/postgres.go
API includes lazy_mode in Get/Update endpoints; UpdateSettings calls SetLazyMode(), persists LazyMode to DB, and returns it.
Frontend data loading and UI changes
frontend/src/hooks/useDataLoader.ts, frontend/src/pages/Accounts.tsx, frontend/src/pages/Settings.tsx
useDataLoader accepts LoadOptions; Accounts loader reads/caches lazy_mode and conditionally renders record+usage timestamps; Settings page adds lazy_mode, normalizes saves, and locks scheduler fields when active.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I nibble code in quiet mode,
I skip the churn, avoid the load,
A toggle set to gentle pace,
Timestamps show a patient face,
Hooray, the lazy rabbit codes with grace. 🐰

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Feat/lazy account mode' directly describes the main feature being added across the entire changeset—a lazy account mode that passively fetches account information.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a “Lazy Mode” system setting that disables proactive refresh/probing behavior, and surfaces additional “usage updated” timestamps in the Accounts UI when Lazy Mode is enabled.

Changes:

  • Introduces lazy_mode in settings across backend (store + admin API), DB migrations (Postgres/SQLite), and frontend types/UI.
  • Updates background refresh / probe behavior to no-op under Lazy Mode; adjusts account dispatch selection logic for Lazy Mode.
  • Enhances Accounts table to show separate “record updated” vs “usage updated” times in Lazy Mode, with new i18n strings.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
main.go Adds default LazyMode: false in startup settings initialization.
frontend/src/types.ts Extends types for lazy_mode and codex_usage_updated_at.
frontend/src/pages/Settings.tsx Adds Lazy Mode control and disables related controls when enabled.
frontend/src/pages/Accounts.tsx Fetches settings to detect Lazy Mode and conditionally renders extra timestamps.
frontend/src/locales/en.json Adds i18n strings for Lazy Mode and updated-at labels.
frontend/src/locales/zh.json Adds i18n strings for Lazy Mode and updated-at labels.
database/sqlite.go Adds lazy_mode column to system_settings and migration list.
database/postgres.go Adds lazy_mode column, plumbs through SystemSettings read/write.
auth/store.go Implements Lazy Mode flag, disables background jobs, adds lazy dispatch path.
admin/handler.go Adds Lazy Mode to settings API; avoids async refresh on add/import when lazy.
admin/bootstrap.go Adds default LazyMode: false to bootstrap settings.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

<SettingField label={t('settings.autoCleanFullUsage')} description={t('settings.autoCleanFullUsageDesc')}>
<Select
value={settingsForm.auto_clean_full_usage ? 'true' : 'false'}
value={lazyModeActive ? 'false' : settingsForm.auto_clean_full_usage ? 'true' : 'false'}
Comment on lines 696 to +700
type="number"
min={1}
max={1440}
value={settingsForm.background_refresh_interval_minutes}
value={lazyModeActive ? 0 : settingsForm.background_refresh_interval_minutes}
disabled={lazyModeActive}
Comment on lines +405 to 418
const [
accountsResponse,
apiKeysResponse,
opsOverview,
groupsResponse,
settings,
] =
await Promise.all([
api.getAccounts(),
api.getAPIKeys(),
api.getOpsOverview().catch((): OpsOverviewResponse | null => null),
api.listAccountGroups().catch(() => ({ groups: [] })),
api.getSettings().catch((): SystemSettings | null => null),
]);
Comment thread frontend/src/pages/Accounts.tsx Outdated
accounts: accountsResponse.accounts ?? [],
apiKeys: apiKeysResponse.keys ?? [],
opsOverview,
lazyMode: settings?.lazy_mode ?? false,
Comment thread auth/store.go Outdated
Comment on lines +2645 to +2654
if acc.NeedsRefresh() && hasRefreshCredential {
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
err := s.refreshAccount(ctx, acc)
cancel()
if err != nil {
log.Printf("[账号 %d] lazy mode 调度前刷新失败: %v", acc.DBID, err)
return false
}
}
return acc.IsAvailable()
Comment thread auth/store.go
Comment on lines 4244 to 4250
defer s.mu.RUnlock()
count := 0
for _, acc := range s.accounts {
if acc.IsAvailable() {
if (s.GetLazyMode() && s.accountLazySelectable(acc)) || (!s.GetLazyMode() && acc.IsAvailable()) {
count++
}
}
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/Settings.tsx (1)

464-469: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist lazy-mode forced values when saving.

The UI forces background_refresh_interval_minutes and auto_clean_full_usage in lazy mode, but api.updateSettings(settingsForm) can still submit stale underlying values. That can save a config different from what users see.

Suggested fix
 const handleSaveSettings = async () => {
   setSavingSettings(true)
   try {
     const adminSecretChanged = settingsForm.admin_auth_source !== 'env' && settingsForm.admin_secret !== loadedAdminSecret
-    const updated = await api.updateSettings(settingsForm)
+    const payload = settingsForm.lazy_mode
+      ? {
+          ...settingsForm,
+          background_refresh_interval_minutes: 0,
+          auto_clean_full_usage: false,
+        }
+      : settingsForm
+    const updated = await api.updateSettings(payload)
     setSettingsForm(updated)

Also applies to: 699-701, 904-907

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Settings.tsx` around lines 464 - 469, The save path can
submit stale values for fields that the UI forces in lazy mode
(background_refresh_interval_minutes and auto_clean_full_usage), so before
calling api.updateSettings from handlers like handleSaveSettings (and the
similar save handlers around the other save flows), ensure you overwrite those
keys on the payload with the enforced UI values when lazy mode is active; i.e.,
compute the effective payload from settingsForm, then if
lazyMode/isLazyModeEnabled (or the UI condition that forces values) set
payload.background_refresh_interval_minutes = <forced value> and
payload.auto_clean_full_usage = <forced value> and then call
api.updateSettings(payload), then setSettingsForm(updated).
🧹 Nitpick comments (1)
frontend/src/pages/Accounts.tsx (1)

404-418: ⚡ Quick win

Avoid fetching settings on every silent accounts reload.

loadAccounts now calls api.getSettings() on each reload, including polling-driven reloadSilently(). Consider caching lazy_mode (or sourcing it from accounts payload) to avoid repeated settings calls on hot refresh paths.

Also applies to: 515-520, 721-726

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Accounts.tsx` around lines 404 - 418, loadAccounts
currently calls api.getSettings() on every invocation (including polling-driven
reloadSilently), causing unnecessary requests; modify loadAccounts (and the
other callers like reloadSilently) to avoid fetching settings on silent reloads
by caching the needed setting (lazy_mode) in component state (e.g., lazyMode) or
sourcing it from the accounts payload, and only call api.getSettings() when the
cache is empty or when a full/manual refresh is requested; update places that
call api.getSettings() (the loadAccounts implementation and the other load paths
referenced) to read from the cached lazyMode state instead of always calling
api.getSettings().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@frontend/src/pages/Settings.tsx`:
- Around line 464-469: The save path can submit stale values for fields that the
UI forces in lazy mode (background_refresh_interval_minutes and
auto_clean_full_usage), so before calling api.updateSettings from handlers like
handleSaveSettings (and the similar save handlers around the other save flows),
ensure you overwrite those keys on the payload with the enforced UI values when
lazy mode is active; i.e., compute the effective payload from settingsForm, then
if lazyMode/isLazyModeEnabled (or the UI condition that forces values) set
payload.background_refresh_interval_minutes = <forced value> and
payload.auto_clean_full_usage = <forced value> and then call
api.updateSettings(payload), then setSettingsForm(updated).

---

Nitpick comments:
In `@frontend/src/pages/Accounts.tsx`:
- Around line 404-418: loadAccounts currently calls api.getSettings() on every
invocation (including polling-driven reloadSilently), causing unnecessary
requests; modify loadAccounts (and the other callers like reloadSilently) to
avoid fetching settings on silent reloads by caching the needed setting
(lazy_mode) in component state (e.g., lazyMode) or sourcing it from the accounts
payload, and only call api.getSettings() when the cache is empty or when a
full/manual refresh is requested; update places that call api.getSettings() (the
loadAccounts implementation and the other load paths referenced) to read from
the cached lazyMode state instead of always calling api.getSettings().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0832fc8c-a34e-4e57-8125-e9a732a4468d

📥 Commits

Reviewing files that changed from the base of the PR and between 0627e0b and cd0e302.

📒 Files selected for processing (11)
  • admin/bootstrap.go
  • admin/handler.go
  • auth/store.go
  • database/postgres.go
  • database/sqlite.go
  • frontend/src/locales/en.json
  • frontend/src/locales/zh.json
  • frontend/src/pages/Accounts.tsx
  • frontend/src/pages/Settings.tsx
  • frontend/src/types.ts
  • main.go

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@auth/store.go`:
- Around line 2637-2642: The helper currently schedules a background refresh via
s.triggerLazyRefreshAsync(acc) when s.lazyNeedsDispatchRefresh(acc) is true but
then returns false, causing callers (e.g., NextExcludingWithFilter* and dispatch
code) to drop an otherwise still-valid access token; change the helper so that
after calling s.triggerLazyRefreshAsync(acc) it does NOT return false
immediately but instead returns acc.IsAvailable() (i.e., keep using the token
while refresh runs), making sure you only treat the account as unavailable when
it actually reports unavailable/expired and not just when
lazyNeedsDispatchRefresh flips ~5 minutes before expiry.
- Around line 2726-2729: The loop calling s.lazyNeedsDispatchRefresh(acc) then
s.triggerLazyRefreshAsync(acc) is redundant and causes many candidates to be
dispatched during a full account scan; remove this trigger so only the candidate
selected by acquireLazyCandidate() (which already goes through
ensureLazyDispatchReady()) is refreshed. Locate the code that calls
s.lazyNeedsDispatchRefresh(acc) and s.triggerLazyRefreshAsync(acc) inside the
full scan and delete that conditional block (or guard it with a check that the
account equals the one returned by acquireLazyCandidate()), leaving lazy
dispatch responsibility solely to acquireLazyCandidate() /
ensureLazyDispatchReady().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b3dc038-425c-450d-bd80-86357778b27b

📥 Commits

Reviewing files that changed from the base of the PR and between cd0e302 and 00109e8.

📒 Files selected for processing (4)
  • auth/store.go
  • frontend/src/hooks/useDataLoader.ts
  • frontend/src/pages/Accounts.tsx
  • frontend/src/pages/Settings.tsx

Comment thread auth/store.go
Comment on lines +2637 to +2642
if s.lazyNeedsDispatchRefresh(acc) {
s.triggerLazyRefreshAsync(acc)
return false
}
return acc.IsAvailable()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep using a still-valid access token while refresh runs.

NeedsRefresh() flips 5 minutes before expiry, but this helper now returns false immediately after scheduling the background refresh. That makes lazy mode drop otherwise usable accounts for that whole window, and direct NextExcludingWithFilter* callers can get nil even though dispatch could still succeed.

Suggested fix
 func (s *Store) ensureLazyDispatchReady(acc *Account) bool {
 	if acc == nil {
 		return false
 	}
 	if s.lazyNeedsDispatchRefresh(acc) {
 		s.triggerLazyRefreshAsync(acc)
-		return false
+		if acc.GetAccessToken() == "" {
+			return false
+		}
 	}
 	return acc.IsAvailable()
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if s.lazyNeedsDispatchRefresh(acc) {
s.triggerLazyRefreshAsync(acc)
return false
}
return acc.IsAvailable()
}
if s.lazyNeedsDispatchRefresh(acc) {
s.triggerLazyRefreshAsync(acc)
if acc.GetAccessToken() == "" {
return false
}
}
return acc.IsAvailable()
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/store.go` around lines 2637 - 2642, The helper currently schedules a
background refresh via s.triggerLazyRefreshAsync(acc) when
s.lazyNeedsDispatchRefresh(acc) is true but then returns false, causing callers
(e.g., NextExcludingWithFilter* and dispatch code) to drop an otherwise
still-valid access token; change the helper so that after calling
s.triggerLazyRefreshAsync(acc) it does NOT return false immediately but instead
returns acc.IsAvailable() (i.e., keep using the token while refresh runs),
making sure you only treat the account as unavailable when it actually reports
unavailable/expired and not just when lazyNeedsDispatchRefresh flips ~5 minutes
before expiry.

Comment thread auth/store.go
Comment on lines +2726 to +2729
if s.lazyNeedsDispatchRefresh(acc) {
s.triggerLazyRefreshAsync(acc)
continue
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Only refresh the chosen lazy-mode candidate.

This runs inside the full account scan, so a single request can kick off refreshes for every near-expiry candidate in the pool. That defeats the passive/lazy goal and creates unnecessary upstream bursts. acquireLazyCandidate() already sends the selected account through ensureLazyDispatchReady(), so this trigger is redundant there.

Suggested fix
-			if s.lazyNeedsDispatchRefresh(acc) {
-				s.triggerLazyRefreshAsync(acc)
-				continue
-			}
-
 			load := atomic.LoadInt64(&acc.ActiveRequests)
 			tier, _, dispatchScore, limit := acc.schedulerSnapshot(maxConcurrency)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if s.lazyNeedsDispatchRefresh(acc) {
s.triggerLazyRefreshAsync(acc)
continue
}
load := atomic.LoadInt64(&acc.ActiveRequests)
tier, _, dispatchScore, limit := acc.schedulerSnapshot(maxConcurrency)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@auth/store.go` around lines 2726 - 2729, The loop calling
s.lazyNeedsDispatchRefresh(acc) then s.triggerLazyRefreshAsync(acc) is redundant
and causes many candidates to be dispatched during a full account scan; remove
this trigger so only the candidate selected by acquireLazyCandidate() (which
already goes through ensureLazyDispatchReady()) is refreshed. Locate the code
that calls s.lazyNeedsDispatchRefresh(acc) and s.triggerLazyRefreshAsync(acc)
inside the full scan and delete that conditional block (or guard it with a check
that the account equals the one returned by acquireLazyCandidate()), leaving
lazy dispatch responsibility solely to acquireLazyCandidate() /
ensureLazyDispatchReady().

@james-6-23 james-6-23 merged commit b8f0783 into james-6-23:main May 23, 2026
6 checks passed
@abwuge abwuge deleted the feat/lazy-account-mode branch May 23, 2026 06:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants