From 8f6f1df05927a0d5917e0ffd57dd35a17f5f75e8 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 29 May 2026 12:05:22 -0700 Subject: [PATCH] feat(web): restore Orchestrator settings tab + Telegram link UI in Profile Restores two surfaces orphaned in the Phase-12 nav restructure. Orchestrator: moved web/src/components/OrchestratorTab.tsx -> web/src/pages/settings/OrchestratorTab.tsx, refactored hand-rolled markup onto shared ui primitives (Card, Button, Field, StatusPill, Modal). Data logic (GET/PUT /api/orchestrator, POST start/stop) preserved verbatim. Wired into SettingsPage as a new "orchestrator" sub-tab. Telegram: new TelegramCard in ProfileTab covering all three states (bot not configured / unlinked / linked) over GET /api/telegram/status, POST /link-code, DELETE /link, PUT /default-session. Sessions read via useSessions for the default-session select, array-guarded. Co-Authored-By: Claude Opus 4.8 (1M context) --- web/src/components/OrchestratorTab.tsx | 188 -------------------- web/src/pages/SettingsPage.tsx | 16 +- web/src/pages/settings/OrchestratorTab.tsx | 189 +++++++++++++++++++++ web/src/pages/settings/ProfileTab.tsx | 170 +++++++++++++++++- 4 files changed, 371 insertions(+), 192 deletions(-) delete mode 100644 web/src/components/OrchestratorTab.tsx create mode 100644 web/src/pages/settings/OrchestratorTab.tsx diff --git a/web/src/components/OrchestratorTab.tsx b/web/src/components/OrchestratorTab.tsx deleted file mode 100644 index 1ce24cd..0000000 --- a/web/src/components/OrchestratorTab.tsx +++ /dev/null @@ -1,188 +0,0 @@ -import { useEffect, useState } from 'react' -import { hubFetch } from '../lib/api' - -type OrchestratorSnapshot = { - enabled: boolean - name: string - custom_instructions: string | null - session_id: string | null - status: 'disabled' | 'enabled_idle' | 'running' -} - -export function OrchestratorTab({ token }: { token: string }) { - const [snap, setSnap] = useState(null) - const [name, setName] = useState('Orchestrator') - const [instructions, setInstructions] = useState('') - const [err, setErr] = useState(null) - const [busy, setBusy] = useState(false) - const [showEnableModal, setShowEnableModal] = useState(false) - const [savedFlash, setSavedFlash] = useState(false) - - async function refresh() { - try { - const r = await hubFetch(token, '/api/orchestrator') - setSnap(r) - setName(r.name) - setInstructions(r.custom_instructions ?? '') - } catch (e: any) { - setErr(e?.message ?? 'load failed') - } - } - - useEffect(() => { void refresh() }, []) - - async function patch(body: Partial<{ enabled: boolean; name: string; custom_instructions: string | null }>) { - setBusy(true); setErr(null) - try { - const r = await hubFetch(token, '/api/orchestrator', { method: 'PUT', json: body }) - setSnap(r) - setName(r.name) - setInstructions(r.custom_instructions ?? '') - setSavedFlash(true); setTimeout(() => setSavedFlash(false), 1200) - } catch (e: any) { - setErr(e?.message ?? 'save failed') - } finally { - setBusy(false) - } - } - - async function start() { - setBusy(true); setErr(null) - try { - await hubFetch(token, '/api/orchestrator/start', { method: 'POST', json: {} }) - await refresh() - } catch (e: any) { - setErr(e?.message ?? 'start failed') - } finally { - setBusy(false) - } - } - - async function stop() { - setBusy(true); setErr(null) - try { - await hubFetch(token, '/api/orchestrator/stop', { method: 'POST', json: {} }) - await refresh() - } catch (e: any) { - setErr(e?.message ?? 'stop failed') - } finally { - setBusy(false) - } - } - - if (!snap) { - return
Loading…
- } - - return ( -
-
-
-
-

Orchestrator session

-

- A pinned Claude session that runs in your repos parent folder and is taught how to read state from - and coordinate your other Claude sessions via the hub API. When enabled it auto-launches as soon as - your supervisor connects and stays available (it's the default target for Telegram). Disable it any - time below — once disabled it won't auto-launch again until you turn it back on. -

-
- {snap.status === 'running' ? 'Running' : snap.status === 'enabled_idle' ? 'Idle' : 'Disabled'} -
- -
- - {snap.enabled && snap.status !== 'running' && ( - - )} - {snap.status === 'running' && ( - - )} - {savedFlash && Saved} -
- - {snap.enabled && ( -
-
- - setName(e.target.value)} - onBlur={() => { if (name.trim() && name !== snap.name) void patch({ name: name.trim() }) }} - placeholder="Orchestrator" - maxLength={64} - className="w-full bg-[var(--bg-tertiary)]/60 text-[var(--text-primary)] rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-indigo-500/40" - /> -
-
- -