diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..631b97999
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+# 敏感信息
+API/.env
+
+# 运行时数据
+assets/
+data/
+
+# 系统文件
+.DS_Store
+__pycache__/
+*.pyc
+history.json
diff --git a/.qoder/better-loop/2026-07-21/160331-infinite-canvas/canvas.json b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/canvas.json
new file mode 100644
index 000000000..9e4924ab3
--- /dev/null
+++ b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/canvas.json
@@ -0,0 +1,123 @@
+{
+ "schemaVersion": 1,
+ "summary": {
+ "evidenceMode": "session-limited",
+ "evidenceBoundary": {
+ "manifest": {
+ "schemaVersion": 2,
+ "sourceFingerprint": "7254844991a82cce",
+ "adapterVersion": "qoder-task-loop-source-v2",
+ "platform": "qoder",
+ "selection": {
+ "strategy": "all-eligible",
+ "eligibleCount": 0,
+ "analyzedCount": 0,
+ "confidence": "Low"
+ }
+ },
+ "deliveryEvidenceLevels": [],
+ "sourceGaps": []
+ },
+ "semanticFacets": {
+ "schemaVersion": 1,
+ "status": "supplementary",
+ "entries": [
+ {
+ "id": "session-insight:source-coverage",
+ "kind": "redacted-summary",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "source-coverage",
+ "Low"
+ ],
+ "summary": "Analyzed 0 of 0 sessions; 0/5 enabled source roots exist. Use this as the current workspace evidence boundary for final insight cards.",
+ "evidenceRefs": [],
+ "modelVersion": "session-insights-v1"
+ },
+ {
+ "id": "session-insight:validation-behavior",
+ "kind": "redacted-summary",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "validation-behavior",
+ "Low"
+ ],
+ "summary": "No validation command category was observed in the analyzed session sample. Inspect more sessions or add explicit validation guidance before claiming validation-after-edit behavior.",
+ "evidenceRefs": [],
+ "modelVersion": "session-insights-v1"
+ },
+ {
+ "id": "session-insight:post-edit-validation",
+ "kind": "rework-correction",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "post-edit-validation",
+ "Low"
+ ],
+ "summary": "No edit event was observed in the analyzed sample. Inspect more sessions before making claims about edit or validation habits.",
+ "evidenceRefs": [],
+ "modelVersion": "session-insights-v1"
+ },
+ {
+ "id": "session-insight:execution-friction",
+ "kind": "friction-taxonomy",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "execution-friction",
+ "Low"
+ ],
+ "summary": "No strong execution friction signal in the analyzed sample. Keep friction claims narrow unless additional failed commands, rejected actions, or warnings are inspected.",
+ "evidenceRefs": [],
+ "modelVersion": "session-insights-v1"
+ }
+ ]
+ },
+ "learningCapture": {
+ "schemaVersion": 1,
+ "state": "N/A",
+ "summary": "暂不适用——需要两个可比的观察窗口和一次改进对比。",
+ "interventions": []
+ }
+ },
+ "dimensions": [
+ {
+ "id": "task-understanding"
+ },
+ {
+ "id": "controlled-execution"
+ },
+ {
+ "id": "change-validation"
+ },
+ {
+ "id": "reliable-delivery"
+ },
+ {
+ "id": "learning-capture"
+ }
+ ],
+ "findings": [
+ {
+ "id": "agent-context-entrypoint-missing"
+ },
+ {
+ "id": "monolith-scope-localization"
+ },
+ {
+ "id": "no-automated-validation"
+ },
+ {
+ "id": "no-quality-enforcement-gate"
+ },
+ {
+ "id": "dependency-not-pinned"
+ },
+ {
+ "id": "no-change-acceptance-path"
+ }
+ ]
+}
diff --git a/.qoder/better-loop/2026-07-21/160331-infinite-canvas/findings.json b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/findings.json
new file mode 100644
index 000000000..aefc1b7d1
--- /dev/null
+++ b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/findings.json
@@ -0,0 +1,191 @@
+{
+ "summary": {
+ "projectName": "Infinite-Canvas",
+ "locale": "zh-CN",
+ "modelId": "agent-work-loop-v4",
+ "reportContractVersion": 24,
+ "overview": "Infinite-Canvas 是一个 FastAPI 单体项目,具备基本启动脚本和依赖声明,但缺乏 agent 上下文指引、自动化验证、质量门禁和变更验收机制。本次审查为 session-limited 模式(无合格会话),结论仅基于项目静态证据。",
+ "aiAgentPractice": {
+ "inspectedSurfaces": [
+ "Workflows",
+ "Plugins",
+ "Memories"
+ ],
+ "coverageRows": [
+ {
+ "surface": "Workflows",
+ "scopes": [
+ "Project"
+ ],
+ "count": 7,
+ "paths": [
+ "workflows/2511.json",
+ "workflows/Flux2-Klein.json",
+ "workflows/LTXDirectorv2-API.json",
+ "workflows/upscale.json",
+ "workflows/Z-Image.json",
+ "workflows/Z-Image-Enhance.json",
+ "workflows/LTXDirectorv2-API.config.json"
+ ]
+ },
+ {
+ "surface": "Plugins",
+ "scopes": [
+ "Plugin"
+ ],
+ "count": 2,
+ "paths": [
+ "better-loop",
+ "qoder-create-plugin"
+ ]
+ },
+ {
+ "surface": "Memories",
+ "scopes": [
+ "Project"
+ ],
+ "count": 2,
+ "paths": [
+ "~/.qoder/memories/019f6f6d/projects/Users-yangfan-Desktop-Infinite-Canvas/project_introduction/Infinite-Canvas_AI_Agent项目概述.md",
+ "~/.qoder/memories/019f6f6d/projects/Users-yangfan-Desktop-Infinite-Canvas/project_tech_stack/Python后端技术栈与协议支持.md"
+ ]
+ }
+ ]
+ },
+ "suggestions": [],
+ "assignmentSummaries": [],
+ "dimensions": [
+ {
+ "id": "task-understanding",
+ "label": "任务理解",
+ "score": 25,
+ "summary": "README 提供了 fork 约束等基本信息,但无 agent 指令文件或模块化结构,agent 无法高效定位任务边界和风险区域。",
+ "findingRefs": [
+ "agent-context-entrypoint-missing",
+ "monolith-scope-localization"
+ ]
+ },
+ {
+ "id": "controlled-execution",
+ "label": "可控执行",
+ "score": 30,
+ "summary": "存在一键启动脚本和基本依赖声明,但依赖未锁定版本,无 doctor/reset 路径,环境可复现性不足。",
+ "findingRefs": [
+ "dependency-not-pinned"
+ ]
+ },
+ {
+ "id": "change-validation",
+ "label": "改动验证",
+ "score": 5,
+ "summary": "项目中不存在任何测试、lint、typecheck 或 CI 配置,agent 变更后无任何自动化反馈信号。",
+ "findingRefs": [
+ "no-automated-validation"
+ ]
+ },
+ {
+ "id": "reliable-delivery",
+ "label": "可靠交付",
+ "score": 10,
+ "summary": "无 CI 流水线、分支保护、PR 验收路径或回滚机制,变更可直接合入主分支而无机械化检查。",
+ "findingRefs": [
+ "no-quality-enforcement-gate",
+ "no-change-acceptance-path"
+ ]
+ },
+ {
+ "id": "learning-capture",
+ "label": "经验沉淀",
+ "score": 35,
+ "summary": "存在 2 条项目记忆和 7 个工作流资产,但无可观察的循环检测、复用改进或后续验证证据。",
+ "findingRefs": []
+ }
+ ]
+ },
+ "findings": [
+ {
+ "id": "agent-context-entrypoint-missing",
+ "title": "缺少 Agent 上下文入口文件",
+ "severity": "Medium",
+ "reason": "项目无 AGENTS.md、CLAUDE.md 或等效的 agent 指令文件。agent 无法从项目结构中获得任务边界、高风险区域、模块职责或下一步指引,只能依赖通读 README 和遍历目录推断上下文。",
+ "expectedOutput": [
+ "创建一个结构化的 agent 入口文件,覆盖项目定位、目录说明、启动路径、高风险区域和任务路由。"
+ ],
+ "expectedArtifact": "AGENTS.md",
+ "aiFixPrompt": "/better-loop fix this issue\n\n为 Infinite-Canvas 创建一个简洁的 AGENTS.md 入口文件(80-150 行),包含:项目定位、目录职责、启动命令、高风险区域、常见任务路径。\n\n## Validation\n\n- 确认文件存在且内容覆盖上述五个部分\n- 确认命令与 mac-启动服务.sh 和 main.py 一致",
+ "dimensionRefs": [
+ "task-understanding"
+ ]
+ },
+ {
+ "id": "monolith-scope-localization",
+ "title": "17,787 行单文件单体阻碍变更范围定位",
+ "severity": "High",
+ "reason": "main.py 包含全部路由、WebSocket 管理、LLM 调用、生图逻辑和文件处理(17,787 行),无模块或包拆分。agent 无法通过目录结构定位变更范围,任何修改都需要在巨大单文件中搜索目标,增加误改风险。",
+ "expectedOutput": [
+ "将 main.py 按功能域拆分为独立模块,每个模块职责单一,agent 可通过目录结构定位变更范围。"
+ ],
+ "expectedArtifact": "模块拆分方案",
+ "aiFixPrompt": "/better-loop fix this issue\n\n制定 main.py 的模块拆分方案:按功能域(路由、WebSocket、LLM、生图、文件处理)拆分为独立模块,保持现有 API 兼容。\n\n## Validation\n\n- 服务正常启动且端口 3000 可访问\n- 各模块可独立导入无循环依赖",
+ "dimensionRefs": [
+ "task-understanding"
+ ]
+ },
+ {
+ "id": "no-automated-validation",
+ "title": "无任何自动化验证手段",
+ "severity": "High",
+ "reason": "项目中不存在测试文件、lint 配置(ruff/flake8)、类型检查(mypy/pyright)或 CI 流水线。agent 变更后唯一的反馈是「服务是否启动不报错」,无法检测逻辑回归、类型错误或代码风格问题。",
+ "expectedOutput": [
+ "配置 ruff lint 和 pytest 冒烟测试,agent 变更后可在 2 分钟内获得可操作的反馈信号。"
+ ],
+ "expectedArtifact": "验证配置",
+ "aiFixPrompt": "/better-loop fix this issue\n\n为项目添加最小验证层:1) 配置 ruff 作为 linter;2) 添加 pytest 及至少覆盖核心路由的冒烟测试;3) 在 requirements.txt 中声明开发依赖。\n\n## Validation\n\n- ruff check 可运行并输出可操作的诊断\n- pytest 至少有一个通过的冒烟测试\n- 验证命令可在 2 分钟内完成",
+ "dimensionRefs": [
+ "change-validation"
+ ]
+ },
+ {
+ "id": "no-quality-enforcement-gate",
+ "title": "无质量执行门禁",
+ "severity": "Medium",
+ "reason": "无 pre-commit hooks、CI pipeline、schema 校验或架构边界检查。质量完全依赖人工审查,agent 产出的代码无机械化拦截路径,违反约束的变更可无阻碍地进入代码库。",
+ "expectedOutput": [
+ "配置 pre-commit hook,在提交前自动运行 lint 检查,违规时阻止提交并给出修复方向。"
+ ],
+ "expectedArtifact": "质量门禁配置",
+ "aiFixPrompt": "/better-loop fix this issue\n\n添加最小质量门禁:1) 配置 pre-commit hook 运行 ruff check;2) 添加基本的 .pre-commit-config.yaml。\n\n## Validation\n\n- pre-commit run --all-files 可执行\n- 违规时输出包含文件和规则信息",
+ "dimensionRefs": [
+ "reliable-delivery"
+ ]
+ },
+ {
+ "id": "dependency-not-pinned",
+ "title": "依赖未锁定版本,环境不可复现",
+ "severity": "Medium",
+ "reason": "requirements.txt 中 7 个依赖均无版本约束(无 == 或 >= 限定),无 pip-tools/poetry lockfile。不同时间或环境安装可能产生不兼容版本,agent 无法保证确定性环境复现。",
+ "expectedOutput": [
+ "生成带版本约束的依赖文件,确保不同环境安装结果一致。"
+ ],
+ "expectedArtifact": "锁定的依赖文件",
+ "aiFixPrompt": "/better-loop fix this issue\n\n锁定依赖版本:1) 使用 pip freeze 或 pip-compile 生成带版本约束的 requirements.txt;2) 或迁移到 poetry/pip-tools 管理 lockfile。\n\n## Validation\n\n- requirements.txt 中每个依赖有明确版本约束\n- 从 lockfile 安装后服务正常启动",
+ "dimensionRefs": [
+ "controlled-execution"
+ ]
+ },
+ {
+ "id": "no-change-acceptance-path",
+ "title": "无变更验收路径和安全边界",
+ "severity": "Medium",
+ "reason": "无分支保护、PR 模板、CODEOWNERS 或合并前检查。agent 产出的变更可直接合入主分支而无验收步骤。同时 data/ 目录下的运行时数据虽被 .gitignore 排除但存在于工作目录,agent 操作可能意外修改用户数据而无感知。",
+ "expectedOutput": [
+ "声明变更验收路径和运行时数据操作边界,agent 产出变更有明确的验收步骤。"
+ ],
+ "expectedArtifact": "验收流程文档",
+ "aiFixPrompt": "/better-loop fix this issue\n\n建立最小变更验收路径:1) 在 README 或 AGENTS.md 中声明变更需通过分支+PR 流程;2) 对 data/ 目录添加操作边界说明。\n\n## Validation\n\n- 文档中明确了变更验收流程\n- data/ 目录的操作边界有明确说明",
+ "dimensionRefs": [
+ "reliable-delivery"
+ ]
+ }
+ ]
+}
diff --git a/.qoder/better-loop/2026-07-21/160331-infinite-canvas/report.canvas.tsx b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/report.canvas.tsx
new file mode 100644
index 000000000..2036a5510
--- /dev/null
+++ b/.qoder/better-loop/2026-07-21/160331-infinite-canvas/report.canvas.tsx
@@ -0,0 +1,1825 @@
+import {
+ AreaChart,
+ Button,
+ Callout,
+ Card,
+ CardBody,
+ CardHeader,
+ CollapsibleSection,
+ Dialog,
+ Divider,
+ Fluency,
+ Grid,
+ H1,
+ H2,
+ IconButton,
+ MetricsGrid,
+ Row,
+ RiskHeatmap,
+ SendToChatButton,
+ Stack,
+ Table,
+ Tag,
+ Text,
+} from "qoder/canvas";
+import hostReportData from "./findings.json";
+import canvasData from "./canvas.json";
+
+function mergeCanvasRows(hostRows, canvasRows) {
+ const detailById = new Map(
+ (Array.isArray(canvasRows) ? canvasRows : [])
+ .filter((row) => row && typeof row === "object" && typeof row.id === "string")
+ .map((row) => [row.id, row]),
+ );
+ return (Array.isArray(hostRows) ? hostRows : []).map((row) => ({ ...detailById.get(row?.id), ...row }));
+}
+
+function mergeCanvasObjects(host, detail) {
+ if (!host || typeof host !== "object" || Array.isArray(host)) return detail;
+ if (!detail || typeof detail !== "object" || Array.isArray(detail)) return host;
+ const merged = { ...host };
+ for (const [key, value] of Object.entries(detail)) {
+ merged[key] = value && typeof value === "object" && !Array.isArray(value)
+ ? mergeCanvasObjects(host[key], value)
+ : value;
+ }
+ return merged;
+}
+
+function mergeCanvasReport(host, detail) {
+ const summary = host?.summary ?? {};
+ if (!detail || typeof detail !== "object" || Array.isArray(detail)) return host;
+ return {
+ summary: {
+ ...(detail?.summary ?? {}),
+ ...summary,
+ atAGlance: mergeCanvasObjects(detail?.summary?.atAGlance, summary.atAGlance),
+ dimensions: mergeCanvasRows(summary.dimensions, detail?.dimensions),
+ },
+ findings: mergeCanvasRows(host?.findings, detail?.findings),
+ };
+}
+
+const report = mergeCanvasReport(hostReportData, canvasData);
+
+const pageStyle = { maxWidth: 960, margin: "0 auto", padding: 16, boxSizing: "border-box" };
+const taskLoopPageStyle = { ...pageStyle, maxWidth: 1200, padding: 20 };
+const taskLoopReaderCopyStyle = { maxWidth: 680 };
+
+const DIMENSION_SUMMARY_EXAMPLE = "Example: project guidance makes the main workflow clear, but ownership for cross-cutting changes is not documented.";
+
+function list(value) {
+ return Array.isArray(value) ? value : [];
+}
+
+function clampScore(value) {
+ const score = Number(value);
+ if (!Number.isFinite(score)) return 0;
+ return Math.max(0, Math.min(100, score));
+}
+
+function projectName() {
+ return report.summary?.projectName ?? "Qoder Harness Report";
+}
+
+function textValue(value) {
+ return typeof value === "string" ? value.trim() : "";
+}
+
+function openingStrengths() {
+ const explicit = list(report.summary?.strengths).map(textValue).filter(Boolean);
+ return explicit.length ? explicit.slice(0, 3) : ["Reviewed project signals are organized into dimensions and issue findings."];
+}
+
+function averageScore(dimensions) {
+ if (dimensions.length === 0) return 0;
+ return Math.round(dimensions.reduce((sum, row) => sum + clampScore(row.score), 0) / dimensions.length);
+}
+
+function scoreTone(score) {
+ if (score >= 70) return "success";
+ if (score >= 40) return "warning";
+ return "danger";
+}
+
+function stageStatus(score) {
+ if (score >= 70) return "high";
+ if (score >= 40) return "medium";
+ if (score > 0) return "low";
+ return "blocked";
+}
+
+function fluencyReason(row) {
+ return textValue(row?.summary) || taskLoopCopy(
+ "No reviewed score explanation is available for this dimension.",
+ "这个维度暂时没有经过复核的评分说明。",
+ );
+}
+
+function splitFluencyTooltipReason(value) {
+ let remaining = textValue(value).replace(/\s+/g, " ");
+ const chunks = [];
+ const limits = /[\u3400-\u9fff]/.test(remaining) ? [20, 20, 20, 20] : [34, 30, 30, 30];
+ for (const limit of limits) {
+ if (!remaining) break;
+ if (remaining.length <= limit) {
+ chunks.push(remaining);
+ remaining = "";
+ break;
+ }
+ let cut = remaining.lastIndexOf(" ", limit);
+ if (cut < Math.floor(limit * 0.55)) cut = limit;
+ chunks.push(remaining.slice(0, cut).trim());
+ remaining = remaining.slice(cut).trim();
+ }
+ if (remaining && chunks.length) {
+ chunks[chunks.length - 1] = `${chunks[chunks.length - 1].slice(0, 29)}…`;
+ }
+ return chunks;
+}
+
+function dimensionFluencyStages(dimensions) {
+ return dimensions.map((row) => {
+ const score = clampScore(row.score);
+ const usesGenericBand = row.id !== "learning-capture";
+ return {
+ id: row.id,
+ name: taskLoopDimensionLabel(row.id),
+ score,
+ ...(usesGenericBand ? { status: stageStatus(score), blocker: score <= 20 } : {}),
+ };
+ });
+}
+
+function dimensionFluencyTooltip(row) {
+ const [title, ...rows] = splitFluencyTooltipReason(fluencyReason(row));
+ return {
+ title,
+ rows: rows.map((value) => ({ value })),
+ };
+}
+
+function severityTone(value) {
+ if (value === "Critical" || value === "High") return "danger";
+ if (value === "Medium") return "warning";
+ if (value === "Low") return "success";
+ return "neutral";
+}
+
+function severityRank(value) {
+ if (value === "Critical") return 0;
+ if (value === "High") return 1;
+ if (value === "Medium") return 2;
+ if (value === "Low") return 3;
+ return 4;
+}
+
+function dimensionLabel(id, dimensions) {
+ const match = dimensions.find((row) => row.id === id);
+ return match?.label ?? id.replace(/-/g, " ");
+}
+
+function aiAgentPractice() {
+ return report.summary?.aiAgentPractice ?? {};
+}
+
+function practiceRows() {
+ const rows = aiAgentPractice().coverageRows;
+ return Array.isArray(rows) ? rows : [];
+}
+
+function inspectedSurfaces() {
+ const surfaces = aiAgentPractice().inspectedSurfaces;
+ return Array.isArray(surfaces) ? surfaces : [];
+}
+
+function visiblePracticePaths(value) {
+ return list(value).map(textValue).filter((candidate) => candidate
+ && !candidate.includes("SharedClientCache/projects/")
+ && !candidate.startsWith("/")
+ && !/^[A-Za-z]:[\\/]/.test(candidate)
+ && !candidate.split(/[\\/]/).includes(".."));
+}
+
+function practiceDescription(surface) {
+ const descriptions = {
+ Rules: ["Standing project guidance and task-routing instructions.", "项目常驻指引与任务路由说明。"],
+ Skills: ["Reusable agent workflows available to the project.", "项目可用的可复用 Agent 工作流。"],
+ "Custom Agents": ["Specialized agent profiles available for delegated work.", "可用于委派工作的专用 Agent 配置。"],
+ Hooks: ["Lifecycle automation around agent and delivery events.", "围绕 Agent 与交付事件的生命周期自动化。"],
+ MCP: ["External tools and resources exposed through MCP.", "通过 MCP 暴露的外部工具与资源。"],
+ Commands: ["Named command entry points for repeatable agent work.", "可重复 Agent 工作的命令入口。"],
+ Workflows: ["Reusable multi-step project workflows.", "可复用的多步骤项目工作流。"],
+ Plugins: ["Installed packages that contribute agent capabilities.", "提供 Agent 能力的已安装插件。"],
+ "Session Insights": ["Task-session evidence available for report analysis.", "可用于报告分析的任务会话证据。"],
+ Memories: ["Representative project or global Memory note files.", "项目级或全局 Memory 的代表性笔记文件。"],
+ };
+ const copy = descriptions[surface] ?? ["Recorded agent capability sources.", "已记录的 Agent 能力来源。"];
+ return taskLoopCopy(copy[0], copy[1]);
+}
+
+function TaskLoopPracticePaths({ paths }) {
+ if (paths.length === 0) return null;
+ const preview = paths.slice(0, 2);
+ const remaining = paths.slice(2);
+ return (
+
+ {preview.map((path, index) => (
+ {path}
+ ))}
+ {remaining.length ? (
+
+ {taskLoopCopy(`View ${remaining.length} more locations`, `查看其余 ${remaining.length} 个位置`)}
+
+ )}
+ bodyStyle={{ padding: "4px 0 0 16px" }}
+ headerStyle={{ borderBottom: "none", minHeight: 24 }}
+ >
+
+ {remaining.map((path, index) => (
+ {path}
+ ))}
+
+
+ ) : null}
+
+ );
+}
+
+function PracticeSourceCard({ row }) {
+ const paths = visiblePracticePaths(row.paths);
+ const scopes = list(row.scopes);
+ return (
+
+
+
+ {row.surface ?? taskLoopCopy("Surface", "能力面")}
+
+ )}
+ trailing={Number.isInteger(Number(row.count)) ? {row.count} : undefined}
+ />
+
+
+ {practiceDescription(row.surface)}
+ {scopes.length || paths.length ? (
+
+ {scopes.length ? (
+
+ {scopes.map((scope) => {scope})}
+
+ ) : null}
+ {paths.length ? (
+
+ {taskLoopCopy("Sources", "来源")}
+
+
+ ) : null}
+
+ ) : null}
+
+
+
+ );
+}
+
+function OpeningStrengths() {
+ const strengths = openingStrengths();
+ return (
+
+
+ {strengths.map((strength, index) => (
+ {strength}
+ ))}
+
+
+ );
+}
+
+function DimensionSummary({ dimensions }) {
+ return (
+
+ {dimensions.map((row) => {
+ const score = clampScore(row.score);
+ return (
+
+ {taskLoopDimensionLabel(row.id)}}
+ trailing={{score}%}
+ />
+
+
+ {textValue(row.summary) || DIMENSION_SUMMARY_EXAMPLE}
+ {list(row.findingRefs).length ? (
+ Linked findings: {row.findingRefs.join(", ")}
+ ) : null}
+
+
+
+ );
+ })}
+
+ );
+}
+
+function FindingItem({ row, dimensions }) {
+ return (
+
+ {row.title ?? row.id}}
+ trailing={{row.severity ?? "Unrated"}}
+ />
+
+
+
+ {list(row.dimensionRefs).slice(0, 1).map((ref) => (
+ {dimensionLabel(ref, dimensions)}
+ ))}
+
+
+
+ AI Fix
+
+
+
+
+
+ );
+}
+
+function PracticeCoverage() {
+ const rows = practiceRows();
+ const surfaces = inspectedSurfaces();
+
+ return (
+
+
+ AI Agent Practices
+ {surfaces.length ? {surfaces.length} surfaces : null}
+
+ {rows.length ? (
+
+ {rows.map((row, index) => )}
+
+ ) : No AI Agent practice rows recorded.}
+
+ );
+}
+
+function usesChineseReaderCopy() {
+ const locale = textValue(report.summary?.locale);
+ if (locale) return locale.toLowerCase().startsWith("zh");
+ const readerSample = [
+ ...list(report.summary?.strengths),
+ ...list(report.findings).slice(0, 3).flatMap((row) => [row?.title, row?.reason, row?.reader]),
+ ].map(textValue).join(" ");
+ return /[\u3400-\u9fff]/.test(readerSample);
+}
+
+function taskLoopCopy(english, chinese) {
+ return usesChineseReaderCopy() ? chinese : english;
+}
+
+function taskLoopDimensionLabel(id) {
+ if (!id) return taskLoopCopy("not observed", "未观察到");
+ return dimensionLabel(id, list(report.summary?.dimensions));
+}
+
+function learningStateLabel(value) {
+ const labels = {
+ "N/A": ["Needs a comparison", "需要比较"],
+ pending: ["Comparison planned", "已计划比较"],
+ improving: ["Improving", "正在改善"],
+ unchanged: ["No clear change", "没有明显变化"],
+ regressing: ["Worse — stop or revert", "变差——停止或回退"],
+ "outcome-supported": ["A later result supports it", "后续结果支持它"],
+ }[value];
+ return labels ? taskLoopCopy(labels[0], labels[1]) : taskLoopCopy("Not observed", "未观察到");
+}
+
+function taskLoopSummary() {
+ return report.summary?.atAGlance ?? {};
+}
+
+function taskLoopSessionOverview() {
+ const entries = list(report.summary?.semanticFacets?.entries);
+ const usage = entries.find((entry) => entry?.id === "session-insight:session-usage-efficiency");
+ return textValue(usage?.summary);
+}
+
+function taskLoopUsageActivity() {
+ const activity = report.summary?.usageActivity;
+ return activity && list(activity.dates).length ? activity : null;
+}
+
+function taskLoopUsageEfficiency() {
+ const usage = report.summary?.usageEfficiency;
+ if (!usage || typeof usage !== "object" || Array.isArray(usage)) return null;
+ return usage.selection || usage.accounting || usage.longSessions || usage.modelUsage || usage.reviewLead
+ ? usage
+ : null;
+}
+
+function taskLoopUsageCoverageInfo(activity, usage, sessionOverview) {
+ if (textValue(sessionOverview)) return sessionOverview;
+ const analyzed = Number(usage?.selection?.analyzedSessionCount);
+ const eligible = Number(usage?.selection?.eligibleSessionCount);
+ if (Number.isFinite(analyzed) && analyzed >= 0 && Number.isFinite(eligible) && eligible >= 0) {
+ return analyzed === eligible
+ ? taskLoopCopy(`This analysis covered ${formatUsageNumber(analyzed)} sessions.`, `本次分析覆盖 ${formatUsageNumber(analyzed)} 个会话。`)
+ : taskLoopCopy(
+ `This analysis covered ${formatUsageNumber(analyzed)} of ${formatUsageNumber(eligible)} eligible sessions.`,
+ `本次分析覆盖 ${formatUsageNumber(eligible)} 个候选会话中的 ${formatUsageNumber(analyzed)} 个。`,
+ );
+ }
+ const total = Number(activity?.sessions?.total);
+ return Number.isFinite(total) && total >= 0
+ ? taskLoopCopy(`This analysis covered ${formatUsageNumber(total)} sessions.`, `本次分析覆盖 ${formatUsageNumber(total)} 个会话。`)
+ : "";
+}
+
+function usageSeriesTotal(series) {
+ return list(series).reduce((sum, row) => sum + Number(row?.total ?? 0), 0);
+}
+
+function usageActivityMatrix(activity) {
+ const sourceDates = list(activity?.dates);
+ if (!sourceDates.length) return null;
+ const last = new Date(`${sourceDates.at(-1)}T00:00:00.000Z`);
+ const windowStart = new Date(last.getTime() - (364 * 86_400_000));
+ const start = new Date(windowStart);
+ start.setUTCDate(start.getUTCDate() - start.getUTCDay());
+ const weekCount = 53;
+ const columns = Array.from({ length: weekCount }, (_, index) => {
+ const date = new Date(start.getTime() + (index * 7 * 86_400_000));
+ const previous = index > 0 ? new Date(start.getTime() + ((index - 1) * 7 * 86_400_000)) : null;
+ if (!previous || previous.getUTCMonth() === date.getUTCMonth()) return "";
+ return usesChineseReaderCopy()
+ ? `${date.getUTCMonth() + 1}月`
+ : date.toLocaleDateString("en-US", { month: "short", timeZone: "UTC" });
+ });
+ const values = Array.from({ length: 7 }, () => Array(weekCount).fill(null));
+ for (let dayOffset = 0; dayOffset < 365; dayOffset += 1) {
+ const date = new Date(windowStart.getTime() + (dayOffset * 86_400_000));
+ const week = Math.floor((date.getTime() - start.getTime()) / (7 * 86_400_000));
+ const dateKey = date.toISOString().slice(0, 10);
+ values[date.getUTCDay()][week] = {
+ id: dateKey,
+ value: 0,
+ ariaLabel: taskLoopCopy(`${dateKey}: no observed activity`, `${dateKey}:未观察到活动`),
+ };
+ }
+ sourceDates.forEach((date, index) => {
+ const parsed = new Date(`${date}T00:00:00.000Z`);
+ const week = Math.floor((parsed.getTime() - start.getTime()) / (7 * 86_400_000));
+ if (week < 0 || week >= weekCount) return;
+ const active = Number(activity?.sessions?.activeMinutes?.[index] ?? 0);
+ values[parsed.getUTCDay()][week] = {
+ id: date,
+ value: active,
+ ariaLabel: taskLoopCopy(`${date}: ${formatActivityMinutes(active)}`, `${date}:${formatActivityMinutes(active)}`),
+ };
+ });
+ return { columns, values, start: windowStart.toISOString().slice(0, 10), end: sourceDates.at(-1) };
+}
+
+function usageChartWindow(activity) {
+ const dates = list(activity?.dates);
+ const offset = Math.max(0, dates.length - 30);
+ return { offset, categories: dates.slice(offset).map((date) => String(date).slice(5)) };
+}
+
+function visibleUsageSeries(series, offset, limit = 5) {
+ const rows = list(series);
+ const named = rows.filter((row) => row?.name !== "Other");
+ const primary = named.slice(0, limit).map((row) => ({ name: usageSeriesLabel(row.name), data: list(row.daily).slice(offset), total: Number(row.total ?? 0) }));
+ const remainder = [...named.slice(limit), ...rows.filter((row) => row?.name === "Other")];
+ if (remainder.length > 0) {
+ const length = primary[0]?.data.length ?? list(remainder[0]?.daily).slice(offset).length;
+ primary.push({
+ name: "Other",
+ total: remainder.reduce((sum, row) => sum + Number(row?.total ?? 0), 0),
+ data: Array.from({ length }, (_, index) => remainder.reduce((sum, row) => sum + Number(list(row?.daily).slice(offset)[index] ?? 0), 0)),
+ });
+ }
+ return primary;
+}
+
+function formatUsageNumber(value) {
+ return Math.round(Number(value ?? 0)).toLocaleString(usesChineseReaderCopy() ? "zh-CN" : "en-US");
+}
+
+function usageSeriesLabel(value) {
+ if (value === "Unknown model") return taskLoopCopy("Unattributed model", "未归属模型");
+ if (value === "Unknown Skill") return taskLoopCopy("Unattributed Skill", "未归属 Skill");
+ return value;
+}
+
+function formatActivityMinutes(value) {
+ const formatted = Number(value ?? 0).toLocaleString(usesChineseReaderCopy() ? "zh-CN" : "en-US", {
+ maximumFractionDigits: 1,
+ });
+ return `${formatted} ${taskLoopCopy("min", "分钟")}`;
+}
+
+function taskLoopCoverage() {
+ return taskLoopSummary().coverage ?? {};
+}
+
+function confidenceLabel(value) {
+ const normalized = textValue(value).toLowerCase();
+ const labels = {
+ high: ["High confidence", "高可信度"],
+ medium: ["Medium confidence", "中等可信度"],
+ low: ["Low confidence", "低可信度"],
+ }[normalized];
+ return labels ? taskLoopCopy(labels[0], labels[1]) : taskLoopCopy("Confidence not recorded", "未记录可信度");
+}
+
+function confidenceTone(value) {
+ const normalized = textValue(value).toLowerCase();
+ if (normalized === "high") return "success";
+ if (normalized === "medium") return "warning";
+ return "neutral";
+}
+
+function taskLoopStateLabel(value) {
+ const labels = {
+ Wired: ["Wired", "机制已接入"],
+ Present: ["Present", "已发现机制"],
+ Unobserved: ["Unobserved", "未观察到"],
+ observed: ["Observed", "已观察到"],
+ "Not applicable": ["Not applicable", "暂不适用"],
+ "N/A": ["Needs a comparison", "需要比较"],
+ }[value];
+ return labels ? taskLoopCopy(labels[0], labels[1]) : textValue(value) || "—";
+}
+
+function taskLoopSubdimensionLabel(id) {
+ for (const dimension of list(report.summary?.dimensions)) {
+ const match = list(dimension?.subdimensions).find((row) => row?.id === id);
+ if (match) return match.label ?? id;
+ }
+ return id;
+}
+
+function evidenceReferenceLabel(item) {
+ return textValue(item?.label) || textValue(item?.id) || taskLoopCopy("Unnamed evidence", "未命名证据");
+}
+
+function evidenceReferenceMeta(item) {
+ return [
+ textValue(item?.status),
+ textValue(item?.type),
+ Number.isFinite(Number(item?.line)) ? `${taskLoopCopy("line", "行")} ${item.line}` : "",
+ ].filter(Boolean).join(" · ");
+}
+
+function EvidenceReferenceList({ items }) {
+ return (
+
+ {items.map((item, index) => (
+
+
+ {item?.group ? {item.group} : null}
+ {item?.kind ? {item.kind} : null}
+ {evidenceReferenceLabel(item)}
+
+ {evidenceReferenceMeta(item) ? {evidenceReferenceMeta(item)} : null}
+
+ ))}
+
+ );
+}
+
+function severityLabel(value) {
+ const labels = {
+ Critical: ["Critical", "紧急"],
+ High: ["High", "高"],
+ Medium: ["Medium", "中"],
+ Low: ["Low", "低"],
+ }[value];
+ return labels ? taskLoopCopy(labels[0], labels[1]) : value ?? "—";
+}
+
+function practiceSurfaceGlyph(surface) {
+ return ({ Rules: "R", Skills: "S", "Custom Agents": "A", Hooks: "H", MCP: "M" })[surface] ?? textValue(surface).slice(0, 1).toUpperCase() ?? "?";
+}
+
+function PracticeSurfaceIcon({ row }) {
+ return {practiceSurfaceGlyph(row?.surface)};
+}
+
+function TaskLoopReportHeader({ findings }) {
+ const sources = practiceRows().filter((row) => Number(row?.count) > 0);
+ const overview = textValue(report.summary?.overview);
+ return (
+
+ {projectName()}
+ {overview ? {overview} : null}
+
+
+ {taskLoopCopy(`${findings.length} prioritized improvements`, `${findings.length} 项优先优化`)}
+
+ {sources.length ? (
+
+ {taskLoopCopy(`${sources.length} practice source types`, `${sources.length} 类实践来源`)}
+
+ ) : null}
+
+
+ );
+}
+
+function TaskLoopFluency({ dimensions }) {
+ if (dimensions.length === 0) return null;
+ return (
+
+
+ {taskLoopCopy("Agent Work Loop", "Agent 工作流")}
+ {dimensions.length} {taskLoopCopy("dimensions", "个维度")}
+
+
+ dimensionFluencyTooltip(dimensions[index])}
+ height={180}
+ highThreshold={70}
+ mediumThreshold={40}
+ showStageLabels
+ />
+
+
+ );
+}
+
+function practiceCount(row) {
+ const value = Number(row?.count);
+ return Number.isInteger(value) ? value : "—";
+}
+
+function practiceScopeCell(row) {
+ const scopes = list(row?.scopes).map(textValue).filter(Boolean);
+ if (!scopes.length) return —;
+ return (
+
+ {scopes.map((scope) => {scope})}
+
+ );
+}
+
+function practiceSourceCell(row) {
+ const [firstPath] = visiblePracticePaths(row?.paths);
+ return firstPath
+ ? {firstPath}
+ : {taskLoopCopy("No source location recorded", "未记录来源位置")};
+}
+
+function practiceSourceDetail(row) {
+ const remaining = visiblePracticePaths(row?.paths).slice(1);
+ if (!remaining.length) return null;
+ const pathListStyle = remaining.length > 8
+ ? { maxHeight: 220, overflowY: "auto", paddingRight: 4 }
+ : { paddingRight: 4 };
+ return (
+
+ {taskLoopCopy(`View ${remaining.length} more locations`, `查看其余 ${remaining.length} 个位置`)}
+
+ )}
+ bodyStyle={{ padding: "6px 0 2px 16px" }}
+ headerStyle={{ borderBottom: "none", minHeight: 24 }}
+ >
+
+ {remaining.map((path, index) => (
+ {path}
+ ))}
+
+
+ );
+}
+
+function taskLoopPracticeColumns() {
+ return [
+ {
+ key: "surface",
+ title: taskLoopCopy("Asset", "资产"),
+ minWidth: "300px",
+ render: (row) => (
+
+
+
+ {row.surface ?? taskLoopCopy("Surface", "能力面")}
+
+ {practiceDescription(row.surface)}
+
+ ),
+ },
+ {
+ key: "coverage",
+ title: taskLoopCopy("Coverage", "覆盖范围"),
+ width: "170px",
+ minWidth: "150px",
+ render: (row) => (
+
+
+ {taskLoopCopy(`${practiceCount(row)} sources`, `${practiceCount(row)} 个来源`)}
+
+ {practiceScopeCell(row)}
+
+ ),
+ },
+ {
+ key: "source",
+ title: taskLoopCopy("Representative source", "代表来源"),
+ minWidth: "260px",
+ render: practiceSourceCell,
+ },
+ ];
+}
+
+function TaskLoopPracticeTable({ rows = practiceRows() }) {
+ if (rows.length === 0) {
+ return {taskLoopCopy("No Agent assets recorded.", "未记录 Agent 工程资产。")};
+ }
+ return (
+
row.surface ?? "surface"}
+ density="compact"
+ renderDetail={practiceSourceDetail}
+ emptyText={taskLoopCopy("No Agent asset coverage recorded", "未记录 Agent 工程资产覆盖")}
+ />
+ );
+}
+
+function TaskLoopActivityHeatmap({ activity }) {
+ const matrix = usageActivityMatrix(activity);
+ if (!matrix) return {taskLoopCopy("No dated session activity was observed.", "没有观察到带日期的会话活动。")};
+ return (
+
+
+ {taskLoopCopy("Daily activity (active minutes)", "每日活动(活跃分钟)")}
+ {matrix.start} — {matrix.end}
+
+ formatActivityMinutes(value)}
+ cellSize={16}
+ columnWidth={16}
+ rowLabelWidth={34}
+ responsive
+ minCellSize={8}
+ minGap={2}
+ initialScrollPosition="end"
+ colorTemplate={{
+ none: { background: "rgba(127, 127, 127, 0.1)", border: "transparent" },
+ low: { background: "rgba(64, 166, 103, 0.22)", border: "transparent" },
+ medium: { background: "rgba(54, 158, 94, 0.42)", border: "transparent" },
+ high: { background: "rgba(38, 139, 78, 0.66)", border: "transparent" },
+ critical: { background: "rgba(24, 115, 63, 0.9)", border: "transparent" },
+ }}
+ maxHeight={190}
+ labels={{ ariaLabel: taskLoopCopy("Daily session activity", "每日会话活动") }}
+ />
+
+ );
+}
+
+function UsageStatRow({ label, value }) {
+ if (value === undefined || value === null || value === "") return null;
+ return (
+
+ {label}
+ {value}
+
+ );
+}
+
+function UsageRankList({ series, limit = 5 }) {
+ const rows = list(series).slice(0, limit);
+ if (!rows.length) return {taskLoopCopy("No usage observed.", "未观察到用量。")};
+ return (
+
+ {rows.map((row, index) => (
+
+
+ {index + 1}
+ {usageSeriesLabel(row.name)}
+
+ {formatUsageNumber(row.total)}
+
+ ))}
+
+ );
+}
+
+function taskLoopModelUsageColumns() {
+ return [
+ {
+ key: "model",
+ title: taskLoopCopy("Model", "模型"),
+ minWidth: "180px",
+ render: (row) => {usageSeriesLabel(row.model)},
+ },
+ {
+ key: "responseCount",
+ title: taskLoopCopy("Responses", "响应数"),
+ width: "110px",
+ align: "right",
+ render: (row) => {formatUsageNumber(row.responseCount)},
+ },
+ {
+ key: "usageFieldObservedCount",
+ title: taskLoopCopy("Usage fields observed", "观察到用量字段"),
+ minWidth: "160px",
+ align: "right",
+ render: (row) => {formatUsageNumber(row.usageFieldObservedCount)},
+ },
+ {
+ key: "nonZeroUsageCount",
+ title: taskLoopCopy("Non-zero usage", "非零用量记录"),
+ minWidth: "140px",
+ align: "right",
+ render: (row) => {formatUsageNumber(row.nonZeroUsageCount)},
+ },
+ ];
+}
+
+function TaskLoopModelUsageTable({ rows }) {
+ if (!rows.length) return null;
+ return (
+
+
+ {taskLoopCopy("Model response accounting", "模型响应明细")}
+ {rows.length} {taskLoopCopy("models", "个模型")}
+
+
+ {taskLoopCopy(
+ "These are response counts, not model-active session counts or a quality comparison.",
+ "这里统计的是响应次数,不是模型活跃会话数,也不代表模型质量对比。",
+ )}
+
+ row.model}
+ density="compact"
+ />
+
+ );
+}
+
+function TaskLoopLongSessionReview({ usage }) {
+ const lead = usage?.reviewLead;
+ const samples = list(usage?.longSessions?.samples);
+ if (!lead || !samples.length) return null;
+ const estimate = usage.longSessions?.estimate;
+ const coverage = lead.sampleCoverage;
+ const pendingCount = coverage?.shown ?? samples.length;
+ const analyzedCount = usage.selection?.analyzedSessionCount ?? 0;
+ const longestActiveMinutes = usage.longSessions?.longestActiveMinutes ?? Math.max(...samples.map((sample) => Number(sample.activeMinutes ?? 0)));
+ return (
+
+
+
+
+ {taskLoopCopy(`${pendingCount} long sessions need review`, `${pendingCount} 个长会话待复核`)}
+ {pendingCount} {taskLoopCopy("pending", "待复核")}
+
+
+ {taskLoopCopy(
+ `${pendingCount} of ${formatUsageNumber(analyzedCount)} analyzed sessions crossed the ${estimate?.activeThresholdMinutes ?? 45}-minute estimate threshold; the longest estimate is ${formatActivityMinutes(longestActiveMinutes)}. Treat them as investigation leads until reviewed.`,
+ `${formatUsageNumber(analyzedCount)} 个已分析会话中有 ${pendingCount} 个超过 ${estimate?.activeThresholdMinutes ?? 45} 分钟估算阈值,最长估算为 ${formatActivityMinutes(longestActiveMinutes)}。在人工复核前,只将其视为调查线索。`,
+ )}
+
+
+
+ {taskLoopCopy(`Review ${pendingCount} sessions`, `复核 ${pendingCount} 个会话`)}
+
+
+
+ {samples.map((sample, index) => {
+ const failureCount = Number(sample.failureCount ?? 0);
+ const roleLabel = sample.role === "user-thread-candidate"
+ ? taskLoopCopy("Main-thread candidate", "主线程候选")
+ : sample.role === "child-agent-candidate"
+ ? taskLoopCopy("Child-Agent candidate", "子 Agent 候选")
+ : sample.role;
+ return (
+
+
+
+ {sample.alias}
+
+ {sample.userInputSummary}
+ {taskLoopCopy("Role", "角色")}: {roleLabel}
+
+
+
+ {taskLoopCopy("Estimated active time", "估算活跃时长")}
+ {formatActivityMinutes(sample.activeMinutes)}
+
+
+ {formatUsageNumber(failureCount)} {taskLoopCopy("failures", "失败事件")}
+
+
+ {index < samples.length - 1 ? : null}
+
+ );
+ })}
+
+ {estimate ? (
+
+ {taskLoopCopy(
+ `Estimate boundary: event gaps are capped at ${estimate.gapCapMinutes} minutes and gaps over ${estimate.idleGapMinutes} minutes are treated as idle.`,
+ `估算边界:事件间隔最多计 ${estimate.gapCapMinutes} 分钟,超过 ${estimate.idleGapMinutes} 分钟按空闲处理。`,
+ )}
+
+ ) : null}
+
+ );
+}
+
+function TaskLoopProjectUsage({ activity, usage }) {
+ if (!activity && !usage) return null;
+ const activeMinutes = list(activity?.sessions?.activeMinutes).reduce((sum, value) => sum + Number(value ?? 0), 0);
+ const census = usage?.selection;
+ const longSessions = usage?.longSessions;
+ const skillUses = usageSeriesTotal(activity?.skills);
+ const analyzedSessions = census
+ ? `${formatUsageNumber(census.analyzedSessionCount)} / ${formatUsageNumber(census.eligibleSessionCount)}`
+ : activity ? formatUsageNumber(activity.sessions?.total) : null;
+ return (
+
+ {activity ? : null}
+ {activity ? : null}
+
+
+ {taskLoopCopy("Activity insights", "使用概览")}
+
+ {activity ? : null}
+ {activity ? : null}
+ {longSessions ? : null}
+
+
+ {taskLoopCopy("Most used Skills", "最常使用的 Skills")}
+
+
+
+
+ );
+}
+
+function TaskLoopUsageMethodology({ usage }) {
+ if (!usage) return null;
+ const census = usage.selection;
+ const accounting = usage.accounting;
+ const roles = usage.roles;
+ const outcomeReview = usage.outcomeReview;
+ const taskSelection = taskLoopCoverage().selection ?? {};
+ const modelUsage = list(usage.modelUsage);
+ const hasCoverage = census || Object.keys(taskSelection).length || roles || accounting;
+ if (!hasCoverage && !modelUsage.length && !usage.reviewLead) return null;
+
+ return (
+
+ {census || Object.keys(taskSelection).length ? (
+
+ {taskLoopMeasurementBoundaryText(taskSelection, census)}
+
+ ) : null}
+ {roles || accounting ? (
+
+ {roles ? (
+
+ {taskLoopCopy("Session composition", "会话构成")}
+
+
+
+ ) : null}
+ {accounting ? (
+
+ {taskLoopCopy("Measurement coverage", "计量覆盖")}
+
+
+
+
+
+
+ ) : null}
+
+ ) : null}
+ {modelUsage.length ? : null}
+
+ {accounting?.mode === "effort-proxy"
+ ? taskLoopCopy("Active time and model-session counts are effort proxies; exact token or credit savings are unavailable.", "活跃时间和模型会话数仅代表投入;目前无法精确计算 token 或 credit 节省。")
+ : taskLoopCopy("Usage totals describe observed activity, not counterfactual savings.", "用量只描述已观察活动,不代表反事实节省。")}
+ {outcomeReview && !outcomeReview.comparableModelOutcomeEvidence
+ ? taskLoopCopy(" Model outcomes need a controlled A/B before comparison.", " 模型效果需要通过受控 A/B 后才能比较。")
+ : ""}
+
+
+ );
+}
+
+function usageTrendLeader(series) {
+ return series.reduce((leader, row) => Number(row.total ?? 0) > Number(leader?.total ?? -1) ? row : leader, null);
+}
+
+function usageTrendRange(categories) {
+ if (!categories.length) return taskLoopCopy("Latest observations", "最近观测");
+ if (categories.length === 1) return categories[0];
+ return `${categories[0]} – ${categories[categories.length - 1]}`;
+}
+
+function TaskLoopUsageTrend({ title, totalLabel, leaderDescription, series, categories }) {
+ if (!series.length) return null;
+ const total = series.reduce((sum, row) => sum + Number(row.total ?? 0), 0);
+ const leader = usageTrendLeader(series);
+ const range = usageTrendRange(categories);
+ return (
+
+
+
+
+ {title}
+
+ {taskLoopCopy(
+ `${range} · ${formatUsageNumber(total)} ${totalLabel}`,
+ `${range} · 共 ${formatUsageNumber(total)} ${totalLabel}`,
+ )}
+
+
+
+ {leader ? (
+
+ {usageSeriesLabel(leader.name)} · {formatUsageNumber(leader.total)} {totalLabel}
+ {leaderDescription}
+
+ ) : null}
+
+
+
+ );
+}
+
+function TaskLoopUsageTrends({ activity }) {
+ if (!activity) return null;
+ const chartWindow = usageChartWindow(activity);
+ const categories = chartWindow.categories;
+ const modelSeries = visibleUsageSeries(activity.models, chartWindow.offset);
+ const skillSeries = visibleUsageSeries(activity.skills, chartWindow.offset);
+ if (!modelSeries.length && !skillSeries.length) return null;
+ return (
+
+ {taskLoopCopy("Usage trends", "用量趋势")}
+
+
+
+
+
+ );
+}
+
+function taskLoopSessionInsightTitle(id) {
+ const labels = {
+ "session-insight:source-coverage": ["Source coverage", "数据覆盖"],
+ "session-insight:validation-behavior": ["Validation behavior", "验证行为"],
+ "session-insight:post-edit-validation": ["Post-edit validation", "改动后验证"],
+ "session-insight:execution-friction": ["Execution friction", "执行摩擦"],
+ "session-insight:tool-mix": ["Tool mix", "工具使用"],
+ "session-insight:observed-hooks": ["Observed hooks", "Hook 执行"],
+ "session-insight:planning-workflow": ["Planning workflow", "规划工作流"],
+ "session-insight:session-complexity": ["Session complexity", "会话复杂度"],
+ "session-insight:session-usage-efficiency": ["Session effort", "会话投入"],
+ }[id];
+ return labels ? taskLoopCopy(labels[0], labels[1]) : id;
+}
+
+function taskLoopSessionInsightConfidence(row) {
+ return list(row?.labels).map(textValue).find((value) => ["High", "Medium", "Low"].includes(value)) ?? "";
+}
+
+function taskLoopSessionInsightColumns() {
+ return [
+ {
+ key: "id",
+ title: taskLoopCopy("Observation", "观察主题"),
+ minWidth: "150px",
+ render: (row) => {taskLoopSessionInsightTitle(row.id)},
+ },
+ {
+ key: "summary",
+ title: taskLoopCopy("What was observed", "观察说明"),
+ minWidth: "420px",
+ render: (row) => {row.summary ?? "—"},
+ },
+ {
+ key: "confidence",
+ title: taskLoopCopy("Confidence", "可信度"),
+ minWidth: "120px",
+ render: (row) => {
+ const confidence = taskLoopSessionInsightConfidence(row);
+ return confidence ? {confidenceLabel(confidence)} : —;
+ },
+ },
+ {
+ key: "evidenceRefs",
+ title: taskLoopCopy("Evidence", "证据"),
+ width: "80px",
+ align: "right",
+ render: (row) => {list(row.evidenceRefs).length},
+ },
+ ];
+}
+
+function taskLoopSessionInsightDetail(row) {
+ const evidenceRefs = list(row?.evidenceRefs);
+ return (
+ {taskLoopCopy("View raw observation metadata", "查看原始观察元数据")}}
+ bodyStyle={{ padding: "6px 0 2px 16px" }}
+ headerStyle={{ borderBottom: "none", minHeight: 24 }}
+ >
+
+
+ {taskLoopCopy("Insight ID", "洞察 ID")}: {row.id} · {taskLoopCopy("Status", "状态")}: {row.status ?? "—"} · {taskLoopCopy("Kind", "类型")}: {row.kind ?? "—"} · {taskLoopCopy("Model", "模型版本")}: {row.modelVersion ?? "—"}
+
+ {list(row.labels).length ? (
+ {row.labels.map((label) => {label})}
+ ) : null}
+ {evidenceRefs.length ? : {taskLoopCopy("No raw evidence references recorded.", "未记录原始证据引用。")}}
+
+
+ );
+}
+
+function taskLoopRepresentativeSessionInsights(entries) {
+ const preferredIds = [
+ "session-insight:post-edit-validation",
+ "session-insight:execution-friction",
+ "session-insight:tool-mix",
+ ];
+ const preferred = preferredIds
+ .map((id) => entries.find((entry) => entry?.id === id))
+ .filter((entry) => entry && list(entry.evidenceRefs).length > 0);
+ const remaining = entries
+ .filter((entry) => list(entry?.evidenceRefs).length > 0 && !preferred.includes(entry))
+ .sort((left, right) => list(right.evidenceRefs).length - list(left.evidenceRefs).length);
+ return [...preferred, ...remaining].slice(0, 3);
+}
+
+function TaskLoopSessionInsightsDialog({ entries }) {
+ return (
+