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:
+11
-2
@@ -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
@@ -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} />
|
||||
) : (
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user