principles-disciple 1.14.0 → 1.16.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.
@@ -0,0 +1,85 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as os from 'os';
4
+ import * as path from 'path';
5
+
6
+ const { mockExecuteNocturnalReflectionAsync } = vi.hoisted(() => ({
7
+ mockExecuteNocturnalReflectionAsync: vi.fn(),
8
+ }));
9
+
10
+ vi.mock('../../src/service/nocturnal-service.js', () => ({
11
+ executeNocturnalReflectionAsync: mockExecuteNocturnalReflectionAsync,
12
+ }));
13
+
14
+ vi.mock('../../src/utils/subagent-probe.js', () => ({
15
+ isSubagentRuntimeAvailable: vi.fn(() => true),
16
+ }));
17
+
18
+ vi.mock('../../src/core/nocturnal-paths.js', () => ({
19
+ resolveNocturnalDir: vi.fn((workspaceDir: string) => path.join(workspaceDir, '.state', 'nocturnal', 'samples')),
20
+ }));
21
+
22
+ import { NocturnalWorkflowManager, nocturnalWorkflowSpec } from '../../src/service/subagent-workflow/nocturnal-workflow-manager.js';
23
+ import { safeRmDir } from '../test-utils.js';
24
+
25
+ describe('NocturnalWorkflowManager runtime hardening', () => {
26
+ let workspaceDir: string;
27
+ let stateDir: string;
28
+
29
+ beforeEach(() => {
30
+ vi.clearAllMocks();
31
+ workspaceDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pd-nocturnal-wf-'));
32
+ stateDir = path.join(workspaceDir, '.state');
33
+ fs.mkdirSync(stateDir, { recursive: true });
34
+ });
35
+
36
+ afterEach(() => {
37
+ safeRmDir(workspaceDir);
38
+ });
39
+
40
+ it('marks workflow terminal_error when async pipeline throws a gateway-only runtime error', async () => {
41
+ mockExecuteNocturnalReflectionAsync.mockRejectedValue(
42
+ new Error('Plugin runtime subagent methods are only available during a gateway request.')
43
+ );
44
+
45
+ const manager = new NocturnalWorkflowManager({
46
+ workspaceDir,
47
+ stateDir,
48
+ logger: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() } as any,
49
+ runtimeAdapter: {} as any,
50
+ });
51
+
52
+ const handle = await manager.startWorkflow(nocturnalWorkflowSpec, {
53
+ parentSessionId: 'sleep_reflection:test',
54
+ taskInput: {},
55
+ metadata: {
56
+ snapshot: {
57
+ sessionId: 'session-1',
58
+ startedAt: '2026-04-10T00:00:00.000Z',
59
+ updatedAt: '2026-04-10T00:01:00.000Z',
60
+ assistantTurns: [],
61
+ userTurns: [],
62
+ toolCalls: [],
63
+ painEvents: [],
64
+ gateBlocks: [],
65
+ stats: {
66
+ totalAssistantTurns: 1,
67
+ totalToolCalls: 1,
68
+ totalPainEvents: 0,
69
+ totalGateBlocks: 0,
70
+ failureCount: 0,
71
+ },
72
+ },
73
+ },
74
+ });
75
+
76
+ await Promise.resolve();
77
+ await Promise.resolve();
78
+
79
+ const summary = await manager.getWorkflowDebugSummary(handle.workflowId);
80
+ expect(summary?.state).toBe('terminal_error');
81
+ expect(summary?.recentEvents.some((event) => event.eventType === 'nocturnal_failed')).toBe(true);
82
+
83
+ manager.dispose();
84
+ });
85
+ });
package/ui/src/charts.tsx CHANGED
@@ -861,6 +861,7 @@ interface LineChartProps {
861
861
  showDots?: boolean;
862
862
  showArea?: boolean;
863
863
  unit?: string;
864
+ emptyText?: string;
864
865
  }
865
866
 
866
867
  export function LineChart({
@@ -872,11 +873,13 @@ export function LineChart({
872
873
  showDots = true,
873
874
  showArea = true,
874
875
  unit = '',
876
+ emptyText = '',
875
877
  }: LineChartProps) {
876
878
  if (!data || data.length === 0) {
879
+ if (!emptyText) return null;
877
880
  return (
878
881
  <div style={{ height, display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-secondary)', fontSize: '0.85rem' }}>
879
- 暂无数据
882
+ {emptyText}
880
883
  </div>
881
884
  );
882
885
  }
@@ -256,7 +256,7 @@ export function ThinkingModelsPage() {
256
256
  /* ── Has data: full dashboard ── */
257
257
  <>
258
258
  {/* Coverage Trend */}
259
- {data.coverageTrend.length >= 1 && (
259
+ {data.coverageTrend.length >= 1 ? (
260
260
  <section className="panel" style={{ marginBottom: SPACE[4] }}>
261
261
  <h3 className="section-title">
262
262
  {t('thinkingModels.coverageTrend')}
@@ -269,8 +269,14 @@ export function ThinkingModelsPage() {
269
269
  showGrid
270
270
  showDots
271
271
  showArea
272
+ emptyText={t('common.noData')}
272
273
  />
273
274
  </section>
275
+ ) : (
276
+ <EmptyState
277
+ title={t('thinkingModels.emptyCoverageTrend')}
278
+ description={t('thinkingModels.emptyCoverageTrendDesc')}
279
+ />
274
280
  )}
275
281
 
276
282
  {/* Search + Sort + Filter */}
@@ -445,6 +451,7 @@ export function ThinkingModelsPage() {
445
451
  showGrid={false}
446
452
  showDots
447
453
  showArea
454
+ emptyText={t('common.noData')}
448
455
  />
449
456
  </article>
450
457
  );
@@ -511,6 +518,7 @@ export function ThinkingModelsPage() {
511
518
  showGrid
512
519
  showDots
513
520
  showArea
521
+ emptyText={t('common.noData')}
514
522
  />
515
523
  </article>
516
524
  ) : (