mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-02 13:38:48 +02:00
[eric] agents: labelling a tool group after a restart loads the chat from disk instead of 500ing; copy-as-image stops fetching CSP-blocked fonts
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012G8kyALnPjsA7aJFmMBq3R
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
37c44294a1
commit
17ebd689f6
@@ -276,6 +276,12 @@ async def generate_group_meta(session_id: str, body: dict):
|
||||
tool_calls = body.get("tool_calls", [])
|
||||
if not group_id or not tool_calls:
|
||||
raise HTTPException(status_code=400, detail="group_id and tool_calls are required")
|
||||
# Same disk fallback as GET /sessions/{id}: after a backend restart every settled chat is on disk, not in memory, and this used to 500 on the first tool group the renderer labelled.
|
||||
if agent_manager.get_session(session_id) is None:
|
||||
try:
|
||||
await agent_manager.resume_session(session_id)
|
||||
except ValueError:
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
|
||||
# Dedup: share an in-flight Future across callers; refinement requests bypass since they may want fresh results.
|
||||
is_refinement = body.get("is_refinement", False)
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
"""generate-group-meta must find a chat that only lives on disk, the way GET /sessions/{id} does.
|
||||
|
||||
Seen in Eric's production console 2026-09-01: after a backend restart the renderer labelled a tool
|
||||
group in an open chat, the route looked only in memory, `metadata.generate_group_meta` raised
|
||||
ValueError, and the 500 surfaced as a CORS error (starlette's error middleware sits outside the CORS
|
||||
middleware, so a raw 500 carries no CORS headers)."""
|
||||
import asyncio
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
import backend.apps.agents.agents as agents_mod
|
||||
|
||||
|
||||
def p_run(coro):
|
||||
return asyncio.run(coro)
|
||||
|
||||
|
||||
def test_a_chat_that_is_only_on_disk_is_loaded_then_labelled(monkeypatch) -> None:
|
||||
calls: list = []
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "get_session", lambda sid: None)
|
||||
|
||||
async def fake_resume(sid):
|
||||
calls.append(("resume", sid))
|
||||
return object()
|
||||
|
||||
async def fake_generate(sid, group_id, tool_calls, results_summary=None, is_refinement=False):
|
||||
calls.append(("generate", sid, group_id))
|
||||
return {"name": "Reads", "icon": "<svg/>"}
|
||||
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "resume_session", fake_resume)
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "generate_group_meta", fake_generate)
|
||||
out = p_run(agents_mod.generate_group_meta("abc123", {"group_id": "g1", "tool_calls": [{"tool": "Read"}]}))
|
||||
assert out["name"] == "Reads"
|
||||
assert calls == [("resume", "abc123"), ("generate", "abc123", "g1")]
|
||||
|
||||
|
||||
def test_a_chat_that_exists_nowhere_is_an_honest_404_not_a_500(monkeypatch) -> None:
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "get_session", lambda sid: None)
|
||||
|
||||
async def fake_resume(sid):
|
||||
raise ValueError(f"Session {sid} not found")
|
||||
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "resume_session", fake_resume)
|
||||
with pytest.raises(HTTPException) as exc:
|
||||
p_run(agents_mod.generate_group_meta("nope", {"group_id": "g1", "tool_calls": [{"tool": "Read"}]}))
|
||||
assert exc.value.status_code == 404
|
||||
|
||||
|
||||
def test_an_in_memory_chat_never_touches_the_disk(monkeypatch) -> None:
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "get_session", lambda sid: object())
|
||||
|
||||
async def fake_resume(sid):
|
||||
raise AssertionError("resume_session must not run for a chat already in memory")
|
||||
|
||||
async def fake_generate(sid, group_id, tool_calls, results_summary=None, is_refinement=False):
|
||||
return {"name": "ok"}
|
||||
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "resume_session", fake_resume)
|
||||
monkeypatch.setattr(agents_mod.agent_manager, "generate_group_meta", fake_generate)
|
||||
assert p_run(agents_mod.generate_group_meta("mem", {"group_id": "g", "tool_calls": [{"tool": "Read"}]}))["name"] == "ok"
|
||||
@@ -23,7 +23,7 @@
|
||||
font-src 'self' data: file: https://fonts.gstatic.com;
|
||||
img-src 'self' data: blob: file: http: https:;
|
||||
media-src 'self' data: blob: http: https:;
|
||||
connect-src 'self' file: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https://api.openswarm.com https://*.openswarm.com https://openswarm.com https://api.github.com;
|
||||
connect-src 'self' data: file: http://localhost:* http://127.0.0.1:* ws://localhost:* ws://127.0.0.1:* https://api.openswarm.com https://*.openswarm.com https://openswarm.com https://api.github.com;
|
||||
frame-src 'self' file: http: https: http://localhost:* http://127.0.0.1:*;
|
||||
worker-src 'self' blob:;
|
||||
object-src 'none';
|
||||
|
||||
@@ -55,7 +55,8 @@ const WidgetCopyChip: React.FC<WidgetCopyChipProps> = ({ component, props, conta
|
||||
const node = containerRef.current;
|
||||
if (node) {
|
||||
// Visuals (chart, stats, map, image...) copy as a real image; 2x for retina-crisp pastes.
|
||||
const dataUrl = await toPng(node as HTMLElement, { pixelRatio: 2 });
|
||||
// skipFonts: the Google Fonts stylesheets are CSP-blocked for fetch, so embedding them only logs errors and changes nothing.
|
||||
const dataUrl = await toPng(node as HTMLElement, { pixelRatio: 2, skipFonts: true });
|
||||
const blob = await (await fetch(dataUrl)).blob();
|
||||
await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
|
||||
flashCopied();
|
||||
|
||||
@@ -1202,7 +1202,7 @@ async function enumerateCandidates(wv: BrowserWebview): Promise<RankItem[]> {
|
||||
await walkSession(child.sessionId, _AX_CHILD_TIMEOUT_MS, 'child frame');
|
||||
}
|
||||
if (framesDropped > 0) {
|
||||
console.log(`[cdp] enumerateCandidates capped at ${_MAX_TOTAL_FRAMES} frames; dropped ${framesDropped}`);
|
||||
console.debug(`[cdp] enumerateCandidates capped at ${_MAX_TOTAL_FRAMES} frames; dropped ${framesDropped}`);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user