feat: add project workbench launch
This commit is contained in:
@@ -31,6 +31,7 @@ interface ScenarioState {
|
||||
requestLedger: JsonRecord[];
|
||||
legacyRequestLedger: JsonRecord[];
|
||||
chatRequests: JsonRecord[];
|
||||
projectLinks: JsonRecord[];
|
||||
listOmitSelected: boolean;
|
||||
sessionDelayMs: number;
|
||||
sessionDetailDelayMs: number;
|
||||
@@ -142,6 +143,7 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
||||
}
|
||||
if (path === "/auth/login" && method === "POST") return authLoginResponse(response);
|
||||
if (path === "/v1/workbench/events" && method === "GET") return sse(request, response, url);
|
||||
if (path === "/v1/workbench/launches" && method === "POST") return json(response, 201, await createWorkbenchLaunch(request));
|
||||
if (path === "/v1/workbench/sessions" && method === "GET") {
|
||||
await delay(state.sessionDelayMs);
|
||||
return json(response, 200, workbenchSessionListPayload(url));
|
||||
@@ -226,7 +228,7 @@ async function handleRequest(request: IncomingMessage, response: ServerResponse)
|
||||
if (path === "/v1/live-builds") return json(response, 200, { status: "ok", builds: [] });
|
||||
if (path === "/v1/hwpod/specs") return json(response, 200, hwpodSpecsPayload());
|
||||
if (path === "/v1/hwpod-node-ops") return json(response, 200, hwpodNodeOpsPayload());
|
||||
if (path.startsWith("/v1/project-management")) return projectManagementPayload(response, url, method);
|
||||
if (path.startsWith("/v1/project-management")) return projectManagementPayload(request, response, url, method);
|
||||
if (path === "/v1/web-performance/summary") {
|
||||
if (state.scenarioId === "performance-summary-error") return errorDiagnosticResponse(response, 502, "/v1/web-performance/summary", "upstream_unavailable", "暂时无法连接上游。");
|
||||
const matrixStatus = performanceSummaryErrorStatus(state.scenarioId);
|
||||
@@ -285,13 +287,20 @@ function hwpodNodeOpsPayload(): JsonRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function projectManagementPayload(response: ServerResponse, url: URL, method: string): void {
|
||||
if (method !== "GET" && method !== "HEAD") return json(response, 405, { ok: false, contractVersion: "project-management-v1", serviceId: "hwlab-project-management", error: { code: "method_not_allowed" }, valuesRedacted: true });
|
||||
async function projectManagementPayload(request: IncomingMessage, response: ServerResponse, url: URL, method: string): Promise<void> {
|
||||
const path = url.pathname;
|
||||
const source = projectManagementSource();
|
||||
const files = projectManagementFiles();
|
||||
const tasks = projectManagementTasks();
|
||||
const links = projectManagementLinks();
|
||||
const links = state.projectLinks;
|
||||
const tasks = projectManagementTasks(links);
|
||||
|
||||
if (path === "/v1/project-management/workbench-links" && method === "POST") {
|
||||
const body = await readJson(request);
|
||||
const link = projectManagementLinkFromBody(body);
|
||||
state.projectLinks.unshift(link);
|
||||
return json(response, 201, projectManagementEnvelope({ link }));
|
||||
}
|
||||
if (method !== "GET" && method !== "HEAD") return json(response, 405, { ok: false, contractVersion: "project-management-v1", serviceId: "hwlab-project-management", error: { code: "method_not_allowed" }, valuesRedacted: true });
|
||||
|
||||
if (path === "/v1/project-management" || path === "/v1/project-management/navigation") {
|
||||
return json(response, 200, projectManagementEnvelope({
|
||||
@@ -303,7 +312,7 @@ function projectManagementPayload(response: ServerResponse, url: URL, method: st
|
||||
{ id: "projects", label: "Projects", apiRoute: "/v1/project-management/projects" },
|
||||
{ id: "mdtodo", label: "MDTODO", apiRoute: "/v1/project-management/mdtodo/tasks" }
|
||||
],
|
||||
capabilities: { mdtodoProjection: true, workbenchLaunch: false, workbenchLinks: true, sourceOfTruth: "markdown-files" }
|
||||
capabilities: { mdtodoProjection: true, workbenchLaunch: true, workbenchLinks: true, sourceOfTruth: "markdown-files" }
|
||||
},
|
||||
projection: { sourceId: source.sourceId, documentCount: files.length, taskCount: tasks.length, projectedAt: "2026-06-25T09:00:00.000Z" }
|
||||
}));
|
||||
@@ -331,18 +340,79 @@ function projectManagementFiles(): JsonRecord[] {
|
||||
];
|
||||
}
|
||||
|
||||
function projectManagementTasks(): JsonRecord[] {
|
||||
function projectManagementTasks(links: JsonRecord[] = []): JsonRecord[] {
|
||||
const linkCount = (taskRef: string) => links.filter((link) => link.taskRef === taskRef).length;
|
||||
return [
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t1", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "t1", title: "实现项目根导航", status: "done", parentTaskRef: null, depth: 0, lineNumber: 4, ordinal: 1, linkCount: 1, updatedAt: "2026-06-25T09:00:00.000Z" },
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t2", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "t2", title: "展示 MDTODO 任务详情", status: "open", parentTaskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t1", depth: 1, lineNumber: 5, ordinal: 2, linkCount: 0, updatedAt: "2026-06-25T09:00:00.000Z" },
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_rollout:t1", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_rollout", taskId: "t1", title: "D601 v03 页面验收", status: "blocked", parentTaskRef: null, depth: 0, lineNumber: 3, ordinal: 3, linkCount: 0, updatedAt: "2026-06-25T09:00:00.000Z" }
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t1", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "t1", title: "实现项目根导航", status: "done", parentTaskRef: null, depth: 0, lineNumber: 4, ordinal: 1, linkCount: linkCount("mdtodo:hwlab-v03-mdtodo:file_plan:t1"), updatedAt: "2026-06-25T09:00:00.000Z" },
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t2", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "t2", title: "展示 MDTODO 任务详情", status: "open", parentTaskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t1", depth: 1, lineNumber: 5, ordinal: 2, linkCount: linkCount("mdtodo:hwlab-v03-mdtodo:file_plan:t2"), updatedAt: "2026-06-25T09:00:00.000Z" },
|
||||
{ taskRef: "mdtodo:hwlab-v03-mdtodo:file_rollout:t1", projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_rollout", taskId: "t1", title: "D601 v03 页面验收", status: "blocked", parentTaskRef: null, depth: 0, lineNumber: 3, ordinal: 3, linkCount: linkCount("mdtodo:hwlab-v03-mdtodo:file_rollout:t1"), updatedAt: "2026-06-25T09:00:00.000Z" }
|
||||
];
|
||||
}
|
||||
|
||||
function projectManagementLinks(): JsonRecord[] {
|
||||
function projectManagementSeedLinks(): JsonRecord[] {
|
||||
return [{ linkId: "lnk_project_task_root", projectId: "project_hwlab_v03", taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:t1", sessionId: "ses_project_seed", traceId: "trc_project_seed", role: "reference", updatedAt: "2026-06-25T09:00:00.000Z" }];
|
||||
}
|
||||
|
||||
function projectManagementLinkFromBody(body: JsonRecord): JsonRecord {
|
||||
const now = new Date().toISOString();
|
||||
return {
|
||||
linkId: String(body.linkId ?? `lnk_project_launch_${Date.now().toString(36)}`),
|
||||
projectId: String(body.projectId ?? "project_hwlab_v03"),
|
||||
taskRef: typeof body.taskRef === "string" ? body.taskRef : null,
|
||||
sessionId: typeof body.sessionId === "string" ? body.sessionId : null,
|
||||
traceId: typeof body.traceId === "string" ? body.traceId : null,
|
||||
role: String(body.role ?? "launch"),
|
||||
updatedAt: now,
|
||||
link: body.link && typeof body.link === "object" ? body.link : { valuesRedacted: true }
|
||||
};
|
||||
}
|
||||
|
||||
async function createWorkbenchLaunch(request: IncomingMessage): Promise<JsonRecord> {
|
||||
const body = await readJson(request);
|
||||
const now = new Date().toISOString();
|
||||
const sessionId = typeof body.sessionId === "string" && body.sessionId.trim() ? body.sessionId : `ses_project_launch_${Date.now().toString(36)}`;
|
||||
const conversationId = typeof body.conversationId === "string" && body.conversationId.trim() ? body.conversationId : `cnv_project_launch_${Date.now().toString(36)}`;
|
||||
const launchContext = body.launchContext && typeof body.launchContext === "object" ? body.launchContext as JsonRecord : {};
|
||||
const session: SessionRecord = {
|
||||
sessionId,
|
||||
conversationId,
|
||||
status: "idle",
|
||||
projectId: String(body.projectId ?? launchContext.projectId ?? "project_hwlab_v03"),
|
||||
launchContext,
|
||||
metadata: { launchContext },
|
||||
startedAt: now,
|
||||
updatedAt: now,
|
||||
messageCount: 0,
|
||||
firstUserMessagePreview: null,
|
||||
messages: []
|
||||
};
|
||||
state.sessions.unshift(session);
|
||||
const link = projectManagementLinkFromBody({
|
||||
linkId: `lnk_project_launch_${sessionId.replace(/^ses_/u, "")}`,
|
||||
projectId: session.projectId,
|
||||
taskRef: body.taskRef,
|
||||
sessionId,
|
||||
traceId: null,
|
||||
role: "launch",
|
||||
link: { launchContext, workbenchUrl: `/workbench/sessions/${sessionId}`, valuesRedacted: true }
|
||||
});
|
||||
state.projectLinks.unshift(link);
|
||||
return {
|
||||
ok: true,
|
||||
status: "created",
|
||||
contractVersion: "workbench-launch-v1",
|
||||
sessionId,
|
||||
conversationId,
|
||||
projectId: session.projectId,
|
||||
taskRef: body.taskRef,
|
||||
workbenchUrl: `/workbench/sessions/${sessionId}`,
|
||||
linkId: link.linkId,
|
||||
link,
|
||||
launchContext,
|
||||
valuesRedacted: true
|
||||
};
|
||||
}
|
||||
|
||||
function webPerformanceSummaryPayload(url: URL): JsonRecord {
|
||||
const sampleWindow = fakePerformanceWindow(url.searchParams.get("window"));
|
||||
const rows = [
|
||||
@@ -808,6 +878,7 @@ function createScenarioState(scenarioId: string): ScenarioState {
|
||||
requestLedger: [],
|
||||
legacyRequestLedger: [],
|
||||
chatRequests: [],
|
||||
projectLinks: projectManagementSeedLinks(),
|
||||
listOmitSelected: id === "selected-missing-from-list",
|
||||
sessionDelayMs: id === "loading" ? 2_500 : 0,
|
||||
sessionDetailDelayMs: id === "legacy-cnv-deeplink-canonical" ? 1_500 : id === "session-switch-delayed-detail-frame" ? 900 : id === "submit-authority-race" ? 1_000 : 0,
|
||||
|
||||
Reference in New Issue
Block a user