From 186acbc58c4025fa2a60ca691451e01dcdbac2ed Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Wed, 7 Oct 2026 23:57:22 +0200 Subject: [PATCH] Show success feedback when copying OAuth device codes Reuse the shared button-feedback helper to briefly show a green checkmark after copying a sign-in code, while preserving existing notifications and automatic reset. Cover successful copies, clipboard failures, and missing codes in the reconnect regression test; update plugin documentation. Validation: 218 OAuth tests and 3 plugin scan tests passed, along with the Node reconnect test and a live UI check of clipboard contents, success styling, and icon reset. --- plugins/_oauth/AGENTS.md | 2 +- plugins/_oauth/README.md | 2 +- plugins/_oauth/webui/config.html | 7 ++++++- plugins/_oauth/webui/oauth-config-store.js | 5 +++-- tests/test_oauth_reconnect.mjs | 12 +++++++++--- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/plugins/_oauth/AGENTS.md b/plugins/_oauth/AGENTS.md index e701efcc1..b933c047d 100644 --- a/plugins/_oauth/AGENTS.md +++ b/plugins/_oauth/AGENTS.md @@ -27,7 +27,7 @@ - Provider cards and model slot actions must be driven by backend provider status. Do not reintroduce hardcoded frontend provider lists or fallback provider catalogs. - OAuth account surfaces in settings, discovery, and onboarding must use the provider registry/status summary rather than Codex-only frontend state. - OAuth settings pending-auth controls such as device codes, manual callback input, and provider setup fields must render inline under the relevant provider row, not as a detached section below all providers. -- Device challenge codes use a selectable standalone box and an accessible copy button, reusing the shared clipboard helper and OAuth notifications for feedback. +- Device challenge codes use a selectable standalone box and an accessible copy button, reusing the shared clipboard and button-feedback helpers: successful copies briefly show a green checkmark and retain OAuth notifications. - OAuth device-code polling must honor provider `interval`, `expires_at`, and `slow_down` updates; do not poll immediately or keep a stale fixed interval after a provider asks the client to slow down. - OAuth settings model slots must keep provider choice editable per slot, list only connected OAuth account providers, and persist the selected provider IDs into `chat_model.provider` and `utility_model.provider`. - When exactly one OAuth provider is connected, use it as an unsaved default only for empty slots or slots already using that provider. A different saved provider must keep the explicit `Choose connected provider` prompt until the user opts into the switch. diff --git a/plugins/_oauth/README.md b/plugins/_oauth/README.md index ce576dd61..aed9ace39 100644 --- a/plugins/_oauth/README.md +++ b/plugins/_oauth/README.md @@ -15,7 +15,7 @@ OAuth-backed model providers do not require users to enter API keys. Agent Zero ### Codex/ChatGPT (`codex_oauth`) - Uses the existing Codex device-code flow. -- Shows the sign-in code in a selectable box with a copy button. +- Shows the sign-in code in a selectable box with a copy button that briefly turns into a green checkmark after copying. - Writes Codex-compatible credentials to an Agent Zero-owned `auth.json` file. - Refreshes local tokens when needed. - If a refresh token expires or is revoked, shows an **Open OAuth settings** toast. Choose **Reconnect** on the Codex row and complete sign-in, then retry your request. No manual disconnect is needed; cancelling sign-in preserves the saved credentials and model choices. diff --git a/plugins/_oauth/webui/config.html b/plugins/_oauth/webui/config.html index 974e9cb24..746317cc2 100644 --- a/plugins/_oauth/webui/config.html +++ b/plugins/_oauth/webui/config.html @@ -166,7 +166,7 @@ Enter this code
-
@@ -855,6 +855,11 @@ cursor: pointer; } + .oauth-icon-button.success { + color: #4caf50; + border-color: currentColor; + } + .oauth-model-config { display: grid; gap: 12px; diff --git a/plugins/_oauth/webui/oauth-config-store.js b/plugins/_oauth/webui/oauth-config-store.js index bbe8a8d39..ad7237063 100644 --- a/plugins/_oauth/webui/oauth-config-store.js +++ b/plugins/_oauth/webui/oauth-config-store.js @@ -1,6 +1,6 @@ import { createStore } from "/js/AlpineStore.js"; import { callJsonApi, fetchApi } from "/js/api.js"; -import { copyToClipboard } from "/components/messages/action-buttons/simple-action-buttons.js"; +import { copyToClipboard, showButtonFeedback } from "/components/messages/action-buttons/simple-action-buttons.js"; import { store as modelConfigStore } from "/plugins/_model_config/webui/model-config-store.js"; import { store as notificationStore, @@ -311,11 +311,12 @@ export const store = createStore("oauthConfig", { return this.devices[String(providerId || "")] || null; }, - async copyDeviceCode(providerId) { + async copyDeviceCode(providerId, button = null) { const code = this.providerDevice(providerId)?.user_code; if (!code) return; try { await copyToClipboard(code); + if (button) showButtonFeedback(button, true, "content_copy"); void toastFrontendSuccess("Sign-in code copied.", "OAuth Connections"); } catch { void toastFrontendError("Could not copy the code. Select it and copy manually.", "OAuth Connections"); diff --git a/tests/test_oauth_reconnect.mjs b/tests/test_oauth_reconnect.mjs index e53823e1f..6fd3fa5fa 100644 --- a/tests/test_oauth_reconnect.mjs +++ b/tests/test_oauth_reconnect.mjs @@ -6,6 +6,8 @@ const requests = []; const timers = new Map(); const dismissed = []; const copied = []; +const buttonFeedback = []; +const copyButton = {}; const feedback = []; let clipboardDenied = false; const source = readFileSync(new URL('../plugins/_oauth/webui/oauth-config-store.js', import.meta.url), 'utf8') @@ -29,6 +31,7 @@ const store = vm.runInNewContext(`${source}\nstore`, { if (clipboardDenied) throw new Error('Permission denied'); copied.push(code); }, + showButtonFeedback: (...args) => buttonFeedback.push(args), notificationStore: { dismissToast: id => dismissed.push(id) }, }); const provider = 'codex_oauth'; @@ -41,20 +44,23 @@ await store.connectProvider(provider); assert.equal(store.providerPrimaryLabel(provider), 'Waiting'); assert.equal(store.providerDetailOpen(provider), true); assert.equal(store.providerDevice(provider).user_code, 'TEST'); -await store.copyDeviceCode(provider); +await store.copyDeviceCode(provider, copyButton); assert.deepEqual(copied, ['TEST']); +assert.deepEqual(buttonFeedback, [[copyButton, true, 'content_copy']]); assert.equal(feedback.pop(), 'Sign-in code copied.'); clipboardDenied = true; -await store.copyDeviceCode(provider); +await store.copyDeviceCode(provider, copyButton); assert.equal(feedback.pop(), 'Could not copy the code. Select it and copy manually.'); +assert.equal(buttonFeedback.length, 1); assert.equal(timers.size, 1); await store.connectProvider(provider); assert.equal(requests.length, 1); store.cancelConnect(provider); assert.equal(timers.size, 0); assert.equal(store.providerDevice(provider), null); -await store.copyDeviceCode(provider); +await store.copyDeviceCode(provider, copyButton); assert.equal(copied.length, 1); +assert.equal(buttonFeedback.length, 1); assert.equal(feedback.length, 0); assert.equal(store.providerConnected(provider), true); assert.equal(store.modelConfig.chat_model.name, 'chosen-model');