diff --git a/cheddahbot/clickup.py b/cheddahbot/clickup.py index 51e95b9..38c93d8 100644 --- a/cheddahbot/clickup.py +++ b/cheddahbot/clickup.py @@ -3,6 +3,7 @@ from __future__ import annotations import logging +import time from dataclasses import dataclass, field from pathlib import Path from typing import Any @@ -144,30 +145,58 @@ class ClickUpClient: log.info("Found %d tasks across %d lists in space %s", len(all_tasks), len(list_ids), space_id) return all_tasks - # ── Write ── + # ── Write (with retry) ── + + @staticmethod + def _retry(fn, max_attempts: int = 3, backoff: float = 2.0): + """Call *fn* up to *max_attempts* times with exponential backoff. + + Returns the result of *fn* on success, or re-raises the last exception. + Only retries on network/transport errors and 5xx status errors. + """ + last_exc: Exception | None = None + for attempt in range(1, max_attempts + 1): + try: + return fn() + except (httpx.TransportError, httpx.HTTPStatusError) as e: + # Don't retry 4xx client errors (bad request, auth, not found, etc.) + if isinstance(e, httpx.HTTPStatusError) and e.response.status_code < 500: + raise + last_exc = e + if attempt < max_attempts: + wait = backoff ** attempt + log.warning("Retry %d/%d after %.1fs: %s", attempt, max_attempts, wait, e) + time.sleep(wait) + raise last_exc def update_task_status(self, task_id: str, status: str) -> bool: """Update a task's status.""" try: - resp = self._client.put(f"/task/{task_id}", json={"status": status}) - resp.raise_for_status() + def _call(): + resp = self._client.put(f"/task/{task_id}", json={"status": status}) + resp.raise_for_status() + return resp + self._retry(_call) log.info("Updated task %s status to '%s'", task_id, status) return True - except httpx.HTTPStatusError as e: + except (httpx.TransportError, httpx.HTTPStatusError) as e: log.error("Failed to update task %s status: %s", task_id, e) return False def add_comment(self, task_id: str, text: str) -> bool: """Add a comment to a task.""" try: - resp = self._client.post( - f"/task/{task_id}/comment", - json={"comment_text": text}, - ) - resp.raise_for_status() + def _call(): + resp = self._client.post( + f"/task/{task_id}/comment", + json={"comment_text": text}, + ) + resp.raise_for_status() + return resp + self._retry(_call) log.info("Added comment to task %s", task_id) return True - except httpx.HTTPStatusError as e: + except (httpx.TransportError, httpx.HTTPStatusError) as e: log.error("Failed to add comment to task %s: %s", task_id, e) return False @@ -183,17 +212,20 @@ class ClickUpClient: log.warning("Attachment file not found: %s", fp) return False try: - with open(fp, "rb") as f: - resp = httpx.post( - f"{BASE_URL}/task/{task_id}/attachment", - headers={"Authorization": self._token}, - files={"attachment": (fp.name, f, "application/octet-stream")}, - timeout=60.0, - ) - resp.raise_for_status() + def _call(): + with open(fp, "rb") as f: + resp = httpx.post( + f"{BASE_URL}/task/{task_id}/attachment", + headers={"Authorization": self._token}, + files={"attachment": (fp.name, f, "application/octet-stream")}, + timeout=60.0, + ) + resp.raise_for_status() + return resp + self._retry(_call) log.info("Uploaded attachment %s to task %s", fp.name, task_id) return True - except httpx.HTTPStatusError as e: + except (httpx.TransportError, httpx.HTTPStatusError) as e: log.warning("Failed to upload attachment to task %s: %s", task_id, e) return False diff --git a/cheddahbot/config.py b/cheddahbot/config.py index 7694ec6..fd622ab 100644 --- a/cheddahbot/config.py +++ b/cheddahbot/config.py @@ -40,7 +40,7 @@ class ClickUpConfig: space_id: str = "" poll_interval_minutes: int = 20 poll_statuses: list[str] = field(default_factory=lambda: ["to do"]) - review_status: str = "review" + review_status: str = "internal review" in_progress_status: str = "in progress" task_type_field_name: str = "Work Category" default_auto_execute: bool = False diff --git a/config.yaml b/config.yaml index b818620..566081b 100644 --- a/config.yaml +++ b/config.yaml @@ -43,7 +43,7 @@ email: clickup: poll_interval_minutes: 20 # 3x per hour poll_statuses: ["to do"] - review_status: "review" + review_status: "internal review" in_progress_status: "in progress" task_type_field_name: "Work Category" default_auto_execute: false diff --git a/identity/SOUL.md b/identity/SOUL.md index 4c66a86..294a9f2 100644 --- a/identity/SOUL.md +++ b/identity/SOUL.md @@ -1,11 +1,12 @@ # Soul -You are Cheddah, a sharp and resourceful AI assistant. +You are Cheddah, a sharp and resourceful AI assistant. You are built on the avatar of Winston Churchill. Smart, confident, but friendly. ## Personality - Direct, no-nonsense, but warm - You use humor when appropriate -- You're proactive - suggest things before being asked +- You're proactive - suggest things before being asked. Especially as you begin to notice patterns in my behavior and schedule you can recommend items that fit. +- You are a good designer (css/html/js) and Bryan is not so you should help him when you can. If we don't have a designer skill installed you should go find a few and recommend one or two to include that you can use. - You remember what the user tells you and reference it naturally - You adapt your communication style to match the user's preferences @@ -16,5 +17,6 @@ You are Cheddah, a sharp and resourceful AI assistant. - Ask for clarification rather than guessing on important decisions ## Quirks -- You occasionally use the word "cheddah" as slang for money/value +- You like to open converstions with random trivia facts - but only once at the begining per chat. +- You may use puns or quotes from Winston Churchill that fit the situation - but not too forced. - You appreciate efficiency and elegant solutions diff --git a/identity/USER.md b/identity/USER.md index 54ea41a..527eeda 100644 --- a/identity/USER.md +++ b/identity/USER.md @@ -1,16 +1,19 @@ # User Profile ## Identity -- Name: (your name here) -- How to address: (first name, nickname, etc.) +- Name: Bryan +- How to address: Bryan or Yan - Origin: Cheddah is named after the user's Xbox Live gamertag, "CheddahYetti." - Fun Fact: The name is a nod to living in Wisconsin and the user being a "big guy." ## Context -- Technical level: (beginner/intermediate/advanced) +- Technical level: (advanced in Python) - Primary language: Python - Working on: (current projects) + ## Preferences -- Communication style: (concise/detailed) -- (anything else you want the agent to know) +- Communication style: Likes to know what is going on before giving the go-ahead, but once he understands is willing to let the agents go to work without any input. +- If he says he is going to bed or going away for awhile, you can ask if we can run in unattended mode - he'll usually say yes. +- Simple code is better - he's the only guy in the shop so dont do code like it's for an enterprise. Simple and maintainable is important. +- He needs help with organization, and he really needs help keeping project documentation up to date. Do that for him.