diff --git a/backend/apps/agents/core/seq_log.py b/backend/apps/agents/core/seq_log.py index 17f40be9..6a0b1309 100644 --- a/backend/apps/agents/core/seq_log.py +++ b/backend/apps/agents/core/seq_log.py @@ -34,10 +34,10 @@ class SeqLogStore: """Process-wide store. Per-session locks live inside `p_SessionSeqLog`.""" def __init__(self, persist_dir: Optional[str] = None) -> None: - self._per_session: dict[str, p_SessionSeqLog] = {} + self.per_session: dict[str, p_SessionSeqLog] = {} # Coarse lock guards only the setdefault path; never crosses an await. - self._dict_lock = asyncio.Lock() - self._persist_dir = persist_dir + self.p_dict_lock = asyncio.Lock() + self.p_persist_dir = persist_dir if persist_dir: try: os.makedirs(persist_dir, exist_ok=True) @@ -45,18 +45,18 @@ class SeqLogStore: logger.warning("seq_log: failed to create persist dir %s", persist_dir) async def _get_or_create(self, session_id: str) -> p_SessionSeqLog: - log = self._per_session.get(session_id) + log = self.per_session.get(session_id) if log is not None: return log - async with self._dict_lock: - log = self._per_session.get(session_id) + async with self.p_dict_lock: + log = self.per_session.get(session_id) if log is None: log = p_SessionSeqLog() - self._per_session[session_id] = log + self.per_session[session_id] = log return log def _peek(self, session_id: str) -> Optional[p_SessionSeqLog]: - return self._per_session.get(session_id) + return self.per_session.get(session_id) @asynccontextmanager async def stamp( @@ -99,13 +99,13 @@ class SeqLogStore: return log.seq if log else 0 def _terminal_path(self, session_id: str) -> Optional[str]: - if not self._persist_dir: + if not self.p_persist_dir: return None # Session ids are uuid4 hex; sanitize anyway against path traversal. safe = "".join(c for c in session_id if c.isalnum() or c in ("-", "_")) if not safe: return None - return os.path.join(self._persist_dir, f"{safe}.json") + return os.path.join(self.p_persist_dir, f"{safe}.json") def persist_terminal(self, session_id: str, payload_str: str) -> None: """Atomic write of a terminal event for post-restart clients; best-effort, never blocks broadcast.""" @@ -134,7 +134,7 @@ class SeqLogStore: def clear(self, session_id: str) -> None: """Drop in-memory log and persisted terminal; for full deletion only, closed-but-retained sessions keep it.""" - self._per_session.pop(session_id, None) + self.per_session.pop(session_id, None) path = self._terminal_path(session_id) if path and os.path.exists(path): try: diff --git a/backend/apps/outputs/runtime.py b/backend/apps/outputs/runtime.py index 670c3fe6..534bbb25 100644 --- a/backend/apps/outputs/runtime.py +++ b/backend/apps/outputs/runtime.py @@ -85,15 +85,15 @@ class AppRuntime: # _start_new_mode). frontend_url returns null until this flips, # so the preview pane doesn't try to navigate to an unbound port # and show a "Site can't be reached" error mid-npm-install. - self._frontend_ready: bool = False + self.p_frontend_ready: bool = False # True while the process tree is SIGSTOP'd in the idle pool. A frozen # vite still holds its port but can't answer it, so frontend_url must # stay null while suspended (else the webview loads a dead port = the # ERR_FAILED on fast app-switching). - self._suspended: bool = False + self.p_suspended: bool = False self.process: Optional[asyncio.subprocess.Process] = None self.log_buffer: deque[LogLine] = deque(maxlen=LOG_BUFFER_LINES) - self._subscribers: set[LogSubscriber] = set() + self.p_subscribers: set[LogSubscriber] = set() # Recent build/runtime errors scraped from stderr; drained by # the agent's post-tool hook after Write/Edit so the agent sees # vite/babel/uvicorn errors in its next turn and can self-fix @@ -101,10 +101,10 @@ class AppRuntime: self.recent_errors: deque[str] = deque(maxlen=RECENT_ERRORS_MAX) self.render_state: Optional[str] = None self.render_error_text: str = "" - self._stdout_task: Optional[asyncio.Task] = None - self._stderr_task: Optional[asyncio.Task] = None - self._wait_task: Optional[asyncio.Task] = None - self._frontend_ready_task: Optional[asyncio.Task] = None + self.p_stdout_task: Optional[asyncio.Task] = None + self.p_stderr_task: Optional[asyncio.Task] = None + self.p_wait_task: Optional[asyncio.Task] = None + self.p_frontend_ready_task: Optional[asyncio.Task] = None self.p_lock = asyncio.Lock() def drain_errors(self) -> list[str]: @@ -151,7 +151,7 @@ class AppRuntime: # the ERR_FAILED you see on reopen. No live process, no URL. # And gated on `not _suspended`: a SIGSTOP'd idle runtime is "running" # (returncode is None) but frozen, so its port won't answer. - if self.frontend_port and self._frontend_ready and self.running and not self._suspended: + if self.frontend_port and self.p_frontend_ready and self.running and not self.p_suspended: return f"http://127.0.0.1:{self.frontend_port}/" return None @@ -283,13 +283,13 @@ class AppRuntime: return False backend_note = f" + backend on {self.port}" if self.port else "" self._broadcast(LogLine("runtime", f"[runtime] {launch_desc} started; frontend on {self.frontend_port}{backend_note} (pid {self.process.pid})")) - self._stdout_task = asyncio.create_task(self._pipe_stream(self.process.stdout, "stdout")) - self._stderr_task = asyncio.create_task(self._pipe_stream(self.process.stderr, "stderr")) - self._wait_task = asyncio.create_task(self._await_exit()) + self.p_stdout_task = asyncio.create_task(self._pipe_stream(self.process.stdout, "stdout")) + self.p_stderr_task = asyncio.create_task(self._pipe_stream(self.process.stderr, "stderr")) + self.p_wait_task = asyncio.create_task(self._await_exit()) # Kick off the port-bind poller so frontend_url flips on once # Vite is actually accepting connections. - self._frontend_ready = False - self._frontend_ready_task = asyncio.create_task(self._await_frontend_bind()) + self.p_frontend_ready = False + self.p_frontend_ready_task = asyncio.create_task(self._await_frontend_bind()) return True def _resolve_launch(self, env: dict) -> tuple[list[str], str, str]: @@ -368,7 +368,7 @@ class AppRuntime: await writer.wait_closed() except Exception: pass - self._frontend_ready = True + self.p_frontend_ready = True self._broadcast(LogLine( "runtime", f"[runtime] frontend ready at http://127.0.0.1:{port}/", @@ -424,9 +424,9 @@ class AppRuntime: self.process = None return False self._broadcast(LogLine("runtime", f"[runtime] backend started on port {self.port} (pid {self.process.pid})")) - self._stdout_task = asyncio.create_task(self._pipe_stream(self.process.stdout, "stdout")) - self._stderr_task = asyncio.create_task(self._pipe_stream(self.process.stderr, "stderr")) - self._wait_task = asyncio.create_task(self._await_exit()) + self.p_stdout_task = asyncio.create_task(self._pipe_stream(self.process.stdout, "stdout")) + self.p_stderr_task = asyncio.create_task(self._pipe_stream(self.process.stderr, "stderr")) + self.p_wait_task = asyncio.create_task(self._await_exit()) return True def _spawn_env_base(self) -> dict[str, str]: @@ -448,8 +448,8 @@ class AppRuntime: if not self.process or self.process.returncode is not None: # Still cancel the bind poller in case stop() races a # never-launched runtime; defensive no-op otherwise. - if self._frontend_ready_task and not self._frontend_ready_task.done(): - self._frontend_ready_task.cancel() + if self.p_frontend_ready_task and not self.p_frontend_ready_task.done(): + self.p_frontend_ready_task.cancel() return try: # Walk the descendant tree first so vite/uvicorn grandchildren @@ -468,9 +468,9 @@ class AppRuntime: pass # Cancel the bind poller so it stops scanning a port that's # gone away, and reset the readiness flag. - if self._frontend_ready_task and not self._frontend_ready_task.done(): - self._frontend_ready_task.cancel() - self._frontend_ready = False + if self.p_frontend_ready_task and not self.p_frontend_ready_task.done(): + self.p_frontend_ready_task.cancel() + self.p_frontend_ready = False async def restart(self) -> bool: await self.stop() @@ -480,7 +480,7 @@ class AppRuntime: """Register a log subscriber. Immediately replays the ring buffer so a Terminal pane that opens mid-session shows context. Returns an unsubscribe function.""" - self._subscribers.add(cb) + self.p_subscribers.add(cb) for line in list(self.log_buffer): try: cb(line) @@ -488,14 +488,14 @@ class AppRuntime: pass def p_unsub() -> None: - self._subscribers.discard(cb) + self.p_subscribers.discard(cb) return p_unsub def _broadcast(self, line: LogLine) -> None: self.log_buffer.append(line) # Snapshot subscribers; they can self-remove during dispatch. - for cb in list(self._subscribers): + for cb in list(self.p_subscribers): try: cb(line) except Exception: @@ -536,7 +536,7 @@ class AppRuntime: # Unclean death (vite crash, OOM, orphaned parent) must drop readiness; # otherwise frontend_url keeps advertising a dead port and the preview # navigates into ERR_FAILED. stop() already does this for clean stops. - self._frontend_ready = False + self.p_frontend_ready = False self._broadcast(LogLine("runtime", f"[runtime] backend exited with code {rc}")) @@ -553,11 +553,11 @@ class AppRuntimeManager: def __init__(self) -> None: # workspace_id → AppRuntime, currently has >=1 subscriber. self.runtimes: dict[str, AppRuntime] = {} - self._attached: dict[str, int] = {} + self.p_attached: dict[str, int] = {} # workspace_id → AppRuntime with no subscribers but still # alive. OrderedDict gives O(1) move_to_end + popitem(last=False) # for LRU semantics. - self._idle_lru: "OrderedDict[str, AppRuntime]" = OrderedDict() + self.idle_lru: "OrderedDict[str, AppRuntime]" = OrderedDict() self.p_lock = asyncio.Lock() async def attach(self, workspace_id: str, workspace_path: str) -> AppRuntime: @@ -571,7 +571,7 @@ class AppRuntimeManager: if rt is None: # Maybe the runtime is sitting idle in the LRU; revive # it without paying the spawn cost again. - idle_rt = self._idle_lru.pop(workspace_id, None) + idle_rt = self.idle_lru.pop(workspace_id, None) if idle_rt is not None and idle_rt.running: rt = idle_rt rt.workspace_path = workspace_path @@ -580,7 +580,7 @@ class AppRuntimeManager: # SIGCONT the process tree if A2 had it paused while # idle. Pair with the SIGSTOP in detach() below. resume_process_tree(rt.process) - rt._suspended = False + rt.p_suspended = False else: if idle_rt is not None: # Stale idle entry; process died while idling. @@ -595,7 +595,7 @@ class AppRuntimeManager: # folder), trust the latest caller; they have the # current truth. rt.workspace_path = workspace_path - self._attached[workspace_id] = self._attached.get(workspace_id, 0) + 1 + self.p_attached[workspace_id] = self.p_attached.get(workspace_id, 0) + 1 if not revived and not rt.running: await rt.start() # Stop any dead idle runtime outside the lock to avoid blocking. @@ -610,11 +610,11 @@ class AppRuntimeManager: to_idle: Optional[AppRuntime] = None to_reap: list[AppRuntime] = [] async with self.p_lock: - count = self._attached.get(workspace_id, 0) - 1 + count = self.p_attached.get(workspace_id, 0) - 1 if count > 0: - self._attached[workspace_id] = count + self.p_attached[workspace_id] = count return - self._attached.pop(workspace_id, None) + self.p_attached.pop(workspace_id, None) rt = self.runtimes.pop(workspace_id, None) if rt is None: return @@ -625,12 +625,12 @@ class AppRuntimeManager: if not rt.running: to_reap.append(rt) else: - self._idle_lru[workspace_id] = rt - self._idle_lru.move_to_end(workspace_id) + self.idle_lru[workspace_id] = rt + self.idle_lru.move_to_end(workspace_id) suspend_process_tree(rt.process) - rt._suspended = True - while len(self._idle_lru) > MAX_IDLE_RUNTIMES: - _, old_rt = self._idle_lru.popitem(last=False) + rt.p_suspended = True + while len(self.idle_lru) > MAX_IDLE_RUNTIMES: + _, old_rt = self.idle_lru.popitem(last=False) # Reaping a stopped process: SIGCONT first so the # SIGTERM in stop() can be delivered cleanly (a # SIGSTOP'd process can't run its own shutdown). @@ -647,7 +647,7 @@ class AppRuntimeManager: except Exception: logger.exception("failed to reap idle runtime %s", workspace_id) if to_idle is not None: - logger.debug("workspace %s idled (LRU size now %d)", workspace_id, len(self._idle_lru)) + logger.debug("workspace %s idled (LRU size now %d)", workspace_id, len(self.idle_lru)) def get(self, workspace_id: str) -> Optional[AppRuntime]: # Active subscribers see the live runtime; idle-pool members @@ -656,7 +656,7 @@ class AppRuntimeManager: rt = self.runtimes.get(workspace_id) if rt is not None: return rt - return self._idle_lru.get(workspace_id) + return self.idle_lru.get(workspace_id) def drain_errors_for_path(self, file_path: str) -> list[str]: """If `file_path` falls under one of the live workspace @@ -675,7 +675,7 @@ class AppRuntimeManager: # navigated away from the workspace mid-build, but the agent # could still be editing files; the LRU keeps the runtime alive # for ~3 idle slots. - for rt in (*self.runtimes.values(), *self._idle_lru.values()): + for rt in (*self.runtimes.values(), *self.idle_lru.values()): try: ws_root = os.path.abspath(rt.workspace_path) except Exception: @@ -685,18 +685,18 @@ class AppRuntimeManager: return [] def get_render_state_for_workspace(self, workspace_id: str) -> tuple[Optional[str], str]: - rt = self.runtimes.get(workspace_id) or self._idle_lru.get(workspace_id) + rt = self.runtimes.get(workspace_id) or self.idle_lru.get(workspace_id) if rt is None: return None, "" return rt.render_state, rt.render_error_text def reset_render_state_for_workspace(self, workspace_id: str) -> None: - rt = self.runtimes.get(workspace_id) or self._idle_lru.get(workspace_id) + rt = self.runtimes.get(workspace_id) or self.idle_lru.get(workspace_id) if rt is not None: rt.reset_render_state() async def restart(self, workspace_id: str, workspace_path: Optional[str] = None) -> Optional[AppRuntime]: - rt = self.runtimes.get(workspace_id) or self._idle_lru.get(workspace_id) + rt = self.runtimes.get(workspace_id) or self.idle_lru.get(workspace_id) if rt is None: return None if workspace_path: @@ -718,12 +718,12 @@ class AppRuntimeManager: victims: list[AppRuntime] = [] for rt in list(self.runtimes.values()): victims.append(rt) - for rt in list(self._idle_lru.values()): + for rt in list(self.idle_lru.values()): resume_process_tree(rt.process) victims.append(rt) self.runtimes.clear() - self._idle_lru.clear() - self._attached.clear() + self.idle_lru.clear() + self.p_attached.clear() if not victims: return 0 await asyncio.gather( diff --git a/backend/apps/swarm/closure.py b/backend/apps/swarm/closure.py index aade3218..6eee6259 100644 --- a/backend/apps/swarm/closure.py +++ b/backend/apps/swarm/closure.py @@ -41,10 +41,10 @@ def p_created_with() -> str: class p_Ctx: def __init__(self, local_to_bundle: dict[tuple, str]): - self._m = local_to_bundle + self.p_m = local_to_bundle def bundle_id_for(self, etype: EntityType, local_id: str) -> str | None: - return self._m.get((etype, local_id)) + return self.p_m.get((etype, local_id)) # ---------- export ---------- diff --git a/backend/apps/swarm/entities/dashboards.py b/backend/apps/swarm/entities/dashboards.py index 63667d4e..29357759 100644 --- a/backend/apps/swarm/entities/dashboards.py +++ b/backend/apps/swarm/entities/dashboards.py @@ -19,7 +19,7 @@ class DashboardExportable: def __init__(self, did: str, name: str, data: dict): self.local_id = did self.name = name - self._data = data + self.p_data = data @classmethod def load(cls, local_id: str) -> "DashboardExportable | None": @@ -29,7 +29,7 @@ class DashboardExportable: return cls(local_id, data.get("name") or "Dashboard", data) def serialize(self, ctx: ExportContext) -> dict: - layout = dict(self._data.get("layout") or {}) + layout = dict(self.p_data.get("layout") or {}) cards = {} for sid, card in (layout.get("cards") or {}).items(): bid = ctx.bundle_id_for(EntityType.session, sid) @@ -53,7 +53,7 @@ class DashboardExportable: c["spawned_by"] = ctx.bundle_id_for(EntityType.session, spawn) if spawn else None browser_cards[bkey] = c expanded = [b for b in (ctx.bundle_id_for(EntityType.session, s) for s in (layout.get("expanded_session_ids") or [])) if b] - return {"name": self._data.get("name") or "Dashboard", "layout": { + return {"name": self.p_data.get("name") or "Dashboard", "layout": { **layout, "cards": cards, "view_cards": view_cards, "browser_cards": browser_cards, "notes": layout.get("notes") or {}, "expanded_session_ids": expanded, @@ -63,7 +63,7 @@ class DashboardExportable: return {} def dependencies(self) -> list[DepRef]: - layout = self._data.get("layout") or {} + layout = self.p_data.get("layout") or {} deps = [DepRef(EntityType.session, sid, "has_agent") for sid in (layout.get("cards") or {})] deps += [DepRef(EntityType.app, oid, "has_app") for oid in (layout.get("view_cards") or {})] return deps diff --git a/backend/apps/swarm/entities/modes.py b/backend/apps/swarm/entities/modes.py index 54ebe22d..da62a400 100644 --- a/backend/apps/swarm/entities/modes.py +++ b/backend/apps/swarm/entities/modes.py @@ -19,7 +19,7 @@ class ModeExportable: def __init__(self, mode_id: str, name: str, data: dict): self.local_id = mode_id self.name = name - self._data = data + self.p_data = data @classmethod def load(cls, local_id: str) -> "ModeExportable | None": @@ -33,7 +33,7 @@ class ModeExportable: return cls(local_id, d.get("name") or local_id, d) def serialize(self, ctx: ExportContext) -> dict: - return {k: v for k, v in self._data.items() if k not in P_DROP} + return {k: v for k, v in self.p_data.items() if k not in P_DROP} def files(self) -> dict[str, bytes]: return {} diff --git a/backend/apps/swarm/entities/sessions.py b/backend/apps/swarm/entities/sessions.py index 03c421a0..1f7f8589 100644 --- a/backend/apps/swarm/entities/sessions.py +++ b/backend/apps/swarm/entities/sessions.py @@ -32,7 +32,7 @@ class SessionExportable: def __init__(self, sid: str, name: str, data: dict): self.local_id = sid self.name = name - self._data = data + self.p_data = data @classmethod def load(cls, local_id: str) -> "SessionExportable | None": @@ -52,31 +52,31 @@ class SessionExportable: return cls(local_id, d.get("name") or "Agent", d) def serialize(self, ctx: ExportContext) -> dict: - return {k: self._data.get(k) for k in P_KEEP if k in self._data} + return {k: self.p_data.get(k) for k in P_KEEP if k in self.p_data} def files(self) -> dict[str, bytes]: return {} def dependencies(self) -> list[DepRef]: - mode = self._data.get("mode") + mode = self.p_data.get("mode") if mode and mode not in P_BUILTIN_MODES: return [DepRef(EntityType.mode, mode, "uses_mode")] return [] def requirements(self) -> list[Requirement]: reqs: list[Requirement] = [] - for mcp in self._data.get("active_mcps") or []: + for mcp in self.p_data.get("active_mcps") or []: reqs.append(Requirement( kind=RequirementKind.mcp_action, key=mcp, label=mcp, detail="An agent here uses this action.", )) - mode = self._data.get("mode") or "agent" + mode = self.p_data.get("mode") or "agent" if mode in P_BUILTIN_MODES and mode != "agent": reqs.append(Requirement( kind=RequirementKind.builtin_mode, key=mode, label=f"{mode} mode", detail="A built-in mode an agent runs in.", )) - provider = self._data.get("provider") or "anthropic" + provider = self.p_data.get("provider") or "anthropic" reqs.append(Requirement( kind=RequirementKind.api_key, key=provider, label=f"A {provider} model", detail="Set up this provider so the agents can run.", diff --git a/backend/apps/swarm/entities/skills.py b/backend/apps/swarm/entities/skills.py index 0b677e3a..fe0f33e0 100644 --- a/backend/apps/swarm/entities/skills.py +++ b/backend/apps/swarm/entities/skills.py @@ -20,8 +20,8 @@ class SkillExportable: def __init__(self, local_id: str, name: str, payload: dict, files: dict[str, bytes] | None = None): self.local_id = local_id self.name = name - self._payload = payload - self._files = files or {} + self.payload = payload + self.p_files = files or {} @classmethod def load(cls, local_id: str) -> "SkillExportable | None": @@ -46,10 +46,10 @@ class SkillExportable: return cls(local_id, name, payload, files) def serialize(self, ctx: ExportContext) -> dict: - return dict(self._payload) + return dict(self.payload) def files(self) -> dict[str, bytes]: - return dict(self._files) + return dict(self.p_files) def dependencies(self) -> list[DepRef]: return [] diff --git a/backend/apps/swarm/entities/workflows.py b/backend/apps/swarm/entities/workflows.py index cfdc7204..33b2466d 100644 --- a/backend/apps/swarm/entities/workflows.py +++ b/backend/apps/swarm/entities/workflows.py @@ -48,7 +48,7 @@ class WorkflowExportable: def __init__(self, local_id: str, name: str, data: dict): self.local_id = local_id self.name = name - self._data = data + self.p_data = data @classmethod def load(cls, local_id: str) -> "WorkflowExportable | None": @@ -62,7 +62,7 @@ class WorkflowExportable: return cls(local_id, data.get("title") or "Untitled workflow", data) def serialize(self, ctx: ExportContext) -> dict: - return sanitize_workflow(self._data) + return sanitize_workflow(self.p_data) def files(self) -> dict[str, bytes]: return {} @@ -72,18 +72,18 @@ class WorkflowExportable: def requirements(self) -> list[Requirement]: reqs: list[Requirement] = [] - for name in (self._data.get("actions") or {}).get("configured_sets") or []: + for name in (self.p_data.get("actions") or {}).get("configured_sets") or []: reqs.append(Requirement( kind=RequirementKind.mcp_action, key=name, label=name, detail="This workflow uses this action.", )) - mode = self._data.get("mode") or "agent" + mode = self.p_data.get("mode") or "agent" if mode in P_BUILTIN_MODES and mode != "agent": reqs.append(Requirement( kind=RequirementKind.builtin_mode, key=mode, label=f"{mode} mode", detail="A built-in mode this workflow runs in.", )) - provider = self._data.get("provider") or "anthropic" + provider = self.p_data.get("provider") or "anthropic" reqs.append(Requirement( kind=RequirementKind.api_key, key=provider, label=f"A {provider} model", detail="Set up this provider to run the workflow.", diff --git a/backend/apps/swarm/exportable.py b/backend/apps/swarm/exportable.py index 506662c2..383a0f55 100644 --- a/backend/apps/swarm/exportable.py +++ b/backend/apps/swarm/exportable.py @@ -27,13 +27,13 @@ class RemapTable: """bundle_id -> fresh local id, filled as import walks entities leaves-first.""" def __init__(self) -> None: - self._m: dict[str, str] = {} + self.p_m: dict[str, str] = {} def assign(self, bundle_id: str, local_id: str) -> None: - self._m[bundle_id] = local_id + self.p_m[bundle_id] = local_id def local(self, bundle_id: str) -> str | None: - return self._m.get(bundle_id) + return self.p_m.get(bundle_id) @runtime_checkable diff --git a/backend/tests/test_browser_self_audit.py b/backend/tests/test_browser_self_audit.py index ec2c37d7..0c4cd375 100644 --- a/backend/tests/test_browser_self_audit.py +++ b/backend/tests/test_browser_self_audit.py @@ -71,10 +71,10 @@ def test_audit_fires_every_n_finished_tasks(monkeypatch, tmp_path): class p_SyncThread: def __init__(self, target=None, **kw): - self._t = target + self.p_t = target def start(self): - self._t() + self.p_t() monkeypatch.setattr(m.threading, "Thread", p_SyncThread) log = [{"tool": "BrowserClickIndex", "elapsed_ms": 5, "result_summary": "ok"}] diff --git a/backend/tests/test_browser_validator.py b/backend/tests/test_browser_validator.py index ffcf186f..ffd1557a 100644 --- a/backend/tests/test_browser_validator.py +++ b/backend/tests/test_browser_validator.py @@ -20,16 +20,16 @@ class p_FakeClient: """Minimal Anthropic-shaped client: client.messages.create(...).""" def __init__(self, resp=None, raise_exc=None): - self._resp = resp - self._raise = raise_exc + self.p_resp = resp + self.p_raise = raise_exc self.calls = [] self.messages = self async def create(self, **kwargs): self.calls.append(kwargs) - if self._raise: - raise self._raise - return self._resp + if self.p_raise: + raise self.p_raise + return self.p_resp def test_returns_extracted_guidance_and_assembles_prompt(): diff --git a/backend/tests/test_disconnect_resilience.py b/backend/tests/test_disconnect_resilience.py index fd78bc8c..db84bfe8 100644 --- a/backend/tests/test_disconnect_resilience.py +++ b/backend/tests/test_disconnect_resilience.py @@ -286,7 +286,7 @@ def test_terminal_event_visible_after_full_eviction(p_patch_persist_dir): # Simulate a process restart: clear the in-memory ring buffer # but keep the persisted terminal file. - seq_log._per_session.pop(sid, None) + seq_log.per_session.pop(sid, None) with client.websocket_connect(f"/ws/agents/{sid}") as ws: ws.send_text(json.dumps({"event": "client:hello", "data": {"last_seq": 0, "connection_uuid": "c1"}})) diff --git a/backend/tests/test_outputs_runtime_cleanup.py b/backend/tests/test_outputs_runtime_cleanup.py index 35f89946..faa873ec 100644 --- a/backend/tests/test_outputs_runtime_cleanup.py +++ b/backend/tests/test_outputs_runtime_cleanup.py @@ -146,7 +146,7 @@ async def test_stop_all_kills_active(): await asyncio.sleep(0.05) else: raise AssertionError(f"port {port} not released after stop_all") - assert not m.runtimes and not m._idle_lru, "manager should be empty after stop_all" + assert not m.runtimes and not m.idle_lru, "manager should be empty after stop_all" print("PASS test_stop_all_kills_active") @@ -161,7 +161,7 @@ async def test_stop_all_kills_idle(): # Detach -> moves into LRU + SIGSTOP'd. If stop_all forgets to # SIGCONT before SIGTERM, the kill queues and the process hangs. await m.detach("ws-idle") - assert "ws-idle" in m._idle_lru, "should be in idle LRU" + assert "ws-idle" in m.idle_lru, "should be in idle LRU" # Confirm the process is suspended (T state on Linux, T on darwin). # Skip the OS check; just rely on the eventual kill working. killed = await m.stop_all() diff --git a/backend/tests/test_skills_folders.py b/backend/tests/test_skills_folders.py index d4361313..7e47b633 100644 --- a/backend/tests/test_skills_folders.py +++ b/backend/tests/test_skills_folders.py @@ -168,7 +168,7 @@ def test_swarm_export_folder_skill_carries_supporting_files(skills_dir): files = exp.files() assert "scripts/go.py" in files assert files["scripts/go.py"] == b"print(1)" - assert exp._payload["content"] == "render" + assert exp.payload["content"] == "render" def test_swarm_import_writes_folder_when_files_present(skills_dir): diff --git a/backend/tests/test_web_search_ddg.py b/backend/tests/test_web_search_ddg.py index da7904c2..aae4067a 100644 --- a/backend/tests/test_web_search_ddg.py +++ b/backend/tests/test_web_search_ddg.py @@ -28,7 +28,7 @@ class p_FakeResp: class p_FakeClient: """Stands in for httpx.AsyncClient; returns a canned response.""" def __init__(self, resp: p_FakeResp): - self._resp = resp + self.p_resp = resp async def __aenter__(self): return self @@ -37,7 +37,7 @@ class p_FakeClient: return False async def post(self, *a, **k): - return self._resp + return self.p_resp def p_patch_client(monkeypatch, resp: p_FakeResp):