feat(web): Markdown 渲染器 + 拆解轨迹 + 需你处理区 + 复审报告折叠卡片

- RichDoc(marked+DOMPurify+highlight.js CDN)统一渲染 gate spec/复审报告/任务树 SPEC;- Transcript 组件:拆解评审「拆解轨迹」懒加载 planner run transcript;- 「审批闸」改名「需你处理」并并入 needs_attention(requeue/接管/取消 + 完整复审报告,按代码/安全分段折叠卡片、标裁决徽章);- 子任务显 scopeFiles 文件范围 + frozen「待拆解确认」标。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-29 23:32:30 +08:00
parent 5a609a8675
commit af690701bf
10 changed files with 280 additions and 66 deletions
+11 -2
View File
@@ -54,6 +54,13 @@ export function buildTaskTree(flat) {
if (pid && byId.has(pid)) byId.get(pid).children.push(node);
else roots.push(node);
});
// 冻结标记:父任务尚未批准拆解(plan_review/analyzing)时,子任务虽落在 speccing/ready/blocked
// 实际被编排器闸冻结、不会领取执行。前端据此改显「待拆解确认」,避免误读成「写方案中/可执行」。
byId.forEach((node) => {
const pid = node._raw.parentId;
const ps = pid && byId.has(pid) ? byId.get(pid)._raw.status : null;
node.frozen = ps === 'plan_review' || ps === 'analyzing';
});
// 子节点按优先级(P0 在前)再按创建时间排序
const sortKids = (n) => {
n.children.sort((a, b) => a.prio.localeCompare(b.prio) || a._raw.createdAt.localeCompare(b._raw.createdAt));
@@ -76,13 +83,15 @@ export function buildTaskTree(flat) {
return { active, archived };
}
// ── 审批闸卡片 ─────────────────────────────────────────
// ── 「需你处理」卡片:审批闸(plan/spec/exec) + 需人工(attention) ────────────
export function adaptApproval(tk, projName) {
const gate = tk.status.replace('_review', ''); // plan | spec | exec
// needs_attention 不是审批闸,是卡住态——并入同区但操作不同(requeue/接管/取消,非 accept/reject
const gate = tk.status === 'needs_attention' ? 'attention' : tk.status.replace('_review', ''); // plan | spec | exec | attention
const r = tk.result || {};
let doc;
if (gate === 'plan') doc = tk.plan;
else if (gate === 'spec') doc = tk.spec;
else if (gate === 'attention') doc = tk.lastRunError || tk.spec || tk.plan || '(任务卡住,无错误详情——可能为执行前重评估 / 合并冲突)';
else doc = r.summary || r.diffSummary || tk.operations; // exec:优先 code review 报告(做了什么/怎么做/测试/逐条 review)
const out = {
id: tk.id, gate, taskId: tk.id, title: tk.title,
+7 -4
View File
@@ -300,19 +300,22 @@ export function App() {
const project = projects.find((p) => p.id === currentId) || projects[0];
const projNameOf = (pid) => (projectsRaw.find((p) => p.id === pid) || {}).name;
const { active: tasks, archived } = buildTaskTree(tasksRaw);
// 「需你处理」= 三个审批闸 + needs_attention(卡住待人工)。后者并入同区但操作不同。
const REVIEW = new Set(['plan_review', 'spec_review', 'exec_review']);
const gates = approvalsRaw
.filter((a) => a.projectId === currentId && REVIEW.has(a.status))
.filter((a) => a.projectId === currentId && (REVIEW.has(a.status) || a.status === 'needs_attention'))
.map((a) => {
const g = adaptApproval(a, projNameOf(a.projectId));
// 拆解评审:挂上实际拆出的子任务(标题/复杂度/优先级/依赖/状态),供评审弹层结构化展示
if (g.gate === 'plan') {
g.subtasks = tasksRaw
.filter((tk) => tk.parentId === a.id)
.map((tk) => ({ id: tk.id, title: tk.title, complexity: tk.complexity, priority: tk.priority, deps: tk.deps || [], status: tk.status }));
.map((tk) => ({ id: tk.id, title: tk.title, complexity: tk.complexity, priority: tk.priority, deps: tk.deps || [], status: tk.status, scopeFiles: tk.scopeFiles || [] }));
}
return g;
});
})
// 审批闸在前、需人工在后
.sort((x, y) => (x.gate === 'attention' ? 1 : 0) - (y.gate === 'attention' ? 1 : 0));
const agents = adaptActiveAgents(agentsResp, currentId);
const events = eventsRaw.map(adaptEvent);
const quota = adaptQuota(usage);
@@ -423,7 +426,7 @@ export function App() {
onSync={onSync}
onClose={() => setShowConfig(false)} /> : null}
<window.MaestroKitAgentSection agents={agents} quota={quota} t={t} onOpenStream={setStreamAgent} />
<window.MaestroKitGateSection approvals={gates} onDecide={decide} t={t} />
<window.MaestroKitGateSection approvals={gates} onDecide={decide} taskOps={taskOps} onTakeover={takeover} t={t} />
{tasks.length ? (
<window.MaestroKitTaskSection tasks={tasks} onToast={toast} onCreate={createTask} onTakeover={takeover} taskOps={taskOps} t={t} />
) : (
+5
View File
@@ -7,6 +7,11 @@ import '@ds/_ds_bundle.js';
// 文案 i18n(真实数据由 src/api.js + src/adapt.js 提供,不再用 data.js mock
import '@ds/ui_kits/console/i18n.js';
// 共享 Markdown 渲染组件(window.MaestroRichDoc)——须在引用它的 GateSection/TaskTree 之前
import '@ds/ui_kits/console/RichDoc.jsx';
// 共享 transcript 回放组件(window.MaestroTranscript)——拆解评审「拆解轨迹」等用
import '@ds/ui_kits/console/Transcript.jsx';
// 六个整屏 surface(保持 design/ 源文件不动,作为单一真相源)
import '@ds/ui_kits/console/Sidebar.jsx';
import '@ds/ui_kits/console/EventPanel.jsx';