principles-disciple 1.32.0 → 1.33.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 (35) hide show
  1. package/openclaw.plugin.json +1 -1
  2. package/package.json +1 -1
  3. package/src/core/correction-cue-learner.ts +203 -0
  4. package/src/core/correction-types.ts +88 -0
  5. package/src/core/init.ts +67 -0
  6. package/src/service/correction-observer-types.ts +58 -0
  7. package/src/service/correction-observer-workflow-manager.ts +218 -0
  8. package/src/service/evolution-worker.ts +161 -140
  9. package/src/service/nocturnal-service.ts +4 -1
  10. package/src/service/subagent-workflow/index.ts +14 -0
  11. package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +3 -1
  12. package/tests/service/evolution-worker.nocturnal.test.ts +14 -1
  13. package/tests/commands/implementation-lifecycle.test.ts +0 -362
  14. package/tests/core/detection-funnel.test.ts +0 -63
  15. package/tests/core/evolution-e2e.test.ts +0 -58
  16. package/tests/core/evolution-engine-gate-integration.test.ts +0 -543
  17. package/tests/core/evolution-engine.test.ts +0 -562
  18. package/tests/core/evolution-reducer.test.ts +0 -180
  19. package/tests/core/evolution-user-stories.e2e.test.ts +0 -249
  20. package/tests/core/local-worker-routing.test.ts +0 -757
  21. package/tests/core/rule-host.test.ts +0 -389
  22. package/tests/core/trajectory-correction-pain.test.ts +0 -180
  23. package/tests/hooks/gate-edit-verification.test.ts +0 -435
  24. package/tests/hooks/llm.test.ts +0 -308
  25. package/tests/hooks/progressive-trust-gate.test.ts +0 -277
  26. package/tests/hooks/prompt.test.ts +0 -1473
  27. package/tests/index.integration.test.ts +0 -179
  28. package/tests/index.shadow-routing.integration.test.ts +0 -140
  29. package/tests/service/evolution-worker.test.ts +0 -462
  30. package/tests/service/nocturnal-service.test.ts +0 -577
  31. package/tests/service/nocturnal-workflow-manager.test.ts +0 -441
  32. package/tests/tools/critique-prompt.test.ts +0 -260
  33. package/tests/tools/deep-reflect.test.ts +0 -232
  34. package/tests/tools/model-index.test.ts +0 -246
  35. package/tests/ui/app.test.tsx +0 -114
@@ -1,179 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import plugin from '../src/index';
3
- import * as fs from 'fs';
4
-
5
- vi.mock('fs');
6
-
7
- describe('Plugin Integration', () => {
8
- let mockApi: any;
9
- let registeredHooks: Map<string, Function>;
10
- let registeredServices: any[];
11
- let registeredCommands: any[];
12
- let registeredRoutes: any[];
13
-
14
- beforeEach(() => {
15
- registeredHooks = new Map();
16
- registeredServices = [];
17
- registeredCommands = [];
18
- registeredRoutes = [];
19
-
20
- mockApi = {
21
- logger: {
22
- info: vi.fn(),
23
- warn: vi.fn(),
24
- error: vi.fn(),
25
- debug: vi.fn(),
26
- },
27
- pluginConfig: { language: 'en' },
28
- resolvePath: vi.fn((p: string) => `/resolved/${p}`),
29
- on: vi.fn((hookName: string, handler: Function) => {
30
- registeredHooks.set(hookName, handler);
31
- }),
32
- registerService: vi.fn((service: any) => {
33
- registeredServices.push(service);
34
- }),
35
- registerCommand: vi.fn((command: any) => {
36
- registeredCommands.push(command);
37
- }),
38
- registerHttpRoute: vi.fn((route: any) => {
39
- registeredRoutes.push(route);
40
- }),
41
- registerTool: vi.fn(),
42
- };
43
-
44
- vi.mocked(fs.existsSync).mockReturnValue(false);
45
- vi.mocked(fs.mkdirSync).mockReturnValue(undefined);
46
- vi.mocked(fs.copyFileSync).mockReturnValue(undefined);
47
- });
48
-
49
- afterEach(() => {
50
- vi.resetAllMocks();
51
- });
52
-
53
- describe('register()', () => {
54
- it('should NOT call resolvePath(".") during register', () => {
55
- plugin.register(mockApi);
56
-
57
- // 关键:register 不应该调用 resolvePath('.')
58
- // 因为它会返回进程工作目录,而不是配置的 workspace
59
- expect(mockApi.resolvePath).not.toHaveBeenCalled();
60
- });
61
-
62
- it('should register all required hooks', () => {
63
- plugin.register(mockApi);
64
-
65
- const expectedHooks = [
66
- 'before_prompt_build',
67
- 'before_tool_call',
68
- 'after_tool_call',
69
- 'before_reset',
70
- 'before_compaction',
71
- 'llm_output',
72
- 'subagent_spawning',
73
- 'subagent_ended',
74
- ];
75
-
76
- for (const hook of expectedHooks) {
77
- expect(registeredHooks.has(hook)).toBe(true);
78
- }
79
- });
80
-
81
- it('registers the Principles Console HTTP route', () => {
82
- plugin.register(mockApi);
83
-
84
- expect(registeredRoutes).toHaveLength(1);
85
- expect(registeredRoutes[0]).toEqual(expect.objectContaining({
86
- path: '/plugins/principles',
87
- auth: 'plugin',
88
- match: 'prefix',
89
- handler: expect.any(Function),
90
- }));
91
- });
92
-
93
- it('should use ctx.workspaceDir from hook context, not from register', async () => {
94
- plugin.register(mockApi);
95
-
96
- const handler = registeredHooks.get('before_prompt_build');
97
- expect(handler).toBeDefined();
98
-
99
- // 模拟正确的 workspaceDir 来自 context
100
- const mockCtx = {
101
- workspaceDir: '/correct/workspace/from/config',
102
- sessionKey: 'test-session',
103
- trigger: 'user',
104
- };
105
-
106
- // 调用 handler
107
- await handler({ prompt: 'test', messages: [] }, mockCtx);
108
-
109
- // 验证没有使用 resolvePath 的返回值
110
- expect(mockApi.resolvePath).not.toHaveBeenCalled();
111
- });
112
-
113
- it('should register EvolutionWorker service', () => {
114
- plugin.register(mockApi);
115
-
116
- const serviceIds = registeredServices.map((service) => service.id);
117
- expect(serviceIds).toContain('principles-evolution-worker');
118
- expect(serviceIds).toContain('principles-disciple-trajectory');
119
- });
120
-
121
- it('should register all slash commands', () => {
122
- plugin.register(mockApi);
123
-
124
- const commandNames = registeredCommands.map(c => c.name);
125
- const expectedCommands = [
126
- 'pd-init',
127
- 'pd-okr',
128
- 'pd-evolve',
129
- 'pd-bootstrap',
130
- 'pd-research',
131
- 'pd-thinking',
132
- 'pd-status',
133
- 'pd-daily',
134
- 'pd-grooming',
135
- 'pd-help'
136
- ];
137
- for (const cmd of expectedCommands) {
138
- expect(commandNames).toContain(cmd);
139
- }
140
- });
141
- });
142
-
143
- describe('workspaceDir source verification', () => {
144
- it('before_tool_call should use ctx.workspaceDir', () => {
145
- plugin.register(mockApi);
146
-
147
- const handler = registeredHooks.get('before_tool_call');
148
- const mockCtx = { workspaceDir: '/workspace/from/context' };
149
-
150
- // 调用 handler(不会 block)
151
- handler({ tool: 'write', params: {} }, mockCtx);
152
-
153
- // 关键:不应该调用 resolvePath
154
- expect(mockApi.resolvePath).not.toHaveBeenCalled();
155
- });
156
-
157
- it('after_tool_call should use ctx.workspaceDir', () => {
158
- plugin.register(mockApi);
159
-
160
- const handler = registeredHooks.get('after_tool_call');
161
- const mockCtx = { workspaceDir: '/workspace/from/context' };
162
-
163
- handler({ tool: 'write', result: { success: true } }, mockCtx);
164
-
165
- expect(mockApi.resolvePath).not.toHaveBeenCalled();
166
- });
167
-
168
- it('llm_output should use ctx.workspaceDir', () => {
169
- plugin.register(mockApi);
170
-
171
- const handler = registeredHooks.get('llm_output');
172
- const mockCtx = { workspaceDir: '/workspace/from/context' };
173
-
174
- handler({ assistantTexts: ['test'] }, mockCtx);
175
-
176
- expect(mockApi.resolvePath).not.toHaveBeenCalled();
177
- });
178
- });
179
- });
@@ -1,140 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import * as os from 'os';
5
- import plugin from '../src/index.js';
6
- import {
7
- registerTrainingRun,
8
- startTrainingRun,
9
- completeTrainingRun,
10
- registerCheckpoint,
11
- attachEvalSummary,
12
- markCheckpointDeployable,
13
- } from '../src/core/model-training-registry.js';
14
- import { advancePromotion, DEFAULT_BASELINE_METRICS } from '../src/core/promotion-gate.js';
15
- import { bindCheckpointToWorkerProfile, enableRoutingForProfile } from '../src/core/model-deployment-registry.js';
16
- import { computeShadowStats } from '../src/core/shadow-observation-registry.js';
17
-
18
- function makeTmpDir(): string {
19
- return fs.mkdtempSync(path.join(os.tmpdir(), 'pd-shadow-hook-test-'));
20
- }
21
-
22
- function rmdir(dir: string): void {
23
- try {
24
- if (fs.existsSync(dir)) {
25
- fs.rmSync(dir, { recursive: true, force: true });
26
- }
27
- } catch {
28
- // ignore cleanup errors
29
- }
30
- }
31
-
32
- function setupShadowReadyReaderCheckpoint(stateDir: string): string {
33
- const run = registerTrainingRun(stateDir, {
34
- targetModelFamily: 'qwen2.5-7b-reader',
35
- datasetFingerprint: 'fp-dataset',
36
- exportId: 'export-1',
37
- sampleCount: 10,
38
- configFingerprint: 'fp-config',
39
- });
40
- startTrainingRun(stateDir, run.trainRunId);
41
- completeTrainingRun(stateDir, run.trainRunId);
42
-
43
- const checkpoint = registerCheckpoint(stateDir, {
44
- trainRunId: run.trainRunId,
45
- targetModelFamily: 'qwen2.5-7b-reader',
46
- artifactPath: path.join(stateDir, 'checkpoints', 'ckpt-reader'),
47
- });
48
-
49
- attachEvalSummary(stateDir, checkpoint.checkpointId, {
50
- evalId: 'eval-1',
51
- checkpointId: checkpoint.checkpointId,
52
- benchmarkId: 'bench-1',
53
- targetModelFamily: 'qwen2.5-7b-reader',
54
- mode: 'reduced_prompt',
55
- baselineScore: 0.5,
56
- candidateScore: 0.7,
57
- delta: 0.2,
58
- verdict: 'pass',
59
- });
60
- markCheckpointDeployable(stateDir, checkpoint.checkpointId, true);
61
- advancePromotion(stateDir, {
62
- checkpointId: checkpoint.checkpointId,
63
- targetProfile: 'local-reader',
64
- baselineMetrics: DEFAULT_BASELINE_METRICS,
65
- orchestratorReviewPassed: true,
66
- reviewNote: 'shadow ready for test',
67
- });
68
- bindCheckpointToWorkerProfile(stateDir, 'local-reader', checkpoint.checkpointId);
69
- enableRoutingForProfile(stateDir, 'local-reader');
70
- return checkpoint.checkpointId;
71
- }
72
-
73
- describe('plugin shadow routing integration', () => {
74
- let tmpDir: string;
75
-
76
- beforeEach(() => {
77
- tmpDir = makeTmpDir();
78
- });
79
-
80
- afterEach(() => {
81
- rmdir(tmpDir);
82
- });
83
-
84
- it('records and completes shadow observations from runtime subagent hooks', async () => {
85
- const checkpointId = setupShadowReadyReaderCheckpoint(tmpDir);
86
- const hooks = new Map<string, Function>();
87
-
88
- const api: any = {
89
- logger: { info() {}, warn() {}, error() {}, debug() {} },
90
- pluginConfig: { language: 'en' },
91
- rootDir: tmpDir,
92
- resolvePath: (p: string) => (p === '.' ? tmpDir : path.join(tmpDir, p)),
93
- registerHttpRoute() {},
94
- on(name: string, handler: Function) {
95
- hooks.set(name, handler);
96
- },
97
- registerService() {},
98
- registerCommand() {},
99
- registerTool() {},
100
- };
101
-
102
- plugin.register(api);
103
-
104
- const spawning = hooks.get('subagent_spawning');
105
- const ended = hooks.get('subagent_ended');
106
- expect(spawning).toBeTypeOf('function');
107
- expect(ended).toBeTypeOf('function');
108
-
109
- for (let i = 0; i < 5; i++) {
110
- const sessionKey = `agent:local-reader:test-child-${i}`;
111
- spawning!(
112
- {
113
- childSessionKey: sessionKey,
114
- agentId: 'local-reader',
115
- mode: 'run',
116
- threadRequested: false,
117
- label: 'shadow-test',
118
- },
119
- { workspaceDir: tmpDir }
120
- );
121
-
122
- ended!(
123
- {
124
- targetSessionKey: sessionKey,
125
- targetKind: 'subagent',
126
- reason: 'completed',
127
- outcome: 'ok',
128
- },
129
- { workspaceDir: tmpDir }
130
- );
131
- }
132
-
133
- const stats = computeShadowStats(tmpDir, { checkpointId, windowMs: 7 * 24 * 60 * 60 * 1000 });
134
- expect(stats).not.toBeNull();
135
- expect(stats!.totalCount).toBe(5);
136
- expect(stats!.acceptedCount).toBe(5);
137
- expect(stats!.rejectRate).toBe(0);
138
- expect(stats!.isStatisticallySignificant).toBe(true);
139
- });
140
- });