feat: add project-aware agent workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m9s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m39s

This commit is contained in:
2026-10-22 18:52:18 +08:00
parent c6a0e74e72
commit 2d8506fea0
191 changed files with 12373 additions and 3556 deletions
@@ -1,7 +1,7 @@
import { z } from "zod";
import { investigationReportSchema, investigationDiagramSchema } from "./investigation-report";
import { investigationPresentationCursorSchema } from "./investigation-presentation";
import type { AccessTokenProvider } from "@/shared/auth/keycloak-auth";
import type { WorkbenchAccess } from "./request-context";
import { env } from "@/shared/config/env";
import { createWorkbenchHeaders, findOwnedWorkbenchProject, getServerError } from "./project-client";
@@ -316,6 +316,39 @@ const taskDataSnapshotSchema = z.object({
"run_burst_location",
"analyze_valve_isolation",
"run_leakage_identification",
"get_mutation_status",
"query_nearby_elements",
"list_regions",
"get_region",
"get_region_nodes",
"get_node_connections",
"query_network_extent",
"list_major_nodes",
"get_model_configuration",
"list_model_configurations",
"search_project_places",
"reverse_geocode",
"get_project_info",
"export_project_model",
"get_sensor_placement_candidate",
"export_sensor_placement",
"list_task_schedules",
"get_task_run_history",
"calculate_demand_allocation",
"run_burst_simulation",
"edit_model_element",
"edit_model_configuration",
"clean_scada_readings",
"write_scada_readings",
"delete_scada_readings",
"save_simulation_scenario",
"create_sensor_placement",
"update_sensor_placement",
"create_operational_task",
"create_task_schedule",
"update_task_schedule",
"retry_operational_task",
"cancel_task_run",
]),
source_ref: z.string().min(1),
queried_at: workbenchDateTimeSchema,
@@ -366,7 +399,7 @@ export type CreateOperationalTaskInput = {
const SERVER_API_BASE_URL = env.TJWATER_SERVER_API_BASE_URL.replace(/\/$/, "");
async function operationalTaskRequest(path: string, getAccessToken?: AccessTokenProvider, init?: RequestInit) {
async function operationalTaskRequest(path: string, getAccessToken?: WorkbenchAccess, init?: RequestInit) {
const { project, accessToken } = await findOwnedWorkbenchProject(getAccessToken, init?.signal ?? undefined);
const headers = new Headers(createWorkbenchHeaders(accessToken, project.project_id));
if (init?.body) headers.set("Content-Type", "application/json");
@@ -376,7 +409,7 @@ async function operationalTaskRequest(path: string, getAccessToken?: AccessToken
return payload;
}
export async function fetchOperationalTask(taskId: string, getAccessToken?: AccessTokenProvider, signal?: AbortSignal) {
export async function fetchOperationalTask(taskId: string, getAccessToken?: WorkbenchAccess, signal?: AbortSignal) {
return operationalTaskSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}`,
getAccessToken,
@@ -384,19 +417,19 @@ export async function fetchOperationalTask(taskId: string, getAccessToken?: Acce
));
}
export async function fetchOperationalTaskReports(taskId: string, offset: number, getAccessToken?: AccessTokenProvider) {
export async function fetchOperationalTaskReports(taskId: string, offset: number, getAccessToken?: WorkbenchAccess) {
return reportPageSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/reports?limit=20&offset=${offset}`, getAccessToken,
));
}
export async function fetchOperationalTaskPresentationCursor(taskId: string, getAccessToken?: AccessTokenProvider, signal?: AbortSignal) {
export async function fetchOperationalTaskPresentationCursor(taskId: string, getAccessToken?: WorkbenchAccess, signal?: AbortSignal) {
return investigationPresentationCursorSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/presentation-cursor`, getAccessToken, { signal }
));
}
export async function createOperationalTask(input: CreateOperationalTaskInput, getAccessToken?: AccessTokenProvider) {
export async function createOperationalTask(input: CreateOperationalTaskInput, getAccessToken?: WorkbenchAccess) {
return operationalTaskSchema.parse(await operationalTaskRequest(
"/operational-tasks",
getAccessToken,
@@ -409,7 +442,7 @@ export async function decideOperationalTaskAction(taskId: string, actionId: stri
expected_version: number;
decision: OperationalTaskDecision;
comment?: string;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return actionSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/actions/${encodeURIComponent(actionId)}/decision`,
getAccessToken,
@@ -420,7 +453,7 @@ export async function decideOperationalTaskAction(taskId: string, actionId: stri
export async function executeOperationalTaskAction(taskId: string, actionId: string, input: {
request_id: string;
expected_version: number;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return actionSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/actions/${encodeURIComponent(actionId)}/execute`,
getAccessToken,
@@ -430,7 +463,7 @@ export async function executeOperationalTaskAction(taskId: string, actionId: str
export async function updateOperationalWorkOrderDraft(taskId: string, actionId: string, input: {
request_id: string; expected_version: number; payload: Record<string, unknown>;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return actionSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/actions/${encodeURIComponent(actionId)}/draft`,
getAccessToken, { method: "PATCH", body: JSON.stringify(input) }
@@ -439,7 +472,7 @@ export async function updateOperationalWorkOrderDraft(taskId: string, actionId:
export async function confirmOperationalWorkOrderDispatch(taskId: string, actionId: string, input: {
request_id: string; expected_version: number;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return actionSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/actions/${encodeURIComponent(actionId)}/confirm-dispatch`,
getAccessToken, { method: "POST", body: JSON.stringify(input) }
@@ -449,14 +482,14 @@ export async function confirmOperationalWorkOrderDispatch(taskId: string, action
export async function decideOperationalTaskAccess(taskId: string, accessRequestId: string, input: {
request_id: string; expected_version: number; decision: "approved" | "rejected";
approved_scopes: string[]; comment?: string;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return accessRequestSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/access-requests/${encodeURIComponent(accessRequestId)}/decision`,
getAccessToken, { method: "POST", body: JSON.stringify(input) }
));
}
export async function fetchOperationalTaskNotifications(getAccessToken?: AccessTokenProvider, signal?: AbortSignal) {
export async function fetchOperationalTaskNotifications(getAccessToken?: WorkbenchAccess, signal?: AbortSignal) {
return notificationPageSchema.parse(await operationalTaskRequest(
"/operational-task-notifications?unread_only=false&limit=100&offset=0",
getAccessToken,
@@ -464,7 +497,7 @@ export async function fetchOperationalTaskNotifications(getAccessToken?: AccessT
)).items;
}
export async function acknowledgeOperationalTaskNotification(notificationId: string, version: number, requestId: string, getAccessToken?: AccessTokenProvider) {
export async function acknowledgeOperationalTaskNotification(notificationId: string, version: number, requestId: string, getAccessToken?: WorkbenchAccess) {
return notificationSchema.parse(await operationalTaskRequest(
`/operational-task-notifications/${encodeURIComponent(notificationId)}/acknowledge`,
getAccessToken,
@@ -472,7 +505,7 @@ export async function acknowledgeOperationalTaskNotification(notificationId: str
));
}
export async function fetchOperationalTaskSchedules(getAccessToken?: AccessTokenProvider, signal?: AbortSignal) {
export async function fetchOperationalTaskSchedules(getAccessToken?: WorkbenchAccess, signal?: AbortSignal) {
return schedulePageSchema.parse(await operationalTaskRequest(
"/operational-tasks/schedules",
getAccessToken,
@@ -488,7 +521,7 @@ export async function createOperationalTaskSchedule(input: {
interval_seconds: number;
next_run_at: string;
priority: "normal" | "urgent";
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return scheduleSchema.parse(await operationalTaskRequest(
"/operational-tasks/schedules",
getAccessToken,
@@ -499,7 +532,7 @@ export async function createOperationalTaskSchedule(input: {
export async function reconcileOperationalTaskAction(taskId: string, actionId: string, input: {
request_id: string;
expected_version: number;
}, getAccessToken?: AccessTokenProvider) {
}, getAccessToken?: WorkbenchAccess) {
return actionSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/actions/${encodeURIComponent(actionId)}/reconcile`,
getAccessToken,
@@ -507,7 +540,7 @@ export async function reconcileOperationalTaskAction(taskId: string, actionId: s
));
}
export async function fetchOperationalTaskEvidenceSnapshot(taskId: string, evidenceId: string, getAccessToken?: AccessTokenProvider) {
export async function fetchOperationalTaskEvidenceSnapshot(taskId: string, evidenceId: string, getAccessToken?: WorkbenchAccess) {
return taskDataSnapshotSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/evidence/${encodeURIComponent(evidenceId)}/snapshot`,
getAccessToken