diff --git a/README.md b/README.md index 25f5ebb56..9233bad04 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,17 @@ account exclusion, affinity expiry, or 401/403 and 429 recovery can rebind them. selection order when one of them — usually your Codex Desktop login — should only be reached for once the others are drained. +Command Code supports an opt-in OAuth account pool +(`config.commandCodeAccountPool.enabled`): +sticky session affinity, 429 failover, and new-session quota / round-robin / fill-first +picking with per-account 5h + weekly usage bars in the dashboard. The GUI's account +panel shows an **Add account** button that opens the login and also accepts a pasted +redirect URL / authorization code / raw API key (Command Code). +Pool settings are explicit under `commandCodeAccountPool`: +`enabled`, `autoSwitchThreshold` (0–100), `strategy` (`quota`, `round-robin`, or +`fill-first`), and `stickyLimit` (1–100). Command Code also supports +`accountPriorities` (-100–100 per account) and `activeAccountPinned`. + ### For agents ```bash diff --git a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx index 60c15225d..279939176 100644 --- a/gui/src/components/provider-workspace/ProviderAuthPanel.tsx +++ b/gui/src/components/provider-workspace/ProviderAuthPanel.tsx @@ -119,6 +119,10 @@ export default function ProviderAuthPanel({ const [addingKey, setAddingKey] = useState(false); const [newKey, setNewKey] = useState(""); const [keyBusy, setKeyBusy] = useState(false); + const [manualCode, setManualCode] = useState(""); + const [manualCodeBusy, setManualCodeBusy] = useState(false); + const [manualCodeMsg, setManualCodeMsg] = useState(""); + const [manualCodeOk, setManualCodeOk] = useState(true); const [importBusy, setImportBusy] = useState(false); const [importStatus, setImportStatus] = useState<"idle" | "invalid" | "failed" | "complete">("idle"); const [importResult, setImportResult] = useState(null); @@ -195,6 +199,25 @@ export default function ProviderAuthPanel({ } }; + const submitManualCode = async () => { + const input = manualCode.trim(); + if (!input || manualCodeBusy || !authHandlers.onSubmitManualCode) return; + setManualCodeBusy(true); + setManualCodeMsg(""); + try { + await authHandlers.onSubmitManualCode(item.name, input); + setManualCode(""); + setManualCodeOk(true); + setManualCodeMsg(t("prov.pasteOk")); + } catch (error) { + setManualCodeOk(false); + const message = error instanceof Error && error.message.trim() ? error.message : t("prov.networkError"); + setManualCodeMsg(t("prov.pasteFail", { error: message })); + } finally { + setManualCodeBusy(false); + } + }; + const importCockpitFile = async (file: File | undefined) => { if (!file || importBusy) return; setImportBusy(true); @@ -326,6 +349,53 @@ export default function ProviderAuthPanel({ )} + {authHandlers.onSubmitManualCode && ( +
+
+ {item.name === "command-code" + ? t("prov.pasteCommandCodeHint") + : t("prov.pasteRedirectHint")} +
+
+ { setManualCode(e.target.value); setManualCodeMsg(""); }} + onKeyDown={e => { + if (e.key === "Enter" && manualCode.trim()) { + e.preventDefault(); + void submitManualCode(); + } + }} + placeholder={item.name === "command-code" ? t("prov.pasteCommandCodePlaceholder") : t("prov.pasteRedirect")} + aria-label={item.name === "command-code" ? t("prov.pasteCommandCodePlaceholder") : t("prov.pasteRedirect")} + disabled={manualCodeBusy} + className="input text-label" + style={{ flex: 1 }} + /> + +
+ {manualCodeMsg && ( +
+ {manualCodeMsg} +
+ )} +
+ )} {authHandlers.onCancelLogin && (