[hAIk]: added in debug statements instead of printing/logging

This commit is contained in:
haikdc
2026-04-18 07:43:31 -07:00
parent ca2ea81f82
commit d6d6bc2271
20 changed files with 223 additions and 46 deletions
+176
View File
@@ -0,0 +1,176 @@
---
name: swarm-debug
description: >-
Instrument Python code with toggleable debug output using swarm-debug.
Use when adding debug statements, print statemens, or logging. Use when toggling debug visibility, managing debug
statements, or working with the swarm_debug module. NOTE: you should never use print or logging statements, only use debug.
---
# swarm-debug
A non-invasive debug logger for Python. You add `debug()` calls to code; visibility is controlled per-file via CLI or GUI without modifying source.
## CRITICAL: Always use the CLI, never read raw files
- **NEVER** read `~/.swarm-debug/projects/<hash>/debug_toggles.json` directly with `cat`, `head`, `tail`, `read`, or any file tool. The raw JSON is an internal per-project cache and may be stale or inconsistent with the actual codebase.
- **ALWAYS** use `swarm-debug status` or `swarm-debug status --json` to inspect state. The CLI rescans for `debug(` calls and returns the true resolved state.
- **NEVER** write to `debug_toggles.json` directly (stored per-project under `~/.swarm-debug/projects/<hash>/`). Use `swarm-debug toggle`, `swarm-debug set-color`, `swarm-debug set-emoji`, etc.
## CRITICAL: Locate and activate the correct Python environment first
Before running **any** `swarm-debug` command, you must find the environment that has the `swarm-debug` package installed:
1. **Check `.vscode/settings.json`** in the project root for a `python.defaultInterpreterPath`. If it points to a venv (e.g. `${workspaceFolder}/backend/.venv/bin/python`), activate that venv first:
```bash
source <path-to-that-venv>/bin/activate
```
2. **If no `.vscode/settings.json` exists** (or it has no interpreter path), search the project for a `.venv` directory that contains the `swarm-debug` package:
```bash
find . -path '*/.venv/bin/swarm-debug' -print -quit
```
If found, activate that venv.
3. **If neither exists**, ask the user where the `swarm-debug` package is installed. Do **NOT** fall back to reading the raw JSON file or guessing a system Python path.
## Adding debug statements
```python
from swarm_debug import debug
debug(my_var) # prints: [func_name] : my_var: int = 42
debug("checkpoint") # prints: [func_name] : checkpoint (italic)
debug(err) # errors auto-force ON with red output
debug("x=%s y=%s", x, y) # %-style formatting
```
`debug()` inspects the call stack to extract the caller's file, function, variable names, and indentation. No format strings or manual labels needed -- pass variables directly.
### Full signature
```python
debug(*args, mode='debug', override_max_chars=False, sep=<auto>, end='\n',
pretty=True, lang=None, table=<auto>)
```
| Kwarg | Type | Default | Description |
|---|---|---|---|
| `mode` | `str` | `"debug"` | Log level. `"all"` (always), `"debug"` (default), `"test"` (high priority) |
| `override_max_chars` | `bool` | `False` | Bypass the 3000-char truncation limit |
| `sep` | `str` | auto | Join all args with this separator (like `print(sep=...)`) |
| `pretty` | `bool` | `True` | Pretty-print dicts, lists, sets, tuples, dataclasses with Rich |
| `lang` | `str\|None` | `None` | Syntax-highlight all args as this language (e.g. `"sql"`, `"json"`, `"html"`) |
| `table` | `bool` | auto | Force table layout on/off. Auto-on when >1 non-text data args |
### Rich output features
All output is rendered with Rich. Function names in the output are clickable file links (in terminals that support OSC 8 hyperlinks like iTerm2 and Windows Terminal). Type annotations are shown in dim text for non-string values.
**Pretty-printed data structures** (on by default):
```python
debug(my_dict) # dicts, lists, sets, dataclasses are pretty-printed with Rich
debug(my_dict, pretty=False) # opt out for flat single-line output
```
**Syntax-highlighted strings** (explicit lang= kwarg):
```python
debug(sql_query, lang="sql") # SQL keyword highlighting
debug(json_string, lang="json") # JSON syntax coloring
debug(html_body, lang="html") # HTML highlighting
```
**Table layout** (auto: on when >1 non-text data args, off otherwise):
```python
debug(x, y, z) # 3 data args -> table with Name | Type | Value columns
debug(x) # single arg -> inline output (no table)
debug("msg", x) # 1 text + 1 data arg -> inline (only 1 data arg)
debug("msg", x, y) # 1 text + 2 data args -> table
debug(x, y, z, table=False) # force per-line output
debug(x, table=True) # force table even for a single arg
```
**Diff output** -- compare two values with a unified diff:
```python
debug.diff(old_state, new_state) # default label "diff"
debug.diff(old_state, new_state, label="state") # custom label
```
**Timing** -- measure how long a block takes:
```python
with debug.time("database query"):
result = db.execute(query)
# prints: [func] : ⏱ database query took 0.123s (green/yellow/red based on duration)
```
**Truncation**: values over 3000 chars are truncated (first 1500 + `...` + last 1500). Pass `override_max_chars=True` to disable.
**Indent group markers**: when indentation level changes between `debug()` calls, a visual rule line is emitted to mark the group boundary.
## CLI reference
All commands work standalone (no server required). Paths are relative to project root.
```bash
# View current state
swarm-debug status # human-readable tree with [ON]/[OFF] tags
swarm-debug status --json # machine-readable JSON (pipe to jq, python, etc.)
swarm-debug stats # flat table of all files with path/status/color/emoji
# Toggle visibility
swarm-debug toggle on src/agents/planner.py # single file
swarm-debug toggle off src/agents/ # whole directory (recursive)
swarm-debug toggle on --all # everything
# Configuration
swarm-debug set-root /path/to/project
swarm-debug set-color src/agents/planner.py "#ff0000" # single file
swarm-debug set-color src/agents/ "#ff0000" # directory (propagates lightened color to children)
swarm-debug set-emoji src/agents/planner.py "🔴" # single file
swarm-debug set-emoji src/agents/ "🔴" # directory (propagates emoji to children)
swarm-debug reset # reset all colors/emojis (with confirmation)
# GUI
swarm-debug gui # launches web UI at localhost:6969
swarm-debug gui --port 8080 # custom port
swarm-debug gui --verbose # show all server logs in the terminal
# Cursor skill management
swarm-debug install-cursor-skill # copy SKILL.md to .cursor/skills/swarm-debug/
swarm-debug uninstall-cursor-skill # remove the skill directory
# Package management
swarm-debug --version # show version (also checks for updates and skill staleness)
swarm-debug --upgrade # upgrade to latest version from PyPI
swarm-debug --help-all # detailed help for all commands + API-only endpoints
```
## Typical workflow
1. **Instrument**: Add `debug()` calls to files you want to observe.
2. **Set root**: `swarm-debug set-root /path/to/project` (only needed once; persisted in `~/.swarm-debug/projects/<hash>/root_dir.txt`).
3. **Toggle on** the files you care about: `swarm-debug toggle on src/core/engine.py`.
4. **Run** the program -- only toggled-on files produce debug output.
5. **Toggle off** when done: `swarm-debug toggle off src/core/engine.py`.
## How it works
- Internal state is cached per-project in `~/.swarm-debug/projects/<hash>/debug_toggles.json` (where `<hash>` is derived from the project root path), but this file should never be read or written directly. Always use the CLI commands to inspect or modify state.
- The CLI and GUI both read/write through the same underlying store.
- After any CLI/GUI change, a `needs_resync.txt` flag is set. The next `debug()` call in the running program reloads the config automatically -- no restart needed.
- Only `.py` files that contain `debug(` calls appear in the tree.
## Key behaviors
- **Directory changes propagate**: toggling, setting color, or setting emoji on a directory propagates to all children recursively.
- **Manual-override flags**: each concern has its own flag (`set_manually` for toggles, `set_manually_color` for color, `set_manually_emoji` for emoji). When you explicitly set a file's value, its flag is set so parent propagation won't override it.
- **Errors bypass toggles**: if a `debug()` argument is an Exception or contains "error", it always prints (red, with a cross emoji), regardless of toggle state.
- **Indentation preserved**: `debug()` reads source indentation and renders nested output with visual indent bars.
## Reading status programmatically
```bash
# Get JSON and extract toggled-on files with jq
swarm-debug status --json | jq '.. | objects | select(.is_toggled == true and (.children | not)) | .name'
```
## Environment variables
- `SWARM_DEBUG_ROOT` -- overrides the project root (highest priority, above persisted file and cwd).
@@ -1,6 +1,7 @@
import asyncio
from typing import Callable, Awaitable, Dict
from pydantic import BaseModel, Field, InstanceOf
from swarm_debug import debug
from typeguard import typechecked
class FutureBridge(BaseModel):
@@ -26,7 +27,7 @@ class FutureBridge(BaseModel):
try:
return await asyncio.wait_for(future, timeout=timeout)
except asyncio.TimeoutError:
print(f"[FutureBridge.request] Request {request_id} timed out after {timeout}s")
debug(f"[FutureBridge.request] Request {request_id} timed out after {timeout}s")
return {"error": "Timed out"}
finally:
self.p_pending.pop(request_id, None)
+2 -1
View File
@@ -31,6 +31,7 @@ from backend.apps.agents.COMMS_MANAGER.COMMS_MANAGER import COMMS_MANAGER
from backend.apps.settings.settings import load_settings
from backend.core.llm.resolve_sdk_env import resolve_sdk_env
from backend.ports import NINE_ROUTER_PORT
from swarm_debug import debug
from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk.types import HookMatcher, McpServerConfig
from backend.core.tools.shared_structs.Toolkit import Toolkit
@@ -70,7 +71,7 @@ async def agents_lifespan():
)
SESSIONS[stored.session_id] = stored
except Exception as e:
print(f"[agents lifespan] Skipping corrupt session {stored.session_id}: {e}")
debug(f"[agents lifespan] Skipping corrupt session {stored.session_id}: {e}")
yield
for agent in list[Agent](SESSIONS.values()):
await agent.stop_agent()
+3 -2
View File
@@ -13,6 +13,7 @@ from backend.apps.agents.agents import get_all_sessions, delete_session
from backend.apps.settings.settings import load_settings
from backend.apps.dashboards.generate_dashboard_name import generate_dashboard_name
from backend.ports import NINE_ROUTER_PORT
from swarm_debug import debug
from typing import Optional
from backend.config.paths import DB_ROOT
@@ -93,7 +94,7 @@ async def generate_name(dashboard_id: str):
if generated:
fallback = generated
except Exception as e:
print(f"[dashboards.generate_name] ERROR: Dashboard name generation failed, using fallback: {e}")
debug(f"[dashboards.generate_name] ERROR: Dashboard name generation failed, using fallback: {e}")
dashboard.name = fallback
dashboard.auto_named = True
@@ -139,7 +140,7 @@ async def delete_dashboard(dashboard_id: str):
try:
await delete_session(session["session_id"])
except Exception:
print(f"[dashboards.delete_dashboard] ERROR: Failed to delete session {session.get('session_id')} during dashboard deletion")
debug(f"[dashboards.delete_dashboard] ERROR: Failed to delete session {session.get('session_id')} during dashboard deletion")
DASHBOARD_STORE.delete(dashboard_id)
return {"ok": True}
@@ -2,6 +2,7 @@ import asyncio
import time
from typing import Optional
from swarm_debug import debug
from typeguard import typechecked
from pydantic import BaseModel, Field, InstanceOf
@@ -45,5 +46,5 @@ class RegistryRefreshLoop(BaseModel):
)
self.updated_at = time.time()
except Exception as e:
print(f"[RegistryRefreshLoop] Skill registry refresh error: {e}")
debug(f"[RegistryRefreshLoop] Skill registry refresh error: {e}")
await asyncio.sleep(self.refresh_interval_s)
@@ -4,6 +4,7 @@
import asyncio
import httpx
from swarm_debug import debug
from typeguard import typechecked
from backend.apps.skills.RegistryRefreshLoop.fetch_all_registry_skills.utils.fetch_skill_paths import fetch_skill_paths
from backend.apps.skills.RegistryRefreshLoop.fetch_all_registry_skills.utils.fetch_one_skill import fetch_one_skill
@@ -25,9 +26,9 @@ async def fetch_all_registry_skills(
manifest_url=f"{github_base_url}/{github_repo}/{github_branch}{manifest_extension}"
)
except Exception as e:
print(f"[fetch_all_registry_skills] Skill registry manifest fetch failed: {e}")
debug(f"[fetch_all_registry_skills] Skill registry manifest fetch failed: {e}")
return result
print(f"[fetch_all_registry_skills] Skill registry: found {len(paths)} skills in manifest, fetching...")
debug(f"[fetch_all_registry_skills] Skill registry: found {len(paths)} skills in manifest, fetching...")
sem = asyncio.Semaphore(num_concurrent_fetches)
records = await asyncio.gather(
*[fetch_one_skill(
@@ -43,5 +44,5 @@ async def fetch_all_registry_skills(
for rec in records:
if rec:
result[rec["name"]] = rec
print(f"[fetch_all_registry_skills] Skill registry cache refreshed: {len(result)} skills")
debug(f"[fetch_all_registry_skills] Skill registry cache refreshed: {len(result)} skills")
return result
@@ -2,6 +2,7 @@ import asyncio
from typing import Optional
import httpx
from backend.apps.skills.parse_frontmatter import parse_frontmatter
from swarm_debug import debug
from typeguard import typechecked
from pydantic import InstanceOf
@@ -22,7 +23,7 @@ async def fetch_one_skill(
return None
raw = resp.text
except Exception as exc:
print(f"[fetch_one_skill] Failed to fetch {folder}/SKILL.md: {exc}")
debug(f"[fetch_one_skill] Failed to fetch {folder}/SKILL.md: {exc}")
return None
meta, body = parse_frontmatter(raw)
-3
View File
@@ -1,7 +1,6 @@
"""Skills SubApp — local skill CRUD, workspace management, and remote registry."""
import json
import logging
import os
from contextlib import asynccontextmanager
from typing import Any, Optional
@@ -15,8 +14,6 @@ from backend.apps.skills.SkillStore.SkillStore import SkillStore
from backend.apps.skills.parse_frontmatter import parse_frontmatter
from backend.apps.skills.RegistryRefreshLoop.RegistryRefreshLoop import RegistryRefreshLoop
logger = logging.getLogger(__name__)
# ---------------------------------------------------------------------------
# Paths & singletons
# ---------------------------------------------------------------------------
@@ -2,6 +2,7 @@
import httpx
from pydantic import Field, BaseModel, InstanceOf
from swarm_debug import debug
from typeguard import typechecked
from backend.apps.subscriptions.NineRouter.helpers.constants import NINE_ROUTER_API, NINE_ROUTER_V1
@@ -21,7 +22,7 @@ class NineRouterClient(BaseModel):
if r.status_code == 200:
return r.json()
except Exception as e:
print(f"9Router providers fetch failed: {e}")
debug(f"9Router providers fetch failed: {e}")
return []
@typechecked
@@ -94,9 +95,9 @@ class NineRouterClient(BaseModel):
"codeVerifier": code_verifier,
"state": state,
}
print(f"exchange_oauth: provider={provider} redirect_uri={redirect_uri}")
debug(f"exchange_oauth: provider={provider} redirect_uri={redirect_uri}")
r = await self.p_http.post(f"{NINE_ROUTER_API}/oauth/{provider}/exchange", json=payload)
print(f"exchange_oauth: status={r.status_code}")
debug(f"exchange_oauth: status={r.status_code}")
r.raise_for_status()
return r.json()
@@ -117,7 +118,7 @@ class NineRouterClient(BaseModel):
for m in models
]
except Exception as e:
print(f"9Router models fetch failed: {e}")
debug(f"9Router models fetch failed: {e}")
return []
@typechecked
@@ -1,5 +1,6 @@
import re
from swarm_debug import debug
from typeguard import typechecked
_ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]|\x1b[c78]")
+3 -2
View File
@@ -14,6 +14,7 @@ from fastapi.responses import HTMLResponse, JSONResponse
from backend.config.Apps import SubApp
from backend.apps.subscriptions.NineRouter.NineRouter import NineRouter
from backend.apps.subscriptions.html_constants import SUCCESS_HTML, ERROR_STYLE
from swarm_debug import debug
P_PENDING_OAUTH: Dict[str, dict] = {}
@@ -27,7 +28,7 @@ async def subscriptions_lifespan():
try:
await router.ensure_running()
except Exception as e:
print(f"9Router auto-start failed: {e}")
debug(f"9Router auto-start failed: {e}")
yield
try:
await router.stop()
@@ -156,7 +157,7 @@ async def subscriptions_callback(request: Request):
pending["redirect_uri"], pending["code_verifier"], state,
)
except Exception as e:
print(f"OAuth callback: exchange failed for provider={pending['provider']}: {e}")
debug(f"OAuth callback: exchange failed for provider={pending['provider']}: {e}")
return HTMLResponse(
f'<html><body {ERROR_STYLE}><div style="text-align:center">'
f'<h2>Connection failed</h2><p style="color:#888">{e}</p></div></body></html>'
@@ -5,7 +5,6 @@ Pure business logic with no HTTP/FastAPI dependencies.
import base64
import hashlib
import logging
import os
import secrets
import time
@@ -19,11 +18,10 @@ from backend.apps.tools.OAuthService.OAUTH_PROVIDERS.OAuthProvider import OAuthP
from backend.apps.tools.OAuthService.OAUTH_PROVIDERS.OAUTH_PROVIDERS import OAUTH_PROVIDERS
from backend.apps.tools.shared_utils.ToolDefinition import ToolDefinition
from backend.core.db.PydanticStore import PydanticStore
from swarm_debug import debug
from typeguard import typechecked
from backend.ports import BACKEND_DEV_PORT
logger = logging.getLogger(__name__)
class OAuthService(BaseModel):
store: PydanticStore[ToolDefinition]
@@ -130,7 +128,7 @@ class OAuthService(BaseModel):
resp = await client.post(provider.token_url, data=token_data, headers=headers)
if resp.status_code != 200:
logger.warning("OAuth token exchange failed: %s", resp.text)
debug(f"OAuth token exchange failed: {resp.text}")
raise RuntimeError(resp.text)
tokens: Dict[str, Any] = resp.json()
@@ -190,7 +188,7 @@ class OAuthService(BaseModel):
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
except Exception as e:
logger.warning("Failed to revoke token for tool %s: %s", tool.id, e)
debug(f"Failed to revoke token for tool {tool.id}: {e}")
tool.oauth_tokens = {}
tool.auth_status = "configured"
@@ -241,7 +239,7 @@ class OAuthService(BaseModel):
self.store.save(tool)
return new_token
except Exception as e:
logger.warning("OAuth token refresh failed for tool %s: %s", tool.id, e)
debug(f"OAuth token refresh failed for tool {tool.id}: {e}")
return None
@typechecked
@@ -254,5 +252,5 @@ class OAuthService(BaseModel):
if resp.status_code == 200:
return resp.json().get(field)
except Exception as e:
logger.warning("Failed to fetch userinfo%s: %s", f" for {label}" if label else "", e)
debug(f"Failed to fetch userinfo{f' for {label}' if label else ''}: {e}")
return None
@@ -1,4 +1,5 @@
from typing import Any
from swarm_debug import debug
from backend.apps.tools.discover_tools.DiscoveryError import DiscoveryError, DiscoveryConfigError
from backend.apps.tools.discover_tools.utils.discover_mcp_tools_stdio import discover_mcp_tools_stdio
from backend.apps.tools.discover_tools.utils.discover_mcp_tools_http import discover_mcp_tools_http
@@ -36,7 +37,7 @@ async def discover_tools(config: dict[str, Any], tool_name: str = "") -> list[di
try:
return await discover_mcp_tools_http(url, config.get("headers"))
except DiscoveryError:
print(f"[discover_tools] Streamable HTTP failed for {tool_name}, retrying with SSE")
debug(f"[discover_tools] Streamable HTTP failed for {tool_name}, retrying with SSE")
return await discover_mcp_tools_sse(url, config.get("headers"))
raise DiscoveryConfigError(f"Unsupported MCP transport type: '{transport}'")
@@ -5,6 +5,7 @@ from backend.core.tools.shared_structs.MCP_Tool import STDIO_MCP_Tool
from backend.apps.tools.shared_utils.mcp_config import resolve_command, augmented_path
# TODO: either remove the import, or make them non private vars
from backend.config.paths import P_BACKEND_DIR, p_is_packaged
from swarm_debug import debug
from typeguard import typechecked
@@ -21,7 +22,7 @@ def build_stdio_tool(
if resolved:
command = resolved
else:
print(f"[build_stdio_tool] Command '{command}' not found on PATH or bundled directories")
debug(f"[build_stdio_tool] Command '{command}' not found on PATH or bundled directories")
env = config.get("env", {})
env.setdefault("PATH", augmented_path())
@@ -12,6 +12,7 @@ from backend.core.tools.shared_structs.MCP_Tool import MCP_Tool
from backend.apps.tools.tool_definition_to_mcp_tool.helpers.inject_credentials import inject_credentials
from backend.apps.tools.tool_definition_to_mcp_tool.helpers.build_stdio_tool import build_stdio_tool
from backend.apps.tools.tool_definition_to_mcp_tool.helpers.build_http_sse_tool import build_http_sse_tool
from swarm_debug import debug
from typeguard import typechecked
import re
@@ -48,5 +49,5 @@ def tool_definition_to_mcp_tool(
elif transport in ("http", "sse"):
return build_http_sse_tool(tool_def, config, server_name, transport)
print(f"[tool_definition_to_mcp_tool] Unsupported MCP transport type '{transport}' for tool {tool_def.name}")
debug(f"[tool_definition_to_mcp_tool] Unsupported MCP transport type '{transport}' for tool {tool_def.name}")
return None
+4 -6
View File
@@ -4,7 +4,6 @@ from typing_extensions import List
from claude_agent_sdk.types import McpServerConfig
from pydantic import BaseModel, Field
import json
import logging
import os
import time
from contextlib import asynccontextmanager
@@ -27,10 +26,9 @@ from backend.core.tools.shared_structs.TOOL_PERMISSIONS import TOOL_PERMISSIONS
from backend.core.tools.shared_structs.Toolkit import Toolkit
from backend.core.tools.shared_structs.Tool import Tool
from backend.core.tools.shared_structs.MCP_Tool import MCP_Tool
from swarm_debug import debug
from typeguard import typechecked
logger = logging.getLogger(__name__)
TOOLS_DIR = os.path.join(DB_ROOT, "tools")
BUILTIN_PERMS_PATH = os.path.join(TOOLS_DIR, "builtin_permissions.json")
@@ -77,12 +75,12 @@ def save_builtin_permissions(perms: dict[str, str]) -> None:
json.dump(perms, f, indent=2)
@tools.router.get("/builtin/permissions")
@tools.router.get("/get_builtin_permissions")
async def get_builtin_permissions() -> dict:
return {"permissions": load_builtin_permissions()}
@tools.router.put("/builtin/permissions")
@tools.router.put("/update_builtin_permissions")
async def update_builtin_permissions(body: dict) -> dict:
valid_names = {t["name"] for t in BUILTIN_TOOLS}
valid_policies = {"allow", "ask", "deny"}
@@ -200,7 +198,7 @@ async def discover(tool_id: str) -> dict:
raise HTTPException(status_code=502, detail=str(e))
except Exception as e:
msg = str(e).strip() or type(e).__name__
logger.warning(f"MCP tool discovery failed for {tool.name}: {msg}", exc_info=True)
debug(f"MCP tool discovery failed for {tool.name}: {msg}")
raise HTTPException(status_code=502, detail=f"Discovery failed: {msg}")
permissions: dict[str, Any] = {}
+2 -2
View File
@@ -2,7 +2,7 @@ import os
from backend.ports import BACKEND_DEV_PORT
from fastapi import FastAPI, APIRouter
# import debug
from swarm_debug import debug
from uuid import uuid4
from typing import List
from contextlib import asynccontextmanager
@@ -34,7 +34,7 @@ class MainApp:
# debug(sub_app.name)
await stack.enter_async_context(sub_app.lifespan())
_port = os.environ.get("OPENSWARM_PORT", str(BACKEND_DEV_PORT))
print(f"\nCheck out the API docs at: http://127.0.0.1:{_port}/docs\n")
debug(f"\nCheck out the API docs at: http://127.0.0.1:{_port}/docs\n")
yield
self.app = FastAPI(lifespan=lifespan)
+2 -4
View File
@@ -1,5 +1,4 @@
import asyncio
import logging
import os
from copy import deepcopy
from typing import Any, Dict, List, Literal, Optional
@@ -18,11 +17,10 @@ from backend.core.events.events import (
ApprovalRequestEvent, EventCallback, AnyEvent,
)
from backend.core.tools.shared_structs.Toolkit import Toolkit
from swarm_debug import debug
os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000")
logger = logging.getLogger(__name__)
class Agent(BaseModel):
model: str
@@ -125,7 +123,7 @@ class Agent(BaseModel):
async def send_message(self, msg: Message) -> None:
async with self.lock:
if self.task is not None and not self.task.done():
logger.warning("[Agent.send_message] Agent %s is already running", self.session_id)
debug(f"[Agent.send_message] Agent {self.session_id} is already running")
return
await self.emit(AgentMessageEvent(
@@ -1,6 +1,6 @@
import asyncio
import logging
from swarm_debug import debug
from typeguard import typechecked
from claude_agent_sdk import (
@@ -18,8 +18,6 @@ from backend.core.events.events import (
)
from typing import List, Dict, Optional
logger = logging.getLogger(__name__)
@typechecked
async def run_agent_loop(
@@ -79,7 +77,7 @@ async def run_agent_loop(
raise
except Exception as e:
logger.exception("Agent %s error: %s", session_id, e)
debug(f"Agent {session_id} error: {e}")
error_msg = SystemMessage(
content=f"Error: {e}",
branch_id=branch_id,
+2 -2
View File
@@ -38,7 +38,7 @@ BACKEND_PORT=$(python3 -c "import json; print(json.load(open('$PROJECT_ROOT_ABSP
# --- Start the backend server ---
echo "Starting backend server on http://0.0.0.0:${BACKEND_PORT} ..."
cd "$PROJECT_ROOT_ABSPATH"
"$UV_BIN" run --project "$BACKEND_DIR_ABSPATH" python -m uvicorn backend.main:app \
WATCHFILES_FORCE_POLLING=true "$UV_BIN" run --project "$BACKEND_DIR_ABSPATH" python -m uvicorn backend.main:app \
--host 0.0.0.0 --port "$BACKEND_PORT" --reload \
--reload-dir "$BACKEND_DIR_ABSPATH" \
--reload-exclude '*.pyc'
--reload-exclude '*.pyc'