diff --git a/docs/2-handbook/web-ui/agent.md b/docs/2-handbook/web-ui/agent.md index 3a73a45d..fd88e84e 100644 --- a/docs/2-handbook/web-ui/agent.md +++ b/docs/2-handbook/web-ui/agent.md @@ -247,13 +247,12 @@ use semantic status codes: **400** for missing/malformed input failures. The matching mutations fire `LiveEvent` variants on the per-agent bus, so the client doesn't refetch `/api/state` on submit — the SSE stream -delivers the new state faster anyway. Only the login flow still -polls (session output streams in updates that aren't event- -shaped). +delivers the new state faster anyway. + +The agent has no login flow of its own. An agent without credentials +reports `needs_login_idle`, the operator logs in from the swarm UI, and the +credentials arrive from the swarm secret store. -- `POST /login/{start,code,cancel}` — claude OAuth login flow. - Start/cancel emit `LiveEvent::StatusChanged` to flip the - badge to/from `needs_login_in_progress`. - `POST /api/cancel` — SIGINT the in-flight claude turn and the commands it started. Emits a `LiveEvent::Note`. - `POST /api/compact` — run `/compact` on the persistent session @@ -282,8 +281,8 @@ shaped). `needs_login_idle`. The turn-loop's next iteration parks into `wait_for_login`, which snapshots the credential dir (now missing the wiped files) and resumes when a fresh - credentials file appears via the dashboard's `/login/code` - flow (the same mtime-resumption path manual re-login uses). + credentials file appears (a login from the swarm UI stores one, + and the agent installs it). Always returns 200 with a body describing what happened — it folds per-file errors into the response + the Note so the @@ -291,8 +290,7 @@ shaped). error. Missing files (already logged out) count as idempotent. - `GET /api/state` — cold-load snapshot (`StateSnapshot`) consumed by - the frontend (`useAgentState.ts`) on page load and, on a poll, while - `status === 'needs_login_in_progress'`. Includes `turn_state`, + the frontend (`useAgentState.ts`) on page load. Includes `turn_state`, `context_window_tokens`, `qualified_label`, `model`, `model_source`, `effort`, `effort_source`, `links`, and other fields described inline throughout this document. All @@ -364,7 +362,7 @@ shaped). Bus events (new vocabulary on `/events/stream`): - `status_changed { status }` — `online` / `rate_limited` / - `needs_login_idle` / `needs_login_in_progress`. Drives the + `needs_login_idle`. Drives the alive-badge. The harness sets `rate_limited` when it detects a 429 response and clears it when the retry sleep expires. - `model_changed { model }` — drives the model chip. The harness emits it diff --git a/docs/4-implementation/web-ui/shape.md b/docs/4-implementation/web-ui/shape.md index 300bfeda..aca75d63 100644 --- a/docs/4-implementation/web-ui/shape.md +++ b/docs/4-implementation/web-ui/shape.md @@ -295,7 +295,7 @@ at without rebuilding the dist. The cases that matter: The gateway upstream config strips the prefix before forwarding to the per-agent server, so the agent's Rust routes (`api/state`, -`events/stream`, `screen/ws`, `login/start`, …) keep their absolute +`events/stream`, `screen/ws`, …) keep their absolute paths server-side. Only the browser-facing URLs key off the mount prefix. diff --git a/frontend/packages/agent/src/Root.tsx b/frontend/packages/agent/src/Root.tsx index 0859b644..61266b49 100644 --- a/frontend/packages/agent/src/Root.tsx +++ b/frontend/packages/agent/src/Root.tsx @@ -1,9 +1,7 @@ // — root of the Preact rewrite. Wires `Header` + `StatusChips` // to the real `/api/state` snapshot via `useAgentState`. Covers -// everything `app.js` did, including the `needs_login_idle`/ -// `needs_login_in_progress` recovery flow (`LoginFlow`) — an agent with -// no credentials has no other web-UI path back online, so this isn't -// optional polish. +// everything `app.js` did. An agent without credentials shows the +// `needs_login_idle` badge; it logs in from the swarm UI, not here. import { useEffect, useRef, useState } from "preact/hooks"; import type { BadgeTone } from "@hive/shared/badge.js"; import { SettingsMenu } from "@hive/shared/settings-menu.js"; @@ -20,7 +18,6 @@ import { HeaderPill } from "./components/HeaderPill.js"; import { SidePanel } from "./components/SidePanel.js"; import { InboxPanel } from "./components/InboxPanel.js"; import { TodosPanel } from "./components/TodosPanel.js"; -import { LoginFlow } from "./components/LoginFlow.js"; import { useAgentState } from "./hooks/useAgentState.js"; import { useTodos } from "./hooks/useTodos.js"; import { fmtAge, fmtTokens } from "./lib/format.js"; @@ -49,7 +46,6 @@ const ALIVE_LABELS: Record< online: { glyph: "●", text: "alive", tone: "positive" }, rate_limited: { glyph: "⊘", text: "rate limited", tone: "warning" }, needs_login_idle: { glyph: "◌", text: "needs login", tone: "warning" }, - needs_login_in_progress: { glyph: "◌", text: "logging in", tone: "warning" }, }; const STATE_LABELS: Record< string, @@ -336,14 +332,6 @@ export function Root() { )}
- {state.status === "needs_login_idle" || - state.status === "needs_login_in_progress" ? ( - - ) : null}
{panel} diff --git a/frontend/packages/agent/src/agent.css b/frontend/packages/agent/src/agent.css index edc17b48..df155c81 100644 --- a/frontend/packages/agent/src/agent.css +++ b/frontend/packages/agent/src/agent.css @@ -233,35 +233,6 @@ h3 { overflow: hidden; } -/* Login flow overlay: only rendered when status != online. Sits - centred over the (likely-empty) terminal area; doesn't take chrome - space in the normal online flow. `--agent-header-real-h` (set by - the new Preact page's `Header.tsx` via ResizeObserver, falling back - to the static `--agent-header-h` guess before the first - measurement / on the old app.js page, which never sets it) is the - header's ACTUAL rendered height, not an assumed one — see - Header.tsx's file comment for why this matters (a static guess is - wrong the moment the header wraps an extra line on some viewport). */ -.agent-status-overlay { - position: absolute; - top: calc(var(--agent-header-real-h, var(--agent-header-h)) + 1.5em); - left: 50%; - transform: translateX(-50%); - max-width: 44em; - width: calc(100% - 3em); - z-index: 10; -} -.agent-status-overlay:empty { - display: none; -} -.agent-status-overlay > * { - background: var(--bg-elev); - border: 1px solid var(--purple-dim); - border-radius: 6px; - padding: 1em 1.2em; - box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4); -} - .meta { color: var(--muted); font-size: 0.85em; @@ -308,16 +279,6 @@ a:hover { text-shadow: 0 0 10px currentColor; box-shadow: 0 0 10px -2px currentColor; } -.btn-login { - color: var(--amber); - border-color: var(--amber); -} -.btn-cancel { - color: var(--red); - border-color: var(--red); - font-size: 0.85em; - padding: 0.15em 0.6em; -} /* Orphaned rules — left here as a tombstone so a search for the class name finds them. Live consumers gone: - `.btn-rebuild` was a per-agent header chip, then briefly an @@ -343,57 +304,6 @@ a:hover { .sendform input:focus { outline: 1px solid var(--purple); } -.loginform { - display: flex; - gap: 0.6em; - margin-top: 0.5em; - align-items: stretch; -} -.loginform input { - font-family: inherit; - font-size: 1em; - background: rgba(255, 255, 255, 0.04); - color: var(--fg); - border: 1px solid var(--purple-dim); - padding: 0.4em 0.6em; - flex: 1; -} -.loginform input:focus { - outline: 1px solid var(--purple); -} - -/* Show / hide toggle for the masked OAuth-code input. Quiet by - default (muted border + transparent bg), lights amber on hover - / when pressed (`aria-pressed="true"`) so the operator sees at - a glance whether the code is currently visible. */ -.loginform-reveal { - font-family: inherit; - font-size: 1em; - background: transparent; - color: var(--muted); - border: 1px solid var(--purple-dim); - border-radius: 3px; - padding: 0 0.6em; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; - transition: - color 0.15s ease, - border-color 0.15s ease, - box-shadow 0.15s ease; -} -.loginform-reveal:hover, -.loginform-reveal:focus-visible { - color: var(--amber); - border-color: var(--amber); - outline: none; -} -.loginform-reveal[aria-pressed="true"] { - color: var(--amber); - border-color: var(--amber); - box-shadow: 0 0 8px -2px var(--amber); -} pre.diff { background: rgba(255, 255, 255, 0.03); border: 1px solid var(--purple-dim); diff --git a/frontend/packages/agent/src/components/LoginFlow.css b/frontend/packages/agent/src/components/LoginFlow.css deleted file mode 100644 index 620afc1b..00000000 --- a/frontend/packages/agent/src/components/LoginFlow.css +++ /dev/null @@ -1,36 +0,0 @@ -/* .login-card — one elevated-surface card for the whole login-recovery - overlay, replacing the old markup's per-`

`/`

` boxing - (`agent.css`'s `.agent-status-overlay > *`, still needed there - verbatim since app.js's still-live markup depends on it until the - index.html cutover — this file coexists with it rather than editing - it). Same visual language as `Dropdown`'s `.ui-dropdown` - (`@hive/shared/dropdown/Dropdown.css`) — background/border/radius/ - shadow values match exactly, so this reads as the same "popup" family - as every other floating surface in the app, just laid out as a static - block instead of an anchored, position:absolute menu (that part of - `Dropdown` is specific to hanging under a trigger and doesn't apply - here — `.agent-status-overlay` already centres this card itself). */ -.login-card { - display: flex; - flex-direction: column; - gap: 0.6em; - background: var(--bg-elev); - border: 1px solid var(--border); - border-radius: 0.5em; - box-shadow: 0 0.4em 1em rgba(0, 0, 0, 0.35); - padding: 1em 1.2em; -} -.login-card > * { - margin: 0; -} -/* claude's real OAuth URL is one long unbroken token (a query string - with no whitespace) — without this it doesn't wrap at the card's - bounded width (.agent-status-overlay caps at 44em) and instead - overflows straight past the card's right edge (mara reported this - with a screenshot). `overflow-wrap: anywhere` lets the - browser break mid-token as a last resort, same as any other - long-URL-in-a-narrow-column case — `word-break: break-all` is the - older alternative but breaks more eagerly than needed here. */ -.login-url { - overflow-wrap: anywhere; -} diff --git a/frontend/packages/agent/src/components/LoginFlow.tsx b/frontend/packages/agent/src/components/LoginFlow.tsx deleted file mode 100644 index 351c1cdb..00000000 --- a/frontend/packages/agent/src/components/LoginFlow.tsx +++ /dev/null @@ -1,186 +0,0 @@ -// — the `needs_login_idle` / `needs_login_in_progress` -// overlay. Ports app.js's `renderNeedsLoginIdle`/`renderLoginInProgress` -// as real functional UI, not just a status label: an agent whose -// credentials got rotated (or hit `/logout`) has NO other way back -// online, so this is load-bearing, not cosmetic. Reuses agent.css's -// `.status-needs-login`/`.btn-login`/`.loginform`/`.loginform-reveal` -// rules verbatim (same class names as the old markup). -// -// mara (screenshot review): "reuse the popup component for the login, -// the floating separate panels should be one card." The old markup's -// `.agent-status-overlay > *` rule boxed every direct-child `

`/ -// `` separately (a fine translation of `el().append()`-per-line, -// but not what a hand-authored component should do) — this renders one -// `.login-card` instead, styled with the same "elevated surface" look -// `Dropdown`'s `.ui-dropdown` already established (`@hive/shared/ -// dropdown/Dropdown.css`), not `Dropdown` itself (its `options.map` -// render is menu-item-shaped, not a generic content container — not a -// fit for a login form + output pane). -import { useState } from "preact/hooks"; -import type { SessionView } from "../types.js"; -import { - postLoginStart, - postLoginCode, - postLoginCancel, -} from "../lib/loginAction.js"; -import "./LoginFlow.css"; - -function LoginIdle({ onStarted }: { onStarted: () => void }) { - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - - function submit(e: Event) { - e.preventDefault(); - setBusy(true); - setError(null); - postLoginStart().then((r) => { - setBusy(false); - if (r.ok) onStarted(); - else setError(r.detail ?? "failed"); - }); - } - - return ( -

- ); -} - -function LoginInProgress({ - session, - onDone, -}: { - session: SessionView | null; - onDone: () => void; -}) { - const [code, setCode] = useState(""); - const [reveal, setReveal] = useState(false); - const [busy, setBusy] = useState(false); - const [error, setError] = useState(null); - const finished = session?.finished ?? false; - - function submitCode(e: Event) { - e.preventDefault(); - if (!code) return; - setBusy(true); - setError(null); - // Success: the next `/api/state` poll flips `status` back to - // `online` (or leaves it `needs_login_in_progress` on a bad code, - // with the process's own stderr showing up in `output` below) — - // nothing local to update here either way. - postLoginCode(code).then((r) => { - setBusy(false); - if (!r.ok) setError(r.detail ?? "failed"); - }); - } - - function cancel(e: Event) { - e.preventDefault(); - setBusy(true); - postLoginCancel().then(() => { - setBusy(false); - onDone(); - }); - } - - return ( - - ); -} - -export function LoginFlow({ - status, - session, - onRefresh, -}: { - status: "needs_login_idle" | "needs_login_in_progress"; - session: SessionView | null; - onRefresh: () => void; -}) { - return ( -
- {status === "needs_login_idle" ? ( - - ) : ( - - )} -
- ); -} diff --git a/frontend/packages/agent/src/components/MetaNav.css b/frontend/packages/agent/src/components/MetaNav.css index 84e9d5c7..16755e6d 100644 --- a/frontend/packages/agent/src/components/MetaNav.css +++ b/frontend/packages/agent/src/components/MetaNav.css @@ -1,7 +1,6 @@ /* popover — matches `@hive/shared`'s `Dropdown` (`.ui-dropdown`) values exactly (background/border/radius/shadow, item padding/hover), - same "reuse the values, not the component" call `.login-card` - (LoginFlow.css) already made, for the same reason: this popover's + a "reuse the values, not the component" call: this popover's items are real `` tags, not `Dropdown`'s command-dispatch `