Skip to content

feat(zoo-gateway): auth callback and multi-profile token sync#347

Merged
JamesRobert20 merged 12 commits into
mainfrom
feat/zoo-gateway-auth-sync
Jun 3, 2026
Merged

feat(zoo-gateway): auth callback and multi-profile token sync#347
JamesRobert20 merged 12 commits into
mainfrom
feat/zoo-gateway-auth-sync

Conversation

@JamesRobert20
Copy link
Copy Markdown
Contributor

@JamesRobert20 JamesRobert20 commented May 27, 2026

Summary

  • Propagates OAuth callback token to all ClineProvider instances (sequential writes to avoid profile-store races)
  • Seeds/syncs zoo-gateway profiles, including non-active profiles for model fetch
  • Clears in-memory active profile on sign-out even when disk is already clean

Part 3 of the Zoo Gateway stack. Depends on PR2 (stacked on #344).

Test plan

  • handleUri multi-instance + serialization tests
  • webviewMessageHandler separate-profile lookup test
  • Manual: sign in/out across multiple profiles

Summary by CodeRabbit

  • New Features

    • Zoo Code auth callbacks now propagate sequentially to all active provider instances.
    • Zoo Gateway profiles are auto-seeded/updated when cached auth tokens exist but profiles are missing or stale.
    • Sign-out now clears Zoo session tokens across all Zoo Gateway profiles.
  • Error Handling

    • Centralized Zoo Gateway error classification and user-facing surfaces for auth, session, credit, and account issues.
  • Internationalization

    • Added localized error messages and action buttons across 16 locales.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 78828f04-cdb7-4e78-93a2-c070471a3418

📥 Commits

Reviewing files that changed from the base of the PR and between 42b3f0c and bc30b7b.

📒 Files selected for processing (2)
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/webview/webviewMessageHandler.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/webview/webviewMessageHandler.ts
  • src/core/webview/tests/ClineProvider.spec.ts

📝 Walkthrough

Walkthrough

Synchronizes Zoo Gateway session tokens across all active editor instances, adds session-cleared-aware token resolution, centralizes gateway API error classification and UX surfacing, seeds and persists zoo-gateway provider profiles at webview init, and updates model fetchers, sign-out, tests, and i18n strings accordingly.

Changes

Zoo Gateway multi-instance token propagation and profile persistence

Layer / File(s) Summary
Session token resolution and state tracking
src/services/zoo-code-auth.ts, src/services/__tests__/zoo-code-auth.test.ts
Adds _sessionCleared state and resolveZooGatewaySessionToken(); treats 5xx verification as transient ("unreachable") and adjusts set/clear lifecycle.
Error classification and user-facing error handling
src/api/providers/zoo-gateway.ts, src/api/providers/__tests__/zoo-gateway.spec.ts
Adds classifyGatewayApiError() and surfaceGatewayApiError() to map gateway errors to actions (clear token, show i18n message, open URLs); wraps streaming/non-streaming flows to surface UX errors before rethrow.
Model fetcher session token integration
src/api/providers/fetchers/zoo-gateway.ts, src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts
Switches /models bearer token source to resolveZooGatewaySessionToken() and updates tests to mock it.
Multi-instance authorization callback propagation
src/activate/handleUri.ts, src/core/webview/ClineProvider.ts, src/activate/__tests__/handleUri.spec.ts
Adds ClineProvider.getAllInstances() and propagateZooGatewayCallback(token) to sequentially invoke handleZooCodeCallback() across all instances; tests verify fan-out, ordering, and partial-failure behavior.
Provider profile persistence and initialization seeding
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/ClineProvider.spec.ts
Implements ensureZooGatewayProfileSeeded() on init; refactors handleZooCodeCallback() to create/update all zoo-gateway profiles with token/baseUrl and activate only the active profile in-memory.
Router model aggregation and multi-instance sign-out cleanup
src/core/webview/webviewMessageHandler.ts, src/core/webview/__tests__/webviewMessageHandler.spec.ts
Adds zoo-gateway to requestRouterModels candidates; expands zooCodeSignOut to clear zooSessionToken across all zoo-gateway profiles (in-memory update for active profile, persisted cleanup for others).
Welcome component API configuration validation
webview-ui/src/components/welcome/WelcomeViewProvider.tsx
Forwards zooCodeIsAuthenticated into getWelcomeApiConfiguration/validation and initial setup flows.
Localized user-facing error and button messages
src/i18n/locales/*/common.json
Adds zooAuth.errors and zooAuth.buttons keys across locale files for surfaced gateway error messages and action buttons.
Tests and mocks updated
src/activate/__tests__/handleUri.spec.ts, src/api/providers/__tests__/zoo-gateway.spec.ts, src/core/webview/__tests__/*, src/api/providers/fetchers/__tests__/*
Extends and adapts tests and mocks to cover multi-instance propagation, serialized callbacks, token resolution, error classification/surfacing, profile seeding, and sign-out cleanup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Zoo-Code-Org/Zoo-Code#32: Also modifies /auth-callback handling in src/activate/handleUri.ts; related to propagation vs visible-only refresh.
  • Zoo-Code-Org/Zoo-Code#344: Prior Zoo Gateway integration changes that this PR extends (token resolution & handler/fetcher scaffolding).

Suggested reviewers

  • hannesrudolph
  • navedmerchant
  • edelauna
  • taltas

🐰 I hop and carry tokens, neat,
Across each window, step by step I beat,
Seed the profiles, fan the call,
So every instance hears the mall—
A tiny rabbit keeps auth sweet.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description lacks required template sections: no GitHub Issue link, no detailed description of implementation, and incomplete test procedure with only checkboxes. Add the GitHub Issue number in 'Related GitHub Issue' section, provide detailed implementation notes in 'Description', complete the test procedure with specific steps, and check all pre-submission checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: OAuth callback token propagation and multi-profile token synchronization for Zoo Gateway.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/zoo-gateway-auth-sync

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

src/core/webview/__tests__/ClineProvider.spec.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/core/webview/webviewMessageHandler.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.


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.

@JamesRobert20 JamesRobert20 mentioned this pull request May 27, 2026
1 task
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from adedf0a to 8634510 Compare May 27, 2026 14:25
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 654117d to 1326a97 Compare May 27, 2026 14:29
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from 8634510 to 7bc8c0c Compare May 27, 2026 14:42
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch 3 times, most recently from f245208 to 078bce0 Compare May 27, 2026 15:10
@codecov
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

❌ Patch coverage is 82.93515% with 50 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/api/providers/zoo-gateway.ts 85.15% 18 Missing and 1 partial ⚠️
src/core/webview/webviewMessageHandler.ts 67.50% 13 Missing ⚠️
src/core/webview/ClineProvider.ts 86.25% 11 Missing ⚠️
src/services/zoo-code-auth.ts 71.42% 6 Missing ⚠️
src/activate/handleUri.ts 92.85% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

JamesRobert20 pushed a commit that referenced this pull request May 27, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@proyectoauraorg proyectoauraorg left a comment

Choose a reason for hiding this comment

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

Review: Auth Callback + Multi-Profile Token Sync

Verdict: ✅ Approved (with security notes for future hardening)

Verified

  • handleUri.spec.ts — 8 tests pass (multi-instance fan-out, sequential serialization)
  • ClineProvider.spec.ts — 82 tests pass (6 new auth profile sync tests)
  • webviewMessageHandler.spec.ts — 44 tests pass (2 new sign-out tests)
  • ✅ Sequential execution of callbacks avoids profile-store race conditions
  • ✅ Sign-out clears tokens from ALL zoo-gateway profiles (correct symmetry)
  • ✅ Model list fetch recovers credentials from non-active profiles

Security Notes (future hardening, not blockers)

  1. Token storage: zooSessionToken is stored in JSON-serialized ProviderSettings on disk. VS Code's globalStorage is OS-protected, but migrating to SecretStorage would add an extra layer of defense.
  2. Base URL validation: zooGatewayBaseUrl is derived from getZooCodeBaseUrl(). In production this is fine, but a misconfigured ZOO_CODE_BASE_URL env var could route tokens to an unintended host. Consider adding domain validation.

Both are hardening improvements for a future iteration, not blockers for this PR.

Dependency

Depends on #345. After #345 merges, this should rebase cleanly.

JamesRobert20 pushed a commit that referenced this pull request May 28, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 65885a8 to 13c98d0 Compare May 28, 2026 20:26
JamesRobert20 pushed a commit that referenced this pull request May 28, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 13c98d0 to d4050f8 Compare May 28, 2026 20:33
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from a40f6e9 to 983c133 Compare May 28, 2026 21:19
JamesRobert20 pushed a commit that referenced this pull request May 28, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from d3165a0 to 7e54cf1 Compare May 28, 2026 21:29
JamesRobert20 pushed a commit that referenced this pull request May 28, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 7e54cf1 to 57a128a Compare May 28, 2026 23:33
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from 33892e3 to 6ae680e Compare May 29, 2026 03:04
JamesRobert20 pushed a commit that referenced this pull request May 29, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 3079c57 to 795d179 Compare May 29, 2026 03:08
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from 6ae680e to 946d6d1 Compare May 29, 2026 03:15
JamesRobert20 pushed a commit that referenced this pull request May 29, 2026
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 795d179 to 8dab953 Compare May 29, 2026 03:16
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 44ed33a to bf91fde Compare June 2, 2026 13:55
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from e2e4536 to babbe9e Compare June 2, 2026 13:55
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch 2 times, most recently from faa5854 to 94b8bc6 Compare June 2, 2026 16:02
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-settings-ui branch from 5aa42cc to 2fbf902 Compare June 3, 2026 00:49
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 94b8bc6 to 0dbdf07 Compare June 3, 2026 00:51
Copy link
Copy Markdown
Contributor

@taltas taltas left a comment

Choose a reason for hiding this comment

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

Couple of small code cleaness tweaks.

Comment thread src/api/providers/zoo-gateway.ts Outdated
Comment on lines +50 to +90
async function surfaceGatewayApiError(error: unknown): Promise<void> {
const status = getApiErrorStatus(error)
if (status === undefined) return
const code = getApiErrorCode(error)

if (status === 401) {
// Wipe before sign-in so the callback rebinds against an empty slot.
await clearZooCodeToken()
const action = await vscode.window.showErrorMessage(
t("common:zooAuth.errors.session_expired"),
t("common:zooAuth.buttons.sign_in"),
)
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(buildZooCodeSignInUrl()))
}
return
}

const isBudgetExceeded = status === 429 && (code === "monthly_budget_exceeded" || code === "daily_budget_exceeded")
if (status === 402 || isBudgetExceeded) {
const message = isBudgetExceeded
? t("common:zooAuth.errors.budget_exceeded")
: t("common:zooAuth.errors.out_of_credits")
const action = await vscode.window.showErrorMessage(message, t("common:zooAuth.buttons.add_credits"))
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(`${getZooCodeBaseUrl()}/dashboard/credits`))
}
return
}

if (status === 403) {
const action = await vscode.window.showErrorMessage(
t("common:zooAuth.errors.account_unavailable"),
t("common:zooAuth.buttons.contact_support"),
)
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(`${getZooCodeBaseUrl()}/support`))
}
return
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should split this function out into transport behaviour and VS Code UX side effects:

Suggested change
async function surfaceGatewayApiError(error: unknown): Promise<void> {
const status = getApiErrorStatus(error)
if (status === undefined) return
const code = getApiErrorCode(error)
if (status === 401) {
// Wipe before sign-in so the callback rebinds against an empty slot.
await clearZooCodeToken()
const action = await vscode.window.showErrorMessage(
t("common:zooAuth.errors.session_expired"),
t("common:zooAuth.buttons.sign_in"),
)
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(buildZooCodeSignInUrl()))
}
return
}
const isBudgetExceeded = status === 429 && (code === "monthly_budget_exceeded" || code === "daily_budget_exceeded")
if (status === 402 || isBudgetExceeded) {
const message = isBudgetExceeded
? t("common:zooAuth.errors.budget_exceeded")
: t("common:zooAuth.errors.out_of_credits")
const action = await vscode.window.showErrorMessage(message, t("common:zooAuth.buttons.add_credits"))
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(`${getZooCodeBaseUrl()}/dashboard/credits`))
}
return
}
if (status === 403) {
const action = await vscode.window.showErrorMessage(
t("common:zooAuth.errors.account_unavailable"),
t("common:zooAuth.buttons.contact_support"),
)
if (action) {
void vscode.env.openExternal(vscode.Uri.parse(`${getZooCodeBaseUrl()}/support`))
}
return
}
}
type ZooGatewayApiErrorAction =
| { kind: "sign_in" }
| { kind: "add_credits" }
| { kind: "contact_support" }
| { kind: "none" }
function classifyGatewayApiError(error: unknown): ZooGatewayApiErrorAction {
const status = getApiErrorStatus(error)
const code = getApiErrorCode(error)
...
}
async function surfaceGatewayApiError(error: unknown): Promise<void> {
switch (classifyGatewayApiError(error).kind) {
case "sign_in":
...
return
case "add_credits":
...
return
case "contact_support":
...
return
default:
return
}
}

This will make it easier to test and follow the logic.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 06fc185. Split into a pure classifyGatewayApiError(error) -> ZooGatewayApiErrorAction and a thin surfaceGatewayApiError that switches on the action for the VS Code side effects. The classifier is exported and now has focused unit tests covering 401/402/429-budget/429-non-budget/403/no-status, so the mapping is verified without the notification mocks.

Comment thread src/activate/handleUri.ts Outdated
Comment on lines +64 to +73
const allInstances = ClineProvider.getAllInstances()
for (const instance of allInstances) {
try {
await instance.handleZooCodeCallback(token)
} catch (error) {
console.error(
"Failed to persist Zoo Gateway token for a provider instance:",
error instanceof Error ? error.message : error,
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is doing more than just routing now. We should split this into a helper function to keep it DRY.

Suggested change
const allInstances = ClineProvider.getAllInstances()
for (const instance of allInstances) {
try {
await instance.handleZooCodeCallback(token)
} catch (error) {
console.error(
"Failed to persist Zoo Gateway token for a provider instance:",
error instanceof Error ? error.message : error,
)
}
async function propagateZooGatewayCallback(token: string) {
const allInstances = ClineProvider.getAllInstances()
for (const instance of allInstances) {
try {
await instance.handleZooCodeCallback(token)
} catch (error) {
console.error(
"Failed to persist Zoo Gateway token for a provider instance:",
error instanceof Error ? error.message : error,
)
}
}
}
....
await propagateZooGatewayCallback(token)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done in 06fc185. Extracted the fan-out into propagateZooGatewayCallback(token), so the /auth-callback case just calls that after setZooCodeUserInfo. The sequential read-modify-write and per-instance error isolation (plus the rationale comment) moved into the helper unchanged.

JamesRobert20 added a commit that referenced this pull request Jun 3, 2026
…llback fan-out

Address review feedback on PR #347:

- zoo-gateway.ts: split surfaceGatewayApiError into a pure
  classifyGatewayApiError (error -> action) plus a thin UX layer that
  switches on the action. The classifier is exported and covered by
  focused unit tests, so the status/code -> action mapping no longer
  needs the VS Code notification mocks to verify.
- handleUri.ts: extract the per-instance token propagation loop into a
  propagateZooGatewayCallback helper, keeping the /auth-callback case
  focused on routing. Behaviour (sequential read-modify-write, per
  instance error isolation) is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 changed the base branch from feat/zoo-gateway-settings-ui to main June 3, 2026 01:30
James Mtendamema and others added 11 commits June 2, 2026 19:35
Co-authored-by: Cursor <cursoragent@cursor.com>
Add ClineProvider tests for handleZooCodeCallback, ensureZooGatewayProfileSeeded, and webviewMessageHandler zooCodeSignOut to satisfy codecov patch on PR #347.

Co-authored-by: Cursor <cursoragent@cursor.com>
Clear the cached token on 401 and offer sign-in. On insufficient credits
or budget limits, open the credits page. On account frozen/banned, open
support. Errors still propagate to the task layer after the toast.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ocales

Adds session_expired, out_of_credits, account_unavailable, budget_exceeded
under zooAuth.errors and a new zooAuth.buttons block (sign_in, add_credits,
contact_support) introduced by the gateway 401/402/403 UX so check-translations
passes.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ov patch

Adds vscode + i18n mocks and asserts the 401/402/403/429 paths in
surfaceGatewayApiError: token clear + sign-in URL on 401, add-credits
URL on 402 and budget-coded 429, support URL on 403, no-op on 429
without a budget code or on errors without a status. Also verifies the
helper still runs before completePrompt rewraps the upstream error.

Co-authored-by: Cursor <cursoragent@cursor.com>
…rors

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…l fetch

Co-authored-by: Cursor <cursoragent@cursor.com>
The website's /api/extension/auth/verify route now returns 503 when the
backend can't reach the database, instead of crashing. The extension
previously treated any non-OK response from this endpoint as a
definitively invalid token, which meant a transient backend hiccup
would silently clear the user's session and force a fresh sign-in.

verifyZooCodeToken now returns "unreachable" for 5xx responses (same
classification as a network error), so initZooCodeAuth keeps the cached
token in place and reports subscription status as "unknown" until the
backend recovers. handleAuthCallback shows the could-not-verify message
on 5xx so users see this is a temporary issue rather than a bad token.

Co-authored-by: Cursor <cursoragent@cursor.com>
…llback fan-out

Address review feedback on PR #347:

- zoo-gateway.ts: split surfaceGatewayApiError into a pure
  classifyGatewayApiError (error -> action) plus a thin UX layer that
  switches on the action. The classifier is exported and covered by
  focused unit tests, so the status/code -> action mapping no longer
  needs the VS Code notification mocks to verify.
- handleUri.ts: extract the per-instance token propagation loop into a
  propagateZooGatewayCallback helper, keeping the /auth-callback case
  focused on routing. Behaviour (sequential read-modify-write, per
  instance error isolation) is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
@JamesRobert20 JamesRobert20 force-pushed the feat/zoo-gateway-auth-sync branch from 06fc185 to 42b3f0c Compare June 3, 2026 01:36
Copy link
Copy Markdown
Contributor

@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: 3

Caution

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

⚠️ Outside diff range comments (2)
src/services/zoo-code-auth.ts (1)

55-65: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate external token removals into _sessionCleared.

Line 56 updates _cachedToken when secret storage changes, but it never flips _sessionCleared. In another VS Code window, a sign-out or 401 clear will therefore leave this instance with _cachedToken === undefined and _sessionCleared === false, so resolveZooGatewaySessionToken() falls back to the stale profile token that src/api/providers/zoo-gateway.ts and src/api/providers/fetchers/zoo-gateway.ts still pass in. That breaks multi-instance sign-out and can keep sending a revoked bearer until profile state is refreshed.

Suggested fix
 		if (e.key === ZOO_CODE_TOKEN_KEY) {
 			secretStorage?.get(ZOO_CODE_TOKEN_KEY).then((token) => {
 				_cachedToken = token
+				_sessionCleared = !token
 				// Reset subscription status when token changes
 				_cachedSubscriptionStatus = "unknown"
 				_lastSubscriptionCheck = 0
 				if (token) {
 					checkSubscriptionStatus().catch(() => {})
 				}
 			})
 		}
🤖 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 `@src/services/zoo-code-auth.ts` around lines 55 - 65, When secretStorage
changes for ZOO_CODE_TOKEN_KEY in the context.secrets.onDidChange handler, also
propagate external removals into the auth state by setting _sessionCleared =
true when the retrieved token is undefined (or when the change indicates
removal); update the handler that currently sets _cachedToken,
_cachedSubscriptionStatus, and _lastSubscriptionCheck to additionally flip
_sessionCleared (and only clear it when a fresh token is present), so
resolveZooGatewaySessionToken and related functions (e.g.,
resolveZooGatewaySessionToken, checkSubscriptionStatus) no longer fall back to
stale profile tokens after a remote sign-out.
src/api/providers/zoo-gateway.ts (1)

133-154: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Refresh Authorization per request using the latest resolved Zoo session token.

  • The OpenAI client is constructed once with apiKey: sessionToken || "not-provided"; later ensureAuthenticated() re-resolves the token but doesn’t update the client’s credential.
  • createMessage(...) only forwards X-Zoo-* enrichment headers, so the outgoing call does not override Authorization.
  • completePrompt(...) passes no per-request headers, so it will also keep using the stale client credential.
Suggested fix
 export class ZooGatewayHandler extends RouterProvider implements SingleCompletionHandler {
 	constructor(options: ApiHandlerOptions) {
 		const baseURL = options.zooGatewayBaseUrl ?? `${getZooCodeBaseUrl()}/api/gateway/v1`

 		const sessionToken = resolveZooGatewaySessionToken(options.zooSessionToken)
@@
 		})
 	}

-	private ensureAuthenticated(): void {
-		if (!resolveZooGatewaySessionToken(this.options.zooSessionToken)) {
+	private ensureAuthenticated(): string {
+		const token = resolveZooGatewaySessionToken(this.options.zooSessionToken)
+		if (!token) {
 			throw new Error(ZOO_GATEWAY_AUTH_ERROR)
 		}
+		return token
 	}
@@
 	override async *createMessage(
@@
 	): ApiStream {
-		this.ensureAuthenticated()
+		const sessionToken = this.ensureAuthenticated()

 		const { id: modelId, info } = await this.fetchModel()
@@
 		// Build request headers with enrichment metadata
-		const requestHeaders: Record<string, string> = {}
+		const requestHeaders: Record<string, string> = {
+			Authorization: `Bearer ${sessionToken}`,
+		}
 		if (metadata?.taskId) {
 			requestHeaders["X-Zoo-Task-ID"] = metadata.taskId
 		}
 		if (metadata?.mode) {
 			requestHeaders["X-Zoo-Mode"] = metadata.mode
 		}
@@
 	async completePrompt(prompt: string): Promise<string> {
-		this.ensureAuthenticated()
+		const sessionToken = this.ensureAuthenticated()

 		const { id: modelId, info } = await this.fetchModel()
@@
-			const response = await this.client.chat.completions.create(requestOptions)
+			const response = await this.client.chat.completions.create(requestOptions, {
+				headers: {
+					Authorization: `Bearer ${sessionToken}`,
+				},
+			})
 			return response.choices[0]?.message.content || ""
🤖 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 `@src/api/providers/zoo-gateway.ts` around lines 133 - 154, The client is
created once with apiKey: sessionToken which can go stale; fix by re-resolving
the Zoo session token (call resolveZooGatewaySessionToken / ensureAuthenticated)
inside each request path and inject an explicit per-request Authorization header
(Authorization: Bearer <token>) into the headers you pass to the parent OpenAI
client rather than relying on the initially constructed apiKey; specifically
update createMessage(...) and completePrompt(...) to call
resolveZooGatewaySessionToken (or ensureAuthenticated) at the start and merge
Authorization into the per-request headers (alongside the existing X-Zoo-*
enrichment headers) so every outgoing call uses the latest token.
🧹 Nitpick comments (3)
src/core/webview/__tests__/webviewMessageHandler.spec.ts (1)

1154-1179: ⚡ Quick win

Assert the already-empty branch still disconnects and pushes updated state.

This test only proves upsertProviderProfile() runs. If the handler regresses to skipping disconnectZooCode() or postStateToWebview() when disk is already clean, this branch still passes even though sign-out is incomplete for the user. Please pin those two side effects here as well. As per coding guidelines, "Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling."

🤖 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 `@src/core/webview/__tests__/webviewMessageHandler.spec.ts` around lines 1154 -
1179, The test only asserts upsertProviderProfile was called but must also
verify the side effects disconnectZooCode and postStateToWebview occur when disk
already has no token; update the spec for webviewMessageHandler to stub/mock
mockClineProvider.disconnectZooCode and mockClineProvider.postStateToWebview (or
their equivalents on the mocked provider), call webviewMessageHandler as before,
and add expectations that disconnectZooCode() and postStateToWebview() were
called (in addition to the existing upsertProviderProfile assertion) to ensure
the handler still disconnects and pushes updated state.
src/api/providers/__tests__/zoo-gateway.spec.ts (1)

209-247: ⚡ Quick win

Replace one duplicate unauthenticated test with the token-refresh regression.

These two cases cover the same failure path. One of them would be more valuable as a regression that constructs ZooGatewayHandler before flipping the mocked cached token, then asserts the next request uses the refreshed bearer token. That would lock in the runtime-resolution behavior this PR is trying to add.

As per coding guidelines, "Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling."

🤖 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 `@src/api/providers/__tests__/zoo-gateway.spec.ts` around lines 209 - 247, The
duplicate unauthenticated test should be replaced with a token-refresh
regression: keep the first failing unauthenticated test, then change the second
test to construct a ZooGatewayHandler({}) before toggling the mocked cached
token value, simulate a refreshed bearer token (update whatever mock for cached
token/provider returns), call handler.createMessage("You are helpful.", [{ role:
"user", content: "Hello" }]) and drain the stream, and assert that mockCreate
was invoked with an Authorization header containing the refreshed bearer token
(or that the outgoing request used the new token); locate symbols
ZooGatewayHandler, createMessage, mockCreate and the cached-token mock to
implement this behavior. Ensure the test flips the cached token after handler
instantiation to validate runtime-resolution of the token.
src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts (1)

8-12: ⚡ Quick win

Exercise the new resolver path in this suite.

The added mock still behaves like the old implementation, so these tests never prove that getZooGatewayModels() uses resolveZooGatewaySessionToken() rather than options?.zooSessionToken directly. Add one case where zooSessionToken is omitted, the resolver returns a sentinel token, and the request asserts that sentinel in Authorization.

Suggested test shape
 vitest.mock("../../../../services/zoo-code-auth", () => ({
 	getCachedZooCodeToken: vitest.fn(() => ""),
 	getZooCodeBaseUrl: vitest.fn(() => "https://example.test"),
-	resolveZooGatewaySessionToken: vitest.fn((profileToken?: string) => profileToken || undefined),
+	resolveZooGatewaySessionToken: vitest.fn(),
 }))
+
+import { resolveZooGatewaySessionToken } from "../../../../services/zoo-code-auth"
+
+const mockedResolveZooGatewaySessionToken = vitest.mocked(resolveZooGatewaySessionToken)

 describe("Zoo Gateway Fetchers", () => {
 	beforeEach(() => {
 		vitest.clearAllMocks()
 	})
@@
+		it("uses the resolved session token when no profile token is provided", async () => {
+			mockedResolveZooGatewaySessionToken.mockReturnValueOnce("zoo_ext_cached_token")
+			mockedAxios.get.mockResolvedValueOnce(mockResponse)
+
+			await getZooGatewayModels({ zooGatewayBaseUrl: baseUrl } as any)
+
+			expect(mockedResolveZooGatewaySessionToken).toHaveBeenCalledWith(undefined)
+			expect(mockedAxios.get).toHaveBeenCalledWith(
+				`${baseUrl}/models`,
+				expect.objectContaining({
+					headers: expect.objectContaining({ Authorization: "Bearer zoo_ext_cached_token" }),
+				}),
+			)
+		})

As per coding guidelines, **/{__tests__,tests,test}/**/*.{test,spec}.{ts,tsx,js} should use package-local unit tests for pure logic, request construction, retry decisions, and error handling.

Also applies to: 61-87

🤖 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 `@src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts` around lines 8 -
12, Add a new test in the zoo-gateway.spec.ts suite that omits the
options.zooSessionToken when calling getZooGatewayModels(), configures the
vitest mock for resolveZooGatewaySessionToken to return a sentinel string (e.g.,
"SENTINEL_TOKEN"), and asserts that the outgoing request includes Authorization:
`Bearer SENTINEL_TOKEN`; locate the call site to getZooGatewayModels(), update
the mock for resolveZooGatewaySessionToken in the test to return the sentinel,
perform the request, and assert the Authorization header matches the sentinel to
prove getZooGatewayModels() uses resolveZooGatewaySessionToken() rather than
options?.zooSessionToken.
🤖 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 `@src/core/webview/__tests__/ClineProvider.spec.ts`:
- Around line 3763-3775: The test titled "logs and posts state when profile
persistence fails" only checks the error log but should also assert that
postStateToWebview was called; update the spec after calling
provider.handleZooCodeCallback("zoo_ext_token") to include an assertion that
provider.postStateToWebview was invoked (e.g.,
expect(provider.postStateToWebview).toHaveBeenCalled() or
toHaveBeenCalledWith(...) ). Ensure you reference the existing mocks/spies on
provider.getState (mockRejectedValue), provider.postStateToWebview
(mockResolvedValue), and the providerSettingsManager stub so the new assertion
verifies the state refresh happens even when getState fails.

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts`:
- Around line 371-372: The test asserts that "zoo-gateway" models are fetched
but the shared getState() mock never sets apiConfiguration.zooGatewayBaseUrl, so
either seed apiConfiguration.zooGatewayBaseUrl in the mocked getState for this
suite (and the other failing cases around the same area) or change the
expectations to not include "zoo-gateway" when that config is absent; update the
relevant tests in webviewMessageHandler.spec.ts (the suite using the mocked
getState and assertions that compare against mockModels for "zoo-gateway"
alongside "vercel-ai-gateway") to ensure the mocked state and expectations match
the production contract.

In `@src/core/webview/webviewMessageHandler.ts`:
- Around line 2452-2487: The loop over
provider.providerSettingsManager.listConfig() should not be aborted by a single
profile failure: wrap the per-profile work (calls to
provider.providerSettingsManager.getProfile, provider.upsertProviderProfile, and
provider.providerSettingsManager.saveConfig) in its own try/catch so errors for
one entry are logged via provider.log but do not break the for loop; keep the
existing logic for isThisProfileActive (using currentSettings.apiProvider and
currentApiConfigName) and only call saveConfig when profile.zooSessionToken was
present, and always attempt upsertProviderProfile for the active profile even if
its persisted token was already cleared.

---

Outside diff comments:
In `@src/api/providers/zoo-gateway.ts`:
- Around line 133-154: The client is created once with apiKey: sessionToken
which can go stale; fix by re-resolving the Zoo session token (call
resolveZooGatewaySessionToken / ensureAuthenticated) inside each request path
and inject an explicit per-request Authorization header (Authorization: Bearer
<token>) into the headers you pass to the parent OpenAI client rather than
relying on the initially constructed apiKey; specifically update
createMessage(...) and completePrompt(...) to call resolveZooGatewaySessionToken
(or ensureAuthenticated) at the start and merge Authorization into the
per-request headers (alongside the existing X-Zoo-* enrichment headers) so every
outgoing call uses the latest token.

In `@src/services/zoo-code-auth.ts`:
- Around line 55-65: When secretStorage changes for ZOO_CODE_TOKEN_KEY in the
context.secrets.onDidChange handler, also propagate external removals into the
auth state by setting _sessionCleared = true when the retrieved token is
undefined (or when the change indicates removal); update the handler that
currently sets _cachedToken, _cachedSubscriptionStatus, and
_lastSubscriptionCheck to additionally flip _sessionCleared (and only clear it
when a fresh token is present), so resolveZooGatewaySessionToken and related
functions (e.g., resolveZooGatewaySessionToken, checkSubscriptionStatus) no
longer fall back to stale profile tokens after a remote sign-out.

---

Nitpick comments:
In `@src/api/providers/__tests__/zoo-gateway.spec.ts`:
- Around line 209-247: The duplicate unauthenticated test should be replaced
with a token-refresh regression: keep the first failing unauthenticated test,
then change the second test to construct a ZooGatewayHandler({}) before toggling
the mocked cached token value, simulate a refreshed bearer token (update
whatever mock for cached token/provider returns), call
handler.createMessage("You are helpful.", [{ role: "user", content: "Hello" }])
and drain the stream, and assert that mockCreate was invoked with an
Authorization header containing the refreshed bearer token (or that the outgoing
request used the new token); locate symbols ZooGatewayHandler, createMessage,
mockCreate and the cached-token mock to implement this behavior. Ensure the test
flips the cached token after handler instantiation to validate
runtime-resolution of the token.

In `@src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts`:
- Around line 8-12: Add a new test in the zoo-gateway.spec.ts suite that omits
the options.zooSessionToken when calling getZooGatewayModels(), configures the
vitest mock for resolveZooGatewaySessionToken to return a sentinel string (e.g.,
"SENTINEL_TOKEN"), and asserts that the outgoing request includes Authorization:
`Bearer SENTINEL_TOKEN`; locate the call site to getZooGatewayModels(), update
the mock for resolveZooGatewaySessionToken in the test to return the sentinel,
perform the request, and assert the Authorization header matches the sentinel to
prove getZooGatewayModels() uses resolveZooGatewaySessionToken() rather than
options?.zooSessionToken.

In `@src/core/webview/__tests__/webviewMessageHandler.spec.ts`:
- Around line 1154-1179: The test only asserts upsertProviderProfile was called
but must also verify the side effects disconnectZooCode and postStateToWebview
occur when disk already has no token; update the spec for webviewMessageHandler
to stub/mock mockClineProvider.disconnectZooCode and
mockClineProvider.postStateToWebview (or their equivalents on the mocked
provider), call webviewMessageHandler as before, and add expectations that
disconnectZooCode() and postStateToWebview() were called (in addition to the
existing upsertProviderProfile assertion) to ensure the handler still
disconnects and pushes updated state.
🪄 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: 45d0dd2c-712e-4d9b-b203-354348b966ab

📥 Commits

Reviewing files that changed from the base of the PR and between f9c9c09 and 42b3f0c.

📒 Files selected for processing (31)
  • src/activate/__tests__/handleUri.spec.ts
  • src/activate/handleUri.ts
  • src/api/providers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/fetchers/__tests__/zoo-gateway.spec.ts
  • src/api/providers/fetchers/zoo-gateway.ts
  • src/api/providers/zoo-gateway.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.spec.ts
  • src/core/webview/__tests__/webviewMessageHandler.spec.ts
  • src/core/webview/webviewMessageHandler.ts
  • src/i18n/locales/ca/common.json
  • src/i18n/locales/de/common.json
  • src/i18n/locales/en/common.json
  • src/i18n/locales/es/common.json
  • src/i18n/locales/fr/common.json
  • src/i18n/locales/hi/common.json
  • src/i18n/locales/id/common.json
  • src/i18n/locales/it/common.json
  • src/i18n/locales/ja/common.json
  • src/i18n/locales/ko/common.json
  • src/i18n/locales/nl/common.json
  • src/i18n/locales/pl/common.json
  • src/i18n/locales/pt-BR/common.json
  • src/i18n/locales/ru/common.json
  • src/i18n/locales/tr/common.json
  • src/i18n/locales/vi/common.json
  • src/i18n/locales/zh-CN/common.json
  • src/i18n/locales/zh-TW/common.json
  • src/services/__tests__/zoo-code-auth.test.ts
  • src/services/zoo-code-auth.ts
  • webview-ui/src/components/welcome/WelcomeViewProvider.tsx

Comment thread src/core/webview/__tests__/ClineProvider.spec.ts
Comment on lines +371 to +372
"vercel-ai-gateway": mockModels,
"zoo-gateway": mockModels,
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot Jun 3, 2026

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

Seed zooGatewayBaseUrl before expecting zoo-gateway models here.

This suite’s shared getState() mock never sets apiConfiguration.zooGatewayBaseUrl, but these assertions now expect zoo-gateway fetches to succeed anyway. That no longer matches the production contract described in this PR, so the test can pass against an impossible state and stop catching a missing-base-URL regression. Either add zooGatewayBaseUrl to the mocked config for these cases or keep zoo-gateway empty when that config is absent. As per coding guidelines, "Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling."

Also applies to: 459-460, 517-518

🤖 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 `@src/core/webview/__tests__/webviewMessageHandler.spec.ts` around lines 371 -
372, The test asserts that "zoo-gateway" models are fetched but the shared
getState() mock never sets apiConfiguration.zooGatewayBaseUrl, so either seed
apiConfiguration.zooGatewayBaseUrl in the mocked getState for this suite (and
the other failing cases around the same area) or change the expectations to not
include "zoo-gateway" when that config is absent; update the relevant tests in
webviewMessageHandler.spec.ts (the suite using the mocked getState and
assertions that compare against mockModels for "zoo-gateway" alongside
"vercel-ai-gateway") to ensure the mocked state and expectations match the
production contract.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Skipping. The premise does not hold against the current code: getZooGatewayModels self-resolves the base URL (options?.zooGatewayBaseUrl ?? getZooCodeBaseUrl()/api/gateway/v1), so zooGatewayBaseUrl is an optional override, not a required field. The candidate in requestRouterModels passes it through as-is and the mocked getModels returns mockModels for every provider regardless, so these assertions match the production contract. There is no missing-base-URL state to regress here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@JamesRobert20, understood — that's a clear and correct explanation. Since getZooGatewayModels falls back to getZooCodeBaseUrl()/api/gateway/v1 when no zooGatewayBaseUrl is provided, the field is genuinely optional and the test assertions are valid as-is. My original concern was incorrect. Thanks for the clarification.


✏️ Learnings added
Learnt from: JamesRobert20
Repo: Zoo-Code-Org/Zoo-Code PR: 347
File: src/core/webview/__tests__/webviewMessageHandler.spec.ts:371-372
Timestamp: 2026-06-03T01:59:47.117Z
Learning: In Zoo-Code (`src/api/providers/fetchers/zoo-gateway.ts`), `getZooGatewayModels` self-resolves the base URL via `options?.zooGatewayBaseUrl ?? getZooCodeBaseUrl() + "/api/gateway/v1"`. Therefore `zooGatewayBaseUrl` is an optional override, not a required field. In `requestRouterModels` (webviewMessageHandler), the candidate passes `zooGatewayBaseUrl` through as-is, and the zoo-gateway fetch succeeds even when the config omits that field. Do not flag missing `zooGatewayBaseUrl` in test mocks as a contract violation.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/providers/deepseek-v4.test.ts : For DeepSeek V4 E2E tests, redirect the OpenAI-compatible DeepSeek client through aimock by setting `deepSeekBaseUrl: ${AIMOCK_URL}/v1`.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/**/*.test.ts : Always restore the default OpenRouter config in `suiteTeardown` when tests change persisted provider/model settings, so subsequent suites are unaffected.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/**/*.test.ts : For fetch-interceptor test suites, reset in-memory request/event capture in `setup()` or allocate a fresh per-test buffer instead of reusing shared mutable state; scope request-shape assertions to the current probe or test tag only; do not pull in older requests.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T15:41:10.836Z
Learning: Applies to **/webview-ui/**/{__tests__,tests,test}/**/*.{test,spec}.{ts,tsx} : Use `webview-ui` tests for React rendering, hooks, component state, forms, validation, and webview UI wiring.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/**/*.test.ts : When using a non-default provider (e.g. Anthropic) in E2E tests, point aimock at the endpoint by passing `anthropicBaseUrl: aimockUrl` (without a `/v1` suffix) to `api.setConfiguration()`.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/providers/xai.test.ts : For xAI Grok E2E tests, patch `globalThis.fetch` to intercept requests to the Responses API endpoint. When a local `fixtures/xai.json` recording exists, it can replay recorded real-API SSE events; otherwise fall back to hand-crafted SSE events using a hardcoded `readCallId`.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/providers/zai.test.ts : When adding a test to the Z.ai GLM suite, add a matching fixture to the `installZAiFetchInterceptor` call in `suiteSetup` using a short unique prefix (e.g. `"zai-glm-e2e-mytest:"`) that won't appear in `<environment_details>`.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: apps/vscode-e2e/AGENTS.md:0-0
Timestamp: 2026-05-21T22:27:19.778Z
Learning: Applies to apps/vscode-e2e/src/suite/providers/xai.test.ts : When adding a new test to the xAI Grok suite, update the hand-crafted interceptor response with a short unique probe tag (e.g. `"xai-e2e:grok-4.20"`) that won't appear in `<environment_details>`.

Learnt from: F915
Repo: Zoo-Code-Org/Zoo-Code PR: 421
File: src/api/providers/fetchers/modelCache.ts:100-126
Timestamp: 2026-06-01T13:21:27.362Z
Learning: In Zoo-Code (src/api/providers/fetchers/modelCache.ts), the Bailian provider intentionally uses a provider-only cache key ("bailian") rather than a per-baseUrl key. This is a documented design trade-off: full per-baseUrl keying would require changing shared infrastructure signatures (getModelsFromCache, writeModels, readModels, inFlightRefresh) used by 10+ providers. Safety is maintained by: (1) most users stay on a single region, (2) region switches call flushModels(refresh=true) which fetches fresh data first, (3) 5-minute memory cache TTL, (4) workspace-dependent URLs validated in the BailianHandler constructor. The planned future fix is a "bailian:<normalizedBaseUrl>" composite key. Do not re-flag this as a bug.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T15:41:10.836Z
Learning: Applies to **/{__tests__,tests,test}/**/*.{test,spec}.{ts,tsx,js} : Use package-local unit tests for pure logic, parsing, state transitions, validation, serialization, request construction, retry decisions, and error handling.

Learnt from: CR
Repo: Zoo-Code-Org/Zoo-Code PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-05-14T15:41:10.836Z
Learning: Applies to apps/vscode-e2e/**/*.{test,spec}.{ts,tsx} : Keep e2e tests focused on high-value smoke coverage across boundaries. Avoid placing detailed protocol, parsing, storage, retry, or edge-case assertions in e2e when they can be covered reliably at a lower layer.

Comment thread src/core/webview/webviewMessageHandler.ts
…efresh

Wrap per-profile work in the zoo-gateway sign-out cleanup loop in its own
try/catch so one corrupted profile or failed write no longer aborts cleanup
of the remaining profiles. Also assert postStateToWebview runs on the
handleZooCodeCallback persistence-failure path.

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown
Contributor

@taltas taltas left a comment

Choose a reason for hiding this comment

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

Changes addressed

@JamesRobert20 JamesRobert20 added this pull request to the merge queue Jun 3, 2026
Merged via the queue into main with commit 75d4eee Jun 3, 2026
11 checks passed
@JamesRobert20 JamesRobert20 deleted the feat/zoo-gateway-auth-sync branch June 3, 2026 02:33
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.

4 participants