[eric] backend: leading-_ -> p_/P_/public for class methods + module conditional-block vars + _ReadyServer class + _pending_oauth import; promote cross-file (search_ddg)

This commit is contained in:
ciregenz
2026-06-23 21:50:08 -07:00
parent d608eb68e3
commit 1bcc78567b
18 changed files with 106 additions and 106 deletions
+2 -2
View File
@@ -394,8 +394,8 @@ async def subscriptions_connect(body: dict):
result = await start_oauth(provider)
if result.get("flow") == "authorization_code" and result.get("state"):
from backend.main import _pending_oauth
_pending_oauth[result["state"]] = {
from backend.main import p_pending_oauth
p_pending_oauth[result["state"]] = {
"provider": provider,
"code_verifier": result.get("code_verifier", ""),
"redirect_uri": result.get("redirect_uri", ""),
+9 -9
View File
@@ -44,7 +44,7 @@ class SeqLogStore:
except Exception:
logger.warning("seq_log: failed to create persist dir %s", persist_dir)
async def _get_or_create(self, session_id: str) -> p_SessionSeqLog:
async def p_get_or_create(self, session_id: str) -> p_SessionSeqLog:
log = self.per_session.get(session_id)
if log is not None:
return log
@@ -55,7 +55,7 @@ class SeqLogStore:
self.per_session[session_id] = log
return log
def _peek(self, session_id: str) -> Optional[p_SessionSeqLog]:
def p_peek(self, session_id: str) -> Optional[p_SessionSeqLog]:
return self.per_session.get(session_id)
@asynccontextmanager
@@ -63,7 +63,7 @@ class SeqLogStore:
self, session_id: str, event: str, data: dict
) -> AsyncIterator[tuple[int, str]]:
"""Atomically assign seq, buffer, and yield (seq, payload); caller's send must happen inside the with-block."""
log = await self._get_or_create(session_id)
log = await self.p_get_or_create(session_id)
async with log.lock:
log.seq += 1
seq = log.seq
@@ -81,7 +81,7 @@ class SeqLogStore:
self, session_id: str, last_seq: int
) -> tuple[Optional[int], Optional[int], list[str]]:
"""Return (oldest_buffered_seq, newest_buffered_seq, events)."""
log = self._peek(session_id)
log = self.p_peek(session_id)
if log is None:
return (None, None, [])
# asyncio is single-threaded; deque list() is safe vs concurrent append/eviction. No lock needed for read.
@@ -95,10 +95,10 @@ class SeqLogStore:
def current_seq(self, session_id: str) -> int:
"""Last assigned seq, or 0 if no log exists for the session."""
log = self._peek(session_id)
log = self.p_peek(session_id)
return log.seq if log else 0
def _terminal_path(self, session_id: str) -> Optional[str]:
def p_terminal_path(self, session_id: str) -> Optional[str]:
if not self.p_persist_dir:
return None
# Session ids are uuid4 hex; sanitize anyway against path traversal.
@@ -109,7 +109,7 @@ class SeqLogStore:
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."""
path = self._terminal_path(session_id)
path = self.p_terminal_path(session_id)
if not path:
return
try:
@@ -123,7 +123,7 @@ class SeqLogStore:
)
def load_terminal(self, session_id: str) -> Optional[str]:
path = self._terminal_path(session_id)
path = self.p_terminal_path(session_id)
if not path or not os.path.exists(path):
return None
try:
@@ -135,7 +135,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)
path = self._terminal_path(session_id)
path = self.p_terminal_path(session_id)
if path and os.path.exists(path):
try:
os.remove(path)
+4 -4
View File
@@ -135,8 +135,8 @@ class ConnectionManager:
# wipes pending_futures, which is correct because
# reconcile_on_startup also marks waiting_approval sessions as
# stopped so there's nothing to answer anyway.
events = self._filter_stale_approvals(events)
events = self._strip_replayed_closes(events)
events = self.p_filter_stale_approvals(events)
events = self.p_strip_replayed_closes(events)
for s in events:
try:
await websocket.send_text(s)
@@ -164,7 +164,7 @@ class ConnectionManager:
"current_seq": newest if newest is not None else 0,
}
def _strip_replayed_closes(self, events: list[str]) -> list[str]:
def p_strip_replayed_closes(self, events: list[str]) -> list[str]:
"""Drop `agent:closed` events from a replay buffer.
agent:closed is a transition event ("session JUST closed") whose
@@ -188,7 +188,7 @@ class ConnectionManager:
out.append(payload_str)
return out
def _filter_stale_approvals(self, events: list[str]) -> list[str]:
def p_filter_stale_approvals(self, events: list[str]) -> list[str]:
"""Return events minus any `agent:approval_request` whose request_id
is no longer in pending_futures. JSON parse is per-event but replay
only runs on (re)connect, so it isn't a hot path.
+1 -1
View File
@@ -16,7 +16,7 @@ P_OR_MODELS_TTL_OK = 3600.0
P_OR_MODELS_TTL_FAIL = 30.0
p_or_models_cache: dict = {"models": None, "fetched_at": 0.0, "ok": False}
_9router_cache: dict = {"available": None, "checked_at": 0}
p_9router_cache: dict = {"available": None, "checked_at": 0}
# Per-model published pricing in $/1M tokens (input, output) for direct
+2 -2
View File
@@ -139,7 +139,7 @@ class WebSearchTool(BaseTool):
num_results: int = input_data.get("num_results", 5)
try:
results = await self._search_ddg(query, num_results)
results = await self.search_ddg(query, num_results)
if not results:
return [{"type": "text", "text": f"No search results found for: {query}"}]
return [{"type": "text", "text": results}]
@@ -152,7 +152,7 @@ class WebSearchTool(BaseTool):
return [{"type": "text", "text": f"Web search error: {exc}"}]
@staticmethod
async def _search_ddg(query: str, num_results: int) -> str:
async def search_ddg(query: str, num_results: int) -> str:
"""Query DuckDuckGo HTML endpoint and parse results."""
async with httpx.AsyncClient(
timeout=P_HTTP_TIMEOUT,
+4 -4
View File
@@ -34,7 +34,7 @@ class Output(BaseModel):
@model_validator(mode="before")
@classmethod
def _migrate_flat_fields(cls, data: Any) -> Any:
def p_migrate_flat_fields(cls, data: Any) -> Any:
"""Migrate legacy frontend_code/backend_code fields into the files dict."""
if not isinstance(data, dict):
return data
@@ -91,7 +91,7 @@ class OutputCreate(BaseModel):
@model_validator(mode="before")
@classmethod
def _migrate_flat_fields(cls, data: Any) -> Any:
def p_migrate_flat_fields(cls, data: Any) -> Any:
if not isinstance(data, dict):
return data
if "files" not in data or not data["files"]:
@@ -121,7 +121,7 @@ class OutputUpdate(BaseModel):
@model_validator(mode="before")
@classmethod
def _migrate_flat_fields(cls, data: Any) -> Any:
def p_migrate_flat_fields(cls, data: Any) -> Any:
if not isinstance(data, dict):
return data
if "files" not in data:
@@ -186,7 +186,7 @@ class WorkspaceSeedRequest(BaseModel):
@model_validator(mode="before")
@classmethod
def _migrate_flat_fields(cls, data: Any) -> Any:
def p_migrate_flat_fields(cls, data: Any) -> Any:
"""Accept legacy frontend_code/backend_code/schema_json fields."""
if not isinstance(data, dict):
return data
+35 -35
View File
@@ -82,7 +82,7 @@ class AppRuntime:
self.frontend_port: Optional[int] = None
# New-mode only: flips True once something is actually listening
# on frontend_port (we kick off a background poll task in
# _start_new_mode). frontend_url returns null until this flips,
# p_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.p_frontend_ready: bool = False
@@ -142,7 +142,7 @@ class AppRuntime:
@property
def frontend_url(self) -> Optional[str]:
# Gated on `_frontend_ready` (set by the background bind-poll
# task in _start_new_mode) so the preview pane only switches
# task in p_start_new_mode) so the preview pane only switches
# over once Vite is actually accepting connections. Without
# this, the editor flashes a "Site can't be reached" error
# while `npm install` is running.
@@ -189,10 +189,10 @@ class AppRuntime:
# vite emits "frontend ready" (or its 180s timeout
# fires), which is the moment the next workspace can
# start its own vite without competing for the same
# CPU. See `_await_frontend_bind` for the release.
# CPU. See `p_await_frontend_bind` for the release.
await p_vite_boot_lock.acquire()
try:
ok = await self._start_new_mode()
ok = await self.p_start_new_mode()
if not ok:
# Spawn failed before the bind-poll task was
# created; release synchronously so we don't
@@ -202,9 +202,9 @@ class AppRuntime:
except Exception:
p_vite_boot_lock.release()
raise
return await self._start_old_mode()
return await self.p_start_old_mode()
async def _start_new_mode(self) -> bool:
async def p_start_new_mode(self) -> bool:
env_path = os.path.join(self.workspace_path, ".env")
fp_raw = read_env_value(env_path, "FRONTEND_PORT")
bp_raw = read_env_value(env_path, "BACKEND_PORT")
@@ -222,7 +222,7 @@ class AppRuntime:
# .env so the bash run.sh subprocess reads the new port.
if self.frontend_port and not is_port_free(self.frontend_port):
new_port = find_free_port()
self._broadcast(LogLine(
self.p_broadcast(LogLine(
"runtime",
f"[runtime] persisted FRONTEND_PORT {self.frontend_port} is in use; reallocating to {new_port}",
))
@@ -240,7 +240,7 @@ class AppRuntime:
# from a prior session would otherwise block the new spawn.
if self.port and not is_port_free(self.port):
new_port = find_free_port()
self._broadcast(LogLine(
self.p_broadcast(LogLine(
"runtime",
f"[runtime] persisted BACKEND_PORT {self.port} is in use; reallocating to {new_port}",
))
@@ -249,7 +249,7 @@ class AppRuntime:
else:
self.port = None
env = self._spawn_env_base()
env = self.p_spawn_env_base()
# bash run.sh reads .env itself; we don't need to set
# FRONTEND_PORT / BACKEND_PORT here. We DO export the install
# paths so the template's `backend/run.sh` can find our
@@ -264,7 +264,7 @@ class AppRuntime:
env["OPENSWARM_DEBUGGER_PATH"] = DEBUGGER_PATH
env["OPENSWARM_TEMPLATE_BACKEND_PATH"] = TEMPLATE_BACKEND_PATH
cmd, spawn_cwd, launch_desc = self._resolve_launch(env)
cmd, spawn_cwd, launch_desc = self.p_resolve_launch(env)
try:
self.process = await asyncio.create_subprocess_exec(
*cmd,
@@ -276,23 +276,23 @@ class AppRuntime:
)
except Exception as e:
logger.exception("failed to start new-mode runtime for %s", self.workspace_id)
self._broadcast(LogLine("runtime", f"[runtime] failed to start: {e}"))
self.p_broadcast(LogLine("runtime", f"[runtime] failed to start: {e}"))
self.frontend_port = None
self.port = None
self.process = None
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.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())
self.p_broadcast(LogLine("runtime", f"[runtime] {launch_desc} started; frontend on {self.frontend_port}{backend_note} (pid {self.process.pid})"))
self.p_stdout_task = asyncio.create_task(self.p_pipe_stream(self.process.stdout, "stdout"))
self.p_stderr_task = asyncio.create_task(self.p_pipe_stream(self.process.stderr, "stderr"))
self.p_wait_task = asyncio.create_task(self.p_await_exit())
# Kick off the port-bind poller so frontend_url flips on once
# Vite is actually accepting connections.
self.p_frontend_ready = False
self.p_frontend_ready_task = asyncio.create_task(self._await_frontend_bind())
self.p_frontend_ready_task = asyncio.create_task(self.p_await_frontend_bind())
return True
def _resolve_launch(self, env: dict) -> tuple[list[str], str, str]:
def p_resolve_launch(self, env: dict) -> tuple[list[str], str, str]:
"""Pick the new-mode launch command.
Default is `bash run.sh` at the workspace root, which handles both
@@ -319,7 +319,7 @@ class AppRuntime:
)
return [p_resolve_bash(), "run.sh"], self.workspace_path, "bash run.sh"
async def _await_frontend_bind(self) -> None:
async def p_await_frontend_bind(self) -> None:
"""Poll `frontend_port` every FRONTEND_BIND_POLL_INTERVAL until
something binds (Vite dev server) or we hit the timeout. Emits a
`[runtime]` log line on success/failure so the Terminal pane
@@ -369,7 +369,7 @@ class AppRuntime:
except Exception:
pass
self.p_frontend_ready = True
self._broadcast(LogLine(
self.p_broadcast(LogLine(
"runtime",
f"[runtime] frontend ready at http://127.0.0.1:{port}/",
))
@@ -384,7 +384,7 @@ class AppRuntime:
await asyncio.sleep(FRONTEND_BIND_POLL_INTERVAL)
# Timed out; keep the runtime up (Terminal might show useful
# errors) but surface why the preview never appeared.
self._broadcast(LogLine(
self.p_broadcast(LogLine(
"runtime",
f"[runtime] frontend did NOT bind on port {port} after "
f"{FRONTEND_BIND_TIMEOUT_SECONDS}s; check the Terminal "
@@ -397,12 +397,12 @@ class AppRuntime:
# already released.
p_release_boot_lock()
async def _start_old_mode(self) -> bool:
async def p_start_old_mode(self) -> bool:
if not self.has_backend_file:
self.port = None
return False
self.port = find_free_port()
env = self._spawn_env_base()
env = self.p_spawn_env_base()
env["PORT"] = str(self.port)
env["BACKEND_PORT"] = str(self.port) # alias; both common names work
try:
@@ -419,17 +419,17 @@ class AppRuntime:
)
except Exception as e:
logger.exception("failed to start backend for %s", self.workspace_id)
self._broadcast(LogLine("runtime", f"[runtime] failed to start: {e}"))
self.p_broadcast(LogLine("runtime", f"[runtime] failed to start: {e}"))
self.port = None
self.process = None
return False
self._broadcast(LogLine("runtime", f"[runtime] backend started on port {self.port} (pid {self.process.pid})"))
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())
self.p_broadcast(LogLine("runtime", f"[runtime] backend started on port {self.port} (pid {self.process.pid})"))
self.p_stdout_task = asyncio.create_task(self.p_pipe_stream(self.process.stdout, "stdout"))
self.p_stderr_task = asyncio.create_task(self.p_pipe_stream(self.process.stderr, "stderr"))
self.p_wait_task = asyncio.create_task(self.p_await_exit())
return True
def _spawn_env_base(self) -> dict[str, str]:
def p_spawn_env_base(self) -> dict[str, str]:
"""Inherited env minus the install token. Backend.py can hit our
REST API back via its own creds if it really needs to, but it
shouldn't inherit the host process's token by default."""
@@ -492,7 +492,7 @@ class AppRuntime:
return p_unsub
def _broadcast(self, line: LogLine) -> None:
def p_broadcast(self, line: LogLine) -> None:
self.log_buffer.append(line)
# Snapshot subscribers; they can self-remove during dispatch.
for cb in list(self.p_subscribers):
@@ -501,7 +501,7 @@ class AppRuntime:
except Exception:
pass
def _maybe_capture_error(self, text: str) -> None:
def p_maybe_capture_error(self, text: str) -> None:
if ERROR_PATTERNS.search(text):
self.recent_errors.append(text.rstrip())
@@ -512,7 +512,7 @@ class AppRuntime:
idx = text.index("[openswarm:app-error]") + len("[openswarm:app-error]")
self.set_render_error(text[idx:].strip())
async def _pipe_stream(self, stream: Optional[asyncio.StreamReader], name: str) -> None:
async def p_pipe_stream(self, stream: Optional[asyncio.StreamReader], name: str) -> None:
if stream is None:
return
try:
@@ -522,14 +522,14 @@ class AppRuntime:
break
text = raw.decode(errors="replace").rstrip("\r\n")
if text:
self._broadcast(LogLine(name, text))
self.p_broadcast(LogLine(name, text))
if name == "stderr" or name == "stdout":
self._maybe_capture_error(text)
self.p_maybe_capture_error(text)
self.p_maybe_capture_render_beacon(text)
except Exception:
logger.exception("log pipe error (%s) for %s", name, self.workspace_id)
async def _await_exit(self) -> None:
async def p_await_exit(self) -> None:
if not self.process:
return
rc = await self.process.wait()
@@ -537,7 +537,7 @@ class AppRuntime:
# otherwise frontend_url keeps advertising a dead port and the preview
# navigates into ERR_FAILED. stop() already does this for clean stops.
self.p_frontend_ready = False
self._broadcast(LogLine("runtime", f"[runtime] backend exited with code {rc}"))
self.p_broadcast(LogLine("runtime", f"[runtime] backend exited with code {rc}"))
class AppRuntimeManager:
+7 -7
View File
@@ -33,7 +33,7 @@ logger = logging.getLogger(__name__)
p_pulse_task: asyncio.Task | None = None
p_drain_task: asyncio.Task | None = None
_9r_start_task: asyncio.Task | None = None
p_9r_start_task: asyncio.Task | None = None
p_last_9r_cost: float | None = None
p_last_9r_prompt_tokens: int | None = None
@@ -122,7 +122,7 @@ async def p_drain_loop():
@asynccontextmanager
async def service_lifespan():
global p_pulse_task, p_drain_task, _9r_start_task
global p_pulse_task, p_drain_task, p_9r_start_task
try:
from backend.apps.settings.settings import load_settings, save_settings
@@ -200,7 +200,7 @@ async def service_lifespan():
# user sends an agent message, and the dispatch path calls ensure_running()
# itself (now serialized, so no double-spawn), so the first message waits
# for readiness lazily. This is the single biggest warm-startup win.
_9r_start_task = asyncio.create_task(ensure_9router())
p_9r_start_task = asyncio.create_task(ensure_9router())
except Exception as e:
logger.debug(f"9Router auto-start skipped: {e}")
@@ -225,13 +225,13 @@ async def service_lifespan():
pass
p_drain_task = None
if _9r_start_task and not _9r_start_task.done():
_9r_start_task.cancel()
if p_9r_start_task and not p_9r_start_task.done():
p_9r_start_task.cancel()
try:
await _9r_start_task
await p_9r_start_task
except (asyncio.CancelledError, Exception):
pass
_9r_start_task = None
p_9r_start_task = None
try:
from backend.apps.nine_router import stop as stop_9router
+1 -1
View File
@@ -452,7 +452,7 @@ async def search(body: SearchBody) -> dict:
# through to the slower-but-grounded backends.
from backend.apps.agents.tools.web import WebSearchTool, DDGRateLimited
try:
text = await WebSearchTool._search_ddg(body.query, body.num_results)
text = await WebSearchTool.search_ddg(body.query, body.num_results)
except DDGRateLimited:
# Surface the throttle as a recorded error (not a silent None) so the
# caller can see WHY we fell through to a slower backend.
+10 -10
View File
@@ -63,10 +63,10 @@ def get_auth_token() -> str:
class p_TokenScrubFilter(logging.Filter):
"""Logging filter that redacts the install token from log records (defense in depth)."""
_PLACEHOLDER = "<REDACTED:openswarm-token>"
P_PLACEHOLDER = "<REDACTED:openswarm-token>"
@staticmethod
def _args_might_contain_token(args) -> bool:
def p_args_might_contain_token(args) -> bool:
"""Cheap pre-check; avoids eager %-formatting on the >99% of records that don't mention the token."""
if not args:
return False
@@ -81,7 +81,7 @@ class p_TokenScrubFilter(logging.Filter):
return False
@classmethod
def _scrub_args(cls, args):
def p_scrub_args(cls, args):
"""Scrub token from args while preserving tuple/dict shape; uvicorn's AccessFormatter unpacks args as a 5-tuple and explodes on None."""
if args is None:
return args
@@ -91,7 +91,7 @@ class p_TokenScrubFilter(logging.Filter):
if isinstance(v, str) and TOKEN in v:
if new_dict is None:
new_dict = dict(args)
new_dict[k] = v.replace(TOKEN, cls._PLACEHOLDER)
new_dict[k] = v.replace(TOKEN, cls.P_PLACEHOLDER)
return new_dict if new_dict is not None else args
if isinstance(args, tuple):
new_list = None
@@ -99,10 +99,10 @@ class p_TokenScrubFilter(logging.Filter):
if isinstance(v, str) and TOKEN in v:
if new_list is None:
new_list = list(args)
new_list[i] = v.replace(TOKEN, cls._PLACEHOLDER)
new_list[i] = v.replace(TOKEN, cls.P_PLACEHOLDER)
return tuple(new_list) if new_list is not None else args
if isinstance(args, str) and TOKEN in args:
return args.replace(TOKEN, cls._PLACEHOLDER)
return args.replace(TOKEN, cls.P_PLACEHOLDER)
return args
def filter(self, record: logging.LogRecord) -> bool: # pragma: no cover (defensive)
@@ -110,19 +110,19 @@ class p_TokenScrubFilter(logging.Filter):
return True
# Fast path: skip eager %-formatting on records that don't mention the token.
raw_msg = record.msg if isinstance(record.msg, str) else ""
if TOKEN not in raw_msg and not self._args_might_contain_token(record.args):
if TOKEN not in raw_msg and not self.p_args_might_contain_token(record.args):
return True
# Slow path: in-place args rewrite (preserves shape for AccessFormatter), then re-render to catch tokens buried in custom reprs.
try:
if isinstance(record.msg, str) and TOKEN in record.msg:
record.msg = record.msg.replace(TOKEN, self._PLACEHOLDER)
scrubbed = self._scrub_args(record.args)
record.msg = record.msg.replace(TOKEN, self.P_PLACEHOLDER)
scrubbed = self.p_scrub_args(record.args)
if scrubbed is not record.args:
record.args = scrubbed
try:
rendered = record.getMessage()
if TOKEN in rendered:
record.msg = rendered.replace(TOKEN, self._PLACEHOLDER)
record.msg = rendered.replace(TOKEN, self.P_PLACEHOLDER)
record.args = None
except Exception:
pass
+2 -2
View File
@@ -35,7 +35,7 @@ class MainApp:
# per-SubApp markers and a cold-start stall can only be guessed at.
# One perf_counter + flushed print per app pins exactly which
# lifespan (or the cold first-touch I/O entering it) dominates.
_boot_t0 = time.perf_counter()
p_boot_t0 = time.perf_counter()
for sub_app in sub_apps:
debug(sub_app.name)
_t0 = time.perf_counter()
@@ -43,7 +43,7 @@ class MainApp:
_dt = (time.perf_counter() - _t0) * 1000
if _dt > 50: # only flag a slow lifespan; keeps boot logs quiet
print(f"[perf] lifespan {sub_app.name} t={_dt:.0f}ms", flush=True)
print(f"[perf] lifespans-total t={(time.perf_counter() - _boot_t0) * 1000:.0f}ms", flush=True)
print(f"[perf] lifespans-total t={(time.perf_counter() - p_boot_t0) * 1000:.0f}ms", flush=True)
_port = os.environ.get("OPENSWARM_PORT", "8324")
print(f"\nCheck out the API docs at: http://127.0.0.1:{_port}/docs\n")
yield
+6 -6
View File
@@ -7,7 +7,7 @@ import uuid
from backend.config.paths import DATA_ROOT
_INSTALL_ID_FILE = os.path.join(DATA_ROOT, "install_id")
P_INSTALL_ID_FILE = os.path.join(DATA_ROOT, "install_id")
_cached: str | None = None
@@ -18,9 +18,9 @@ def get_install_id() -> str:
return _cached
try:
with open(_INSTALL_ID_FILE, "r", encoding="utf-8") as f:
with open(P_INSTALL_ID_FILE, "r", encoding="utf-8") as f:
existing = f.read().strip()
if _looks_like_uuid(existing):
if p_looks_like_uuid(existing):
_cached = existing
return _cached
except FileNotFoundError:
@@ -29,8 +29,8 @@ def get_install_id() -> str:
pass
fresh = str(uuid.uuid4())
os.makedirs(os.path.dirname(_INSTALL_ID_FILE) or ".", exist_ok=True)
fd = os.open(_INSTALL_ID_FILE, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600)
os.makedirs(os.path.dirname(P_INSTALL_ID_FILE) or ".", exist_ok=True)
fd = os.open(P_INSTALL_ID_FILE, os.O_CREAT | os.O_WRONLY | os.O_TRUNC, 0o600)
try:
os.write(fd, fresh.encode("utf-8"))
finally:
@@ -39,7 +39,7 @@ def get_install_id() -> str:
return _cached
def _looks_like_uuid(s: str) -> bool:
def p_looks_like_uuid(s: str) -> bool:
if len(s) != 36:
return False
try:
+2 -2
View File
@@ -21,12 +21,12 @@ import time
logger = logging.getLogger(__name__)
_write_lock = threading.Lock()
p_write_lock = threading.Lock()
def atomic_write_json(path: str, payload, *, indent: int = 2) -> None:
directory = os.path.dirname(path) or "."
with _write_lock:
with p_write_lock:
os.makedirs(directory, exist_ok=True)
fd, tmp = tempfile.mkstemp(prefix=".tmp-", suffix=".json", dir=directory)
try:
+9 -9
View File
@@ -3,20 +3,20 @@
import os
import sys
_BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
P_BACKEND_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
_is_packaged = os.environ.get("OPENSWARM_PACKAGED") == "1"
p_is_packaged = os.environ.get("OPENSWARM_PACKAGED") == "1"
if _is_packaged:
if p_is_packaged:
if sys.platform == "darwin":
_app_support = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "OpenSwarm")
p_app_support = os.path.join(os.path.expanduser("~"), "Library", "Application Support", "OpenSwarm")
elif sys.platform == "win32":
_app_support = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "OpenSwarm")
p_app_support = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "OpenSwarm")
else:
_app_support = os.path.join(os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share")), "OpenSwarm")
DATA_ROOT = os.path.join(_app_support, "data")
p_app_support = os.path.join(os.environ.get("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share")), "OpenSwarm")
DATA_ROOT = os.path.join(p_app_support, "data")
else:
DATA_ROOT = os.path.join(_BACKEND_DIR, "data")
DATA_ROOT = os.path.join(P_BACKEND_DIR, "data")
SESSIONS_DIR = os.path.join(DATA_ROOT, "sessions")
TOOLS_DIR = os.path.join(DATA_ROOT, "tools")
@@ -34,4 +34,4 @@ TRUSTED_SENSITIVE_PATHS_PATH = os.path.join(DATA_ROOT, "trusted_sensitive_paths.
# Per-install auth token for the localhost API; see auth.py.
AUTH_TOKEN_FILE = os.path.join(DATA_ROOT, "auth.token")
BACKEND_DIR = _BACKEND_DIR
BACKEND_DIR = P_BACKEND_DIR
+6 -6
View File
@@ -79,10 +79,10 @@ install_token_scrubber()
try:
import uuid as p_uuid
from backend.apps.settings.store import load_settings as p_load_boot_settings, save_settings as p_save_boot_settings
_boot_settings = p_load_boot_settings()
if not getattr(_boot_settings, "installation_id", None):
_boot_settings.installation_id = p_uuid.uuid4().hex
p_save_boot_settings(_boot_settings)
p_boot_settings = p_load_boot_settings()
if not getattr(p_boot_settings, "installation_id", None):
p_boot_settings.installation_id = p_uuid.uuid4().hex
p_save_boot_settings(p_boot_settings)
except Exception:
pass
@@ -956,7 +956,7 @@ if __name__ == "__main__":
import uvicorn.config
class _ReadyServer(uvicorn.Server):
class p_ReadyServer(uvicorn.Server):
"""Subclass that prints a machine-readable READY line on startup."""
async def startup(self, sockets=None):
await super().startup(sockets)
@@ -966,6 +966,6 @@ if __name__ == "__main__":
uvicorn.run("backend.main:app", host=args.host, port=args.port, reload=True)
else:
config = uvicorn.Config("backend.main:app", host=args.host, port=args.port)
server = _ReadyServer(config)
server = p_ReadyServer(config)
import asyncio
asyncio.run(server.serve())
@@ -6,7 +6,7 @@ What this proves:
3. AppRuntimeManager.stop_all() resumes SIGSTOP'd idle runtimes before reaping (otherwise the SIGTERM is queued and the process never dies).
4. is_port_free() correctly detects collisions.
5. write_env_value() updates a single key without clobbering siblings.
6. _start_new_mode() rewrites .env's FRONTEND_PORT when the persisted port is in use, and the spawned child sees the rewritten value.
6. p_start_new_mode() rewrites .env's FRONTEND_PORT when the persisted port is in use, and the spawned child sees the rewritten value.
7. Same collision-rewrite happens for BACKEND_PORT when it's not "NONE".
Run with: backend/.venv/bin/python backend/tests/test_outputs_runtime_cleanup.py
+2 -2
View File
@@ -40,13 +40,13 @@ def p_no_network(monkeypatch):
def p_ddg_returns(monkeypatch, text):
async def p_f(query, num):
return text
monkeypatch.setattr(WebSearchTool, "_search_ddg", staticmethod(p_f))
monkeypatch.setattr(WebSearchTool, "search_ddg", staticmethod(p_f))
def p_ddg_throttled(monkeypatch):
async def p_f(query, num):
raise DDGRateLimited(query)
monkeypatch.setattr(WebSearchTool, "_search_ddg", staticmethod(p_f))
monkeypatch.setattr(WebSearchTool, "search_ddg", staticmethod(p_f))
@pytest.mark.asyncio
+3 -3
View File
@@ -61,7 +61,7 @@ P_HTML_WITH_AD = """
async def test_202_raises_rate_limited_not_empty(monkeypatch):
p_patch_client(monkeypatch, p_FakeResp(202, "<html>throttle challenge, no results</html>"))
with pytest.raises(DDGRateLimited):
await WebSearchTool._search_ddg("anything", 5)
await WebSearchTool.search_ddg("anything", 5)
@pytest.mark.asyncio
@@ -76,7 +76,7 @@ async def test_execute_reports_rate_limit_clearly(monkeypatch):
@pytest.mark.asyncio
async def test_ads_are_stripped_real_results_kept(monkeypatch):
p_patch_client(monkeypatch, p_FakeResp(200, P_HTML_WITH_AD))
out = await WebSearchTool._search_ddg("topic", 5)
out = await WebSearchTool.search_ddg("topic", 5)
assert "example.com/real" in out
assert "Real Result Title" in out
# the sponsored row and its tracker URL must not appear
@@ -89,5 +89,5 @@ async def test_ads_are_stripped_real_results_kept(monkeypatch):
async def test_genuinely_empty_is_not_a_rate_limit(monkeypatch):
# 200 with no result blocks is a real empty result set, not a throttle.
p_patch_client(monkeypatch, p_FakeResp(200, "<html><body>nothing here</body></html>"))
out = await WebSearchTool._search_ddg("zxcvqwer no hits", 5)
out = await WebSearchTool.search_ddg("zxcvqwer no hits", 5)
assert out == ""