principles-disciple 1.32.0 → 1.34.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/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/src/core/correction-cue-learner.ts +203 -0
- package/src/core/correction-types.ts +88 -0
- package/src/core/evolution-logger.ts +3 -3
- package/src/core/init.ts +67 -0
- package/src/service/correction-observer-types.ts +58 -0
- package/src/service/correction-observer-workflow-manager.ts +218 -0
- package/src/service/evolution-worker.ts +172 -146
- package/src/service/nocturnal-service.ts +4 -1
- package/src/service/subagent-workflow/index.ts +14 -0
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +3 -1
- package/tests/service/evolution-worker.nocturnal.test.ts +14 -1
- package/tests/service/evolution-worker.timeout.test.ts +350 -0
- package/tests/commands/implementation-lifecycle.test.ts +0 -362
- package/tests/core/detection-funnel.test.ts +0 -63
- package/tests/core/evolution-e2e.test.ts +0 -58
- package/tests/core/evolution-engine-gate-integration.test.ts +0 -543
- package/tests/core/evolution-engine.test.ts +0 -562
- package/tests/core/evolution-reducer.test.ts +0 -180
- package/tests/core/evolution-user-stories.e2e.test.ts +0 -249
- package/tests/core/local-worker-routing.test.ts +0 -757
- package/tests/core/rule-host.test.ts +0 -389
- package/tests/core/trajectory-correction-pain.test.ts +0 -180
- package/tests/hooks/gate-edit-verification.test.ts +0 -435
- package/tests/hooks/llm.test.ts +0 -308
- package/tests/hooks/progressive-trust-gate.test.ts +0 -277
- package/tests/hooks/prompt.test.ts +0 -1473
- package/tests/index.integration.test.ts +0 -179
- package/tests/index.shadow-routing.integration.test.ts +0 -140
- package/tests/service/evolution-worker.test.ts +0 -462
- package/tests/service/nocturnal-service.test.ts +0 -577
- package/tests/service/nocturnal-workflow-manager.test.ts +0 -441
- package/tests/tools/critique-prompt.test.ts +0 -260
- package/tests/tools/deep-reflect.test.ts +0 -232
- package/tests/tools/model-index.test.ts +0 -246
- 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
|
-
});
|