From dbfa1a3c7d302cb1f92f3855f3f852657781b168 Mon Sep 17 00:00:00 2001 From: haikdc Date: Fri, 17 Apr 2026 23:34:15 -0700 Subject: [PATCH] [Haik]: Fixed all pydantic instance attribute violations --- .vscode/settings.json | 17 +++++- .../OLDapps/agents/manager/agent_manager.py | 6 +- backend/OLDapps/analytics/analytics.py | 5 +- backend/OLDapps/mcp_registry/mcp_registry.py | 3 +- .../classes/FrontendBroadcaster.py | 3 +- .../COMMS_MANAGER/classes/FutureBridge.py | 4 +- backend/apps/modes/BUILTIN_MODES.py | 4 +- .../utils/fetch_one_skill.py | 3 +- .../utils/fetch_skill_paths.py | 3 +- .../subscriptions/NineRouter/NineRouter.py | 4 +- .../NineRouterClient/NineRouterClient.py | 4 +- .../NineRouterProcess/NineRouterProcess.py | 4 +- backend/core/Agent/Agent.py | 4 +- backend/core/events/events.py | 4 +- ports.config.json | 2 +- run/push.sh | 55 +++++++++++++++++++ 16 files changed, 101 insertions(+), 24 deletions(-) create mode 100644 run/push.sh diff --git a/.vscode/settings.json b/.vscode/settings.json index a95a04c0..f5b1af46 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,5 +11,20 @@ "python.analysis.diagnosticSeverityOverrides": { "reportMissingTypeStubs": "none" }, - "eslint.workingDirectories": ["frontend"] + "eslint.workingDirectories": [ + "frontend" + ], + "cursorpyright.analysis.diagnosticSeverityOverrides": { + "reportMissingTypeStubs": "none" + }, + "cursorpyright.analysis.exclude": [ + "backend/.venv", + "backend/uv-bin", + "backend/data", + "backend/tests" + ], + "cursorpyright.analysis.include": [ + "backend" + ], + "cursorpyright.analysis.typeCheckingMode": "strict" } diff --git a/backend/OLDapps/agents/manager/agent_manager.py b/backend/OLDapps/agents/manager/agent_manager.py index dafa950e..5dbe9f51 100644 --- a/backend/OLDapps/agents/manager/agent_manager.py +++ b/backend/OLDapps/agents/manager/agent_manager.py @@ -9,8 +9,6 @@ Heavy logic lives in sibling modules: - session_store – on-disk persistence, history, message copying """ -from __future__ import annotations - import asyncio import logging import os @@ -18,6 +16,8 @@ from datetime import datetime from typing import Optional from uuid import uuid4 +from pydantic import InstanceOf + from backend.apps.agents.models import AgentConfig, AgentSession, Message from backend.apps.agents.manager.ws_manager import ws_manager from backend.apps.agents.execution.prompt_builder import resolve_mode @@ -47,7 +47,7 @@ os.environ.setdefault("CLAUDE_CODE_STREAM_CLOSE_TIMEOUT", "3600000") class AgentManager: def __init__(self): self.sessions: dict[str, AgentSession] = {} - self.tasks: dict[str, asyncio.Task] = {} + self.tasks: dict[str, InstanceOf[asyncio.Task]] = {} async def launch_agent(self, config: AgentConfig) -> AgentSession: session_id = uuid4().hex diff --git a/backend/OLDapps/analytics/analytics.py b/backend/OLDapps/analytics/analytics.py index 3a4e330f..943bf686 100644 --- a/backend/OLDapps/analytics/analytics.py +++ b/backend/OLDapps/analytics/analytics.py @@ -5,6 +5,9 @@ import logging import platform from contextlib import asynccontextmanager from datetime import datetime +from typing import Optional + +from pydantic import InstanceOf from backend.config.Apps import SubApp from backend.apps.analytics.collector import init as init_collector, shutdown as shutdown_collector, record, identify @@ -22,7 +25,7 @@ logger = logging.getLogger(__name__) APP_VERSION = "1.0.20" -_heartbeat_task: asyncio.Task | None = None +_heartbeat_task: Optional[InstanceOf[asyncio.Task]] = None async def _heartbeat_loop(): diff --git a/backend/OLDapps/mcp_registry/mcp_registry.py b/backend/OLDapps/mcp_registry/mcp_registry.py index 31f3be10..ae830a04 100644 --- a/backend/OLDapps/mcp_registry/mcp_registry.py +++ b/backend/OLDapps/mcp_registry/mcp_registry.py @@ -7,6 +7,7 @@ import time from contextlib import asynccontextmanager from typing import Optional +from pydantic import InstanceOf import httpx from fastapi import Query from backend.config.Apps import SubApp @@ -24,7 +25,7 @@ GITHUB_CONCURRENT = 10 _cache: dict[str, dict] = {} _cache_updated_at: float = 0 -_refresh_task: Optional[asyncio.Task] = None +_refresh_task: Optional[InstanceOf[asyncio.Task]] = None _stars_cache: dict[str, int] = {} diff --git a/backend/apps/agents/COMMS_MANAGER/classes/FrontendBroadcaster.py b/backend/apps/agents/COMMS_MANAGER/classes/FrontendBroadcaster.py index c913061f..8e5e5ea6 100644 --- a/backend/apps/agents/COMMS_MANAGER/classes/FrontendBroadcaster.py +++ b/backend/apps/agents/COMMS_MANAGER/classes/FrontendBroadcaster.py @@ -3,12 +3,13 @@ from fastapi import WebSocket from typing import List from typeguard import typechecked from pydantic import BaseModel, Field +from pydantic import InstanceOf class FrontendBroadcaster(BaseModel): """Singleton WebSocket connection pool for broadcasting to dashboard clients.""" - p_connections: List[WebSocket] = Field(default_factory=list) + p_connections: List[InstanceOf[WebSocket]] = Field(default_factory=list) @typechecked async def connect(self, ws: WebSocket) -> None: diff --git a/backend/apps/agents/COMMS_MANAGER/classes/FutureBridge.py b/backend/apps/agents/COMMS_MANAGER/classes/FutureBridge.py index a531749a..c36c81cc 100644 --- a/backend/apps/agents/COMMS_MANAGER/classes/FutureBridge.py +++ b/backend/apps/agents/COMMS_MANAGER/classes/FutureBridge.py @@ -1,6 +1,6 @@ import asyncio from typing import Callable, Awaitable, Dict -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, InstanceOf from typeguard import typechecked class FutureBridge(BaseModel): @@ -10,7 +10,7 @@ class FutureBridge(BaseModel): block until the frontend responds (or timeout). """ - p_pending: Dict[str, asyncio.Future] = Field(default_factory=dict) + p_pending: Dict[str, InstanceOf[asyncio.Future]] = Field(default_factory=dict) # TODO: add better type specing for the output of this function @typechecked diff --git a/backend/apps/modes/BUILTIN_MODES.py b/backend/apps/modes/BUILTIN_MODES.py index 0bfc6689..3b6cfe35 100644 --- a/backend/apps/modes/BUILTIN_MODES.py +++ b/backend/apps/modes/BUILTIN_MODES.py @@ -6,7 +6,7 @@ Separated from models.py to keep schema classes small and data separate. from backend.apps.modes.Mode import Mode from typing import List from backend.config.paths import DB_ROOT -from backend.apps.app_builder.app_builder import APP_BUILDER_WORKSPACE_DIR +from backend.apps.app_builder.app_builder import APP_BUILDER_CONTENT_DIR import os SKILLS_WORKSPACE: str = os.path.join(DB_ROOT, "skills") @@ -75,7 +75,7 @@ BUILTIN_MODES: List[Mode] = [ is_builtin=True, icon="view_quilt", color="#f472b6", - default_folder=APP_BUILDER_WORKSPACE_DIR, + default_folder=APP_BUILDER_CONTENT_DIR, ), Mode( id="skill-builder", diff --git a/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_one_skill.py b/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_one_skill.py index 03656b39..1043e2d2 100644 --- a/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_one_skill.py +++ b/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_one_skill.py @@ -3,10 +3,11 @@ from typing import Optional import httpx from backend.apps.skills.parse_frontmatter import parse_frontmatter from typeguard import typechecked +from pydantic import InstanceOf @typechecked async def fetch_one_skill( - client: httpx.AsyncClient, + client: InstanceOf[httpx.AsyncClient], sem: asyncio.Semaphore, folder: str, plugin_name: str, diff --git a/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_skill_paths.py b/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_skill_paths.py index 4720c93c..5b0f6cb0 100644 --- a/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_skill_paths.py +++ b/backend/apps/skills/RegistryRefreshLoop/fetch_all_registry_skills/utils/fetch_skill_paths.py @@ -1,10 +1,11 @@ import httpx from typeguard import typechecked +from pydantic import InstanceOf @typechecked async def fetch_skill_paths( - client: httpx.AsyncClient, + client: InstanceOf[httpx.AsyncClient], manifest_url: str ) -> list[tuple[str, str]]: diff --git a/backend/apps/subscriptions/NineRouter/NineRouter.py b/backend/apps/subscriptions/NineRouter/NineRouter.py index 6352e5d9..78d7253a 100644 --- a/backend/apps/subscriptions/NineRouter/NineRouter.py +++ b/backend/apps/subscriptions/NineRouter/NineRouter.py @@ -8,7 +8,7 @@ ensure-task, HTTP client) lives in one place. import asyncio from typing import ClassVar, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, InstanceOf from typeguard import typechecked from backend.apps.subscriptions.NineRouter.helpers.NineRouterProcess.NineRouterProcess import NineRouterProcess @@ -19,7 +19,7 @@ class NineRouter(BaseModel): p_instance: ClassVar[Optional["NineRouter"]] = None p_process: NineRouterProcess = Field(default_factory=NineRouterProcess) p_client: NineRouterClient = Field(default_factory=NineRouterClient) - p_ensure_task: Optional[asyncio.Task] = None + p_ensure_task: Optional[InstanceOf[asyncio.Task]] = None @classmethod def get(cls) -> "NineRouter": diff --git a/backend/apps/subscriptions/NineRouter/helpers/NineRouterClient/NineRouterClient.py b/backend/apps/subscriptions/NineRouter/helpers/NineRouterClient/NineRouterClient.py index 05faacfd..0bd9a582 100644 --- a/backend/apps/subscriptions/NineRouter/helpers/NineRouterClient/NineRouterClient.py +++ b/backend/apps/subscriptions/NineRouter/helpers/NineRouterClient/NineRouterClient.py @@ -1,7 +1,7 @@ """HTTP client for 9Router's REST API.""" import httpx -from pydantic import Field, BaseModel +from pydantic import Field, BaseModel, InstanceOf from typeguard import typechecked from backend.apps.subscriptions.NineRouter.helpers.constants import NINE_ROUTER_API, NINE_ROUTER_V1 @@ -9,7 +9,7 @@ from backend.ports import NINE_ROUTER_PORT class NineRouterClient(BaseModel): - p_http: httpx.AsyncClient = Field(default_factory=httpx.AsyncClient(timeout=15.0)) + p_http: InstanceOf[httpx.AsyncClient] = Field(default_factory=lambda: httpx.AsyncClient(timeout=15.0)) async def aclose(self) -> None: await self.p_http.aclose() diff --git a/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/NineRouterProcess.py b/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/NineRouterProcess.py index dbef3a92..617a803a 100644 --- a/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/NineRouterProcess.py +++ b/backend/apps/subscriptions/NineRouter/helpers/NineRouterProcess/NineRouterProcess.py @@ -12,7 +12,7 @@ import threading from typing import Optional import httpx -from pydantic import Field, BaseModel +from pydantic import Field, BaseModel, InstanceOf from typeguard import typechecked from backend.ports import NINE_ROUTER_PORT @@ -25,7 +25,7 @@ P_THIS_DIR: str = os.path.dirname(os.path.abspath(__file__)) class NineRouterProcess(BaseModel): - p_process: Optional[subprocess.Popen] = Field(default=None) + p_process: Optional[InstanceOf[subprocess.Popen]] = Field(default=None) @typechecked def is_running(self) -> bool: diff --git a/backend/core/Agent/Agent.py b/backend/core/Agent/Agent.py index bc0fec8e..d4f0373c 100644 --- a/backend/core/Agent/Agent.py +++ b/backend/core/Agent/Agent.py @@ -43,7 +43,7 @@ class Agent(BaseModel): toolkit: Optional[Toolkit] = Field(default=None, exclude=True) on_event: Optional[EventCallback] = Field(default=None, exclude=True) - task: Optional[asyncio.Task] = None + task: Optional[InstanceOf[asyncio.Task]] = None lock: InstanceOf[asyncio.Lock] = Field(default_factory=asyncio.Lock) @typechecked @@ -96,7 +96,7 @@ class Agent(BaseModel): session_id=self.session_id, status="waiting_approval", )) - future: asyncio.Future = asyncio.get_event_loop().create_future() + future: InstanceOf[asyncio.Future] = asyncio.get_event_loop().create_future() try: await self.emit(ApprovalRequestEvent( session_id=self.session_id, diff --git a/backend/core/events/events.py b/backend/core/events/events.py index 42ac648c..ea4518d3 100644 --- a/backend/core/events/events.py +++ b/backend/core/events/events.py @@ -1,6 +1,6 @@ import asyncio -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, InstanceOf from typing import Annotated, Any, Dict, Literal, Optional, Union, Callable, Awaitable from backend.core.shared_structs.agent.Message.Message import AnyMessage @@ -74,7 +74,7 @@ class ApprovalRequestEvent(BaseModel): request_id: str tool_name: str tool_input: Dict[str, Any] - future: asyncio.Future = Field(exclude=True) + future: InstanceOf[asyncio.Future] = Field(exclude=True) AnyEvent = Annotated[ diff --git a/ports.config.json b/ports.config.json index f2bee490..d98eaec7 100644 --- a/ports.config.json +++ b/ports.config.json @@ -1,6 +1,6 @@ { "backend": { - "dev": 8324, + "dev": 8326, "prod": 8325 }, "frontend": { diff --git a/run/push.sh b/run/push.sh new file mode 100644 index 00000000..271ab0c1 --- /dev/null +++ b/run/push.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +set -euo pipefail + +# --- Args --- +if [ $# -ne 2 ]; then + echo -e "\033[1;31mError: exactly 2 arguments required\033[0m" + echo -e "\033[0;90mUsage: bash push.sh Name \"commit message\"\033[0m" + exit 1 +fi + +NAME="$1" +MESSAGE="$2" + +# --- Helpers --- +banner() { + local msg="$1" + local len=${#msg} + local border=$(printf '═%.0s' $(seq 1 $((len + 4)))) + echo "" + echo -e "\033[1;36m╔${border}╗\033[0m" + echo -e "\033[1;36m║ \033[1;33m${msg}\033[1;36m ║\033[0m" + echo -e "\033[1;36m╚${border}╝\033[0m" + echo "" +} + +gate() { + local prompt="${1:?gate requires a prompt argument}" + while true; do + read -rp $'\033[1;35m► '"${prompt}"$' [Y/n] \033[0m' yn + case "${yn:-Y}" in + [Yy]*) return 0 ;; + [Nn]*) echo -e "\033[1;31m✗ Aborted.\033[0m"; exit 1 ;; + *) echo "Please answer y or n." ;; + esac + done +} + +# --- Workflow --- +banner "git status" +git status +gate "Stage all changes?" + +banner "git add ." +git add . +git status +gate "Commit these changes?" + +banner "git commit -m \"[${NAME}]: ${MESSAGE}\"" +git commit -m "[${NAME}]: ${MESSAGE}" +gate "Push to remote?" + +banner "git push" +git push + +banner "All changes pushed successfully ✓" \ No newline at end of file