principles-disciple 1.138.0 → 1.139.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/capabilities.js +3 -2
- package/dist/commands/disable-impl.js +1 -1
- package/dist/commands/focus.js +10 -2
- package/dist/commands/rollback-impl.js +1 -1
- package/dist/commands/thinking-os.js +5 -1
- package/dist/core/correction-cue-learner.js +2 -0
- package/dist/core/event-log.js +17 -10
- package/dist/core/focus-history.js +1 -1
- package/dist/core/hygiene/tracker.js +3 -3
- package/dist/core/principle-compiler/compiler.js +2 -0
- package/dist/core/replay-engine.js +1 -1
- package/dist/core/rule-host.js +3 -1
- package/dist/core/thinking-os-parser.js +13 -4
- package/dist/service/evolution-worker.js +7 -3
- package/dist/service/runtime-summary-service.js +8 -1
- package/dist/service/workflow-watchdog.js +2 -1
- package/dist/utils/retry.js +7 -2
- package/dist/utils/session-key.js +4 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -16,10 +16,11 @@ function scanEnvironment(wctx) {
|
|
|
16
16
|
const tools = {};
|
|
17
17
|
for (const tool of TOOLS_TO_SCAN) {
|
|
18
18
|
try {
|
|
19
|
-
const
|
|
19
|
+
const lines = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n');
|
|
20
|
+
const versionLine = lines[0];
|
|
20
21
|
tools[tool.name] = {
|
|
21
22
|
available: true,
|
|
22
|
-
version: versionLine.trim(),
|
|
23
|
+
version: versionLine ? versionLine.trim() : undefined,
|
|
23
24
|
};
|
|
24
25
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch parameter intentionally unused - we only care that the command failed
|
|
25
26
|
}
|
|
@@ -108,7 +108,7 @@ export function handleDisableImplCommand(ctx) {
|
|
|
108
108
|
const subcommand = parts[0] || '';
|
|
109
109
|
const implId = subcommand === 'list' ? '' : subcommand;
|
|
110
110
|
const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
|
|
111
|
-
const reason = reasonMatch ? reasonMatch[1] : null;
|
|
111
|
+
const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
|
|
112
112
|
// Subcommand: list
|
|
113
113
|
if (subcommand === 'list' || subcommand === '') {
|
|
114
114
|
return _handleListActive(stateDir, isZh);
|
package/dist/commands/focus.js
CHANGED
|
@@ -57,6 +57,8 @@ function compressFocusContent(content, workspaceDir) {
|
|
|
57
57
|
};
|
|
58
58
|
for (let i = 0; i < lines.length; i++) {
|
|
59
59
|
const line = lines[i];
|
|
60
|
+
if (!line)
|
|
61
|
+
continue;
|
|
60
62
|
const trimmedLine = line.trim();
|
|
61
63
|
// 识别章节
|
|
62
64
|
if (/^#{1,3}\s*.*状态快照|📍/.test(trimmedLine)) {
|
|
@@ -245,7 +247,8 @@ async function compressFocus(workspaceDir, isZh, api) {
|
|
|
245
247
|
compressedContent = oldContent;
|
|
246
248
|
}
|
|
247
249
|
// 6. 更新版本号和日期
|
|
248
|
-
const
|
|
250
|
+
const versionParts = oldVersion.split('.');
|
|
251
|
+
const majorVersion = versionParts[0] ?? '';
|
|
249
252
|
const newVersion = `${(parseInt(majorVersion, 10) || 1) + 1}`;
|
|
250
253
|
const [today] = new Date().toISOString().split('T');
|
|
251
254
|
const newContent = compressedContent
|
|
@@ -314,6 +317,11 @@ function rollbackFocus(workspaceDir, index, isZh) {
|
|
|
314
317
|
: `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
|
|
315
318
|
}
|
|
316
319
|
const targetFile = files[index - 1];
|
|
320
|
+
if (!targetFile) {
|
|
321
|
+
return isZh
|
|
322
|
+
? `❌ 无效的序号: ${index}\n\n💡 请输入 1-${files.length} 之间的数字`
|
|
323
|
+
: `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
|
|
324
|
+
}
|
|
317
325
|
const historyContent = fs.readFileSync(targetFile.path, 'utf-8');
|
|
318
326
|
// 备份当前版本
|
|
319
327
|
const currentContent = fs.existsSync(focusPath)
|
|
@@ -416,7 +424,7 @@ export async function handleFocusCommand(ctx, api) {
|
|
|
416
424
|
break;
|
|
417
425
|
case 'rollback':
|
|
418
426
|
case 'rb': {
|
|
419
|
-
const index = parseInt(args[1], 10);
|
|
427
|
+
const index = parseInt(args[1] ?? '', 10);
|
|
420
428
|
if (isNaN(index)) {
|
|
421
429
|
result = isZh
|
|
422
430
|
? '❌ 请指定要回滚的版本序号\n\n💡 输入 `/pd-focus history` 查看可用版本'
|
|
@@ -46,7 +46,7 @@ export function handleRollbackImplCommand(ctx) {
|
|
|
46
46
|
const subcommand = args.split(/\s+/)[0] || '';
|
|
47
47
|
const implId = subcommand === 'list' ? '' : subcommand;
|
|
48
48
|
const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
|
|
49
|
-
const reason = reasonMatch ? reasonMatch[1] : null;
|
|
49
|
+
const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
|
|
50
50
|
// List active
|
|
51
51
|
if (subcommand === 'list' || subcommand === '') {
|
|
52
52
|
return _handleListActiveRollback(stateDir, isZh);
|
|
@@ -16,7 +16,11 @@ function getModels(wctx) {
|
|
|
16
16
|
for (const line of lines) {
|
|
17
17
|
const match = /^###\s*(T-\d+):\s*(.*)/.exec(line);
|
|
18
18
|
if (match) {
|
|
19
|
-
|
|
19
|
+
const key = match[1];
|
|
20
|
+
const value = match[2];
|
|
21
|
+
if (key === undefined || value === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
models[key] = value.trim();
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -103,6 +103,8 @@ export class CorrectionCueLearner {
|
|
|
103
103
|
if (keywordIndex < 0)
|
|
104
104
|
continue;
|
|
105
105
|
const keyword = this.store.keywords[keywordIndex];
|
|
106
|
+
if (!keyword)
|
|
107
|
+
continue;
|
|
106
108
|
this.store.keywords[keywordIndex] = {
|
|
107
109
|
...keyword,
|
|
108
110
|
hitCount: (keyword.hitCount ?? 0) + 1,
|
package/dist/core/event-log.js
CHANGED
|
@@ -30,7 +30,7 @@ export class EventLog {
|
|
|
30
30
|
return path.join(this.logsDir, `events_${date}.jsonl`);
|
|
31
31
|
}
|
|
32
32
|
getTodayStr() {
|
|
33
|
-
return new Date().toISOString().split('T')[0];
|
|
33
|
+
return new Date().toISOString().split('T')[0] ?? '';
|
|
34
34
|
}
|
|
35
35
|
ensureEventsFile() {
|
|
36
36
|
const today = this.getTodayStr();
|
|
@@ -255,7 +255,7 @@ export class EventLog {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
formatDate(date) {
|
|
258
|
-
return date.toISOString().split('T')[0];
|
|
258
|
+
return date.toISOString().split('T')[0] ?? '';
|
|
259
259
|
}
|
|
260
260
|
loadStats() {
|
|
261
261
|
if (fs.existsSync(this.statsFile)) {
|
|
@@ -343,11 +343,14 @@ export class EventLog {
|
|
|
343
343
|
if (!stats.hooks.byType[data.hook]) {
|
|
344
344
|
stats.hooks.byType[data.hook] = { total: 0, success: 0, failure: 0 };
|
|
345
345
|
}
|
|
346
|
-
stats.hooks.byType[data.hook]
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
346
|
+
const hookStats = stats.hooks.byType[data.hook];
|
|
347
|
+
if (hookStats) {
|
|
348
|
+
hookStats.total++;
|
|
349
|
+
if (entry.category === 'success')
|
|
350
|
+
hookStats.success++;
|
|
351
|
+
else
|
|
352
|
+
hookStats.failure++;
|
|
353
|
+
}
|
|
351
354
|
}
|
|
352
355
|
}
|
|
353
356
|
else if (entry.type === 'empathy_rollback') {
|
|
@@ -383,13 +386,13 @@ export class EventLog {
|
|
|
383
386
|
const raw = entry.data;
|
|
384
387
|
if (Object.prototype.hasOwnProperty.call(raw, 'category')) {
|
|
385
388
|
const cat = raw['category'];
|
|
386
|
-
if (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields') {
|
|
389
|
+
if (typeof cat === 'string' && (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields')) {
|
|
387
390
|
stats.evolution.diagnosticianReportsWritten++;
|
|
388
391
|
}
|
|
389
|
-
if (cat === 'missing_json') {
|
|
392
|
+
if (typeof cat === 'string' && cat === 'missing_json') {
|
|
390
393
|
stats.evolution.reportsMissingJson++;
|
|
391
394
|
}
|
|
392
|
-
if (cat === 'incomplete_fields') {
|
|
395
|
+
if (typeof cat === 'string' && cat === 'incomplete_fields') {
|
|
393
396
|
stats.evolution.reportsIncompleteFields++;
|
|
394
397
|
}
|
|
395
398
|
}
|
|
@@ -651,6 +654,8 @@ export class EventLog {
|
|
|
651
654
|
const allEvents = this.getMergedEvents();
|
|
652
655
|
for (let i = allEvents.length - 1; i >= 0; i--) {
|
|
653
656
|
const entry = allEvents[i];
|
|
657
|
+
if (!entry)
|
|
658
|
+
continue;
|
|
654
659
|
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
655
660
|
const data = entry.data;
|
|
656
661
|
if (data.source === 'user_empathy' && !data.deduped) {
|
|
@@ -664,6 +669,8 @@ export class EventLog {
|
|
|
664
669
|
const allEvents = this.getMergedEvents();
|
|
665
670
|
for (let i = allEvents.length - 1; i >= 0; i--) {
|
|
666
671
|
const entry = allEvents[i];
|
|
672
|
+
if (!entry)
|
|
673
|
+
continue;
|
|
667
674
|
if (entry.sessionId === sessionId && entry.type === "pain_signal") {
|
|
668
675
|
return entry.data;
|
|
669
676
|
}
|
|
@@ -503,7 +503,7 @@ export function recoverFromTemplate(focusPath, extensionRoot) {
|
|
|
503
503
|
};
|
|
504
504
|
}
|
|
505
505
|
let template = fs.readFileSync(templatePath, 'utf-8');
|
|
506
|
-
const
|
|
506
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
507
507
|
template = template.replace(/{YYYY-MM-DD}/g, today);
|
|
508
508
|
if (fs.existsSync(focusPath)) {
|
|
509
509
|
const backupPath = `${focusPath}.corrupted.${Date.now()}.md`;
|
|
@@ -24,7 +24,7 @@ export class HygieneTracker {
|
|
|
24
24
|
this.currentStats = this.loadStats();
|
|
25
25
|
}
|
|
26
26
|
loadStats() {
|
|
27
|
-
const
|
|
27
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
28
28
|
if (fs.existsSync(this.statsFile)) {
|
|
29
29
|
try {
|
|
30
30
|
const content = fs.readFileSync(this.statsFile, 'utf-8');
|
|
@@ -54,7 +54,7 @@ export class HygieneTracker {
|
|
|
54
54
|
saveStats() {
|
|
55
55
|
let allStats = {};
|
|
56
56
|
// Check if we need to rotate date (reset currentStats if date changed)
|
|
57
|
-
const
|
|
57
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
58
58
|
if (this.currentStats.date !== today) {
|
|
59
59
|
this.currentStats = createEmptyHygieneStats(today);
|
|
60
60
|
}
|
|
@@ -101,7 +101,7 @@ export class HygieneTracker {
|
|
|
101
101
|
}
|
|
102
102
|
getStats() {
|
|
103
103
|
// Check for date change on every get
|
|
104
|
-
const
|
|
104
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
105
105
|
if (this.currentStats.date !== today) {
|
|
106
106
|
this.currentStats = createEmptyHygieneStats(today);
|
|
107
107
|
}
|
|
@@ -244,6 +244,8 @@ export class PrincipleCompiler {
|
|
|
244
244
|
if (patterns.length === 0)
|
|
245
245
|
return [];
|
|
246
246
|
const pattern = patterns[0];
|
|
247
|
+
if (!pattern)
|
|
248
|
+
return [];
|
|
247
249
|
// Skip replay when the pattern has no regex qualifier -- the generated template
|
|
248
250
|
// blocks ALL calls to the tool, making it impossible to construct a passing
|
|
249
251
|
// positive case. Replay is only meaningful when the template is selective.
|
|
@@ -30,7 +30,7 @@ export class ReplayEngine {
|
|
|
30
30
|
}
|
|
31
31
|
getLatestReport(implementationId) {
|
|
32
32
|
const reports = this.listReports(implementationId);
|
|
33
|
-
return reports
|
|
33
|
+
return reports[0] ?? null;
|
|
34
34
|
}
|
|
35
35
|
hasPassingReport(implementationId) {
|
|
36
36
|
return this.listReports(implementationId).some((report) => report.overallDecision === 'pass');
|
package/dist/core/rule-host.js
CHANGED
|
@@ -23,7 +23,10 @@ function extractTag(content, tagName) {
|
|
|
23
23
|
const match = content.match(regex);
|
|
24
24
|
if (!match)
|
|
25
25
|
return '';
|
|
26
|
-
|
|
26
|
+
const raw = match[1];
|
|
27
|
+
if (!raw)
|
|
28
|
+
return '';
|
|
29
|
+
return raw.trim().replace(/\s+/g, ' ');
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* Parse THINKING_OS.md content and extract all <directive> blocks.
|
|
@@ -35,14 +38,20 @@ export function parseThinkingOsMd(content) {
|
|
|
35
38
|
const directiveRegex = /<directive\s+([^>]*)>([\s\S]*?)<\/directive>/gi;
|
|
36
39
|
let _match = null;
|
|
37
40
|
while ((_match = directiveRegex.exec(content)) !== null) {
|
|
38
|
-
const
|
|
41
|
+
const attrs = _match[1];
|
|
42
|
+
const body = _match[2];
|
|
43
|
+
if (!attrs || !body)
|
|
44
|
+
continue;
|
|
39
45
|
const idMatch = /id="([^"]+)"/i.exec(attrs);
|
|
40
46
|
const nameMatch = /name="([^"]+)"/i.exec(attrs);
|
|
41
47
|
if (!idMatch)
|
|
42
48
|
continue;
|
|
49
|
+
const id = idMatch[1];
|
|
50
|
+
if (!id)
|
|
51
|
+
continue;
|
|
43
52
|
const directive = {
|
|
44
|
-
id
|
|
45
|
-
name: nameMatch ? nameMatch[1] : '',
|
|
53
|
+
id,
|
|
54
|
+
name: nameMatch ? (nameMatch[1] ?? '') : '',
|
|
46
55
|
trigger: extractTag(body, 'trigger'),
|
|
47
56
|
must: extractTag(body, 'must'),
|
|
48
57
|
forbidden: extractTag(body, 'forbidden'),
|
|
@@ -93,7 +93,8 @@ export function purgeStaleFailedTasks(queue, logger) {
|
|
|
93
93
|
// Remove purged items from the queue (mutates in place)
|
|
94
94
|
const purgedIds = new Set(purged.map((t) => t.id));
|
|
95
95
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
96
|
-
|
|
96
|
+
const task = queue[i];
|
|
97
|
+
if (task && purgedIds.has(task.id))
|
|
97
98
|
queue.splice(i, 1);
|
|
98
99
|
}
|
|
99
100
|
const summary = Object.entries(byReason)
|
|
@@ -377,17 +378,20 @@ async function processDetectionQueue(wctx, api, eventLog) {
|
|
|
377
378
|
if (wctx.trajectory) {
|
|
378
379
|
const searchResults = wctx.trajectory.searchPainEvents(text, 5);
|
|
379
380
|
if (searchResults.length > 0) {
|
|
381
|
+
const topResult = searchResults[0];
|
|
382
|
+
if (!topResult)
|
|
383
|
+
continue;
|
|
380
384
|
// Found similar pain events - record as L3 semantic hit
|
|
381
385
|
if (eventLog) {
|
|
382
386
|
eventLog.recordRuleMatch(undefined, {
|
|
383
387
|
ruleId: 'l3_semantic',
|
|
384
388
|
layer: 'L3',
|
|
385
|
-
severity:
|
|
389
|
+
severity: topResult.score,
|
|
386
390
|
textPreview: text.substring(0, 100)
|
|
387
391
|
});
|
|
388
392
|
}
|
|
389
393
|
// Update detection funnel cache with L3 hit result
|
|
390
|
-
funnel.updateCache(text, { detected: true, severity:
|
|
394
|
+
funnel.updateCache(text, { detected: true, severity: topResult.score });
|
|
391
395
|
// Don't track as candidate - this is a confirmed L3 hit
|
|
392
396
|
if (logger)
|
|
393
397
|
logger.info(`[PD:EvolutionWorker] L3 semantic hit: found ${searchResults.length} similar pain events for "${text.substring(0, 50)}..."`);
|
|
@@ -290,7 +290,10 @@ export class RuntimeSummaryService {
|
|
|
290
290
|
if (sessions.length === 0) {
|
|
291
291
|
return { session: null, reason: 'none' };
|
|
292
292
|
}
|
|
293
|
-
|
|
293
|
+
const session = sessions[0];
|
|
294
|
+
if (!session)
|
|
295
|
+
return { session: null, reason: 'none' };
|
|
296
|
+
return { session, reason: 'latest_active' };
|
|
294
297
|
}
|
|
295
298
|
static mergeSessionSnapshots(persistedSessions, workspaceDir) {
|
|
296
299
|
const merged = new Map();
|
|
@@ -392,6 +395,8 @@ export class RuntimeSummaryService {
|
|
|
392
395
|
if (!m)
|
|
393
396
|
continue;
|
|
394
397
|
const fileDate = m[1];
|
|
398
|
+
if (!fileDate)
|
|
399
|
+
continue;
|
|
395
400
|
if (fileDate > newestDate) {
|
|
396
401
|
newestDate = fileDate;
|
|
397
402
|
bestFile = path.join(dir, file);
|
|
@@ -463,6 +468,8 @@ export class RuntimeSummaryService {
|
|
|
463
468
|
static findLastPainSignal(events, sessionId) {
|
|
464
469
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
465
470
|
const entry = events[i];
|
|
471
|
+
if (!entry)
|
|
472
|
+
continue;
|
|
466
473
|
if (entry.type !== 'pain_signal')
|
|
467
474
|
continue;
|
|
468
475
|
if (sessionId && entry.sessionId !== sessionId)
|
|
@@ -32,7 +32,8 @@ export async function runWorkflowWatchdog(wctx, api, logger) {
|
|
|
32
32
|
const ageMin = Math.round((now - wf.created_at) / 60000);
|
|
33
33
|
details.push(`stale_active: ${wf.workflow_id} (${wf.workflow_type}, ${ageMin}min old)`);
|
|
34
34
|
const events = store.getEvents(wf.workflow_id);
|
|
35
|
-
const
|
|
35
|
+
const lastEvent = events[events.length - 1];
|
|
36
|
+
const lastEventReason = lastEvent ? lastEvent.reason : 'unknown';
|
|
36
37
|
if (isExpectedSubagentError(lastEventReason)) {
|
|
37
38
|
logger?.debug?.(`[PD:Watchdog] Skipping stale active workflow ${wf.workflow_id}: expected subagent error (${lastEventReason})`);
|
|
38
39
|
continue;
|
package/dist/utils/retry.js
CHANGED
|
@@ -245,11 +245,13 @@ export function percentile(values, p) {
|
|
|
245
245
|
const n = sorted.length;
|
|
246
246
|
// For small samples, use median to avoid overfitting
|
|
247
247
|
if (n < 10) {
|
|
248
|
-
|
|
248
|
+
const median = sorted[Math.floor(n / 2)];
|
|
249
|
+
return median ?? 0;
|
|
249
250
|
}
|
|
250
251
|
// Standard percentile calculation (nearest-rank method)
|
|
251
252
|
const rank = Math.ceil((p / 100) * n);
|
|
252
|
-
|
|
253
|
+
const result = sorted[Math.min(rank, n) - 1];
|
|
254
|
+
return result ?? 0;
|
|
253
255
|
}
|
|
254
256
|
/**
|
|
255
257
|
* Clamp timeout to safe bounds.
|
|
@@ -317,6 +319,9 @@ export function computeAdaptiveTimeout(history, fallbackMs, options = {}) {
|
|
|
317
319
|
const sorted = [...history].sort((a, b) => a - b);
|
|
318
320
|
const rank = Math.ceil((p / 100) * sorted.length);
|
|
319
321
|
const pValue = sorted[Math.min(rank, sorted.length) - 1];
|
|
322
|
+
if (pValue === undefined) {
|
|
323
|
+
return Math.max(minTimeoutMs, Math.min(maxTimeoutMs, fallbackMs));
|
|
324
|
+
}
|
|
320
325
|
const adaptive = pValue * safetyMultiplier;
|
|
321
326
|
return Math.max(minTimeoutMs, Math.min(maxTimeoutMs, Math.round(adaptive)));
|
|
322
327
|
}
|
|
@@ -13,6 +13,9 @@ export function extractAgentIdFromSessionKey(sessionKey) {
|
|
|
13
13
|
const match = /^agent:([^:]+):/.exec(sessionKey);
|
|
14
14
|
if (!match)
|
|
15
15
|
return undefined;
|
|
16
|
-
const
|
|
16
|
+
const raw = match[1];
|
|
17
|
+
if (!raw)
|
|
18
|
+
return undefined;
|
|
19
|
+
const agentId = raw.trim();
|
|
17
20
|
return agentId || undefined;
|
|
18
21
|
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "principles-disciple",
|
|
3
3
|
"name": "Principles Disciple",
|
|
4
4
|
"description": "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.139.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|