diff --git a/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts b/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts index e33ecf0b..bcead1d1 100644 --- a/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts +++ b/web/hwlab-cloud-web/scripts/workbench-e2e-server.ts @@ -294,6 +294,26 @@ async function projectManagementPayload(request: IncomingMessage, response: Serv const links = state.projectLinks; const tasks = projectManagementTasks(links); + if (path === "/v1/project-management/mdtodo/sources" && method === "POST") { + const body = await readJson(request); + return json(response, 201, projectManagementEnvelope({ source: projectManagementSourceFromBody(body, source) })); + } + const sourceMutationMatch = path.match(/^\/v1\/project-management\/mdtodo\/sources\/([^/]+)$/u); + if (sourceMutationMatch && method === "PATCH") { + const body = await readJson(request); + const sourceId = decodeURIComponent(sourceMutationMatch[1] ?? String(source.sourceId)); + return json(response, 200, projectManagementEnvelope({ source: projectManagementSourceFromBody(body, { ...source, sourceId }) })); + } + const sourceProbeMatch = path.match(/^\/v1\/project-management\/mdtodo\/sources\/([^/]+)\/probe$/u); + if (sourceProbeMatch && method === "POST") { + const sourceId = decodeURIComponent(sourceProbeMatch[1] ?? String(source.sourceId)); + return json(response, 200, projectManagementEnvelope({ probe: { ok: true, status: "ok", sourceId, sourceKind: "hwpod-workspace", entries: files.length, valuesRedacted: true } })); + } + const sourceReindexMatch = path.match(/^\/v1\/project-management\/mdtodo\/sources\/([^/]+)\/reindex$/u); + if (sourceReindexMatch && method === "POST") { + const sourceId = decodeURIComponent(sourceReindexMatch[1] ?? String(source.sourceId)); + return json(response, 200, projectManagementEnvelope({ projection: { sourceId, sourceKind: "hwpod-workspace", rootRef: "docs/MDTODO/", documentCount: files.length, taskCount: tasks.length, projectedAt: "2026-06-25T09:05:00.000Z", valuesRedacted: true } })); + } if (path === "/v1/project-management/workbench-links" && method === "POST") { const body = await readJson(request); const link = projectManagementLinkFromBody(body); @@ -330,27 +350,49 @@ function projectManagementEnvelope(payload: JsonRecord): JsonRecord { } function projectManagementSource(): JsonRecord { - return { sourceId: "hwlab-v03-mdtodo", kind: "mdtodo-file-tree", displayName: "HWLAB MDTODO", projectId: "project_hwlab_v03", status: "active", rootRef: "[redacted]", valuesRedacted: true }; + return { sourceId: "hwlab-v03-mdtodo", kind: "mdtodo-file-tree", sourceKind: "hwpod-workspace", displayName: "HWLAB v0.3 MDTODO", projectId: "project_hwlab_v03", status: "active", hwpodId: "d601-f103-v2", nodeId: "node-d601-f103-v2", workspaceRootRef: "", mdtodoRootRef: "docs/MDTODO/", maxFiles: 120, valuesRedacted: true }; +} + +function projectManagementSourceFromBody(body: JsonRecord, base: JsonRecord): JsonRecord { + return { + ...base, + sourceId: String(body.sourceId ?? base.sourceId ?? "hwlab-v03-mdtodo"), + sourceKind: String(body.sourceKind ?? "hwpod-workspace"), + displayName: String(body.displayName ?? base.displayName ?? "HWLAB v0.3 MDTODO"), + projectId: String(body.projectId ?? base.projectId ?? "project_hwlab_v03"), + hwpodId: String(body.hwpodId ?? base.hwpodId ?? "d601-f103-v2"), + nodeId: String(body.nodeId ?? base.nodeId ?? "node-d601-f103-v2"), + workspaceRootRef: String(body.workspaceRootRef ?? base.workspaceRootRef ?? ""), + mdtodoRootRef: String(body.mdtodoRootRef ?? base.mdtodoRootRef ?? "docs/MDTODO/"), + maxFiles: positiveInteger(body.maxFiles, Number(base.maxFiles ?? 120)), + status: "active", + valuesRedacted: true + }; } function projectManagementFiles(): JsonRecord[] { return [ - { sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", projectId: "project_hwlab_v03", relativePath: "project-management/PJ2026-01/MDTODO.md", title: "项目管理任务", taskCount: 2, parseError: null, revision: 1, updatedAt: "2026-06-25T09:00:00.000Z" }, - { sourceId: "hwlab-v03-mdtodo", fileRef: "file_rollout", projectId: "project_hwlab_v03", relativePath: "ops/D601-v03-rollout.md", title: "D601 发布任务", taskCount: 1, parseError: null, revision: 1, updatedAt: "2026-06-25T09:00:00.000Z" } + { sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", projectId: "project_hwlab_v03", relativePath: "docs/MDTODO/hwlab-v03-mdtodo-web-sample.md", title: "项目管理任务", taskCount: 3, parseError: null, revision: 3, fingerprint: "sha256:e2e-plan", updatedAt: "2026-06-25T09:00:00.000Z" }, + { sourceId: "hwlab-v03-mdtodo", fileRef: "file_rollout", projectId: "project_hwlab_v03", relativePath: "docs/MDTODO/D601-v03-rollout.md", title: "D601 发布任务", taskCount: 1, parseError: null, revision: 1, fingerprint: "sha256:e2e-rollout", updatedAt: "2026-06-25T09:00:00.000Z" } ]; } function projectManagementTasks(links: JsonRecord[] = []): JsonRecord[] { const linkCount = (taskRef: string) => links.filter((link) => link.taskRef === taskRef).length; + const r1 = "mdtodo:hwlab-v03-mdtodo:file_plan:R1"; + const r11 = "mdtodo:hwlab-v03-mdtodo:file_plan:R1.1"; + const r111 = "mdtodo:hwlab-v03-mdtodo:file_plan:R1.1.1"; + const r2 = "mdtodo:hwlab-v03-mdtodo:file_rollout:R2"; 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: 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" } + { taskRef: r1, projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "R1", title: "实现项目根导航", status: "done", parentTaskRef: null, depth: 0, lineNumber: 4, ordinal: 1, linkCount: linkCount(r1), updatedAt: "2026-06-25T09:00:00.000Z" }, + { taskRef: r11, projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "R1.1", title: "展示 MDTODO 任务详情", status: "open", parentTaskRef: r1, depth: 1, lineNumber: 8, ordinal: 2, linkCount: linkCount(r11), updatedAt: "2026-06-25T09:00:00.000Z" }, + { taskRef: r111, projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_plan", taskId: "R1.1.1", title: "Source 顶部控制", status: "in_progress", parentTaskRef: r11, depth: 2, lineNumber: 12, ordinal: 3, linkCount: linkCount(r111), updatedAt: "2026-06-25T09:00:00.000Z" }, + { taskRef: r2, projectId: "project_hwlab_v03", sourceId: "hwlab-v03-mdtodo", fileRef: "file_rollout", taskId: "R2", title: "D601 v03 页面验收", status: "blocked", parentTaskRef: null, depth: 0, lineNumber: 3, ordinal: 4, linkCount: linkCount(r2), updatedAt: "2026-06-25T09:00:00.000Z" } ]; } 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" }]; + return [{ linkId: "lnk_project_task_root", projectId: "project_hwlab_v03", taskRef: "mdtodo:hwlab-v03-mdtodo:file_plan:R1", sessionId: "ses_project_seed", traceId: "trc_project_seed", role: "reference", updatedAt: "2026-06-25T09:00:00.000Z" }]; } function projectManagementLinkFromBody(body: JsonRecord): JsonRecord { diff --git a/web/hwlab-cloud-web/src/api/index.ts b/web/hwlab-cloud-web/src/api/index.ts index 706c66df..f7258078 100644 --- a/web/hwlab-cloud-web/src/api/index.ts +++ b/web/hwlab-cloud-web/src/api/index.ts @@ -14,7 +14,7 @@ export { apiKeysAPI } from "./apiKeys"; export { usageAPI } from "./usage"; export { billingAPI } from "./billing"; export { systemAPI, type SkillUploadFileInput } from "./system"; -export { projectManagementAPI, type MdtodoFileRecord, type MdtodoTaskRecord, type ProjectNavigationResponse, type ProjectRecord, type ProjectSource, type ProjectWorkbenchLinkRecord } from "./projectManagement"; +export { projectManagementAPI, type MdtodoFileRecord, type MdtodoTaskRecord, type ProjectNavigationResponse, type ProjectRecord, type ProjectSource, type ProjectSourceInput, type ProjectWorkbenchLinkRecord } from "./projectManagement"; import { fetchJson } from "./client"; import { accessAPI } from "./access"; diff --git a/web/hwlab-cloud-web/src/api/projectManagement.ts b/web/hwlab-cloud-web/src/api/projectManagement.ts index 46961487..93961645 100644 --- a/web/hwlab-cloud-web/src/api/projectManagement.ts +++ b/web/hwlab-cloud-web/src/api/projectManagement.ts @@ -58,13 +58,31 @@ export interface ProjectRecord { export interface ProjectSource { sourceId: string; kind?: string; + sourceKind?: string; displayName?: string; projectId?: string; status?: string; + hwpodId?: string | null; + nodeId?: string | null; + workspaceRootRef?: string | null; + mdtodoRootRef?: string | null; + maxFiles?: number; updatedAt?: string; valuesRedacted?: boolean; } +export interface ProjectSourceInput { + sourceId?: string; + sourceKind?: string; + displayName?: string; + projectId?: string; + hwpodId?: string; + nodeId?: string; + workspaceRootRef?: string; + mdtodoRootRef?: string; + maxFiles?: number; +} + export interface MdtodoFileRecord { sourceId: string; fileRef: string; @@ -109,6 +127,9 @@ export interface ProjectWorkbenchLinkRecord { export interface ProjectListResponse extends ProjectManagementEnvelope { projects?: ProjectRecord[] } export interface ProjectSourceListResponse extends ProjectManagementEnvelope { sources?: ProjectSource[] } +export interface ProjectSourceMutationResponse extends ProjectManagementEnvelope { source?: ProjectSource } +export interface ProjectSourceProbeResponse extends ProjectManagementEnvelope { probe?: { ok?: boolean; status?: string; sourceId?: string; sourceKind?: string; entries?: number; valuesRedacted?: boolean } } +export interface ProjectSourceReindexResponse extends ProjectManagementEnvelope { projection?: ProjectProjectionSummary & { sourceKind?: string; rootRef?: string; valuesRedacted?: boolean } } export interface MdtodoFileListResponse extends ProjectManagementEnvelope { files?: MdtodoFileRecord[] } export interface MdtodoTaskListResponse extends ProjectManagementEnvelope { tasks?: MdtodoTaskRecord[] } export interface ProjectWorkbenchLinkListResponse extends ProjectManagementEnvelope { links?: ProjectWorkbenchLinkRecord[] } @@ -129,6 +150,10 @@ export const projectManagementAPI = { navigation: (): Promise> => fetchJson("/v1/project-management/navigation", { timeoutMs: 12000, timeoutName: "project navigation" }), projects: (): Promise> => fetchJson("/v1/project-management/projects", { timeoutMs: 12000, timeoutName: "project list" }), sources: (): Promise> => fetchJson("/v1/project-management/mdtodo/sources", { timeoutMs: 12000, timeoutName: "project mdtodo sources" }), + createSource: (input: ProjectSourceInput): Promise> => fetchJson("/v1/project-management/mdtodo/sources", { method: "POST", body: JSON.stringify(input), timeoutMs: 12000, timeoutName: "project mdtodo source create" }), + updateSource: (sourceId: string, input: ProjectSourceInput): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}`, { method: "PATCH", body: JSON.stringify(input), timeoutMs: 12000, timeoutName: "project mdtodo source update" }), + probeSource: (sourceId: string): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}/probe`, { method: "POST", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "project mdtodo source probe" }), + reindexSource: (sourceId: string): Promise> => fetchJson(`/v1/project-management/mdtodo/sources/${encodeURIComponent(sourceId)}/reindex`, { method: "POST", body: JSON.stringify({}), timeoutMs: 12000, timeoutName: "project mdtodo source reindex" }), files: (sourceId?: string | null): Promise> => fetchJson(`/v1/project-management/mdtodo/files${queryString({ sourceId })}`, { timeoutMs: 12000, timeoutName: "project mdtodo files" }), tasks: (query: MdtodoTaskQuery = {}): Promise> => fetchJson(`/v1/project-management/mdtodo/tasks${queryString({ sourceId: query.sourceId, fileRef: query.fileRef, projectId: query.projectId })}`, { timeoutMs: 12000, timeoutName: "project mdtodo tasks" }), workbenchLinks: (query: ProjectWorkbenchLinkQuery = {}): Promise> => fetchJson(`/v1/project-management/workbench-links${queryString({ taskRef: query.taskRef, projectId: query.projectId, sessionId: query.sessionId })}`, { timeoutMs: 12000, timeoutName: "project workbench links" }) diff --git a/web/hwlab-cloud-web/src/views/projects/MdtodoView.vue b/web/hwlab-cloud-web/src/views/projects/MdtodoView.vue index 5133da8e..ee455921 100644 --- a/web/hwlab-cloud-web/src/views/projects/MdtodoView.vue +++ b/web/hwlab-cloud-web/src/views/projects/MdtodoView.vue @@ -2,9 +2,9 @@