feat(workbench): integrate operational workflows and map state

This commit is contained in:
2026-09-24 04:34:46 +08:00
parent 1752b9ef84
commit 436d5500f1
76 changed files with 5081 additions and 1937 deletions
@@ -5,11 +5,6 @@ import type { components } from "./generated/schema";
type Schemas = components["schemas"];
export type PeripheralContext = Schemas["Context"];
export type PeripheralTask = Schemas["Task"];
export type PeripheralTaskPage = Schemas["TaskPage"];
export type PeripheralCreateTask = Schemas["CreateTask"];
export type PeripheralTransition = Schemas["Transition"];
export type PeripheralAddFeedback = Schemas["AddFeedback"];
export type PeripheralScenarioState = Schemas["ScenarioState"];
export type PeripheralScenario = PeripheralScenarioState["scenario"];
export type PeripheralPointPage = Schemas["PointPage"];
@@ -26,7 +21,7 @@ export type PeripheralCreateTestDataRecord = Schemas["CreateTestDataRecord"];
export type PeripheralUpdateTestDataRecord = Schemas["UpdateTestDataRecord"];
export type PeripheralDeletedResource = Schemas["DeletedResource"];
type PeripheralMutationResult = PeripheralTask | PeripheralScenarioState | PeripheralWorkOrder |
type PeripheralMutationResult = PeripheralScenarioState | PeripheralWorkOrder |
PeripheralTestDataRecord | PeripheralDeletedResource;
const API_PREFIX = "/api/sample/v1";
@@ -34,7 +29,6 @@ const API_PREFIX = "/api/sample/v1";
export class PeripheralSampleClient {
private readonly baseUrl = env.TJWATER_PERIPHERAL_SAMPLE_API_BASE_URL;
private projectIdPromise: Promise<string> | null = null;
private readonly taskProjection = new Map<string, PeripheralTask>();
private readonly pendingCommands = new Map<string, { requestId: string; payload: string }>();
private scenarioProjection: PeripheralScenarioState | null = null;
@@ -56,17 +50,6 @@ export class PeripheralSampleClient {
return this.get<PeripheralObservations>(`/monitoring/observations?${query}`, signal);
}
async listTasks(cursor?: string | null, signal?: AbortSignal) {
const query = new URLSearchParams({ limit: "20" });
if (cursor) query.set("cursor", cursor);
const page = await this.get<PeripheralTaskPage>(`/tasks?${query}`, signal);
return { ...page, items: page.items.map((task) => this.projectTask(task)) };
}
getTask(taskId: string, signal?: AbortSignal) {
return this.getTaskProjected(`/tasks/${encodeURIComponent(taskId)}`, signal);
}
async getReceipt(requestId: string, signal?: AbortSignal) {
const receipt = await this.get<Schemas["Receipt"]>(
`/requests/${encodeURIComponent(requestId)}`, signal
@@ -84,26 +67,6 @@ export class PeripheralSampleClient {
}, signal);
}
createTask(input: Omit<PeripheralCreateTask, "request_id">, signal?: AbortSignal) {
return this.postRecoverable<PeripheralTask>("/tasks", {
...input, request_id: crypto.randomUUID()
}, signal);
}
transition(taskId: string, input: Omit<PeripheralTransition, "request_id">, signal?: AbortSignal) {
return this.postRecoverable<PeripheralTask>(
`/tasks/${encodeURIComponent(taskId)}/transitions`,
{ ...input, request_id: crypto.randomUUID() }, signal
);
}
addFeedback(taskId: string, input: Omit<PeripheralAddFeedback, "request_id" | "feedback_id">, signal?: AbortSignal) {
return this.postRecoverable<PeripheralTask>(
`/tasks/${encodeURIComponent(taskId)}/feedback`,
{ ...input, request_id: crypto.randomUUID(), feedback_id: crypto.randomUUID() }, signal
);
}
createWorkOrder(input: Omit<PeripheralCreateWorkOrder, "request_id">, signal?: AbortSignal) {
return this.postRecoverable<PeripheralWorkOrder>("/work-orders", {
...input, request_id: crypto.randomUUID()
@@ -144,10 +107,6 @@ export class PeripheralSampleClient {
);
}
private async getTaskProjected(path: string, signal?: AbortSignal) {
return this.projectTask(await this.get<PeripheralTask>(path, signal));
}
private async getScenarioProjected(path: string, signal?: AbortSignal) {
return this.projectScenario(await this.get<PeripheralScenarioState>(path, signal));
}
@@ -268,8 +227,7 @@ export class PeripheralSampleClient {
return this.projectIdPromise;
}
private projectResult(result: PeripheralMutationResult) {
if (isTask(result)) return this.projectTask(result);
private projectResult(result: unknown): PeripheralMutationResult {
if (isScenarioState(result)) return this.projectScenario(result);
if (isManagedResult(result)) return result;
throw new PeripheralSampleError("INVALID_RESPONSE", "响应缺少可识别的样板资源,提交是否生效未知", "unknown", null);
@@ -283,21 +241,6 @@ export class PeripheralSampleClient {
return next;
}
private projectTask(next: PeripheralTask) {
const current = this.taskProjection.get(next.task_id);
if (current && next.revision < current.revision) return current;
const feedback = [...new Map(next.feedback.map((item) => [item.feedback_id, item])).values()];
const projected = { ...next, feedback };
this.taskProjection.set(next.task_id, projected);
return projected;
}
}
function isTask(value: unknown): value is PeripheralTask {
return Boolean(value && typeof value === "object" &&
typeof (value as Partial<PeripheralTask>).task_id === "string" &&
typeof (value as Partial<PeripheralTask>).revision === "number" &&
Array.isArray((value as Partial<PeripheralTask>).feedback));
}
function isScenarioState(value: unknown): value is PeripheralScenarioState {
@@ -367,10 +310,6 @@ function isRecognizedReadPayload(path: string, value: unknown): boolean {
if (path.startsWith("/requests/")) {
return typeof item.request_id === "string" && "result" in item;
}
if (path.startsWith("/tasks?")) {
return isPageOf(value, isTask) && (item.next_cursor === null || typeof item.next_cursor === "string");
}
if (path.startsWith("/tasks/")) return isTask(value);
if (path.startsWith("/monitoring/points")) return isPageOf(value, isPoint);
if (path.startsWith("/monitoring/alerts")) return isPageOf(value, isAlert);
if (path.startsWith("/monitoring/observations")) {