Close gaps: GLM-judge (anthropic registry) + run cancel + deep-link
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

#2 GLM judge (closes #114): registry NamedProvider gains a `format` field;
build_provider_registry builds an AnthropicProvider for format="anthropic".
GLM's coding/OpenAI endpoint is ToS-throttled for raw SDK, but its Anthropic
endpoint (api.z.ai/api/anthropic) accepts raw API calls (verified x-api-key
-> glm-4.7), so CLAWMATES_JUDGE_MODEL=glm:glm-4.7 routes the door governor /
topology judge through GLM with no runtime-routing. (Kimi-as-judge still needs
a Platform key — coding key is agent-only.)

#3 run-control: POST /api/topology-runs/{id}/cancel (workspace-scoped,
queued/running only); the worker honors it at the step boundary (checks
current_status in the checkpoint callback) and won't clobber a cancel with
`failed`. Frontend Run tab gains a Cancel button and clickable recent runs
that deep-link into a live/replayed stream (SSE replays from checkpoint).

cm-* tests (incl. new cancel test) + clippy + frontend lint/typecheck green.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-18 03:07:41 -07:00
co-authored by Claude Opus 4.8
parent f845dfb15f
commit 6e87433c66
10 changed files with 199 additions and 18 deletions
@@ -43,6 +43,7 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const [runs, setRuns] = useState<RunSummary[]>([]);
const [runId, setRunId] = useState<string | null>(null);
const esRef = useRef<EventSource | null>(null);
async function loadRuns() {
@@ -68,6 +69,7 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
* browser auto-sends Last-Event-ID on reconnect so the server resumes. */
function stream(id: string) {
esRef.current?.close();
setRunId(id);
const es = new EventSource(`/api/topology-runs/${id}/events`);
esRef.current = es;
setStatus("running");
@@ -139,6 +141,27 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
}
}
/** Request cancellation; the SSE `done` event reports the cancelled status. */
async function cancel() {
if (!runId) return;
try {
await fetch(`/api/topology-runs/${runId}/cancel`, { method: "POST" });
} catch {
/* the worker also stops on the next status read */
}
}
/** Deep-link into a past/running run: the SSE endpoint replays its steps from
* the checkpoint, then tails live if it's still running. */
function openRun(id: string) {
setError(null);
setSteps([]);
setFinalOutput(null);
setStatus("running");
setBusy(true);
stream(id);
}
const started = status !== null;
return (
@@ -175,7 +198,7 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
/>
</label>
</div>
<div>
<div className="flex gap-2">
<button
type="button"
onClick={run}
@@ -184,6 +207,15 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
>
{busy ? "Running…" : "Run topology"}
</button>
{busy ? (
<button
type="button"
onClick={cancel}
className="rounded-lg border border-border px-4 py-2 text-sm text-muted-foreground hover:text-foreground"
>
Cancel
</button>
) : null}
</div>
{error ? <p className="text-sm text-red-500">{error}</p> : null}
@@ -230,16 +262,22 @@ export function TopologyRun({ catalog }: { catalog: CatalogEntry[] }) {
<p className="mb-2 text-xs font-medium text-muted-foreground">Recent runs</p>
<ul className="flex flex-col gap-1">
{runs.slice(0, 8).map((r) => (
<li key={r.id} className="flex items-center gap-2 text-xs text-muted-foreground">
<span
className={`rounded-full px-2 py-0.5 ${
STATUS_STYLE[r.status] ?? "bg-muted text-muted-foreground"
}`}
<li key={r.id}>
<button
type="button"
onClick={() => openRun(r.id)}
className="flex w-full items-center gap-2 rounded-md px-1 py-1 text-left text-xs text-muted-foreground hover:bg-muted/40"
>
{r.status}
</span>
<span className="text-foreground">{r.kind}</span>
<span className="truncate">{r.task}</span>
<span
className={`rounded-full px-2 py-0.5 ${
STATUS_STYLE[r.status] ?? "bg-muted text-muted-foreground"
}`}
>
{r.status}
</span>
<span className="text-foreground">{r.kind}</span>
<span className="truncate">{r.task}</span>
</button>
</li>
))}
</ul>