diff --git a/app/src/components/Select.tsx b/app/src/components/Select.tsx index fc62a2d..96b3ed8 100644 --- a/app/src/components/Select.tsx +++ b/app/src/components/Select.tsx @@ -14,7 +14,11 @@ import { Icon } from "./Icon"; * Leeworks Select, ported from components/forms/Select.jsx. Native + ) : null} + Task diff --git a/app/src/state/AppState.tsx b/app/src/state/AppState.tsx index 68e187f..659a1e2 100644 --- a/app/src/state/AppState.tsx +++ b/app/src/state/AppState.tsx @@ -14,6 +14,7 @@ import { type ApiClient, type ApiError, type Checkmark, + type ClaudeModel, type LogEntry, type Project, } from "../api/client"; @@ -95,6 +96,8 @@ interface AppStateValue { loading: boolean; error: string | null; projects: Project[]; + /* Registered model backends (the spawn/schedule dropdown next to the subscription). */ + models: ClaudeModel[]; waiting: WaitingItem[]; recent: RecentItem[]; counts: { running: number; waiting: number; done: number }; @@ -108,7 +111,7 @@ interface AppStateValue { // Mutations. sendAnswer: (text: string) => Promise<{ resumed: boolean; note?: string }>; - spawn: (project: string, task: string) => Promise; + spawn: (project: string, task: string, modelId?: number | null) => Promise; kill: (project: string, name: string) => Promise; } @@ -152,6 +155,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { // Fleet data. const [projects, setProjects] = useState([]); + const [models, setModels] = useState([]); const [agentsByProject, setAgentsByProject] = useState>({}); const [checkmarks, setCheckmarks] = useState>({}); const [logsByAgent, setLogsByAgent] = useState>({}); @@ -166,6 +170,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { const resetData = useCallback(() => { setProjects([]); + setModels([]); setAgentsByProject({}); setCheckmarks({}); setLogsByAgent({}); @@ -190,6 +195,15 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { setError(null); const projs = await client.api("/projects"); + // Model backends feed the spawn dropdown; an older server without the endpoint + // (404) just means "subscription only", so failures leave the list empty. + const modelList = await client + .api("/claude/models") + .catch((e) => { + if (e instanceof AuthError) throw e; + return [] as ClaudeModel[]; + }); + // Per-agent/per-project sub-requests are isolated: one flaky agent (a 500 on // its log, say) must not blank the whole fleet. A rejected AuthError still // propagates via onUnauthorized inside the client; here we just record the @@ -249,6 +263,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { for (const [k, v] of logEntries) logMap[k] = v; setProjects(projs); + setModels(modelList); setAgentsByProject(abp); setCheckmarks(cmMap); setLogsByAgent(logMap); @@ -424,11 +439,15 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { ); const spawn = useCallback( - async (project: string, task: string) => { + async (project: string, task: string, modelId?: number | null) => { if (!client) throw new Error("not connected"); const name = deriveAgentName(task); await client.api(`/projects/${enc(project)}/agents/spawn`, { - body: { name, ...(task.trim() ? { task: task.trim() } : {}) }, + body: { + name, + ...(task.trim() ? { task: task.trim() } : {}), + ...(modelId != null ? { model_id: modelId } : {}), + }, }); await refresh(); }, @@ -460,6 +479,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { loading, error, projects, + models, waiting, recent, counts, @@ -483,6 +503,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) { loading, error, projects, + models, waiting, recent, counts,