mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 12:17:45 +02:00
[hAIk]: added in debug statements instead of printing/logging
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
-3
@@ -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
-1
@@ -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)
|
||||
|
||||
@@ -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
@@ -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]")
|
||||
|
||||
@@ -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,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,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)
|
||||
|
||||
@@ -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
@@ -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'
|
||||
Reference in New Issue
Block a user