diff --git a/backend/apps/health/health.py b/backend/apps/health/health.py index c24fb463..33749ee6 100644 --- a/backend/apps/health/health.py +++ b/backend/apps/health/health.py @@ -2,14 +2,11 @@ from backend.config.Apps import SubApp from contextlib import asynccontextmanager from fastapi.responses import PlainTextResponse from typeguard import typechecked -import debug from fastapi import status, HTTPException @asynccontextmanager async def health_lifespan(): - debug("START") yield - debug("END") health = SubApp("health", health_lifespan) diff --git a/backend/apps/outputs/runtime.py b/backend/apps/outputs/runtime.py index 5d449f07..19c4f91c 100644 --- a/backend/apps/outputs/runtime.py +++ b/backend/apps/outputs/runtime.py @@ -211,7 +211,7 @@ class AppRuntime: self.port = None 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 debugger to satisfy its `from swarm_debug import debug`. (Also written into .env at seed time, but env-var path is the more reliable read site for subshells.) NOTE: keep these in sync with seed_webapp_template_workspace. + # bash run.sh reads .env itself; we don't need to set FRONTEND_PORT / BACKEND_PORT here. We DO export the install paths as env vars (the more reliable read site for subshells). OPENSWARM_DEBUGGER_PATH is legacy-only: workspaces seeded before the PyPI swap have a run.sh that editable-installs the bundled debugger from it; new templates resolve `swarm-debug` from PyPI via pyproject. from backend.apps.outputs.view_builder_templates import ( DEBUGGER_PATH, TEMPLATE_BACKEND_PATH, diff --git a/backend/apps/outputs/swarm_debug_skill.md b/backend/apps/outputs/swarm_debug_skill.md index 9237653b..1f70102a 100644 --- a/backend/apps/outputs/swarm_debug_skill.md +++ b/backend/apps/outputs/swarm_debug_skill.md @@ -1,9 +1,9 @@ # swarm-debug — OpenSwarm's logger for App backends -`swarm_debug` (also importable as `debug` for legacy reasons) is OpenSwarm's -opinionated `print()` replacement for the App Builder's backend code. It -prints colored, indented, frame-aware log lines that read at a glance and -land in the App Builder's **Terminal** tab under the `[BACKEND]` prefix. +`swarm_debug` (the `swarm-debug` package on PyPI) is OpenSwarm's opinionated +`print()` replacement for the App Builder's backend code. It prints colored, +indented, frame-aware log lines that read at a glance and land in the App +Builder's **Terminal** tab under the `[BACKEND]` prefix. It's pre-installed in every App Builder workspace that has a backend (i.e. after `bash backend_init.sh`). Use it instead of `print()`. @@ -100,13 +100,46 @@ debug(huge_payload, override_max_chars=True) ## Modes (custom log levels) -`debug` accepts a `mode` kwarg that maps to a configurable log channel. -Default is `'debug'`. The Terminal pane shows all modes; if you want to -hide a category, configure it in `Debugleton` (see `debugger_backend/`). +`debug` accepts a `mode` kwarg that maps to a log channel. Valid values are +`'all'` (always shown), `'debug'` (the default), and `'test'` (high +priority). Anything else raises. ```python -debug(payload, mode='info') -debug(suspicious_input, mode='warning') +debug(payload, mode='all') +debug(flaky_result, mode='test') +``` + +--- + +## More tools (pretty-print, tables, diffs, timing) + +```python +debug(my_dict) # structured values pretty-print by default +debug(my_dict, pretty=False) # force flat single-line output +debug(sql_query, lang="sql") # syntax-highlight a string (sql, json, html, ...) +debug(x, y, z) # 2+ data args auto-render as a Name|Type|Value table +debug(x, y, z, table=False) # force per-line instead +debug("a", "b", sep=", ") # join args into one line, like print(sep=...) +debug("about to retry", error=True) # force red error styling on a non-exception + +debug.diff(old_value, new_value) # unified diff of two values +with debug.time("fetch users"): # times the block, prints the duration + rows = fetch_users() +``` + +--- + +## Visibility (why output might not show) + +Output is gated per-file: only files toggled ON print. OpenSwarm re-toggles +every file ON at each backend boot, and new code needs a backend restart to +load anyway (no auto-reload), so in practice your `debug()` lines are always +visible after the restart that loads them. If you ever need to manage this +yourself, the CLI lives in the workspace venv: + +```bash +.venv/bin/swarm-debug status # what's toggled where +.venv/bin/swarm-debug toggle on --all # everything visible (run from the workspace root) ``` --- @@ -159,5 +192,8 @@ and effect across the two halves of your stack. | Log several values in one call | `debug(a, b, c)` | | Log an exception with red coloring | `debug(err)` (variable name must contain "err" or "error", or pass an `Exception` instance) | | Avoid truncation | `debug(value, override_max_chars=True)` | -| Use a different log channel | `debug(value, mode='info')` | -| Same thing, legacy import | `import debug; debug(value)` (function and module share the name — see swarm_debug.py shim) | +| Always-shown log channel | `debug(value, mode='all')` | +| Diff two values | `debug.diff(old, new)` | +| Time a block | `with debug.time("label"): ...` | +| Flat instead of pretty-printed | `debug(value, pretty=False)` | +| Syntax-highlight a string | `debug(query, lang="sql")` | diff --git a/backend/apps/outputs/view_builder_templates.py b/backend/apps/outputs/view_builder_templates.py index 8df78412..9db26734 100644 --- a/backend/apps/outputs/view_builder_templates.py +++ b/backend/apps/outputs/view_builder_templates.py @@ -472,12 +472,12 @@ def p_ensure_warm_python_venv() -> str | None: logger.warning("warm-venv create failed: %s", r.stderr[-1500:]) return None - # Install the template's dependencies (fastapi[standard], typeguard, transitives); NOT the workspace's own backend, which gets editable-installed per-workspace by run.sh after the cache copy. The venv layout differs by platform: POSIX puts executables in `bin/`, Windows in `Scripts/`, and the executable name itself gets `.exe`. + # Install the template's dependencies (fastapi[standard], typeguard, swarm-debug, transitives); keep this list in sync with webapp_template/backend/pyproject.toml. NOT the workspace's own backend, which gets editable-installed per-workspace by run.sh after the cache copy. The venv layout differs by platform: POSIX puts executables in `bin/`, Windows in `Scripts/`, and the executable name itself gets `.exe`. if os.name == "nt": pip = os.path.join(venv_dir, "Scripts", "pip.exe") else: pip = os.path.join(venv_dir, "bin", "pip") - deps = ["fastapi[standard]", "typeguard==4.4.2"] + deps = ["fastapi[standard]", "typeguard==4.4.2", "swarm-debug"] r = subprocess.run( [pip, "install", "--disable-pip-version-check", *deps], capture_output=True, text=True, timeout=600, @@ -557,14 +557,13 @@ def seed_webapp_template_workspace(workspace_dir: str, frontend_port: int) -> No 2. Sed both `.env` and `.env.example` to set `FRONTEND_PORT=`. BACKEND_PORT stays NONE in both (per spec; the agent flips it via backend_init.sh when it needs a backend). - 3. Append two install-specific paths to `.env` ONLY (NOT - `.env.example`; these are absolute paths on the current - machine, not template defaults): + 3. Append an install-specific path to `.env` ONLY (NOT + `.env.example`; it is an absolute path on the current + machine, not a template default): OPENSWARM_TEMPLATE_BACKEND_PATH= - OPENSWARM_DEBUGGER_PATH= - The first is read by `backend_init.sh`; the second is read by - the template's `backend/run.sh` to install our local debugger - before `pip install -e .`. + It is read by `backend_init.sh`. The debugger is no longer + seeded from a local path; the template's `pip install -e .` + resolves `swarm-debug` from PyPI. Idempotent within reason; re-running over an existing workspace overwrites template files and re-asserts the env values. @@ -593,7 +592,6 @@ def seed_webapp_template_workspace(workspace_dir: str, frontend_port: int) -> No # Install-specific paths; .env only. patch_env_port(env_path, "OPENSWARM_TEMPLATE_BACKEND_PATH", TEMPLATE_BACKEND_PATH) - patch_env_port(env_path, "OPENSWARM_DEBUGGER_PATH", DEBUGGER_PATH) # Backend-venv warm-cache path; backend_init.sh checks this for a pre-populated `.venv/` to cp -aR into the workspace instead of paying the ~25s venv-create + pip-install cost. Written even if the cache isn't ready yet; backend_init.sh re-checks at run time. patch_env_port(env_path, "OPENSWARM_BACKEND_VENV_CACHE", warm_venv_dir()) diff --git a/backend/apps/outputs/webapp_template/backend/pyproject.toml b/backend/apps/outputs/webapp_template/backend/pyproject.toml index e96191a6..8f0d7f51 100644 --- a/backend/apps/outputs/webapp_template/backend/pyproject.toml +++ b/backend/apps/outputs/webapp_template/backend/pyproject.toml @@ -6,6 +6,7 @@ requires-python = ">=3.10" dependencies = [ "fastapi[standard]", "typeguard==4.4.2", + "swarm-debug", ] [tool.setuptools] diff --git a/backend/apps/outputs/webapp_template/backend/run.sh b/backend/apps/outputs/webapp_template/backend/run.sh index 52f7405e..de9b848a 100755 --- a/backend/apps/outputs/webapp_template/backend/run.sh +++ b/backend/apps/outputs/webapp_template/backend/run.sh @@ -92,10 +92,6 @@ else # --- Install Python dependencies --- echo "Installing dependencies..." cd "$BACKEND_DIR_ABSPATH" - if [[ -n "${OPENSWARM_DEBUGGER_PATH:-}" && -d "$OPENSWARM_DEBUGGER_PATH" ]]; then - echo "Installing OpenSwarm debugger (swarm_debug) from $OPENSWARM_DEBUGGER_PATH" - "$VENV_PY" -m pip install -e "$OPENSWARM_DEBUGGER_PATH" - fi "$VENV_PY" -m pip install -e . if [[ $? -ne 0 ]]; then echo "Error: Failed to install Python dependencies." @@ -112,6 +108,10 @@ fi # the backend to pick up new code it can hit OpenSwarm's # /api/outputs/workspace/{ws}/runtime/restart endpoint, which sends a # clean SIGTERM and restarts via this same script. +# swarm-debug gates output on per-file toggles that default OFF; force all ON each boot so agent-added files show in the Terminal. +if [[ "$IS_WIN" == "1" ]]; then SWARM_DEBUG_BIN="$VENV_DIR/Scripts/swarm-debug.exe"; else SWARM_DEBUG_BIN="$VENV_DIR/bin/swarm-debug"; fi +( cd "$BACKEND_DIR_ABSPATH/.." && "$SWARM_DEBUG_BIN" toggle on --all >/dev/null 2>&1 ) || true + echo "Starting backend server on http://0.0.0.0:${BACKEND_PORT:-8324} ..." cd "$BACKEND_DIR_ABSPATH/.." "$VENV_PY" -m uvicorn backend.main:app --host 0.0.0.0 --port "${BACKEND_PORT:-8324}" diff --git a/backend/apps/outputs/webapp_template/backend_init.sh b/backend/apps/outputs/webapp_template/backend_init.sh index e4bf5c3f..aa27aef5 100755 --- a/backend/apps/outputs/webapp_template/backend_init.sh +++ b/backend/apps/outputs/webapp_template/backend_init.sh @@ -38,7 +38,7 @@ if [[ -d ./backend ]]; then fi # Resolve master template backend/ path. OPENSWARM_TEMPLATE_BACKEND_PATH -# is written into .env at seed time; OPENSWARM_DEBUGGER_PATH the same. +# is written into .env at seed time. if [[ -z "${OPENSWARM_TEMPLATE_BACKEND_PATH:-}" ]]; then echo "ERROR: OPENSWARM_TEMPLATE_BACKEND_PATH not set in .env. This" >&2 echo " workspace was seeded by an older OpenSwarm; ask the" >&2 diff --git a/backend/apps/swarm/entities/apps.py b/backend/apps/swarm/entities/apps.py index 648bbb50..409a871f 100644 --- a/backend/apps/swarm/entities/apps.py +++ b/backend/apps/swarm/entities/apps.py @@ -137,7 +137,7 @@ def p_free_port() -> int: def p_localize_env(folder: str) -> None: """Regenerate the workspace .env on the importer's machine: a fresh port plus - this install's absolute template/debugger paths (the source's were dropped).""" + this install's absolute template path (the source's was dropped).""" env_path = os.path.join(folder, ".env") example = os.path.join(folder, ".env.example") if not os.path.exists(env_path): @@ -147,7 +147,6 @@ def p_localize_env(folder: str) -> None: return # flat app: no run.sh, no env needed try: from backend.apps.outputs.view_builder_templates import ( - DEBUGGER_PATH, TEMPLATE_BACKEND_PATH, link_node_modules, patch_env_port, @@ -157,7 +156,6 @@ def p_localize_env(folder: str) -> None: return patch_env_port(env_path, "FRONTEND_PORT", str(p_free_port())) patch_env_port(env_path, "OPENSWARM_TEMPLATE_BACKEND_PATH", TEMPLATE_BACKEND_PATH) - patch_env_port(env_path, "OPENSWARM_DEBUGGER_PATH", DEBUGGER_PATH) try: patch_env_port(env_path, "OPENSWARM_BACKEND_VENV_CACHE", warm_venv_dir()) except Exception: diff --git a/backend/apps/web/web.py b/backend/apps/web/web.py index bf632be5..0f2ef54f 100644 --- a/backend/apps/web/web.py +++ b/backend/apps/web/web.py @@ -17,16 +17,12 @@ from fastapi import HTTPException from pydantic import BaseModel, Field from typeguard import typechecked -import debug - from backend.config.Apps import SubApp @asynccontextmanager async def web_lifespan(): - debug("START") yield - debug("END") web = SubApp("web", web_lifespan) diff --git a/backend/config/Apps.py b/backend/config/Apps.py index 1b739864..989ec45b 100644 --- a/backend/config/Apps.py +++ b/backend/config/Apps.py @@ -2,7 +2,6 @@ import os import time from fastapi import FastAPI, APIRouter -import debug from uuid import uuid4 from typing import List from contextlib import asynccontextmanager @@ -12,28 +11,23 @@ from typing import Callable class SubApp: def __init__(self, name:str, lifespan:Callable): - debug("START", name) self.id = uuid4() self.name = name self.prefix = f"/api/{name}" self.lifespan = lifespan self.router = APIRouter() - debug("END") def __str__(self): return f"SubApp(name={self.name}, prefix={self.prefix}, id={self.id})" class MainApp: def __init__(self, sub_apps: List[SubApp]): - debug("START") - @asynccontextmanager async def lifespan(app: FastAPI): async with AsyncExitStack() as stack: - # [perf] per-lifespan boot timing. debug() is a no-op in the packaged build, so without this the packaged backend.log has no 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. + # [perf] per-lifespan boot timing. Without this the packaged backend.log has no 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. p_boot_t0 = time.perf_counter() for sub_app in sub_apps: - debug(sub_app.name) p_t0 = time.perf_counter() await stack.enter_async_context(sub_app.lifespan()) p_dt = (time.perf_counter() - p_t0) * 1000 @@ -51,5 +45,4 @@ class MainApp: sub_app.router, prefix=sub_app.prefix, tags=[sub_app.name] - ) - debug("END") \ No newline at end of file + ) \ No newline at end of file diff --git a/backend/run.sh b/backend/run.sh index b62bdc31..ffedede8 100755 --- a/backend/run.sh +++ b/backend/run.sh @@ -32,18 +32,6 @@ if [[ ! -d "$VENV_DIR" ]]; then fi source "$VENV_DIR/bin/activate" -# --- Install custom debugger module if not already installed --- -DEBUGGER_DIR_ABSPATH="$PROJECT_ROOT_ABSPATH/debugger" -if ! pip3 show debug > /dev/null 2>&1; then - echo "Installing debugger module..." - cd "$DEBUGGER_DIR_ABSPATH" - pip3 install -e . - if [[ $? -ne 0 ]]; then - echo "Failed to install debugger module." - exit 1 - fi -fi - # --- Install Python dependencies --- echo "Installing dependencies..." cd "$BACKEND_DIR_ABSPATH" diff --git a/run.ps1 b/run.ps1 index 1fd97543..b578ed5e 100644 --- a/run.ps1 +++ b/run.ps1 @@ -59,8 +59,6 @@ try { if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed" } & $VenvPy -m pip install --quiet -r (Join-Path $ScriptDir 'backend\requirements.txt') if ($LASTEXITCODE -ne 0) { throw "pip install backend reqs failed" } - & $VenvPy -m pip install --quiet -e (Join-Path $ScriptDir 'debugger') - if ($LASTEXITCODE -ne 0) { throw "pip install debugger failed" } } finally { $ErrorActionPreference = $prevEAP } diff --git a/scripts/build-python-env-win.ps1 b/scripts/build-python-env-win.ps1 index fe6166ea..f79af25d 100644 --- a/scripts/build-python-env-win.ps1 +++ b/scripts/build-python-env-win.ps1 @@ -79,10 +79,6 @@ if ($LASTEXITCODE -ne 0) { throw "pip upgrade failed" } & $PythonBin -m pip install -r (Join-Path $ProjectRoot 'backend\requirements.lock') if ($LASTEXITCODE -ne 0) { throw "pip install requirements failed" } -Write-Host "Installing debugger module..." -& $PythonBin -m pip install (Join-Path $ProjectRoot 'debugger') -if ($LASTEXITCODE -ne 0) { throw "pip install debugger failed" } - Write-Host "Verifying claude-agent-sdk..." & $PythonBin -c "import claude_agent_sdk; print('claude-agent-sdk installed')" if ($LASTEXITCODE -ne 0) { throw "claude-agent-sdk verification failed" } diff --git a/scripts/build-python-env.sh b/scripts/build-python-env.sh index c00eca93..9dbe5c90 100755 --- a/scripts/build-python-env.sh +++ b/scripts/build-python-env.sh @@ -89,10 +89,6 @@ echo "Installing backend dependencies (from requirements.lock)..." "$PYTHON_BIN" -m pip install --upgrade pip "$PYTHON_BIN" -m pip install -r "$PROJECT_ROOT/backend/requirements.lock" -# Install the debugger module -echo "Installing debugger module..." -"$PYTHON_BIN" -m pip install "$PROJECT_ROOT/debugger" - # Verify claude-agent-sdk and its bundled binary echo "Verifying claude-agent-sdk..." "$PYTHON_BIN" -c "import claude_agent_sdk; print(f'claude-agent-sdk installed')" diff --git a/scripts/fetch-webapp-template.sh b/scripts/fetch-webapp-template.sh index fd304668..c0699656 100755 --- a/scripts/fetch-webapp-template.sh +++ b/scripts/fetch-webapp-template.sh @@ -3,13 +3,11 @@ # # Idempotent — wipes the existing vendored dir and re-clones at the pinned ref. # Strips files we don't want shipped (LICENSE, README.md, .gitignore — we -# author our own minimal .gitignore inside the snapshot). Applies our two -# patches: -# 1. backend/run.sh: pip-install $OPENSWARM_DEBUGGER_PATH if set, before -# the existing `pip install -e .` — resolves the `swarm-debug` dep -# from OpenSwarm's bundled debugger/ package instead of PyPI (where -# it doesn't exist). -# 2. Add our own backend_init.sh at the snapshot root. +# author our own minimal .gitignore inside the snapshot). Applies our +# patches (swarm-debug toggle-on at boot, vite config pinning, .gitignore, +# backend_init.sh). The template's `swarm-debug` dependency now resolves +# from PyPI like any other dep; the old local-debugger injection patches +# (editable-install of the bundled debugger/) are gone. # # Update REF to bump the pinned snapshot. CI / a future test could compare # `git rev-parse HEAD` of a fresh clone against REF and fail on drift. @@ -36,22 +34,19 @@ mkdir -p "$DEST" ( cd "$TMP/clone" && rm -rf .git LICENSE README.md .gitignore ) cp -R "$TMP/clone/." "$DEST/" -# Patch 1: backend/run.sh installs OpenSwarm's local debugger/ before the -# template's own `pip install -e .` so `from swarm_debug import debug` in -# the template's backend code resolves to our bundled package (the PyPI -# `swarm-debug` doesn't exist — our local package registers as `debug` -# and exposes both `debug` and `swarm_debug` module names via setup.py -# py_modules). +# Patch 1: backend/run.sh forces all swarm-debug per-file toggles ON at +# every boot (they default OFF, including files the agent creates later), +# so `debug()` output actually lands in the App Builder Terminal. Runs +# from the workspace root because that's uvicorn's cwd = the package's +# per-project data-dir key. RUN_SH="$DEST/backend/run.sh" -if ! grep -q "OPENSWARM_DEBUGGER_PATH" "$RUN_SH"; then - # Insert the install line just before `pip install -e .`. macOS sed - # vs GNU sed: use a portable awk inline rewrite. +if ! grep -q "swarm-debug gates output" "$RUN_SH"; then awk ' - /pip install -e \./ && !inserted { - print "if [[ -n \"${OPENSWARM_DEBUGGER_PATH:-}\" && -d \"$OPENSWARM_DEBUGGER_PATH\" ]]; then" - print " echo \"Installing OpenSwarm debugger (swarm_debug) from $OPENSWARM_DEBUGGER_PATH\"" - print " pip install -e \"$OPENSWARM_DEBUGGER_PATH\"" - print "fi" + /^echo "Starting backend server/ && !inserted { + print "# swarm-debug gates output on per-file toggles that default OFF; force all ON each boot so agent-added files show in the Terminal." + print "if [[ \"$IS_WIN\" == \"1\" ]]; then SWARM_DEBUG_BIN=\"$VENV_DIR/Scripts/swarm-debug.exe\"; else SWARM_DEBUG_BIN=\"$VENV_DIR/bin/swarm-debug\"; fi" + print "( cd \"$BACKEND_DIR_ABSPATH/..\" && \"$SWARM_DEBUG_BIN\" toggle on --all >/dev/null 2>&1 ) || true" + print "" inserted = 1 } { print } @@ -59,16 +54,6 @@ if ! grep -q "OPENSWARM_DEBUGGER_PATH" "$RUN_SH"; then chmod +x "$RUN_SH" fi -# Patch 1b: drop `"swarm-debug"` from the template's backend/pyproject.toml -# dependencies. The OpenSwarm debugger gets installed separately via Patch -# 1's `pip install -e $OPENSWARM_DEBUGGER_PATH`. Leaving the dep listed -# would make pip 404 against PyPI (no such package). -PYPROJECT="$DEST/backend/pyproject.toml" -awk ' - /^[[:space:]]*"swarm-debug",?[[:space:]]*$/ { next } - { print } -' "$PYPROJECT" > "$PYPROJECT.tmp" && mv "$PYPROJECT.tmp" "$PYPROJECT" - # Patch 1c: vite.config.ts — pin host to 127.0.0.1 (so our IPv4-only # bind poller in runtime.py:_await_frontend_bind() actually sees the # bound socket on macOS, where `localhost` can resolve to ::1), disable @@ -161,7 +146,7 @@ if [[ -d ./backend ]]; then fi # Resolve master template backend/ path. OPENSWARM_TEMPLATE_BACKEND_PATH -# is written into .env at seed time; OPENSWARM_DEBUGGER_PATH the same. +# is written into .env at seed time. if [[ -z "${OPENSWARM_TEMPLATE_BACKEND_PATH:-}" ]]; then echo "ERROR: OPENSWARM_TEMPLATE_BACKEND_PATH not set in .env. This" >&2 echo " workspace was seeded by an older OpenSwarm; ask the" >&2