Fix task looping: increase stale recovery to 6h, add file logging, use UNC paths
- Stale task recovery threshold 2h → 6h to prevent resetting tasks while Cora is still running - Add rotating file logger (WARNING+) to logs/cheddahbot.log for debugging - Silence httpx/httpcore INFO spam from terminal - Switch watch folder paths from Z: drive letters to UNC paths to avoid intermittent mount drops - Fix test_db tests to add messages so list_conversations includes them Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>fix/customer-field-migration
parent
83c7c378e5
commit
45ab4c1b33
|
|
@ -1,6 +1,7 @@
|
||||||
"""Entry point: python -m cheddahbot"""
|
"""Entry point: python -m cheddahbot"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from .agent import Agent
|
from .agent import Agent
|
||||||
|
|
@ -15,6 +16,22 @@ logging.basicConfig(
|
||||||
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
|
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
|
||||||
datefmt="%H:%M:%S",
|
datefmt="%H:%M:%S",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Warnings and errors to rotating log file
|
||||||
|
_log_dir = Path(__file__).resolve().parent.parent / "logs"
|
||||||
|
_log_dir.mkdir(exist_ok=True)
|
||||||
|
_file_handler = RotatingFileHandler(
|
||||||
|
_log_dir / "cheddahbot.log", maxBytes=5 * 1024 * 1024, backupCount=5
|
||||||
|
)
|
||||||
|
_file_handler.setLevel(logging.WARNING)
|
||||||
|
_file_handler.setFormatter(
|
||||||
|
logging.Formatter("%(asctime)s [%(name)s] %(levelname)s: %(message)s")
|
||||||
|
)
|
||||||
|
logging.getLogger().addHandler(_file_handler)
|
||||||
|
|
||||||
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||||
|
|
||||||
log = logging.getLogger("cheddahbot")
|
log = logging.getLogger("cheddahbot")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -271,7 +271,7 @@ class Scheduler:
|
||||||
return self._clickup_client
|
return self._clickup_client
|
||||||
|
|
||||||
# Maximum time a task can stay in "automation underway" before recovery (seconds)
|
# Maximum time a task can stay in "automation underway" before recovery (seconds)
|
||||||
STALE_TASK_THRESHOLD_SECONDS = 2 * 60 * 60 # 2 hours
|
STALE_TASK_THRESHOLD_SECONDS = 6 * 60 * 60 # 6 hours
|
||||||
|
|
||||||
def _clickup_loop(self):
|
def _clickup_loop(self):
|
||||||
"""Poll ClickUp for tasks on a regular interval."""
|
"""Poll ClickUp for tasks on a regular interval."""
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ clickup:
|
||||||
# Link Building settings
|
# Link Building settings
|
||||||
link_building:
|
link_building:
|
||||||
blm_dir: "E:/dev/Big-Link-Man"
|
blm_dir: "E:/dev/Big-Link-Man"
|
||||||
watch_folder: "Z:/cora-inbox"
|
watch_folder: "//PennQnap1/SHARE1/cora-inbox"
|
||||||
watch_interval_minutes: 60
|
watch_interval_minutes: 60
|
||||||
default_branded_plus_ratio: 0.7
|
default_branded_plus_ratio: 0.7
|
||||||
|
|
||||||
|
|
@ -104,12 +104,12 @@ autocora:
|
||||||
success_status: "running cora"
|
success_status: "running cora"
|
||||||
error_status: "error"
|
error_status: "error"
|
||||||
enabled: true
|
enabled: true
|
||||||
cora_human_inbox: "Z:/Cora-For-Human"
|
cora_human_inbox: "//PennQnap1/SHARE1/Cora-For-Human"
|
||||||
|
|
||||||
# Content creation settings
|
# Content creation settings
|
||||||
content:
|
content:
|
||||||
cora_inbox: "Z:/content-cora-inbox"
|
cora_inbox: "//PennQnap1/SHARE1/content-cora-inbox"
|
||||||
outline_dir: "Z:/content-outlines"
|
outline_dir: "//PennQnap1/SHARE1/content-outlines"
|
||||||
|
|
||||||
# ntfy.sh push notifications
|
# ntfy.sh push notifications
|
||||||
ntfy:
|
ntfy:
|
||||||
|
|
|
||||||
|
|
@ -10,21 +10,27 @@ from cheddahbot.db import Database
|
||||||
class TestConversationsAgentName:
|
class TestConversationsAgentName:
|
||||||
"""Conversations are tagged by agent_name for per-agent history filtering."""
|
"""Conversations are tagged by agent_name for per-agent history filtering."""
|
||||||
|
|
||||||
|
def _add_msg(self, db, conv_id):
|
||||||
|
"""Add a dummy message so list_conversations() includes this conv."""
|
||||||
|
db.add_message(conv_id, "user", "hello")
|
||||||
|
|
||||||
def test_create_with_default_agent_name(self, tmp_db):
|
def test_create_with_default_agent_name(self, tmp_db):
|
||||||
tmp_db.create_conversation("conv1")
|
tmp_db.create_conversation("conv1")
|
||||||
|
self._add_msg(tmp_db, "conv1")
|
||||||
convs = tmp_db.list_conversations()
|
convs = tmp_db.list_conversations()
|
||||||
assert len(convs) == 1
|
assert len(convs) == 1
|
||||||
assert convs[0]["agent_name"] == "default"
|
assert convs[0]["agent_name"] == "default"
|
||||||
|
|
||||||
def test_create_with_custom_agent_name(self, tmp_db):
|
def test_create_with_custom_agent_name(self, tmp_db):
|
||||||
tmp_db.create_conversation("conv1", agent_name="writer")
|
tmp_db.create_conversation("conv1", agent_name="writer")
|
||||||
|
self._add_msg(tmp_db, "conv1")
|
||||||
convs = tmp_db.list_conversations()
|
convs = tmp_db.list_conversations()
|
||||||
assert convs[0]["agent_name"] == "writer"
|
assert convs[0]["agent_name"] == "writer"
|
||||||
|
|
||||||
def test_list_filters_by_agent_name(self, tmp_db):
|
def test_list_filters_by_agent_name(self, tmp_db):
|
||||||
tmp_db.create_conversation("c1", agent_name="default")
|
for cid, agent in [("c1", "default"), ("c2", "writer"), ("c3", "default")]:
|
||||||
tmp_db.create_conversation("c2", agent_name="writer")
|
tmp_db.create_conversation(cid, agent_name=agent)
|
||||||
tmp_db.create_conversation("c3", agent_name="default")
|
self._add_msg(tmp_db, cid)
|
||||||
|
|
||||||
default_convs = tmp_db.list_conversations(agent_name="default")
|
default_convs = tmp_db.list_conversations(agent_name="default")
|
||||||
writer_convs = tmp_db.list_conversations(agent_name="writer")
|
writer_convs = tmp_db.list_conversations(agent_name="writer")
|
||||||
|
|
@ -35,14 +41,16 @@ class TestConversationsAgentName:
|
||||||
assert len(all_convs) == 3
|
assert len(all_convs) == 3
|
||||||
|
|
||||||
def test_list_without_filter_returns_all(self, tmp_db):
|
def test_list_without_filter_returns_all(self, tmp_db):
|
||||||
tmp_db.create_conversation("c1", agent_name="a")
|
for cid, agent in [("c1", "a"), ("c2", "b")]:
|
||||||
tmp_db.create_conversation("c2", agent_name="b")
|
tmp_db.create_conversation(cid, agent_name=agent)
|
||||||
|
self._add_msg(tmp_db, cid)
|
||||||
|
|
||||||
convs = tmp_db.list_conversations()
|
convs = tmp_db.list_conversations()
|
||||||
assert len(convs) == 2
|
assert len(convs) == 2
|
||||||
|
|
||||||
def test_list_returns_agent_name_in_results(self, tmp_db):
|
def test_list_returns_agent_name_in_results(self, tmp_db):
|
||||||
tmp_db.create_conversation("c1", agent_name="researcher")
|
tmp_db.create_conversation("c1", agent_name="researcher")
|
||||||
|
self._add_msg(tmp_db, "c1")
|
||||||
convs = tmp_db.list_conversations()
|
convs = tmp_db.list_conversations()
|
||||||
assert "agent_name" in convs[0]
|
assert "agent_name" in convs[0]
|
||||||
assert convs[0]["agent_name"] == "researcher"
|
assert convs[0]["agent_name"] == "researcher"
|
||||||
|
|
@ -52,6 +60,7 @@ class TestConversationsAgentName:
|
||||||
db_path = tmp_path / "test_migrate.db"
|
db_path = tmp_path / "test_migrate.db"
|
||||||
db1 = Database(db_path)
|
db1 = Database(db_path)
|
||||||
db1.create_conversation("c1", agent_name="ops")
|
db1.create_conversation("c1", agent_name="ops")
|
||||||
|
db1.add_message("c1", "user", "hello")
|
||||||
# Re-init on same DB file triggers migration again
|
# Re-init on same DB file triggers migration again
|
||||||
db2 = Database(db_path)
|
db2 = Database(db_path)
|
||||||
convs = db2.list_conversations()
|
convs = db2.list_conversations()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue