Files
roboco/roboco/api/app.py
T

702 lines
27 KiB
Python
Raw Normal View History

2025-12-10 02:49:54 +01:00
"""
FastAPI Application Factory
Creates and configures the FastAPI application with all routes,
middleware, and event handlers.
"""
import asyncio
2025-12-10 02:49:54 +01:00
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from roboco.api.auth.routes import mount_cloud_auth
from roboco.api.auth.seed import ensure_seed_user_startup
2026-06-29 05:38:21 +02:00
from roboco.api.deps import _auth_required, get_orchestrator_or_none
from roboco.api.middleware import setup_middleware
from roboco.api.routes.a2a import router as a2a_router
from roboco.api.routes.a2a import wellknown_router as a2a_wellknown_router
from roboco.api.routes.agents import router as agents_router
from roboco.api.routes.board_programs import router as board_programs_router
from roboco.api.routes.cockpit import router as cockpit_router
from roboco.api.routes.company_goals import router as company_goals_router
from roboco.api.routes.coroner import router as coroner_router
2025-12-12 02:45:47 +01:00
from roboco.api.routes.dashboard import router as dashboard_router
from roboco.api.routes.docs import router as docs_router
from roboco.api.routes.dogfood import router as dogfood_router
from roboco.api.routes.git import router as git_router
from roboco.api.routes.github_app import router as github_app_router
2025-12-12 02:45:47 +01:00
from roboco.api.routes.health import router as health_router
from roboco.api.routes.journals import router as journals_router
from roboco.api.routes.kanban import router as kanban_router
from roboco.api.routes.mirror import router as mirror_router
2025-12-12 02:45:47 +01:00
from roboco.api.routes.notifications import router as notifications_router
from roboco.api.routes.optimal import router as optimal_router
from roboco.api.routes.orchestrator import router as orchestrator_router
from roboco.api.routes.periscope import router as periscope_router
from roboco.api.routes.pest_control import router as pest_control_router
from roboco.api.routes.pitch import router as pitch_router
2026-06-26 01:43:08 +02:00
from roboco.api.routes.playbooks import router as playbooks_router
2026-06-03 06:35:03 +02:00
from roboco.api.routes.product import router as product_router
from roboco.api.routes.project import router as project_router
2026-06-09 17:08:34 +02:00
from roboco.api.routes.prompter_live import router as prompter_live_router
from roboco.api.routes.provider import router as provider_router
2026-06-26 01:43:08 +02:00
from roboco.api.routes.release import router as release_router
from roboco.api.routes.research import router as research_router
from roboco.api.routes.roadmap import router as roadmap_router
from roboco.api.routes.scales import router as scales_router
from roboco.api.routes.secretary import router as secretary_router
from roboco.api.routes.secretary_live import router as secretary_live_router
from roboco.api.routes.sentinel import router as sentinel_router
2026-06-12 23:11:01 +02:00
from roboco.api.routes.settings import router as settings_router
from roboco.api.routes.spackle import router as spackle_router
2025-12-12 02:45:47 +01:00
from roboco.api.routes.stream import router as stream_router
from roboco.api.routes.system import router as system_router
2025-12-12 02:45:47 +01:00
from roboco.api.routes.tasks import router as tasks_router
from roboco.api.routes.telegram import mount_telegram_miniapp_auth
from roboco.api.routes.telegram import router as telegram_router
from roboco.api.routes.usage import router as usage_router
2026-06-03 06:35:03 +02:00
from roboco.api.routes.v1 import do as do_module
from roboco.api.routes.v1 import flow_auditor as flow_auditor_module
from roboco.api.routes.v1 import flow_board as flow_board_module
from roboco.api.routes.v1 import flow_cell_pm as flow_cell_pm_module
from roboco.api.routes.v1 import flow_dev as flow_dev_module
from roboco.api.routes.v1 import flow_doc as flow_doc_module
from roboco.api.routes.v1 import flow_main_pm as flow_main_pm_module
from roboco.api.routes.v1 import flow_pr_reviewer as flow_pr_reviewer_module
2026-06-03 06:35:03 +02:00
from roboco.api.routes.v1 import flow_qa as flow_qa_module
from roboco.api.routes.video import router as video_router
from roboco.api.routes.video import tiktok_router
from roboco.api.routes.work_session import router as work_session_router
from roboco.api.routes.x import router as x_router
2025-12-12 02:45:47 +01:00
from roboco.api.websocket import router as ws_router
2025-12-10 02:49:54 +01:00
from roboco.config import settings
2026-06-17 16:35:43 +02:00
from roboco.db.base import close_db, get_session_factory, init_db
from roboco.logging import get_logger, setup_logging
from roboco.security import apply_guard, guarded_lifespan
from roboco.services.extraction import ExtractionPipeline, ExtractionService
from roboco.services.learning import get_learning_service
from roboco.services.optimal import close_optimal_service, get_optimal_service
from roboco.services.playbook import PlaybookService
from roboco.services.rag_index_failures import backfill_unindexed_journals, reclaim_due
2026-06-17 16:35:43 +02:00
from roboco.services.settings import apply_persisted_feature_flags
2025-12-10 02:49:54 +01:00
from roboco.services.transcription import TranscriptionService
# Setup logging before anything else
setup_logging()
logger = get_logger(__name__)
2025-12-12 02:45:47 +01:00
class _AppServices:
"""Holder for application service instances (initialized in lifespan)."""
transcription: TranscriptionService | None = None
extraction: ExtractionPipeline | None = None
2025-12-10 02:49:54 +01:00
async def _reconcile_unindexed_playbooks(app: FastAPI) -> None:
"""Re-index APPROVED playbooks left ``indexed_ok=False`` by a failed
post-commit embed (e.g. an Ollama restart mid-approval-burst). Best-effort:
a failure here never blocks startup — rows stay unindexed and the next
startup retries them. Skipped when RAG is disabled (no optimal) or the
org-memory loop is off (the index is inert).
"""
if app.state.optimal is None or not settings.org_memory_enabled:
return
try:
async with get_session_factory()() as session:
svc = PlaybookService(session)
reconciled = await svc.reconcile_unindexed_approved()
if reconciled:
logger.info(
"Playbook reconcile: re-indexed unindexed approved",
count=reconciled,
)
except Exception as e:
logger.warning("Playbook reconcile failed; continuing", error=str(e))
async def _reclaim_rag_index_failures(app: FastAPI) -> None:
"""Reclaim dead-lettered RAG index writes (embedder 429 after retries, etc.).
Best-effort: a failure here never blocks startup — due rows stay in the
dead-letter and the next startup retries them. Skipped when RAG is
disabled (no optimal).
"""
if app.state.optimal is None:
return
try:
reclaimed = await reclaim_due(app.state.optimal)
if reclaimed:
logger.info(
"RAG index dead-letter reclaim: re-indexed rows",
count=reclaimed,
)
except Exception as e:
logger.warning("RAG index dead-letter reclaim failed; continuing", error=str(e))
async def _backfill_unindexed_journals(app: FastAPI) -> None:
"""Re-index journal/learning entries silently zero-chunked before the
per-index chunk-floor fix (see ``backfill_unindexed_journals``'s
docstring). Best-effort: a failure here never blocks startup — the rows
stay and the next startup retries them. Skipped when RAG is disabled.
"""
if app.state.optimal is None:
return
try:
await backfill_unindexed_journals(app.state.optimal)
except Exception as e:
logger.warning("Journal/learning RAG backfill failed; continuing", error=str(e))
async def _reconcile_rag_indexes(app: FastAPI) -> None:
"""Run all RAG index reconcile passes: playbooks, dead-letter reclaim,
and the journals/learnings zero-chunk backfill."""
await _reconcile_unindexed_playbooks(app)
await _reclaim_rag_index_failures(app)
await _backfill_unindexed_journals(app)
logger.info("RAG index reconcile finished")
def _log_reconcile_outcome(task: asyncio.Task[None]) -> None:
"""Surface a background-reconcile crash; each pass already swallows its
own errors, so anything landing here is an unexpected bug, not a retry."""
if task.cancelled():
return
exc = task.exception()
if exc is not None:
logger.error("Background RAG reconcile crashed", error=repr(exc))
def _schedule_rag_reconcile(app: FastAPI) -> asyncio.Task[None]:
"""Schedule the reconcile without awaiting it (see lifespan comment)."""
task = asyncio.create_task(_reconcile_rag_indexes(app))
task.add_done_callback(_log_reconcile_outcome)
app.state.rag_reconcile_task = task
return task
def _cancel_rag_reconcile(app: FastAPI) -> None:
"""Stop a still-running background reconcile at shutdown."""
task = getattr(app.state, "rag_reconcile_task", None)
if task is not None and not task.done():
task.cancel()
async def _apply_flag_overrides() -> None:
"""Overlay panel-persisted feature-flag overrides onto the live config so
the rest of startup (and the dispatch loops) read the panel's choices;
unset flags keep their env/config default. Best-effort — a failure here
must not block startup, the env defaults still apply."""
try:
async with get_session_factory()() as flags_db:
applied_flags = await apply_persisted_feature_flags(flags_db)
if applied_flags:
logger.info("Applied persisted feature-flag overrides", flags=applied_flags)
except Exception as e:
logger.warning("Feature-flag overlay failed; using env defaults", error=str(e))
2025-12-10 02:49:54 +01:00
@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None]:
2025-12-10 02:49:54 +01:00
"""
Application lifespan manager.
Handles startup and shutdown events.
"""
logger.info(
"Starting RoboCo API",
version=settings.app_version,
environment=settings.environment,
)
2026-06-05 16:35:22 +02:00
if not _auth_required():
logger.warning(
"Agent auth is in HEADER-TRUST mode (ROBOCO_AGENT_AUTH_REQUIRED is "
"not set to true): the API accepts X-Agent-Id / X-Agent-Role without "
"verifying a signed token, so any client that can reach it may act as "
"any role, including 'ceo'. Acceptable only on a trusted private "
"network. Set ROBOCO_AGENT_AUTH_REQUIRED=true and do NOT expose this "
"API to untrusted networks.",
)
2026-04-20 15:10:54 +02:00
# Startup: apply Alembic migrations (+ create_all fallback for fresh DBs).
# init_db runs on every environment now — migrations are idempotent via
# alembic_version, and this is the only way new schema (e.g. enum value
# additions like NotificationType.APPROVAL) reaches the running DB.
await init_db()
logger.info("Database initialized")
2025-12-10 02:49:54 +01:00
# Cloud auth: idempotently upsert the single seeded CEO login user.
# No-op unless ROBOCO_CLOUD_AUTH_ENABLED (see ensure_seed_user_startup).
await ensure_seed_user_startup()
await _apply_flag_overrides()
2026-06-17 16:35:43 +02:00
2025-12-10 02:49:54 +01:00
# Initialize Phase 2 services
2025-12-12 02:45:47 +01:00
_AppServices.transcription = TranscriptionService()
await _AppServices.transcription.start()
2025-12-10 02:49:54 +01:00
extraction_service = ExtractionService()
2025-12-12 02:45:47 +01:00
_AppServices.extraction = ExtractionPipeline(extraction_service)
2025-12-10 02:49:54 +01:00
# Store in app state for access in routes
2025-12-12 02:45:47 +01:00
app.state.transcription = _AppServices.transcription
app.state.extraction = _AppServices.extraction
2025-12-10 02:49:54 +01:00
2026-01-01 22:15:23 +01:00
# Initialize OptimalService (RAG) - BLOCKS until fully ready
# This ensures /health only returns 200 when RAG is operational
# Typical initialization time: 30-90 seconds (embedding + indexing)
try:
2026-01-01 22:15:23 +01:00
logger.info("Initializing OptimalService (RAG)...")
optimal_service = await get_optimal_service()
app.state.optimal = optimal_service
logger.info("OptimalService (RAG) initialized successfully")
except Exception as e:
logger.warning(
"OptimalService (RAG) initialization failed - RAG features disabled",
error=str(e),
)
app.state.optimal = None
2025-12-10 02:49:54 +01:00
# Wire the learning-propagation singleton to OptimalService. Without this,
# record_learning() raises "not initialized" and every task completion logs
# "Failed to extract learnings". Skipped when RAG is disabled (no optimal).
if app.state.optimal is not None:
try:
learning_service = await get_learning_service()
await learning_service.initialize(app.state.optimal)
logger.info("LearningPropagationService initialized")
except Exception as e:
logger.warning("LearningPropagationService init failed", error=str(e))
# Reconcile RAG index state in the BACKGROUND: re-index APPROVED playbooks
# left unindexed by a failed post-commit embed, reclaim dead-lettered
# index writes, and backfill zero-chunk journals/learnings. Never awaited
# here — uvicorn binds the socket only after this lifespan completes, and
# a 200-entry backfill behind a busy Ollama held the API down for 30+
# minutes when this was a blocking await (2026-07-09 deploy).
_schedule_rag_reconcile(app)
2026-01-01 22:15:23 +01:00
logger.info("All services initialized, API ready")
2025-12-10 02:49:54 +01:00
yield
# Shutdown
logger.info("Shutting down RoboCo API")
_cancel_rag_reconcile(app)
2025-12-12 02:45:47 +01:00
if _AppServices.transcription:
await _AppServices.transcription.stop()
2025-12-10 02:49:54 +01:00
2026-06-29 05:38:21 +02:00
# Stop the orchestrator BEFORE closing the DB / OptimalService. stop()
# cancels the background loops, stops the agents (finalizing work sessions
# + agent state via DB writes), and drains fire-and-forget bg writes
# (respawn_tracker upserts, audit-log rows) — all needing the DB still
# open. Closing the DB first silently dropped those final writes (the old
# order, where only bootstrap's finally block called stop() AFTER lifespan
# had already closed the DB). Best-effort: a stop error must not block the
# resource teardown below. No-op when no orchestrator is wired (tests,
# skip_orchestrator). bootstrap's finally block re-calls stop() as a safety
# net; stop() is idempotent (guarded by the _stopped flag) so the second
# call is a no-op.
orchestrator = get_orchestrator_or_none()
if orchestrator is not None:
try:
await orchestrator.stop()
except Exception as e:
logger.warning("Orchestrator stop failed during shutdown", error=str(e))
2025-12-10 02:49:54 +01:00
# Close Phase 3 services
await close_optimal_service()
await close_db()
logger.info("Shutdown complete")
def _mount_v1_routers(app: FastAPI) -> None:
"""Mount every API v1 (intent-verb + content-tool) router."""
app.include_router(flow_dev_module.router)
app.include_router(flow_qa_module.router)
app.include_router(flow_doc_module.router)
app.include_router(flow_cell_pm_module.router)
app.include_router(flow_main_pm_module.router)
app.include_router(flow_board_module.router)
app.include_router(flow_auditor_module.router)
app.include_router(flow_pr_reviewer_module.router)
app.include_router(do_module.router)
def _mount_board_program_routers(app: FastAPI, api_prefix: str) -> None:
"""Mount every Board Program route — the generic registry route plus each
program's own per-item / read-only surface. Grouped into one helper
(mirrors ``_mount_v1_routers``) to keep ``create_app``'s own statement
count from growing unbounded as programs are added."""
# Board roadmap engine — the CEO approves/rejects items within a held
# roadmap cycle. Approving materializes a BACKLOG task; nothing auto-starts.
app.include_router(roadmap_router, prefix=f"{api_prefix}/roadmap", tags=["Roadmap"])
# Board Programs — the generic registry status + off-schedule "run now"
# (roadmap + x_feature today; every later program rides the same route).
app.include_router(
board_programs_router,
prefix=f"{api_prefix}/board-programs",
tags=["Board Programs"],
)
# Pest Control (Board Program) — the CEO approves/rejects items within a
# held bug-hunt cycle. Approving materializes a BACKLOG task; nothing
# auto-starts.
app.include_router(
pest_control_router,
prefix=f"{api_prefix}/pest-control",
tags=["Pest Control"],
)
# Periscope (Board Program) — the CEO reads filed market-research briefs.
# Read-only: a brief is a report, not a queue item; nothing to approve.
app.include_router(
periscope_router, prefix=f"{api_prefix}/periscope", tags=["Periscope"]
)
# Coroner (Board Program) — read-only Postmortems list. A postmortem
# completes atomically at propose_postmortem time; there is nothing here
# for the CEO to approve/reject.
app.include_router(coroner_router, prefix=f"{api_prefix}/coroner", tags=["Coroner"])
# Sentinel (Board Program) — the CEO reads filed org-wide quality-drift
# reports. Read-only: a report is a report, not a queue item; nothing to
# approve.
app.include_router(
sentinel_router, prefix=f"{api_prefix}/sentinel", tags=["Sentinel"]
)
# Spackle (Board Program) — the CEO approves/rejects items within a held
# gap-fill cycle. Approving materializes a BACKLOG task; nothing
# auto-starts.
app.include_router(spackle_router, prefix=f"{api_prefix}/spackle", tags=["Spackle"])
# Scales (Board Program) — the CEO approves/rejects items within a held
# portfolio-rebalance cycle. Approving EXECUTES the item against the live
# target task (reprioritize or cancel); nothing here creates a task.
app.include_router(scales_router, prefix=f"{api_prefix}/scales", tags=["Scales"])
# Mirror (Board Program) — the CEO approves/rejects items within a held
# messaging-fixes cycle. Approving materializes a BACKLOG docs task;
# nothing auto-starts.
app.include_router(mirror_router, prefix=f"{api_prefix}/mirror", tags=["Mirror"])
# Dogfood (Board Program) — the CEO approves/rejects items within a held
# friction-fix cycle. Approving materializes a BACKLOG task; nothing
# auto-starts.
app.include_router(dogfood_router, prefix=f"{api_prefix}/dogfood", tags=["Dogfood"])
2025-12-10 02:49:54 +01:00
def create_app() -> FastAPI:
"""
Create and configure the FastAPI application.
Returns:
Configured FastAPI application instance.
"""
app = FastAPI(
title="RoboCo API",
description="AI Agents Company - Messaging and Task Management API",
version=settings.app_version,
2025-12-18 03:29:08 +01:00
docs_url="/docs", # if settings.debug else None,
redoc_url="/redoc", # if settings.debug else None,
# Wraps the existing lifespan with fastapi-guard's when armed (drives the
# middleware's redis/geo/agent init); returns it unchanged when off.
lifespan=guarded_lifespan(lifespan),
2025-12-10 02:49:54 +01:00
)
# ==========================================================================
# Middleware
# ==========================================================================
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=settings.cors_allow_credentials,
allow_methods=["*"],
allow_headers=["*"],
)
# Setup custom middleware (error handling, logging, correlation IDs)
setup_middleware(app)
# fastapi-guard HTTP security layer — no-op unless ROBOCO_GUARD_ENABLED.
# Mounted last so SecurityMiddleware is outermost and blocks hostile traffic
# before it reaches the app (guard does its own request logging); order can
# be tuned during passive-mode calibration.
apply_guard(app)
2025-12-10 02:49:54 +01:00
# ==========================================================================
# Routes
# ==========================================================================
# Health check
2025-12-12 02:45:47 +01:00
app.include_router(health_router, tags=["Health"])
2025-12-10 02:49:54 +01:00
# A2A Protocol: Well-known endpoints at root level
# (/.well-known/agent.json, /agents/{id}/.well-known/agent.json)
app.include_router(a2a_wellknown_router, tags=["A2A Protocol"])
2025-12-10 02:49:54 +01:00
# API v1
api_prefix = "/api"
2025-12-10 02:49:54 +01:00
app.include_router(
agents_router,
prefix=f"{api_prefix}/agents",
tags=["Agents"],
)
2026-06-12 23:11:01 +02:00
app.include_router(
settings_router,
prefix=f"{api_prefix}/settings",
tags=["Settings"],
)
app.include_router(
company_goals_router,
prefix=f"{api_prefix}/company-goals",
tags=["Company"],
)
2025-12-10 02:49:54 +01:00
app.include_router(
2025-12-12 02:45:47 +01:00
notifications_router,
2025-12-10 02:49:54 +01:00
prefix=f"{api_prefix}/notifications",
tags=["Notifications"],
)
# Phase 2: Stream processing and permissions
app.include_router(
2025-12-12 02:45:47 +01:00
stream_router,
2025-12-10 02:49:54 +01:00
prefix=f"{api_prefix}/stream",
tags=["Stream Processing"],
)
# Phase 3: Intelligence - Optimal API and Journal API
app.include_router(
2025-12-12 02:45:47 +01:00
optimal_router,
2025-12-15 20:59:31 +01:00
prefix=f"{api_prefix}/optimal",
2025-12-10 02:49:54 +01:00
tags=["Optimal API"],
)
app.include_router(
2025-12-12 02:45:47 +01:00
journals_router,
2025-12-15 20:59:31 +01:00
prefix=f"{api_prefix}/journals",
2025-12-10 02:49:54 +01:00
tags=["Journals"],
)
# Web research — pluggable external search/fetch for Board + PM agents.
app.include_router(
research_router,
prefix=f"{api_prefix}/research",
tags=["Research"],
)
# Cockpit — the CEO's read-only "is the business winning?" summary.
app.include_router(
cockpit_router,
prefix=f"{api_prefix}/cockpit",
tags=["Cockpit"],
)
2026-06-26 01:43:08 +02:00
# Release manager — the CEO approves/rejects a held release proposal.
app.include_router(
release_router,
prefix=f"{api_prefix}/release",
tags=["Release"],
)
# Playbooks — the Auditor (or CEO) curates the drafted playbook library.
app.include_router(
playbooks_router,
prefix=f"{api_prefix}/playbooks",
tags=["Playbooks"],
)
# X (Twitter) engine — the CEO approves/rejects held posts/replies and
# manages credentials. Nothing here posts except an explicit approve.
app.include_router(
x_router,
prefix=f"{api_prefix}/x",
tags=["X"],
)
# Board Programs — the generic registry route + each program's own
# per-item / read-only surface (roadmap, board-programs, pest control,
# periscope, coroner, sentinel, spackle, scales). Grouped into one helper
# to keep this function's own statement count from growing unbounded as
# programs are added (mirrors ``_mount_v1_routers`` below).
_mount_board_program_routers(app, api_prefix)
# Video engine — the CEO requests an on-demand marketing video; the
# release/spotlight triggers open the same UX/UI authoring task via their
# own hooks. Nothing renders or posts from this route alone.
app.include_router(
video_router,
prefix=f"{api_prefix}/video",
tags=["Video"],
)
# TikTok credentials — write-only OAuth2 secrets for the video engine's
# inbox-upload poster (mirrors /x/credentials).
app.include_router(
tiktok_router,
prefix=f"{api_prefix}/tiktok",
tags=["TikTok"],
)
# Telegram notifications bridge — CEO-managed bot-token + chat-id credentials
# (write-only); the fan-out itself runs server-side from the CEO producers.
app.include_router(
telegram_router,
prefix=f"{api_prefix}/telegram",
tags=["Telegram"],
)
# Telegram Mini App sign-in — public, pre-auth; mounted only when both
# telegram_miniapp_enabled and cloud_auth_enabled are armed.
mount_telegram_miniapp_auth(app, f"{api_prefix}/telegram")
# Pitches — Board proposals + CEO approve -> auto-provision origination path.
app.include_router(
pitch_router,
prefix=f"{api_prefix}/pitches",
tags=["Pitches"],
)
# Secretary — the CEO's chief-of-staff: company-state reads + gated directives.
app.include_router(
secretary_router,
prefix=f"{api_prefix}/secretary",
tags=["Secretary"],
)
# Secretary live chat — panel <-> Secretary container bridge.
app.include_router(
secretary_live_router,
prefix=f"{api_prefix}/secretary",
tags=["Secretary"],
)
2025-12-10 02:49:54 +01:00
# Phase 5: Management - Tasks, Kanban, Dashboards
app.include_router(
2025-12-12 02:45:47 +01:00
tasks_router,
2025-12-15 20:59:31 +01:00
prefix=f"{api_prefix}/tasks",
2025-12-10 02:49:54 +01:00
tags=["Tasks"],
)
app.include_router(
2025-12-12 02:45:47 +01:00
kanban_router,
2025-12-15 20:59:31 +01:00
prefix=f"{api_prefix}/kanban",
2025-12-10 02:49:54 +01:00
tags=["Kanban"],
)
app.include_router(
2025-12-12 02:45:47 +01:00
dashboard_router,
2025-12-15 20:59:31 +01:00
prefix=f"{api_prefix}/dashboard",
2025-12-10 02:49:54 +01:00
tags=["Dashboard"],
)
# Phase 7: Agent Runtime
app.include_router(
2025-12-12 02:45:47 +01:00
orchestrator_router,
prefix=f"{api_prefix}/orchestrator",
tags=["Orchestrator"],
)
# A2A Protocol: API endpoints
app.include_router(
a2a_router,
prefix=f"{api_prefix}/a2a",
tags=["A2A Protocol"],
)
# Git Integration
app.include_router(
git_router,
prefix=f"{api_prefix}/git",
tags=["Git Operations"],
)
# GitHub App integration — CEO-managed credentials + the "Select repo"
# picker's installation/repository listing (see roboco/services/github_app_auth.py).
app.include_router(
github_app_router,
prefix=f"{api_prefix}/github-app",
tags=["GitHub App"],
)
# Project Management
app.include_router(
project_router,
prefix=f"{api_prefix}/projects",
tags=["Projects"],
)
2026-06-03 06:35:03 +02:00
# Product Management
app.include_router(
product_router,
prefix=f"{api_prefix}/products",
tags=["Products"],
)
# AI Providers (model routing + Ollama-cloud fallback)
app.include_router(
provider_router,
prefix=f"{api_prefix}/providers",
tags=["Providers"],
)
2026-06-09 17:08:34 +02:00
# Prompter live chat — panel <-> spawned intake agent (SSE + relay)
app.include_router(
prompter_live_router,
prefix=f"{api_prefix}/prompter",
tags=["Prompter"],
)
# Work Sessions
app.include_router(
work_session_router,
prefix=f"{api_prefix}/work-sessions",
tags=["Work Sessions"],
)
# Documentation
app.include_router(
docs_router,
prefix=f"{api_prefix}/docs",
tags=["Documentation"],
)
# Token Usage Analytics
app.include_router(
usage_router,
prefix=f"{api_prefix}/usage",
tags=["Usage Analytics"],
)
# System monitoring (rate-limits, etc.)
app.include_router(
system_router,
prefix=f"{api_prefix}/system",
tags=["System"],
)
# Cloud auth — /auth/status is always public; login/logout mount only
# when ROBOCO_CLOUD_AUTH_ENABLED (mirrors apply_guard's conditional mount).
mount_cloud_auth(app, f"{api_prefix}/auth")
2026-05-02 03:11:49 +02:00
# API v1 — intent-verb flow + content-tool endpoints (each module owns
# its own prefix); grouped into one helper to keep create_app's own
# statement count from growing unbounded as roles are added.
_mount_v1_routers(app)
2026-05-02 03:11:49 +02:00
2025-12-10 02:49:54 +01:00
# ==========================================================================
# WebSocket
# ==========================================================================
app.include_router(ws_router, prefix="/ws", tags=["WebSocket"])
return app
# Create the default application instance
app = create_app()