feat(server): integrate MCP queries and investigation messaging
Add project-bound OAuth MCP queries and controlled analysis with scoped evidence, limits, timeouts and a nearby-model spatial query using EPSG:4326 inputs. Default nearby searches to 200 meters and five results. Integrate investigation messaging/presentation, health/readiness and typed history; remove duplicate web search and fix online simulation and compressed baseline writes. Include project migrations 023-025; keep local credentials and demo seeding excluded. Validation: 319 focused pytest checks; real PostGIS temporary-table regression; read-only Jinqian bridge radius comparison; cross-language MCP discovery/calls.
This commit is contained in:
@@ -24,6 +24,7 @@ from app.auth.project_dependencies import (
|
||||
get_project_pg_connection,
|
||||
)
|
||||
from app.domain.domain_errors import DomainError
|
||||
from app.domain.schemas.workbench_runtime import WorkbenchDateTime
|
||||
from app.domain.schemas.operational_data import TaskDataQuery, TaskDataSnapshot
|
||||
from app.domain.schemas.operational_task import (
|
||||
ActionDecisionCommand,
|
||||
@@ -70,6 +71,9 @@ from app.domain.schemas.operational_task import (
|
||||
TaskStatus,
|
||||
TaskDataQueryResult,
|
||||
TaskType,
|
||||
TaskPresentationEventCommand,
|
||||
TaskPresentationEventResult,
|
||||
TaskPresentationCursor,
|
||||
)
|
||||
from app.infra.db.postgresql.operational_tasks import OperationalTaskRepository
|
||||
from app.infra.integrations.operational_actions import (
|
||||
@@ -92,10 +96,9 @@ def _service(ctx: ProjectContext, conn: AsyncConnection) -> OperationalTaskServi
|
||||
|
||||
|
||||
def _task_permissions(ctx: ProjectContext) -> set[str]:
|
||||
permissions = set(permissions_for_context(ctx))
|
||||
if ctx.system_role == "service":
|
||||
permissions.difference_update({SCADA_VIEW, SIMULATION_VIEW})
|
||||
return permissions
|
||||
# Investigations use preconfigured project access, including service accounts.
|
||||
# Do not force an already-authorized reader through legacy per-task delegation.
|
||||
return set(permissions_for_context(ctx))
|
||||
|
||||
|
||||
def _raise_domain(exc: DomainError):
|
||||
@@ -176,6 +179,8 @@ async def list_operational_task_runs(
|
||||
run_status: Literal[
|
||||
"queued", "claimed", "running", "succeeded", "failed", "timed_out", "cancelled"
|
||||
] | None = Query(default=None, alias="status"),
|
||||
task_type: TaskType | None = None,
|
||||
task_created_from: WorkbenchDateTime | None = None,
|
||||
limit: int = Query(default=50, ge=1, le=100),
|
||||
cursor: str | None = None,
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_VIEW)),
|
||||
@@ -193,6 +198,8 @@ async def list_operational_task_runs(
|
||||
try:
|
||||
items, next_cursor = await _service(ctx, conn).list_runs(
|
||||
status=run_status,
|
||||
task_type=task_type,
|
||||
task_created_from=task_created_from,
|
||||
limit=limit,
|
||||
cursor_created_at=cursor_created_at,
|
||||
cursor_run_id=cursor_run_id,
|
||||
@@ -202,6 +209,32 @@ async def list_operational_task_runs(
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.get("/operational-tasks/{task_id}/runs", response_model=OperationalTaskRunPage)
|
||||
async def list_task_run_history(
|
||||
task_id: UUID,
|
||||
limit: int = Query(default=20, ge=1, le=100),
|
||||
offset: int = Query(default=0, ge=0),
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_VIEW)),
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
):
|
||||
try:
|
||||
return OperationalTaskRunPage(items=await _service(ctx, conn).task_runs(task_id, limit, offset))
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.get("/operational-tasks/{task_id}/runs/{run_id}", response_model=OperationalTaskRun)
|
||||
async def get_task_run(
|
||||
task_id: UUID, run_id: UUID,
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_VIEW)),
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
):
|
||||
try:
|
||||
return await _service(ctx, conn).task_run(task_id, run_id)
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/operational-tasks/schedules",
|
||||
response_model=OperationalTaskSchedule,
|
||||
@@ -272,7 +305,10 @@ async def recover_operational_task_leases(
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
):
|
||||
try:
|
||||
return await _service(ctx, conn).recover_expired_leases(command.limit)
|
||||
return await _service(ctx, conn).recover_expired_leases(
|
||||
command.limit, task_type=command.task_type,
|
||||
task_created_from=command.task_created_from,
|
||||
)
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
@@ -473,6 +509,31 @@ async def publish_operational_task_artifacts(
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.get("/operational-tasks/{task_id}/presentation-cursor", response_model=TaskPresentationCursor)
|
||||
async def get_operational_task_presentation_cursor(
|
||||
task_id: UUID,
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_VIEW)),
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
):
|
||||
try:
|
||||
return await _service(ctx, conn).presentation_cursor(task_id)
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.post("/operational-tasks/{task_id}/presentation-events", response_model=TaskPresentationEventResult)
|
||||
async def publish_operational_task_presentation_event(
|
||||
task_id: UUID,
|
||||
command: TaskPresentationEventCommand,
|
||||
ctx: ProjectContext = Depends(require_permission(OPERATIONAL_TASK_EXECUTE)),
|
||||
conn: AsyncConnection = Depends(get_project_pg_connection),
|
||||
):
|
||||
try:
|
||||
return await _service(ctx, conn).publish_presentation_event(task_id, command)
|
||||
except DomainError as exc:
|
||||
_raise_domain(exc)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/operational-tasks/{task_id}/data-query", response_model=TaskDataQueryResult
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user