principles-disciple 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +66 -0
  3. package/esbuild.config.js +1 -1
  4. package/openclaw.plugin.json +4 -4
  5. package/package.json +2 -3
  6. package/run-nocturnal.mjs +30 -0
  7. package/scripts/db-migrate.mjs +170 -0
  8. package/scripts/sync-plugin.mjs +250 -6
  9. package/src/commands/archive-impl.ts +136 -0
  10. package/src/commands/capabilities.ts +4 -2
  11. package/src/commands/context.ts +5 -1
  12. package/src/commands/disable-impl.ts +151 -0
  13. package/src/commands/evolution-status.ts +64 -19
  14. package/src/commands/export.ts +8 -6
  15. package/src/commands/focus.ts +8 -20
  16. package/src/commands/nocturnal-review.ts +5 -7
  17. package/src/commands/nocturnal-rollout.ts +1 -12
  18. package/src/commands/nocturnal-train.ts +21 -47
  19. package/src/commands/pain.ts +10 -5
  20. package/src/commands/principle-rollback.ts +4 -2
  21. package/src/commands/promote-impl.ts +274 -0
  22. package/src/commands/rollback-impl.ts +234 -0
  23. package/src/commands/rollback.ts +6 -3
  24. package/src/commands/samples.ts +2 -0
  25. package/src/commands/thinking-os.ts +3 -4
  26. package/src/commands/workflow-debug.ts +2 -1
  27. package/src/config/errors.ts +1 -0
  28. package/src/core/AGENTS.md +34 -0
  29. package/src/core/adaptive-thresholds.ts +4 -3
  30. package/src/core/code-implementation-storage.ts +241 -0
  31. package/src/core/config.ts +5 -2
  32. package/src/core/control-ui-db.ts +29 -10
  33. package/src/core/detection-funnel.ts +12 -7
  34. package/src/core/diagnostician-task-store.ts +156 -0
  35. package/src/core/dictionary.ts +4 -4
  36. package/src/core/empathy-keyword-matcher.ts +7 -3
  37. package/src/core/empathy-types.ts +13 -2
  38. package/src/core/event-log.ts +14 -6
  39. package/src/core/evolution-engine.ts +27 -31
  40. package/src/core/evolution-logger.ts +3 -2
  41. package/src/core/evolution-reducer.ts +110 -31
  42. package/src/core/evolution-types.ts +10 -0
  43. package/src/core/external-training-contract.ts +1 -0
  44. package/src/core/focus-history.ts +38 -24
  45. package/src/core/hygiene/tracker.ts +10 -6
  46. package/src/core/init.ts +5 -2
  47. package/src/core/migration.ts +3 -3
  48. package/src/core/model-deployment-registry.ts +6 -4
  49. package/src/core/model-training-registry.ts +5 -3
  50. package/src/core/nocturnal-arbiter.ts +13 -14
  51. package/src/core/nocturnal-artifact-lineage.ts +117 -0
  52. package/src/core/nocturnal-artificer.ts +257 -0
  53. package/src/core/nocturnal-candidate-scoring.ts +4 -2
  54. package/src/core/nocturnal-compliance.ts +67 -19
  55. package/src/core/nocturnal-dataset.ts +95 -2
  56. package/src/core/nocturnal-executability.ts +2 -3
  57. package/src/core/nocturnal-export.ts +6 -3
  58. package/src/core/nocturnal-rule-implementation-validator.ts +245 -0
  59. package/src/core/nocturnal-trajectory-extractor.ts +10 -3
  60. package/src/core/nocturnal-trinity.ts +319 -61
  61. package/src/core/pain-context-extractor.ts +29 -15
  62. package/src/core/pain.ts +7 -5
  63. package/src/core/path-resolver.ts +16 -15
  64. package/src/core/paths.ts +2 -1
  65. package/src/core/pd-task-reconciler.ts +463 -0
  66. package/src/core/pd-task-service.ts +42 -0
  67. package/src/core/pd-task-store.ts +77 -0
  68. package/src/core/pd-task-types.ts +128 -0
  69. package/src/core/principle-internalization/deprecated-readiness.ts +91 -0
  70. package/src/core/principle-internalization/internalization-routing-policy.ts +208 -0
  71. package/src/core/principle-internalization/lifecycle-metrics.ts +149 -0
  72. package/src/core/principle-internalization/lifecycle-read-model.ts +243 -0
  73. package/src/core/principle-internalization/lifecycle-refresh.ts +11 -0
  74. package/src/core/principle-internalization/principle-lifecycle-service.ts +167 -0
  75. package/src/core/principle-training-state.ts +95 -370
  76. package/src/core/principle-tree-ledger.ts +733 -0
  77. package/src/core/principle-tree-migration.ts +195 -0
  78. package/src/core/profile.ts +3 -1
  79. package/src/core/promotion-gate.ts +14 -18
  80. package/src/core/replay-engine.ts +562 -0
  81. package/src/core/risk-calculator.ts +6 -4
  82. package/src/core/rule-host-helpers.ts +39 -0
  83. package/src/core/rule-host-types.ts +82 -0
  84. package/src/core/rule-host.ts +245 -0
  85. package/src/core/rule-implementation-runtime.ts +38 -0
  86. package/src/core/schema/db-types.ts +16 -0
  87. package/src/core/schema/index.ts +26 -0
  88. package/src/core/schema/migration-runner.ts +207 -0
  89. package/src/core/schema/migrations/001-init-trajectory.ts +211 -0
  90. package/src/core/schema/migrations/002-init-central.ts +122 -0
  91. package/src/core/schema/migrations/003-init-workflow.ts +55 -0
  92. package/src/core/schema/migrations/004-add-thinking-and-gfi.ts +74 -0
  93. package/src/core/schema/migrations/index.ts +31 -0
  94. package/src/core/schema/schema-definitions.ts +650 -0
  95. package/src/core/session-tracker.ts +6 -4
  96. package/src/core/shadow-observation-registry.ts +6 -3
  97. package/src/core/system-logger.ts +2 -2
  98. package/src/core/thinking-models.ts +182 -46
  99. package/src/core/thinking-os-parser.ts +156 -0
  100. package/src/core/training-program.ts +7 -7
  101. package/src/core/trajectory.ts +42 -36
  102. package/src/core/workspace-context.ts +77 -11
  103. package/src/core/workspace-dir-validation.ts +152 -0
  104. package/src/hooks/AGENTS.md +31 -0
  105. package/src/hooks/bash-risk.ts +3 -1
  106. package/src/hooks/edit-verification.ts +9 -5
  107. package/src/hooks/gate-block-helper.ts +5 -1
  108. package/src/hooks/gate.ts +152 -5
  109. package/src/hooks/gfi-gate.ts +9 -2
  110. package/src/hooks/lifecycle-routing.ts +124 -0
  111. package/src/hooks/lifecycle.ts +12 -12
  112. package/src/hooks/llm.ts +17 -109
  113. package/src/hooks/message-sanitize.ts +5 -3
  114. package/src/hooks/pain.ts +19 -15
  115. package/src/hooks/progressive-trust-gate.ts +7 -1
  116. package/src/hooks/prompt.ts +169 -60
  117. package/src/hooks/subagent.ts +5 -4
  118. package/src/hooks/thinking-checkpoint.ts +2 -0
  119. package/src/hooks/trajectory-collector.ts +15 -12
  120. package/src/http/principles-console-route.ts +31 -68
  121. package/src/i18n/commands.ts +2 -2
  122. package/src/index.ts +130 -40
  123. package/src/service/central-database.ts +131 -43
  124. package/src/service/central-health-service.ts +47 -0
  125. package/src/service/central-overview-service.ts +135 -0
  126. package/src/service/central-sync-service.ts +87 -0
  127. package/src/service/control-ui-query-service.ts +46 -36
  128. package/src/service/event-log-auditor.ts +261 -0
  129. package/src/service/evolution-query-service.ts +23 -22
  130. package/src/service/evolution-worker.ts +565 -261
  131. package/src/service/health-query-service.ts +213 -36
  132. package/src/service/nocturnal-runtime.ts +8 -4
  133. package/src/service/nocturnal-service.ts +503 -59
  134. package/src/service/nocturnal-target-selector.ts +5 -7
  135. package/src/service/runtime-summary-service.ts +2 -1
  136. package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +25 -336
  137. package/src/service/subagent-workflow/dynamic-timeout.ts +30 -0
  138. package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +48 -386
  139. package/src/service/subagent-workflow/index.ts +2 -0
  140. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +169 -284
  141. package/src/service/subagent-workflow/runtime-direct-driver.ts +114 -16
  142. package/src/service/subagent-workflow/subagent-error-utils.ts +25 -0
  143. package/src/service/subagent-workflow/types.ts +9 -4
  144. package/src/service/subagent-workflow/workflow-manager-base.ts +573 -0
  145. package/src/service/subagent-workflow/workflow-store.ts +71 -11
  146. package/src/service/trajectory-service.ts +2 -1
  147. package/src/tools/critique-prompt.ts +1 -1
  148. package/src/tools/deep-reflect.ts +175 -209
  149. package/src/tools/model-index.ts +2 -1
  150. package/src/types/event-types.ts +2 -2
  151. package/src/types/principle-tree-schema.ts +29 -23
  152. package/src/utils/file-lock.ts +5 -3
  153. package/src/utils/io.ts +5 -2
  154. package/src/utils/nlp.ts +5 -46
  155. package/src/utils/node-vm-polyfill.ts +11 -0
  156. package/src/utils/plugin-logger.ts +2 -0
  157. package/src/utils/retry.ts +572 -0
  158. package/src/utils/subagent-probe.ts +1 -1
  159. package/templates/langs/en/core/AGENTS.md +0 -13
  160. package/templates/langs/en/core/SOUL.md +1 -31
  161. package/templates/langs/en/core/TOOLS.md +0 -4
  162. package/templates/langs/en/principles/THINKING_OS.md +77 -0
  163. package/templates/langs/en/skills/admin/SKILL.md +0 -1
  164. package/templates/langs/en/skills/evolution-framework-update/SKILL.md +1 -1
  165. package/templates/langs/en/skills/pd-diagnostician/SKILL.md +18 -5
  166. package/templates/langs/zh/core/AGENTS.md +0 -22
  167. package/templates/langs/zh/core/SOUL.md +1 -31
  168. package/templates/langs/zh/core/TOOLS.md +0 -4
  169. package/templates/langs/zh/principles/THINKING_OS.md +77 -0
  170. package/templates/langs/zh/skills/admin/SKILL.md +0 -1
  171. package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +1 -1
  172. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +25 -4
  173. package/tests/commands/evolution-status.test.ts +119 -0
  174. package/tests/commands/implementation-lifecycle.test.ts +362 -0
  175. package/tests/core/code-implementation-storage.test.ts +398 -0
  176. package/tests/core/evolution-reducer.detector-metadata.test.ts +28 -28
  177. package/tests/core/nocturnal-artifact-lineage.test.ts +53 -0
  178. package/tests/core/nocturnal-artificer.test.ts +241 -0
  179. package/tests/core/nocturnal-compliance-p-principles.test.ts +133 -0
  180. package/tests/core/nocturnal-rule-implementation-validator.test.ts +127 -0
  181. package/tests/core/pd-task-store.test.ts +126 -0
  182. package/tests/core/principle-internalization/deprecated-readiness.test.ts +193 -0
  183. package/tests/core/principle-internalization/internalization-routing-policy.test.ts +212 -0
  184. package/tests/core/principle-internalization/lifecycle-metrics.test.ts +350 -0
  185. package/tests/core/principle-internalization/principle-lifecycle-service.test.ts +211 -0
  186. package/tests/core/principle-training-state.test.ts +228 -1
  187. package/tests/core/principle-tree-ledger.test.ts +423 -0
  188. package/tests/core/regression-v1-9-1.test.ts +265 -0
  189. package/tests/core/replay-engine.test.ts +234 -0
  190. package/tests/core/rule-host-helpers.test.ts +120 -0
  191. package/tests/core/rule-host.test.ts +389 -0
  192. package/tests/core/rule-implementation-runtime.test.ts +64 -0
  193. package/tests/core/workspace-context.test.ts +53 -0
  194. package/tests/core/workspace-dir-validation.test.ts +272 -0
  195. package/tests/hooks/gate-rule-host-pipeline.test.ts +385 -0
  196. package/tests/hooks/pain.test.ts +74 -10
  197. package/tests/hooks/prompt.test.ts +63 -1
  198. package/tests/integration/principle-lifecycle.e2e.test.ts +197 -0
  199. package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +211 -0
  200. package/tests/service/data-endpoints-regression.test.ts +834 -0
  201. package/tests/service/evolution-worker.test.ts +0 -123
  202. package/tests/service/nocturnal-service-code-candidate.test.ts +330 -0
  203. package/tests/utils/nlp.test.ts +1 -19
  204. package/tests/utils/retry.test.ts +327 -0
  205. package/ui/src/App.tsx +1 -1
  206. package/ui/src/api.ts +4 -0
  207. package/ui/src/charts.tsx +366 -0
  208. package/ui/src/components/WorkspaceConfig.tsx +107 -75
  209. package/ui/src/i18n/ui.ts +89 -31
  210. package/ui/src/pages/EvolutionPage.tsx +1 -1
  211. package/ui/src/pages/OverviewPage.tsx +441 -81
  212. package/ui/src/pages/ThinkingModelsPage.tsx +287 -69
  213. package/ui/src/styles.css +43 -0
  214. package/ui/src/types.ts +17 -1
  215. package/src/agents/nocturnal-dreamer.md +0 -152
  216. package/src/agents/nocturnal-philosopher.md +0 -138
  217. package/src/agents/nocturnal-reflector.md +0 -126
  218. package/src/agents/nocturnal-scribe.md +0 -164
  219. package/templates/workspace/.principles/00-kernel.md +0 -51
  220. package/templates/workspace/.principles/DECISION_POLICY.json +0 -44
  221. package/templates/workspace/.principles/PRINCIPLES.md +0 -20
  222. package/templates/workspace/.principles/PROFILE.json +0 -54
  223. package/templates/workspace/.principles/PROFILE.schema.json +0 -56
  224. package/templates/workspace/.principles/THINKING_OS.md +0 -64
  225. package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +0 -7
  226. package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +0 -9
  227. package/templates/workspace/.principles/models/_INDEX.md +0 -27
  228. package/templates/workspace/.principles/models/first_principles.md +0 -62
  229. package/templates/workspace/.principles/models/marketing_4p.md +0 -52
  230. package/templates/workspace/.principles/models/porter_five.md +0 -63
  231. package/templates/workspace/.principles/models/swot.md +0 -60
  232. package/templates/workspace/.principles/models/user_story_map.md +0 -63
  233. package/templates/workspace/.state/WORKBOARD.json +0 -4
@@ -1,17 +1,385 @@
1
1
  import React, { useCallback, useEffect, useState } from 'react';
2
2
  import { api } from '../api';
3
- import type { OverviewResponse, OverviewHealthResponse } from '../types';
4
- import { Sparkline, GroupedBarChart, TimeRangeSelector, StatusBadge } from '../charts';
3
+ import type { OverviewResponse, CentralHealthResponse, WorkspaceHealthEntry } from '../types';
4
+ import { Sparkline, GroupedBarChart, TimeRangeSelector, DonutChart, BulletChart, GaugeChart, PrincipleStack, QueueBar, StatusBadge, LineChart } from '../charts';
5
5
  import { useI18n } from '../i18n/ui';
6
6
  import { formatPercent, formatDate } from '../utils/format';
7
7
  import { WorkspaceConfig } from '../components/WorkspaceConfig';
8
8
  import { Loading, ErrorState } from '../components';
9
9
  import { useAutoRefresh } from '../hooks/useAutoRefresh';
10
10
 
11
+ // ---------------------------------------------------------------------------
12
+ // Helper: get health status
13
+ // ---------------------------------------------------------------------------
14
+
15
+ function getHealthStatus(current: number, threshold: number): 'excellent' | 'good' | 'warning' | 'danger' {
16
+ if (current === 0) return 'excellent';
17
+ if (current < threshold * 0.5) return 'good';
18
+ if (current < threshold) return 'warning';
19
+ return 'danger';
20
+ }
21
+
22
+ const HEALTH_LABELS: Record<string, { zh: string; en: string; emoji: string }> = {
23
+ excellent: { zh: '优秀', en: 'Excellent', emoji: '🟢' },
24
+ good: { zh: '良好', en: 'Good', emoji: '🟡' },
25
+ warning: { zh: '警告', en: 'Warning', emoji: '🟠' },
26
+ danger: { zh: '危险', en: 'Critical', emoji: '🔴' },
27
+ };
28
+
29
+ const TRUST_DESC_KEYS = ['observer', 'editor', 'developer', 'architect'];
30
+ const TIER_DESC_KEYS = ['seed', 'sprout', 'sapling', 'tree', 'forest'];
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // WorkspaceHealthPanel — redesigned for clarity
34
+ // ---------------------------------------------------------------------------
35
+
36
+ function WorkspaceHealthPanel({ entry }: { entry: WorkspaceHealthEntry }) {
37
+ const { t } = useI18n();
38
+ const h = entry.health;
39
+ const totalPrinciples = h.principles.candidate + h.principles.probation + h.principles.active + h.principles.deprecated;
40
+ const status = getHealthStatus(h.gfi.current, h.gfi.threshold);
41
+ const statusLabel = HEALTH_LABELS[status];
42
+ const trustIdx = Math.max(0, Math.min(3, h.trust.stage - 1));
43
+ const trustDescKey = TRUST_DESC_KEYS[trustIdx] ?? 'observer';
44
+ const tierIdx = Math.max(0, Math.min(4, parseInt(h.evolution.tier.replace(/\D/g, ''), 10) - 1));
45
+ const tierDescKey = TIER_DESC_KEYS[tierIdx] ?? 'seed';
46
+
47
+ return (
48
+ <section className="panel" style={{ marginBottom: 'var(--space-5)' }}>
49
+ <div className="panel-header">
50
+ <div className="panel-header-left">
51
+ <h3 style={{ fontSize: '0.95rem', fontWeight: 600 }}>{entry.workspaceName}</h3>
52
+ <StatusBadge variant={h.painFlag.active ? 'warning' : 'success'}>
53
+ {h.painFlag.active ? `${t('overview.health.source')}: ${h.painFlag.source}` : t('overview.health.normal')}
54
+ </StatusBadge>
55
+ </div>
56
+ </div>
57
+ <div className="panel-content">
58
+
59
+ {/* Row 1: Health Status (wide) | Trust | Evolution */}
60
+ <div className="grid" style={{ gridTemplateColumns: '2fr 1fr 1fr', gap: 'var(--space-4)', marginBottom: 'var(--space-4)' }}>
61
+
62
+ {/* 今日健康度 */}
63
+ <div>
64
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 6 }}>
65
+ <span style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', textTransform: 'uppercase', letterSpacing: '0.05em' }}>
66
+ {t('overview.health.gfi')}
67
+ </span>
68
+ <StatusBadge variant={status === 'danger' ? 'error' : status === 'warning' ? 'warning' : 'success'}>
69
+ {statusLabel.emoji} {statusLabel.zh}
70
+ </StatusBadge>
71
+ </div>
72
+ {/* Big number */}
73
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, marginBottom: 8 }}>
74
+ <span style={{ fontSize: '2.5rem', fontWeight: 700, color: `var(--${status === 'danger' ? 'error' : status === 'warning' ? 'warning' : 'success'})`, lineHeight: 1 }}>
75
+ {h.gfi.current}
76
+ </span>
77
+ <span style={{ fontSize: '0.85rem', color: 'var(--text-secondary)' }}>
78
+ / {h.gfi.threshold} {t('overview.health.threshold')}
79
+ </span>
80
+ </div>
81
+ {/* Bullet Chart */}
82
+ <BulletChart
83
+ value={h.gfi.current}
84
+ target={h.gfi.threshold}
85
+ peak={h.gfi.peakToday}
86
+ max={Math.max(h.gfi.threshold * 2, 150)}
87
+ width={280}
88
+ />
89
+ {/* Mini sparkline for quick glance */}
90
+ {h.gfi.trend.length >= 2 && (
91
+ <div style={{ marginTop: 4 }}>
92
+ <Sparkline
93
+ data={h.gfi.trend.map(d => d.value)}
94
+ width={280}
95
+ height={20}
96
+ color={status === 'danger' ? 'var(--error)' : status === 'warning' ? 'var(--warning)' : 'var(--success)'}
97
+ fillOpacity={0.1}
98
+ />
99
+ </div>
100
+ )}
101
+ </div>
102
+
103
+ {/* 权限等级 */}
104
+ <div style={{ textAlign: 'center' }}>
105
+ <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
106
+ {t('overview.health.trustStage')}
107
+ </div>
108
+ <GaugeChart
109
+ value={h.trust.score}
110
+ label={h.trust.stageLabel}
111
+ sublabel={t(`overview.health.trustDesc.${trustDescKey}`)}
112
+ size={90}
113
+ segments={[
114
+ { label: 'Observer', color: 'var(--text-secondary)', max: 30 },
115
+ { label: 'Editor', color: 'var(--info)', max: 60 },
116
+ { label: 'Developer', color: 'var(--accent)', max: 80 },
117
+ { label: 'Architect', color: 'var(--success)', max: 100 },
118
+ ]}
119
+ />
120
+ <div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: 4 }}>
121
+ {t('overview.health.stage')} {h.trust.stage} · {t('overview.health.score')} {h.trust.score}
122
+ </div>
123
+ </div>
124
+
125
+ {/* 进化等级 */}
126
+ <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', alignItems: 'center' }}>
127
+ <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 4, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
128
+ {t('overview.health.epTier')}
129
+ </div>
130
+ <div style={{ fontSize: '1.5rem', fontWeight: 700, color: 'var(--accent)' }}>{h.evolution.tier}</div>
131
+ <div style={{ fontSize: '0.7rem', color: 'var(--text-secondary)', marginTop: 2 }}>
132
+ {h.evolution.points} {t('overview.health.points')}
133
+ </div>
134
+ <div style={{ fontSize: '0.65rem', color: 'var(--text-secondary)', marginTop: 4, textAlign: 'center', maxWidth: 100 }}>
135
+ {t(`overview.health.tierDesc.${tierDescKey}`)}
136
+ </div>
137
+ </div>
138
+ </div>
139
+
140
+ {/* Row 2: Principles | Queue | PainFlag */}
141
+ <div className="grid" style={{ gridTemplateColumns: '1fr 1fr 1fr', gap: 'var(--space-4)' }}>
142
+
143
+ {/* 原则分布 */}
144
+ <div>
145
+ <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
146
+ {t('overview.health.principlesTotal')}: {totalPrinciples}
147
+ </div>
148
+ <PrincipleStack
149
+ candidate={h.principles.candidate}
150
+ probation={h.principles.probation}
151
+ active={h.principles.active}
152
+ deprecated={h.principles.deprecated}
153
+ />
154
+ </div>
155
+
156
+ {/* 任务队列 */}
157
+ <div>
158
+ <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
159
+ {t('overview.health.queueBacklog')}: {h.queue.pending}
160
+ </div>
161
+ <QueueBar
162
+ pending={h.queue.pending}
163
+ inProgress={h.queue.inProgress}
164
+ completed={h.queue.completed}
165
+ />
166
+ </div>
167
+
168
+ {/* 问题检测 */}
169
+ <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
170
+ <div style={{ fontSize: '0.75rem', fontWeight: 600, color: 'var(--text-secondary)', marginBottom: 8, textTransform: 'uppercase', letterSpacing: '0.05em' }}>
171
+ {t('overview.health.painFlag')}
172
+ </div>
173
+ {h.painFlag.active ? (
174
+ <div style={{ padding: 'var(--space-2)', backgroundColor: 'rgba(184, 134, 11, 0.08)', borderRadius: 6, borderLeft: '3px solid var(--warning)' }}>
175
+ <div style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--warning)' }}>⚠ {t('overview.health.active')}</div>
176
+ <div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: 2 }}>
177
+ {t('overview.health.source')}: {h.painFlag.source}
178
+ </div>
179
+ {h.painFlag.score !== null && (
180
+ <div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)' }}>
181
+ {t('overview.health.score')}: {h.painFlag.score}
182
+ </div>
183
+ )}
184
+ </div>
185
+ ) : (
186
+ <div style={{ padding: 'var(--space-2)', backgroundColor: 'rgba(74, 124, 111, 0.08)', borderRadius: 6, borderLeft: '3px solid var(--success)' }}>
187
+ <div style={{ fontSize: '0.85rem', fontWeight: 600, color: 'var(--success)' }}>✓ {t('overview.health.normal')}</div>
188
+ <div style={{ fontSize: '0.75rem', color: 'var(--text-secondary)', marginTop: 2 }}>
189
+ {t('overview.health.noActivePain')}
190
+ </div>
191
+ </div>
192
+ )}
193
+ </div>
194
+ </div>
195
+
196
+ {/* Full-width GFI trend chart */}
197
+ <section style={{ marginTop: 'var(--space-4)', borderTop: '1px solid var(--border)', paddingTop: 'var(--space-4)' }}>
198
+ <div style={{ display: 'flex', alignItems: 'baseline', gap: 8, marginBottom: 8 }}>
199
+ <span style={{ fontSize: '0.8rem', fontWeight: 600, color: 'var(--text-primary)' }}>
200
+ 📈 {t('overview.health.gfi')} · 今日趋势
201
+ </span>
202
+ <span style={{ fontSize: '0.7rem', color: 'var(--text-secondary)' }}>
203
+ 今日峰值: {h.gfi.peakToday}
204
+ </span>
205
+ </div>
206
+ {h.gfi.trend.length >= 2 ? (
207
+ <LineChart
208
+ data={h.gfi.trend.map(d => ({
209
+ label: d.hour.slice(11, 16),
210
+ value: d.value,
211
+ }))}
212
+ width={560}
213
+ height={160}
214
+ color={status === 'danger' ? 'var(--error)' : status === 'warning' ? 'var(--warning)' : 'var(--success)'}
215
+ />
216
+ ) : (
217
+ <div style={{ height: 160, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
218
+ 今日暂无 GFI 记录
219
+ </div>
220
+ )}
221
+ </section>
222
+ </div>
223
+ </section>
224
+ );
225
+ }
226
+
227
+ // ---------------------------------------------------------------------------
228
+ // Thinking Model Distribution — shows each model's usage count
229
+ // Dynamically rendered from API modelDefinitions (no hardcoding!)
230
+ // ---------------------------------------------------------------------------
231
+
232
+ function ThinkingModelDistribution({
233
+ modelBreakdown,
234
+ definitions,
235
+ }: {
236
+ modelBreakdown?: Array<{ modelId: string; hits: number }>;
237
+ definitions?: Array<{ modelId: string; name: string; description: string }>;
238
+ }) {
239
+ if (!definitions || definitions.length === 0) {
240
+ return (
241
+ <div style={{ textAlign: 'center', padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
242
+ 暂无思维模型定义
243
+ </div>
244
+ );
245
+ }
246
+
247
+ const hitsMap = new Map(modelBreakdown?.map(m => [m.modelId, m.hits]) ?? []);
248
+ const hasAnyHits = [...hitsMap.values()].some(v => v > 0);
249
+
250
+ if (!hasAnyHits) {
251
+ return (
252
+ <div style={{ textAlign: 'center', padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
253
+ 暂无思维模型使用记录。AI 开始使用后这里会显示数据。
254
+ </div>
255
+ );
256
+ }
257
+
258
+ const maxHits = Math.max(...definitions.map(d => hitsMap.get(d.modelId) ?? 0), 1);
259
+
260
+ return (
261
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
262
+ {definitions.map(def => {
263
+ const hits = hitsMap.get(def.modelId) ?? 0;
264
+ const pct = (hits / maxHits) * 100;
265
+ return (
266
+ <div key={def.modelId} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
267
+ <div style={{ minWidth: 90, fontSize: '0.72rem', fontWeight: 500, color: 'var(--text-primary)' }}>
268
+ {def.name}
269
+ </div>
270
+ <div style={{ flex: 1, height: 10, backgroundColor: 'var(--bg-sunken)', borderRadius: 5, overflow: 'hidden' }}>
271
+ <div
272
+ style={{
273
+ width: `${pct}%`,
274
+ height: '100%',
275
+ backgroundColor: hits > 0 ? 'var(--accent)' : 'transparent',
276
+ borderRadius: 5,
277
+ minWidth: hits > 0 ? 4 : 0,
278
+ transition: 'width 0.3s ease',
279
+ }}
280
+ />
281
+ </div>
282
+ <div style={{ minWidth: 24, textAlign: 'right', fontSize: '0.72rem', color: hits > 0 ? 'var(--text-primary)' : 'var(--text-secondary)', fontWeight: 600 }}>
283
+ {hits}
284
+ </div>
285
+ <div style={{ minWidth: 100, fontSize: '0.65rem', color: 'var(--text-secondary)' }}>
286
+ {def.description}
287
+ </div>
288
+ </div>
289
+ );
290
+ })}
291
+ </div>
292
+ );
293
+ }
294
+
295
+ // ---------------------------------------------------------------------------
296
+ // KPI Card with explanation
297
+ // ---------------------------------------------------------------------------
298
+
299
+ interface KpiCardProps {
300
+ label: string;
301
+ value: string | number;
302
+ explain: { zh: string; en: string };
303
+ trend?: number[];
304
+ trendColor?: string;
305
+ }
306
+
307
+ function ExplainKpiCard({ label, value, explain, trend, trendColor }: KpiCardProps) {
308
+ const { t } = useI18n();
309
+ const [showExplain, setShowExplain] = useState(false);
310
+
311
+ return (
312
+ <article className="panel kpi" style={{ position: 'relative' }}>
313
+ <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 8 }}>
314
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4, flex: 1 }}>
315
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
316
+ <span
317
+ className="label"
318
+ onClick={() => setShowExplain(!showExplain)}
319
+ style={{ cursor: 'pointer', userSelect: 'none' }}
320
+ >
321
+ {label}
322
+ </span>
323
+ <button
324
+ onClick={() => setShowExplain(!showExplain)}
325
+ style={{
326
+ background: 'none',
327
+ border: '1px solid var(--border)',
328
+ borderRadius: 4,
329
+ cursor: 'pointer',
330
+ color: 'var(--text-secondary)',
331
+ width: 22,
332
+ height: 22,
333
+ display: 'flex',
334
+ alignItems: 'center',
335
+ justifyContent: 'center',
336
+ fontSize: '0.75rem',
337
+ lineHeight: 1,
338
+ padding: 0,
339
+ flexShrink: 0,
340
+ }}
341
+ title="查看说明"
342
+ >
343
+ ?
344
+ </button>
345
+ </div>
346
+ <span className="value">{value}</span>
347
+ </div>
348
+ {trend && trend.length >= 2 && (
349
+ <div className="stat-sparkline" style={{ flexShrink: 0, alignSelf: 'center' }}>
350
+ <Sparkline data={trend} width={50} height={16} color={trendColor} />
351
+ </div>
352
+ )}
353
+ </div>
354
+ {showExplain && (
355
+ <div
356
+ style={{
357
+ marginTop: 6,
358
+ padding: '8px 10px',
359
+ background: 'var(--bg-sunken)',
360
+ border: '1px solid var(--border)',
361
+ borderRadius: 'var(--radius-sm)',
362
+ fontSize: '0.78rem',
363
+ color: 'var(--text-secondary)',
364
+ lineHeight: 1.5,
365
+ }}
366
+ >
367
+ 💡 {t(explain.zh) || explain.zh}
368
+ </div>
369
+ )}
370
+ </article>
371
+ );
372
+ }
373
+
374
+ // ---------------------------------------------------------------------------
375
+ // OverviewPage
376
+ // ---------------------------------------------------------------------------
377
+
11
378
  export function OverviewPage() {
12
379
  const { t } = useI18n();
13
380
  const [data, setData] = useState<OverviewResponse | null>(null);
14
- const [health, setHealth] = useState<OverviewHealthResponse | null>(null);
381
+ const [centralHealth, setCentralHealth] = useState<CentralHealthResponse | null>(null);
382
+ const [healthError, setHealthError] = useState('');
15
383
  const [error, setError] = useState('');
16
384
  const [syncing, setSyncing] = useState(false);
17
385
  const [days, setDays] = useState(30);
@@ -28,7 +396,14 @@ export function OverviewPage() {
28
396
 
29
397
  const loadAll = useCallback(async () => {
30
398
  await loadCentralOverview();
31
- api.getOverviewHealth().then(setHealth).catch(() => {});
399
+ try {
400
+ const health = await api.getCentralHealth();
401
+ setCentralHealth(health);
402
+ setHealthError('');
403
+ } catch (err) {
404
+ console.error('[OverviewPage] Failed to load central health:', err);
405
+ setHealthError(String(err));
406
+ }
32
407
  }, [loadCentralOverview]);
33
408
 
34
409
  const { lastRefresh, isRefreshing, refresh } = useAutoRefresh(loadAll, {
@@ -58,8 +433,6 @@ export function OverviewPage() {
58
433
  const centralInfo = (data as OverviewResponse & { centralInfo?: { workspaceCount: number; enabledWorkspaceCount: number; workspaces: string[]; enabledWorkspaces: string[] } }).centralInfo;
59
434
  const dailyTrend = data.dailyTrend ?? [];
60
435
 
61
- // Prepare sparkline data
62
- const toolCallsTrend = dailyTrend.map(d => d.toolCalls);
63
436
  const failuresTrend = dailyTrend.map(d => d.failures);
64
437
  const correctionsTrend = dailyTrend.map(d => d.userCorrections);
65
438
  const thinkingTrend = dailyTrend.map(d => d.thinkingTurns);
@@ -85,76 +458,63 @@ export function OverviewPage() {
85
458
 
86
459
  <WorkspaceConfig />
87
460
 
88
- {/* System Health Cards (Phase 5) */}
89
- {health && (
90
- <section className="kpi-grid" style={{ marginBottom: 'var(--space-5)' }}>
91
- <article className="panel kpi" style={{ borderLeft: `3px solid ${health.gfi.current >= health.gfi.threshold ? 'var(--error)' : 'var(--success)'}` }}>
92
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill={health.gfi.current >= health.gfi.threshold ? 'var(--error)' : 'var(--success)'}/></svg>{t('overview.health.gfi')}</span>
93
- <span className="value">{health.gfi.current}</span>
94
- <span>{t('overview.health.threshold')}: {health.gfi.threshold} | {t('overview.health.peakToday')}: {health.gfi.peakToday}</span>
95
- </article>
96
- <article className="panel kpi" style={{ borderLeft: `3px solid ${health.painFlag.active ? 'var(--warning)' : 'var(--success)'}` }}>
97
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill={health.painFlag.active ? 'var(--warning)' : 'var(--success)'}/></svg>{t('overview.health.painFlag')}</span>
98
- <span className="value">{health.painFlag.active ? t('overview.health.active') : t('overview.health.normal')}</span>
99
- <span>{health.painFlag.source ? `${t('overview.health.source')}: ${health.painFlag.source}` : t('overview.health.noActivePain')}</span>
100
- </article>
101
- <article className="panel kpi" style={{ borderLeft: '3px solid var(--info)' }}>
102
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill="var(--info)"/></svg>{t('overview.health.trustStage')}</span>
103
- <span className="value">{health.trust.stageLabel}</span>
104
- <span>{t('overview.health.stage')} {health.trust.stage} | {t('overview.health.score')}: {health.trust.score}</span>
105
- </article>
106
- <article className="panel kpi" style={{ borderLeft: '3px solid var(--accent)' }}>
107
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill="var(--accent)"/></svg>{t('overview.health.epTier')}</span>
108
- <span className="value">{health.evolution.tier}</span>
109
- <span>{t('overview.health.points')}: {health.evolution.points}</span>
110
- </article>
111
- <article className="panel kpi" style={{ borderLeft: '3px solid var(--success)' }}>
112
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill="var(--success)"/></svg>{t('overview.health.principlesTotal')}</span>
113
- <span className="value">{health.principles.candidate + health.principles.probation + health.principles.active + health.principles.deprecated}</span>
114
- <span>{t('overview.health.candidate')}: {health.principles.candidate} | {t('overview.health.probation')}: {health.principles.probation} | {t('overview.health.active2')}: {health.principles.active} | {t('overview.health.deprecated')}: {health.principles.deprecated}</span>
115
- </article>
116
- <article className="panel kpi" style={{ borderLeft: `3px solid ${health.queue.pending > 5 ? 'var(--warning)' : 'var(--success)'}` }}>
117
- <span className="label"><svg width="10" height="10" viewBox="0 0 10 10" style={{marginRight: '6px', verticalAlign: 'middle'}}><circle cx="5" cy="5" r="5" fill={health.queue.pending > 5 ? 'var(--warning)' : 'var(--success)'}/></svg>{t('overview.health.queueBacklog')}</span>
118
- <span className="value">{health.queue.pending}</span>
119
- <span>{t('overview.health.pending')}: {health.queue.pending} | {t('overview.health.inProgress')}: {health.queue.inProgress} | {t('overview.health.completed')}: {health.queue.completed}</span>
120
- </article>
461
+ {/* Per-Workspace Health Panels */}
462
+ {centralHealth && centralHealth.workspaces.length > 0 ? (
463
+ centralHealth.workspaces.map((entry) => (
464
+ <WorkspaceHealthPanel key={entry.workspaceName} entry={entry} />
465
+ ))
466
+ ) : healthError ? (
467
+ <section className="panel" style={{ marginBottom: 'var(--space-4)', borderColor: 'var(--error)' }}>
468
+ <div style={{ textAlign: 'center', padding: 'var(--space-4)', color: 'var(--error)' }}>
469
+ Failed to load health data: {healthError}
470
+ </div>
471
+ </section>
472
+ ) : (
473
+ <section className="panel" style={{ marginBottom: 'var(--space-4)' }}>
474
+ <div style={{ textAlign: 'center', padding: 'var(--space-4)', color: 'var(--text-secondary)' }}>
475
+ {t('overview.health.noWorkspaces') || 'No enabled workspaces found'}
476
+ </div>
121
477
  </section>
122
478
  )}
123
479
 
480
+ {/* Aggregate KPI Grid with explanations */}
124
481
  <section className="kpi-grid">
125
- <article className="panel kpi">
126
- <span className="label">{t('overview.repeatErrorRate')}</span>
127
- <span className="value">{formatPercent(data.summary.repeatErrorRate)}</span>
128
- {failuresTrend.length >= 2 && (
129
- <div className="stat-sparkline"><Sparkline data={failuresTrend} width={50} height={16} color="var(--error)" /></div>
130
- )}
131
- </article>
132
- <article className="panel kpi">
133
- <span className="label">{t('overview.userCorrectionRate')}</span>
134
- <span className="value">{formatPercent(data.summary.userCorrectionRate)}</span>
135
- {correctionsTrend.length >= 2 && (
136
- <div className="stat-sparkline"><Sparkline data={correctionsTrend} width={50} height={16} color="var(--warning)" /></div>
137
- )}
138
- </article>
139
- <article className="panel kpi">
140
- <span className="label">{t('overview.pendingSamples')}</span>
141
- <span className="value">{data.summary.pendingSamples}</span>
142
- </article>
143
- <article className="panel kpi">
144
- <span className="label">{t('overview.approvedSamples')}</span>
145
- <span className="value">{data.summary.approvedSamples}</span>
146
- </article>
147
- <article className="panel kpi">
148
- <span className="label">{t('overview.thinkingCoverage')}</span>
149
- <span className="value">{formatPercent(data.summary.thinkingCoverageRate)}</span>
150
- {thinkingTrend.length >= 2 && (
151
- <div className="stat-sparkline"><Sparkline data={thinkingTrend} width={50} height={16} color="var(--info)" /></div>
152
- )}
153
- </article>
154
- <article className="panel kpi">
155
- <span className="label">{t('overview.painEvents')}</span>
156
- <span className="value">{data.summary.painEvents}</span>
157
- </article>
482
+ <ExplainKpiCard
483
+ label={t('overview.repeatErrorRate')}
484
+ value={formatPercent(data.summary.repeatErrorRate)}
485
+ explain={{ zh: 'AI 重复犯同样错误的比例。如果这个值高,说明 AI 没有从之前的错误中学习。', en: 'Percentage of times AI repeats the same mistake.' }}
486
+ trend={failuresTrend}
487
+ trendColor="var(--error)"
488
+ />
489
+ <ExplainKpiCard
490
+ label={t('overview.userCorrectionRate')}
491
+ value={formatPercent(data.summary.userCorrectionRate)}
492
+ explain={{ zh: '用户手动纠正 AI 操作的比例。如果这个值高,说明 AI 经常做错了需要你介入。', en: 'How often you had to manually correct AI actions.' }}
493
+ trend={correctionsTrend}
494
+ trendColor="var(--warning)"
495
+ />
496
+ <ExplainKpiCard
497
+ label={t('overview.pendingSamples')}
498
+ value={data.summary.pendingSamples}
499
+ explain={{ zh: '等待你审核的纠正样本。审核后 AI 会从中学习。', en: 'Correction samples waiting for your review. Reviewing them helps AI learn.' }}
500
+ />
501
+ <ExplainKpiCard
502
+ label={t('overview.approvedSamples')}
503
+ value={data.summary.approvedSamples}
504
+ explain={{ zh: '你已批准的高质量纠正样本。这些是 AI 的学习素材。', en: 'High-quality corrections you approved. These become AI training data.' }}
505
+ />
506
+ <ExplainKpiCard
507
+ label={t('overview.thinkingCoverage')}
508
+ value={formatPercent(data.summary.thinkingCoverageRate)}
509
+ explain={{ zh: 'AI 使用"深度思考"模式的任务比例。这个值太低可能说明 AI 在跳过思考直接执行。', en: 'Percentage of tasks where AI used deep thinking mode instead of acting immediately.' }}
510
+ trend={thinkingTrend}
511
+ trendColor="var(--info)"
512
+ />
513
+ <ExplainKpiCard
514
+ label={t('overview.painEvents')}
515
+ value={data.summary.painEvents}
516
+ explain={{ zh: '系统检测到的"痛苦信号"总数。包括工具失败、用户抱怨、AI 行为异常等。', en: 'Total pain signals detected: tool failures, user complaints, AI anomalies.' }}
517
+ />
158
518
  </section>
159
519
 
160
520
  <div className="grid two-columns">
@@ -221,18 +581,18 @@ export function OverviewPage() {
221
581
  ))}
222
582
  </div>
223
583
  </section>
584
+ {/* AI 思维使用分布 */}
224
585
  <section className="panel">
225
- <h3>{t('overview.thinkingSummary')}</h3>
226
- <div className="stack">
227
- <div className="row-card"><strong>{t('overview.activeModels')}</strong><span>{data.thinkingSummary.activeModels}</span></div>
228
- <div className="row-card"><strong>{t('overview.dormantModels')}</strong><span>{data.thinkingSummary.dormantModels}</span></div>
229
- <div className="row-card"><strong>{t('overview.effectiveModels')}</strong><span>{data.thinkingSummary.effectiveModels}</span></div>
230
- <div className="row-card"><strong>{t('overview.coverage')}</strong><span>{formatPercent(data.thinkingSummary.coverageRate)}</span></div>
231
- <div className="row-card"><strong>{t('overview.principleEvents')}</strong><span>{data.summary.principleEventCount}</span></div>
232
- </div>
586
+ <h3>{t('overview.thinkingDistribution')}</h3>
587
+ <p style={{ fontSize: '0.72rem', color: 'var(--text-secondary)', marginTop: -4, marginBottom: 10 }}>
588
+ {t('overview.thinkingDistributionDesc')}
589
+ </p>
590
+ <ThinkingModelDistribution
591
+ modelBreakdown={(data.thinkingSummary as { modelBreakdown?: Array<{ modelId: string; hits: number }> }).modelBreakdown}
592
+ definitions={data.thinkingSummary.modelDefinitions}
593
+ />
233
594
  </section>
234
595
  </div>
235
596
  </div>
236
597
  );
237
598
  }
238
-