diff --git a/docs/2-handbook/swarm/claude-login.md b/docs/2-handbook/swarm/claude-login.md index 42243b7b0..4f1e24cfb 100644 --- a/docs/2-handbook/swarm/claude-login.md +++ b/docs/2-handbook/swarm/claude-login.md @@ -20,6 +20,11 @@ A started login waits 15 minutes for its code, then stops. Starting another logi for the same agent replaces the running one. A refused code fails the login, and the dialog shows what claude printed. +You don't have to wait for the agent to fail first: the **claude login** row in the +card's settings has a **log in** button too, which reads **log in again** once the swarm has a +login for the agent, so a fresh agent or a forced relogin starts there. It opens the +same dialog. + The agent's own web UI login (`/login`) still works, unchanged. It goes away once you have confirmed the swarm login works. diff --git a/docs/4-implementation/web-ui/swarm-ui.md b/docs/4-implementation/web-ui/swarm-ui.md index fc9a2d979..8bfd47480 100644 --- a/docs/4-implementation/web-ui/swarm-ui.md +++ b/docs/4-implementation/web-ui/swarm-ui.md @@ -71,8 +71,9 @@ the code back. When the secret store has the login, the dialog closes and a short toast in the top-right corner says so. The credentials go to the agent through the secret store ([`claude-login.md`](../../2-handbook/swarm/claude-login.md)). The agent's own web UI login still works too. The settings section of an opened card has a **claude login** row: when -the swarm stored the agent's login, or that none is, with a **delete login** -action that asks first. +the swarm stored the agent's login, or that none is, with a **log in** button +(**log in again** when the swarm holds one) that opens the same dialog, and a **delete +login** action that asks first. A card shows **updating** when the agent's status snapshot has `updating` set. The hive sets it when an update stops a running container and clears it when diff --git a/frontend/packages/swarm-ui/src/agent/detail/AgentClaudeLogin.tsx b/frontend/packages/swarm-ui/src/agent/detail/AgentClaudeLogin.tsx index dbf52a963..b15735b8a 100644 --- a/frontend/packages/swarm-ui/src/agent/detail/AgentClaudeLogin.tsx +++ b/frontend/packages/swarm-ui/src/agent/detail/AgentClaudeLogin.tsx @@ -1,13 +1,15 @@ // — the swarm's stored claude login for one agent: when it // was written (`GET …/claude-login`, read from the secret store's metadata), -// or that none is, and a delete that removes every version of it -// (`DELETE …/claude-login/stored`) after a confirmation. +// or that none is, a button that logs the agent in (or in again) without +// waiting for its turns to fail first, and a delete that removes every version +// of it (`DELETE …/claude-login/stored`) after a confirmation. // // Its state belongs to one agent: callers key it by agent name. import { useCallback, useEffect, useState } from "preact/hooks"; import { ApiErrorPanel } from "@hive/shared/api-error-panel.js"; import { readApiError, type ProblemDetails } from "@hive/shared/api-error.js"; import { Badge } from "@hive/shared/badge.js"; +import { ClaudeLoginDialog } from "../claude-login/ClaudeLoginDialog.js"; import { ConfirmDialog } from "../../ui/confirm-dialog/ConfirmDialog.js"; import { Hint } from "../../ui/hint/Hint.js"; import { RelativeTime } from "../../ui/relative-time/RelativeTime.js"; @@ -26,6 +28,7 @@ export function AgentClaudeLogin({ agent }: { agent: string }) { const [confirming, setConfirming] = useState(false); const [deleting, setDeleting] = useState(false); const [deleteError, setDeleteError] = useState(null); + const [loggingIn, setLoggingIn] = useState(false); const load = useCallback(async () => { try { @@ -75,17 +78,37 @@ export function AgentClaudeLogin({ agent }: { agent: string }) { return errorPanel ?? …; } const at = storedAtMs(login); + const loginBadge = ( + setLoggingIn(true)} + /> + ); return ( <> {errorPanel} + {loggingIn ? ( + { + setLoggingIn(false); + void load(); + }} + /> + ) : null} {login.stored_at === null ? ( - none stored + <> + none stored {loginBadge} + ) : ( <> stored{" "} {at === null ? login.stored_at : } {" "} + {loginBadge}{" "}