principles-disciple 1.13.0 → 1.14.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.
package/src/index.ts CHANGED
@@ -54,7 +54,6 @@ import { PDTaskService } from './core/pd-task-service.js';
54
54
  import { CentralSyncService } from './service/central-sync-service.js';
55
55
  import { ensureWorkspaceTemplates } from './core/init.js';
56
56
  import { migrateDirectoryStructure } from './core/migration.js';
57
- import { runMigrationIfNeeded } from './core/principle-tree-migration.js';
58
57
  import { SystemLogger } from './core/system-logger.js';
59
58
  import { createDeepReflectTool } from './tools/deep-reflect.js';
60
59
  import { PathResolver, resolveWorkspaceDirFromApi } from './core/path-resolver.js';
@@ -126,9 +125,6 @@ const plugin = {
126
125
  const workspaceDir = ctx.workspaceDir || api.resolvePath('.');
127
126
  if (!workspaceInitialized && workspaceDir) {
128
127
  migrateDirectoryStructure(api, workspaceDir);
129
- // Phase 11: Migrate trainingStore principles to tree.principles
130
- const { stateDir } = WorkspaceContext.fromHookContext({ workspaceDir });
131
- runMigrationIfNeeded(stateDir, workspaceDir);
132
128
  ensureWorkspaceTemplates(api, workspaceDir, language);
133
129
  SystemLogger.log(workspaceDir, 'SYSTEM_BOOT', `Principles Disciple online. Language: ${language}`);
134
130
  workspaceInitialized = true;
@@ -235,7 +231,7 @@ const plugin = {
235
231
  try {
236
232
  const workspaceDir = resolveToolHookWorkspaceDir(ctx, api, 'trajectory.after_tool_call');
237
233
  TrajectoryCollector.handleAfterToolCall(event, { ...ctx, workspaceDir });
238
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
234
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
239
235
  } catch (_err) {
240
236
  // Non-critical: don't log, just skip
241
237
  }
@@ -248,7 +244,7 @@ const plugin = {
248
244
  try {
249
245
  const workspaceDir = resolveToolHookWorkspaceDir(ctx as unknown as Record<string, unknown>, api, 'trajectory.llm_output');
250
246
  TrajectoryCollector.handleLlmOutput(event, { ...ctx, workspaceDir });
251
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
247
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: catch binding intentionally unused
252
248
  } catch (_err) {
253
249
  // Non-critical: don't log, just skip
254
250
  }
@@ -258,7 +254,7 @@ const plugin = {
258
254
  // ── Hook: Subagent Loop Closure ──
259
255
  api.on(
260
256
  'subagent_spawning',
261
-
257
+ // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars -- Reason: ctx param required by hook callback signature but not used in this handler
262
258
  (event: PluginHookSubagentSpawningEvent, _ctx: PluginHookSubagentContext): void | PluginHookSubagentSpawningResult => {
263
259
  try {
264
260
  // Resolve workspace via official API, falling back to PathResolver
@@ -1146,14 +1146,13 @@ async function executeNocturnalReflectionWithAdapter(
1146
1146
  // eslint-disable-next-line no-useless-assignment -- Reason: initial value unused due to immediate reassignment in all branches
1147
1147
  let snapshot: NocturnalSessionSnapshot | null = null;
1148
1148
 
1149
- if (options.principleIdOverride && options.snapshotOverride) {
1150
- // Skip Selector: use provided principleId and snapshot directly
1151
- selectedPrincipleId = options.principleIdOverride;
1152
- selectedSessionId = options.snapshotOverride.sessionId;
1153
- snapshot = options.snapshotOverride;
1154
- console.log(`[nocturnal-service] Using override: principleId=${selectedPrincipleId}, sessionId=${selectedSessionId}`);
1155
- // Calculate violation density from snapshot stats for meaningful diagnostics
1156
- const snapStats = options.snapshotOverride.stats;
1149
+ if (options.principleIdOverride && options.snapshotOverride) {
1150
+ // Skip Selector: use provided principleId and snapshot directly
1151
+ selectedPrincipleId = options.principleIdOverride;
1152
+ selectedSessionId = options.snapshotOverride.sessionId;
1153
+ snapshot = options.snapshotOverride;
1154
+ // Calculate violation density from snapshot stats for meaningful diagnostics
1155
+ const snapStats = options.snapshotOverride.stats;
1157
1156
  const totalToolCalls = snapStats?.totalToolCalls ?? 0;
1158
1157
  const failureCount = snapStats?.failureCount ?? 0;
1159
1158
  const violationDensity = totalToolCalls > 0 ? failureCount / totalToolCalls : 0;
@@ -1178,7 +1177,6 @@ async function executeNocturnalReflectionWithAdapter(
1178
1177
  diagnostics.idle = { isIdle: true, mostRecentActivityAt: 0, idleForMs: 0, userActiveSessions: 0, abandonedSessionIds: [], trajectoryGuardrailConfirmsIdle: true, reason: 'selector skipped (override provided)' };
1179
1178
  } else {
1180
1179
  // Normal Selector path
1181
- console.log(`[nocturnal-service] Step 2/7: Target selection (normal path)`);
1182
1180
  const extractor = createNocturnalTrajectoryExtractor(workspaceDir, stateDir);
1183
1181
  const selector = new NocturnalTargetSelector(workspaceDir, stateDir, extractor, {
1184
1182
  idleCheckOverride: options.idleCheckOverride,
@@ -1187,10 +1185,8 @@ async function executeNocturnalReflectionWithAdapter(
1187
1185
 
1188
1186
  const selection = selector.select();
1189
1187
  diagnostics.selection = selection;
1190
- console.log(`[nocturnal-service] Selector result: decision=${selection.decision}, skipReason=${selection.skipReason ?? 'none'}`);
1191
1188
 
1192
1189
  if (selection.decision === 'skip') {
1193
- console.warn(`[nocturnal-service] Target selection skipped: ${selection.skipReason}`);
1194
1190
  return {
1195
1191
  success: false,
1196
1192
  noTargetSelected: true,
@@ -176,14 +176,8 @@ export class NocturnalWorkflowManager implements WorkflowManager {
176
176
  // Other workflow managers (empathy, deep-reflect) have this check
177
177
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Reason: TrinityRuntimeAdapter interface doesn't expose api.runtime.subagent, but OpenClawTrinityRuntimeAdapter has it
178
178
  const subagent = (this.runtimeAdapter as any).api?.runtime?.subagent;
179
- const apiAvailable = !!(this.runtimeAdapter as any).api;
180
- const runtimeAvailable = !!(this.runtimeAdapter as any).api?.runtime;
181
- const subagentAvailable = isSubagentRuntimeAvailable(subagent);
182
-
183
- this.logger.info(`[PD:NocturnalWorkflow] Subagent availability check: api=${apiAvailable}, runtime=${runtimeAvailable}, subagent=${subagentAvailable}`);
184
-
185
- if (!subagentAvailable) {
186
- this.logger.warn(`[PD:NocturnalWorkflow] Subagent runtime unavailable (api=${apiAvailable}, runtime=${runtimeAvailable}), skipping workflow`);
179
+ if (!isSubagentRuntimeAvailable(subagent)) {
180
+ this.logger.warn(`[PD:NocturnalWorkflow] Subagent runtime unavailable, skipping workflow`);
187
181
  throw new Error(`NocturnalWorkflowManager: subagent runtime unavailable`);
188
182
  }
189
183
 
@@ -240,15 +234,12 @@ export class NocturnalWorkflowManager implements WorkflowManager {
240
234
  // When principleId is provided, we pass it as principleIdOverride to skip Selector.
241
235
  // When principleId is missing, Selector will choose a principle from training store.
242
236
  this.logger.info(`[PD:NocturnalWorkflow] Calling executeNocturnalReflectionAsync for full pipeline (principleId=${principleId ?? 'auto-select'})`);
243
- const pipelineStart = Date.now();
244
237
 
245
238
  // #213: Wrap fire-and-forget Promise with .catch() to prevent
246
239
  // unhandled promise rejections if anything throws outside the try-catch
247
240
  // (e.g., during parameter construction or environment errors).
248
241
  Promise.resolve().then(async () => {
249
242
  try {
250
- this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline step 1/4: Starting executeNocturnalReflectionAsync`);
251
-
252
243
  const result = await executeNocturnalReflectionAsync(
253
244
  this.workspaceDir,
254
245
  this.stateDir,
@@ -271,26 +262,20 @@ export class NocturnalWorkflowManager implements WorkflowManager {
271
262
  }
272
263
  );
273
264
 
274
- const pipelineDuration = Date.now() - pipelineStart;
275
- this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline completed in ${pipelineDuration}ms, success=${result.success}`);
276
-
277
265
  if (result.success) {
278
- this.logger.info(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline step 4/4: Completed successfully, artifactId=${result.diagnostics?.persistedPath}`);
279
266
  this.store.recordEvent(workflowId, 'nocturnal_completed', null, 'completed', 'Full pipeline completed via executeNocturnalReflectionAsync', {
280
267
  artifactId: result.diagnostics?.persistedPath,
281
268
  });
282
269
  this.completedWorkflows.set(workflowId, Date.now());
283
270
  } else {
284
271
  const reason = result.noTargetSelected ? 'no_target_selected' : 'validation_failed';
285
- this.logger.warn(`[PD:NocturnalWorkflow] [${workflowId}] Pipeline failed: reason=${reason}, noTargetSelected=${result.noTargetSelected}, skipReason=${result.skipReason ?? 'none'}, validationFailures=${result.validationFailures?.length ?? 0}`);
286
272
  this.store.recordEvent(workflowId, 'nocturnal_failed', null, 'terminal_error', reason, {
287
273
  failures: result.validationFailures,
288
274
  skipReason: result.skipReason,
289
275
  });
290
276
  }
291
277
  } catch (err) {
292
- const errDuration = Date.now() - pipelineStart;
293
- this.logger.error(`[PD:NocturnalWorkflow] [${workflowId}] executeNocturnalReflectionAsync threw after ${errDuration}ms: ${String(err)}`);
278
+ this.logger.error(`[PD:NocturnalWorkflow] executeNocturnalReflectionAsync threw: ${String(err)}`);
294
279
  this.store.recordEvent(workflowId, 'nocturnal_failed', null, 'terminal_error', String(err), { workflowId });
295
280
  }
296
281
  }).catch((err) => {
@@ -61,17 +61,4 @@ LLMs are highly sensitive to XML tags; this structure is designed to boost instr
61
61
  <must>Maintain extreme digital cleanliness. The project root is SACRED. Use strict `kebab-case` for all naming. Clean up all test scripts and debug artifacts after the task.</must>
62
62
  <forbidden>Creating arbitrary temporary files (e.g., `test.txt`, `temp.md`, `debug.log`) in the project root directory.</forbidden>
63
63
  </directive>
64
-
65
- <!-- 复杂任务分解与记忆外化 (Complex Task Decomposition & Memory Externalization) -->
66
- <directive id="T-09" name="DIVIDE_AND_CONQUER">
67
- <trigger>When facing a complex task with multiple interdependent steps or large-scale refactoring.</trigger>
68
- <must>Break the work into smallest meaningful units. Execute in dependency order. Validate each unit before proceeding.</must>
69
- <forbidden>Tackle complex tasks as a single monolithic operation. Mix unrelated changes in one edit.</forbidden>
70
- </directive>
71
-
72
- <directive id="T-10" name="MEMORY_EXTERNALIZATION">
73
- <trigger>When drawing conclusions, completing analysis, or about to switch context.</trigger>
74
- <must>Write conclusions to a file (plan.md, scratchpad, memory) before proceeding. Preserve reasoning for future reference.</must>
75
- <forbidden>Keep important conclusions only in conversation context. Lose state between turns.</forbidden>
76
- </directive>
77
64
  </thinking_os_core_directives>
@@ -61,17 +61,4 @@
61
61
  <must>保持极致的数字洁癖。项目根目录是神圣的。所有命名必须严格使用 `kebab-case`。任务结束后清理所有的测试脚本和 Debug 遗留物。</must>
62
62
  <forbidden>在项目根目录下随意创建临时文件(如 `test.txt`、`temp.md`、`debug.log`)。</forbidden>
63
63
  </directive>
64
-
65
- <!-- 复杂任务分解与记忆外化 (Complex Task Decomposition & Memory Externalization) -->
66
- <directive id="T-09" name="DIVIDE_AND_CONQUER">
67
- <trigger>面对包含多个相互依赖步骤的复杂任务或大规模重构时。</trigger>
68
- <must>将工作拆分为最小有意义的单元。按依赖顺序执行。在继续之前验证每个单元。</must>
69
- <forbidden>将复杂任务当作单一操作处理。在一次编辑中混合不相关的变更。</forbidden>
70
- </directive>
71
-
72
- <directive id="T-10" name="MEMORY_EXTERNALIZATION">
73
- <trigger>得出结论、完成分析或即将切换上下文时。</trigger>
74
- <must>将结论写入文件(plan.md、scratchpad、memory)后再继续。保留推理过程供未来参考。</must>
75
- <forbidden>将重要结论仅保留在对话上下文中。在会话切换后丢失状态。</forbidden>
76
- </directive>
77
64
  </thinking_os_core_directives>
package/ui/src/i18n/ui.ts CHANGED
@@ -52,6 +52,7 @@ export const i18n = {
52
52
  timeRange: { zh: '时间范围', en: 'Time Range' },
53
53
  total: { zh: '共', en: 'Total' },
54
54
  items: { zh: '条', en: 'items' },
55
+ search: { zh: '搜索...', en: 'Search...' },
55
56
  },
56
57
 
57
58
  // ========================================================================
@@ -70,6 +71,7 @@ export const i18n = {
70
71
  checking: { zh: '正在验证身份...', en: 'Verifying identity...' },
71
72
  loginTitle: { zh: 'Principles Console', en: 'Principles Console' },
72
73
  loginSubtitle: { zh: 'AI Agent 进化流程监控平台', en: 'AI Agent Evolution Monitoring Platform' },
74
+ tokenLabel: { zh: 'Gateway Token', en: 'Gateway Token' },
73
75
  tokenPlaceholder: { zh: '请输入您的 Gateway Token', en: 'Enter your Gateway Token' },
74
76
  tokenHint: { zh: '在服务器上运行 openclaw config get gateway.auth.token 获取 Token', en: 'Run openclaw config get gateway.auth.token on your server to get the Token' },
75
77
  loginButton: { zh: '登 录', en: 'Sign In' },
@@ -184,6 +186,11 @@ export const i18n = {
184
186
  createdAt: { zh: '创建时间', en: 'Created At' },
185
187
  failureMode: { zh: '失败模式', en: 'Failure Mode' },
186
188
  relatedThinking: { zh: '相关思维', en: 'Thinking Hits' },
189
+
190
+ // Missing i18n keys (previously hardcoded in components)
191
+ noGfiToday: { zh: '今日暂无 GFI 记录', en: 'No GFI data today' },
192
+ noWorkspacesFound: { zh: '未找到已启用的工作区', en: 'No enabled workspaces found' },
193
+ refreshing: { zh: '刷新中', en: 'Refreshing' },
187
194
  },
188
195
 
189
196
  // ========================================================================
@@ -213,6 +220,9 @@ export const i18n = {
213
220
  // Actions
214
221
  approve: { zh: '批准', en: 'Approve' },
215
222
  reject: { zh: '拒绝', en: 'Reject' },
223
+
224
+ // Error messages (previously hardcoded English)
225
+ reviewFailed: { zh: '审核操作失败', en: 'Review operation failed' },
216
226
  },
217
227
 
218
228
  // ========================================================================
@@ -236,6 +246,7 @@ export const i18n = {
236
246
  emptyAllActiveDesc: { zh: '没有休眠模型。', en: 'No dormant models.' },
237
247
  noModelsYet: { zh: '暂无思维模型数据', en: 'No thinking model data yet' },
238
248
  noModelsYetDesc: { zh: 'AI 开始使用后,这里会显示思维模型的使用情况。', en: 'Thinking model usage will appear here once AI starts working.' },
249
+ noMatches: { zh: '没有匹配的模型', en: 'No models match your filters.' },
239
250
 
240
251
  // Recommendations
241
252
  dormantModels: { zh: '休眠模型', en: 'Dormant Models' },
@@ -247,6 +258,8 @@ export const i18n = {
247
258
  // Detail sections
248
259
  outcomeStats: { zh: '效果统计', en: 'Outcome Stats' },
249
260
  usageTrend: { zh: '使用趋势', en: 'Usage Trend' },
261
+ emptyUsageTrend: { zh: '暂无使用趋势记录', en: 'No usage trend data yet' },
262
+ emptyUsageTrendDesc: { zh: '该模型尚未被触发使用。', en: 'This model has not been triggered yet.' },
250
263
  success: { zh: '成功', en: 'Success' },
251
264
  failure: { zh: '失败', en: 'Failure' },
252
265
  pain: { zh: '痛点', en: 'Pain' },
@@ -265,11 +278,32 @@ export const i18n = {
265
278
  emptyDesc: { zh: '点击左侧列表中的模型,查看场景分布和最近事件', en: 'Click a model from the list to inspect scenario coverage and recent events' },
266
279
  noDataTitle: { zh: '思维模型定义', en: 'Thinking Model Definitions' },
267
280
  noDataDesc: { zh: '以下是 10 个思维模型的定义。当 AI 开始使用后,这里会显示每个模型的使用统计。', en: 'Below are 10 thinking model definitions. Usage statistics will appear once the AI starts working.' },
281
+ noUsageDataYet: { zh: '该模型暂无使用数据', en: 'No usage data for this model yet' },
282
+ noUsageDataDesc: { zh: '当模型被触发后,使用趋势和事件会在此显示。', en: 'Usage trends and events will appear once the model is triggered.' },
268
283
 
269
284
  // Table
270
285
  hits: { zh: '命中', en: 'Hits' },
271
286
  successRate: { zh: '成功率', en: 'Success Rate' },
272
287
  failureRate: { zh: '失败率', en: 'Failure Rate' },
288
+
289
+ // Comparison mode
290
+ compare: { zh: '对比', en: 'Compare' },
291
+ compareSelected: { zh: '对比选中', en: 'Compare Selected' },
292
+ exitCompare: { zh: '退出对比', en: 'Exit Compare' },
293
+ comparisonTitle: { zh: '模型对比', en: 'Model Comparison' },
294
+ comparisonEmpty: { zh: '请选择至少 2 个模型进行对比', en: 'Select at least 2 models to compare' },
295
+
296
+ // Search and sort
297
+ filterAll: { zh: '全部', en: 'All' },
298
+ searchPlaceholder: { zh: '按名称或场景搜索...', en: 'Search by name or scenario...' },
299
+ sortByHits: { zh: '命中数', en: 'Hits' },
300
+ sortBySuccessRate: { zh: '成功率', en: 'Success Rate' },
301
+ sortByName: { zh: '名称', en: 'Name' },
302
+
303
+ // Loading states
304
+ loadingDetail: { zh: '正在加载模型详情...', en: 'Loading model details...' },
305
+ loadingComparison: { zh: '正在加载对比数据...', en: 'Loading comparison data...' },
306
+ modelLoading: { zh: '加载中', en: 'Loading' },
273
307
  },
274
308
 
275
309
  // ========================================================================
@@ -301,6 +335,11 @@ export const i18n = {
301
335
  analyzing: { zh: '分析中', en: 'Analyzing' },
302
336
  principle_generated: { zh: '原则生成', en: 'Principle Generated' },
303
337
  completed: { zh: '已完成', en: 'Completed' },
338
+ // Principle status labels
339
+ candidate: { zh: '候选', en: 'Candidate' },
340
+ probation: { zh: '试用', en: 'Probation' },
341
+ active: { zh: '活跃', en: 'Active' },
342
+ deprecated: { zh: '废弃', en: 'Deprecated' },
304
343
  },
305
344
 
306
345
  // Status filter
@@ -331,6 +370,19 @@ export const i18n = {
331
370
  evolutionTimeline: { zh: '进化时间线', en: 'Evolution Timeline' },
332
371
  detailedEvents: { zh: '详细事件', en: 'Detailed Events' },
333
372
  reason: { zh: '原因', en: 'Reason' },
373
+
374
+ // EvolutionPage panel headers (previously hardcoded Chinese)
375
+ currentStage: { zh: '当前阶段', en: 'Current Stage' },
376
+ principleLifecycle: { zh: '原则生命周期', en: 'Principle Lifecycle' },
377
+ nocturnalTrainingStatus: { zh: '夜间训练状态', en: 'Nocturnal Training Status' },
378
+ trainingQueue: { zh: '训练队列', en: 'Training Queue' },
379
+ arbiterPassRate: { zh: 'Arbiter 通过率', en: 'Arbiter Pass Rate' },
380
+ orpoSampleCount: { zh: 'ORPO 样本数', en: 'ORPO Sample Count' },
381
+ modelDeployments: { zh: '模型部署', en: 'Model Deployments' },
382
+ deploymentCount: { zh: '个', en: 'deployments' },
383
+ pendingShort: { zh: '待', en: 'P' },
384
+ inProgressShort: { zh: '中', en: 'I' },
385
+ completedShort: { zh: '完', en: 'C' },
334
386
  },
335
387
 
336
388
  // ========================================================================
@@ -1,45 +1,34 @@
1
- import React, { useCallback, useEffect, useMemo, useState } from 'react';
1
+ import React, { useEffect, useMemo, useState } from 'react';
2
2
  import { ChevronLeft, Clock, Activity, Shield, Zap, BookOpen } from 'lucide-react';
3
3
  import { api } from '../api';
4
4
  import type {
5
- OverviewResponse,
6
- SampleDetailResponse,
7
- SamplesResponse,
8
- ThinkingModelDetailResponse,
9
- ThinkingOverviewResponse,
10
5
  EvolutionTasksResponse,
11
6
  EvolutionTraceResponse,
12
7
  EvolutionStatsResponse,
13
- OverviewHealthResponse,
14
8
  EvolutionPrinciplesResponse,
15
- FeedbackGfiResponse,
16
- GateStatsResponse,
17
- GateBlockItem,
18
- EmpathyEvent,
19
- FeedbackGateBlock,
20
9
  } from '../types';
21
- import { Sparkline, DonutChart, GroupedBarChart, TimeRangeSelector, CollapsiblePanel, StatusBadge, EmptyState } from '../charts';
10
+ import { DonutChart, GroupedBarChart, TimeRangeSelector, StatusBadge, EmptyState } from '../charts';
22
11
  import { useI18n } from '../i18n/ui';
23
12
  import { formatPercent, formatDate, formatDuration } from '../utils/format';
24
13
  import { Loading, ErrorState } from '../components';
25
14
 
26
15
  const STAGE_COLORS: Record<string, string> = {
27
- pain_detected: '#ef4444',
28
- queued: '#f59e0b',
29
- started: '#3b82f6',
16
+ pain_detected: 'var(--error)',
17
+ queued: 'var(--warning)',
18
+ started: 'var(--info)',
30
19
  analyzing: '#8b5cf6',
31
- principle_generated: '#22c55e',
32
- completed: '#22c55e',
20
+ principle_generated: 'var(--success)',
21
+ completed: 'var(--success)',
33
22
  };
34
23
 
35
- const STAGE_LABELS: Record<string, string> = {
36
- pain_detected: '痛点检测',
37
- queued: '已入队',
38
- started: '开始处理',
39
- analyzing: '分析中',
40
- principle_generated: '原则生成',
41
- completed: '已完成',
42
- };
24
+ const STAGE_LABEL_KEYS: string[] = [
25
+ 'pain_detected',
26
+ 'queued',
27
+ 'started',
28
+ 'analyzing',
29
+ 'principle_generated',
30
+ 'completed',
31
+ ];
43
32
 
44
33
  export function EvolutionPage() {
45
34
  const { t } = useI18n();
@@ -86,10 +75,10 @@ export function EvolutionPage() {
86
75
 
87
76
  // Prepare donut chart data
88
77
  const statusSegments = [
89
- { label: t('evolution.pending'), value: stats.pending, color: '#f59e0b' },
90
- { label: t('evolution.inProgress'), value: stats.inProgress, color: '#3b82f6' },
91
- { label: t('evolution.completed'), value: stats.completed, color: '#22c55e' },
92
- { label: t('evolution.failed'), value: stats.failed, color: '#ef4444' },
78
+ { label: t('evolution.pending'), value: stats.pending, color: 'var(--warning)' },
79
+ { label: t('evolution.inProgress'), value: stats.inProgress, color: 'var(--info)' },
80
+ { label: t('evolution.completed'), value: stats.completed, color: 'var(--success)' },
81
+ { label: t('evolution.failed'), value: stats.failed, color: 'var(--error)' },
93
82
  ].filter(s => s.value > 0);
94
83
 
95
84
  return (
@@ -112,23 +101,15 @@ export function EvolutionPage() {
112
101
  {/* Current Stage Indicator */}
113
102
  {evoPrinciples && (
114
103
  <section className="panel" style={{ marginBottom: 'var(--space-5)' }}>
115
- <h3>当前阶段</h3>
104
+ <h3>{t('evolution.currentStage')}</h3>
116
105
  <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-3)', padding: 'var(--space-3) 0' }}>
117
- <span style={{
118
- padding: 'var(--space-2) var(--space-4)',
119
- borderRadius: 'var(--radius-lg)',
120
- fontSize: '15px',
121
- fontWeight: 600,
122
- background: 'var(--accent)',
123
- color: '#fff',
124
- border: '2px solid var(--accent)',
125
- }}>
126
- {evoPrinciples.activeStage === 'pending' ? <><Clock size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.pending')}</> :
127
- evoPrinciples.activeStage === 'in_progress' ? <><Activity size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.in_progress')}</> :
128
- evoPrinciples.activeStage === 'completed' ? <><Shield size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.completed')}</> :
129
- evoPrinciples.activeStage === 'idle' ? <><Zap size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>{t('evolution.activeStage.idle')}</> : evoPrinciples.activeStage}
106
+ <span className="stage-badge">
107
+ {evoPrinciples.activeStage === 'pending' ? <><Clock size={16} /><span>{t('evolution.activeStage.pending')}</span></> :
108
+ evoPrinciples.activeStage === 'in_progress' ? <><Activity size={16} /><span>{t('evolution.activeStage.in_progress')}</span></> :
109
+ evoPrinciples.activeStage === 'completed' ? <><Shield size={16} /><span>{t('evolution.activeStage.completed')}</span></> :
110
+ evoPrinciples.activeStage === 'idle' ? <><Zap size={16} /><span>{t('evolution.activeStage.idle')}</span></> : evoPrinciples.activeStage}
130
111
  </span>
131
- <span style={{ color: 'var(--text-secondary)', fontSize: '14px' }}>
112
+ <span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
132
113
  {t('evolution.enhancementLoopStatus')}
133
114
  </span>
134
115
  </div>
@@ -139,12 +120,12 @@ export function EvolutionPage() {
139
120
  {evoPrinciples && (
140
121
  <div className="grid two-columns" style={{ marginBottom: 'var(--space-5)' }}>
141
122
  <section className="panel">
142
- <h3><BookOpen size={16} style={{marginRight: 6, verticalAlign: 'middle'}}/>原则生命周期</h3>
123
+ <h3><BookOpen size={16} />{t('evolution.principleLifecycle')}</h3>
143
124
  <div className="pill-row" style={{ marginBottom: 'var(--space-3)' }}>
144
- <StatusBadge variant="warning">候选: {evoPrinciples.principles.summary.candidate}</StatusBadge>
145
- <StatusBadge variant="info">试用: {evoPrinciples.principles.summary.probation}</StatusBadge>
146
- <StatusBadge variant="success">活跃: {evoPrinciples.principles.summary.active}</StatusBadge>
147
- <StatusBadge variant="error">废弃: {evoPrinciples.principles.summary.deprecated}</StatusBadge>
125
+ <StatusBadge variant="warning">{t('evolution.stageLabels.candidate')}: {evoPrinciples.principles.summary.candidate}</StatusBadge>
126
+ <StatusBadge variant="info">{t('evolution.stageLabels.probation')}: {evoPrinciples.principles.summary.probation}</StatusBadge>
127
+ <StatusBadge variant="success">{t('evolution.stageLabels.active')}: {evoPrinciples.principles.summary.active}</StatusBadge>
128
+ <StatusBadge variant="error">{t('evolution.stageLabels.deprecated')}: {evoPrinciples.principles.summary.deprecated}</StatusBadge>
148
129
  </div>
149
130
  {evoPrinciples.principles.recent.length > 0 && (
150
131
  <div className="stack">
@@ -161,23 +142,23 @@ export function EvolutionPage() {
161
142
  )}
162
143
  </section>
163
144
  <section className="panel">
164
- <h3>💤 夜间训练状态</h3>
145
+ <h3>💤 {t('evolution.nocturnalTrainingStatus')}</h3>
165
146
  <div className="stack">
166
147
  <div className="row-card">
167
- <strong>训练队列</strong>
168
- <span>待: {evoPrinciples.nocturnalTraining.queue.pending} | 中: {evoPrinciples.nocturnalTraining.queue.inProgress} | 完: {evoPrinciples.nocturnalTraining.queue.completed}</span>
148
+ <strong>{t('evolution.trainingQueue')}</strong>
149
+ <span>{t('evolution.pendingShort')}: {evoPrinciples.nocturnalTraining.queue.pending} | {t('evolution.inProgressShort')}: {evoPrinciples.nocturnalTraining.queue.inProgress} | {t('evolution.completedShort')}: {evoPrinciples.nocturnalTraining.queue.completed}</span>
169
150
  </div>
170
151
  <div className="row-card">
171
- <strong>Arbiter 通过率</strong>
152
+ <strong>{t('evolution.arbiterPassRate')}</strong>
172
153
  <span>{(evoPrinciples.nocturnalTraining.arbiterPassRate * 100).toFixed(1)}%</span>
173
154
  </div>
174
155
  <div className="row-card">
175
- <strong>ORPO 样本数</strong>
156
+ <strong>{t('evolution.orpoSampleCount')}</strong>
176
157
  <span>{evoPrinciples.nocturnalTraining.orpoSampleCount}</span>
177
158
  </div>
178
159
  <div className="row-card">
179
- <strong>模型部署</strong>
180
- <span>{evoPrinciples.nocturnalTraining.deployments.length} 个</span>
160
+ <strong>{t('evolution.modelDeployments')}</strong>
161
+ <span>{evoPrinciples.nocturnalTraining.deployments.length} {t('evolution.deploymentCount')}</span>
181
162
  </div>
182
163
  </div>
183
164
  </section>
@@ -136,5 +136,3 @@ export function FeedbackPage() {
136
136
  </div>
137
137
  );
138
138
  }
139
-
140
- // ===== Phase 6: Gate Monitor Page =====
@@ -80,7 +80,7 @@ export function GateMonitorPage() {
80
80
  <section className="panel">
81
81
  <h3>🔐 {t('gate.trustEngine')}</h3>
82
82
  <div style={{ padding: 'var(--space-3) 0' }}>
83
- <div style={{ fontSize: '24px', fontWeight: 700 }}>Stage {gateStats.trust.stage}: {gateStats.trust.status}</div>
83
+ <div style={{ fontSize: '1.5rem', fontWeight: 700 }}>Stage {gateStats.trust.stage}: {gateStats.trust.status}</div>
84
84
  <div style={{ marginTop: 'var(--space-2)', width: '100%', height: '12px', background: 'var(--bg-sunken)', borderRadius: '6px' }}>
85
85
  <div style={{ width: `${gateStats.trust.score}%`, height: '100%', background: 'var(--info)', borderRadius: '6px' }} />
86
86
  </div>
@@ -92,11 +92,11 @@ export function GateMonitorPage() {
92
92
  <section className="panel">
93
93
  <h3>🌱 {t('gate.evolutionEngine')}</h3>
94
94
  <div style={{ padding: 'var(--space-3) 0' }}>
95
- <div style={{ fontSize: '24px', fontWeight: 700 }}>{gateStats.evolution.tier} ({gateStats.evolution.status})</div>
95
+ <div style={{ fontSize: '1.5rem', fontWeight: 700 }}>{gateStats.evolution.tier} ({gateStats.evolution.status})</div>
96
96
  <div style={{ marginTop: 'var(--space-2)', width: '100%', height: '12px', background: 'var(--bg-sunken)', borderRadius: '6px' }}>
97
97
  <div style={{ width: `${Math.min(100, gateStats.evolution.points / 10)}%`, height: '100%', background: 'var(--success)', borderRadius: '6px' }} />
98
98
  </div>
99
- <div style={{ fontSize: '13px', color: 'var(--text-secondary)', marginTop: 'var(--space-1)' }}>
99
+ <div className="text-sm" style={{ color: 'var(--text-secondary)', marginTop: 'var(--space-1)' }}>
100
100
  {t('gate.points')}: {gateStats.evolution.points}
101
101
  </div>
102
102
  </div>
@@ -44,7 +44,7 @@ export function LoginPage() {
44
44
 
45
45
  <form className="login-form" onSubmit={handleSubmit}>
46
46
  <div className="form-group">
47
- <label htmlFor="token">Gateway Token</label>
47
+ <label htmlFor="token">{t('auth.tokenLabel')}</label>
48
48
  <input
49
49
  id="token"
50
50
  type="password"
@@ -52,6 +52,7 @@ export function LoginPage() {
52
52
  onChange={(e) => setToken(e.target.value)}
53
53
  placeholder={t('auth.tokenPlaceholder')}
54
54
  autoComplete="off"
55
+ aria-label={t('auth.tokenLabel')}
55
56
  />
56
57
  <span className="form-hint">
57
58
  {t('auth.tokenHint')}
@@ -196,11 +196,11 @@ function WorkspaceHealthPanel({ entry }: { entry: WorkspaceHealthEntry }) {
196
196
  {/* Full-width GFI trend chart */}
197
197
  <section style={{ marginTop: 'var(--space-4)', borderTop: '1px solid var(--border)', paddingTop: 'var(--space-4)' }}>
198
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')} · 今日趋势
199
+ <span className="text-lg text-semibold">
200
+ {t('overview.health.gfi')} · {t('overview.recentTrend')}
201
201
  </span>
202
- <span style={{ fontSize: '0.7rem', color: 'var(--text-secondary)' }}>
203
- 今日峰值: {h.gfi.peakToday}
202
+ <span className="text-sm" style={{ color: 'var(--text-secondary)' }}>
203
+ {t('overview.health.peakToday')}: {h.gfi.peakToday}
204
204
  </span>
205
205
  </div>
206
206
  {h.gfi.trend.length >= 2 ? (
@@ -236,10 +236,11 @@ function ThinkingModelDistribution({
236
236
  modelBreakdown?: Array<{ modelId: string; hits: number }>;
237
237
  definitions?: Array<{ modelId: string; name: string; description: string }>;
238
238
  }) {
239
+ const { t } = useI18n();
239
240
  if (!definitions || definitions.length === 0) {
240
241
  return (
241
- <div style={{ textAlign: 'center', padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
242
- 暂无思维模型定义
242
+ <div className="text-center" style={{ padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: 'var(--text-lg, 0.8rem)' }}>
243
+ {t('overview.noDefinitions')}
243
244
  </div>
244
245
  );
245
246
  }
@@ -249,8 +250,8 @@ function ThinkingModelDistribution({
249
250
 
250
251
  if (!hasAnyHits) {
251
252
  return (
252
- <div style={{ textAlign: 'center', padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
253
- 暂无思维模型使用记录。AI 开始使用后这里会显示数据。
253
+ <div className="text-center" style={{ padding: 'var(--space-3)', color: 'var(--text-secondary)', fontSize: 'var(--text-lg, 0.8rem)' }}>
254
+ {t('overview.noUsage')}
254
255
  </div>
255
256
  );
256
257
  }
@@ -472,7 +473,7 @@ export function OverviewPage() {
472
473
  ) : (
473
474
  <section className="panel" style={{ marginBottom: 'var(--space-4)' }}>
474
475
  <div style={{ textAlign: 'center', padding: 'var(--space-4)', color: 'var(--text-secondary)' }}>
475
- {t('overview.health.noWorkspaces') || 'No enabled workspaces found'}
476
+ {t('overview.health.noWorkspaces') || t('overview.noWorkspacesFound')}
476
477
  </div>
477
478
  </section>
478
479
  )}
@@ -46,7 +46,7 @@ export function SamplesPage() {
46
46
  setData(samples);
47
47
  setSelected(detail);
48
48
  } catch (err) {
49
- setError(err instanceof Error ? err.message : 'Review operation failed');
49
+ setError(err instanceof Error ? err.message : t('samples.reviewFailed'));
50
50
  }
51
51
  }
52
52
 
@@ -122,11 +122,11 @@ export function SamplesPage() {
122
122
  </div>
123
123
  <article>
124
124
  <h4>{t('samples.badAttempt')}</h4>
125
- <pre>{selected.badAttempt.rawText || selected.badAttempt.sanitizedText}</pre>
125
+ <pre className="sample-pre">{selected.badAttempt.rawText || selected.badAttempt.sanitizedText}</pre>
126
126
  </article>
127
127
  <article>
128
128
  <h4>{t('samples.userCorrection')}</h4>
129
- <pre>{selected.userCorrection.rawText}</pre>
129
+ <pre className="sample-pre">{selected.userCorrection.rawText}</pre>
130
130
  </article>
131
131
  <article>
132
132
  <h4>{t('samples.recoveryToolSpan')}</h4>