refactor: strip Gemini from the platform, and level up the architecture_mapper

Two things.

1. The architecture_mapper proposal, applied AND made durable.

The GLM proposal (019fddd9) was accepted in full: the agent's system_prompt now
carries the Mermaid-first constraint and its brain was rewritten. Both verified
against the live row and the .h5 file.

But `apply_identity` writes `UPDATE agents SET system_prompt` and
`apply_brain_consolidation` writes that agent's brain — neither touches the team
TEMPLATE. That agent is mission-scoped, so the improvement would have died with
the mission. The model's actual insight was sharp and worth keeping: "Mermaid
diagrams beat prose" lived in the brain SEED and not in the system PROMPT, so it
only applied when the agent happened to consult its brain. That constraint is
now in templates/teams/codebase_research.toml, where every future Codebase
Research team inherits it.

(The proposal's second item mostly restated anti-patterns the seed already
lists, so the seed is unchanged. Applying an LLM's suggestion is not the same as
agreeing with all of it.)

2. Gemini is gone.

Removed: the `gemini.default` provider alias and its `is_exact_provider_match`
prefix, GEMINI_API_KEY forwarding to agent containers, the evaluator's
gemini->gemini family row, the model selectors in claws/teams/planner and in
TeamWizard + AgentComputer, and the commented provider block in the runtime
config example (whose ZEROCLAW_AGENT_MAP example still mapped a worker_gemini
that no longer existed).

`provider_alias_for("gemini")` now returns claude_cli.default via the
unrecognised-model branch, which LOGS. A stray gemini binding degrades visibly
rather than resolving to a provider row we no longer ship. A test pins that, and
another pins that GEMINI_API_KEY is forwarded in NEITHER auth mode, so adding it
back to the list is a visible change rather than an accident.

Avatar generation is DELETED, not disabled — it called Gemini's image model, and
there is no alternative: Claude and Kimi are text-only, and z.ai answers
"Unknown Model" for cogview-3-flash and cogview-4 on our plan (measured, not
assumed). AvatarModal keeps UPLOAD, which never needed a provider; only the
prompt-generation half is gone.

240 backend lib tests, 89 frontend tests, clean tsc + eslint, build succeeds.
This commit is contained in:
Omar Sobh
2026-08-07 14:15:53 -07:00
parent f6c3ddbf81
commit 87f188ae73
13 changed files with 51 additions and 142 deletions
@@ -1,14 +1,18 @@
"use client";
// Avatar editor modal for a claw: upload an image OR generate one from a prompt
// (Gemini / Nano Banana, via /api/generate-avatar — max 5 attempts), preview it,
// then Save (downscaled to 256² and persisted via PATCH /api/claws/{id}).
// Avatar editor modal for a claw: upload an image, preview it, then Save
// (downscaled to 256² and persisted via PATCH /api/claws/{id}).
//
// Prompt-based generation was removed with the rest of the Gemini dependency:
// it called Gemini's image model, and no provider we use can generate images
// (Claude and Kimi are text-only; z.ai returns "Unknown Model" for cogview on
// our plan). Upload is untouched — it never needed a provider. Re-add a
// generate path here if an image provider is ever wired.
import { useEffect, useRef, useState } from "react";
import { Camera, Sparkles, Upload, X } from "lucide-react";
import { Camera, Upload, X } from "lucide-react";
const mono = "'JetBrains Mono', ui-monospace, monospace";
const MAX_ATTEMPTS = 5;
// Cover-fit to a square and re-encode small so avatars stay lightweight.
function downscale(dataUrl: string, size = 256): Promise<string> {
@@ -33,9 +37,7 @@ function downscale(dataUrl: string, size = 256): Promise<string> {
export function AvatarModal({ clawId, clawName, current, onClose, onSaved }: { clawId: string; clawName: string; current?: string | null; onClose: () => void; onSaved: (dataUrl: string) => void }) {
const [preview, setPreview] = useState<string | null>(current ?? null);
const [prompt, setPrompt] = useState("");
const [attempts, setAttempts] = useState(0);
const [busy, setBusy] = useState<null | "gen" | "save">(null);
const [busy, setBusy] = useState<null | "save">(null);
const [error, setError] = useState<string | null>(null);
const fileRef = useRef<HTMLInputElement>(null);
@@ -53,20 +55,6 @@ export function AvatarModal({ clawId, clawName, current, onClose, onSaved }: { c
reader.readAsDataURL(file);
}
async function generate() {
if (busy || attempts >= MAX_ATTEMPTS || !prompt.trim()) return;
setBusy("gen");
setError(null);
try {
const res = await fetch("/api/generate-avatar", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ prompt }) });
const j = (await res.json().catch(() => ({}))) as { image?: string; error?: string };
if (!res.ok || !j.image) setError(j.error || "generation failed");
else { setPreview(j.image); setAttempts((a) => a + 1); }
} catch {
setError("generation failed");
}
setBusy(null);
}
async function save() {
if (!preview || busy) return;
@@ -84,7 +72,6 @@ export function AvatarModal({ clawId, clawName, current, onClose, onSaved }: { c
}
}
const maxed = attempts >= MAX_ATTEMPTS;
return (
<div onClick={onClose} role="presentation" style={{ position: "fixed", inset: 0, zIndex: 110, background: "rgba(0,0,0,.62)", backdropFilter: "blur(4px)", display: "flex", alignItems: "center", justifyContent: "center", padding: 24, animation: "cm-fade .18s ease" }}>
@@ -92,7 +79,7 @@ export function AvatarModal({ clawId, clawName, current, onClose, onSaved }: { c
<div style={{ display: "flex", alignItems: "flex-start", gap: 12, marginBottom: 16 }}>
<div style={{ flex: 1 }}>
<div style={{ fontSize: 18, fontWeight: 700, color: "#f3f3f5" }}>{clawName}&apos;s image</div>
<div style={{ fontSize: 12.5, color: "#8a8a92", marginTop: 2 }}>Upload one, or generate from a prompt.</div>
<div style={{ fontSize: 12.5, color: "#8a8a92", marginTop: 2 }}>Upload a PNG or JPG.</div>
</div>
<button type="button" onClick={onClose} aria-label="Close" style={{ width: 30, height: 30, flex: "none", borderRadius: 8, border: "1px solid rgba(255,255,255,.12)", background: "transparent", color: "#9a9aa2", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center" }}><X size={15} /></button>
</div>
@@ -108,24 +95,6 @@ export function AvatarModal({ clawId, clawName, current, onClose, onSaved }: { c
</div>
</div>
<div style={{ borderTop: "1px solid rgba(255,255,255,.07)", paddingTop: 14 }}>
<div style={{ display: "flex", alignItems: "center", gap: 7, marginBottom: 8 }}>
<Sparkles size={13} color="#c98af0" />
<span style={{ fontFamily: mono, fontSize: 10, letterSpacing: ".08em", color: "#c98af0" }}>GENERATE FROM PROMPT</span>
<span style={{ flex: 1 }} />
<span style={{ fontFamily: mono, fontSize: 9, color: maxed ? "#e8b465" : "#5a5a62" }}>{attempts}/{MAX_ATTEMPTS} attempts</span>
</div>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
placeholder="e.g. a calm robotic owl mascot, soft teal gradient, minimal"
rows={2}
style={{ width: "100%", resize: "none", borderRadius: 9, border: "1px solid rgba(255,255,255,.12)", background: "#101014", color: "#eaeaee", fontSize: 12.5, padding: "8px 10px", outline: "none", fontFamily: "inherit" }}
/>
<button type="button" onClick={generate} disabled={busy !== null || maxed || !prompt.trim()} style={{ marginTop: 8, width: "100%", display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 7, padding: "9px 0", borderRadius: 9, border: "1px solid rgba(201,138,240,.4)", background: maxed || !prompt.trim() ? "rgba(201,138,240,.06)" : "rgba(201,138,240,.14)", color: "#d9b6f7", fontSize: 12.5, fontWeight: 600, cursor: busy || maxed || !prompt.trim() ? "default" : "pointer", opacity: busy === "gen" ? 0.7 : 1 }}>
<Sparkles size={14} /> {busy === "gen" ? "Generating…" : maxed ? "Max attempts reached" : attempts > 0 ? "Regenerate" : "Generate"}
</button>
</div>
{error ? <div role="alert" style={{ marginTop: 12, fontFamily: mono, fontSize: 10.5, color: "#ff8a7a" }}>{error}</div> : null}