feat: expand intelligent investigation workspace
Generic Container CI/CD / test-build-publish (push) Successful in 1m52s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m52s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent ddd03dbff6
commit c0e4231b83
223 changed files with 13155 additions and 5304 deletions
+1 -1
View File
@@ -231,7 +231,7 @@ export async function mockAgentApi(page: Page, options: MockAgentApiOptions = {}
await route.fulfill({
headers: corsHeaders,
json: {
schema_version: "frontend-action-registry@1",
schema_version: "frontend-action-registry@2",
actions: []
}
});
@@ -1,5 +1,5 @@
import type { Page } from "@playwright/test";
import type { OperationalTask } from "../../../src/features/workbench/api/operational-task-client";
import type { OperationalTask, OperationalTaskNotification } from "../../../src/features/workbench/api/operational-task-client";
import { mockAgentApi } from "./mock-agent-api";
import { mockWorkbenchContext } from "./mock-workbench-context";
@@ -35,7 +35,8 @@ export function investigationFixture(index: number, title: string): OperationalT
};
}
export async function mockInvestigationPresentation(page: Page, tasks: OperationalTask[]) {
export type SavedPresentationEvent = { task_id: string; event_seq: number; run_id: string; execution_generation: number; occurred_at: string; command: unknown };
export async function mockInvestigationPresentation(page: Page, tasks: OperationalTask[], savedEvents: SavedPresentationEvent[] = [], notifications: OperationalTaskNotification[] = []) {
await mockWorkbenchContext(page);
await mockAgentApi(page);
const mutations: string[] = [];
@@ -62,17 +63,28 @@ export async function mockInvestigationPresentation(page: Page, tasks: Operation
artifact_availability: "not_configured", schedule_availability: "not_configured", work_order_availability: "not_configured",
alert_availability: "not_configured", generated_at: now, conditions: [], alerts: [], scenarios: [], latest_analysis: null, work_orders: [] },
tasks: tasks.map((task) => Object.fromEntries(Object.entries(task).filter(([key]) =>
!["latest_plan", "latest_report", "latest_progress", "evidence", "actions", "access_requests", "work_order_snapshot", "permissions"].includes(key)))),
notifications: [] } });
!["latest_plan", "latest_report", "latest_progress", "evidence", "actions", "access_requests", "work_order_snapshot", "permissions", "questions"].includes(key)))),
notifications } });
});
await page.route("https://tjwater-api.invalid/api/v1/operational-tasks/**", (route) => {
const task = tasks.find((item) => route.request().url().includes(item.task_id));
if (!task) return route.fulfill({ status: 404 });
if (route.request().url().endsWith("/presentation-cursor")) return route.fulfill({ json: {
task_id: task.task_id, execution_generation: task.execution_generation, active_run: {
run_id: task.active_run!.run_id, generation: task.execution_generation, status: "running"
}, event_cursor: 10
task_id: task.task_id, execution_generation: task.execution_generation, active_run: task.active_run ? {
run_id: task.active_run.run_id, generation: task.execution_generation, status: "running"
} : null, event_cursor: sequence
} });
if (route.request().url().includes("/presentation-events")) {
const url = new URL(route.request().url());
const through = Number(url.searchParams.get("through") ?? sequence);
const after = Number(url.searchParams.get("after") ?? 0);
const limit = Number(url.searchParams.get("limit") ?? 100);
const events = savedEvents.filter((item) => item.task_id === task.task_id && item.event_seq > after && item.event_seq <= through).sort((a, b) => a.event_seq - b.event_seq);
return route.fulfill({ json: { task_id: task.task_id, event_cursor: through,
items: events.slice(0, limit).map(({ task_id: _, ...item }) => item),
next_after: events.length > limit ? events[limit - 1].event_seq : null
} });
}
if (route.request().url().includes("/reports")) return route.fulfill({ json: { items: [], next_offset: null } });
return route.fulfill({ json: task });
});
@@ -92,17 +104,20 @@ export async function mockInvestigationPresentation(page: Page, tasks: Operation
return new Response(body, { headers: { "Content-Type": "text/event-stream" } });
};
});
let sequence = 10;
let sequence = Math.max(10, ...savedEvents.map((item) => item.event_seq));
return {
mutations,
emit: async (task: OperationalTask, command: unknown) => page.evaluate((event) => {
emit: async (task: OperationalTask, command: unknown) => {
savedEvents.push({ task_id: task.task_id, run_id: task.active_run!.run_id, event_seq: sequence + 1, execution_generation: task.execution_generation, occurred_at: now, command });
return page.evaluate((event) => {
(window as unknown as { emitInvestigationTestEvent: (event: unknown) => void }).emitInvestigationTestEvent(event);
}, { schema: "workbench-event@1", event_seq: ++sequence,
event_id: `95000000-0000-4000-8000-${String(sequence).padStart(12, "0")}`, project_id: projectId,
topic: "operational.presentation.requested", aggregate_type: "operational_task", aggregate_id: task.task_id,
aggregate_revision: 1, occurred_at: now, metadata: {
task_id: task.task_id, run_id: task.active_run!.run_id, execution_generation: task.execution_generation, command
} })
} });
}
};
}