principles-disciple 1.66.0 → 1.67.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/ADVANCED_CONFIG_ZH.md +0 -4
- package/README.md +0 -11
- package/openclaw.plugin.json +1 -25
- package/package.json +1 -1
- package/src/commands/context.ts +6 -14
- package/src/commands/evolution-status.ts +29 -3
- package/src/commands/pain.ts +3 -4
- package/src/config/defaults/runtime.ts +0 -2
- package/src/constants/tools.ts +0 -1
- package/src/core/config.ts +0 -30
- package/src/core/event-log.ts +0 -6
- package/src/hooks/prompt.ts +3 -35
- package/src/hooks/subagent.ts +0 -7
- package/src/index.ts +0 -2
- package/src/service/evolution-worker.ts +0 -13
- package/src/service/runtime-summary-service.ts +94 -15
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +1 -204
- package/src/service/subagent-workflow/index.ts +0 -8
- package/src/service/subagent-workflow/types.ts +0 -11
- package/src/service/subagent-workflow/workflow-manager-base.ts +1 -1
- package/src/tools/critique-prompt.ts +1 -97
- package/src/tools/deep-reflect.ts +1 -337
- package/src/tools/model-index.ts +1 -100
- package/src/types/event-payload.ts +0 -6
- package/src/types/event-types.ts +0 -86
- package/src/types.ts +0 -21
- package/templates/langs/en/core/TOOLS.md +0 -43
- package/templates/langs/zh/core/TOOLS.md +0 -43
- package/templates/pain_settings.json +0 -21
- package/tests/commands/evolution-status.test.ts +288 -0
- package/tests/core/event-log.test.ts +1 -29
- package/tests/service/runtime-summary-service.test.ts +1 -1
|
@@ -8,6 +8,8 @@ import { appendCandidateArtifactLineageRecord } from '../../src/core/nocturnal-a
|
|
|
8
8
|
import { getImplementationAssetRoot } from '../../src/core/code-implementation-storage.js';
|
|
9
9
|
import { EvolutionReducerImpl } from '../../src/core/evolution-reducer.js';
|
|
10
10
|
import { saveLedger } from '../../src/core/principle-tree-ledger.js';
|
|
11
|
+
import { WorkflowFunnelLoader } from '../../src/core/workflow-funnel-loader.js';
|
|
12
|
+
import { RuntimeSummaryService } from '../../src/service/runtime-summary-service.js';
|
|
11
13
|
|
|
12
14
|
const tempDirs: string[] = [];
|
|
13
15
|
|
|
@@ -338,4 +340,290 @@ describe('evolution commands', () => {
|
|
|
338
340
|
|
|
339
341
|
expect(result.text).toContain('internalization routes: P-001:skill@');
|
|
340
342
|
});
|
|
343
|
+
|
|
344
|
+
it('renders workflowFunnel blocks when YAML-driven funnels are present', () => {
|
|
345
|
+
const workspace = makeTempDir();
|
|
346
|
+
const stateDir = path.join(workspace, '.state');
|
|
347
|
+
|
|
348
|
+
// Write workflows.yaml with two funnels matching actual schema
|
|
349
|
+
const workflowsYaml = `
|
|
350
|
+
version: "1.0"
|
|
351
|
+
funnels:
|
|
352
|
+
- workflowId: nocturnal
|
|
353
|
+
stages:
|
|
354
|
+
- name: dreamer_completed
|
|
355
|
+
eventType: nocturnal_dreamer_completed
|
|
356
|
+
eventCategory: completed
|
|
357
|
+
statsField: evolution.nocturnalDreamerCompleted
|
|
358
|
+
- name: artifact_persisted
|
|
359
|
+
eventType: nocturnal_artifact_persisted
|
|
360
|
+
eventCategory: completed
|
|
361
|
+
statsField: evolution.nocturnalArtifactPersisted
|
|
362
|
+
- workflowId: rulehost
|
|
363
|
+
stages:
|
|
364
|
+
- name: evaluated
|
|
365
|
+
eventType: rulehost_evaluated
|
|
366
|
+
eventCategory: evaluated
|
|
367
|
+
statsField: evolution.rulehostEvaluated
|
|
368
|
+
`;
|
|
369
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
|
|
370
|
+
|
|
371
|
+
// Write daily stats with matching event counts (must be in logs/ subdirectory)
|
|
372
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
373
|
+
writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
|
|
374
|
+
[today]: {
|
|
375
|
+
evolution: {
|
|
376
|
+
nocturnalDreamerCompleted: 3,
|
|
377
|
+
nocturnalArtifactPersisted: 2,
|
|
378
|
+
rulehostEvaluated: 15,
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const result = handleEvolutionStatusCommand({
|
|
384
|
+
config: { workspaceDir: workspace, language: 'en' },
|
|
385
|
+
} as any);
|
|
386
|
+
|
|
387
|
+
expect(result.text).toContain('Workflow Funnel: nocturnal');
|
|
388
|
+
expect(result.text).toMatch(/dreamer_completed: 3/);
|
|
389
|
+
expect(result.text).toMatch(/artifact_persisted: 2/);
|
|
390
|
+
expect(result.text).toContain('Workflow Funnel: rulehost');
|
|
391
|
+
expect(result.text).toMatch(/evaluated: 15/);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it('skips funnel block when workflowFunnels is empty array', () => {
|
|
395
|
+
const workspace = makeTempDir();
|
|
396
|
+
|
|
397
|
+
// Write empty funnels array — valid YAML but no funnels defined
|
|
398
|
+
const workflowsYaml = `version: "1.0"\nfunnels: []`;
|
|
399
|
+
fs.writeFileSync(path.join(workspace, '.state', 'workflows.yaml'), workflowsYaml, 'utf8');
|
|
400
|
+
|
|
401
|
+
const result = handleEvolutionStatusCommand({
|
|
402
|
+
config: { workspaceDir: workspace, language: 'en' },
|
|
403
|
+
} as any);
|
|
404
|
+
|
|
405
|
+
expect(result.text).not.toContain('Workflow Funnel:');
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it('shows degraded status and warning when YAML load has warnings', () => {
|
|
409
|
+
const workspace = makeTempDir();
|
|
410
|
+
const stateDir = path.join(workspace, '.state');
|
|
411
|
+
|
|
412
|
+
// Write malformed YAML — loader emits a parse warning
|
|
413
|
+
const badYaml = `
|
|
414
|
+
version: "1.0"
|
|
415
|
+
funnels:
|
|
416
|
+
- workflowId: nocturnal
|
|
417
|
+
stages:
|
|
418
|
+
- name: "unclosed string
|
|
419
|
+
`;
|
|
420
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), badYaml, 'utf8');
|
|
421
|
+
|
|
422
|
+
const result = handleEvolutionStatusCommand({
|
|
423
|
+
config: { workspaceDir: workspace, language: 'en' },
|
|
424
|
+
} as any);
|
|
425
|
+
|
|
426
|
+
// Should still render — degraded but not crashed
|
|
427
|
+
expect(result.text).toContain('Evolution Status');
|
|
428
|
+
// Loader warning should appear in output
|
|
429
|
+
expect(result.text).toMatch(/YAML load warning|YAML parse error|workflows\.yaml/);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('renders Chinese workflow funnel labels from YAML', () => {
|
|
433
|
+
const workspace = makeTempDir();
|
|
434
|
+
const stateDir = path.join(workspace, '.state');
|
|
435
|
+
|
|
436
|
+
const workflowsYaml = `
|
|
437
|
+
version: "1.0"
|
|
438
|
+
funnels:
|
|
439
|
+
- workflowId: nocturnal
|
|
440
|
+
stages:
|
|
441
|
+
- name: 做梦完成
|
|
442
|
+
eventType: nocturnal_dreamer_completed
|
|
443
|
+
eventCategory: completed
|
|
444
|
+
statsField: evolution.nocturnalDreamerCompleted
|
|
445
|
+
`;
|
|
446
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
|
|
447
|
+
|
|
448
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
449
|
+
writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
|
|
450
|
+
[today]: {
|
|
451
|
+
evolution: { nocturnalDreamerCompleted: 7 },
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
const result = handleEvolutionStatusCommand({
|
|
456
|
+
config: { workspaceDir: workspace, language: 'zh' },
|
|
457
|
+
} as any);
|
|
458
|
+
|
|
459
|
+
expect(result.text).toContain('Workflow 漏斗: nocturnal');
|
|
460
|
+
expect(result.text).toMatch(/做梦完成: 7/);
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
describe('YAML funnel E2E integration tests', () => {
|
|
465
|
+
// TEST-01: Full YAML-driven flow with real WorkflowFunnelLoader
|
|
466
|
+
it('e2e_test_full_yaml_driven_flow', () => {
|
|
467
|
+
const workspace = makeTempDir();
|
|
468
|
+
const stateDir = path.join(workspace, '.state');
|
|
469
|
+
|
|
470
|
+
// Create real WorkflowFunnelLoader (not mocked)
|
|
471
|
+
const loader = new WorkflowFunnelLoader(stateDir);
|
|
472
|
+
loader.watch();
|
|
473
|
+
|
|
474
|
+
// Write valid workflows.yaml
|
|
475
|
+
const workflowsYaml = `
|
|
476
|
+
version: "1.0"
|
|
477
|
+
funnels:
|
|
478
|
+
- workflowId: nocturnal
|
|
479
|
+
stages:
|
|
480
|
+
- name: dreamer_completed
|
|
481
|
+
eventType: nocturnal_dreamer_completed
|
|
482
|
+
eventCategory: completed
|
|
483
|
+
statsField: evolution.nocturnalDreamerCompleted
|
|
484
|
+
- name: artifact_persisted
|
|
485
|
+
eventType: nocturnal_artifact_persisted
|
|
486
|
+
eventCategory: completed
|
|
487
|
+
statsField: evolution.nocturnalArtifactPersisted
|
|
488
|
+
- workflowId: rulehost
|
|
489
|
+
stages:
|
|
490
|
+
- name: evaluated
|
|
491
|
+
eventType: rulehost_evaluated
|
|
492
|
+
eventCategory: evaluated
|
|
493
|
+
statsField: evolution.rulehostEvaluated
|
|
494
|
+
`;
|
|
495
|
+
fs.writeFileSync(path.join(stateDir, 'workflows.yaml'), workflowsYaml, 'utf8');
|
|
496
|
+
loader.load(); // reload after writing file
|
|
497
|
+
|
|
498
|
+
// Write daily-stats.json
|
|
499
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
500
|
+
writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
|
|
501
|
+
[today]: {
|
|
502
|
+
evolution: {
|
|
503
|
+
nocturnalDreamerCompleted: 3,
|
|
504
|
+
nocturnalArtifactPersisted: 2,
|
|
505
|
+
rulehostEvaluated: 15,
|
|
506
|
+
},
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
// Call getSummary directly with real loader data
|
|
511
|
+
const summary = RuntimeSummaryService.getSummary(workspace, {
|
|
512
|
+
funnels: loader.getAllFunnels(),
|
|
513
|
+
loaderWarnings: loader.getWarnings(),
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Assert workflowFunnels structure
|
|
517
|
+
expect(summary.workflowFunnels).toBeDefined();
|
|
518
|
+
expect(summary.workflowFunnels!.length).toBe(2);
|
|
519
|
+
expect(summary.workflowFunnels![0].funnelKey).toBe('nocturnal');
|
|
520
|
+
expect(summary.workflowFunnels![0].stages[0].label).toBe('dreamer_completed');
|
|
521
|
+
expect(summary.workflowFunnels![0].stages[0].count).toBe(3);
|
|
522
|
+
expect(summary.workflowFunnels![0].stages[1].label).toBe('artifact_persisted');
|
|
523
|
+
expect(summary.workflowFunnels![0].stages[1].count).toBe(2);
|
|
524
|
+
expect(summary.workflowFunnels![1].funnelKey).toBe('rulehost');
|
|
525
|
+
expect(summary.workflowFunnels![1].stages[0].label).toBe('evaluated');
|
|
526
|
+
expect(summary.workflowFunnels![1].stages[0].count).toBe(15);
|
|
527
|
+
expect(summary.workflowFunnels![0].funnelLabel).toBe('nocturnal');
|
|
528
|
+
|
|
529
|
+
// DEGRADED-01: valid YAML + valid stats → status ok, no funnel-related warnings
|
|
530
|
+
expect(summary.metadata.status).toBe('ok');
|
|
531
|
+
// Note: warnings array may contain non-funnel warnings (GFI/Daily stats defaults); funnel warnings are checked separately
|
|
532
|
+
const funnelWarnings = summary.metadata.warnings.filter(w => w.includes('statsField') || w.includes('YAML load'));
|
|
533
|
+
expect(funnelWarnings).toHaveLength(0);
|
|
534
|
+
|
|
535
|
+
loader.dispose();
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// TEST-02: Degraded fallback when YAML is missing
|
|
539
|
+
it('e2e_test_degraded_fallback_on_missing_yaml', () => {
|
|
540
|
+
const workspace = makeTempDir();
|
|
541
|
+
const stateDir = path.join(workspace, '.state');
|
|
542
|
+
|
|
543
|
+
// Create real loader pointing at stateDir with NO workflows.yaml
|
|
544
|
+
const loader = new WorkflowFunnelLoader(stateDir);
|
|
545
|
+
loader.watch();
|
|
546
|
+
|
|
547
|
+
// Write daily-stats.json (so the file exists, but no YAML)
|
|
548
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
549
|
+
writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
|
|
550
|
+
[today]: { evolution: {} },
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// Call getSummary — should not crash
|
|
554
|
+
const summary = RuntimeSummaryService.getSummary(workspace, {
|
|
555
|
+
funnels: loader.getAllFunnels(),
|
|
556
|
+
loaderWarnings: loader.getWarnings(),
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
// Assert degraded status
|
|
560
|
+
expect(summary.metadata.status).toBe('degraded');
|
|
561
|
+
// loaderWarnings are prefixed with "YAML load warning: " when propagated to metadata.warnings
|
|
562
|
+
expect(summary.metadata.warnings).toContain('YAML load warning: workflows.yaml file not found.');
|
|
563
|
+
// DEGRADED-02: funnels absent/empty when YAML missing — empty array is acceptable, just not rendered
|
|
564
|
+
expect(summary.workflowFunnels == null || summary.workflowFunnels.length === 0).toBe(true);
|
|
565
|
+
|
|
566
|
+
loader.dispose();
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// TEST-03: Hot-reload — YAML changes reflected after loader.load()
|
|
570
|
+
it('e2e_test_hot_reload_reflects_yaml_changes', () => {
|
|
571
|
+
const workspace = makeTempDir();
|
|
572
|
+
const stateDir = path.join(workspace, '.state');
|
|
573
|
+
|
|
574
|
+
const loader = new WorkflowFunnelLoader(stateDir);
|
|
575
|
+
loader.watch();
|
|
576
|
+
|
|
577
|
+
// Write initial workflows.yaml with original_label
|
|
578
|
+
const workflowsYamlV1 = `
|
|
579
|
+
version: "1.0"
|
|
580
|
+
funnels:
|
|
581
|
+
- workflowId: nocturnal
|
|
582
|
+
stages:
|
|
583
|
+
- name: original_label
|
|
584
|
+
eventType: nocturnal_dreamer_completed
|
|
585
|
+
eventCategory: completed
|
|
586
|
+
statsField: evolution.nocturnalDreamerCompleted
|
|
587
|
+
`;
|
|
588
|
+
const yamlPath = path.join(stateDir, 'workflows.yaml');
|
|
589
|
+
fs.writeFileSync(yamlPath, workflowsYamlV1, 'utf8');
|
|
590
|
+
loader.load();
|
|
591
|
+
|
|
592
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
593
|
+
writeJson(path.join(stateDir, 'logs', 'daily-stats.json'), {
|
|
594
|
+
[today]: { evolution: { nocturnalDreamerCompleted: 5 } },
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// First call — should show original_label
|
|
598
|
+
const summary1 = RuntimeSummaryService.getSummary(workspace, {
|
|
599
|
+
funnels: loader.getAllFunnels(),
|
|
600
|
+
loaderWarnings: loader.getWarnings(),
|
|
601
|
+
});
|
|
602
|
+
expect(summary1.workflowFunnels![0].stages[0].label).toBe('original_label');
|
|
603
|
+
expect(summary1.workflowFunnels![0].stages[0].count).toBe(5);
|
|
604
|
+
|
|
605
|
+
// Modify workflows.yaml to use modified_label
|
|
606
|
+
const workflowsYamlV2 = `
|
|
607
|
+
version: "1.0"
|
|
608
|
+
funnels:
|
|
609
|
+
- workflowId: nocturnal
|
|
610
|
+
stages:
|
|
611
|
+
- name: modified_label
|
|
612
|
+
eventType: nocturnal_dreamer_completed
|
|
613
|
+
eventCategory: completed
|
|
614
|
+
statsField: evolution.nocturnalDreamerCompleted
|
|
615
|
+
`;
|
|
616
|
+
fs.writeFileSync(yamlPath, workflowsYamlV2, 'utf8');
|
|
617
|
+
loader.load(); // trigger hot-reload manually
|
|
618
|
+
|
|
619
|
+
// Second call — should show modified_label
|
|
620
|
+
const summary2 = RuntimeSummaryService.getSummary(workspace, {
|
|
621
|
+
funnels: loader.getAllFunnels(),
|
|
622
|
+
loaderWarnings: loader.getWarnings(),
|
|
623
|
+
});
|
|
624
|
+
expect(summary2.workflowFunnels![0].stages[0].label).toBe('modified_label');
|
|
625
|
+
expect(summary2.workflowFunnels![0].stages[0].count).toBe(5);
|
|
626
|
+
|
|
627
|
+
loader.dispose();
|
|
628
|
+
});
|
|
341
629
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
2
|
import { EventLogService, EventLog } from '../../src/core/event-log.js';
|
|
3
|
-
import type { DailyStats,
|
|
3
|
+
import type { DailyStats, DiagnosticianReportEventData } from '../../src/types/event-types.js';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import * as os from 'os';
|
|
@@ -19,32 +19,6 @@ describe('EventLog', () => {
|
|
|
19
19
|
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
describe('recordDeepReflection', () => {
|
|
23
|
-
it('should record deep reflection event', () => {
|
|
24
|
-
const data: DeepReflectionEventData = {
|
|
25
|
-
modelId: 'claude-sonnet-4',
|
|
26
|
-
modelSelectionMode: 'auto',
|
|
27
|
-
confidenceScore: 0.85,
|
|
28
|
-
insightsGenerated: 3,
|
|
29
|
-
durationMs: 1500,
|
|
30
|
-
passed: true
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
eventLog.recordDeepReflection('session-1', data);
|
|
34
|
-
eventLog.flush();
|
|
35
|
-
|
|
36
|
-
const today = new Date().toISOString().slice(0, 10);
|
|
37
|
-
const eventsFile = path.join(tempDir, 'logs', `events_${today}.jsonl`);
|
|
38
|
-
const content = fs.readFileSync(eventsFile, 'utf-8');
|
|
39
|
-
const event = JSON.parse(content.trim());
|
|
40
|
-
|
|
41
|
-
expect(event.type).toBe('deep_reflection');
|
|
42
|
-
expect(event.category).toBe('passed');
|
|
43
|
-
expect(event.data.modelId).toBe('claude-sonnet-4');
|
|
44
|
-
expect(event.data.modelSelectionMode).toBe('auto');
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
|
|
48
22
|
describe('DailyStats', () => {
|
|
49
23
|
it('should aggregate tool call statistics correctly', () => {
|
|
50
24
|
// Record multiple tool calls
|
|
@@ -97,8 +71,6 @@ describe('EventLog', () => {
|
|
|
97
71
|
// Hooks field
|
|
98
72
|
expect(stats.hooks).toBeDefined();
|
|
99
73
|
|
|
100
|
-
// Deep Reflection field
|
|
101
|
-
expect(stats.deepReflection).toBeDefined();
|
|
102
74
|
});
|
|
103
75
|
|
|
104
76
|
it('should increment tools.failure on error', () => {
|
|
@@ -678,7 +678,7 @@ describe('RuntimeSummaryService', () => {
|
|
|
678
678
|
last_updated: '2026-03-20T10:00:00Z',
|
|
679
679
|
});
|
|
680
680
|
writeJson(path.join(workspace, '.state', 'logs', 'daily-stats.json'), {
|
|
681
|
-
|
|
681
|
+
[new Date().toISOString().slice(0, 10)]: {
|
|
682
682
|
toolCalls: 120,
|
|
683
683
|
painSignals: 15,
|
|
684
684
|
evolutionTasks: 5,
|