Replace token-paste with native device-code/PKCE provider sign-in

Implement the official OAuth login flows natively (ported from pi-ai's
reference): GitHub Copilot device-code, GPT account (ChatGPT Codex) and
Google Antigravity authorization-code + PKCE via a loopback callback server.
Obtained credentials are stored as JSON through the existing secret vault.
Onboarding and Settings now run the real sign-in instead of pasting an
access token.
This commit is contained in:
ZacharyZhang-NY committed 2026-05-29 20:08:55 -04:00
1 parent d5bf6c6b5f
commit 49a9f16221
24 files changed
+1057 -64

No files matched your search

@@ -5,6 +5,7 @@ import {
type ProviderCatalogEntry,
} from "../../../shared/domain";
import { Button, Field, Icon, IconButton, Pill } from "../shared/ui";
import { ProviderOAuthConnect } from "../shared/ProviderOAuthConnect";
import type { SnapshotActions } from "./useSnapshot";
type Step = "welcome" | "provider" | "company" | "mode" | "mission";
@@ -244,7 +245,6 @@ function ProviderStep({
const selected = providers.find((entry) => entry.key === draft.providerKey) ?? providers[0];
if (!selected) throw new Error("No provider available for onboarding.");
const isOAuthLike = selected.authType === "oauth_pkce" || selected.authType === "account_subscription";
const secretLabel = isOAuthLike ? "Access token" : "API key";
return (
<div className="form-grid">
@@ -275,26 +275,25 @@ function ProviderStep({
{selected.credentialHelp && (
<div style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5 }}>{selected.credentialHelp}</div>
)}
{isOAuthLike && selected.signInUrl && (
<div className="row" style={{ gap: 8 }}>
<Button
tone="primary"
onClick={() => void window.agentCompany.openExternalUrl(selected.signInUrl ?? "")}
>
Open {selected.label} sign-in
</Button>
<span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{selected.signInUrl}</span>
</div>
)}
<Field label={secretLabel}>
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
{isOAuthLike ? (
<ProviderOAuthConnect
key={selected.key}
providerKey={selected.key}
label={selected.label}
buildInput={() => ({ providerKey: selected.key })}
onComplete={(secret) => onChange({ providerSecret: secret ?? "" })}
/>
</Field>
) : (
<Field label="API key">
<input
className="input"
type="password"
autoComplete="off"
value={draft.providerSecret}
onChange={(event) => onChange({ providerSecret: event.target.value })}
/>
</Field>
)}
<Field label="Model">
{selected.models.length ? (
<select
@@ -1,6 +1,7 @@
import { useState, type FormEvent } from "react";
import { Button, Field, Pill } from "../../shared/ui";
import { Dialog } from "../../shared/Dialog";
import { ProviderOAuthConnect } from "../../shared/ProviderOAuthConnect";
import type {
AgentRecord,
ProviderCatalogEntry,
@@ -41,19 +42,19 @@ type WorkspaceScope = "organization" | "workspace";
export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, catalog, agents, workspaces, providerRateLimits, providerRequests, actions }: ProvidersPanelProps) {
const [active, setActive] = useState<ProviderRecord | null>(null);
const [signInLaunched, setSignInLaunched] = useState(false);
const connected = providers.filter((provider) => provider.enabled);
const available = providers.filter((provider) => !provider.enabled);
const activeCatalogEntry = active ? catalog.find((entry) => entry.key === active.providerKey) ?? null : null;
const activeIsOAuth =
activeCatalogEntry?.authType === "oauth_pkce" || activeCatalogEntry?.authType === "account_subscription";
const close = () => {
setActive(null);
setSignInLaunched(false);
};
const save = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (!active || !activeCatalogEntry) return;
if (!active || !activeCatalogEntry || activeIsOAuth) return;
const form = new FormData(event.currentTarget);
const secret = composeSecret(activeCatalogEntry, form);
void actions.run(() => window.agentCompany.saveProviderCredential({
@@ -65,12 +66,6 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat
})).then((result) => { if (result) close(); });
};
const launchSignIn = () => {
if (!active) return;
setSignInLaunched(true);
void actions.run(() => window.agentCompany.launchProviderSignIn({ orgId, providerId: active.id, employeeId }));
};
return (
<div className="page">
<h1 className="page-h1">Providers</h1>
@@ -124,16 +119,22 @@ export function ProvidersPanel({ orgId, employeeId, providers, authProfiles, cat
title={active ? `Connect ${active.label}` : "Connect provider"}
open={Boolean(active)}
submitLabel="Save"
hideSubmit={activeIsOAuth}
onClose={close}
onSubmit={save}
>
{active && activeCatalogEntry && (
<ProviderConnectFields
provider={active}
entry={activeCatalogEntry}
signInLaunched={signInLaunched}
onLaunchSignIn={launchSignIn}
/>
activeIsOAuth ? (
<ProviderOAuthFields
orgId={orgId}
employeeId={employeeId}
provider={active}
entry={activeCatalogEntry}
onConnected={() => { void actions.reload(); close(); }}
/>
) : (
<ProviderConnectFields provider={active} entry={activeCatalogEntry} />
)
)}
</Dialog>
</div>
@@ -274,13 +275,9 @@ function ProviderPolicyRow({
function ProviderConnectFields({
provider,
entry,
signInLaunched,
onLaunchSignIn,
}: {
provider: ProviderRecord;
entry: ProviderCatalogEntry;
signInLaunched: boolean;
onLaunchSignIn: () => void;
}) {
if (entry.authType === "api_key") {
return (
@@ -291,24 +288,48 @@ function ProviderConnectFields({
</>
);
}
if (entry.authType === "oauth_pkce" || entry.authType === "account_subscription") {
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<div className="row" style={{ gap: 8 }}>
<Button tone={signInLaunched ? "ghost" : "primary"} onClick={onLaunchSignIn}>
{signInLaunched ? "Re-open sign-in window" : `Open ${entry.label} sign-in`}
</Button>
{entry.signInUrl && <span className="mono" style={{ fontSize: 11, color: "var(--text-3)" }}>{entry.signInUrl}</span>}
</div>
<Field label="Access token"><input className="input" name="secret" type="password" required /></Field>
<ModelPicker entry={entry} provider={provider} />
</>
);
}
return <CloudFieldsForm entry={entry} provider={provider} />;
}
function ProviderOAuthFields({
orgId,
employeeId,
provider,
entry,
onConnected,
}: {
orgId: string;
employeeId: string;
provider: ProviderRecord;
entry: ProviderCatalogEntry;
onConnected: () => void;
}) {
const [model, setModel] = useState(provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "");
return (
<>
<p style={{ fontSize: 12.5, color: "var(--text-3)", lineHeight: 1.5, margin: 0 }}>{entry.credentialHelp}</p>
<Field label="Default model">
{entry.models.length ? (
<select className="input" value={model} onChange={(event) => setModel(event.target.value)}>
{entry.models.map((option) => (
<option key={option.id} value={option.id}>{option.name}</option>
))}
</select>
) : (
<input className="input" value={model} onChange={(event) => setModel(event.target.value)} />
)}
</Field>
<ProviderOAuthConnect
key={entry.key}
providerKey={entry.key}
label={entry.label}
buildInput={() => ({ providerKey: entry.key, orgId, providerId: provider.id, employeeId, model })}
onComplete={() => onConnected()}
/>
</>
);
}
function ModelPicker({ entry, provider }: { entry: ProviderCatalogEntry; provider: ProviderRecord }) {
const defaultValue = provider.model ?? entry.defaultModel ?? entry.models[0]?.id ?? "";
if (entry.models.length === 0) {
@@ -8,6 +8,7 @@ export function Dialog({
submitLabel,
onClose,
onSubmit,
hideSubmit = false,
}: {
title: string;
open: boolean;
@@ -15,6 +16,7 @@ export function Dialog({
submitLabel: string;
onClose: () => void;
onSubmit: (event: FormEvent<HTMLFormElement>) => void;
hideSubmit?: boolean;
}) {
if (!open) return null;
return (
@@ -23,8 +25,8 @@ export function Dialog({
<h2 className="modal-title">{title}</h2>
<div style={{ display: "grid", gap: 12 }}>{children}</div>
<div className="modal-actions">
<Button tone="ghost" onClick={onClose}>Cancel</Button>
<Button tone="primary" type="submit">{submitLabel}</Button>
<Button tone="ghost" onClick={onClose}>{hideSubmit ? "Close" : "Cancel"}</Button>
{!hideSubmit && <Button tone="primary" type="submit">{submitLabel}</Button>}
</div>
</form>
</div>
@@ -0,0 +1,82 @@
import { useEffect, useState } from "react";
import type { StartProviderOAuthInput } from "../../../shared/commands";
import { Button, Icon } from "./ui";
type Phase = "idle" | "running" | "done" | "error";
/**
* Runs a provider's native OAuth login (device-code / PKCE) and reports the
* outcome. The browser opens automatically; for the device flow the user code
* is shown here, which is the only interactive instruction required.
*/
export function ProviderOAuthConnect({
providerKey,
label,
buildInput,
onComplete,
}: {
providerKey: string;
label: string;
buildInput: () => StartProviderOAuthInput;
onComplete: (secret: string | null) => void;
}) {
const [phase, setPhase] = useState<Phase>("idle");
const [instruction, setInstruction] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
useEffect(
() =>
window.agentCompany.subscribe((event) => {
if (event.type !== "provider-oauth" || event.providerKey !== providerKey) return;
if (event.phase === "authorize" && event.instructions) setInstruction(event.instructions);
}),
[providerKey],
);
const run = () => {
setPhase("running");
setInstruction(null);
setError(null);
void window.agentCompany.startProviderOAuth(buildInput()).then((result) => {
if (result.ok) {
setPhase("done");
onComplete(result.data.secret);
} else {
setPhase("error");
setError(result.error.message);
}
});
};
const cancel = () => void window.agentCompany.cancelProviderOAuth({ providerKey });
if (phase === "running") {
return (
<div className="oauth-connect">
<div className="oauth-connect-row">
<span className="spinner" aria-hidden />
<span>Waiting for {label} sign-in</span>
<button type="button" className="link-button" onClick={cancel}>Cancel</button>
</div>
{instruction && <div className="oauth-connect-code">{instruction}</div>}
</div>
);
}
if (phase === "done") {
return (
<div className="oauth-connect-row done">
<Icon name="check" size={14} color="var(--green)" />
<span>Signed in to {label}</span>
<button type="button" className="link-button" onClick={run}>Redo</button>
</div>
);
}
return (
<div className="oauth-connect">
<Button tone="primary" onClick={run}>Sign in with {label}</Button>
{error && <div className="oauth-connect-error">{error}</div>}
</div>
);
}
@@ -180,3 +180,28 @@ textarea.input { min-height: 84px; resize: vertical; line-height: 1.5; }
color: var(--text-3);
}
.link-button:hover { color: var(--text-1); }
.oauth-connect { display: flex; flex-direction: column; gap: 8px; }
.oauth-connect-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-2); }
.oauth-connect-row.done { color: var(--text-1); }
.oauth-connect-row .link-button { margin-left: auto; }
.oauth-connect-code {
font-family: ui-monospace, Menlo, Consolas, monospace;
font-size: 12.5px;
color: var(--text-1);
background: var(--panel-3);
border-radius: var(--r-sm);
padding: 8px 10px;
word-break: break-word;
}
.oauth-connect-error { font-size: 12px; color: var(--red); }
.spinner {
width: 13px;
height: 13px;
border-radius: 50%;
border: 2px solid var(--border-strong);
border-top-color: var(--text-2);
animation: spinner-rotate 0.7s linear infinite;
flex-shrink: 0;
}
@keyframes spinner-rotate { to { transform: rotate(360deg); } }