mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Co-authored-by: roboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com> Co-authored-by: Backend Developer 1 <be-dev-1@roboco.tech>
This commit is contained in:
co-authored by
roboco-app[bot] <302741806+roboco-app[bot]@users.noreply.github.com>
Backend Developer 1
parent
e1f5e0950e
commit
9f07183b01
@@ -107,3 +107,22 @@ waivers:
|
|||||||
not a production component — the same pattern already used by the
|
not a production component — the same pattern already used by the
|
||||||
pre-existing use-agents.test.tsx / use-observability.test.tsx hook
|
pre-existing use-agents.test.tsx / use-observability.test.tsx hook
|
||||||
tests colocated under hooks/__tests__/.
|
tests colocated under hooks/__tests__/.
|
||||||
|
- path: roboco/api/routes/prompter_live.py
|
||||||
|
rule: no_lint_suppressions
|
||||||
|
reason: >-
|
||||||
|
preview_live_batch's `session_id` path parameter is unused in the
|
||||||
|
function body (the endpoint is a pure precompute) but must stay in the
|
||||||
|
signature under that exact name for FastAPI to bind the
|
||||||
|
`/live/{session_id}/preview-batch` path — renaming or dropping it
|
||||||
|
breaks routing, so the ARG001 suppression is a permanent framework
|
||||||
|
constraint, not silenced debt.
|
||||||
|
- path: roboco/foundation/policy/lifecycle.py
|
||||||
|
rule: no_lint_suppressions
|
||||||
|
reason: >-
|
||||||
|
The module-load-time `_run_all_lifecycle_validators` import sits below
|
||||||
|
other module code deliberately: its validators live in
|
||||||
|
`roboco.foundation._validate_lifecycle`, a sibling of
|
||||||
|
`foundation/_validate.py`, and `roboco.foundation.__init__` eagerly
|
||||||
|
imports `_validate` — importing it at the top of this file would
|
||||||
|
create a real import cycle. The E402 suppression documents a genuine
|
||||||
|
circular-import constraint, not silenced debt.
|
||||||
|
|||||||
+1
-2
@@ -8,6 +8,7 @@ for production and colored console output for development.
|
|||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
|
import re as _re
|
||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping, MutableMapping
|
from collections.abc import Mapping, MutableMapping
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -39,8 +40,6 @@ def add_app_context(
|
|||||||
# `ghs_`/`gho_`) plus bearer tokens and generic "key = <value>" shapes. The
|
# `ghs_`/`gho_`) plus bearer tokens and generic "key = <value>" shapes. The
|
||||||
# regex is intentionally loose on length so it catches tokens regardless of
|
# regex is intentionally loose on length so it catches tokens regardless of
|
||||||
# future format tweaks.
|
# future format tweaks.
|
||||||
import re as _re # noqa: E402 — module-local alias only
|
|
||||||
|
|
||||||
_SECRET_PATTERNS: list[_re.Pattern[str]] = [
|
_SECRET_PATTERNS: list[_re.Pattern[str]] = [
|
||||||
_re.compile(r"(github_pat_[A-Za-z0-9_]{20,})"),
|
_re.compile(r"(github_pat_[A-Za-z0-9_]{20,})"),
|
||||||
_re.compile(r"(ghp_[A-Za-z0-9_]{20,})"),
|
_re.compile(r"(ghp_[A-Za-z0-9_]{20,})"),
|
||||||
|
|||||||
@@ -54,7 +54,10 @@ class JournalEntry(TimestampMixin):
|
|||||||
# Sentiment/mood tracking (for growth analysis)
|
# Sentiment/mood tracking (for growth analysis)
|
||||||
sentiment: str | None = Field(
|
sentiment: str | None = Field(
|
||||||
default=None,
|
default=None,
|
||||||
description="Sentiment indicator (positive, neutral, negative, frustrated, confident, etc.)", # noqa: E501
|
description=(
|
||||||
|
"Sentiment indicator (positive, neutral, negative, frustrated, "
|
||||||
|
"confident, etc.)"
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Visibility
|
# Visibility
|
||||||
|
|||||||
@@ -10940,12 +10940,13 @@ Start by:
|
|||||||
from roboco.utils.converters import require_uuid
|
from roboco.utils.converters import require_uuid
|
||||||
|
|
||||||
# Compute human-friendly duration
|
# Compute human-friendly duration
|
||||||
|
_MINUTES_PER_HOUR = 60
|
||||||
duration_desc = "unknown duration"
|
duration_desc = "unknown duration"
|
||||||
try:
|
try:
|
||||||
activated_at = datetime.fromisoformat(activated_at_str)
|
activated_at = datetime.fromisoformat(activated_at_str)
|
||||||
elapsed = datetime.now(UTC) - activated_at
|
elapsed = datetime.now(UTC) - activated_at
|
||||||
total_minutes = int(elapsed.total_seconds() / 60)
|
total_minutes = int(elapsed.total_seconds() / 60)
|
||||||
if total_minutes < 60: # noqa: PLR2004
|
if total_minutes < _MINUTES_PER_HOUR:
|
||||||
duration_desc = f"{total_minutes} minute(s)"
|
duration_desc = f"{total_minutes} minute(s)"
|
||||||
else:
|
else:
|
||||||
duration_desc = f"{total_minutes // 60}h {total_minutes % 60}m"
|
duration_desc = f"{total_minutes // 60}h {total_minutes % 60}m"
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ class KanbanService(BaseService):
|
|||||||
|
|
||||||
return KanbanBoard(
|
return KanbanBoard(
|
||||||
id=f"{board_type.value}-{team.value if team else 'all'}",
|
id=f"{board_type.value}-{team.value if team else 'all'}",
|
||||||
title=f"{team.value.title() if team else 'All'} {board_type.value.title()} Board", # noqa: E501
|
title=self._board_title(team, board_type),
|
||||||
board_type=board_type,
|
board_type=board_type,
|
||||||
team=team,
|
team=team,
|
||||||
columns=column_list,
|
columns=column_list,
|
||||||
@@ -207,6 +207,11 @@ class KanbanService(BaseService):
|
|||||||
last_updated=datetime.now(UTC),
|
last_updated=datetime.now(UTC),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _board_title(self, team: Team | None, board_type: KanbanBoardType) -> str:
|
||||||
|
"""Shared board title: '<Team|All> <BoardType> Board'."""
|
||||||
|
team_label = team.value.title() if team else "All"
|
||||||
|
return f"{team_label} {board_type.value.title()} Board"
|
||||||
|
|
||||||
def _get_swimlane_key(self, task: TaskTable, swimlane_by: str) -> str:
|
def _get_swimlane_key(self, task: TaskTable, swimlane_by: str) -> str:
|
||||||
"""Get the swimlane key for a task."""
|
"""Get the swimlane key for a task."""
|
||||||
if swimlane_by == "priority":
|
if swimlane_by == "priority":
|
||||||
@@ -319,7 +324,7 @@ class KanbanService(BaseService):
|
|||||||
|
|
||||||
return KanbanBoard(
|
return KanbanBoard(
|
||||||
id=f"{board_type.value}-{team.value if team else 'all'}-swimlane",
|
id=f"{board_type.value}-{team.value if team else 'all'}-swimlane",
|
||||||
title=f"{team.value.title() if team else 'All'} {board_type.value.title()} Board", # noqa: E501
|
title=self._board_title(team, board_type),
|
||||||
board_type=board_type,
|
board_type=board_type,
|
||||||
team=team,
|
team=team,
|
||||||
swimlanes=swimlanes,
|
swimlanes=swimlanes,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from sqlalchemy import select
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from roboco.foundation.identity import PM_ROLES
|
||||||
from roboco.foundation.identity import Role as _FoundationRole
|
from roboco.foundation.identity import Role as _FoundationRole
|
||||||
from roboco.foundation.policy.communications import NOTIFY_SENDER_ROLES
|
from roboco.foundation.policy.communications import NOTIFY_SENDER_ROLES
|
||||||
from roboco.models import AgentRole, Team
|
from roboco.models import AgentRole, Team
|
||||||
@@ -41,6 +42,11 @@ from roboco.models.permissions import (
|
|||||||
)
|
)
|
||||||
from roboco.services.base import SingletonService
|
from roboco.services.base import SingletonService
|
||||||
|
|
||||||
|
# PM_ROLES is canonical in foundation.identity; re-exported here for
|
||||||
|
# backwards compatibility with existing `from roboco.services.permissions
|
||||||
|
# import PM_ROLES` consumers.
|
||||||
|
__all__ = ["PM_ROLES"]
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# NOTIFICATION PERMISSIONS (derived from foundation.NOTIFY_SENDER_ROLES)
|
# NOTIFICATION PERMISSIONS (derived from foundation.NOTIFY_SENDER_ROLES)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -264,10 +270,6 @@ async def has_privileged_access(db: "AsyncSession", agent_id: UUID) -> bool:
|
|||||||
return role in PRIVILEGED_ROLES if role else False
|
return role in PRIVILEGED_ROLES if role else False
|
||||||
|
|
||||||
|
|
||||||
# PM_ROLES is canonical in foundation.identity. Re-export for backwards
|
|
||||||
# compatibility; new consumers import from foundation directly.
|
|
||||||
from roboco.foundation.identity import PM_ROLES # noqa: F401, E402
|
|
||||||
|
|
||||||
MANAGEMENT_ROLES = frozenset(
|
MANAGEMENT_ROLES = frozenset(
|
||||||
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.CELL_PM, AgentRole.MAIN_PM}
|
{AgentRole.CEO, AgentRole.PRODUCT_OWNER, AgentRole.CELL_PM, AgentRole.MAIN_PM}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -157,9 +157,12 @@ class TgCockpitService(BaseService):
|
|||||||
tokens_by_day,
|
tokens_by_day,
|
||||||
models_by_day,
|
models_by_day,
|
||||||
) = await self._session_metrics_by_day(days)
|
) = await self._session_metrics_by_day(days)
|
||||||
|
_MIN_DAYS_FOR_PRIOR_COMPARISON = 2
|
||||||
series = [round(cost_by_day.get(d, 0.0), 4) for d in days]
|
series = [round(cost_by_day.get(d, 0.0), 4) for d in days]
|
||||||
today_cost = series[-1] if series else 0.0
|
today_cost = series[-1] if series else 0.0
|
||||||
prior_cost = series[-2] if len(series) >= 2 else 0.0 # noqa: PLR2004
|
prior_cost = (
|
||||||
|
series[-2] if len(series) >= _MIN_DAYS_FOR_PRIOR_COMPARISON else 0.0
|
||||||
|
)
|
||||||
today_tokens = tokens_by_day.get(days[-1], 0) if days else 0
|
today_tokens = tokens_by_day.get(days[-1], 0) if days else 0
|
||||||
today_models = models_by_day.get(days[-1], set()) if days else set()
|
today_models = models_by_day.get(days[-1], set()) if days else set()
|
||||||
# $0 with real tokens spent, on a subscription-billed-but-
|
# $0 with real tokens spent, on a subscription-billed-but-
|
||||||
|
|||||||
Reference in New Issue
Block a user