Fix folder watcher matching, notifications, and BLM venv isolation
- Remove status filter from folder watcher so tasks in any open status (including "internal review") are matched by keyword - Add retry logic for stuck processing/blocked/unmatched KV states - Fix notification ordering (newest first, limit 50) and date parsing - Use BLM's own .venv Python instead of uv run for subprocess calls - Document external tools venv convention in CLAUDE.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>cora-start
parent
3b4a8e47be
commit
62186d8dec
|
|
@ -109,6 +109,7 @@ uv add --group test <package>
|
||||||
- **Notifications**: All scheduler events go through `NotificationBus.push()`, never directly to a UI
|
- **Notifications**: All scheduler events go through `NotificationBus.push()`, never directly to a UI
|
||||||
- **Tests**: Use `respx` to mock httpx calls, `tmp_db` fixture for isolated SQLite instances
|
- **Tests**: Use `respx` to mock httpx calls, `tmp_db` fixture for isolated SQLite instances
|
||||||
- **ClickUp attachments**: `ClickUpClient.upload_attachment()` uses module-level `httpx.post()` (not the shared client) for multipart uploads
|
- **ClickUp attachments**: `ClickUpClient.upload_attachment()` uses module-level `httpx.post()` (not the shared client) for multipart uploads
|
||||||
|
- **External tools**: Subprocess calls to external projects (e.g. Big-Link-Man) must use the tool's own `.venv` Python, never `uv run` from CheddahBot's environment. Resolve the venv python at `{tool_dir}/.venv/Scripts/python.exe` (Windows) or `{tool_dir}/.venv/bin/python` (Linux/Mac). Fail if no venv exists.
|
||||||
|
|
||||||
## Multi-Agent Configuration
|
## Multi-Agent Configuration
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -345,8 +345,11 @@ async def get_notifications():
|
||||||
if not _db:
|
if not _db:
|
||||||
return {"notifications": []}
|
return {"notifications": []}
|
||||||
|
|
||||||
notes = _db.get_notifications_after(0, limit=100)
|
rows = _db._conn.execute(
|
||||||
return {"notifications": notes}
|
"SELECT id, message, category, created_at FROM notifications"
|
||||||
|
" ORDER BY id DESC LIMIT 50"
|
||||||
|
).fetchall()
|
||||||
|
return {"notifications": [dict(r) for r in rows]}
|
||||||
|
|
||||||
|
|
||||||
@router.get("/system/kv-states")
|
@router.get("/system/kv-states")
|
||||||
|
|
|
||||||
|
|
@ -488,13 +488,16 @@ class Scheduler:
|
||||||
filename = xlsx_path.name
|
filename = xlsx_path.name
|
||||||
kv_key = f"linkbuilding:watched:{filename}"
|
kv_key = f"linkbuilding:watched:{filename}"
|
||||||
|
|
||||||
# Skip already processed files
|
# Skip completed/failed; retry "processing" (killed run) and "blocked" (missing field)
|
||||||
existing = self.db.kv_get(kv_key)
|
existing = self.db.kv_get(kv_key)
|
||||||
if existing:
|
if existing:
|
||||||
try:
|
try:
|
||||||
state = json.loads(existing)
|
state = json.loads(existing)
|
||||||
if state.get("status") in ("completed", "processing", "failed"):
|
if state.get("status") in ("completed", "failed"):
|
||||||
continue
|
continue
|
||||||
|
if state.get("status") in ("processing", "blocked", "unmatched"):
|
||||||
|
log.info("Retrying '%s' state for %s", state["status"], filename)
|
||||||
|
self.db.kv_delete(kv_key)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|
@ -672,7 +675,7 @@ class Scheduler:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tasks = client.get_tasks_from_space(space_id, statuses=["to do"])
|
tasks = client.get_tasks_from_overall_lists(space_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.warning("ClickUp query failed in _match_xlsx_to_clickup: %s", e)
|
log.warning("ClickUp query failed in _match_xlsx_to_clickup: %s", e)
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,16 @@ def _run_blm_command(
|
||||||
|
|
||||||
Always injects -u/-p from BLM_USERNAME/BLM_PASSWORD env vars.
|
Always injects -u/-p from BLM_USERNAME/BLM_PASSWORD env vars.
|
||||||
"""
|
"""
|
||||||
cmd = ["uv", "run", "python", "main.py", *args]
|
# Use BLM's own venv Python so its dependencies are available
|
||||||
|
venv_python = Path(blm_dir) / ".venv" / "Scripts" / "python.exe"
|
||||||
|
if not venv_python.exists():
|
||||||
|
# Fallback for Linux/Mac
|
||||||
|
venv_python = Path(blm_dir) / ".venv" / "bin" / "python"
|
||||||
|
if not venv_python.exists():
|
||||||
|
raise FileNotFoundError(
|
||||||
|
f"No .venv found in {blm_dir}. External tools must have their own venv."
|
||||||
|
)
|
||||||
|
cmd = [str(venv_python), "main.py", *args]
|
||||||
|
|
||||||
# Inject credentials from env vars
|
# Inject credentials from env vars
|
||||||
username = os.getenv("BLM_USERNAME", "")
|
username = os.getenv("BLM_USERNAME", "")
|
||||||
|
|
|
||||||
|
|
@ -888,7 +888,7 @@ async function loadNotifications() {
|
||||||
|
|
||||||
let html = '<ul class="log-list">';
|
let html = '<ul class="log-list">';
|
||||||
notes.slice(0, 100).forEach(n => {
|
notes.slice(0, 100).forEach(n => {
|
||||||
const time = n.created_at ? new Date(n.created_at * 1000).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' }) : '';
|
const time = n.created_at ? new Date(n.created_at).toLocaleString('en-US', { month:'short', day:'numeric', hour:'2-digit', minute:'2-digit' }) : '';
|
||||||
const level = (n.level || 'info').toLowerCase();
|
const level = (n.level || 'info').toLowerCase();
|
||||||
const typeCls = level === 'error' ? 'error' : level === 'warning' ? 'error' : 'heartbeat';
|
const typeCls = level === 'error' ? 'error' : level === 'warning' ? 'error' : 'heartbeat';
|
||||||
html += `<li class="log-item">
|
html += `<li class="log-item">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue