Close gaps: GLM-judge (anthropic registry) + run cancel + deep-link
#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:
co-authored by
Claude Opus 4.8
parent
f845dfb15f
commit
6e87433c66
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user