feat: add is_chat field to board memory and task_id to approvals, update pagination and response models

This commit is contained in:
Abhimanyu Saharan
2026-02-06 19:11:11 +05:30
parent d86fe0a7a6
commit 6c14af0451
76 changed files with 2070 additions and 571 deletions

93
Makefile Normal file
View File

@@ -0,0 +1,93 @@
.DEFAULT_GOAL := help
SHELL := /usr/bin/env bash
.SHELLFLAGS := -euo pipefail -c
BACKEND_DIR := backend
FRONTEND_DIR := frontend
.PHONY: help
help: ## Show available targets
@grep -E '^[a-zA-Z0-9_.-]+:.*?## ' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf " %-26s %s\n", $$1, $$2}'
.PHONY: setup
setup: backend-sync frontend-sync ## Install/sync backend + frontend deps
.PHONY: backend-sync
backend-sync: ## uv sync backend deps (includes dev extra)
cd $(BACKEND_DIR) && uv sync --extra dev
.PHONY: frontend-sync
frontend-sync: ## npm install frontend deps
cd $(FRONTEND_DIR) && npm install
.PHONY: format
format: backend-format frontend-format ## Format backend + frontend
.PHONY: backend-format
backend-format: ## Format backend (isort + black)
cd $(BACKEND_DIR) && uv run isort .
cd $(BACKEND_DIR) && uv run black .
.PHONY: frontend-format
frontend-format: ## Format frontend (prettier)
cd $(FRONTEND_DIR) && npx prettier --write "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}"
.PHONY: format-check
format-check: backend-format-check frontend-format-check ## Check formatting (no changes)
.PHONY: backend-format-check
backend-format-check: ## Check backend formatting (isort + black)
cd $(BACKEND_DIR) && uv run isort . --check-only --diff
cd $(BACKEND_DIR) && uv run black . --check --diff
.PHONY: frontend-format-check
frontend-format-check: ## Check frontend formatting (prettier)
cd $(FRONTEND_DIR) && npx prettier --check "src/**/*.{ts,tsx,js,jsx,json,css,md}" "*.{ts,js,json,md,mdx}"
.PHONY: lint
lint: backend-lint frontend-lint ## Lint backend + frontend
.PHONY: backend-lint
backend-lint: ## Lint backend (flake8)
cd $(BACKEND_DIR) && uv run flake8 --config .flake8
.PHONY: frontend-lint
frontend-lint: ## Lint frontend (eslint)
cd $(FRONTEND_DIR) && npm run lint
.PHONY: typecheck
typecheck: backend-typecheck frontend-typecheck ## Typecheck backend + frontend
.PHONY: backend-typecheck
backend-typecheck: ## Typecheck backend (mypy --strict)
cd $(BACKEND_DIR) && uv run mypy
.PHONY: frontend-typecheck
frontend-typecheck: ## Typecheck frontend (tsc)
cd $(FRONTEND_DIR) && npx tsc -p tsconfig.json --noEmit
.PHONY: test
test: backend-test ## Run tests
.PHONY: backend-test
backend-test: ## Backend tests (pytest)
cd $(BACKEND_DIR) && uv run pytest
.PHONY: backend-migrate
backend-migrate: ## Apply backend DB migrations (alembic upgrade head)
cd $(BACKEND_DIR) && uv run alembic upgrade head
.PHONY: build
build: frontend-build ## Build artifacts
.PHONY: frontend-build
frontend-build: ## Build frontend (next build)
cd $(FRONTEND_DIR) && npm run build
.PHONY: api-gen
api-gen: ## Regenerate TS API client (requires backend running at 127.0.0.1:8000)
cd $(FRONTEND_DIR) && npm run api:gen
.PHONY: check
check: lint typecheck test build ## Run lint + typecheck + tests + build

4
backend/.gitignore vendored
View File

@@ -3,3 +3,7 @@ __pycache__/
.venv/ .venv/
.env .env
.runlogs/ .runlogs/
# Generated on demand from uv.lock (single source of truth is pyproject.toml + uv.lock).
requirements.txt
requirements-dev.txt

View File

@@ -0,0 +1,101 @@
"""add approvals task_id
Revision ID: 1d844b04ee06
Revises: a5aab244d32d
Create Date: 2026-02-06 17:26:43.336466
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "1d844b04ee06"
down_revision = "a5aab244d32d"
branch_labels = None
depends_on = None
def upgrade() -> None:
# This migration may run in databases where the column/index/constraint were created via
# SQLModel `create_all()` (or a previous hotfix). Make it idempotent to avoid blocking
# upgrades in dev environments.
bind = op.get_bind()
inspector = sa.inspect(bind)
approval_cols = {c["name"] for c in inspector.get_columns("approvals")}
if "task_id" not in approval_cols:
op.add_column("approvals", sa.Column("task_id", sa.Uuid(), nullable=True))
approval_index_names = {i["name"] for i in inspector.get_indexes("approvals")}
if "ix_approvals_task_id" not in approval_index_names:
op.create_index("ix_approvals_task_id", "approvals", ["task_id"], unique=False)
# Backfill from legacy JSON payload keys when they contain a valid UUID.
op.execute(
"""
WITH src AS (
SELECT
id,
COALESCE(
payload->>'task_id',
payload->>'taskId',
payload->>'taskID'
) AS task_id_str
FROM approvals
WHERE task_id IS NULL
),
valid AS (
SELECT
id,
task_id_str::uuid AS task_id
FROM src
WHERE task_id_str ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
),
existing AS (
SELECT v.id, v.task_id
FROM valid AS v
JOIN tasks AS t ON t.id = v.task_id
)
UPDATE approvals AS a
SET task_id = existing.task_id
FROM existing
WHERE a.id = existing.id;
"""
)
# Avoid FK failures if any approvals point at deleted tasks.
op.execute(
"""
UPDATE approvals AS a
SET task_id = NULL
WHERE task_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM tasks AS t WHERE t.id = a.task_id
);
"""
)
approval_fks = inspector.get_foreign_keys("approvals")
has_task_fk = any(
(fk.get("referred_table") == "tasks" and "task_id" in (fk.get("constrained_columns") or []))
for fk in approval_fks
)
if not has_task_fk:
op.create_foreign_key(
"fk_approvals_task_id_tasks",
"approvals",
"tasks",
["task_id"],
["id"],
ondelete="SET NULL",
)
def downgrade() -> None:
op.drop_constraint("fk_approvals_task_id_tasks", "approvals", type_="foreignkey")
op.drop_index("ix_approvals_task_id", table_name="approvals")
op.drop_column("approvals", "task_id")

View File

@@ -0,0 +1,60 @@
"""add board memory is_chat
Revision ID: a5aab244d32d
Revises: 3b9b2f1a6c2d
Create Date: 2026-02-06 17:57:02.110572
"""
from __future__ import annotations
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = "a5aab244d32d"
down_revision = "3b9b2f1a6c2d"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Idempotent: the column/indexes might already exist if the table was created via
# SQLModel `create_all()`.
bind = op.get_bind()
inspector = sa.inspect(bind)
memory_cols = {c["name"] for c in inspector.get_columns("board_memory")}
if "is_chat" not in memory_cols:
op.add_column(
"board_memory",
sa.Column("is_chat", sa.Boolean(), server_default=sa.text("false"), nullable=False),
)
memory_index_names = {i["name"] for i in inspector.get_indexes("board_memory")}
if "ix_board_memory_is_chat" not in memory_index_names:
op.create_index("ix_board_memory_is_chat", "board_memory", ["is_chat"], unique=False)
if "ix_board_memory_board_id_is_chat_created_at" not in memory_index_names:
op.create_index(
"ix_board_memory_board_id_is_chat_created_at",
"board_memory",
["board_id", "is_chat", "created_at"],
unique=False,
)
# Backfill from existing tags arrays.
op.execute(
"""
UPDATE board_memory
SET is_chat = TRUE
WHERE tags IS NOT NULL
AND tags::jsonb @> '["chat"]'::jsonb;
"""
)
def downgrade() -> None:
op.drop_index("ix_board_memory_board_id_is_chat_created_at", table_name="board_memory")
op.drop_index("ix_board_memory_is_chat", table_name="board_memory")
op.drop_column("board_memory", "is_chat")

View File

@@ -1,27 +1,27 @@
from __future__ import annotations from __future__ import annotations
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends
from sqlalchemy import desc from sqlalchemy import desc
from sqlmodel import col, select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.api.deps import ActorContext, require_admin_or_agent from app.api.deps import ActorContext, require_admin_or_agent
from app.db.pagination import paginate
from app.db.session import get_session from app.db.session import get_session
from app.models.activity_events import ActivityEvent from app.models.activity_events import ActivityEvent
from app.schemas.activity_events import ActivityEventRead from app.schemas.activity_events import ActivityEventRead
from app.schemas.pagination import DefaultLimitOffsetPage
router = APIRouter(prefix="/activity", tags=["activity"]) router = APIRouter(prefix="/activity", tags=["activity"])
@router.get("", response_model=list[ActivityEventRead]) @router.get("", response_model=DefaultLimitOffsetPage[ActivityEventRead])
async def list_activity( async def list_activity(
limit: int = Query(50, ge=1, le=200),
offset: int = Query(0, ge=0),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[ActivityEvent]: ) -> DefaultLimitOffsetPage[ActivityEventRead]:
statement = select(ActivityEvent) statement = select(ActivityEvent)
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
statement = statement.where(ActivityEvent.agent_id == actor.agent.id) statement = statement.where(ActivityEvent.agent_id == actor.agent.id)
statement = statement.order_by(desc(col(ActivityEvent.created_at))).offset(offset).limit(limit) statement = statement.order_by(desc(col(ActivityEvent.created_at)))
return list(await session.exec(statement)) return await paginate(session, statement)

View File

@@ -1,9 +1,11 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Sequence
from typing import Any, cast
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlmodel import select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.api import agents as agents_api from app.api import agents as agents_api
@@ -13,6 +15,7 @@ from app.api import board_onboarding as onboarding_api
from app.api import tasks as tasks_api from app.api import tasks as tasks_api
from app.api.deps import ActorContext, get_board_or_404, get_task_or_404 from app.api.deps import ActorContext, get_board_or_404, get_task_or_404
from app.core.agent_auth import AgentAuthContext, get_agent_auth_context from app.core.agent_auth import AgentAuthContext, get_agent_auth_context
from app.db.pagination import paginate
from app.db.session import get_session from app.db.session import get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
@@ -30,6 +33,7 @@ from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboardingRead from app.schemas.board_onboarding import BoardOnboardingAgentUpdate, BoardOnboardingRead
from app.schemas.boards import BoardRead from app.schemas.boards import BoardRead
from app.schemas.common import OkResponse from app.schemas.common import OkResponse
from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
from app.services.activity_log import record_activity from app.services.activity_log import record_activity
@@ -54,15 +58,16 @@ async def _gateway_config(session: AsyncSession, board: Board) -> GatewayClientC
return GatewayClientConfig(url=gateway.url, token=gateway.token) return GatewayClientConfig(url=gateway.url, token=gateway.token)
@router.get("/boards", response_model=list[BoardRead]) @router.get("/boards", response_model=DefaultLimitOffsetPage[BoardRead])
async def list_boards( async def list_boards(
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[Board]: ) -> DefaultLimitOffsetPage[BoardRead]:
statement = select(Board)
if agent_ctx.agent.board_id: if agent_ctx.agent.board_id:
board = await session.get(Board, agent_ctx.agent.board_id) statement = statement.where(col(Board.id) == agent_ctx.agent.board_id)
return [board] if board else [] statement = statement.order_by(col(Board.created_at).desc())
return list(await session.exec(select(Board))) return await paginate(session, statement)
@router.get("/boards/{board_id}", response_model=BoardRead) @router.get("/boards/{board_id}", response_model=BoardRead)
@@ -74,13 +79,12 @@ def get_board(
return board return board
@router.get("/agents", response_model=list[AgentRead]) @router.get("/agents", response_model=DefaultLimitOffsetPage[AgentRead])
async def list_agents( async def list_agents(
board_id: UUID | None = Query(default=None), board_id: UUID | None = Query(default=None),
limit: int | None = Query(default=None, ge=1, le=200),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[AgentRead]: ) -> DefaultLimitOffsetPage[AgentRead]:
statement = select(Agent) statement = select(Agent)
if agent_ctx.agent.board_id: if agent_ctx.agent.board_id:
if board_id and board_id != agent_ctx.agent.board_id: if board_id and board_id != agent_ctx.agent.board_id:
@@ -88,32 +92,33 @@ async def list_agents(
statement = statement.where(Agent.board_id == agent_ctx.agent.board_id) statement = statement.where(Agent.board_id == agent_ctx.agent.board_id)
elif board_id: elif board_id:
statement = statement.where(Agent.board_id == board_id) statement = statement.where(Agent.board_id == board_id)
if limit is not None:
statement = statement.limit(limit)
agents = list(await session.exec(statement))
main_session_keys = await agents_api._get_gateway_main_session_keys(session) main_session_keys = await agents_api._get_gateway_main_session_keys(session)
return [ statement = statement.order_by(col(Agent.created_at).desc())
agents_api._to_agent_read(agents_api._with_computed_status(agent), main_session_keys)
for agent in agents def _transform(items: Sequence[Any]) -> Sequence[Any]:
] agents = cast(Sequence[Agent], items)
return [
agents_api._to_agent_read(agents_api._with_computed_status(agent), main_session_keys)
for agent in agents
]
return await paginate(session, statement, transformer=_transform)
@router.get("/boards/{board_id}/tasks", response_model=list[TaskRead]) @router.get("/boards/{board_id}/tasks", response_model=DefaultLimitOffsetPage[TaskRead])
async def list_tasks( async def list_tasks(
status_filter: str | None = Query(default=None, alias="status"), status_filter: str | None = Query(default=None, alias="status"),
assigned_agent_id: UUID | None = None, assigned_agent_id: UUID | None = None,
unassigned: bool | None = None, unassigned: bool | None = None,
limit: int | None = Query(default=None, ge=1, le=200),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[Task]: ) -> DefaultLimitOffsetPage[TaskRead]:
_guard_board_access(agent_ctx, board) _guard_board_access(agent_ctx, board)
return await tasks_api.list_tasks( return await tasks_api.list_tasks(
status_filter=status_filter, status_filter=status_filter,
assigned_agent_id=assigned_agent_id, assigned_agent_id=assigned_agent_id,
unassigned=unassigned, unassigned=unassigned,
limit=limit,
board=board, board=board,
session=session, session=session,
actor=_actor(agent_ctx), actor=_actor(agent_ctx),
@@ -185,12 +190,15 @@ async def update_task(
) )
@router.get("/boards/{board_id}/tasks/{task_id}/comments", response_model=list[TaskCommentRead]) @router.get(
"/boards/{board_id}/tasks/{task_id}/comments",
response_model=DefaultLimitOffsetPage[TaskCommentRead],
)
async def list_task_comments( async def list_task_comments(
task: Task = Depends(get_task_or_404), task: Task = Depends(get_task_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[ActivityEvent]: ) -> DefaultLimitOffsetPage[TaskCommentRead]:
if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id: if agent_ctx.agent.board_id and task.board_id and agent_ctx.agent.board_id != task.board_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return await tasks_api.list_task_comments( return await tasks_api.list_task_comments(
@@ -217,18 +225,14 @@ async def create_task_comment(
) )
@router.get("/boards/{board_id}/memory", response_model=list[BoardMemoryRead]) @router.get("/boards/{board_id}/memory", response_model=DefaultLimitOffsetPage[BoardMemoryRead])
async def list_board_memory( async def list_board_memory(
limit: int = Query(default=50, ge=1, le=200),
offset: int = Query(default=0, ge=0),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[BoardMemory]: ) -> DefaultLimitOffsetPage[BoardMemoryRead]:
_guard_board_access(agent_ctx, board) _guard_board_access(agent_ctx, board)
return await board_memory_api.list_board_memory( return await board_memory_api.list_board_memory(
limit=limit,
offset=offset,
board=board, board=board,
session=session, session=session,
actor=_actor(agent_ctx), actor=_actor(agent_ctx),
@@ -251,13 +255,16 @@ async def create_board_memory(
) )
@router.get("/boards/{board_id}/approvals", response_model=list[ApprovalRead]) @router.get(
"/boards/{board_id}/approvals",
response_model=DefaultLimitOffsetPage[ApprovalRead],
)
async def list_approvals( async def list_approvals(
status_filter: ApprovalStatus | None = Query(default=None, alias="status"), status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
agent_ctx: AgentAuthContext = Depends(get_agent_auth_context), agent_ctx: AgentAuthContext = Depends(get_agent_auth_context),
) -> list[Approval]: ) -> DefaultLimitOffsetPage[ApprovalRead]:
_guard_board_access(agent_ctx, board) _guard_board_access(agent_ctx, board)
return await approvals_api.list_approvals( return await approvals_api.list_approvals(
status_filter=status_filter, status_filter=status_filter,

View File

@@ -3,8 +3,9 @@ from __future__ import annotations
import asyncio import asyncio
import json import json
import re import re
from collections.abc import AsyncIterator from collections.abc import AsyncIterator, Sequence
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, cast
from uuid import UUID, uuid4 from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
@@ -17,6 +18,7 @@ from app.api.deps import ActorContext, require_admin_auth, require_admin_or_agen
from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.agent_tokens import generate_agent_token, hash_agent_token
from app.core.auth import AuthContext from app.core.auth import AuthContext
from app.core.time import utcnow from app.core.time import utcnow
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session from app.db.session import async_session_maker, get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
@@ -33,6 +35,7 @@ from app.schemas.agents import (
AgentRead, AgentRead,
AgentUpdate, AgentUpdate,
) )
from app.schemas.pagination import DefaultLimitOffsetPage
from app.services.activity_log import record_activity from app.services.activity_log import record_activity
from app.services.agent_provisioning import ( from app.services.agent_provisioning import (
DEFAULT_HEARTBEAT_CONFIG, DEFAULT_HEARTBEAT_CONFIG,
@@ -231,14 +234,28 @@ async def _send_wakeup_message(
await send_message(message, session_key=session_key, config=config, deliver=True) await send_message(message, session_key=session_key, config=config, deliver=True)
@router.get("", response_model=list[AgentRead]) @router.get("", response_model=DefaultLimitOffsetPage[AgentRead])
async def list_agents( async def list_agents(
board_id: UUID | None = Query(default=None),
gateway_id: UUID | None = Query(default=None),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
auth: AuthContext = Depends(require_admin_auth), auth: AuthContext = Depends(require_admin_auth),
) -> list[AgentRead]: ) -> DefaultLimitOffsetPage[AgentRead]:
agents = list(await session.exec(select(Agent)))
main_session_keys = await _get_gateway_main_session_keys(session) main_session_keys = await _get_gateway_main_session_keys(session)
return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents] statement = select(Agent)
if board_id is not None:
statement = statement.where(col(Agent.board_id) == board_id)
if gateway_id is not None:
statement = statement.join(Board, col(Agent.board_id) == col(Board.id)).where(
col(Board.gateway_id) == gateway_id
)
statement = statement.order_by(col(Agent.created_at).desc())
def _transform(items: Sequence[Any]) -> Sequence[Any]:
agents = cast(Sequence[Agent], items)
return [_to_agent_read(_with_computed_status(agent), main_session_keys) for agent in agents]
return await paginate(session, statement, transformer=_transform)
@router.get("/stream") @router.get("/stream")

View File

@@ -7,7 +7,7 @@ from datetime import datetime, timezone
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import asc, or_ from sqlalchemy import asc, case, func, or_
from sqlmodel import col, select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from sse_starlette.sse import EventSourceResponse from sse_starlette.sse import EventSourceResponse
@@ -15,13 +15,32 @@ from sse_starlette.sse import EventSourceResponse
from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, require_admin_or_agent
from app.core.auth import AuthContext from app.core.auth import AuthContext
from app.core.time import utcnow from app.core.time import utcnow
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session from app.db.session import async_session_maker, get_session
from app.models.approvals import Approval from app.models.approvals import Approval
from app.models.boards import Board from app.models.boards import Board
from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate from app.schemas.approvals import ApprovalCreate, ApprovalRead, ApprovalStatus, ApprovalUpdate
from app.schemas.pagination import DefaultLimitOffsetPage
router = APIRouter(prefix="/boards/{board_id}/approvals", tags=["approvals"]) router = APIRouter(prefix="/boards/{board_id}/approvals", tags=["approvals"])
TASK_ID_KEYS: tuple[str, ...] = ("task_id", "taskId", "taskID")
def _extract_task_id(payload: dict[str, object] | None) -> UUID | None:
if not payload:
return None
for key in TASK_ID_KEYS:
value = payload.get(key)
if isinstance(value, UUID):
return value
if isinstance(value, str):
try:
return UUID(value)
except ValueError:
continue
return None
def _parse_since(value: str | None) -> datetime | None: def _parse_since(value: str | None) -> datetime | None:
if not value: if not value:
@@ -66,13 +85,13 @@ async def _fetch_approval_events(
return list(await session.exec(statement)) return list(await session.exec(statement))
@router.get("", response_model=list[ApprovalRead]) @router.get("", response_model=DefaultLimitOffsetPage[ApprovalRead])
async def list_approvals( async def list_approvals(
status_filter: ApprovalStatus | None = Query(default=None, alias="status"), status_filter: ApprovalStatus | None = Query(default=None, alias="status"),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[Approval]: ) -> DefaultLimitOffsetPage[ApprovalRead]:
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id: if actor.agent.board_id and actor.agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@@ -80,7 +99,7 @@ async def list_approvals(
if status_filter: if status_filter:
statement = statement.where(col(Approval.status) == status_filter) statement = statement.where(col(Approval.status) == status_filter)
statement = statement.order_by(col(Approval.created_at).desc()) statement = statement.order_by(col(Approval.created_at).desc())
return list(await session.exec(statement)) return await paginate(session, statement)
@router.get("/stream") @router.get("/stream")
@@ -103,11 +122,53 @@ async def stream_approvals(
break break
async with async_session_maker() as session: async with async_session_maker() as session:
approvals = await _fetch_approval_events(session, board.id, last_seen) approvals = await _fetch_approval_events(session, board.id, last_seen)
pending_approvals_count = int(
(
await session.exec(
select(func.count(col(Approval.id)))
.where(col(Approval.board_id) == board.id)
.where(col(Approval.status) == "pending")
)
).one()
)
task_ids = {approval.task_id for approval in approvals if approval.task_id is not None}
counts_by_task_id: dict[UUID, tuple[int, int]] = {}
if task_ids:
rows = list(
await session.exec(
select(
col(Approval.task_id),
func.count(col(Approval.id)).label("total"),
func.sum(
case((col(Approval.status) == "pending", 1), else_=0)
).label("pending"),
)
.where(col(Approval.board_id) == board.id)
.where(col(Approval.task_id).in_(task_ids))
.group_by(col(Approval.task_id))
)
)
for task_id, total, pending in rows:
if task_id is None:
continue
counts_by_task_id[task_id] = (int(total or 0), int(pending or 0))
for approval in approvals: for approval in approvals:
updated_at = _approval_updated_at(approval) updated_at = _approval_updated_at(approval)
if updated_at > last_seen: if updated_at > last_seen:
last_seen = updated_at last_seen = updated_at
payload = {"approval": _serialize_approval(approval)} payload: dict[str, object] = {
"approval": _serialize_approval(approval),
"pending_approvals_count": pending_approvals_count,
}
if approval.task_id is not None:
counts = counts_by_task_id.get(approval.task_id)
if counts is not None:
total, pending = counts
payload["task_counts"] = {
"task_id": str(approval.task_id),
"approvals_count": total,
"approvals_pending_count": pending,
}
yield {"event": "approval", "data": json.dumps(payload)} yield {"event": "approval", "data": json.dumps(payload)}
await asyncio.sleep(2) await asyncio.sleep(2)
@@ -124,8 +185,10 @@ async def create_approval(
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id: if actor.agent.board_id and actor.agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
task_id = payload.task_id or _extract_task_id(payload.payload)
approval = Approval( approval = Approval(
board_id=board.id, board_id=board.id,
task_id=task_id,
agent_id=payload.agent_id, agent_id=payload.agent_id,
action_type=payload.action_type, action_type=payload.action_type,
payload=payload.payload, payload=payload.payload,

View File

@@ -8,6 +8,7 @@ from datetime import datetime, timezone
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from sqlalchemy import func
from sqlmodel import col, select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from sse_starlette.sse import EventSourceResponse from sse_starlette.sse import EventSourceResponse
@@ -15,6 +16,7 @@ from sse_starlette.sse import EventSourceResponse
from app.api.deps import ActorContext, get_board_or_404, require_admin_or_agent from app.api.deps import ActorContext, get_board_or_404, require_admin_or_agent
from app.core.config import settings from app.core.config import settings
from app.core.time import utcnow from app.core.time import utcnow
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session from app.db.session import async_session_maker, get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
@@ -23,6 +25,7 @@ from app.models.board_memory import BoardMemory
from app.models.boards import Board from app.models.boards import Board
from app.models.gateways import Gateway from app.models.gateways import Gateway
from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead from app.schemas.board_memory import BoardMemoryCreate, BoardMemoryRead
from app.schemas.pagination import DefaultLimitOffsetPage
router = APIRouter(prefix="/boards/{board_id}/memory", tags=["board-memory"]) router = APIRouter(prefix="/boards/{board_id}/memory", tags=["board-memory"])
@@ -90,11 +93,19 @@ async def _fetch_memory_events(
session: AsyncSession, session: AsyncSession,
board_id: UUID, board_id: UUID,
since: datetime, since: datetime,
is_chat: bool | None = None,
) -> list[BoardMemory]: ) -> list[BoardMemory]:
statement = ( statement = (
select(BoardMemory) select(BoardMemory)
.where(col(BoardMemory.board_id) == board_id) .where(col(BoardMemory.board_id) == board_id)
.where(col(BoardMemory.created_at) >= since) # Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
# satisfy the NonEmptyStr response schema.
.where(func.length(func.trim(col(BoardMemory.content))) > 0)
)
if is_chat is not None:
statement = statement.where(col(BoardMemory.is_chat) == is_chat)
statement = (
statement.where(col(BoardMemory.created_at) >= since)
.order_by(col(BoardMemory.created_at)) .order_by(col(BoardMemory.created_at))
) )
return list(await session.exec(statement)) return list(await session.exec(statement))
@@ -159,25 +170,27 @@ async def _notify_chat_targets(
continue continue
@router.get("", response_model=list[BoardMemoryRead]) @router.get("", response_model=DefaultLimitOffsetPage[BoardMemoryRead])
async def list_board_memory( async def list_board_memory(
limit: int = Query(default=50, ge=1, le=200), is_chat: bool | None = Query(default=None),
offset: int = Query(default=0, ge=0),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[BoardMemory]: ) -> DefaultLimitOffsetPage[BoardMemoryRead]:
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id: if actor.agent.board_id and actor.agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
statement = ( statement = (
select(BoardMemory) select(BoardMemory)
.where(col(BoardMemory.board_id) == board.id) .where(col(BoardMemory.board_id) == board.id)
.order_by(col(BoardMemory.created_at).desc()) # Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
.offset(offset) # satisfy the NonEmptyStr response schema.
.limit(limit) .where(func.length(func.trim(col(BoardMemory.content))) > 0)
) )
return list(await session.exec(statement)) if is_chat is not None:
statement = statement.where(col(BoardMemory.is_chat) == is_chat)
statement = statement.order_by(col(BoardMemory.created_at).desc())
return await paginate(session, statement)
@router.get("/stream") @router.get("/stream")
@@ -186,6 +199,7 @@ async def stream_board_memory(
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
since: str | None = Query(default=None), since: str | None = Query(default=None),
is_chat: bool | None = Query(default=None),
) -> EventSourceResponse: ) -> EventSourceResponse:
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id: if actor.agent.board_id and actor.agent.board_id != board.id:
@@ -199,7 +213,12 @@ async def stream_board_memory(
if await request.is_disconnected(): if await request.is_disconnected():
break break
async with async_session_maker() as session: async with async_session_maker() as session:
memories = await _fetch_memory_events(session, board.id, last_seen) memories = await _fetch_memory_events(
session,
board.id,
last_seen,
is_chat=is_chat,
)
for memory in memories: for memory in memories:
if memory.created_at > last_seen: if memory.created_at > last_seen:
last_seen = memory.created_at last_seen = memory.created_at
@@ -231,6 +250,7 @@ async def create_board_memory(
board_id=board.id, board_id=board.id,
content=payload.content, content=payload.content,
tags=payload.tags, tags=payload.tags,
is_chat=is_chat,
source=source, source=source,
) )
session.add(memory) session.add(memory)

View File

@@ -2,7 +2,6 @@ from __future__ import annotations
import logging import logging
import re import re
from datetime import datetime
from uuid import uuid4 from uuid import uuid4
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status

View File

@@ -1,10 +1,11 @@
from __future__ import annotations from __future__ import annotations
import re import re
from uuid import uuid4 from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import delete from sqlalchemy import delete
from sqlalchemy import func
from sqlmodel import col, select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
@@ -12,6 +13,7 @@ from app.api.deps import ActorContext, get_board_or_404, require_admin_auth, req
from app.core.auth import AuthContext from app.core.auth import AuthContext
from app.core.time import utcnow from app.core.time import utcnow
from app.db import crud from app.db import crud
from app.db.pagination import paginate
from app.db.session import get_session from app.db.session import get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import ( from app.integrations.openclaw_gateway import (
@@ -31,6 +33,9 @@ from app.models.task_fingerprints import TaskFingerprint
from app.models.tasks import Task from app.models.tasks import Task
from app.schemas.common import OkResponse from app.schemas.common import OkResponse
from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate from app.schemas.boards import BoardCreate, BoardRead, BoardUpdate
from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.view_models import BoardSnapshot
from app.services.board_snapshot import build_board_snapshot
router = APIRouter(prefix="/boards", tags=["boards"]) router = APIRouter(prefix="/boards", tags=["boards"])
@@ -149,12 +154,17 @@ async def _cleanup_agent_on_gateway(
) )
@router.get("", response_model=list[BoardRead]) @router.get("", response_model=DefaultLimitOffsetPage[BoardRead])
async def list_boards( async def list_boards(
gateway_id: UUID | None = Query(default=None),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[Board]: ) -> DefaultLimitOffsetPage[BoardRead]:
return list(await session.exec(select(Board))) statement = select(Board)
if gateway_id is not None:
statement = statement.where(col(Board.gateway_id) == gateway_id)
statement = statement.order_by(func.lower(col(Board.name)).asc(), col(Board.created_at).desc())
return await paginate(session, statement)
@router.post("", response_model=BoardRead) @router.post("", response_model=BoardRead)
@@ -175,6 +185,18 @@ def get_board(
return board return board
@router.get("/{board_id}/snapshot", response_model=BoardSnapshot)
async def get_board_snapshot(
board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent),
) -> BoardSnapshot:
if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
return await build_board_snapshot(session, board)
@router.patch("/{board_id}", response_model=BoardRead) @router.patch("/{board_id}", response_model=BoardRead)
async def update_board( async def update_board(
payload: BoardUpdate, payload: BoardUpdate,

View File

@@ -1,15 +1,15 @@
from __future__ import annotations from __future__ import annotations
from datetime import datetime
from uuid import UUID from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlmodel import select from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.agent_tokens import generate_agent_token, hash_agent_token from app.core.agent_tokens import generate_agent_token, hash_agent_token
from app.core.auth import AuthContext, get_auth_context from app.core.auth import AuthContext, get_auth_context
from app.core.time import utcnow from app.core.time import utcnow
from app.db.pagination import paginate
from app.db.session import get_session from app.db.session import get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
@@ -17,6 +17,7 @@ from app.models.agents import Agent
from app.models.gateways import Gateway from app.models.gateways import Gateway
from app.schemas.common import OkResponse from app.schemas.common import OkResponse
from app.schemas.gateways import GatewayCreate, GatewayRead, GatewayUpdate from app.schemas.gateways import GatewayCreate, GatewayRead, GatewayUpdate
from app.schemas.pagination import DefaultLimitOffsetPage
from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, provision_main_agent from app.services.agent_provisioning import DEFAULT_HEARTBEAT_CONFIG, provision_main_agent
router = APIRouter(prefix="/gateways", tags=["gateways"]) router = APIRouter(prefix="/gateways", tags=["gateways"])
@@ -362,12 +363,13 @@ async def _send_skyll_disable_message(gateway: Gateway) -> None:
) )
@router.get("", response_model=list[GatewayRead]) @router.get("", response_model=DefaultLimitOffsetPage[GatewayRead])
async def list_gateways( async def list_gateways(
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
auth: AuthContext = Depends(get_auth_context), auth: AuthContext = Depends(get_auth_context),
) -> list[Gateway]: ) -> DefaultLimitOffsetPage[GatewayRead]:
return list(await session.exec(select(Gateway))) statement = select(Gateway).order_by(col(Gateway.created_at).desc())
return await paginate(session, statement)
@router.post("", response_model=GatewayRead) @router.post("", response_model=GatewayRead)

View File

@@ -25,16 +25,19 @@ from app.api.deps import (
) )
from app.core.auth import AuthContext from app.core.auth import AuthContext
from app.core.time import utcnow from app.core.time import utcnow
from app.db.pagination import paginate
from app.db.session import async_session_maker, get_session from app.db.session import async_session_maker, get_session
from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig from app.integrations.openclaw_gateway import GatewayConfig as GatewayClientConfig
from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message from app.integrations.openclaw_gateway import OpenClawGatewayError, ensure_session, send_message
from app.models.activity_events import ActivityEvent from app.models.activity_events import ActivityEvent
from app.models.agents import Agent from app.models.agents import Agent
from app.models.approvals import Approval
from app.models.boards import Board from app.models.boards import Board
from app.models.gateways import Gateway from app.models.gateways import Gateway
from app.models.task_fingerprints import TaskFingerprint from app.models.task_fingerprints import TaskFingerprint
from app.models.tasks import Task from app.models.tasks import Task
from app.schemas.common import OkResponse from app.schemas.common import OkResponse
from app.schemas.pagination import DefaultLimitOffsetPage
from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate from app.schemas.tasks import TaskCommentCreate, TaskCommentRead, TaskCreate, TaskRead, TaskUpdate
from app.services.activity_log import record_activity from app.services.activity_log import record_activity
@@ -410,16 +413,18 @@ async def stream_tasks(
return EventSourceResponse(event_generator(), ping=15) return EventSourceResponse(event_generator(), ping=15)
@router.get("", response_model=list[TaskRead]) @router.get("", response_model=DefaultLimitOffsetPage[TaskRead])
async def list_tasks( async def list_tasks(
status_filter: str | None = Query(default=None, alias="status"), status_filter: str | None = Query(default=None, alias="status"),
assigned_agent_id: UUID | None = None, assigned_agent_id: UUID | None = None,
unassigned: bool | None = None, unassigned: bool | None = None,
limit: int | None = Query(default=None, ge=1, le=200),
board: Board = Depends(get_board_or_404), board: Board = Depends(get_board_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[Task]: ) -> DefaultLimitOffsetPage[TaskRead]:
if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and actor.agent.board_id != board.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
statement = select(Task).where(Task.board_id == board.id) statement = select(Task).where(Task.board_id == board.id)
if status_filter: if status_filter:
statuses = [s.strip() for s in status_filter.split(",") if s.strip()] statuses = [s.strip() for s in status_filter.split(",") if s.strip()]
@@ -434,9 +439,8 @@ async def list_tasks(
statement = statement.where(col(Task.assigned_agent_id) == assigned_agent_id) statement = statement.where(col(Task.assigned_agent_id) == assigned_agent_id)
if unassigned: if unassigned:
statement = statement.where(col(Task.assigned_agent_id).is_(None)) statement = statement.where(col(Task.assigned_agent_id).is_(None))
if limit is not None: statement = statement.order_by(col(Task.created_at).desc())
statement = statement.limit(limit) return await paginate(session, statement)
return list(await session.exec(statement))
@router.post("", response_model=TaskRead) @router.post("", response_model=TaskRead)
@@ -661,17 +665,18 @@ async def delete_task(
) -> OkResponse: ) -> OkResponse:
await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id)) await session.execute(delete(ActivityEvent).where(col(ActivityEvent.task_id) == task.id))
await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id)) await session.execute(delete(TaskFingerprint).where(col(TaskFingerprint.task_id) == task.id))
await session.execute(delete(Approval).where(col(Approval.task_id) == task.id))
await session.delete(task) await session.delete(task)
await session.commit() await session.commit()
return OkResponse() return OkResponse()
@router.get("/{task_id}/comments", response_model=list[TaskCommentRead]) @router.get("/{task_id}/comments", response_model=DefaultLimitOffsetPage[TaskCommentRead])
async def list_task_comments( async def list_task_comments(
task: Task = Depends(get_task_or_404), task: Task = Depends(get_task_or_404),
session: AsyncSession = Depends(get_session), session: AsyncSession = Depends(get_session),
actor: ActorContext = Depends(require_admin_or_agent), actor: ActorContext = Depends(require_admin_or_agent),
) -> list[ActivityEvent]: ) -> DefaultLimitOffsetPage[TaskCommentRead]:
if actor.actor_type == "agent" and actor.agent: if actor.actor_type == "agent" and actor.agent:
if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id: if actor.agent.board_id and task.board_id and actor.agent.board_id != task.board_id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN) raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
@@ -681,7 +686,7 @@ async def list_task_comments(
.where(col(ActivityEvent.event_type) == "task.comment") .where(col(ActivityEvent.event_type) == "task.comment")
.order_by(asc(col(ActivityEvent.created_at))) .order_by(asc(col(ActivityEvent.created_at)))
) )
return list(await session.exec(statement)) return await paginate(session, statement)
@router.post("/{task_id}/comments", response_model=TaskCommentRead) @router.post("/{task_id}/comments", response_model=TaskCommentRead)

View File

@@ -1,11 +1,20 @@
from __future__ import annotations from __future__ import annotations
from pathlib import Path
from typing import Self
from pydantic import model_validator
from pydantic_settings import BaseSettings, SettingsConfigDict from pydantic_settings import BaseSettings, SettingsConfigDict
BACKEND_ROOT = Path(__file__).resolve().parents[2]
DEFAULT_ENV_FILE = BACKEND_ROOT / ".env"
class Settings(BaseSettings): class Settings(BaseSettings):
model_config = SettingsConfigDict( model_config = SettingsConfigDict(
env_file=".env", # Load `backend/.env` regardless of current working directory.
# (Important when running uvicorn from repo root or via a process manager.)
env_file=[DEFAULT_ENV_FILE, ".env"],
env_file_encoding="utf-8", env_file_encoding="utf-8",
extra="ignore", extra="ignore",
) )
@@ -30,5 +39,13 @@ class Settings(BaseSettings):
log_format: str = "text" log_format: str = "text"
log_use_utc: bool = False log_use_utc: bool = False
@model_validator(mode="after")
def _defaults(self) -> Self:
# In dev, default to applying Alembic migrations at startup to avoid schema drift
# (e.g. missing newly-added columns).
if "db_auto_migrate" not in self.model_fields_set and self.environment == "dev":
self.db_auto_migrate = True
return self
settings = Settings() settings = Settings()

View File

@@ -8,4 +8,3 @@ def utcnow() -> datetime:
# Keep naive UTC values for compatibility with existing DB schema/queries. # Keep naive UTC values for compatibility with existing DB schema/queries.
return datetime.now(UTC).replace(tzinfo=None) return datetime.now(UTC).replace(tzinfo=None)

View File

@@ -0,0 +1,28 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable, Sequence
from typing import Any, TypeVar, cast
from fastapi_pagination.ext.sqlalchemy import paginate as _paginate
from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel.sql.expression import Select, SelectOfScalar
from app.schemas.pagination import DefaultLimitOffsetPage
T = TypeVar("T")
Transformer = Callable[[Sequence[Any]], Sequence[Any] | Awaitable[Sequence[Any]]]
async def paginate(
session: AsyncSession,
statement: Select[Any] | SelectOfScalar[Any],
*,
transformer: Transformer | None = None,
) -> DefaultLimitOffsetPage[T]:
# fastapi-pagination is not fully typed (it returns Any), but response_model validation
# ensures runtime correctness. Centralize casts here to keep strict mypy clean.
return cast(
DefaultLimitOffsetPage[T],
await _paginate(session, statement, transformer=transformer),
)

View File

@@ -5,6 +5,7 @@ from contextlib import asynccontextmanager
from fastapi import APIRouter, FastAPI from fastapi import APIRouter, FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi_pagination import add_pagination
from app.api.activity import router as activity_router from app.api.activity import router as activity_router
from app.api.agent import router as agent_router from app.api.agent import router as agent_router
@@ -75,3 +76,5 @@ api_v1.include_router(approvals_router)
api_v1.include_router(tasks_router) api_v1.include_router(tasks_router)
api_v1.include_router(users_router) api_v1.include_router(users_router)
app.include_router(api_v1) app.include_router(api_v1)
add_pagination(app)

View File

@@ -14,6 +14,7 @@ class Approval(SQLModel, table=True):
id: UUID = Field(default_factory=uuid4, primary_key=True) id: UUID = Field(default_factory=uuid4, primary_key=True)
board_id: UUID = Field(foreign_key="boards.id", index=True) board_id: UUID = Field(foreign_key="boards.id", index=True)
task_id: UUID | None = Field(default=None, foreign_key="tasks.id", index=True)
agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True) agent_id: UUID | None = Field(default=None, foreign_key="agents.id", index=True)
action_type: str action_type: str
payload: dict[str, object] | None = Field(default=None, sa_column=Column(JSON)) payload: dict[str, object] | None = Field(default=None, sa_column=Column(JSON))

View File

@@ -16,5 +16,6 @@ class BoardMemory(SQLModel, table=True):
board_id: UUID = Field(foreign_key="boards.id", index=True) board_id: UUID = Field(foreign_key="boards.id", index=True)
content: str content: str
tags: list[str] | None = Field(default=None, sa_column=Column(JSON)) tags: list[str] | None = Field(default=None, sa_column=Column(JSON))
is_chat: bool = Field(default=False, index=True)
source: str | None = None source: str | None = None
created_at: datetime = Field(default_factory=utcnow) created_at: datetime = Field(default_factory=utcnow)

View File

@@ -13,6 +13,7 @@ ApprovalStatus = Literal["pending", "approved", "rejected"]
class ApprovalBase(SQLModel): class ApprovalBase(SQLModel):
action_type: str action_type: str
task_id: UUID | None = None
payload: dict[str, object] | None = None payload: dict[str, object] | None = None
confidence: int confidence: int
rubric_scores: dict[str, int] | None = None rubric_scores: dict[str, int] | None = None

View File

@@ -9,12 +9,18 @@ from app.schemas.common import NonEmptyStr
class BoardMemoryCreate(SQLModel): class BoardMemoryCreate(SQLModel):
# For writes, reject blank/whitespace-only content.
content: NonEmptyStr content: NonEmptyStr
tags: list[str] | None = None tags: list[str] | None = None
source: str | None = None source: str | None = None
class BoardMemoryRead(BoardMemoryCreate): class BoardMemoryRead(SQLModel):
id: UUID id: UUID
board_id: UUID board_id: UUID
# For reads, allow legacy rows that may have empty content (avoid response validation 500s).
content: str
tags: list[str] | None = None
source: str | None = None
is_chat: bool = False
created_at: datetime created_at: datetime

View File

@@ -0,0 +1,21 @@
from __future__ import annotations
from typing import TypeVar
from fastapi import Query
from fastapi_pagination.customization import CustomizedPage, UseParamsFields
from fastapi_pagination.limit_offset import LimitOffsetPage
T = TypeVar("T")
# Project-wide default pagination response model.
# - Keep `limit` / `offset` naming (matches existing API conventions).
# - Cap list endpoints to 200 items per request (matches prior route-level constraints).
DefaultLimitOffsetPage = CustomizedPage[
LimitOffsetPage[T],
UseParamsFields(
limit=Query(200, ge=1, le=200),
offset=Query(0, ge=0),
),
]

View File

@@ -0,0 +1,24 @@
from __future__ import annotations
from sqlmodel import SQLModel
from app.schemas.agents import AgentRead
from app.schemas.approvals import ApprovalRead
from app.schemas.board_memory import BoardMemoryRead
from app.schemas.boards import BoardRead
from app.schemas.tasks import TaskRead
class TaskCardRead(TaskRead):
assignee: str | None = None
approvals_count: int = 0
approvals_pending_count: int = 0
class BoardSnapshot(SQLModel):
board: BoardRead
tasks: list[TaskCardRead]
agents: list[AgentRead]
approvals: list[ApprovalRead]
chat_messages: list[BoardMemoryRead]
pending_approvals_count: int = 0

View File

@@ -0,0 +1,158 @@
from __future__ import annotations
from datetime import timedelta
from uuid import UUID
from sqlalchemy import case, func
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
from app.core.time import utcnow
from app.models.agents import Agent
from app.models.approvals import Approval
from app.models.board_memory import BoardMemory
from app.models.boards import Board
from app.models.gateways import Gateway
from app.models.tasks import Task
from app.schemas.agents import AgentRead
from app.schemas.approvals import ApprovalRead
from app.schemas.board_memory import BoardMemoryRead
from app.schemas.boards import BoardRead
from app.schemas.view_models import BoardSnapshot, TaskCardRead
OFFLINE_AFTER = timedelta(minutes=10)
def _computed_agent_status(agent: Agent) -> str:
now = utcnow()
if agent.status in {"deleting", "updating"}:
return agent.status
if agent.last_seen_at is None:
return "provisioning"
if now - agent.last_seen_at > OFFLINE_AFTER:
return "offline"
return agent.status
async def _gateway_main_session_keys(session: AsyncSession) -> set[str]:
keys = (await session.exec(select(Gateway.main_session_key))).all()
return {key for key in keys if key}
def _agent_to_read(agent: Agent, main_session_keys: set[str]) -> AgentRead:
model = AgentRead.model_validate(agent, from_attributes=True)
computed_status = _computed_agent_status(agent)
is_gateway_main = bool(agent.openclaw_session_id and agent.openclaw_session_id in main_session_keys)
return model.model_copy(update={"status": computed_status, "is_gateway_main": is_gateway_main})
def _memory_to_read(memory: BoardMemory) -> BoardMemoryRead:
return BoardMemoryRead.model_validate(memory, from_attributes=True)
def _approval_to_read(approval: Approval) -> ApprovalRead:
return ApprovalRead.model_validate(approval, from_attributes=True)
def _task_to_card(
task: Task,
*,
agent_name_by_id: dict[UUID, str],
counts_by_task_id: dict[UUID, tuple[int, int]],
) -> TaskCardRead:
card = TaskCardRead.model_validate(task, from_attributes=True)
approvals_count, approvals_pending_count = counts_by_task_id.get(task.id, (0, 0))
assignee = (
agent_name_by_id.get(task.assigned_agent_id) if task.assigned_agent_id is not None else None
)
return card.model_copy(
update={
"assignee": assignee,
"approvals_count": approvals_count,
"approvals_pending_count": approvals_pending_count,
}
)
async def build_board_snapshot(session: AsyncSession, board: Board) -> BoardSnapshot:
board_read = BoardRead.model_validate(board, from_attributes=True)
tasks = list(
await session.exec(
select(Task).where(col(Task.board_id) == board.id).order_by(col(Task.created_at).desc())
)
)
main_session_keys = await _gateway_main_session_keys(session)
agents = list(
await session.exec(
select(Agent).where(col(Agent.board_id) == board.id).order_by(col(Agent.created_at).desc())
)
)
agent_reads = [_agent_to_read(agent, main_session_keys) for agent in agents]
agent_name_by_id = {agent.id: agent.name for agent in agents}
pending_approvals_count = int(
(await session.exec(
select(func.count(col(Approval.id)))
.where(col(Approval.board_id) == board.id)
.where(col(Approval.status) == "pending")
)).one()
)
approvals = list(
await session.exec(
select(Approval)
.where(col(Approval.board_id) == board.id)
.order_by(col(Approval.created_at).desc())
.limit(200)
)
)
approval_reads = [_approval_to_read(approval) for approval in approvals]
counts_by_task_id: dict[UUID, tuple[int, int]] = {}
rows = list(
await session.exec(
select(
col(Approval.task_id),
func.count(col(Approval.id)).label("total"),
func.sum(case((col(Approval.status) == "pending", 1), else_=0)).label("pending"),
)
.where(col(Approval.board_id) == board.id)
.where(col(Approval.task_id).is_not(None))
.group_by(col(Approval.task_id))
)
)
for task_id, total, pending in rows:
if task_id is None:
continue
counts_by_task_id[task_id] = (int(total or 0), int(pending or 0))
task_cards = [
_task_to_card(task, agent_name_by_id=agent_name_by_id, counts_by_task_id=counts_by_task_id)
for task in tasks
]
chat_messages = list(
await session.exec(
select(BoardMemory)
.where(col(BoardMemory.board_id) == board.id)
.where(col(BoardMemory.is_chat).is_(True))
# Old/invalid rows (empty/whitespace-only content) can exist; exclude them to
# satisfy the NonEmptyStr response schema.
.where(func.length(func.trim(col(BoardMemory.content))) > 0)
.order_by(col(BoardMemory.created_at).desc())
.limit(200)
)
)
chat_messages.sort(key=lambda item: item.created_at)
chat_reads = [_memory_to_read(memory) for memory in chat_messages]
return BoardSnapshot(
board=board_read,
tasks=task_cards,
agents=agent_reads,
approvals=approval_reads,
chat_messages=chat_reads,
pending_approvals_count=pending_approvals_count,
)

View File

@@ -12,7 +12,7 @@ name = "openclaw-agency-backend"
version = "0.1.0" version = "0.1.0"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"fastapi==0.115.4", "fastapi==0.128.0",
"uvicorn[standard]==0.30.6", "uvicorn[standard]==0.30.6",
"sqlmodel==0.0.22", "sqlmodel==0.0.22",
"sqlalchemy==2.0.34", "sqlalchemy==2.0.34",
@@ -25,7 +25,8 @@ dependencies = [
"redis==5.1.1", "redis==5.1.1",
"fastapi-clerk-auth==0.0.9", "fastapi-clerk-auth==0.0.9",
"sse-starlette==2.1.3", "sse-starlette==2.1.3",
"jinja2" "jinja2==3.1.6",
"fastapi-pagination==0.15.9",
] ]
[project.optional-dependencies] [project.optional-dependencies]

View File

@@ -1,4 +0,0 @@
pytest==8.3.3
pytest-asyncio==0.24.0
ruff==0.6.9
mypy==1.11.2

View File

@@ -1,14 +0,0 @@
fastapi==0.115.4
uvicorn[standard]==0.30.6
sqlmodel==0.0.22
sqlalchemy==2.0.34
alembic==1.13.2
psycopg[binary]==3.2.1
pydantic-settings==2.5.2
python-dotenv==1.0.1
websockets==12.0
rq==1.16.2
redis==5.1.1
fastapi-clerk-auth==0.0.9
sse-starlette==2.1.3
jinja2==3.1.6

40
backend/uv.lock generated
View File

@@ -16,6 +16,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990, upload-time = "2024-06-26T15:46:21.088Z" }, { url = "https://files.pythonhosted.org/packages/df/ed/c884465c33c25451e4a5cd4acad154c29e5341e3214e220e7f3478aa4b0d/alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953", size = 232990, upload-time = "2024-06-26T15:46:21.088Z" },
] ]
[[package]]
name = "annotated-doc"
version = "0.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
]
[[package]] [[package]]
name = "annotated-types" name = "annotated-types"
version = "0.7.0" version = "0.7.0"
@@ -177,16 +186,17 @@ wheels = [
[[package]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.115.4" version = "0.128.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "annotated-doc" },
{ name = "pydantic" }, { name = "pydantic" },
{ name = "starlette" }, { name = "starlette" },
{ name = "typing-extensions" }, { name = "typing-extensions" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/a9/db/5781f19bd30745885e0737ff3fdd4e63e7bc691710f9da691128bb0dc73b/fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349", size = 300737, upload-time = "2024-10-27T22:02:04.678Z" } sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/f6/af0d1f58f86002be0cf1e2665cdd6f7a4a71cdc8a7a9438cdc9e3b5375fe/fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742", size = 94732, upload-time = "2024-10-27T22:02:00.974Z" }, { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" },
] ]
[[package]] [[package]]
@@ -203,6 +213,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/79/4e/058ecbe4fa0d470c3979f1272c0199cc47afb0ed935edb07b55441be8994/fastapi_clerk_auth-0.0.9-py3-none-any.whl", hash = "sha256:f9a47cfc65a2562c144a798ce0022a288799dac1149001b5a109865d578b2647", size = 6464, upload-time = "2025-11-11T06:12:35.655Z" }, { url = "https://files.pythonhosted.org/packages/79/4e/058ecbe4fa0d470c3979f1272c0199cc47afb0ed935edb07b55441be8994/fastapi_clerk_auth-0.0.9-py3-none-any.whl", hash = "sha256:f9a47cfc65a2562c144a798ce0022a288799dac1149001b5a109865d578b2647", size = 6464, upload-time = "2025-11-11T06:12:35.655Z" },
] ]
[[package]]
name = "fastapi-pagination"
version = "0.15.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "fastapi" },
{ name = "pydantic" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/48/4b/057fea634912ba285e71fa9b65594c9cc90f589ad25ccbdc7549202c12a2/fastapi_pagination-0.15.9.tar.gz", hash = "sha256:e24b0419a6077a75f38970ada2a57e277845fb177cc2da7300374ee8be32e8b5", size = 574790, upload-time = "2026-02-02T18:29:20.863Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c8/7e/7d1a66b618309ef3d1d969ccab187a8c09444e6eb8d3abcd3bc12f145578/fastapi_pagination-0.15.9-py3-none-any.whl", hash = "sha256:21f5ab465fb75e21a3454234603071711b679b1973ba2428e7a63db2221abc09", size = 60100, upload-time = "2026-02-02T18:29:19.363Z" },
]
[[package]] [[package]]
name = "flake8" name = "flake8"
version = "7.1.1" version = "7.1.1"
@@ -226,7 +250,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" }, { url = "https://files.pythonhosted.org/packages/f9/c8/9d76a66421d1ae24340dfae7e79c313957f6e3195c144d2c73333b5bfe34/greenlet-3.3.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:7e806ca53acf6d15a888405880766ec84721aa4181261cd11a457dfe9a7a4975", size = 276443, upload-time = "2026-01-23T15:30:10.066Z" },
{ url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" }, { url = "https://files.pythonhosted.org/packages/81/99/401ff34bb3c032d1f10477d199724f5e5f6fbfb59816ad1455c79c1eb8e7/greenlet-3.3.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d842c94b9155f1c9b3058036c24ffb8ff78b428414a19792b2380be9cecf4f36", size = 597359, upload-time = "2026-01-23T16:00:57.394Z" },
{ url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" }, { url = "https://files.pythonhosted.org/packages/2b/bc/4dcc0871ed557792d304f50be0f7487a14e017952ec689effe2180a6ff35/greenlet-3.3.1-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:20fedaadd422fa02695f82093f9a98bad3dab5fcda793c658b945fcde2ab27ba", size = 607805, upload-time = "2026-01-23T16:05:28.068Z" },
{ url = "https://files.pythonhosted.org/packages/3b/cd/7a7ca57588dac3389e97f7c9521cb6641fd8b6602faf1eaa4188384757df/greenlet-3.3.1-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c620051669fd04ac6b60ebc70478210119c56e2d5d5df848baec4312e260e4ca", size = 622363, upload-time = "2026-01-23T16:15:54.754Z" },
{ url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" }, { url = "https://files.pythonhosted.org/packages/cf/05/821587cf19e2ce1f2b24945d890b164401e5085f9d09cbd969b0c193cd20/greenlet-3.3.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14194f5f4305800ff329cbf02c5fcc88f01886cadd29941b807668a45f0d2336", size = 609947, upload-time = "2026-01-23T15:32:51.004Z" },
{ url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" }, { url = "https://files.pythonhosted.org/packages/a4/52/ee8c46ed9f8babaa93a19e577f26e3d28a519feac6350ed6f25f1afee7e9/greenlet-3.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7b2fe4150a0cf59f847a67db8c155ac36aed89080a6a639e9f16df5d6c6096f1", size = 1567487, upload-time = "2026-01-23T16:04:22.125Z" },
{ url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" }, { url = "https://files.pythonhosted.org/packages/8f/7c/456a74f07029597626f3a6db71b273a3632aecb9afafeeca452cfa633197/greenlet-3.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49f4ad195d45f4a66a0eb9c1ba4832bb380570d361912fa3554746830d332149", size = 1636087, upload-time = "2026-01-23T15:33:47.486Z" },
@@ -235,7 +258,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" }, { url = "https://files.pythonhosted.org/packages/ec/ab/d26750f2b7242c2b90ea2ad71de70cfcd73a948a49513188a0fc0d6fc15a/greenlet-3.3.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:7ab327905cabb0622adca5971e488064e35115430cec2c35a50fd36e72a315b3", size = 275205, upload-time = "2026-01-23T15:30:24.556Z" },
{ url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" }, { url = "https://files.pythonhosted.org/packages/10/d3/be7d19e8fad7c5a78eeefb2d896a08cd4643e1e90c605c4be3b46264998f/greenlet-3.3.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:65be2f026ca6a176f88fb935ee23c18333ccea97048076aef4db1ef5bc0713ac", size = 599284, upload-time = "2026-01-23T16:00:58.584Z" },
{ url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" }, { url = "https://files.pythonhosted.org/packages/ae/21/fe703aaa056fdb0f17e5afd4b5c80195bbdab701208918938bd15b00d39b/greenlet-3.3.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7a3ae05b3d225b4155bda56b072ceb09d05e974bc74be6c3fc15463cf69f33fd", size = 610274, upload-time = "2026-01-23T16:05:29.312Z" },
{ url = "https://files.pythonhosted.org/packages/06/00/95df0b6a935103c0452dad2203f5be8377e551b8466a29650c4c5a5af6cc/greenlet-3.3.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:12184c61e5d64268a160226fb4818af4df02cfead8379d7f8b99a56c3a54ff3e", size = 624375, upload-time = "2026-01-23T16:15:55.915Z" },
{ url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" }, { url = "https://files.pythonhosted.org/packages/cb/86/5c6ab23bb3c28c21ed6bebad006515cfe08b04613eb105ca0041fecca852/greenlet-3.3.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6423481193bbbe871313de5fd06a082f2649e7ce6e08015d2a76c1e9186ca5b3", size = 612904, upload-time = "2026-01-23T15:32:52.317Z" },
{ url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" }, { url = "https://files.pythonhosted.org/packages/c2/f3/7949994264e22639e40718c2daf6f6df5169bf48fb038c008a489ec53a50/greenlet-3.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:33a956fe78bbbda82bfc95e128d61129b32d66bcf0a20a1f0c08aa4839ffa951", size = 1567316, upload-time = "2026-01-23T16:04:23.316Z" },
{ url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" }, { url = "https://files.pythonhosted.org/packages/8d/6e/d73c94d13b6465e9f7cd6231c68abde838bb22408596c05d9059830b7872/greenlet-3.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b065d3284be43728dd280f6f9a13990b56470b81be20375a207cdc814a983f2", size = 1636549, upload-time = "2026-01-23T15:33:48.643Z" },
@@ -244,7 +266,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" }, { url = "https://files.pythonhosted.org/packages/ae/fb/011c7c717213182caf78084a9bea51c8590b0afda98001f69d9f853a495b/greenlet-3.3.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:bd59acd8529b372775cd0fcbc5f420ae20681c5b045ce25bd453ed8455ab99b5", size = 275737, upload-time = "2026-01-23T15:32:16.889Z" },
{ url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" }, { url = "https://files.pythonhosted.org/packages/41/2e/a3a417d620363fdbb08a48b1dd582956a46a61bf8fd27ee8164f9dfe87c2/greenlet-3.3.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31c05dd84ef6871dd47120386aed35323c944d86c3d91a17c4b8d23df62f15b", size = 646422, upload-time = "2026-01-23T16:01:00.354Z" },
{ url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" }, { url = "https://files.pythonhosted.org/packages/b4/09/c6c4a0db47defafd2d6bab8ddfe47ad19963b4e30f5bed84d75328059f8c/greenlet-3.3.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:02925a0bfffc41e542c70aa14c7eda3593e4d7e274bfcccca1827e6c0875902e", size = 658219, upload-time = "2026-01-23T16:05:30.956Z" },
{ url = "https://files.pythonhosted.org/packages/e2/89/b95f2ddcc5f3c2bc09c8ee8d77be312df7f9e7175703ab780f2014a0e781/greenlet-3.3.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3e0f3878ca3a3ff63ab4ea478585942b53df66ddde327b59ecb191b19dbbd62d", size = 671455, upload-time = "2026-01-23T16:15:57.232Z" },
{ url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" }, { url = "https://files.pythonhosted.org/packages/80/38/9d42d60dffb04b45f03dbab9430898352dba277758640751dc5cc316c521/greenlet-3.3.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:34a729e2e4e4ffe9ae2408d5ecaf12f944853f40ad724929b7585bca808a9d6f", size = 660237, upload-time = "2026-01-23T15:32:53.967Z" },
{ url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" }, { url = "https://files.pythonhosted.org/packages/96/61/373c30b7197f9e756e4c81ae90a8d55dc3598c17673f91f4d31c3c689c3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aec9ab04e82918e623415947921dea15851b152b822661cce3f8e4393c3df683", size = 1615261, upload-time = "2026-01-23T16:04:25.066Z" },
{ url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" }, { url = "https://files.pythonhosted.org/packages/fd/d3/ca534310343f5945316f9451e953dcd89b36fe7a19de652a1dc5a0eeef3f/greenlet-3.3.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:71c767cf281a80d02b6c1bdc41c9468e1f5a494fb11bc8688c360524e273d7b1", size = 1683719, upload-time = "2026-01-23T15:33:50.61Z" },
@@ -253,7 +274,6 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" }, { url = "https://files.pythonhosted.org/packages/28/24/cbbec49bacdcc9ec652a81d3efef7b59f326697e7edf6ed775a5e08e54c2/greenlet-3.3.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:3e63252943c921b90abb035ebe9de832c436401d9c45f262d80e2d06cc659242", size = 282706, upload-time = "2026-01-23T15:33:05.525Z" },
{ url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" }, { url = "https://files.pythonhosted.org/packages/86/2e/4f2b9323c144c4fe8842a4e0d92121465485c3c2c5b9e9b30a52e80f523f/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:76e39058e68eb125de10c92524573924e827927df5d3891fbc97bd55764a8774", size = 651209, upload-time = "2026-01-23T16:01:01.517Z" },
{ url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" }, { url = "https://files.pythonhosted.org/packages/d9/87/50ca60e515f5bb55a2fbc5f0c9b5b156de7d2fc51a0a69abc9d23914a237/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c9f9d5e7a9310b7a2f416dd13d2e3fd8b42d803968ea580b7c0f322ccb389b97", size = 654300, upload-time = "2026-01-23T16:05:32.199Z" },
{ url = "https://files.pythonhosted.org/packages/7c/25/c51a63f3f463171e09cb586eb64db0861eb06667ab01a7968371a24c4f3b/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b9721549a95db96689458a1e0ae32412ca18776ed004463df3a9299c1b257ab", size = 662574, upload-time = "2026-01-23T16:15:58.364Z" },
{ url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" }, { url = "https://files.pythonhosted.org/packages/1d/94/74310866dfa2b73dd08659a3d18762f83985ad3281901ba0ee9a815194fb/greenlet-3.3.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:92497c78adf3ac703b57f1e3813c2d874f27f71a178f9ea5887855da413cd6d2", size = 653842, upload-time = "2026-01-23T15:32:55.671Z" },
{ url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" }, { url = "https://files.pythonhosted.org/packages/97/43/8bf0ffa3d498eeee4c58c212a3905dd6146c01c8dc0b0a046481ca29b18c/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ed6b402bc74d6557a705e197d47f9063733091ed6357b3de33619d8a8d93ac53", size = 1614917, upload-time = "2026-01-23T16:04:26.276Z" },
{ url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" }, { url = "https://files.pythonhosted.org/packages/89/90/a3be7a5f378fc6e84abe4dcfb2ba32b07786861172e502388b4c90000d1b/greenlet-3.3.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:59913f1e5ada20fde795ba906916aea25d442abcc0593fba7e26c92b7ad76249", size = 1676092, upload-time = "2026-01-23T15:33:52.176Z" },
@@ -456,6 +476,7 @@ dependencies = [
{ name = "alembic" }, { name = "alembic" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "fastapi-clerk-auth" }, { name = "fastapi-clerk-auth" },
{ name = "fastapi-pagination" },
{ name = "jinja2" }, { name = "jinja2" },
{ name = "psycopg", extra = ["binary"] }, { name = "psycopg", extra = ["binary"] },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
@@ -484,11 +505,12 @@ dev = [
requires-dist = [ requires-dist = [
{ name = "alembic", specifier = "==1.13.2" }, { name = "alembic", specifier = "==1.13.2" },
{ name = "black", marker = "extra == 'dev'", specifier = "==24.10.0" }, { name = "black", marker = "extra == 'dev'", specifier = "==24.10.0" },
{ name = "fastapi", specifier = "==0.115.4" }, { name = "fastapi", specifier = "==0.128.0" },
{ name = "fastapi-clerk-auth", specifier = "==0.0.9" }, { name = "fastapi-clerk-auth", specifier = "==0.0.9" },
{ name = "fastapi-pagination", specifier = "==0.15.9" },
{ name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" }, { name = "flake8", marker = "extra == 'dev'", specifier = "==7.1.1" },
{ name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" }, { name = "isort", marker = "extra == 'dev'", specifier = "==5.13.2" },
{ name = "jinja2" }, { name = "jinja2", specifier = "==3.1.6" },
{ name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.2" }, { name = "mypy", marker = "extra == 'dev'", specifier = "==1.11.2" },
{ name = "psycopg", extras = ["binary"], specifier = "==3.2.1" }, { name = "psycopg", extras = ["binary"], specifier = "==3.2.1" },
{ name = "pydantic-settings", specifier = "==2.5.2" }, { name = "pydantic-settings", specifier = "==2.5.2" },

View File

@@ -18,8 +18,8 @@ import type {
} from "@tanstack/react-query"; } from "@tanstack/react-query";
import type { import type {
ActivityEventRead,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedActivityEventRead,
ListActivityApiV1ActivityGetParams, ListActivityApiV1ActivityGetParams,
} from ".././model"; } from ".././model";
@@ -31,7 +31,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Activity * @summary List Activity
*/ */
export type listActivityApiV1ActivityGetResponse200 = { export type listActivityApiV1ActivityGetResponse200 = {
data: ActivityEventRead[]; data: LimitOffsetPageTypeVarCustomizedActivityEventRead;
status: 200; status: 200;
}; };

View File

@@ -34,9 +34,17 @@ import type {
BoardOnboardingRead, BoardOnboardingRead,
BoardRead, BoardRead,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedAgentRead,
LimitOffsetPageTypeVarCustomizedApprovalRead,
LimitOffsetPageTypeVarCustomizedBoardMemoryRead,
LimitOffsetPageTypeVarCustomizedBoardRead,
LimitOffsetPageTypeVarCustomizedTaskCommentRead,
LimitOffsetPageTypeVarCustomizedTaskRead,
ListAgentsApiV1AgentAgentsGetParams, ListAgentsApiV1AgentAgentsGetParams,
ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams, ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams,
ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams, ListBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams,
ListBoardsApiV1AgentBoardsGetParams,
ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
ListTasksApiV1AgentBoardsBoardIdTasksGetParams, ListTasksApiV1AgentBoardsBoardIdTasksGetParams,
OkResponse, OkResponse,
TaskCommentCreate, TaskCommentCreate,
@@ -54,7 +62,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Boards * @summary List Boards
*/ */
export type listBoardsApiV1AgentBoardsGetResponse200 = { export type listBoardsApiV1AgentBoardsGetResponse200 = {
data: BoardRead[]; data: LimitOffsetPageTypeVarCustomizedBoardRead;
status: 200; status: 200;
}; };
@@ -76,15 +84,30 @@ export type listBoardsApiV1AgentBoardsGetResponse =
| listBoardsApiV1AgentBoardsGetResponseSuccess | listBoardsApiV1AgentBoardsGetResponseSuccess
| listBoardsApiV1AgentBoardsGetResponseError; | listBoardsApiV1AgentBoardsGetResponseError;
export const getListBoardsApiV1AgentBoardsGetUrl = () => { export const getListBoardsApiV1AgentBoardsGetUrl = (
return `/api/v1/agent/boards`; params?: ListBoardsApiV1AgentBoardsGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/agent/boards?${stringifiedParams}`
: `/api/v1/agent/boards`;
}; };
export const listBoardsApiV1AgentBoardsGet = async ( export const listBoardsApiV1AgentBoardsGet = async (
params?: ListBoardsApiV1AgentBoardsGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listBoardsApiV1AgentBoardsGetResponse> => { ): Promise<listBoardsApiV1AgentBoardsGetResponse> => {
return customFetch<listBoardsApiV1AgentBoardsGetResponse>( return customFetch<listBoardsApiV1AgentBoardsGetResponse>(
getListBoardsApiV1AgentBoardsGetUrl(), getListBoardsApiV1AgentBoardsGetUrl(params),
{ {
...options, ...options,
method: "GET", method: "GET",
@@ -92,32 +115,37 @@ export const listBoardsApiV1AgentBoardsGet = async (
); );
}; };
export const getListBoardsApiV1AgentBoardsGetQueryKey = () => { export const getListBoardsApiV1AgentBoardsGetQueryKey = (
return [`/api/v1/agent/boards`] as const; params?: ListBoardsApiV1AgentBoardsGetParams,
) => {
return [`/api/v1/agent/boards`, ...(params ? [params] : [])] as const;
}; };
export const getListBoardsApiV1AgentBoardsGetQueryOptions = < export const getListBoardsApiV1AgentBoardsGetQueryOptions = <
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>(options?: { >(
query?: Partial< params?: ListBoardsApiV1AgentBoardsGetParams,
UseQueryOptions< options?: {
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, query?: Partial<
TError, UseQueryOptions<
TData Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
> TError,
>; TData
request?: SecondParameter<typeof customFetch>; >
}) => { >;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {}; const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? getListBoardsApiV1AgentBoardsGetQueryKey(); queryOptions?.queryKey ?? getListBoardsApiV1AgentBoardsGetQueryKey(params);
const queryFn: QueryFunction< const queryFn: QueryFunction<
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>> Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>
> = ({ signal }) => > = ({ signal }) =>
listBoardsApiV1AgentBoardsGet({ signal, ...requestOptions }); listBoardsApiV1AgentBoardsGet(params, { signal, ...requestOptions });
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
@@ -135,6 +163,7 @@ export function useListBoardsApiV1AgentBoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params: undefined | ListBoardsApiV1AgentBoardsGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -161,6 +190,7 @@ export function useListBoardsApiV1AgentBoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1AgentBoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -187,6 +217,7 @@ export function useListBoardsApiV1AgentBoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1AgentBoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -209,6 +240,7 @@ export function useListBoardsApiV1AgentBoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1AgentBoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1AgentBoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -223,7 +255,10 @@ export function useListBoardsApiV1AgentBoardsGet<
): UseQueryResult<TData, TError> & { ): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>; queryKey: DataTag<QueryKey, TData, TError>;
} { } {
const queryOptions = getListBoardsApiV1AgentBoardsGetQueryOptions(options); const queryOptions = getListBoardsApiV1AgentBoardsGetQueryOptions(
params,
options,
);
const query = useQuery(queryOptions, queryClient) as UseQueryResult< const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData, TData,
@@ -439,7 +474,7 @@ export function useGetBoardApiV1AgentBoardsBoardIdGet<
* @summary List Agents * @summary List Agents
*/ */
export type listAgentsApiV1AgentAgentsGetResponse200 = { export type listAgentsApiV1AgentAgentsGetResponse200 = {
data: AgentRead[]; data: LimitOffsetPageTypeVarCustomizedAgentRead;
status: 200; status: 200;
}; };
@@ -766,7 +801,7 @@ export const useCreateAgentApiV1AgentAgentsPost = <
* @summary List Tasks * @summary List Tasks
*/ */
export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 = { export type listTasksApiV1AgentBoardsBoardIdTasksGetResponse200 = {
data: TaskRead[]; data: LimitOffsetPageTypeVarCustomizedTaskRead;
status: 200; status: 200;
}; };
@@ -1265,7 +1300,7 @@ export const useUpdateTaskApiV1AgentBoardsBoardIdTasksTaskIdPatch = <
*/ */
export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 = export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse200 =
{ {
data: TaskCommentRead[]; data: LimitOffsetPageTypeVarCustomizedTaskCommentRead;
status: 200; status: 200;
}; };
@@ -1290,20 +1325,41 @@ export type listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetRespons
| listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError; | listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponseError;
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl = export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl =
(boardId: string, taskId: string) => { (
return `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`; boardId: string,
taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(
key,
value === null ? "null" : value.toString(),
);
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments?${stringifiedParams}`
: `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`;
}; };
export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet = export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet =
async ( async (
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse> => { ): Promise<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse> => {
return customFetch<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse>( return customFetch<listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetResponse>(
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl( getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetUrl(
boardId, boardId,
taskId, taskId,
params,
), ),
{ {
...options, ...options,
@@ -1313,9 +1369,14 @@ export const listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet =
}; };
export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey = export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey =
(boardId: string, taskId: string) => { (
boardId: string,
taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
) => {
return [ return [
`/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`, `/api/v1/agent/boards/${boardId}/tasks/${taskId}/comments`,
...(params ? [params] : []),
] as const; ] as const;
}; };
@@ -1330,6 +1391,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1352,6 +1414,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey( getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryKey(
boardId, boardId,
taskId, taskId,
params,
); );
const queryFn: QueryFunction< const queryFn: QueryFunction<
@@ -1364,6 +1427,7 @@ export const getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQue
listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet( listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet(
boardId, boardId,
taskId, taskId,
params,
{ signal, ...requestOptions }, { signal, ...requestOptions },
); );
@@ -1404,6 +1468,9 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params:
| undefined
| ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1448,6 +1515,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1492,6 +1560,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1524,6 +1593,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1546,6 +1616,7 @@ export function useListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGet
getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions( getListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
boardId, boardId,
taskId, taskId,
params,
options, options,
); );
@@ -1720,7 +1791,7 @@ export const useCreateTaskCommentApiV1AgentBoardsBoardIdTasksTaskIdCommentsPost
* @summary List Board Memory * @summary List Board Memory
*/ */
export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 = { export type listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetResponse200 = {
data: BoardMemoryRead[]; data: LimitOffsetPageTypeVarCustomizedBoardMemoryRead;
status: 200; status: 200;
}; };
@@ -2121,7 +2192,7 @@ export const useCreateBoardMemoryApiV1AgentBoardsBoardIdMemoryPost = <
* @summary List Approvals * @summary List Approvals
*/ */
export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 = { export type listApprovalsApiV1AgentBoardsBoardIdApprovalsGetResponse200 = {
data: ApprovalRead[]; data: LimitOffsetPageTypeVarCustomizedApprovalRead;
status: 200; status: 200;
}; };

View File

@@ -27,6 +27,8 @@ import type {
AgentRead, AgentRead,
AgentUpdate, AgentUpdate,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedAgentRead,
ListAgentsApiV1AgentsGetParams,
OkResponse, OkResponse,
StreamAgentsApiV1AgentsStreamGetParams, StreamAgentsApiV1AgentsStreamGetParams,
UpdateAgentApiV1AgentsAgentIdPatchParams, UpdateAgentApiV1AgentsAgentIdPatchParams,
@@ -40,26 +42,52 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Agents * @summary List Agents
*/ */
export type listAgentsApiV1AgentsGetResponse200 = { export type listAgentsApiV1AgentsGetResponse200 = {
data: AgentRead[]; data: LimitOffsetPageTypeVarCustomizedAgentRead;
status: 200; status: 200;
}; };
export type listAgentsApiV1AgentsGetResponse422 = {
data: HTTPValidationError;
status: 422;
};
export type listAgentsApiV1AgentsGetResponseSuccess = export type listAgentsApiV1AgentsGetResponseSuccess =
listAgentsApiV1AgentsGetResponse200 & { listAgentsApiV1AgentsGetResponse200 & {
headers: Headers; headers: Headers;
}; };
export type listAgentsApiV1AgentsGetResponse = export type listAgentsApiV1AgentsGetResponseError =
listAgentsApiV1AgentsGetResponseSuccess; listAgentsApiV1AgentsGetResponse422 & {
headers: Headers;
};
export const getListAgentsApiV1AgentsGetUrl = () => { export type listAgentsApiV1AgentsGetResponse =
return `/api/v1/agents`; | listAgentsApiV1AgentsGetResponseSuccess
| listAgentsApiV1AgentsGetResponseError;
export const getListAgentsApiV1AgentsGetUrl = (
params?: ListAgentsApiV1AgentsGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/agents?${stringifiedParams}`
: `/api/v1/agents`;
}; };
export const listAgentsApiV1AgentsGet = async ( export const listAgentsApiV1AgentsGet = async (
params?: ListAgentsApiV1AgentsGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listAgentsApiV1AgentsGetResponse> => { ): Promise<listAgentsApiV1AgentsGetResponse> => {
return customFetch<listAgentsApiV1AgentsGetResponse>( return customFetch<listAgentsApiV1AgentsGetResponse>(
getListAgentsApiV1AgentsGetUrl(), getListAgentsApiV1AgentsGetUrl(params),
{ {
...options, ...options,
method: "GET", method: "GET",
@@ -67,31 +95,37 @@ export const listAgentsApiV1AgentsGet = async (
); );
}; };
export const getListAgentsApiV1AgentsGetQueryKey = () => { export const getListAgentsApiV1AgentsGetQueryKey = (
return [`/api/v1/agents`] as const; params?: ListAgentsApiV1AgentsGetParams,
) => {
return [`/api/v1/agents`, ...(params ? [params] : [])] as const;
}; };
export const getListAgentsApiV1AgentsGetQueryOptions = < export const getListAgentsApiV1AgentsGetQueryOptions = <
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
TError = unknown, TError = HTTPValidationError,
>(options?: { >(
query?: Partial< params?: ListAgentsApiV1AgentsGetParams,
UseQueryOptions< options?: {
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, query?: Partial<
TError, UseQueryOptions<
TData Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
> TError,
>; TData
request?: SecondParameter<typeof customFetch>; >
}) => { >;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {}; const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? getListAgentsApiV1AgentsGetQueryKey(); queryOptions?.queryKey ?? getListAgentsApiV1AgentsGetQueryKey(params);
const queryFn: QueryFunction< const queryFn: QueryFunction<
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>> Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
> = ({ signal }) => listAgentsApiV1AgentsGet({ signal, ...requestOptions }); > = ({ signal }) =>
listAgentsApiV1AgentsGet(params, { signal, ...requestOptions });
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
@@ -103,12 +137,13 @@ export const getListAgentsApiV1AgentsGetQueryOptions = <
export type ListAgentsApiV1AgentsGetQueryResult = NonNullable< export type ListAgentsApiV1AgentsGetQueryResult = NonNullable<
Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>> Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>
>; >;
export type ListAgentsApiV1AgentsGetQueryError = unknown; export type ListAgentsApiV1AgentsGetQueryError = HTTPValidationError;
export function useListAgentsApiV1AgentsGet< export function useListAgentsApiV1AgentsGet<
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params: undefined | ListAgentsApiV1AgentsGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -133,8 +168,9 @@ export function useListAgentsApiV1AgentsGet<
}; };
export function useListAgentsApiV1AgentsGet< export function useListAgentsApiV1AgentsGet<
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListAgentsApiV1AgentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -159,8 +195,9 @@ export function useListAgentsApiV1AgentsGet<
}; };
export function useListAgentsApiV1AgentsGet< export function useListAgentsApiV1AgentsGet<
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListAgentsApiV1AgentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -181,8 +218,9 @@ export function useListAgentsApiV1AgentsGet<
export function useListAgentsApiV1AgentsGet< export function useListAgentsApiV1AgentsGet<
TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>, TData = Awaited<ReturnType<typeof listAgentsApiV1AgentsGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListAgentsApiV1AgentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -197,7 +235,7 @@ export function useListAgentsApiV1AgentsGet<
): UseQueryResult<TData, TError> & { ): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>; queryKey: DataTag<QueryKey, TData, TError>;
} { } {
const queryOptions = getListAgentsApiV1AgentsGetQueryOptions(options); const queryOptions = getListAgentsApiV1AgentsGetQueryOptions(params, options);
const query = useQuery(queryOptions, queryClient) as UseQueryResult< const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData, TData,

View File

@@ -25,6 +25,7 @@ import type {
ApprovalRead, ApprovalRead,
ApprovalUpdate, ApprovalUpdate,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedApprovalRead,
ListApprovalsApiV1BoardsBoardIdApprovalsGetParams, ListApprovalsApiV1BoardsBoardIdApprovalsGetParams,
StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams, StreamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams,
} from ".././model"; } from ".././model";
@@ -37,7 +38,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Approvals * @summary List Approvals
*/ */
export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 = { export type listApprovalsApiV1BoardsBoardIdApprovalsGetResponse200 = {
data: ApprovalRead[]; data: LimitOffsetPageTypeVarCustomizedApprovalRead;
status: 200; status: 200;
}; };

View File

@@ -24,6 +24,7 @@ import type {
BoardMemoryCreate, BoardMemoryCreate,
BoardMemoryRead, BoardMemoryRead,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedBoardMemoryRead,
ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams, ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams,
StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams, StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams,
} from ".././model"; } from ".././model";
@@ -36,7 +37,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Board Memory * @summary List Board Memory
*/ */
export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 = { export type listBoardMemoryApiV1BoardsBoardIdMemoryGetResponse200 = {
data: BoardMemoryRead[]; data: LimitOffsetPageTypeVarCustomizedBoardMemoryRead;
status: 200; status: 200;
}; };

View File

@@ -23,8 +23,11 @@ import type {
import type { import type {
BoardCreate, BoardCreate,
BoardRead, BoardRead,
BoardSnapshot,
BoardUpdate, BoardUpdate,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedBoardRead,
ListBoardsApiV1BoardsGetParams,
OkResponse, OkResponse,
} from ".././model"; } from ".././model";
@@ -36,7 +39,7 @@ type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
* @summary List Boards * @summary List Boards
*/ */
export type listBoardsApiV1BoardsGetResponse200 = { export type listBoardsApiV1BoardsGetResponse200 = {
data: BoardRead[]; data: LimitOffsetPageTypeVarCustomizedBoardRead;
status: 200; status: 200;
}; };
@@ -58,15 +61,30 @@ export type listBoardsApiV1BoardsGetResponse =
| listBoardsApiV1BoardsGetResponseSuccess | listBoardsApiV1BoardsGetResponseSuccess
| listBoardsApiV1BoardsGetResponseError; | listBoardsApiV1BoardsGetResponseError;
export const getListBoardsApiV1BoardsGetUrl = () => { export const getListBoardsApiV1BoardsGetUrl = (
return `/api/v1/boards`; params?: ListBoardsApiV1BoardsGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/boards?${stringifiedParams}`
: `/api/v1/boards`;
}; };
export const listBoardsApiV1BoardsGet = async ( export const listBoardsApiV1BoardsGet = async (
params?: ListBoardsApiV1BoardsGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listBoardsApiV1BoardsGetResponse> => { ): Promise<listBoardsApiV1BoardsGetResponse> => {
return customFetch<listBoardsApiV1BoardsGetResponse>( return customFetch<listBoardsApiV1BoardsGetResponse>(
getListBoardsApiV1BoardsGetUrl(), getListBoardsApiV1BoardsGetUrl(params),
{ {
...options, ...options,
method: "GET", method: "GET",
@@ -74,31 +92,37 @@ export const listBoardsApiV1BoardsGet = async (
); );
}; };
export const getListBoardsApiV1BoardsGetQueryKey = () => { export const getListBoardsApiV1BoardsGetQueryKey = (
return [`/api/v1/boards`] as const; params?: ListBoardsApiV1BoardsGetParams,
) => {
return [`/api/v1/boards`, ...(params ? [params] : [])] as const;
}; };
export const getListBoardsApiV1BoardsGetQueryOptions = < export const getListBoardsApiV1BoardsGetQueryOptions = <
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>(options?: { >(
query?: Partial< params?: ListBoardsApiV1BoardsGetParams,
UseQueryOptions< options?: {
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, query?: Partial<
TError, UseQueryOptions<
TData Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
> TError,
>; TData
request?: SecondParameter<typeof customFetch>; >
}) => { >;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {}; const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? getListBoardsApiV1BoardsGetQueryKey(); queryOptions?.queryKey ?? getListBoardsApiV1BoardsGetQueryKey(params);
const queryFn: QueryFunction< const queryFn: QueryFunction<
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>> Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>
> = ({ signal }) => listBoardsApiV1BoardsGet({ signal, ...requestOptions }); > = ({ signal }) =>
listBoardsApiV1BoardsGet(params, { signal, ...requestOptions });
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
@@ -116,6 +140,7 @@ export function useListBoardsApiV1BoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params: undefined | ListBoardsApiV1BoardsGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -142,6 +167,7 @@ export function useListBoardsApiV1BoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1BoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -168,6 +194,7 @@ export function useListBoardsApiV1BoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1BoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -190,6 +217,7 @@ export function useListBoardsApiV1BoardsGet<
TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>, TData = Awaited<ReturnType<typeof listBoardsApiV1BoardsGet>>,
TError = HTTPValidationError, TError = HTTPValidationError,
>( >(
params?: ListBoardsApiV1BoardsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -204,7 +232,7 @@ export function useListBoardsApiV1BoardsGet<
): UseQueryResult<TData, TError> & { ): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>; queryKey: DataTag<QueryKey, TData, TError>;
} { } {
const queryOptions = getListBoardsApiV1BoardsGetQueryOptions(options); const queryOptions = getListBoardsApiV1BoardsGetQueryOptions(params, options);
const query = useQuery(queryOptions, queryClient) as UseQueryResult< const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData, TData,
@@ -765,3 +793,240 @@ export const useDeleteBoardApiV1BoardsBoardIdDelete = <
queryClient, queryClient,
); );
}; };
/**
* @summary Get Board Snapshot
*/
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse200 = {
data: BoardSnapshot;
status: 200;
};
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse422 = {
data: HTTPValidationError;
status: 422;
};
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseSuccess =
getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse200 & {
headers: Headers;
};
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseError =
getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse422 & {
headers: Headers;
};
export type getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse =
| getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseSuccess
| getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponseError;
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetUrl = (
boardId: string,
) => {
return `/api/v1/boards/${boardId}/snapshot`;
};
export const getBoardSnapshotApiV1BoardsBoardIdSnapshotGet = async (
boardId: string,
options?: RequestInit,
): Promise<getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse> => {
return customFetch<getBoardSnapshotApiV1BoardsBoardIdSnapshotGetResponse>(
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetUrl(boardId),
{
...options,
method: "GET",
},
);
};
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryKey = (
boardId: string,
) => {
return [`/api/v1/boards/${boardId}/snapshot`] as const;
};
export const getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryOptions = <
TData = Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError = HTTPValidationError,
>(
boardId: string,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey =
queryOptions?.queryKey ??
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryKey(boardId);
const queryFn: QueryFunction<
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>
> = ({ signal }) =>
getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(boardId, {
signal,
...requestOptions,
});
return {
queryKey,
queryFn,
enabled: !!boardId,
...queryOptions,
} as UseQueryOptions<
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>,
TError,
TData
> & { queryKey: DataTag<QueryKey, TData, TError> };
};
export type GetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryResult =
NonNullable<
Awaited<ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>>
>;
export type GetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryError =
HTTPValidationError;
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
TData = Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError = HTTPValidationError,
>(
boardId: string,
options: {
query: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
TData
>
> &
Pick<
DefinedInitialDataOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>
>,
"initialData"
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): DefinedUseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
TData = Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError = HTTPValidationError,
>(
boardId: string,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
TData
>
> &
Pick<
UndefinedInitialDataOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>
>,
"initialData"
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
TData = Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError = HTTPValidationError,
>(
boardId: string,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
};
/**
* @summary Get Board Snapshot
*/
export function useGetBoardSnapshotApiV1BoardsBoardIdSnapshotGet<
TData = Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError = HTTPValidationError,
>(
boardId: string,
options?: {
query?: Partial<
UseQueryOptions<
Awaited<
ReturnType<typeof getBoardSnapshotApiV1BoardsBoardIdSnapshotGet>
>,
TError,
TData
>
>;
request?: SecondParameter<typeof customFetch>;
},
queryClient?: QueryClient,
): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>;
} {
const queryOptions =
getGetBoardSnapshotApiV1BoardsBoardIdSnapshotGetQueryOptions(
boardId,
options,
);
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData,
TError
> & { queryKey: DataTag<QueryKey, TData, TError> };
return { ...query, queryKey: queryOptions.queryKey };
}

View File

@@ -34,7 +34,9 @@ import type {
GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams, GetGatewaySessionApiV1GatewaysSessionsSessionIdGetParams,
GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams, GetSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams,
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedGatewayRead,
ListGatewaySessionsApiV1GatewaysSessionsGetParams, ListGatewaySessionsApiV1GatewaysSessionsGetParams,
ListGatewaysApiV1GatewaysGetParams,
OkResponse, OkResponse,
SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams, SendGatewaySessionMessageApiV1GatewaysSessionsSessionIdMessagePostParams,
} from ".././model"; } from ".././model";
@@ -1451,26 +1453,52 @@ export function useGatewayCommandsApiV1GatewaysCommandsGet<
* @summary List Gateways * @summary List Gateways
*/ */
export type listGatewaysApiV1GatewaysGetResponse200 = { export type listGatewaysApiV1GatewaysGetResponse200 = {
data: GatewayRead[]; data: LimitOffsetPageTypeVarCustomizedGatewayRead;
status: 200; status: 200;
}; };
export type listGatewaysApiV1GatewaysGetResponse422 = {
data: HTTPValidationError;
status: 422;
};
export type listGatewaysApiV1GatewaysGetResponseSuccess = export type listGatewaysApiV1GatewaysGetResponseSuccess =
listGatewaysApiV1GatewaysGetResponse200 & { listGatewaysApiV1GatewaysGetResponse200 & {
headers: Headers; headers: Headers;
}; };
export type listGatewaysApiV1GatewaysGetResponse = export type listGatewaysApiV1GatewaysGetResponseError =
listGatewaysApiV1GatewaysGetResponseSuccess; listGatewaysApiV1GatewaysGetResponse422 & {
headers: Headers;
};
export const getListGatewaysApiV1GatewaysGetUrl = () => { export type listGatewaysApiV1GatewaysGetResponse =
return `/api/v1/gateways`; | listGatewaysApiV1GatewaysGetResponseSuccess
| listGatewaysApiV1GatewaysGetResponseError;
export const getListGatewaysApiV1GatewaysGetUrl = (
params?: ListGatewaysApiV1GatewaysGetParams,
) => {
const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/gateways?${stringifiedParams}`
: `/api/v1/gateways`;
}; };
export const listGatewaysApiV1GatewaysGet = async ( export const listGatewaysApiV1GatewaysGet = async (
params?: ListGatewaysApiV1GatewaysGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listGatewaysApiV1GatewaysGetResponse> => { ): Promise<listGatewaysApiV1GatewaysGetResponse> => {
return customFetch<listGatewaysApiV1GatewaysGetResponse>( return customFetch<listGatewaysApiV1GatewaysGetResponse>(
getListGatewaysApiV1GatewaysGetUrl(), getListGatewaysApiV1GatewaysGetUrl(params),
{ {
...options, ...options,
method: "GET", method: "GET",
@@ -1478,32 +1506,37 @@ export const listGatewaysApiV1GatewaysGet = async (
); );
}; };
export const getListGatewaysApiV1GatewaysGetQueryKey = () => { export const getListGatewaysApiV1GatewaysGetQueryKey = (
return [`/api/v1/gateways`] as const; params?: ListGatewaysApiV1GatewaysGetParams,
) => {
return [`/api/v1/gateways`, ...(params ? [params] : [])] as const;
}; };
export const getListGatewaysApiV1GatewaysGetQueryOptions = < export const getListGatewaysApiV1GatewaysGetQueryOptions = <
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
TError = unknown, TError = HTTPValidationError,
>(options?: { >(
query?: Partial< params?: ListGatewaysApiV1GatewaysGetParams,
UseQueryOptions< options?: {
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, query?: Partial<
TError, UseQueryOptions<
TData Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
> TError,
>; TData
request?: SecondParameter<typeof customFetch>; >
}) => { >;
request?: SecondParameter<typeof customFetch>;
},
) => {
const { query: queryOptions, request: requestOptions } = options ?? {}; const { query: queryOptions, request: requestOptions } = options ?? {};
const queryKey = const queryKey =
queryOptions?.queryKey ?? getListGatewaysApiV1GatewaysGetQueryKey(); queryOptions?.queryKey ?? getListGatewaysApiV1GatewaysGetQueryKey(params);
const queryFn: QueryFunction< const queryFn: QueryFunction<
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>> Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
> = ({ signal }) => > = ({ signal }) =>
listGatewaysApiV1GatewaysGet({ signal, ...requestOptions }); listGatewaysApiV1GatewaysGet(params, { signal, ...requestOptions });
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions< return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
@@ -1515,12 +1548,13 @@ export const getListGatewaysApiV1GatewaysGetQueryOptions = <
export type ListGatewaysApiV1GatewaysGetQueryResult = NonNullable< export type ListGatewaysApiV1GatewaysGetQueryResult = NonNullable<
Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>> Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>
>; >;
export type ListGatewaysApiV1GatewaysGetQueryError = unknown; export type ListGatewaysApiV1GatewaysGetQueryError = HTTPValidationError;
export function useListGatewaysApiV1GatewaysGet< export function useListGatewaysApiV1GatewaysGet<
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params: undefined | ListGatewaysApiV1GatewaysGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1545,8 +1579,9 @@ export function useListGatewaysApiV1GatewaysGet<
}; };
export function useListGatewaysApiV1GatewaysGet< export function useListGatewaysApiV1GatewaysGet<
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListGatewaysApiV1GatewaysGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1571,8 +1606,9 @@ export function useListGatewaysApiV1GatewaysGet<
}; };
export function useListGatewaysApiV1GatewaysGet< export function useListGatewaysApiV1GatewaysGet<
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListGatewaysApiV1GatewaysGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1593,8 +1629,9 @@ export function useListGatewaysApiV1GatewaysGet<
export function useListGatewaysApiV1GatewaysGet< export function useListGatewaysApiV1GatewaysGet<
TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>, TData = Awaited<ReturnType<typeof listGatewaysApiV1GatewaysGet>>,
TError = unknown, TError = HTTPValidationError,
>( >(
params?: ListGatewaysApiV1GatewaysGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1609,7 +1646,10 @@ export function useListGatewaysApiV1GatewaysGet<
): UseQueryResult<TData, TError> & { ): UseQueryResult<TData, TError> & {
queryKey: DataTag<QueryKey, TData, TError>; queryKey: DataTag<QueryKey, TData, TError>;
} { } {
const queryOptions = getListGatewaysApiV1GatewaysGetQueryOptions(options); const queryOptions = getListGatewaysApiV1GatewaysGetQueryOptions(
params,
options,
);
const query = useQuery(queryOptions, queryClient) as UseQueryResult< const query = useQuery(queryOptions, queryClient) as UseQueryResult<
TData, TData,

View File

@@ -10,6 +10,7 @@ import type { ApprovalCreateStatus } from "./approvalCreateStatus";
export interface ApprovalCreate { export interface ApprovalCreate {
action_type: string; action_type: string;
task_id?: string | null;
payload?: ApprovalCreatePayload; payload?: ApprovalCreatePayload;
confidence: number; confidence: number;
rubric_scores?: ApprovalCreateRubricScores; rubric_scores?: ApprovalCreateRubricScores;

View File

@@ -10,6 +10,7 @@ import type { ApprovalReadStatus } from "./approvalReadStatus";
export interface ApprovalRead { export interface ApprovalRead {
action_type: string; action_type: string;
task_id?: string | null;
payload?: ApprovalReadPayload; payload?: ApprovalReadPayload;
confidence: number; confidence: number;
rubric_scores?: ApprovalReadRubricScores; rubric_scores?: ApprovalReadRubricScores;

View File

@@ -12,5 +12,6 @@ export interface BoardMemoryRead {
source?: string | null; source?: string | null;
id: string; id: string;
board_id: string; board_id: string;
is_chat?: boolean;
created_at: string; created_at: string;
} }

View File

@@ -0,0 +1,20 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { AgentRead } from "./agentRead";
import type { ApprovalRead } from "./approvalRead";
import type { BoardMemoryRead } from "./boardMemoryRead";
import type { BoardRead } from "./boardRead";
import type { TaskCardRead } from "./taskCardRead";
export interface BoardSnapshot {
board: BoardRead;
tasks: TaskCardRead[];
agents: AgentRead[];
approvals: ApprovalRead[];
chat_messages: BoardMemoryRead[];
pending_approvals_count?: number;
}

View File

@@ -46,6 +46,7 @@ export * from "./boardOnboardingReadMessages";
export * from "./boardOnboardingStart"; export * from "./boardOnboardingStart";
export * from "./boardRead"; export * from "./boardRead";
export * from "./boardReadSuccessMetrics"; export * from "./boardReadSuccessMetrics";
export * from "./boardSnapshot";
export * from "./boardUpdate"; export * from "./boardUpdate";
export * from "./boardUpdateSuccessMetrics"; export * from "./boardUpdateSuccessMetrics";
export * from "./confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200"; export * from "./confirmDeleteAgentApiV1AgentsAgentIdDeleteConfirmPost200";
@@ -90,15 +91,29 @@ export * from "./getSessionHistoryApiV1GatewaysSessionsSessionIdHistoryGetParams
export * from "./healthHealthGet200"; export * from "./healthHealthGet200";
export * from "./healthzHealthzGet200"; export * from "./healthzHealthzGet200";
export * from "./hTTPValidationError"; export * from "./hTTPValidationError";
export * from "./limitOffsetPageTypeVarCustomizedActivityEventRead";
export * from "./limitOffsetPageTypeVarCustomizedAgentRead";
export * from "./limitOffsetPageTypeVarCustomizedApprovalRead";
export * from "./limitOffsetPageTypeVarCustomizedBoardMemoryRead";
export * from "./limitOffsetPageTypeVarCustomizedBoardRead";
export * from "./limitOffsetPageTypeVarCustomizedGatewayRead";
export * from "./limitOffsetPageTypeVarCustomizedTaskCommentRead";
export * from "./limitOffsetPageTypeVarCustomizedTaskRead";
export * from "./listActivityApiV1ActivityGetParams"; export * from "./listActivityApiV1ActivityGetParams";
export * from "./listAgentsApiV1AgentAgentsGetParams"; export * from "./listAgentsApiV1AgentAgentsGetParams";
export * from "./listAgentsApiV1AgentsGetParams";
export * from "./listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams"; export * from "./listApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams";
export * from "./listApprovalsApiV1BoardsBoardIdApprovalsGetParams"; export * from "./listApprovalsApiV1BoardsBoardIdApprovalsGetParams";
export * from "./listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams"; export * from "./listBoardMemoryApiV1AgentBoardsBoardIdMemoryGetParams";
export * from "./listBoardMemoryApiV1BoardsBoardIdMemoryGetParams"; export * from "./listBoardMemoryApiV1BoardsBoardIdMemoryGetParams";
export * from "./listBoardsApiV1AgentBoardsGetParams";
export * from "./listBoardsApiV1BoardsGetParams";
export * from "./listGatewaysApiV1GatewaysGetParams";
export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams"; export * from "./listGatewaySessionsApiV1GatewaysSessionsGetParams";
export * from "./listSessionsApiV1GatewaySessionsGet200"; export * from "./listSessionsApiV1GatewaySessionsGet200";
export * from "./listSessionsApiV1GatewaySessionsGetParams"; export * from "./listSessionsApiV1GatewaySessionsGetParams";
export * from "./listTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams";
export * from "./listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams";
export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams"; export * from "./listTasksApiV1AgentBoardsBoardIdTasksGetParams";
export * from "./listTasksApiV1BoardsBoardIdTasksGetParams"; export * from "./listTasksApiV1BoardsBoardIdTasksGetParams";
export * from "./okResponse"; export * from "./okResponse";
@@ -111,6 +126,8 @@ export * from "./streamAgentsApiV1AgentsStreamGetParams";
export * from "./streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams"; export * from "./streamApprovalsApiV1BoardsBoardIdApprovalsStreamGetParams";
export * from "./streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams"; export * from "./streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams";
export * from "./streamTasksApiV1BoardsBoardIdTasksStreamGetParams"; export * from "./streamTasksApiV1BoardsBoardIdTasksStreamGetParams";
export * from "./taskCardRead";
export * from "./taskCardReadStatus";
export * from "./taskCommentCreate"; export * from "./taskCommentCreate";
export * from "./taskCommentRead"; export * from "./taskCommentRead";
export * from "./taskCreate"; export * from "./taskCreate";

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { ActivityEventRead } from "./activityEventRead";
export interface LimitOffsetPageTypeVarCustomizedActivityEventRead {
items: ActivityEventRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { AgentRead } from "./agentRead";
export interface LimitOffsetPageTypeVarCustomizedAgentRead {
items: AgentRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { ApprovalRead } from "./approvalRead";
export interface LimitOffsetPageTypeVarCustomizedApprovalRead {
items: ApprovalRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { BoardMemoryRead } from "./boardMemoryRead";
export interface LimitOffsetPageTypeVarCustomizedBoardMemoryRead {
items: BoardMemoryRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { BoardRead } from "./boardRead";
export interface LimitOffsetPageTypeVarCustomizedBoardRead {
items: BoardRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { GatewayRead } from "./gatewayRead";
export interface LimitOffsetPageTypeVarCustomizedGatewayRead {
items: GatewayRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { TaskCommentRead } from "./taskCommentRead";
export interface LimitOffsetPageTypeVarCustomizedTaskCommentRead {
items: TaskCommentRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -0,0 +1,17 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { TaskRead } from "./taskRead";
export interface LimitOffsetPageTypeVarCustomizedTaskRead {
items: TaskRead[];
/** @minimum 0 */
total: number;
/** @minimum 1 */
limit: number;
/** @minimum 0 */
offset: number;
}

View File

@@ -7,5 +7,13 @@
export type ListAgentsApiV1AgentAgentsGetParams = { export type ListAgentsApiV1AgentAgentsGetParams = {
board_id?: string | null; board_id?: string | null;
limit?: number | null; /**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
}; };

View File

@@ -0,0 +1,20 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListAgentsApiV1AgentsGetParams = {
board_id?: string | null;
gateway_id?: string | null;
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -7,4 +7,13 @@
export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams = { export type ListApprovalsApiV1AgentBoardsBoardIdApprovalsGetParams = {
status?: "pending" | "approved" | "rejected" | null; status?: "pending" | "approved" | "rejected" | null;
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
}; };

View File

@@ -7,4 +7,13 @@
export type ListApprovalsApiV1BoardsBoardIdApprovalsGetParams = { export type ListApprovalsApiV1BoardsBoardIdApprovalsGetParams = {
status?: "pending" | "approved" | "rejected" | null; status?: "pending" | "approved" | "rejected" | null;
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
}; };

View File

@@ -6,6 +6,7 @@
*/ */
export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams = { export type ListBoardMemoryApiV1BoardsBoardIdMemoryGetParams = {
is_chat?: boolean | null;
/** /**
* @minimum 1 * @minimum 1
* @maximum 200 * @maximum 200

View File

@@ -0,0 +1,18 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListBoardsApiV1AgentBoardsGetParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -0,0 +1,19 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListBoardsApiV1BoardsGetParams = {
gateway_id?: string | null;
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -0,0 +1,18 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListGatewaysApiV1GatewaysGetParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -0,0 +1,19 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListTaskCommentsApiV1AgentBoardsBoardIdTasksTaskIdCommentsGetParams =
{
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -0,0 +1,18 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams = {
/**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
};

View File

@@ -9,5 +9,13 @@ export type ListTasksApiV1AgentBoardsBoardIdTasksGetParams = {
status?: string | null; status?: string | null;
assigned_agent_id?: string | null; assigned_agent_id?: string | null;
unassigned?: boolean | null; unassigned?: boolean | null;
limit?: number | null; /**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
}; };

View File

@@ -9,5 +9,13 @@ export type ListTasksApiV1BoardsBoardIdTasksGetParams = {
status?: string | null; status?: string | null;
assigned_agent_id?: string | null; assigned_agent_id?: string | null;
unassigned?: boolean | null; unassigned?: boolean | null;
limit?: number | null; /**
* @minimum 1
* @maximum 200
*/
limit?: number;
/**
* @minimum 0
*/
offset?: number;
}; };

View File

@@ -7,4 +7,5 @@
export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams = { export type StreamBoardMemoryApiV1BoardsBoardIdMemoryStreamGetParams = {
since?: string | null; since?: string | null;
is_chat?: boolean | null;
}; };

View File

@@ -0,0 +1,25 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
import type { TaskCardReadStatus } from "./taskCardReadStatus";
export interface TaskCardRead {
title: string;
description?: string | null;
status?: TaskCardReadStatus;
priority?: string;
due_at?: string | null;
assigned_agent_id?: string | null;
id: string;
board_id: string | null;
created_by_user_id: string | null;
in_progress_at: string | null;
created_at: string;
updated_at: string;
assignee?: string | null;
approvals_count?: number;
approvals_pending_count?: number;
}

View File

@@ -0,0 +1,16 @@
/**
* Generated by orval v8.2.0 🍺
* Do not edit manually.
* Mission Control API
* OpenAPI spec version: 0.1.0
*/
export type TaskCardReadStatus =
(typeof TaskCardReadStatus)[keyof typeof TaskCardReadStatus];
export const TaskCardReadStatus = {
inbox: "inbox",
in_progress: "in_progress",
review: "review",
done: "done",
} as const;

View File

@@ -22,6 +22,9 @@ import type {
import type { import type {
HTTPValidationError, HTTPValidationError,
LimitOffsetPageTypeVarCustomizedTaskCommentRead,
LimitOffsetPageTypeVarCustomizedTaskRead,
ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
ListTasksApiV1BoardsBoardIdTasksGetParams, ListTasksApiV1BoardsBoardIdTasksGetParams,
OkResponse, OkResponse,
StreamTasksApiV1BoardsBoardIdTasksStreamGetParams, StreamTasksApiV1BoardsBoardIdTasksStreamGetParams,
@@ -292,7 +295,7 @@ export function useStreamTasksApiV1BoardsBoardIdTasksStreamGet<
* @summary List Tasks * @summary List Tasks
*/ */
export type listTasksApiV1BoardsBoardIdTasksGetResponse200 = { export type listTasksApiV1BoardsBoardIdTasksGetResponse200 = {
data: TaskRead[]; data: LimitOffsetPageTypeVarCustomizedTaskRead;
status: 200; status: 200;
}; };
@@ -900,7 +903,7 @@ export const useDeleteTaskApiV1BoardsBoardIdTasksTaskIdDelete = <
*/ */
export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse200 = export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse200 =
{ {
data: TaskCommentRead[]; data: LimitOffsetPageTypeVarCustomizedTaskCommentRead;
status: 200; status: 200;
}; };
@@ -926,19 +929,34 @@ export type listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse =
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl = ( export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl = (
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
) => { ) => {
return `/api/v1/boards/${boardId}/tasks/${taskId}/comments`; const normalizedParams = new URLSearchParams();
Object.entries(params || {}).forEach(([key, value]) => {
if (value !== undefined) {
normalizedParams.append(key, value === null ? "null" : value.toString());
}
});
const stringifiedParams = normalizedParams.toString();
return stringifiedParams.length > 0
? `/api/v1/boards/${boardId}/tasks/${taskId}/comments?${stringifiedParams}`
: `/api/v1/boards/${boardId}/tasks/${taskId}/comments`;
}; };
export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async ( export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async (
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options?: RequestInit, options?: RequestInit,
): Promise<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse> => { ): Promise<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse> => {
return customFetch<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse>( return customFetch<listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetResponse>(
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl( getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetUrl(
boardId, boardId,
taskId, taskId,
params,
), ),
{ {
...options, ...options,
@@ -948,8 +966,15 @@ export const listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet = async (
}; };
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey = export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey =
(boardId: string, taskId: string) => { (
return [`/api/v1/boards/${boardId}/tasks/${taskId}/comments`] as const; boardId: string,
taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
) => {
return [
`/api/v1/boards/${boardId}/tasks/${taskId}/comments`,
...(params ? [params] : []),
] as const;
}; };
export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions = export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions =
@@ -963,6 +988,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -985,6 +1011,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey( getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryKey(
boardId, boardId,
taskId, taskId,
params,
); );
const queryFn: QueryFunction< const queryFn: QueryFunction<
@@ -997,6 +1024,7 @@ export const getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOpt
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet( listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet(
boardId, boardId,
taskId, taskId,
params,
{ signal, ...requestOptions }, { signal, ...requestOptions },
); );
@@ -1035,6 +1063,9 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params:
| undefined
| ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options: { options: {
query: Partial< query: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1077,6 +1108,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1119,6 +1151,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1149,6 +1182,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
>( >(
boardId: string, boardId: string,
taskId: string, taskId: string,
params?: ListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetParams,
options?: { options?: {
query?: Partial< query?: Partial<
UseQueryOptions< UseQueryOptions<
@@ -1171,6 +1205,7 @@ export function useListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet<
getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions( getListTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGetQueryOptions(
boardId, boardId,
taskId, taskId,
params,
options, options,
); );

View File

@@ -116,7 +116,7 @@ export default function EditAgentPage() {
const boardsQuery = useListBoardsApiV1BoardsGet< const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse, listBoardsApiV1BoardsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchOnMount: "always", refetchOnMount: "always",
@@ -148,7 +148,8 @@ export default function EditAgentPage() {
}, },
}); });
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : []; const boards =
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
const loadedAgent: AgentRead | null = const loadedAgent: AgentRead | null =
agentQuery.data?.status === 200 ? agentQuery.data.data : null; agentQuery.data?.status === 200 ? agentQuery.data.data : null;

View File

@@ -106,7 +106,7 @@ export default function AgentDetailPage() {
const boardsQuery = useListBoardsApiV1BoardsGet< const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse, listBoardsApiV1BoardsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchInterval: 60_000, refetchInterval: 60_000,
@@ -118,9 +118,11 @@ export default function AgentDetailPage() {
const agent: AgentRead | null = const agent: AgentRead | null =
agentQuery.data?.status === 200 ? agentQuery.data.data : null; agentQuery.data?.status === 200 ? agentQuery.data.data : null;
const events: ActivityEventRead[] = const events: ActivityEventRead[] =
activityQuery.data?.status === 200 ? activityQuery.data.data : []; activityQuery.data?.status === 200
? activityQuery.data.data.items ?? []
: [];
const boards: BoardRead[] = const boards: BoardRead[] =
boardsQuery.data?.status === 200 ? boardsQuery.data.data : []; boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
const agentEvents = useMemo(() => { const agentEvents = useMemo(() => {
if (!agent) return []; if (!agent) return [];

View File

@@ -91,7 +91,7 @@ export default function NewAgentPage() {
const boardsQuery = useListBoardsApiV1BoardsGet< const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse, listBoardsApiV1BoardsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchOnMount: "always", refetchOnMount: "always",
@@ -111,7 +111,8 @@ export default function NewAgentPage() {
}, },
}); });
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : []; const boards =
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [];
const displayBoardId = boardId || boards[0]?.id || ""; const displayBoardId = boardId || boards[0]?.id || "";
const isLoading = boardsQuery.isLoading || createAgentMutation.isPending; const isLoading = boardsQuery.isLoading || createAgentMutation.isPending;
const errorMessage = error ?? boardsQuery.error?.message ?? null; const errorMessage = error ?? boardsQuery.error?.message ?? null;

View File

@@ -98,7 +98,7 @@ export default function AgentsPage() {
const boardsQuery = useListBoardsApiV1BoardsGet< const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse, listBoardsApiV1BoardsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchInterval: 30_000, refetchInterval: 30_000,
@@ -109,7 +109,7 @@ export default function AgentsPage() {
const agentsQuery = useListAgentsApiV1AgentsGet< const agentsQuery = useListAgentsApiV1AgentsGet<
listAgentsApiV1AgentsGetResponse, listAgentsApiV1AgentsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchInterval: 15_000, refetchInterval: 15_000,
@@ -118,10 +118,15 @@ export default function AgentsPage() {
}); });
const boards = useMemo( const boards = useMemo(
() => (boardsQuery.data?.status === 200 ? boardsQuery.data.data : []), () =>
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [],
[boardsQuery.data] [boardsQuery.data]
); );
const agents = useMemo(() => agentsQuery.data?.data ?? [], [agentsQuery.data]); const agents = useMemo(
() =>
agentsQuery.data?.status === 200 ? agentsQuery.data.data.items ?? [] : [],
[agentsQuery.data]
);
const deleteMutation = useDeleteAgentApiV1AgentsAgentIdDelete< const deleteMutation = useDeleteAgentApiV1AgentsAgentIdDelete<
ApiError, ApiError,
@@ -133,10 +138,18 @@ export default function AgentsPage() {
await queryClient.cancelQueries({ queryKey: agentsKey }); await queryClient.cancelQueries({ queryKey: agentsKey });
const previous = const previous =
queryClient.getQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey); queryClient.getQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey);
if (previous) { if (previous && previous.status === 200) {
const nextItems = previous.data.items.filter(
(agent) => agent.id !== agentId
);
const removedCount = previous.data.items.length - nextItems.length;
queryClient.setQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey, { queryClient.setQueryData<listAgentsApiV1AgentsGetResponse>(agentsKey, {
...previous, ...previous,
data: previous.data.filter((agent) => agent.id !== agentId), data: {
...previous.data,
items: nextItems,
total: Math.max(0, previous.data.total - removedCount),
},
}); });
} }
return { previous }; return { previous };

View File

@@ -16,8 +16,6 @@ import {
useListGatewaysApiV1GatewaysGet, useListGatewaysApiV1GatewaysGet,
} from "@/api/generated/gateways/gateways"; } from "@/api/generated/gateways/gateways";
import type { BoardRead, BoardUpdate } from "@/api/generated/model"; import type { BoardRead, BoardUpdate } from "@/api/generated/model";
import { BoardApprovalsPanel } from "@/components/BoardApprovalsPanel";
import { BoardGoalPanel } from "@/components/BoardGoalPanel";
import { BoardOnboardingChat } from "@/components/BoardOnboardingChat"; import { BoardOnboardingChat } from "@/components/BoardOnboardingChat";
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { DashboardShell } from "@/components/templates/DashboardShell"; import { DashboardShell } from "@/components/templates/DashboardShell";
@@ -72,7 +70,7 @@ export default function EditBoardPage() {
const gatewaysQuery = useListGatewaysApiV1GatewaysGet< const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
listGatewaysApiV1GatewaysGetResponse, listGatewaysApiV1GatewaysGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchOnMount: "always", refetchOnMount: "always",
@@ -105,7 +103,9 @@ export default function EditBoardPage() {
}); });
const gateways = const gateways =
gatewaysQuery.data?.status === 200 ? gatewaysQuery.data.data : []; gatewaysQuery.data?.status === 200
? gatewaysQuery.data.data.items ?? []
: [];
const loadedBoard: BoardRead | null = const loadedBoard: BoardRead | null =
boardQuery.data?.status === 200 ? boardQuery.data.data : null; boardQuery.data?.status === 200 ? boardQuery.data.data : null;
const baseBoard = board ?? loadedBoard; const baseBoard = board ?? loadedBoard;
@@ -224,135 +224,149 @@ export default function EditBoardPage() {
</div> </div>
<div className="p-8"> <div className="p-8">
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]"> <div className="space-y-6">
<div className="space-y-6"> <form
<BoardGoalPanel onSubmit={handleSubmit}
board={baseBoard} className="space-y-6 rounded-xl border border-slate-200 bg-white p-6 shadow-sm"
onStartOnboarding={() => setIsOnboardingOpen(true)} >
/> {resolvedBoardType !== "general" &&
<form baseBoard &&
onSubmit={handleSubmit} !(baseBoard.goal_confirmed ?? false) ? (
className="space-y-6 rounded-xl border border-slate-200 bg-white p-6 shadow-sm" <div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-amber-200 bg-amber-50 px-4 py-3">
> <div className="min-w-0">
<div className="grid gap-6 md:grid-cols-2"> <p className="text-sm font-semibold text-amber-900">
<div className="space-y-2"> Goal needs confirmation
<label className="text-sm font-medium text-slate-900"> </p>
Board name <span className="text-red-500">*</span> <p className="mt-1 text-xs text-amber-800/80">
</label> Start onboarding to draft an objective and success
<Input metrics.
value={resolvedName} </p>
onChange={(event) => setName(event.target.value)}
placeholder="Board name"
disabled={isLoading || !baseBoard}
/>
</div> </div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway <span className="text-red-500">*</span>
</label>
<SearchableSelect
ariaLabel="Select gateway"
value={displayGatewayId}
onValueChange={setGatewayId}
options={gatewayOptions}
placeholder="Select gateway"
searchPlaceholder="Search gateways..."
emptyMessage="No gateways found."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Board type
</label>
<Select value={resolvedBoardType} onValueChange={setBoardType}>
<SelectTrigger>
<SelectValue placeholder="Select board type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="goal">Goal</SelectItem>
<SelectItem value="general">General</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Target date
</label>
<Input
type="date"
value={resolvedTargetDate}
onChange={(event) => setTargetDate(event.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Objective
</label>
<Textarea
value={resolvedObjective}
onChange={(event) => setObjective(event.target.value)}
placeholder="What should this board achieve?"
className="min-h-[120px]"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Success metrics (JSON)
</label>
<Textarea
value={resolvedSuccessMetrics}
onChange={(event) => setSuccessMetrics(event.target.value)}
placeholder='e.g. { "target": "Launch by week 2" }'
className="min-h-[140px] font-mono text-xs"
disabled={isLoading}
/>
<p className="text-xs text-slate-500">
Add key outcomes so the lead agent can measure progress.
</p>
{metricsError ? (
<p className="text-xs text-red-500">{metricsError}</p>
) : null}
</div>
{gateways.length === 0 ? (
<div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<p>No gateways available. Create one in Gateways to continue.</p>
</div>
) : null}
{errorMessage ? (
<p className="text-sm text-red-500">{errorMessage}</p>
) : null}
<div className="flex justify-end gap-3">
<Button <Button
type="button" type="button"
variant="ghost" variant="secondary"
onClick={() => router.push(`/boards/${boardId}`)} onClick={() => setIsOnboardingOpen(true)}
disabled={isLoading} disabled={isLoading || !baseBoard}
> >
Cancel Start onboarding
</Button>
<Button type="submit" disabled={isLoading || !baseBoard || !isFormReady}>
{isLoading ? "Saving…" : "Save changes"}
</Button> </Button>
</div> </div>
</form> ) : null}
</div> <div className="grid gap-6 md:grid-cols-2">
<div className="space-y-6"> <div className="space-y-2">
{boardId ? <BoardApprovalsPanel boardId={boardId} /> : null} <label className="text-sm font-medium text-slate-900">
</div> Board name <span className="text-red-500">*</span>
</label>
<Input
value={resolvedName}
onChange={(event) => setName(event.target.value)}
placeholder="Board name"
disabled={isLoading || !baseBoard}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Gateway <span className="text-red-500">*</span>
</label>
<SearchableSelect
ariaLabel="Select gateway"
value={displayGatewayId}
onValueChange={setGatewayId}
options={gatewayOptions}
placeholder="Select gateway"
searchPlaceholder="Search gateways..."
emptyMessage="No gateways found."
triggerClassName="w-full h-11 rounded-xl border border-slate-300 bg-white px-3 py-2 text-sm font-medium text-slate-900 shadow-sm focus:border-blue-500 focus:ring-2 focus:ring-blue-200"
contentClassName="rounded-xl border border-slate-200 shadow-lg"
itemClassName="px-4 py-3 text-sm text-slate-700 data-[selected=true]:bg-slate-50 data-[selected=true]:text-slate-900"
/>
</div>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Board type
</label>
<Select value={resolvedBoardType} onValueChange={setBoardType}>
<SelectTrigger>
<SelectValue placeholder="Select board type" />
</SelectTrigger>
<SelectContent>
<SelectItem value="goal">Goal</SelectItem>
<SelectItem value="general">General</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Target date
</label>
<Input
type="date"
value={resolvedTargetDate}
onChange={(event) => setTargetDate(event.target.value)}
disabled={isLoading}
/>
</div>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Objective
</label>
<Textarea
value={resolvedObjective}
onChange={(event) => setObjective(event.target.value)}
placeholder="What should this board achieve?"
className="min-h-[120px]"
disabled={isLoading}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-slate-900">
Success metrics (JSON)
</label>
<Textarea
value={resolvedSuccessMetrics}
onChange={(event) => setSuccessMetrics(event.target.value)}
placeholder='e.g. { "target": "Launch by week 2" }'
className="min-h-[140px] font-mono text-xs"
disabled={isLoading}
/>
<p className="text-xs text-slate-500">
Add key outcomes so the lead agent can measure progress.
</p>
{metricsError ? (
<p className="text-xs text-red-500">{metricsError}</p>
) : null}
</div>
{gateways.length === 0 ? (
<div className="rounded-lg border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<p>No gateways available. Create one in Gateways to continue.</p>
</div>
) : null}
{errorMessage ? (
<p className="text-sm text-red-500">{errorMessage}</p>
) : null}
<div className="flex justify-end gap-3">
<Button
type="button"
variant="ghost"
onClick={() => router.push(`/boards/${boardId}`)}
disabled={isLoading}
>
Cancel
</Button>
<Button type="submit" disabled={isLoading || !baseBoard || !isFormReady}>
{isLoading ? "Saving…" : "Save changes"}
</Button>
</div>
</form>
</div> </div>
</div> </div>
</main> </main>

View File

@@ -28,16 +28,14 @@ import {
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { listAgentsApiV1AgentsGet, streamAgentsApiV1AgentsStreamGet } from "@/api/generated/agents/agents"; import { streamAgentsApiV1AgentsStreamGet } from "@/api/generated/agents/agents";
import { import {
listApprovalsApiV1BoardsBoardIdApprovalsGet,
streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet, streamApprovalsApiV1BoardsBoardIdApprovalsStreamGet,
updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch, updateApprovalApiV1BoardsBoardIdApprovalsApprovalIdPatch,
} from "@/api/generated/approvals/approvals"; } from "@/api/generated/approvals/approvals";
import { getBoardApiV1BoardsBoardIdGet } from "@/api/generated/boards/boards"; import { getBoardSnapshotApiV1BoardsBoardIdSnapshotGet } from "@/api/generated/boards/boards";
import { import {
createBoardMemoryApiV1BoardsBoardIdMemoryPost, createBoardMemoryApiV1BoardsBoardIdMemoryPost,
listBoardMemoryApiV1BoardsBoardIdMemoryGet,
streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet, streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet,
} from "@/api/generated/board-memory/board-memory"; } from "@/api/generated/board-memory/board-memory";
import { import {
@@ -45,7 +43,6 @@ import {
createTaskCommentApiV1BoardsBoardIdTasksTaskIdCommentsPost, createTaskCommentApiV1BoardsBoardIdTasksTaskIdCommentsPost,
deleteTaskApiV1BoardsBoardIdTasksTaskIdDelete, deleteTaskApiV1BoardsBoardIdTasksTaskIdDelete,
listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet, listTaskCommentsApiV1BoardsBoardIdTasksTaskIdCommentsGet,
listTasksApiV1BoardsBoardIdTasksGet,
streamTasksApiV1BoardsBoardIdTasksStreamGet, streamTasksApiV1BoardsBoardIdTasksStreamGet,
updateTaskApiV1BoardsBoardIdTasksTaskIdPatch, updateTaskApiV1BoardsBoardIdTasksTaskIdPatch,
} from "@/api/generated/tasks/tasks"; } from "@/api/generated/tasks/tasks";
@@ -54,6 +51,7 @@ import type {
ApprovalRead, ApprovalRead,
BoardMemoryRead, BoardMemoryRead,
BoardRead, BoardRead,
TaskCardRead,
TaskCommentRead, TaskCommentRead,
TaskRead, TaskRead,
} from "@/api/generated/model"; } from "@/api/generated/model";
@@ -61,13 +59,16 @@ import { cn } from "@/lib/utils";
type Board = BoardRead; type Board = BoardRead;
type TaskStatus = Exclude<TaskRead["status"], undefined>; type TaskStatus = Exclude<TaskCardRead["status"], undefined>;
type Task = TaskRead & { type Task = Omit<
TaskCardRead,
"status" | "priority" | "approvals_count" | "approvals_pending_count"
> & {
status: TaskStatus; status: TaskStatus;
priority: string; priority: string;
approvalsCount?: number; approvals_count: number;
approvalsPendingCount?: number; approvals_pending_count: number;
}; };
type Agent = AgentRead & { status: string }; type Agent = AgentRead & { status: string };
@@ -78,10 +79,12 @@ type Approval = ApprovalRead & { status: string };
type BoardChatMessage = BoardMemoryRead; type BoardChatMessage = BoardMemoryRead;
const normalizeTask = (task: TaskRead): Task => ({ const normalizeTask = (task: TaskCardRead): Task => ({
...task, ...task,
status: task.status ?? "inbox", status: task.status ?? "inbox",
priority: task.priority ?? "medium", priority: task.priority ?? "medium",
approvals_count: task.approvals_count ?? 0,
approvals_pending_count: task.approvals_pending_count ?? 0,
}); });
const normalizeAgent = (agent: AgentRead): Agent => ({ const normalizeAgent = (agent: AgentRead): Agent => ({
@@ -94,15 +97,6 @@ const normalizeApproval = (approval: ApprovalRead): Approval => ({
status: approval.status ?? "pending", status: approval.status ?? "pending",
}); });
const approvalTaskId = (approval: Approval) => {
const payload = approval.payload ?? {};
return (
(payload as Record<string, unknown>).task_id ??
(payload as Record<string, unknown>).taskId ??
(payload as Record<string, unknown>).taskID
);
};
const priorities = [ const priorities = [
{ value: "low", label: "Low" }, { value: "low", label: "Low" },
{ value: "medium", label: "Medium" }, { value: "medium", label: "Medium" },
@@ -244,31 +238,38 @@ export default function BoardDetailPage() {
const loadBoard = async () => { const loadBoard = async () => {
if (!isSignedIn || !boardId) return; if (!isSignedIn || !boardId) return;
setIsLoading(true); setIsLoading(true);
setIsApprovalsLoading(true);
setError(null); setError(null);
setApprovalsError(null);
setChatError(null);
try { try {
const [boardResult, tasksResult, agentsResult] = await Promise.all([ const snapshotResult = await getBoardSnapshotApiV1BoardsBoardIdSnapshotGet(
getBoardApiV1BoardsBoardIdGet(boardId), boardId,
listTasksApiV1BoardsBoardIdTasksGet(boardId), );
listAgentsApiV1AgentsGet(), if (snapshotResult.status !== 200) {
]); throw new Error("Unable to load board snapshot.");
}
if (boardResult.status !== 200) throw new Error("Unable to load board."); const snapshot = snapshotResult.data;
if (tasksResult.status !== 200) throw new Error("Unable to load tasks."); setBoard(snapshot.board);
setTasks((snapshot.tasks ?? []).map(normalizeTask));
setBoard(boardResult.data); setAgents((snapshot.agents ?? []).map(normalizeAgent));
setTasks(tasksResult.data.map(normalizeTask)); setApprovals((snapshot.approvals ?? []).map(normalizeApproval));
setAgents(agentsResult.data.map(normalizeAgent)); setChatMessages(snapshot.chat_messages ?? []);
} catch (err) { } catch (err) {
setError(err instanceof Error ? err.message : "Something went wrong."); const message = err instanceof Error ? err.message : "Something went wrong.";
setError(message);
setApprovalsError(message);
setChatError(message);
} finally { } finally {
setIsLoading(false); setIsLoading(false);
setIsApprovalsLoading(false);
} }
}; };
useEffect(() => { useEffect(() => {
loadBoard(); loadBoard();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [boardId, isSignedIn]); }, [board, boardId, isSignedIn]);
useEffect(() => { useEffect(() => {
tasksRef.current = tasks; tasksRef.current = tasks;
@@ -294,54 +295,6 @@ export default function BoardDetailPage() {
return () => window.clearTimeout(timeout); return () => window.clearTimeout(timeout);
}, [chatMessages, isChatOpen]); }, [chatMessages, isChatOpen]);
const loadApprovals = useCallback(async () => {
if (!isSignedIn || !boardId) return;
setIsApprovalsLoading(true);
setApprovalsError(null);
try {
const result = await listApprovalsApiV1BoardsBoardIdApprovalsGet(boardId);
if (result.status !== 200) throw new Error("Unable to load approvals.");
setApprovals(result.data.map(normalizeApproval));
} catch (err) {
setApprovalsError(
err instanceof Error ? err.message : "Unable to load approvals.",
);
} finally {
setIsApprovalsLoading(false);
}
}, [boardId, isSignedIn]);
useEffect(() => {
loadApprovals();
}, [boardId, isSignedIn, loadApprovals]);
const loadBoardChat = useCallback(async () => {
if (!isSignedIn || !boardId) return;
setChatError(null);
try {
const result = await listBoardMemoryApiV1BoardsBoardIdMemoryGet(boardId, {
limit: 200,
});
if (result.status !== 200) throw new Error("Unable to load board chat.");
const data = result.data;
const chatOnly = data.filter((item) => item.tags?.includes("chat"));
const ordered = chatOnly.sort((a, b) => {
const aTime = new Date(a.created_at).getTime();
const bTime = new Date(b.created_at).getTime();
return aTime - bTime;
});
setChatMessages(ordered);
} catch (err) {
setChatError(
err instanceof Error ? err.message : "Unable to load board chat.",
);
}
}, [boardId, isSignedIn]);
useEffect(() => {
loadBoardChat();
}, [boardId, isSignedIn, loadBoardChat]);
const latestChatTimestamp = (items: BoardChatMessage[]) => { const latestChatTimestamp = (items: BoardChatMessage[]) => {
if (!items.length) return undefined; if (!items.length) return undefined;
const latest = items.reduce((max, item) => { const latest = items.reduce((max, item) => {
@@ -353,17 +306,18 @@ export default function BoardDetailPage() {
}; };
useEffect(() => { useEffect(() => {
if (!isSignedIn || !boardId) return; if (!isSignedIn || !boardId || !board) return;
let isCancelled = false; let isCancelled = false;
const abortController = new AbortController(); const abortController = new AbortController();
const connect = async () => { const connect = async () => {
try { try {
const since = latestChatTimestamp(chatMessagesRef.current); const since = latestChatTimestamp(chatMessagesRef.current);
const params = { is_chat: true, ...(since ? { since } : {}) };
const streamResult = const streamResult =
await streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet( await streamBoardMemoryApiV1BoardsBoardIdMemoryStreamGet(
boardId, boardId,
since ? { since } : undefined, params,
{ {
headers: { Accept: "text/event-stream" }, headers: { Accept: "text/event-stream" },
signal: abortController.signal, signal: abortController.signal,
@@ -439,7 +393,7 @@ export default function BoardDetailPage() {
}, [boardId, isSignedIn]); }, [boardId, isSignedIn]);
useEffect(() => { useEffect(() => {
if (!isSignedIn || !boardId) return; if (!isSignedIn || !boardId || !board) return;
let isCancelled = false; let isCancelled = false;
const abortController = new AbortController(); const abortController = new AbortController();
@@ -487,7 +441,15 @@ export default function BoardDetailPage() {
} }
if (eventType === "approval" && data) { if (eventType === "approval" && data) {
try { try {
const payload = JSON.parse(data) as { approval?: ApprovalRead }; const payload = JSON.parse(data) as {
approval?: ApprovalRead;
task_counts?: {
task_id?: string;
approvals_count?: number;
approvals_pending_count?: number;
};
pending_approvals_count?: number;
};
if (payload.approval) { if (payload.approval) {
const normalized = normalizeApproval(payload.approval); const normalized = normalizeApproval(payload.approval);
setApprovals((prev) => { setApprovals((prev) => {
@@ -505,6 +467,25 @@ export default function BoardDetailPage() {
return next; return next;
}); });
} }
if (payload.task_counts?.task_id) {
const taskId = payload.task_counts.task_id;
setTasks((prev) => {
const index = prev.findIndex((task) => task.id === taskId);
if (index === -1) return prev;
const next = [...prev];
const current = next[index];
next[index] = {
...current,
approvals_count:
payload.task_counts?.approvals_count ??
current.approvals_count,
approvals_pending_count:
payload.task_counts?.approvals_pending_count ??
current.approvals_pending_count,
};
return next;
});
}
} catch { } catch {
// Ignore malformed payloads. // Ignore malformed payloads.
} }
@@ -524,7 +505,7 @@ export default function BoardDetailPage() {
isCancelled = true; isCancelled = true;
abortController.abort(); abortController.abort();
}; };
}, [boardId, isSignedIn]); }, [board, boardId, isSignedIn]);
useEffect(() => { useEffect(() => {
if (!selectedTask) { if (!selectedTask) {
@@ -610,14 +591,37 @@ export default function BoardDetailPage() {
return [...prev, payload.comment as TaskComment]; return [...prev, payload.comment as TaskComment];
}); });
} else if (payload.task) { } else if (payload.task) {
const normalizedTask = normalizeTask(payload.task);
setTasks((prev) => { setTasks((prev) => {
const index = prev.findIndex((item) => item.id === normalizedTask.id); const index = prev.findIndex((item) => item.id === payload.task?.id);
if (index === -1) { if (index === -1) {
return [normalizedTask, ...prev]; const assignee = payload.task?.assigned_agent_id
? agentsRef.current.find(
(agent) => agent.id === payload.task?.assigned_agent_id,
)?.name ?? null
: null;
const created = normalizeTask({
...payload.task,
assignee,
approvals_count: 0,
approvals_pending_count: 0,
} as TaskCardRead);
return [created, ...prev];
} }
const next = [...prev]; const next = [...prev];
next[index] = { ...next[index], ...normalizedTask }; const existing = next[index];
const assignee = payload.task?.assigned_agent_id
? agentsRef.current.find(
(agent) => agent.id === payload.task?.assigned_agent_id,
)?.name ?? null
: null;
const updated = normalizeTask({
...existing,
...payload.task,
assignee,
approvals_count: existing.approvals_count,
approvals_pending_count: existing.approvals_pending_count,
} as TaskCardRead);
next[index] = { ...existing, ...updated };
return next; return next;
}); });
} }
@@ -727,7 +731,7 @@ export default function BoardDetailPage() {
isCancelled = true; isCancelled = true;
abortController.abort(); abortController.abort();
}; };
}, [boardId, isSignedIn]); }, [board, boardId, isSignedIn]);
const resetForm = () => { const resetForm = () => {
setTitle(""); setTitle("");
@@ -754,7 +758,14 @@ export default function BoardDetailPage() {
}); });
if (result.status !== 200) throw new Error("Unable to create task."); if (result.status !== 200) throw new Error("Unable to create task.");
const created = normalizeTask(result.data); const created = normalizeTask({
...result.data,
assignee: result.data.assigned_agent_id
? assigneeById.get(result.data.assigned_agent_id) ?? null
: null,
approvals_count: 0,
approvals_pending_count: 0,
} as TaskCardRead);
setTasks((prev) => [created, ...prev]); setTasks((prev) => [created, ...prev]);
setIsDialogOpen(false); setIsDialogOpen(false);
resetForm(); resetForm();
@@ -829,49 +840,9 @@ export default function BoardDetailPage() {
}); });
}, [liveFeed]); }, [liveFeed]);
const pendingApprovalsByTaskId = useMemo(() => {
const map = new Map<string, number>();
approvals
.filter((approval) => approval.status === "pending")
.forEach((approval) => {
const taskId = approvalTaskId(approval);
if (!taskId || typeof taskId !== "string") return;
map.set(taskId, (map.get(taskId) ?? 0) + 1);
});
return map;
}, [approvals]);
const totalApprovalsByTaskId = useMemo(() => {
const map = new Map<string, number>();
approvals.forEach((approval) => {
const taskId = approvalTaskId(approval);
if (!taskId || typeof taskId !== "string") return;
map.set(taskId, (map.get(taskId) ?? 0) + 1);
});
return map;
}, [approvals]);
const displayTasks = useMemo(
() =>
tasks.map((task) => ({
...task,
assignee: task.assigned_agent_id
? assigneeById.get(task.assigned_agent_id)
: undefined,
approvalsCount: totalApprovalsByTaskId.get(task.id) ?? 0,
approvalsPendingCount: pendingApprovalsByTaskId.get(task.id) ?? 0,
})),
[tasks, assigneeById, pendingApprovalsByTaskId, totalApprovalsByTaskId],
);
const boardAgents = useMemo(
() => agents.filter((agent) => !boardId || agent.board_id === boardId),
[agents, boardId],
);
const assignableAgents = useMemo( const assignableAgents = useMemo(
() => boardAgents.filter((agent) => !agent.is_board_lead), () => agents.filter((agent) => !agent.is_board_lead),
[boardAgents], [agents],
); );
const hasTaskChanges = useMemo(() => { const hasTaskChanges = useMemo(() => {
@@ -912,10 +883,7 @@ export default function BoardDetailPage() {
const taskApprovals = useMemo(() => { const taskApprovals = useMemo(() => {
if (!selectedTask) return []; if (!selectedTask) return [];
const taskId = selectedTask.id; const taskId = selectedTask.id;
return approvals.filter((approval) => { return approvals.filter((approval) => approval.task_id === taskId);
const payloadTaskId = approvalTaskId(approval);
return payloadTaskId === taskId;
});
}, [approvals, selectedTask]); }, [approvals, selectedTask]);
const workingAgentIds = useMemo(() => { const workingAgentIds = useMemo(() => {
@@ -935,12 +903,12 @@ export default function BoardDetailPage() {
if (agent.status === "provisioning") return 2; if (agent.status === "provisioning") return 2;
return 3; return 3;
}; };
return [...boardAgents].sort((a, b) => { return [...agents].sort((a, b) => {
const diff = rank(a) - rank(b); const diff = rank(a) - rank(b);
if (diff !== 0) return diff; if (diff !== 0) return diff;
return a.name.localeCompare(b.name); return a.name.localeCompare(b.name);
}); });
}, [boardAgents, workingAgentIds]); }, [agents, workingAgentIds]);
const loadComments = async (taskId: string) => { const loadComments = async (taskId: string) => {
if (!isSignedIn || !boardId) return; if (!isSignedIn || !boardId) return;
@@ -953,7 +921,7 @@ export default function BoardDetailPage() {
taskId, taskId,
); );
if (result.status !== 200) throw new Error("Unable to load comments."); if (result.status !== 200) throw new Error("Unable to load comments.");
setComments(result.data); setComments(result.data.items ?? []);
} catch (err) { } catch (err) {
setCommentsError(err instanceof Error ? err.message : "Something went wrong."); setCommentsError(err instanceof Error ? err.message : "Something went wrong.");
} finally { } finally {
@@ -1059,9 +1027,20 @@ export default function BoardDetailPage() {
}, },
); );
if (result.status !== 200) throw new Error("Unable to update task."); if (result.status !== 200) throw new Error("Unable to update task.");
const updated = normalizeTask(result.data); const previous =
tasksRef.current.find((task) => task.id === selectedTask.id) ??
selectedTask;
const updated = normalizeTask({
...previous,
...result.data,
assignee: result.data.assigned_agent_id
? assigneeById.get(result.data.assigned_agent_id) ?? null
: null,
approvals_count: previous.approvals_count,
approvals_pending_count: previous.approvals_pending_count,
} as TaskCardRead);
setTasks((prev) => setTasks((prev) =>
prev.map((task) => (task.id === updated.id ? updated : task)), prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
); );
setSelectedTask(updated); setSelectedTask(updated);
if (closeOnSuccess) { if (closeOnSuccess) {
@@ -1119,6 +1098,7 @@ export default function BoardDetailPage() {
status, status,
assigned_agent_id: assigned_agent_id:
status === "inbox" ? null : task.assigned_agent_id, status === "inbox" ? null : task.assigned_agent_id,
assignee: status === "inbox" ? null : task.assignee,
} }
: task, : task,
), ),
@@ -1130,9 +1110,17 @@ export default function BoardDetailPage() {
{ status }, { status },
); );
if (result.status !== 200) throw new Error("Unable to move task."); if (result.status !== 200) throw new Error("Unable to move task.");
const updated = normalizeTask(result.data); const updated = normalizeTask({
...currentTask,
...result.data,
assignee: result.data.assigned_agent_id
? assigneeById.get(result.data.assigned_agent_id) ?? null
: null,
approvals_count: currentTask.approvals_count,
approvals_pending_count: currentTask.approvals_pending_count,
} as TaskCardRead);
setTasks((prev) => setTasks((prev) =>
prev.map((task) => (task.id === updated.id ? updated : task)), prev.map((task) => (task.id === updated.id ? { ...task, ...updated } : task)),
); );
} catch (err) { } catch (err) {
setTasks(previousTasks); setTasks(previousTasks);
@@ -1262,6 +1250,7 @@ export default function BoardDetailPage() {
const approvalRows = (approval: Approval) => { const approvalRows = (approval: Approval) => {
const payload = approval.payload ?? {}; const payload = approval.payload ?? {};
const taskId = const taskId =
approval.task_id ??
approvalPayloadValue(payload, "task_id") ?? approvalPayloadValue(payload, "task_id") ??
approvalPayloadValue(payload, "taskId") ?? approvalPayloadValue(payload, "taskId") ??
approvalPayloadValue(payload, "taskID"); approvalPayloadValue(payload, "taskID");
@@ -1499,7 +1488,7 @@ export default function BoardDetailPage() {
<> <>
{viewMode === "board" ? ( {viewMode === "board" ? (
<TaskBoard <TaskBoard
tasks={displayTasks} tasks={tasks}
onTaskSelect={openComments} onTaskSelect={openComments}
onTaskMove={handleTaskMove} onTaskMove={handleTaskMove}
/> />
@@ -1512,7 +1501,7 @@ export default function BoardDetailPage() {
All tasks All tasks
</p> </p>
<p className="text-xs text-slate-500"> <p className="text-xs text-slate-500">
{displayTasks.length} tasks in this board {tasks.length} tasks in this board
</p> </p>
</div> </div>
<Button <Button
@@ -1526,12 +1515,12 @@ export default function BoardDetailPage() {
</div> </div>
</div> </div>
<div className="divide-y divide-slate-100"> <div className="divide-y divide-slate-100">
{displayTasks.length === 0 ? ( {tasks.length === 0 ? (
<div className="px-5 py-8 text-sm text-slate-500"> <div className="px-5 py-8 text-sm text-slate-500">
No tasks yet. Create your first task to get started. No tasks yet. Create your first task to get started.
</div> </div>
) : ( ) : (
displayTasks.map((task) => ( tasks.map((task) => (
<button <button
key={task.id} key={task.id}
type="button" type="button"
@@ -1553,10 +1542,10 @@ export default function BoardDetailPage() {
</p> </p>
</div> </div>
<div className="flex flex-wrap items-center gap-3 text-xs text-slate-500"> <div className="flex flex-wrap items-center gap-3 text-xs text-slate-500">
{task.approvalsPendingCount ? ( {task.approvals_pending_count ? (
<span className="inline-flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wide text-amber-700"> <span className="inline-flex items-center gap-2 text-[10px] font-semibold uppercase tracking-wide text-amber-700">
<span className="h-1.5 w-1.5 rounded-full bg-amber-500" /> <span className="h-1.5 w-1.5 rounded-full bg-amber-500" />
Approval needed · {task.approvalsPendingCount} Approval needed · {task.approvals_pending_count}
</span> </span>
) : null} ) : null}
<span <span

View File

@@ -37,7 +37,7 @@ export default function NewBoardPage() {
const gatewaysQuery = useListGatewaysApiV1GatewaysGet< const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
listGatewaysApiV1GatewaysGetResponse, listGatewaysApiV1GatewaysGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchOnMount: "always", refetchOnMount: "always",
@@ -59,7 +59,9 @@ export default function NewBoardPage() {
}); });
const gateways = const gateways =
gatewaysQuery.data?.status === 200 ? gatewaysQuery.data.data : []; gatewaysQuery.data?.status === 200
? gatewaysQuery.data.data.items ?? []
: [];
const displayGatewayId = gatewayId || gateways[0]?.id || ""; const displayGatewayId = gatewayId || gateways[0]?.id || "";
const isLoading = gatewaysQuery.isLoading || createBoardMutation.isPending; const isLoading = gatewaysQuery.isLoading || createBoardMutation.isPending;
const errorMessage = error ?? gatewaysQuery.error?.message ?? null; const errorMessage = error ?? gatewaysQuery.error?.message ?? null;

View File

@@ -53,7 +53,7 @@ export default function BoardsPage() {
const boardsQuery = useListBoardsApiV1BoardsGet< const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse, listBoardsApiV1BoardsGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchInterval: 30_000, refetchInterval: 30_000,
@@ -62,15 +62,11 @@ export default function BoardsPage() {
}); });
const boards = useMemo( const boards = useMemo(
() => (boardsQuery.data?.status === 200 ? boardsQuery.data.data : []), () =>
boardsQuery.data?.status === 200 ? boardsQuery.data.data.items ?? [] : [],
[boardsQuery.data] [boardsQuery.data]
); );
const sortedBoards = useMemo(
() => [...boards].sort((a, b) => a.name.localeCompare(b.name)),
[boards]
);
const deleteMutation = useDeleteBoardApiV1BoardsBoardIdDelete< const deleteMutation = useDeleteBoardApiV1BoardsBoardIdDelete<
ApiError, ApiError,
{ previous?: listBoardsApiV1BoardsGetResponse } { previous?: listBoardsApiV1BoardsGetResponse }
@@ -82,9 +78,17 @@ export default function BoardsPage() {
const previous = const previous =
queryClient.getQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey); queryClient.getQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey);
if (previous && previous.status === 200) { if (previous && previous.status === 200) {
const nextItems = previous.data.items.filter(
(board) => board.id !== boardId
);
const removedCount = previous.data.items.length - nextItems.length;
queryClient.setQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey, { queryClient.setQueryData<listBoardsApiV1BoardsGetResponse>(boardsKey, {
...previous, ...previous,
data: previous.data.filter((board) => board.id !== boardId), data: {
...previous.data,
items: nextItems,
total: Math.max(0, previous.data.total - removedCount),
},
}); });
} }
return { previous }; return { previous };
@@ -159,7 +163,7 @@ export default function BoardsPage() {
// eslint-disable-next-line react-hooks/incompatible-library // eslint-disable-next-line react-hooks/incompatible-library
const table = useReactTable({ const table = useReactTable({
data: sortedBoards, data: boards,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -191,11 +195,11 @@ export default function BoardsPage() {
Boards Boards
</h1> </h1>
<p className="mt-1 text-sm text-slate-500"> <p className="mt-1 text-sm text-slate-500">
Manage boards and task workflows. {sortedBoards.length} board Manage boards and task workflows. {boards.length} board
{sortedBoards.length === 1 ? "" : "s"} total. {boards.length === 1 ? "" : "s"} total.
</p> </p>
</div> </div>
{sortedBoards.length > 0 ? ( {boards.length > 0 ? (
<Link <Link
href="/boards/new" href="/boards/new"
className={buttonVariants({ size: "md", variant: "primary" })} className={buttonVariants({ size: "md", variant: "primary" })}

View File

@@ -16,11 +16,6 @@ import {
type listAgentsApiV1AgentsGetResponse, type listAgentsApiV1AgentsGetResponse,
useListAgentsApiV1AgentsGet, useListAgentsApiV1AgentsGet,
} from "@/api/generated/agents/agents"; } from "@/api/generated/agents/agents";
import {
type listBoardsApiV1BoardsGetResponse,
useListBoardsApiV1BoardsGet,
} from "@/api/generated/boards/boards";
import type { AgentRead } from "@/api/generated/model";
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar"; import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
import { DashboardShell } from "@/components/templates/DashboardShell"; import { DashboardShell } from "@/components/templates/DashboardShell";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@@ -65,22 +60,12 @@ export default function GatewayDetailPage() {
const gateway = const gateway =
gatewayQuery.data?.status === 200 ? gatewayQuery.data.data : null; gatewayQuery.data?.status === 200 ? gatewayQuery.data.data : null;
const boardsQuery = useListBoardsApiV1BoardsGet<
listBoardsApiV1BoardsGetResponse,
ApiError
>({
query: {
enabled: Boolean(isSignedIn),
refetchInterval: 30_000,
},
});
const agentsQuery = useListAgentsApiV1AgentsGet< const agentsQuery = useListAgentsApiV1AgentsGet<
listAgentsApiV1AgentsGetResponse, listAgentsApiV1AgentsGetResponse,
ApiError ApiError
>({ >(gatewayId ? { gateway_id: gatewayId } : undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn && gatewayId),
refetchInterval: 15_000, refetchInterval: 15_000,
}, },
}); });
@@ -103,22 +88,11 @@ export default function GatewayDetailPage() {
}, },
}); });
const agents = useMemo(() => { const agents = useMemo(
const allAgents = agentsQuery.data?.data ?? []; () =>
const boards = boardsQuery.data?.status === 200 ? boardsQuery.data.data : []; agentsQuery.data?.status === 200 ? agentsQuery.data.data.items ?? [] : [],
if (!gatewayId) { [agentsQuery.data],
return allAgents; );
}
const boardIds = new Set(
boards.filter((board) => board.gateway_id === gatewayId).map((board) => board.id),
);
if (boardIds.size === 0) {
return [];
}
return allAgents.filter(
(agent): agent is AgentRead => Boolean(agent.board_id && boardIds.has(agent.board_id)),
);
}, [agentsQuery.data, boardsQuery.data, gatewayId]);
const status = const status =
statusQuery.data?.status === 200 ? statusQuery.data.data : null; statusQuery.data?.status === 200 ? statusQuery.data.data : null;

View File

@@ -65,7 +65,7 @@ export default function GatewaysPage() {
const gatewaysQuery = useListGatewaysApiV1GatewaysGet< const gatewaysQuery = useListGatewaysApiV1GatewaysGet<
listGatewaysApiV1GatewaysGetResponse, listGatewaysApiV1GatewaysGetResponse,
ApiError ApiError
>({ >(undefined, {
query: { query: {
enabled: Boolean(isSignedIn), enabled: Boolean(isSignedIn),
refetchInterval: 30_000, refetchInterval: 30_000,
@@ -73,7 +73,13 @@ export default function GatewaysPage() {
}, },
}); });
const gateways = useMemo(() => gatewaysQuery.data?.data ?? [], [gatewaysQuery.data]); const gateways = useMemo(
() =>
gatewaysQuery.data?.status === 200
? gatewaysQuery.data.data.items ?? []
: [],
[gatewaysQuery.data]
);
const sortedGateways = useMemo(() => [...gateways], [gateways]); const sortedGateways = useMemo(() => [...gateways], [gateways]);
const deleteMutation = useDeleteGatewayApiV1GatewaysGatewayIdDelete< const deleteMutation = useDeleteGatewayApiV1GatewaysGatewayIdDelete<
@@ -86,10 +92,18 @@ export default function GatewaysPage() {
await queryClient.cancelQueries({ queryKey: gatewaysKey }); await queryClient.cancelQueries({ queryKey: gatewaysKey });
const previous = const previous =
queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey); queryClient.getQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey);
if (previous) { if (previous && previous.status === 200) {
const nextItems = previous.data.items.filter(
(gateway) => gateway.id !== gatewayId
);
const removedCount = previous.data.items.length - nextItems.length;
queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey, { queryClient.setQueryData<listGatewaysApiV1GatewaysGetResponse>(gatewaysKey, {
...previous, ...previous,
data: previous.data.filter((gateway) => gateway.id !== gatewayId), data: {
...previous.data,
items: nextItems,
total: Math.max(0, previous.data.total - removedCount),
},
}); });
} }
return { previous }; return { previous };

View File

@@ -161,6 +161,7 @@ const payloadValue = (payload: Approval["payload"], key: string) => {
const approvalSummary = (approval: Approval) => { const approvalSummary = (approval: Approval) => {
const payload = approval.payload ?? {}; const payload = approval.payload ?? {};
const taskId = const taskId =
approval.task_id ??
payloadValue(payload, "task_id") ?? payloadValue(payload, "task_id") ??
payloadValue(payload, "taskId") ?? payloadValue(payload, "taskId") ??
payloadValue(payload, "taskID"); payloadValue(payload, "taskID");
@@ -223,7 +224,7 @@ export function BoardApprovalsPanel({
const raw = usingExternal const raw = usingExternal
? externalApprovals ?? [] ? externalApprovals ?? []
: approvalsQuery.data?.status === 200 : approvalsQuery.data?.status === 200
? approvalsQuery.data.data ? approvalsQuery.data.data.items ?? []
: []; : [];
return raw.map(normalizeApproval); return raw.map(normalizeApproval);
}, [approvalsQuery.data, externalApprovals, usingExternal]); }, [approvalsQuery.data, externalApprovals, usingExternal]);
@@ -266,9 +267,12 @@ export function BoardApprovalsPanel({
if (!previous || previous.status !== 200) return previous; if (!previous || previous.status !== 200) return previous;
return { return {
...previous, ...previous,
data: previous.data.map((item) => data: {
item.id === approvalId ? result.data : item, ...previous.data,
), items: previous.data.items.map((item) =>
item.id === approvalId ? result.data : item,
),
},
}; };
}, },
); );

View File

@@ -15,8 +15,8 @@ type Task = {
description?: string | null; description?: string | null;
due_at?: string | null; due_at?: string | null;
assigned_agent_id?: string | null; assigned_agent_id?: string | null;
assignee?: string; assignee?: string | null;
approvalsPendingCount?: number; approvals_pending_count?: number;
}; };
type TaskBoardProps = { type TaskBoardProps = {
@@ -187,9 +187,9 @@ export function TaskBoard({
key={task.id} key={task.id}
title={task.title} title={task.title}
priority={task.priority} priority={task.priority}
assignee={task.assignee} assignee={task.assignee ?? undefined}
due={formatDueDate(task.due_at)} due={formatDueDate(task.due_at)}
approvalsPendingCount={task.approvalsPendingCount} approvalsPendingCount={task.approvals_pending_count}
onClick={() => onTaskSelect?.(task)} onClick={() => onTaskSelect?.(task)}
draggable draggable
isDragging={draggingId === task.id} isDragging={draggingId === task.id}