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,6 +1,6 @@
import { z } from "zod";
import { investigationReportSchema, investigationDiagramSchema } from "./investigation-report";
import { investigationPresentationCursorSchema } from "./investigation-presentation";
import { investigationPresentationCursorSchema, investigationPresentationHistoryPageSchema, type InvestigationPresentationHistoryEvent } from "./investigation-presentation";
import type { WorkbenchAccess } from "./request-context";
import { env } from "@/shared/config/env";
import { createWorkbenchHeaders, findOwnedWorkbenchProject, getServerError } from "./project-client";
@@ -191,6 +191,7 @@ const taskPermissionsSchema = z.object({
can_execute_work_orders: z.boolean(),
can_decide_commands: z.boolean(),
can_execute_commands: z.boolean(),
can_answer_questions: z.boolean().optional(),
can_decide_access: z.boolean().default(false)
}).strict();
const externalWorkOrderActivitySchema = z.object({
@@ -221,9 +222,25 @@ const externalWorkOrderSnapshotSchema = z.preprocess((value) => {
scenario_code: z.string().min(1).nullable(),
feedback_requirements: z.array(z.string().min(1)).default([]),
activity: z.array(externalWorkOrderActivitySchema).default([]),
origin: z.literal("synthetic"),
origin: z.literal("system_data"),
updated_at: workbenchDateTimeSchema
}).strict());
const taskQuestionAnswerSchema = z.object({ question_id: z.string().min(1).max(128),
selected_option_ids: z.array(z.string().min(1).max(128)).max(12), custom_text: z.string().max(2000) }).strict();
const taskQuestionRequestSchema = z.object({
question_request_id: z.string().uuid(), task_id: z.string().uuid(), run_id: z.string().uuid(),
execution_generation: z.number().int().positive(), source_session_id: z.string(),
status: z.enum(["pending", "answered", "cancelled"]), version: z.number().int().positive(),
created_at: workbenchDateTimeSchema, answered_at: workbenchDateTimeSchema.nullable(), answered_by: z.string().nullable(),
questions: z.array(z.object({ question_id: z.string().min(1).max(128), title: z.string().min(1).max(200), body: z.string().min(1).max(2000),
options: z.array(z.object({ id: z.string().min(1).max(128), label: z.string().min(1).max(200) }).strict()).max(12),
mode: z.enum(["single", "multiple"]), allow_custom: z.boolean(), required: z.boolean() }).strict()).min(1).max(3),
answers: z.array(taskQuestionAnswerSchema).max(3)
}).strict();
export type TaskQuestionRequest = z.infer<typeof taskQuestionRequestSchema>;
export type TaskQuestionAnswer = z.infer<typeof taskQuestionAnswerSchema>;
export type AnswerTaskQuestion = (questionId: string, version: number, answers: TaskQuestionAnswer[], requestId: string) => Promise<unknown>;
const operationalTaskSchema = taskSummarySchema.extend({
latest_plan: planSnapshotSchema.nullable(),
latest_report: reportRevisionSchema.nullable(),
@@ -231,6 +248,7 @@ const operationalTaskSchema = taskSummarySchema.extend({
evidence: z.array(evidenceSchema),
actions: z.array(actionSchema),
access_requests: z.array(accessRequestSchema).default([]),
questions: z.array(taskQuestionRequestSchema).optional(),
permissions: taskPermissionsSchema,
work_order_snapshot: externalWorkOrderSnapshotSchema.nullable().default(null)
}).strict();
@@ -252,7 +270,7 @@ const notificationSchema = z.object({
title: z.string().min(1),
description: z.string().min(1),
lifecycle_status: z.enum(["active", "resolved"]).default("active"),
subject_type: z.enum(["task", "run", "access_request", "action"]).default("task"),
subject_type: z.enum(["task", "run", "access_request", "action", "question"]).default("task"),
subject_id: z.string().uuid().nullable().default(null),
report_reference: z.object({ execution_generation: z.number().int().positive(), revision: z.number().int().positive() }).strict().nullish(),
resolved_at: workbenchDateTimeSchema.nullable().default(null),
@@ -451,6 +469,39 @@ export async function fetchOperationalTaskPresentationCursor(taskId: string, get
));
}
export async function fetchOperationalTaskPresentationHistory(
taskId: string,
through: number,
getAccessToken?: WorkbenchAccess,
signal?: AbortSignal
) {
const items: InvestigationPresentationHistoryEvent[] = [];
let after = 0;
while (true) {
signal?.throwIfAborted();
const page = investigationPresentationHistoryPageSchema.parse(
await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/presentation-events?after=${after}&through=${through}&limit=100`,
getAccessToken,
{ signal }
)
);
if (page.task_id !== taskId || page.event_cursor !== through)
throw new Error("调查展示历史与所选任务或游标不一致");
let previous = after;
for (const item of page.items) {
if (item.event_seq <= previous || item.event_seq > through)
throw new Error("调查展示历史顺序不合法");
previous = item.event_seq;
items.push({ ...item, task_id: taskId });
}
if (page.next_after === null) return items;
if (page.next_after !== previous || previous <= after)
throw new Error("调查展示历史分页没有前进");
after = previous;
}
}
export async function createOperationalTask(input: CreateOperationalTaskInput, getAccessToken?: WorkbenchAccess) {
return operationalTaskSchema.parse(await operationalTaskRequest(
"/operational-tasks",
@@ -572,3 +623,13 @@ export async function fetchOperationalTaskEvidenceSnapshot(taskId: string, evide
export function createOperationalRequestId() {
return crypto.randomUUID();
}
export async function answerOperationalTaskQuestion(taskId: string, questionId: string,
version: number, answers: TaskQuestionAnswer[], requestId: string, getAccessToken?: WorkbenchAccess) {
return operationalTaskSchema.parse(await operationalTaskRequest(
`/operational-tasks/${encodeURIComponent(taskId)}/questions/${encodeURIComponent(questionId)}/answer`,
getAccessToken, { method: "POST", body: JSON.stringify({ request_id: requestId,
expected_version: version, answers: z.array(taskQuestionAnswerSchema).max(3).parse(answers) }) }
));
}