diff --git a/.github/READMEmd b/.github/READMEmd
index 47ff11b..10f0b99 100644
--- a/.github/READMEmd
+++ b/.github/READMEmd
@@ -87,20 +87,23 @@ dotnet new install LanMountainDesktop.PluginTemplate
dotnet new lmd-plugin -n MyPlugin
```
-- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 4.0.0)
-- **共享契约**: `LanMountainDesktop.Shared.Contracts`
-- **迁移指南**: [PLUGIN_SDK_V4_MIGRATION.md](docs/PLUGIN_SDK_V4_MIGRATION.md)
+- **Plugin SDK**: `LanMountainDesktop.PluginSdk` (API 6.0.0)
+- **共享契约**: `LanMountainDesktop.Core`
+- **迁移指南**: [PLUGIN_SDK_V5_MIGRATION.md](docs/PLUGIN_SDK_V5_MIGRATION.md)
## 项目结构
```
LanMountainDesktop/
-├── LanMountainDesktop/ # 桌面宿主应用
-├── LanMountainDesktop.PluginSdk/ # 官方插件 SDK
-├── LanMountainDesktop.Shared.Contracts/ # 宿主与插件共享契约
-├── LanMountainDesktop.Appearance/ # 主题与外观基础设施
-├── LanMountainDesktop.Settings.Core/# 设置持久化基础设施
-└── LanMountainDesktop.Tests/ # 测试项目
+├── desktop/LanMountainDesktop/ # 桌面宿主应用
+├── desktop/LanMountainDesktop.Launcher/ # 启动器
+├── airapp/LanMountainDesktop.PluginSdk/ # 官方插件 SDK(含隔离、设置、外观)
+├── core/LanMountainDesktop.Core/ # 共享契约、IPC、插件打包
+├── platform/LanMountainDesktop.Platform/ # 平台差异层
+├── airapp/LanMountainDesktop.AirAppRuntime/ # Air APP 运行时
+├── airapp/LanMountainDesktop.AirAppHost/ # Air APP 渲染进程
+├── install/LanDesktopPLONDS.installer/ # 在线安装器
+└── tests/LanMountainDesktop.Tests/ # 测试项目
```
## 生态边界
diff --git a/.github/VERSION_SYNC_INFO.md b/.github/VERSION_SYNC_INFO.md
index 0fe8885..b105250 100644
--- a/.github/VERSION_SYNC_INFO.md
+++ b/.github/VERSION_SYNC_INFO.md
@@ -13,11 +13,11 @@
仓库内的静态版本现在故意保留为开发占位值:
- `Directory.Build.props`
-- `LanMountainDesktop/LanMountainDesktop.csproj`
-- `LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj`
-- `LanMountainDesktop.Shared.Contracts/LanMountainDesktop.Shared.Contracts.csproj`
-- `LanMountainDesktop/app.manifest`
-- `LanMountainDesktop.Launcher/app.manifest`
+- `desktop/LanMountainDesktop/LanMountainDesktop.csproj`
+- `desktop/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj`
+- `core/LanMountainDesktop.Core/LanMountainDesktop.Core.csproj`
+- `desktop/LanMountainDesktop/app.manifest`
+- `desktop/LanMountainDesktop.Launcher/app.manifest`
这些值只是提醒“当前不是正式注入构建”,不能代表发布版本。
@@ -36,7 +36,7 @@ Release 工作流会先从 tag 提取版本:
- 主程序 `.csproj` 的 `Version`
- Launcher `.csproj` 的 `Version`
-- Shared.Contracts `.csproj` 的 `Version`
+- Core `.csproj` 的 `Version`
- `Directory.Build.props`
- 主程序 `app.manifest`
- Launcher `app.manifest`
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 148ed86..90edbee 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -58,7 +58,7 @@ jobs:
with:
name: build-windows-${{ matrix.configuration }}
path: |
- LanMountainDesktop/bin/${{ matrix.configuration }}/
+ desktop/LanMountainDesktop/bin/${{ matrix.configuration }}/
retention-days: 7
build-linux:
@@ -117,7 +117,7 @@ jobs:
with:
name: build-linux
path: |
- LanMountainDesktop/bin/Release/
+ desktop/LanMountainDesktop/bin/Release/
retention-days: 7
build-macos:
@@ -162,7 +162,7 @@ jobs:
with:
name: build-macos
path: |
- LanMountainDesktop/bin/Release/
+ desktop/LanMountainDesktop/bin/Release/
retention-days: 7
build-android:
diff --git a/.github/workflows/installer-build.yml b/.github/workflows/installer-build.yml
index 3c37be3..1623cfb 100644
--- a/.github/workflows/installer-build.yml
+++ b/.github/workflows/installer-build.yml
@@ -7,19 +7,19 @@ on:
paths:
- '.github/workflows/installer-build.yml'
- 'Directory.Packages.props'
- - 'LanDesktopPLONDS.installer/**'
- - 'LanMountainDesktop.Shared.Contracts/**'
+ - 'install/LanDesktopPLONDS.installer/**'
+ - 'core/LanMountainDesktop.Core/**'
pull_request:
paths:
- '.github/workflows/installer-build.yml'
- 'Directory.Packages.props'
- - 'LanDesktopPLONDS.installer/**'
- - 'LanMountainDesktop.Shared.Contracts/**'
+ - 'install/LanDesktopPLONDS.installer/**'
+ - 'core/LanMountainDesktop.Core/**'
workflow_dispatch:
env:
DOTNET_VERSION: '10.0.x'
- INSTALLER_PROJECT: LanDesktopPLONDS.installer/LanDesktopPLONDS.installer.csproj
+ INSTALLER_PROJECT: install/LanDesktopPLONDS.installer/LanDesktopPLONDS.installer.csproj
INSTALLER_RUNTIME: win-x64
INSTALLER_ARTIFACT_DIR: artifacts/installer-online/win-x64
DOTNET_gcServer: 1
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index fa3f05f..550a19f 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -143,7 +143,7 @@ jobs:
$arch = "${{ matrix.arch }}"
$launcherPublishDir = "publish/launcher-win-$arch"
- dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
+ dotnet publish desktop/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj `
-c Release `
-o ./$launcherPublishDir `
--self-contained `
@@ -192,7 +192,7 @@ jobs:
$arch = "${{ matrix.arch }}"
$publishDir = "publish/airapp-runtime-win-$arch"
- dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj `
+ dotnet publish airapp/LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj `
-c Release `
-o ./$publishDir `
--self-contained:false `
@@ -215,7 +215,7 @@ jobs:
$arch = "${{ matrix.arch }}"
$publishDir = "publish/windows-$arch"
- dotnet publish LanMountainDesktop.AirAppHost/LanMountainDesktop.AirAppHost.csproj `
+ dotnet publish airapp/LanMountainDesktop.AirAppHost/LanMountainDesktop.AirAppHost.csproj `
-c Release `
-o ./$publishDir `
--self-contained:false `
@@ -467,7 +467,7 @@ jobs:
- name: Publish Launcher (AOT)
run: |
- dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
+ dotnet publish desktop/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
-c Release \
-o ./publish/launcher-linux-x64 \
--self-contained \
@@ -504,7 +504,7 @@ jobs:
- name: Publish AirAppRuntime
run: |
- dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
+ dotnet publish airapp/LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
-c Release \
-o ./publish/airapp-runtime-linux-x64 \
--self-contained false \
@@ -950,7 +950,7 @@ jobs:
- name: Publish Launcher (AOT)
run: |
- dotnet publish LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
+ dotnet publish desktop/LanMountainDesktop.Launcher/LanMountainDesktop.Launcher.csproj \
-c Release \
-o ./publish/launcher-macos-${{ matrix.arch }} \
--self-contained \
@@ -987,7 +987,7 @@ jobs:
- name: Publish AirAppRuntime
run: |
- dotnet publish LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
+ dotnet publish airapp/LanMountainDesktop.AirAppRuntime/LanMountainDesktop.AirAppRuntime.csproj \
-c Release \
-o ./publish/airapp-runtime-macos-${{ matrix.arch }} \
--self-contained false \
diff --git a/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/canvas.json b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/canvas.json
new file mode 100644
index 0000000..fd98540
--- /dev/null
+++ b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/canvas.json
@@ -0,0 +1,194 @@
+{
+ "schemaVersion": 1,
+ "summary": {
+ "evidenceMode": "session-limited",
+ "evidenceBoundary": {
+ "manifest": {
+ "schemaVersion": 2,
+ "sourceFingerprint": "75cebb1a53875ab1",
+ "adapterVersion": "qoder-task-loop-source-v2",
+ "platform": "qoder",
+ "selection": {
+ "strategy": "all-eligible",
+ "eligibleCount": 1,
+ "analyzedCount": 1,
+ "confidence": "High"
+ }
+ },
+ "episodeCoverage": {
+ "episodeCount": 0,
+ "editedEpisodeCount": 0,
+ "closedEpisodeCount": 0,
+ "recoveredEpisodeCount": 0
+ },
+ "deliveryEvidenceLevels": [],
+ "sourceGaps": []
+ },
+ "semanticFacets": {
+ "schemaVersion": 1,
+ "status": "supplementary",
+ "entries": [
+ {
+ "id": "session-insight:source-coverage",
+ "kind": "redacted-summary",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "source-coverage",
+ "High"
+ ],
+ "summary": "Analyzed 1 of 1 sessions; 1/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"
+ },
+ {
+ "id": "session-insight:session-usage-efficiency",
+ "kind": "redacted-summary",
+ "episodeRef": null,
+ "status": "candidate",
+ "labels": [
+ "session-usage-efficiency",
+ "High"
+ ],
+ "summary": "本次分析覆盖 1 个会话,未发现达到阈值的活跃长会话。 这些活动证据只描述投入;当前 token 或 credit 证据不足,不能据此推断模型偏好、质量或节省。",
+ "evidenceRefs": [],
+ "modelVersion": "session-insights-v1"
+ }
+ ]
+ },
+ "usageActivity": {
+ "schemaVersion": 2,
+ "dateBasis": "UTC",
+ "measurementBasis": "session-starts-active-estimate-model-active-session-days-skill-invocations-and-loads",
+ "truncated": false,
+ "dates": [
+ "2026-08-04"
+ ],
+ "sessions": {
+ "total": 1,
+ "starts": [
+ 1
+ ],
+ "activeMinutes": [
+ 0.9
+ ]
+ },
+ "models": [],
+ "skills": []
+ },
+ "usageEfficiency": {
+ "schemaVersion": 2,
+ "selection": {
+ "strategy": "all-eligible",
+ "eligibleSessionCount": 1,
+ "analyzedSessionCount": 1,
+ "complete": true
+ },
+ "roles": {
+ "userThreadCandidateCount": 0,
+ "childAgentCandidateCount": 1
+ },
+ "longSessions": {
+ "activeCount": 0,
+ "wallOnlyCount": 0,
+ "longestActiveMinutes": 0,
+ "activeRatio": 0,
+ "estimate": {
+ "method": "capped-event-gap",
+ "activeThresholdMinutes": 45,
+ "gapCapMinutes": 5,
+ "idleGapMinutes": 30
+ },
+ "samples": []
+ },
+ "accounting": {
+ "mode": "effort-proxy",
+ "responseCount": 0,
+ "modelAttributedResponseCount": 0,
+ "unattributedResponseCount": 0,
+ "usageFieldObservedCount": 0,
+ "nonZeroUsageCount": 0,
+ "exactCreditsAvailable": false,
+ "pricingVersion": null
+ },
+ "modelUsage": [],
+ "outcomeReview": {
+ "status": "not-applicable",
+ "reviewedCandidateCount": 0,
+ "reviewedActiveLongCount": 0,
+ "comparableModelOutcomeEvidence": false,
+ "recommendation": "controlled-a-b-required"
+ }
+ },
+ "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": "zero-test-files"
+ },
+ {
+ "id": "candidate-skill-unconfigured"
+ }
+ ]
+}
diff --git a/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/findings.json b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/findings.json
new file mode 100644
index 0000000..1c2a016
--- /dev/null
+++ b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/findings.json
@@ -0,0 +1,132 @@
+{
+ "summary": {
+ "projectName": "阑山桌面LanMountainDesktop",
+ "locale": "zh-CN",
+ "modelId": "agent-work-loop-v4",
+ "reportContractVersion": 25,
+ "overview": "项目拥有完善的文档指导和 CI/CD 工作流,但编码代理缺少可复用的 Skill 配置且项目无专用测试文件,限制了变更验证和持续学习能力。",
+ "aiAgentPractice": {
+ "inspectedSurfaces": [
+ "Rules",
+ "Skills",
+ "Workflows",
+ "Custom Agents"
+ ],
+ "coverageRows": [
+ {
+ "surface": "Rules",
+ "scopes": [
+ "Project"
+ ],
+ "count": 1,
+ "paths": [
+ "AGENTS.md"
+ ]
+ },
+ {
+ "surface": "Skills",
+ "scopes": [
+ "Project"
+ ],
+ "count": 0,
+ "paths": []
+ },
+ {
+ "surface": "Workflows",
+ "scopes": [
+ "Project"
+ ],
+ "count": 7,
+ "paths": [
+ ".github/workflows/build.yml",
+ ".github/workflows/code-quality.yml",
+ ".github/workflows/installer-build.yml",
+ ".github/workflows/issue-management.yml",
+ ".github/workflows/plonds-comparator.yml",
+ ".github/workflows/plonds-uploader.yml",
+ ".github/workflows/release.yml"
+ ]
+ },
+ {
+ "surface": "Custom Agents",
+ "scopes": [
+ "Project"
+ ],
+ "count": 0
+ }
+ ]
+ },
+ "assignmentSummaries": [],
+ "dimensions": [
+ {
+ "id": "task-understanding",
+ "label": "任务理解",
+ "score": 70,
+ "summary": "AGENTS.md 提供了全面的架构指引和规范,104 个规格文档使任务边界清晰,但工作流指引未与 CI 直接关联。",
+ "findingRefs": []
+ },
+ {
+ "id": "controlled-execution",
+ "label": "可控执行",
+ "score": 45,
+ "summary": "启动和构建命令在 AGENTS.md 中有文档,但无 Skill、Command 或 Agent 配置,操作能力受限。",
+ "findingRefs": []
+ },
+ {
+ "id": "change-validation",
+ "label": "改动验证",
+ "score": 45,
+ "summary": "CI 工作流提供了验证管道,但项目缺少专用测试文件(990 源文件 vs 0 测试文件),限制了变更验证覆盖。",
+ "findingRefs": [
+ "zero-test-files"
+ ]
+ },
+ {
+ "id": "reliable-delivery",
+ "label": "可靠交付",
+ "score": 48,
+ "summary": "7 个 CI/CD 工作流覆盖了构建、发布和代码质量,但分析窗口内无实际交付证据。",
+ "findingRefs": []
+ },
+ {
+ "id": "learning-capture",
+ "label": "经验沉淀",
+ "score": 35,
+ "summary": "无 Skill 配置、无学习事件、无重复模式检测,持续改进机制尚未建立。",
+ "findingRefs": [
+ "candidate-skill-unconfigured"
+ ]
+ }
+ ]
+ },
+ "findings": [
+ {
+ "id": "zero-test-files",
+ "title": "项目无专用测试文件,变更验证依赖有限",
+ "severity": "Medium",
+ "reason": "项目有 990 个源文件但 0 个专用测试文件。尽管 CI 管道存在,变更验证覆盖受到根本性限制,无法在交付前验证受影响的行为。",
+ "expectedOutput": [
+ "为核心模块添加专用测试文件,确保 dotnet test 可以执行并验证关键行为。"
+ ],
+ "expectedArtifact": "Test files",
+ "aiFixPrompt": "/better-harness fix this issue\n\n为项目的核心模块添加最小但有效的测试覆盖,优先覆盖高频变更的核心路径。\n\n## 验证\n\n- 运行 dotnet test 确认测试通过\n- 确认测试覆盖了至少一个核心业务路径",
+ "dimensionRefs": [
+ "change-validation"
+ ]
+ },
+ {
+ "id": "candidate-skill-unconfigured",
+ "title": "候选 Skill 文件缺少触发和验证配置",
+ "severity": "Low",
+ "reason": ".trae/skills/refactoring-insight/SKILL.md 存在但触发条件为 no、验证为 no、路由为 no。即使激活也无法被自动路由或在正确时机触发。",
+ "expectedOutput": [
+ "补全候选 Skill 的触发条件和验证配置,使其可被正确路由和激活。"
+ ],
+ "expectedArtifact": "Skill configuration",
+ "aiFixPrompt": "/better-harness fix this issue\n\n检查 .trae/skills/refactoring-insight/SKILL.md 的配置,补充触发条件和输出验证。\n\n## 验证\n\n- 确认 Skill 的 trigger 字段已正确配置\n- 确认 Skill 的输出有明确的验证方法",
+ "dimensionRefs": [
+ "learning-capture"
+ ]
+ }
+ ]
+}
diff --git a/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.status.json b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.status.json
new file mode 100644
index 0000000..1e5bf06
--- /dev/null
+++ b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.status.json
@@ -0,0 +1,3 @@
+{
+ "status": "ready"
+}
diff --git a/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.tsx b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.tsx
new file mode 100644
index 0000000..2084dfd
--- /dev/null
+++ b/.qoder/better-harness/2026-08-05/001011-lanmountaindesktop/report.canvas.tsx
@@ -0,0 +1,1788 @@
+import {
+ AreaChart,
+ Button,
+ Callout,
+ Card,
+ CardBody,
+ CardHeader,
+ CollapsibleSection,
+ Dialog,
+ Divider,
+ Fluency,
+ Grid,
+ H1,
+ H2,
+ 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, padding: 20 };
+const taskLoopReaderCopyStyle = { maxWidth: 940 };
+
+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 ?? "Better 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 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 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}
+
+
+ {taskLoopCopy("Anonymized review candidate", "匿名复核候选")}
+
+ {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 (
+