mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-12 12:47:42 +02:00
[eric] agents: extract view-builder render state into its own module
This commit is contained in:
@@ -60,6 +60,11 @@ from backend.apps.agents.manager.streaming.state import ThinkingState, TurnState
|
||||
from backend.apps.agents.manager.streaming.hook_context import HookContext
|
||||
from backend.apps.agents.manager.streaming import thinking as thinking_mod
|
||||
from backend.apps.agents.manager.permissions import gate_hooks
|
||||
from backend.apps.agents.manager.view_builder_state import (
|
||||
VIEW_BUILDER_RENDER_MAX_RETRIES,
|
||||
view_builder_render_retry_counts,
|
||||
view_builder_dirty_sessions,
|
||||
)
|
||||
from backend.apps.agents.manager.session.workspace_git import _detect_git_identity, _ensure_cwd_git_repo
|
||||
from backend.apps.agents.manager.prompt.tool_catalog import (
|
||||
FULL_TOOLS,
|
||||
@@ -99,11 +104,6 @@ logger = logging.getLogger(__name__)
|
||||
os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000")
|
||||
|
||||
|
||||
p_VIEW_BUILDER_RENDER_MAX_RETRIES = 2
|
||||
p_view_builder_render_retry_counts: dict[str, int] = {}
|
||||
p_view_builder_dirty_sessions: set[str] = set()
|
||||
|
||||
|
||||
class AgentManager:
|
||||
def __init__(self):
|
||||
self.sessions: dict[str, AgentSession] = {}
|
||||
@@ -508,7 +508,7 @@ class AgentManager:
|
||||
))
|
||||
|
||||
if session.mode == "view-builder" and (wrote_frontend_file or installed_pkg):
|
||||
p_view_builder_dirty_sessions.add(session.id)
|
||||
view_builder_dirty_sessions.add(session.id)
|
||||
try:
|
||||
from backend.apps.outputs.runtime import (
|
||||
manager as outputs_runtime_manager,
|
||||
@@ -1107,7 +1107,7 @@ class AgentManager:
|
||||
MAX_RETRIES then lets the stop through."""
|
||||
if session.mode != "view-builder":
|
||||
return {}
|
||||
if session.id not in p_view_builder_dirty_sessions:
|
||||
if session.id not in view_builder_dirty_sessions:
|
||||
return {}
|
||||
from backend.apps.outputs.runtime import (
|
||||
manager as outputs_runtime_manager,
|
||||
@@ -1122,31 +1122,31 @@ class AgentManager:
|
||||
state, error_text = outputs_runtime_manager.get_render_state_for_workspace(session.id)
|
||||
|
||||
if state != "error":
|
||||
p_view_builder_render_retry_counts.pop(session.id, None)
|
||||
p_view_builder_dirty_sessions.discard(session.id)
|
||||
view_builder_render_retry_counts.pop(session.id, None)
|
||||
view_builder_dirty_sessions.discard(session.id)
|
||||
return {}
|
||||
|
||||
attempts = p_view_builder_render_retry_counts.get(session.id, 0)
|
||||
if attempts >= p_VIEW_BUILDER_RENDER_MAX_RETRIES:
|
||||
attempts = view_builder_render_retry_counts.get(session.id, 0)
|
||||
if attempts >= VIEW_BUILDER_RENDER_MAX_RETRIES:
|
||||
logger.warning(
|
||||
"view-builder preview still failing after %s attempts for session %s; allowing stop",
|
||||
attempts, session.id,
|
||||
)
|
||||
p_view_builder_render_retry_counts.pop(session.id, None)
|
||||
p_view_builder_dirty_sessions.discard(session.id)
|
||||
view_builder_render_retry_counts.pop(session.id, None)
|
||||
view_builder_dirty_sessions.discard(session.id)
|
||||
return {}
|
||||
|
||||
p_view_builder_render_retry_counts[session.id] = attempts + 1
|
||||
view_builder_render_retry_counts[session.id] = attempts + 1
|
||||
logger.info(
|
||||
"view-builder render block (attempt %s/%s) for session %s",
|
||||
attempts + 1, p_VIEW_BUILDER_RENDER_MAX_RETRIES, session.id,
|
||||
attempts + 1, VIEW_BUILDER_RENDER_MAX_RETRIES, session.id,
|
||||
)
|
||||
trimmed = error_text[-3000:] if len(error_text) > 3000 else error_text
|
||||
return {
|
||||
"decision": "block",
|
||||
"reason": (
|
||||
f"The preview failed to render (attempt {attempts + 1}/"
|
||||
f"{p_VIEW_BUILDER_RENDER_MAX_RETRIES}):\n\n"
|
||||
f"{VIEW_BUILDER_RENDER_MAX_RETRIES}):\n\n"
|
||||
f"{trimmed}\n\n"
|
||||
"Fix this so the app renders before finishing; the user "
|
||||
"currently sees an error instead of the app."
|
||||
@@ -3325,8 +3325,8 @@ class AgentManager:
|
||||
self.sessions.pop(session_id, None)
|
||||
self.tasks.pop(session_id, None)
|
||||
self._live_partial.pop(session_id, None)
|
||||
p_view_builder_render_retry_counts.pop(session_id, None)
|
||||
p_view_builder_dirty_sessions.discard(session_id)
|
||||
view_builder_render_retry_counts.pop(session_id, None)
|
||||
view_builder_dirty_sessions.discard(session_id)
|
||||
|
||||
async def delete_session(self, session_id: str) -> None:
|
||||
"""Permanently delete a session: remove from memory and JSON file.
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
"""Cross-call view-builder render state, shared between the agent loop (which runs the
|
||||
capped render-retry) and the post-tool hook (which marks a session dirty after a frontend
|
||||
write/install). Module-level singletons on purpose: the retry counter and dirty set must
|
||||
persist across turns and be the SAME objects both readers mutate."""
|
||||
|
||||
from typing import Dict, Set
|
||||
|
||||
VIEW_BUILDER_RENDER_MAX_RETRIES = 2
|
||||
# session_id -> consecutive view-builder render attempts (capped, then it gives up).
|
||||
view_builder_render_retry_counts: Dict[str, int] = {}
|
||||
# session_ids whose view-builder workspace was written/installed since the last render.
|
||||
view_builder_dirty_sessions: Set[str] = set()
|
||||
@@ -18,16 +18,16 @@ def test_purge_session_memory_clears_every_structure():
|
||||
mgr.sessions = {"dead": object(), "alive": object()}
|
||||
mgr.tasks = {"dead": object()}
|
||||
mgr._live_partial = {"dead": {"text": "half a reply"}}
|
||||
am.p_view_builder_render_retry_counts["dead"] = 4
|
||||
am.p_view_builder_dirty_sessions.add("dead")
|
||||
am.view_builder_render_retry_counts["dead"] = 4
|
||||
am.view_builder_dirty_sessions.add("dead")
|
||||
|
||||
mgr._purge_session_memory("dead")
|
||||
|
||||
assert "dead" not in mgr.sessions
|
||||
assert "dead" not in mgr.tasks
|
||||
assert "dead" not in mgr._live_partial
|
||||
assert "dead" not in am.p_view_builder_render_retry_counts
|
||||
assert "dead" not in am.p_view_builder_dirty_sessions
|
||||
assert "dead" not in am.view_builder_render_retry_counts
|
||||
assert "dead" not in am.view_builder_dirty_sessions
|
||||
# Only the target id is purged; an unrelated live session survives.
|
||||
assert "alive" in mgr.sessions
|
||||
|
||||
|
||||
Reference in New Issue
Block a user