feat: filter operations by type + fix stale auto-refresh closure (#522) (#527)

* feat: filter operations by type + fix stale closure in auto-refresh

- Add `type` query param to GET /operations endpoint and engine layer
- Add operation type dropdown filter in Background Operations UI
- Fix auto-refresh interval using stale statusFilter/offset closure by
  adding filter state to useEffect deps and wrapping loadOperations in
  useCallback (fixes #522)
- Regenerate OpenAPI spec and all SDK clients

* fix: update Rust CLI list_operations call with new type parameter
This commit is contained in:
Nicolò Boschi 2026-03-09 13:17:00 +01:00 committed by GitHub
parent 7accac94b2
commit f7a60f898d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 188 additions and 60 deletions

View file

@ -3427,13 +3427,17 @@ def _register_routes(app: FastAPI):
"/v1/default/banks/{bank_id}/operations", "/v1/default/banks/{bank_id}/operations",
response_model=OperationsListResponse, response_model=OperationsListResponse,
summary="List async operations", summary="List async operations",
description="Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first.", description="Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.",
operation_id="list_operations", operation_id="list_operations",
tags=["Operations"], tags=["Operations"],
) )
async def api_list_operations( async def api_list_operations(
bank_id: str, bank_id: str,
status: str | None = Query(default=None, description="Filter by status: pending, completed, or failed"), status: str | None = Query(default=None, description="Filter by status: pending, completed, or failed"),
type: str | None = Query(
default=None,
description="Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery",
),
limit: int = Query(default=20, ge=1, le=100, description="Maximum number of operations to return"), limit: int = Query(default=20, ge=1, le=100, description="Maximum number of operations to return"),
offset: int = Query(default=0, ge=0, description="Number of operations to skip"), offset: int = Query(default=0, ge=0, description="Number of operations to skip"),
request_context: RequestContext = Depends(get_request_context), request_context: RequestContext = Depends(get_request_context),
@ -3441,7 +3445,7 @@ def _register_routes(app: FastAPI):
"""List async operations for a memory bank with optional filtering and pagination.""" """List async operations for a memory bank with optional filtering and pagination."""
try: try:
result = await app.state.memory.list_operations( result = await app.state.memory.list_operations(
bank_id, status=status, limit=limit, offset=offset, request_context=request_context bank_id, status=status, task_type=type, limit=limit, offset=offset, request_context=request_context
) )
return OperationsListResponse( return OperationsListResponse(
bank_id=bank_id, bank_id=bank_id,

View file

@ -6840,6 +6840,7 @@ class MemoryEngine(MemoryEngineInterface):
bank_id: str, bank_id: str,
*, *,
status: str | None = None, status: str | None = None,
task_type: str | None = None,
limit: int = 20, limit: int = 20,
offset: int = 0, offset: int = 0,
request_context: "RequestContext", request_context: "RequestContext",
@ -6849,6 +6850,7 @@ class MemoryEngine(MemoryEngineInterface):
Args: Args:
bank_id: Bank identifier bank_id: Bank identifier
status: Optional status filter (pending, completed, failed) status: Optional status filter (pending, completed, failed)
task_type: Optional operation type filter (retain, consolidation, etc.)
limit: Maximum number of operations to return (default 20) limit: Maximum number of operations to return (default 20)
offset: Number of operations to skip (default 0) offset: Number of operations to skip (default 0)
request_context: Request context for authentication request_context: Request context for authentication
@ -6877,6 +6879,10 @@ class MemoryEngine(MemoryEngineInterface):
where_conditions.append(f"status = ${len(params) + 1}") where_conditions.append(f"status = ${len(params) + 1}")
params.append(status) params.append(status)
if task_type:
where_conditions.append(f"operation_type = ${len(params) + 1}")
params.append(task_type)
where_clause = " AND ".join(where_conditions) where_clause = " AND ".join(where_conditions)
# Get total count (with filter) # Get total count (with filter)

View file

@ -242,7 +242,7 @@ impl ApiClient {
pub fn poll_operation(&self, agent_id: &str, operation_id: &str, verbose: bool) -> Result<(bool, Option<String>)> { pub fn poll_operation(&self, agent_id: &str, operation_id: &str, verbose: bool) -> Result<(bool, Option<String>)> {
self.runtime.block_on(async { self.runtime.block_on(async {
loop { loop {
let response = self.client.list_operations(agent_id, None, None, None, None).await?; let response = self.client.list_operations(agent_id, None, None, None, None, None).await?;
let ops = response.into_inner(); let ops = response.into_inner();
// Find our operation // Find our operation
@ -329,7 +329,7 @@ impl ApiClient {
pub fn list_operations(&self, agent_id: &str, _verbose: bool) -> Result<OperationsResponse> { pub fn list_operations(&self, agent_id: &str, _verbose: bool) -> Result<OperationsResponse> {
self.runtime.block_on(async { self.runtime.block_on(async {
let response = self.client.list_operations(agent_id, None, None, None, None).await?; let response = self.client.list_operations(agent_id, None, None, None, None, None).await?;
let value = response.into_inner(); let value = response.into_inner();
// Convert to JSON Value first, then parse into our type // Convert to JSON Value first, then parse into our type
let json_value = serde_json::to_value(&value)?; let json_value = serde_json::to_value(&value)?;

View file

@ -1600,7 +1600,8 @@ paths:
/v1/default/banks/{bank_id}/operations: /v1/default/banks/{bank_id}/operations:
get: get:
description: "Get a list of async operations for a specific agent, with optional\ description: "Get a list of async operations for a specific agent, with optional\
\ filtering by status. Results are sorted by most recent first." \ filtering by status and operation type. Results are sorted by most recent\
\ first."
operationId: list_operations operationId: list_operations
parameters: parameters:
- explode: false - explode: false
@ -1620,6 +1621,16 @@ paths:
nullable: true nullable: true
type: string type: string
style: form style: form
- description: "Filter by operation type: retain, consolidation, refresh_mental_model,\
\ file_convert_retain, webhook_delivery"
explode: true
in: query
name: type
required: false
schema:
nullable: true
type: string
style: form
- description: Maximum number of operations to return - description: Maximum number of operations to return
explode: true explode: true
in: query in: query

View file

@ -280,6 +280,7 @@ type ApiListOperationsRequest struct {
ApiService *OperationsAPIService ApiService *OperationsAPIService
bankId string bankId string
status *string status *string
type_ *string
limit *int32 limit *int32
offset *int32 offset *int32
authorization *string authorization *string
@ -291,6 +292,12 @@ func (r ApiListOperationsRequest) Status(status string) ApiListOperationsRequest
return r return r
} }
// Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery
func (r ApiListOperationsRequest) Type_(type_ string) ApiListOperationsRequest {
r.type_ = &type_
return r
}
// Maximum number of operations to return // Maximum number of operations to return
func (r ApiListOperationsRequest) Limit(limit int32) ApiListOperationsRequest { func (r ApiListOperationsRequest) Limit(limit int32) ApiListOperationsRequest {
r.limit = &limit r.limit = &limit
@ -315,7 +322,7 @@ func (r ApiListOperationsRequest) Execute() (*OperationsListResponse, *http.Resp
/* /*
ListOperations List async operations ListOperations List async operations
Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first. Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.
@param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background(). @param ctx context.Context - for authentication, logging, cancellation, deadlines, tracing, etc. Passed from http.Request or context.Background().
@param bankId @param bankId
@ -354,6 +361,9 @@ func (a *OperationsAPIService) ListOperationsExecute(r ApiListOperationsRequest)
if r.status != nil { if r.status != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "status", r.status, "form", "") parameterAddToHeaderOrQuery(localVarQueryParams, "status", r.status, "form", "")
} }
if r.type_ != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "type", r.type_, "form", "")
}
if r.limit != nil { if r.limit != nil {
parameterAddToHeaderOrQuery(localVarQueryParams, "limit", r.limit, "form", "") parameterAddToHeaderOrQuery(localVarQueryParams, "limit", r.limit, "form", "")
} else { } else {

View file

@ -632,6 +632,7 @@ class OperationsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None,
type: Annotated[Optional[StrictStr], Field(description="Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery")] = None,
limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None, limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None,
offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None, offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -650,12 +651,14 @@ class OperationsApi:
) -> OperationsListResponse: ) -> OperationsListResponse:
"""List async operations """List async operations
Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first. Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.
:param bank_id: (required) :param bank_id: (required)
:type bank_id: str :type bank_id: str
:param status: Filter by status: pending, completed, or failed :param status: Filter by status: pending, completed, or failed
:type status: str :type status: str
:param type: Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery
:type type: str
:param limit: Maximum number of operations to return :param limit: Maximum number of operations to return
:type limit: int :type limit: int
:param offset: Number of operations to skip :param offset: Number of operations to skip
@ -687,6 +690,7 @@ class OperationsApi:
_param = self._list_operations_serialize( _param = self._list_operations_serialize(
bank_id=bank_id, bank_id=bank_id,
status=status, status=status,
type=type,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -716,6 +720,7 @@ class OperationsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None,
type: Annotated[Optional[StrictStr], Field(description="Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery")] = None,
limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None, limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None,
offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None, offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -734,12 +739,14 @@ class OperationsApi:
) -> ApiResponse[OperationsListResponse]: ) -> ApiResponse[OperationsListResponse]:
"""List async operations """List async operations
Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first. Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.
:param bank_id: (required) :param bank_id: (required)
:type bank_id: str :type bank_id: str
:param status: Filter by status: pending, completed, or failed :param status: Filter by status: pending, completed, or failed
:type status: str :type status: str
:param type: Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery
:type type: str
:param limit: Maximum number of operations to return :param limit: Maximum number of operations to return
:type limit: int :type limit: int
:param offset: Number of operations to skip :param offset: Number of operations to skip
@ -771,6 +778,7 @@ class OperationsApi:
_param = self._list_operations_serialize( _param = self._list_operations_serialize(
bank_id=bank_id, bank_id=bank_id,
status=status, status=status,
type=type,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -800,6 +808,7 @@ class OperationsApi:
self, self,
bank_id: StrictStr, bank_id: StrictStr,
status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None, status: Annotated[Optional[StrictStr], Field(description="Filter by status: pending, completed, or failed")] = None,
type: Annotated[Optional[StrictStr], Field(description="Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery")] = None,
limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None, limit: Annotated[Optional[Annotated[int, Field(le=100, strict=True, ge=1)]], Field(description="Maximum number of operations to return")] = None,
offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None, offset: Annotated[Optional[Annotated[int, Field(strict=True, ge=0)]], Field(description="Number of operations to skip")] = None,
authorization: Optional[StrictStr] = None, authorization: Optional[StrictStr] = None,
@ -818,12 +827,14 @@ class OperationsApi:
) -> RESTResponseType: ) -> RESTResponseType:
"""List async operations """List async operations
Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first. Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.
:param bank_id: (required) :param bank_id: (required)
:type bank_id: str :type bank_id: str
:param status: Filter by status: pending, completed, or failed :param status: Filter by status: pending, completed, or failed
:type status: str :type status: str
:param type: Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery
:type type: str
:param limit: Maximum number of operations to return :param limit: Maximum number of operations to return
:type limit: int :type limit: int
:param offset: Number of operations to skip :param offset: Number of operations to skip
@ -855,6 +866,7 @@ class OperationsApi:
_param = self._list_operations_serialize( _param = self._list_operations_serialize(
bank_id=bank_id, bank_id=bank_id,
status=status, status=status,
type=type,
limit=limit, limit=limit,
offset=offset, offset=offset,
authorization=authorization, authorization=authorization,
@ -879,6 +891,7 @@ class OperationsApi:
self, self,
bank_id, bank_id,
status, status,
type,
limit, limit,
offset, offset,
authorization, authorization,
@ -910,6 +923,10 @@ class OperationsApi:
_query_params.append(('status', status)) _query_params.append(('status', status))
if type is not None:
_query_params.append(('type', type))
if limit is not None: if limit is not None:
_query_params.append(('limit', limit)) _query_params.append(('limit', limit))

View file

@ -738,7 +738,7 @@ export const getChunk = <ThrowOnError extends boolean = false>(
/** /**
* List async operations * List async operations
* *
* Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first. * Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.
*/ */
export const listOperations = <ThrowOnError extends boolean = false>( export const listOperations = <ThrowOnError extends boolean = false>(
options: Options<ListOperationsData, ThrowOnError>, options: Options<ListOperationsData, ThrowOnError>,

View file

@ -3715,6 +3715,12 @@ export type ListOperationsData = {
* Filter by status: pending, completed, or failed * Filter by status: pending, completed, or failed
*/ */
status?: string | null; status?: string | null;
/**
* Type
*
* Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery
*/
type?: string | null;
/** /**
* Limit * Limit
* *

View file

@ -9,13 +9,14 @@ export async function GET(
const { agentId } = await params; const { agentId } = await params;
const searchParams = request.nextUrl.searchParams; const searchParams = request.nextUrl.searchParams;
const status = searchParams.get("status") || undefined; const status = searchParams.get("status") || undefined;
const type = searchParams.get("type") || undefined;
const limit = searchParams.get("limit") ? parseInt(searchParams.get("limit")!) : undefined; const limit = searchParams.get("limit") ? parseInt(searchParams.get("limit")!) : undefined;
const offset = searchParams.get("offset") ? parseInt(searchParams.get("offset")!) : undefined; const offset = searchParams.get("offset") ? parseInt(searchParams.get("offset")!) : undefined;
const response = await sdk.listOperations({ const response = await sdk.listOperations({
client: lowLevelClient, client: lowLevelClient,
path: { bank_id: agentId }, path: { bank_id: agentId },
query: { status, limit, offset }, query: { status, type, limit, offset },
}); });
return NextResponse.json(response.data || {}, { status: 200 }); return NextResponse.json(response.data || {}, { status: 200 });
} catch (error) { } catch (error) {

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState, useEffect, useCallback } from "react";
import { useBank } from "@/lib/bank-context"; import { useBank } from "@/lib/bank-context";
import { client } from "@/lib/api"; import { client } from "@/lib/api";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -19,6 +19,13 @@ import {
DialogHeader, DialogHeader,
DialogTitle, DialogTitle,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { RefreshCw, Clock, AlertCircle, CheckCircle, Loader2, X } from "lucide-react"; import { RefreshCw, Clock, AlertCircle, CheckCircle, Loader2, X } from "lucide-react";
interface Operation { interface Operation {
@ -71,11 +78,21 @@ type OperationDetails =
child_operations?: never; child_operations?: never;
}; };
const OPERATION_TYPE_OPTIONS = [
{ value: "all", label: "All types" },
{ value: "retain", label: "Retain" },
{ value: "consolidation", label: "Consolidation" },
{ value: "refresh_mental_model", label: "Mental Model Refresh" },
{ value: "file_convert_retain", label: "File Convert & Retain" },
{ value: "webhook_delivery", label: "Webhook Delivery" },
];
export function BankOperationsView() { export function BankOperationsView() {
const { currentBank } = useBank(); const { currentBank } = useBank();
const [operations, setOperations] = useState<Operation[]>([]); const [operations, setOperations] = useState<Operation[]>([]);
const [totalOperations, setTotalOperations] = useState(0); const [totalOperations, setTotalOperations] = useState(0);
const [statusFilter, setStatusFilter] = useState<string | null>(null); const [statusFilter, setStatusFilter] = useState<string | null>(null);
const [taskTypeFilter, setTaskTypeFilter] = useState<string | null>(null);
const [limit] = useState(10); const [limit] = useState(10);
const [offset, setOffset] = useState(0); const [offset, setOffset] = useState(0);
const [cancellingOpId, setCancellingOpId] = useState<string | null>(null); const [cancellingOpId, setCancellingOpId] = useState<string | null>(null);
@ -84,37 +101,48 @@ export function BankOperationsView() {
const [dialogOpen, setDialogOpen] = useState(false); const [dialogOpen, setDialogOpen] = useState(false);
const [loadingDetails, setLoadingDetails] = useState(false); const [loadingDetails, setLoadingDetails] = useState(false);
const loadOperations = async ( const loadOperations = useCallback(
newStatusFilter: string | null = statusFilter, async (
newOffset: number = offset newStatusFilter: string | null = statusFilter,
) => { newOffset: number = offset,
if (!currentBank) return; newTaskTypeFilter: string | null = taskTypeFilter
) => {
if (!currentBank) return;
setLoading(true); setLoading(true);
try { try {
const opsData = await client.listOperations(currentBank, { const opsData = await client.listOperations(currentBank, {
status: newStatusFilter || undefined, status: newStatusFilter || undefined,
limit, type: newTaskTypeFilter || undefined,
offset: newOffset, limit,
}); offset: newOffset,
setOperations(opsData.operations || []); });
setTotalOperations(opsData.total || 0); setOperations(opsData.operations || []);
} catch (error) { setTotalOperations(opsData.total || 0);
console.error("Error loading operations:", error); } catch (error) {
} finally { console.error("Error loading operations:", error);
setLoading(false); } finally {
} setLoading(false);
}; }
},
[currentBank, statusFilter, offset, taskTypeFilter, limit]
);
const handleFilterChange = (newFilter: string | null) => { const handleFilterChange = (newFilter: string | null) => {
setStatusFilter(newFilter); setStatusFilter(newFilter);
setOffset(0); setOffset(0);
loadOperations(newFilter, 0); loadOperations(newFilter, 0, taskTypeFilter);
};
const handleTaskTypeFilterChange = (newTaskType: string | null) => {
setTaskTypeFilter(newTaskType);
setOffset(0);
loadOperations(statusFilter, 0, newTaskType);
}; };
const handlePageChange = (newOffset: number) => { const handlePageChange = (newOffset: number) => {
setOffset(newOffset); setOffset(newOffset);
loadOperations(statusFilter, newOffset); loadOperations(statusFilter, newOffset, taskTypeFilter);
}; };
const handleCancelOperation = async (operationId: string) => { const handleCancelOperation = async (operationId: string) => {
@ -149,12 +177,14 @@ export function BankOperationsView() {
useEffect(() => { useEffect(() => {
if (currentBank) { if (currentBank) {
loadOperations(); loadOperations(statusFilter, offset, taskTypeFilter);
// Refresh operations every 5 seconds const interval = setInterval(
const interval = setInterval(() => loadOperations(), 5000); () => loadOperations(statusFilter, offset, taskTypeFilter),
5000
);
return () => clearInterval(interval); return () => clearInterval(interval);
} }
}, [currentBank]); }, [currentBank, statusFilter, offset, taskTypeFilter]);
if (!currentBank) return null; if (!currentBank) return null;
@ -180,25 +210,47 @@ export function BankOperationsView() {
{statusFilter ? ` (${statusFilter})` : ""} {statusFilter ? ` (${statusFilter})` : ""}
</p> </p>
</div> </div>
<div className="flex gap-1 bg-muted p-1 rounded-lg"> <div className="flex items-center gap-3">
{[ <Select
{ value: null, label: "All" }, value={taskTypeFilter ?? "all"}
{ value: "pending", label: "Pending" }, onValueChange={(val) => handleTaskTypeFilterChange(val === "all" ? null : val)}
{ value: "completed", label: "Completed" }, >
{ value: "failed", label: "Failed" }, <SelectTrigger className="h-9 w-[180px] text-sm">
].map((filter) => ( <SelectValue placeholder="All types" />
<button </SelectTrigger>
key={filter.value ?? "all"} <SelectContent>
onClick={() => handleFilterChange(filter.value)} {OPERATION_TYPE_OPTIONS.map((opt) => (
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${ <SelectItem key={opt.value} value={opt.value}>
statusFilter === filter.value <div>
? "bg-background shadow-sm" <div>{opt.label}</div>
: "text-muted-foreground hover:text-foreground" {opt.value !== "all" && (
}`} <div className="text-xs text-muted-foreground font-mono">{opt.value}</div>
> )}
{filter.label} </div>
</button> </SelectItem>
))} ))}
</SelectContent>
</Select>
<div className="flex gap-1 bg-muted p-1 rounded-lg">
{[
{ value: null, label: "All" },
{ value: "pending", label: "Pending" },
{ value: "completed", label: "Completed" },
{ value: "failed", label: "Failed" },
].map((filter) => (
<button
key={filter.value ?? "all"}
onClick={() => handleFilterChange(filter.value)}
className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${
statusFilter === filter.value
? "bg-background shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
>
{filter.label}
</button>
))}
</div>
</div> </div>
</div> </div>
<div> <div>

View file

@ -248,10 +248,11 @@ export class ControlPlaneClient {
*/ */
async listOperations( async listOperations(
bankId: string, bankId: string,
options?: { status?: string; limit?: number; offset?: number } options?: { status?: string; type?: string; limit?: number; offset?: number }
) { ) {
const params = new URLSearchParams(); const params = new URLSearchParams();
if (options?.status) params.append("status", options.status); if (options?.status) params.append("status", options.status);
if (options?.type) params.append("type", options.type);
if (options?.limit) params.append("limit", options.limit.toString()); if (options?.limit) params.append("limit", options.limit.toString());
if (options?.offset) params.append("offset", options.offset.toString()); if (options?.offset) params.append("offset", options.offset.toString());
const query = params.toString(); const query = params.toString();

View file

@ -52,7 +52,9 @@
".next-50432/types/**/*.ts", ".next-50432/types/**/*.ts",
".next-50432/dev/types/**/*.ts", ".next-50432/dev/types/**/*.ts",
".next-54840/types/**/*.ts", ".next-54840/types/**/*.ts",
".next-54840/dev/types/**/*.ts" ".next-54840/dev/types/**/*.ts",
".next-64856/types/**/*.ts",
".next-64856/dev/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"

View file

@ -2357,7 +2357,7 @@
"Operations" "Operations"
], ],
"summary": "List async operations", "summary": "List async operations",
"description": "Get a list of async operations for a specific agent, with optional filtering by status. Results are sorted by most recent first.", "description": "Get a list of async operations for a specific agent, with optional filtering by status and operation type. Results are sorted by most recent first.",
"operationId": "list_operations", "operationId": "list_operations",
"parameters": [ "parameters": [
{ {
@ -2387,6 +2387,24 @@
}, },
"description": "Filter by status: pending, completed, or failed" "description": "Filter by status: pending, completed, or failed"
}, },
{
"name": "type",
"in": "query",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"description": "Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery",
"title": "Type"
},
"description": "Filter by operation type: retain, consolidation, refresh_mental_model, file_convert_retain, webhook_delivery"
},
{ {
"name": "limit", "name": "limit",
"in": "query", "in": "query",