principles-disciple 1.5.4 → 1.7.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/dist/commands/context.d.ts +5 -0
- package/dist/commands/context.js +312 -0
- package/dist/commands/evolution-status.d.ts +4 -0
- package/dist/commands/evolution-status.js +138 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +45 -0
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +582 -0
- package/dist/commands/pain.js +143 -6
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/rollback.d.ts +19 -0
- package/dist/commands/rollback.js +119 -0
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/core/config.d.ts +37 -0
- package/dist/core/config.js +47 -0
- package/dist/core/control-ui-db.d.ts +68 -0
- package/dist/core/control-ui-db.js +274 -0
- package/dist/core/detection-funnel.d.ts +1 -1
- package/dist/core/detection-funnel.js +4 -0
- package/dist/core/dictionary.d.ts +2 -0
- package/dist/core/dictionary.js +13 -0
- package/dist/core/event-log.d.ts +22 -1
- package/dist/core/event-log.js +319 -0
- package/dist/core/evolution-engine.d.ts +5 -5
- package/dist/core/evolution-engine.js +18 -18
- package/dist/core/evolution-migration.d.ts +5 -0
- package/dist/core/evolution-migration.js +65 -0
- package/dist/core/evolution-reducer.d.ts +69 -0
- package/dist/core/evolution-reducer.js +369 -0
- package/dist/core/evolution-types.d.ts +103 -0
- package/dist/core/focus-history.d.ts +65 -0
- package/dist/core/focus-history.js +266 -0
- package/dist/core/init.js +30 -7
- package/dist/core/migration.js +0 -2
- package/dist/core/path-resolver.d.ts +3 -0
- package/dist/core/path-resolver.js +90 -31
- package/dist/core/paths.d.ts +7 -8
- package/dist/core/paths.js +48 -40
- package/dist/core/profile.js +1 -1
- package/dist/core/session-tracker.d.ts +4 -0
- package/dist/core/session-tracker.js +15 -0
- package/dist/core/thinking-models.d.ts +38 -0
- package/dist/core/thinking-models.js +170 -0
- package/dist/core/trajectory.d.ts +184 -0
- package/dist/core/trajectory.js +817 -0
- package/dist/core/trust-engine.d.ts +2 -0
- package/dist/core/trust-engine.js +30 -4
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +301 -30
- package/dist/hooks/llm.d.ts +8 -0
- package/dist/hooks/llm.js +347 -69
- package/dist/hooks/message-sanitize.d.ts +3 -0
- package/dist/hooks/message-sanitize.js +37 -0
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +20 -11
- package/dist/hooks/prompt.js +558 -158
- package/dist/hooks/subagent.d.ts +9 -2
- package/dist/hooks/subagent.js +40 -3
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +48 -20
- package/dist/index.js +264 -8
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/empathy-observer-manager.d.ts +42 -0
- package/dist/service/empathy-observer-manager.js +147 -0
- package/dist/service/evolution-worker.d.ts +10 -0
- package/dist/service/evolution-worker.js +156 -24
- package/dist/service/trajectory-service.d.ts +2 -0
- package/dist/service/trajectory-service.js +15 -0
- package/dist/tools/agent-spawn.d.ts +27 -6
- package/dist/tools/agent-spawn.js +339 -87
- package/dist/tools/deep-reflect.d.ts +27 -7
- package/dist/tools/deep-reflect.js +282 -113
- package/dist/types/event-types.d.ts +84 -2
- package/dist/types/event-types.js +33 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +24 -1
- package/openclaw.plugin.json +43 -11
- package/package.json +16 -6
- package/templates/langs/zh/core/HEARTBEAT.md +28 -4
- package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
- package/templates/pain_settings.json +54 -2
- package/templates/workspace/.principles/PROFILE.json +2 -0
- package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
package/dist/core/event-log.js
CHANGED
|
@@ -44,6 +44,9 @@ export class EventLog {
|
|
|
44
44
|
recordGateBlock(sessionId, data) {
|
|
45
45
|
this.record('gate_block', 'blocked', sessionId, data);
|
|
46
46
|
}
|
|
47
|
+
recordGateBypass(sessionId, data) {
|
|
48
|
+
this.record('gate_bypass', 'bypassed', sessionId, data);
|
|
49
|
+
}
|
|
47
50
|
recordPlanApproval(sessionId, data) {
|
|
48
51
|
this.record('plan_approval', 'approved', sessionId, data);
|
|
49
52
|
}
|
|
@@ -57,6 +60,9 @@ export class EventLog {
|
|
|
57
60
|
recordTrustChange(sessionId, data) {
|
|
58
61
|
this.record('trust_change', 'changed', sessionId, data);
|
|
59
62
|
}
|
|
63
|
+
recordEmpathyRollback(sessionId, data) {
|
|
64
|
+
this.record('empathy_rollback', 'rolled_back', sessionId, data);
|
|
65
|
+
}
|
|
60
66
|
recordError(sessionId, message, context) {
|
|
61
67
|
this.record('error', 'failure', sessionId, { message, ...context });
|
|
62
68
|
}
|
|
@@ -115,6 +121,45 @@ export class EventLog {
|
|
|
115
121
|
const data = entry.data;
|
|
116
122
|
stats.pain.signalsDetected++;
|
|
117
123
|
stats.pain.maxScore = Math.max(stats.pain.maxScore, data.score);
|
|
124
|
+
// Update empathy stats for user_empathy source
|
|
125
|
+
if (data.source === 'user_empathy') {
|
|
126
|
+
if (data.deduped) {
|
|
127
|
+
stats.empathy.dedupedCount++;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
stats.empathy.totalEvents++;
|
|
131
|
+
stats.empathy.totalPenaltyScore += data.score || 0;
|
|
132
|
+
// By severity
|
|
133
|
+
if (data.severity) {
|
|
134
|
+
stats.empathy.bySeverity[data.severity]++;
|
|
135
|
+
stats.empathy.scoreBySeverity[data.severity] += data.score || 0;
|
|
136
|
+
}
|
|
137
|
+
// By detection mode
|
|
138
|
+
if (data.detection_mode) {
|
|
139
|
+
stats.empathy.byDetectionMode[data.detection_mode]++;
|
|
140
|
+
}
|
|
141
|
+
// By origin
|
|
142
|
+
if (data.origin) {
|
|
143
|
+
stats.empathy.byOrigin[data.origin]++;
|
|
144
|
+
}
|
|
145
|
+
// Confidence distribution
|
|
146
|
+
const conf = data.confidence ?? 1;
|
|
147
|
+
if (conf >= 0.8)
|
|
148
|
+
stats.empathy.confidenceDistribution.high++;
|
|
149
|
+
else if (conf >= 0.5)
|
|
150
|
+
stats.empathy.confidenceDistribution.medium++;
|
|
151
|
+
else
|
|
152
|
+
stats.empathy.confidenceDistribution.low++;
|
|
153
|
+
}
|
|
154
|
+
// Update dedupe hit rate
|
|
155
|
+
const total = stats.empathy.totalEvents + stats.empathy.dedupedCount;
|
|
156
|
+
stats.empathy.dedupeHitRate = total > 0 ? stats.empathy.dedupedCount / total : 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else if (entry.type === 'empathy_rollback') {
|
|
160
|
+
const data = entry.data;
|
|
161
|
+
stats.empathy.rollbackCount++;
|
|
162
|
+
stats.empathy.rolledBackScore += data.originalScore || 0;
|
|
118
163
|
}
|
|
119
164
|
}
|
|
120
165
|
startFlushTimer() {
|
|
@@ -164,6 +209,280 @@ export class EventLog {
|
|
|
164
209
|
}
|
|
165
210
|
return stats;
|
|
166
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Get aggregated empathy statistics for multiple time ranges.
|
|
214
|
+
* @param range 'today' | 'week' | 'session'
|
|
215
|
+
* @param sessionId Optional session ID for session-scoped stats
|
|
216
|
+
*/
|
|
217
|
+
getEmpathyStats(range, sessionId) {
|
|
218
|
+
const now = new Date();
|
|
219
|
+
const today = this.formatDate(now);
|
|
220
|
+
// Aggregate stats based on range
|
|
221
|
+
const result = {
|
|
222
|
+
totalEvents: 0,
|
|
223
|
+
dedupedCount: 0,
|
|
224
|
+
dedupeHitRate: 0,
|
|
225
|
+
totalPenaltyScore: 0,
|
|
226
|
+
rolledBackScore: 0,
|
|
227
|
+
rollbackCount: 0,
|
|
228
|
+
bySeverity: { mild: 0, moderate: 0, severe: 0 },
|
|
229
|
+
scoreBySeverity: { mild: 0, moderate: 0, severe: 0 },
|
|
230
|
+
byDetectionMode: { structured: 0, legacy_tag: 0 },
|
|
231
|
+
byOrigin: { assistant_self_report: 0, user_manual: 0, system_infer: 0 },
|
|
232
|
+
confidenceDistribution: { high: 0, medium: 0, low: 0 },
|
|
233
|
+
dailyTrend: [],
|
|
234
|
+
};
|
|
235
|
+
if (range === 'session' && sessionId) {
|
|
236
|
+
// For session range, scan event buffer and events file
|
|
237
|
+
this.aggregateSessionEmpathy(sessionId, result);
|
|
238
|
+
}
|
|
239
|
+
else if (range === 'week') {
|
|
240
|
+
// For week range, aggregate last 7 days
|
|
241
|
+
for (let i = 0; i < 7; i++) {
|
|
242
|
+
const date = new Date(now);
|
|
243
|
+
date.setDate(date.getDate() - i);
|
|
244
|
+
const dateStr = this.formatDate(date);
|
|
245
|
+
const stats = this.getDailyStats(dateStr);
|
|
246
|
+
result.totalEvents += stats.empathy.totalEvents;
|
|
247
|
+
result.dedupedCount += stats.empathy.dedupedCount;
|
|
248
|
+
result.totalPenaltyScore += stats.empathy.totalPenaltyScore;
|
|
249
|
+
result.rolledBackScore += stats.empathy.rolledBackScore;
|
|
250
|
+
result.rollbackCount += stats.empathy.rollbackCount;
|
|
251
|
+
for (const sev of ['mild', 'moderate', 'severe']) {
|
|
252
|
+
result.bySeverity[sev] += stats.empathy.bySeverity[sev];
|
|
253
|
+
result.scoreBySeverity[sev] += stats.empathy.scoreBySeverity[sev];
|
|
254
|
+
}
|
|
255
|
+
result.byDetectionMode.structured += stats.empathy.byDetectionMode.structured;
|
|
256
|
+
result.byDetectionMode.legacy_tag += stats.empathy.byDetectionMode.legacy_tag;
|
|
257
|
+
for (const org of ['assistant_self_report', 'user_manual', 'system_infer']) {
|
|
258
|
+
result.byOrigin[org] += stats.empathy.byOrigin[org];
|
|
259
|
+
}
|
|
260
|
+
result.confidenceDistribution.high += stats.empathy.confidenceDistribution.high;
|
|
261
|
+
result.confidenceDistribution.medium += stats.empathy.confidenceDistribution.medium;
|
|
262
|
+
result.confidenceDistribution.low += stats.empathy.confidenceDistribution.low;
|
|
263
|
+
if (stats.empathy.totalEvents > 0 || stats.empathy.dedupedCount > 0) {
|
|
264
|
+
result.dailyTrend.push({
|
|
265
|
+
date: dateStr,
|
|
266
|
+
count: stats.empathy.totalEvents,
|
|
267
|
+
score: stats.empathy.totalPenaltyScore,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
// Today only
|
|
274
|
+
const stats = this.getDailyStats(today);
|
|
275
|
+
Object.assign(result, stats.empathy);
|
|
276
|
+
if (stats.empathy.totalEvents > 0 || stats.empathy.dedupedCount > 0) {
|
|
277
|
+
result.dailyTrend = [{
|
|
278
|
+
date: today,
|
|
279
|
+
count: stats.empathy.totalEvents,
|
|
280
|
+
score: stats.empathy.totalPenaltyScore,
|
|
281
|
+
}];
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Calculate dedupe hit rate
|
|
285
|
+
const total = result.totalEvents + result.dedupedCount;
|
|
286
|
+
result.dedupeHitRate = total > 0 ? result.dedupedCount / total : 0;
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Aggregate empathy stats for a specific session.
|
|
291
|
+
*/
|
|
292
|
+
aggregateSessionEmpathy(sessionId, result) {
|
|
293
|
+
// Check event buffer first
|
|
294
|
+
for (const entry of this.eventBuffer) {
|
|
295
|
+
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
296
|
+
const data = entry.data;
|
|
297
|
+
if (data.source === 'user_empathy') {
|
|
298
|
+
if (data.deduped) {
|
|
299
|
+
result.dedupedCount++;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
result.totalEvents++;
|
|
303
|
+
result.totalPenaltyScore += data.score || 0;
|
|
304
|
+
if (data.severity) {
|
|
305
|
+
result.bySeverity[data.severity]++;
|
|
306
|
+
result.scoreBySeverity[data.severity] += data.score || 0;
|
|
307
|
+
}
|
|
308
|
+
if (data.detection_mode)
|
|
309
|
+
result.byDetectionMode[data.detection_mode]++;
|
|
310
|
+
if (data.origin)
|
|
311
|
+
result.byOrigin[data.origin]++;
|
|
312
|
+
const conf = data.confidence ?? 1;
|
|
313
|
+
if (conf >= 0.8)
|
|
314
|
+
result.confidenceDistribution.high++;
|
|
315
|
+
else if (conf >= 0.5)
|
|
316
|
+
result.confidenceDistribution.medium++;
|
|
317
|
+
else
|
|
318
|
+
result.confidenceDistribution.low++;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else if (entry.sessionId === sessionId && entry.type === 'empathy_rollback') {
|
|
323
|
+
const data = entry.data;
|
|
324
|
+
result.rollbackCount++;
|
|
325
|
+
result.rolledBackScore += data.originalScore || 0;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Also check events file for persisted events
|
|
329
|
+
if (fs.existsSync(this.eventsFile)) {
|
|
330
|
+
try {
|
|
331
|
+
const content = fs.readFileSync(this.eventsFile, 'utf-8');
|
|
332
|
+
const lines = content.trim().split('\n');
|
|
333
|
+
for (const line of lines) {
|
|
334
|
+
if (!line.trim())
|
|
335
|
+
continue;
|
|
336
|
+
try {
|
|
337
|
+
const entry = JSON.parse(line);
|
|
338
|
+
if (entry.sessionId === sessionId) {
|
|
339
|
+
if (entry.type === 'pain_signal') {
|
|
340
|
+
const data = entry.data;
|
|
341
|
+
if (data.source === 'user_empathy') {
|
|
342
|
+
if (data.deduped) {
|
|
343
|
+
result.dedupedCount++;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
result.totalEvents++;
|
|
347
|
+
result.totalPenaltyScore += data.score || 0;
|
|
348
|
+
if (data.severity) {
|
|
349
|
+
result.bySeverity[data.severity]++;
|
|
350
|
+
result.scoreBySeverity[data.severity] += data.score || 0;
|
|
351
|
+
}
|
|
352
|
+
if (data.detection_mode)
|
|
353
|
+
result.byDetectionMode[data.detection_mode]++;
|
|
354
|
+
if (data.origin)
|
|
355
|
+
result.byOrigin[data.origin]++;
|
|
356
|
+
const conf = data.confidence ?? 1;
|
|
357
|
+
if (conf >= 0.8)
|
|
358
|
+
result.confidenceDistribution.high++;
|
|
359
|
+
else if (conf >= 0.5)
|
|
360
|
+
result.confidenceDistribution.medium++;
|
|
361
|
+
else
|
|
362
|
+
result.confidenceDistribution.low++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
else if (entry.type === 'empathy_rollback') {
|
|
367
|
+
const data = entry.data;
|
|
368
|
+
result.rollbackCount++;
|
|
369
|
+
result.rolledBackScore += data.originalScore || 0;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
// Skip malformed lines
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
catch (e) {
|
|
379
|
+
if (this.logger)
|
|
380
|
+
this.logger.error(`[PD] Failed to read events.jsonl: ${String(e)}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Rollback an empathy event by ID.
|
|
386
|
+
* Returns the rolled back score, or 0 if event not found.
|
|
387
|
+
*/
|
|
388
|
+
rollbackEmpathyEvent(eventId, sessionId, reason, triggeredBy) {
|
|
389
|
+
// Search for the event in buffer and file
|
|
390
|
+
let foundEvent = null;
|
|
391
|
+
// Check buffer first
|
|
392
|
+
for (const entry of this.eventBuffer) {
|
|
393
|
+
if (entry.type === 'pain_signal') {
|
|
394
|
+
const data = entry.data;
|
|
395
|
+
if (entry.data.eventId === eventId && data.source === 'user_empathy') {
|
|
396
|
+
foundEvent = { entry, data };
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// If not in buffer, check file
|
|
402
|
+
if (!foundEvent && fs.existsSync(this.eventsFile)) {
|
|
403
|
+
try {
|
|
404
|
+
const content = fs.readFileSync(this.eventsFile, 'utf-8');
|
|
405
|
+
const lines = content.trim().split('\n');
|
|
406
|
+
for (const line of lines) {
|
|
407
|
+
if (!line.trim())
|
|
408
|
+
continue;
|
|
409
|
+
try {
|
|
410
|
+
const entry = JSON.parse(line);
|
|
411
|
+
if (entry.type === 'pain_signal') {
|
|
412
|
+
const data = entry.data;
|
|
413
|
+
if (entry.data.eventId === eventId && data.source === 'user_empathy') {
|
|
414
|
+
foundEvent = { entry, data };
|
|
415
|
+
break;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
// Skip malformed lines
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (e) {
|
|
425
|
+
if (this.logger)
|
|
426
|
+
this.logger.error(`[PD] Failed to read events.jsonl: ${String(e)}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
if (!foundEvent || foundEvent.data.deduped) {
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
const originalScore = foundEvent.data.score || 0;
|
|
433
|
+
// Record the rollback event
|
|
434
|
+
this.recordEmpathyRollback(sessionId, {
|
|
435
|
+
eventId,
|
|
436
|
+
originalScore,
|
|
437
|
+
originalSessionId: foundEvent.entry.sessionId,
|
|
438
|
+
reason,
|
|
439
|
+
triggeredBy,
|
|
440
|
+
});
|
|
441
|
+
return originalScore;
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Get the last empathy event ID for a session (for rollback).
|
|
445
|
+
*/
|
|
446
|
+
getLastEmpathyEventId(sessionId) {
|
|
447
|
+
// Check buffer in reverse
|
|
448
|
+
for (let i = this.eventBuffer.length - 1; i >= 0; i--) {
|
|
449
|
+
const entry = this.eventBuffer[i];
|
|
450
|
+
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
451
|
+
const data = entry.data;
|
|
452
|
+
if (data.source === 'user_empathy' && !data.deduped) {
|
|
453
|
+
return entry.data.eventId || null;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
// Check file
|
|
458
|
+
if (fs.existsSync(this.eventsFile)) {
|
|
459
|
+
try {
|
|
460
|
+
const content = fs.readFileSync(this.eventsFile, 'utf-8');
|
|
461
|
+
const lines = content.trim().split('\n');
|
|
462
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
463
|
+
if (!lines[i].trim())
|
|
464
|
+
continue;
|
|
465
|
+
try {
|
|
466
|
+
const entry = JSON.parse(lines[i]);
|
|
467
|
+
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
468
|
+
const data = entry.data;
|
|
469
|
+
if (data.source === 'user_empathy' && !data.deduped) {
|
|
470
|
+
return entry.data.eventId || null;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// Skip malformed lines
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
catch (e) {
|
|
480
|
+
if (this.logger)
|
|
481
|
+
this.logger.error(`[PD] Failed to read events.jsonl: ${String(e)}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return null;
|
|
485
|
+
}
|
|
167
486
|
/**
|
|
168
487
|
* Dispose of the EventLog, flushing pending data and clearing timer.
|
|
169
488
|
*/
|
|
@@ -81,13 +81,13 @@ export declare class EvolutionEngine {
|
|
|
81
81
|
private isLockStale;
|
|
82
82
|
/** 持久化评分卡(含锁保护) */
|
|
83
83
|
private saveScorecard;
|
|
84
|
-
/**
|
|
85
|
-
private
|
|
86
|
-
private
|
|
84
|
+
/** Per-instance retry queue (P0 fix: was static, causing cross-instance race) */
|
|
85
|
+
private retryQueue;
|
|
86
|
+
private retryTimer;
|
|
87
87
|
/** 调度重试保存 */
|
|
88
|
-
private
|
|
88
|
+
private scheduleRetrySave;
|
|
89
89
|
/** 处理重试队列 */
|
|
90
|
-
private
|
|
90
|
+
private processRetryQueue;
|
|
91
91
|
/** 无锁快速保存(用于重试) */
|
|
92
92
|
private saveScorecardImmediate;
|
|
93
93
|
private generateId;
|
|
@@ -30,10 +30,10 @@ const EXPLORATORY_TOOLS = new Set([
|
|
|
30
30
|
const CONSTRUCTIVE_TOOLS = new Set([
|
|
31
31
|
'write', 'write_file', 'edit', 'edit_file', 'replace', 'apply_patch',
|
|
32
32
|
'insert', 'patch', 'delete_file', 'move_file', 'run_shell_command',
|
|
33
|
-
'
|
|
33
|
+
'pd_run_worker', 'sessions_spawn',
|
|
34
34
|
]);
|
|
35
35
|
// 高风险工具:需要 allowRiskPath 权限
|
|
36
|
-
// 注意:
|
|
36
|
+
// 注意:pd_run_worker 和 sessions_spawn 已从高风险中移出,它们由 allowSubagentSpawn 单独控制
|
|
37
37
|
const HIGH_RISK_TOOLS = new Set([
|
|
38
38
|
'run_shell_command', 'delete_file', 'move_file',
|
|
39
39
|
]);
|
|
@@ -202,7 +202,7 @@ export class EvolutionEngine {
|
|
|
202
202
|
};
|
|
203
203
|
}
|
|
204
204
|
// 子智能体检查
|
|
205
|
-
if ((context.toolName === '
|
|
205
|
+
if ((context.toolName === 'pd_run_worker' || context.toolName === 'sessions_spawn') && !perms.allowSubagentSpawn) {
|
|
206
206
|
return {
|
|
207
207
|
allowed: false,
|
|
208
208
|
reason: `Tier ${this.scorecard.currentTier} (${tierDef.name}) 未解锁子智能体权限`,
|
|
@@ -464,29 +464,29 @@ export class EvolutionEngine {
|
|
|
464
464
|
release();
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
|
-
/**
|
|
468
|
-
|
|
469
|
-
|
|
467
|
+
/** Per-instance retry queue (P0 fix: was static, causing cross-instance race) */
|
|
468
|
+
retryQueue = [];
|
|
469
|
+
retryTimer = null;
|
|
470
470
|
/** 调度重试保存 */
|
|
471
|
-
|
|
471
|
+
scheduleRetrySave() {
|
|
472
472
|
// 每个引擎只保留最新数据
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
//
|
|
476
|
-
if (!
|
|
477
|
-
|
|
478
|
-
|
|
473
|
+
this.retryQueue = this.retryQueue.filter(item => item.engine !== this);
|
|
474
|
+
this.retryQueue.push({ engine: this, data: { ...this.scorecard } });
|
|
475
|
+
// 启动重试定时器(每个实例独立)
|
|
476
|
+
if (!this.retryTimer) {
|
|
477
|
+
this.retryTimer = setTimeout(() => {
|
|
478
|
+
this.processRetryQueue();
|
|
479
479
|
}, 1000);
|
|
480
480
|
}
|
|
481
481
|
}
|
|
482
482
|
/** 处理重试队列 */
|
|
483
|
-
|
|
484
|
-
|
|
483
|
+
processRetryQueue() {
|
|
484
|
+
this.retryTimer = null;
|
|
485
485
|
const latestByEngine = new Map();
|
|
486
|
-
for (const item of
|
|
486
|
+
for (const item of this.retryQueue) {
|
|
487
487
|
latestByEngine.set(item.engine, item.data);
|
|
488
488
|
}
|
|
489
|
-
|
|
489
|
+
this.retryQueue = [];
|
|
490
490
|
for (const [engine, data] of latestByEngine) {
|
|
491
491
|
try {
|
|
492
492
|
engine.saveScorecardImmediate(data);
|
|
@@ -494,7 +494,7 @@ export class EvolutionEngine {
|
|
|
494
494
|
}
|
|
495
495
|
catch (e) {
|
|
496
496
|
console.error(`[Evolution] Retry save failed: ${String(e)}`);
|
|
497
|
-
|
|
497
|
+
engine.scheduleRetrySave(); // 每个引擎独立重试
|
|
498
498
|
}
|
|
499
499
|
}
|
|
500
500
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { stableContentHash } from './evolution-reducer.js';
|
|
4
|
+
import { SystemLogger } from './system-logger.js';
|
|
5
|
+
function appendEvent(streamPath, event) {
|
|
6
|
+
fs.appendFileSync(streamPath, `${JSON.stringify(event)}\n`, 'utf8');
|
|
7
|
+
}
|
|
8
|
+
function loadImportedHashes(streamPath, workspaceDir) {
|
|
9
|
+
if (!fs.existsSync(streamPath))
|
|
10
|
+
return new Set();
|
|
11
|
+
const raw = fs.readFileSync(streamPath, 'utf8').trim();
|
|
12
|
+
if (!raw)
|
|
13
|
+
return new Set();
|
|
14
|
+
const hashes = new Set();
|
|
15
|
+
for (const line of raw.split('\n')) {
|
|
16
|
+
try {
|
|
17
|
+
const event = JSON.parse(line);
|
|
18
|
+
if (event.type !== 'legacy_import')
|
|
19
|
+
continue;
|
|
20
|
+
const hash = event.data.contentHash;
|
|
21
|
+
if (typeof hash === 'string')
|
|
22
|
+
hashes.add(hash);
|
|
23
|
+
}
|
|
24
|
+
catch (e) {
|
|
25
|
+
SystemLogger.log(workspaceDir, 'MIGRATION_WARN', `skip malformed line: ${String(e)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return hashes;
|
|
29
|
+
}
|
|
30
|
+
export function migrateLegacyEvolutionData(workspaceDir) {
|
|
31
|
+
const streamPath = path.join(workspaceDir, 'memory', 'evolution.jsonl');
|
|
32
|
+
fs.mkdirSync(path.dirname(streamPath), { recursive: true });
|
|
33
|
+
const candidates = [
|
|
34
|
+
path.join(workspaceDir, 'memory', 'ISSUE_LOG.md'),
|
|
35
|
+
path.join(workspaceDir, 'memory', 'DECISIONS.md'),
|
|
36
|
+
path.join(workspaceDir, '.principles', 'PRINCIPLES.md'),
|
|
37
|
+
];
|
|
38
|
+
const existingHashes = loadImportedHashes(streamPath, workspaceDir);
|
|
39
|
+
let importedEvents = 0;
|
|
40
|
+
for (const sourceFile of candidates) {
|
|
41
|
+
if (!fs.existsSync(sourceFile)) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const content = fs.readFileSync(sourceFile, 'utf8').trim();
|
|
45
|
+
if (!content) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const contentHash = stableContentHash(`${sourceFile}:${content}`);
|
|
49
|
+
if (existingHashes.has(contentHash)) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
appendEvent(streamPath, {
|
|
53
|
+
ts: new Date().toISOString(),
|
|
54
|
+
type: 'legacy_import',
|
|
55
|
+
data: {
|
|
56
|
+
sourceFile: path.relative(workspaceDir, sourceFile),
|
|
57
|
+
content,
|
|
58
|
+
contentHash,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
importedEvents += 1;
|
|
62
|
+
existingHashes.add(contentHash);
|
|
63
|
+
}
|
|
64
|
+
return { importedEvents, streamPath };
|
|
65
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { EvolutionLoopEvent, Principle } from './evolution-types.js';
|
|
2
|
+
export interface EvolutionReducer {
|
|
3
|
+
emit(event: EvolutionLoopEvent): void;
|
|
4
|
+
emitSync(event: EvolutionLoopEvent): void;
|
|
5
|
+
getEventLog(): EvolutionLoopEvent[];
|
|
6
|
+
getCandidatePrinciples(): Principle[];
|
|
7
|
+
getProbationPrinciples(): Principle[];
|
|
8
|
+
getActivePrinciples(): Principle[];
|
|
9
|
+
getPrincipleById(id: string): Principle | null;
|
|
10
|
+
promote(principleId: string, reason?: string): void;
|
|
11
|
+
deprecate(principleId: string, reason: string): void;
|
|
12
|
+
rollbackPrinciple(principleId: string, reason: string): void;
|
|
13
|
+
recordProbationFeedback(principleId: string, success: boolean): void;
|
|
14
|
+
getStats(): {
|
|
15
|
+
candidateCount: number;
|
|
16
|
+
probationCount: number;
|
|
17
|
+
activeCount: number;
|
|
18
|
+
deprecatedCount: number;
|
|
19
|
+
lastPromotedAt: string | null;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export declare class EvolutionReducerImpl implements EvolutionReducer {
|
|
23
|
+
private readonly streamPath;
|
|
24
|
+
private readonly lockTargetPath;
|
|
25
|
+
private readonly blacklistPath;
|
|
26
|
+
private readonly workspaceDir;
|
|
27
|
+
private readonly memoryEvents;
|
|
28
|
+
private readonly principles;
|
|
29
|
+
private readonly failureStreak;
|
|
30
|
+
private lastPromotedAt;
|
|
31
|
+
private isReplaying;
|
|
32
|
+
constructor(opts: {
|
|
33
|
+
workspaceDir: string;
|
|
34
|
+
});
|
|
35
|
+
emit(event: EvolutionLoopEvent): void;
|
|
36
|
+
emitSync(event: EvolutionLoopEvent): void;
|
|
37
|
+
getEventLog(): EvolutionLoopEvent[];
|
|
38
|
+
getCandidatePrinciples(): Principle[];
|
|
39
|
+
getProbationPrinciples(): Principle[];
|
|
40
|
+
getActivePrinciples(): Principle[];
|
|
41
|
+
getPrincipleById(id: string): Principle | null;
|
|
42
|
+
promote(principleId: string, reason?: string): void;
|
|
43
|
+
deprecate(principleId: string, reason: string): void;
|
|
44
|
+
rollbackPrinciple(principleId: string, reason: string): void;
|
|
45
|
+
recordProbationFeedback(principleId: string, success: boolean): void;
|
|
46
|
+
getStats(): {
|
|
47
|
+
candidateCount: number;
|
|
48
|
+
probationCount: number;
|
|
49
|
+
activeCount: number;
|
|
50
|
+
deprecatedCount: number;
|
|
51
|
+
lastPromotedAt: string | null;
|
|
52
|
+
};
|
|
53
|
+
private ensureDirs;
|
|
54
|
+
private loadFromStream;
|
|
55
|
+
private applyEvent;
|
|
56
|
+
private onCandidateCreated;
|
|
57
|
+
private onPrinciplePromoted;
|
|
58
|
+
private onPrincipleDeprecated;
|
|
59
|
+
private onPrincipleRolledBack;
|
|
60
|
+
private onPainDetected;
|
|
61
|
+
private updateFailureStreakFromPain;
|
|
62
|
+
private nextPrincipleId;
|
|
63
|
+
private getByStatus;
|
|
64
|
+
private sweepExpiredProbation;
|
|
65
|
+
private persistBlacklist;
|
|
66
|
+
private loadBlacklist;
|
|
67
|
+
private isBlacklisted;
|
|
68
|
+
}
|
|
69
|
+
export declare function stableContentHash(input: string): string;
|