fix(ai): org-decisive active matching on either side; org in status-line cache key and health results

Addresses the second review round (internal re-review + Codex on c38840482):

- Active-account matching (logout preselection, /usage in-use marker) is
  org-decisive when EITHER side carries an org: a legacy bare-email active
  row no longer flags org-scoped siblings via the shared email (reverse of
  the previous fix). Both-org-less keeps the email/account fallback, so
  providers without orgs are unaffected.
- Status-line usage context key includes orgId, so rotating between two
  same-email subscriptions invalidates the cached quota immediately
  instead of showing the previous org's numbers for the cache TTL.
- CredentialHealthResult carries orgId/orgName and auth-gateway check
  labels rows with the org, so a failing row names the subscription.
- getOAuthAccountIdentity preserves org-only identities; the login
  success message renders them.
- ACP /usage account-id fallback labels get the org suffix too.
- Regression tests for both matching directions (marker + logout).
This commit is contained in:
chan1103
2026-07-11 17:47:23 +09:00
parent bee01bfc4a
commit a47c3c90ec
10 changed files with 73 additions and 25 deletions
@@ -780,7 +780,16 @@ export class StatusLineComponent implements Component {
const activeProvider = session.state.model?.provider ?? session.model?.provider ?? "";
if (!activeProvider) return "";
const identity = session.modelRegistry?.authStorage?.getOAuthAccountIdentity(activeProvider, session.sessionId);
return [activeProvider, identity?.accountId ?? "", identity?.email ?? "", identity?.projectId ?? ""].join("\0");
// orgId is part of the key: rotating between two same-email Anthropic
// subscriptions must invalidate the cached usage immediately instead of
// showing the previous org's quota for the rest of the cache TTL.
return [
activeProvider,
identity?.accountId ?? "",
identity?.email ?? "",
identity?.projectId ?? "",
identity?.orgId ?? "",
].join("\0");
}
/**