principles-disciple 1.44.0 → 1.45.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 +1 -1
- package/package.json +1 -1
- package/src/hooks/prompt.ts +3 -2
- package/src/service/keyword-optimization-service.ts +24 -2
- package/src/service/subagent-workflow/correction-observer-types.ts +13 -0
- package/src/service/subagent-workflow/correction-observer-workflow-manager.ts +5 -1
- package/tests/core/model-deployment-registry.test.ts +9 -2
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/hooks/prompt.ts
CHANGED
|
@@ -402,9 +402,10 @@ export async function handleBeforePromptBuild(
|
|
|
402
402
|
learner.flush();
|
|
403
403
|
}
|
|
404
404
|
}
|
|
405
|
-
} catch {
|
|
406
|
-
// Fallback to hardcoded detection if learner fails
|
|
405
|
+
} catch (learnerErr) {
|
|
406
|
+
// Fallback to hardcoded detection if learner fails — log for observability
|
|
407
407
|
correctionCue = detectCorrectionCue(userText);
|
|
408
|
+
logger?.warn?.(`[PD:Prompt] CorrectionCueLearner.match() failed (${String(learnerErr)}), fallback=${correctionCue ? `matched="${correctionCue}"` : 'no-match'}`);
|
|
408
409
|
}
|
|
409
410
|
let referencesAssistantTurnId: number | null = null;
|
|
410
411
|
const hasPriorAssistant = event.messages
|
|
@@ -31,12 +31,13 @@ export class KeywordOptimizationService {
|
|
|
31
31
|
applyResult(result: CorrectionObserverResult): void {
|
|
32
32
|
const learner = CorrectionCueLearner.get(this.stateDir);
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
const updates = result.updates ?? {};
|
|
35
|
+
if (!result.updated || Object.keys(updates).length === 0) {
|
|
35
36
|
this.logger?.info?.('[KeywordOptimizationService] No updates to apply');
|
|
36
37
|
return;
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
for (const [term, update] of Object.entries(
|
|
40
|
+
for (const [term, update] of Object.entries(updates)) {
|
|
40
41
|
try {
|
|
41
42
|
switch (update.action) {
|
|
42
43
|
case 'add': {
|
|
@@ -74,6 +75,27 @@ export class KeywordOptimizationService {
|
|
|
74
75
|
this.logger?.warn?.(`[KeywordOptimizationService] ${update.action.toUpperCase()} failed for term="${term}": ${String(opErr)}`);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
78
|
+
|
|
79
|
+
// H-1: Record confirmed false positives — terms where correctionDetected fired
|
|
80
|
+
// but trajectory analysis shows user wasn't actually expressing frustration.
|
|
81
|
+
if (result.fpAnalysisStatus === 'completed' && result.fpTerms && result.fpTerms.length > 0) {
|
|
82
|
+
// Normalize: trim, lowercase, dedupe, sanity cap
|
|
83
|
+
const MAX_FP_TERMS = 20;
|
|
84
|
+
const normalizedFpTerms = [...new Set(
|
|
85
|
+
result.fpTerms
|
|
86
|
+
.map(t => t.trim().toLowerCase())
|
|
87
|
+
.filter(t => t.length > 0)
|
|
88
|
+
)].slice(0, MAX_FP_TERMS);
|
|
89
|
+
|
|
90
|
+
for (const term of normalizedFpTerms) {
|
|
91
|
+
try {
|
|
92
|
+
learner.recordFalsePositive(term);
|
|
93
|
+
this.logger?.info?.(`[KeywordOptimizationService] FP recorded for term="${term}" (weight x0.8)`);
|
|
94
|
+
} catch (fpErr) {
|
|
95
|
+
this.logger?.warn?.(`[KeywordOptimizationService} recordFalsePositive failed for term="${term}": ${String(fpErr)}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
77
99
|
}
|
|
78
100
|
|
|
79
101
|
/**
|
|
@@ -55,6 +55,19 @@ export interface CorrectionObserverResult {
|
|
|
55
55
|
falsePositiveRate?: number;
|
|
56
56
|
reasoning: string;
|
|
57
57
|
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Terms identified as false positives — user message didn't actually indicate
|
|
60
|
+
* frustration/correction despite correctionDetected firing for these terms.
|
|
61
|
+
* CORR-10 / H-1: Calling recordFalsePositive() decays weight by x0.8 per term.
|
|
62
|
+
* Only meaningful when fpAnalysisStatus='completed'.
|
|
63
|
+
*/
|
|
64
|
+
fpTerms?: string[];
|
|
65
|
+
/**
|
|
66
|
+
* Whether FP analysis was performed. 'skipped' means the LLM did not run
|
|
67
|
+
* trajectory analysis (e.g., trajectory was empty). 'completed' means fpTerms
|
|
68
|
+
* contains the LLM's FP findings (may be empty if no FPs were found).
|
|
69
|
+
*/
|
|
70
|
+
fpAnalysisStatus?: 'completed' | 'skipped';
|
|
58
71
|
/** Human-readable summary */
|
|
59
72
|
summary: string;
|
|
60
73
|
}
|
|
@@ -115,6 +115,7 @@ export const correctionObserverWorkflowSpec: SubagentWorkflowSpec<CorrectionObse
|
|
|
115
115
|
'## TASK',
|
|
116
116
|
'Analyze the current correction keyword store and recent user messages.',
|
|
117
117
|
'Recommend ADD/UPDATE/REMOVE actions to improve correction cue accuracy.',
|
|
118
|
+
'Also identify terms that triggered false positives (correctionDetected fired but user message doesn\'t indicate actual frustration).',
|
|
118
119
|
'',
|
|
119
120
|
'## Current Keyword Store (' + keywordStoreSummary.totalKeywords + ' terms):',
|
|
120
121
|
termsList,
|
|
@@ -129,11 +130,14 @@ export const correctionObserverWorkflowSpec: SubagentWorkflowSpec<CorrectionObse
|
|
|
129
130
|
'- ADD: If a correction pattern is detected in messages but not in store',
|
|
130
131
|
'- UPDATE: If a term\'s weight should change based on TP/FP ratio',
|
|
131
132
|
'- REMOVE: If a term has 0 hits after many uses AND high false positive rate (>0.3)',
|
|
133
|
+
'- FALSE POSITIVE: If a term appears in trajectory but the user message doesn\'t actually express frustration (e.g., user said "wrong" but in a factual context, not emotional)',
|
|
134
|
+
'- fpAnalysisStatus: set to "completed" if you performed trajectory analysis (even if no FPs found), or "skipped" if trajectory was empty/unavailable',
|
|
132
135
|
'- Keep reasoning concise (max 100 chars)',
|
|
133
136
|
'- Weight range: 0.1-0.9',
|
|
134
137
|
'',
|
|
135
138
|
'Return strict JSON (no markdown):',
|
|
136
|
-
'{"updated": boolean, "updates": {...}, "summary": string}',
|
|
139
|
+
'{"updated": boolean, "updates": {...}, "fpTerms": ["term1", ...], "fpAnalysisStatus": "completed" | "skipped", "summary": string}',
|
|
140
|
+
'Note: fpTerms is optional — only include if you identified clear false positives.',
|
|
137
141
|
].join('\n');
|
|
138
142
|
},
|
|
139
143
|
|
|
@@ -378,8 +378,15 @@ describe('ModelDeploymentRegistry getDeployment / listDeployments', () => {
|
|
|
378
378
|
|
|
379
379
|
const deployments = listDeployments(tmpDir);
|
|
380
380
|
expect(deployments).toHaveLength(2);
|
|
381
|
-
//
|
|
382
|
-
|
|
381
|
+
// Verify sort order is descending by updatedAt (most recent first)
|
|
382
|
+
const [first, second] = deployments;
|
|
383
|
+
const firstUpdated = new Date(first.updatedAt).getTime();
|
|
384
|
+
const secondUpdated = new Date(second.updatedAt).getTime();
|
|
385
|
+
expect(firstUpdated).toBeGreaterThanOrEqual(secondUpdated);
|
|
386
|
+
// Also verify both expected profiles are present (order-independent)
|
|
387
|
+
const profiles = deployments.map(d => d.workerProfile);
|
|
388
|
+
expect(profiles).toContain('local-reader');
|
|
389
|
+
expect(profiles).toContain('local-editor');
|
|
383
390
|
});
|
|
384
391
|
|
|
385
392
|
it('listDeployments filters by workerProfile', () => {
|