principles-disciple 1.153.0 → 1.154.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/hooks/prompt-helpers.d.ts +14 -0
- package/dist/hooks/prompt-helpers.js +26 -0
- package/dist/hooks/prompt.js +43 -17
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -89,3 +89,17 @@ export declare function formatEvolutionPrinciples(active: EvolutionPrincipleEntr
|
|
|
89
89
|
* @returns Assembled appendSystemContext (empty string if no parts)
|
|
90
90
|
*/
|
|
91
91
|
export declare function assembleAppendSystemContext(parts: AppendSystemContextParts): string;
|
|
92
|
+
/**
|
|
93
|
+
* Extracts short, distinct keyword phrases from an EmpathyObserver reason string.
|
|
94
|
+
*
|
|
95
|
+
* Used to feed newly detected expressions back into the keyword store, so the
|
|
96
|
+
* keyword-based fast path can catch them on future turns without re-running
|
|
97
|
+
* the more expensive observer.
|
|
98
|
+
*
|
|
99
|
+
* Pure function — no I/O, no side effects. Fully unit-testable.
|
|
100
|
+
*
|
|
101
|
+
* @param reason - observer's reason string (e.g., "用户表达了强烈的挫败感,提到反复尝试失败")
|
|
102
|
+
* @param lang - UI locale for minimum length threshold (zh=2 chars, en=3 chars)
|
|
103
|
+
* @returns deduplicated array of up to 3 candidate phrases
|
|
104
|
+
*/
|
|
105
|
+
export declare function extractPhrasesFromReason(reason: string, lang: 'zh' | 'en'): string[];
|
|
@@ -260,3 +260,29 @@ ${executionRules.join('\n')}
|
|
|
260
260
|
`;
|
|
261
261
|
return result;
|
|
262
262
|
}
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
// Block H: Observer feedback → keyword phrase extraction
|
|
265
|
+
// ---------------------------------------------------------------------------
|
|
266
|
+
/**
|
|
267
|
+
* Extracts short, distinct keyword phrases from an EmpathyObserver reason string.
|
|
268
|
+
*
|
|
269
|
+
* Used to feed newly detected expressions back into the keyword store, so the
|
|
270
|
+
* keyword-based fast path can catch them on future turns without re-running
|
|
271
|
+
* the more expensive observer.
|
|
272
|
+
*
|
|
273
|
+
* Pure function — no I/O, no side effects. Fully unit-testable.
|
|
274
|
+
*
|
|
275
|
+
* @param reason - observer's reason string (e.g., "用户表达了强烈的挫败感,提到反复尝试失败")
|
|
276
|
+
* @param lang - UI locale for minimum length threshold (zh=2 chars, en=3 chars)
|
|
277
|
+
* @returns deduplicated array of up to 3 candidate phrases
|
|
278
|
+
*/
|
|
279
|
+
export function extractPhrasesFromReason(reason, lang) {
|
|
280
|
+
const MAX_PHRASES = 3;
|
|
281
|
+
const MIN_LENGTH = lang === 'zh' ? 2 : 3;
|
|
282
|
+
const MAX_LENGTH = 20;
|
|
283
|
+
const segments = reason
|
|
284
|
+
.split(/[,,。.!!??、\n;;]/)
|
|
285
|
+
.map(s => s.trim())
|
|
286
|
+
.filter(s => s.length >= MIN_LENGTH && s.length <= MAX_LENGTH);
|
|
287
|
+
return [...new Set(segments)].slice(0, MAX_PHRASES);
|
|
288
|
+
}
|
package/dist/hooks/prompt.js
CHANGED
|
@@ -11,7 +11,7 @@ import { selectPrinciplesForInjection, DEFAULT_PRINCIPLE_BUDGET } from '../core/
|
|
|
11
11
|
import { getCachedMaskedPrincipleSet, WorkflowFunnelLoader, PiAiRuntimeAdapter, EmpathyObserver, AgentScheduler, RUNTIME_V2_PRINCIPLE_BUDGET, trimToBudget, renderPrinciplesToDirectives } from '@principles/core/runtime-v2';
|
|
12
12
|
import { truncateInjectionToBudget } from '@principles/core/prompt-builder';
|
|
13
13
|
import { PromptActivationReader } from '../core/runtime-v2-prompt-activation-reader.js';
|
|
14
|
-
import { matchEmpathyKeywords, loadKeywordStore, saveKeywordStore, getKeywordStoreSummary, } from '../core/empathy-keyword-matcher.js';
|
|
14
|
+
import { matchEmpathyKeywords, loadKeywordStore, saveKeywordStore, applyKeywordUpdates, getKeywordStoreSummary, } from '../core/empathy-keyword-matcher.js';
|
|
15
15
|
import { severityToPenalty, DEFAULT_EMPATHY_KEYWORD_CONFIG } from '../core/empathy-types.js';
|
|
16
16
|
import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
|
|
17
17
|
import { emitPainDetectedEvent, buildTrajectoryEvidence } from './pain.js';
|
|
@@ -25,7 +25,7 @@ import { buildIntentFrictionBlock } from '@principles/core/runtime-v2';
|
|
|
25
25
|
import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
|
|
26
26
|
import { detectCorrectionCue as coreDetectCorrectionCue, escapeXml, extractMessageContent, isMinimalTrigger, } from '@principles/core/prompt-builder';
|
|
27
27
|
import { sanitizeForEvidence } from './message-sanitize.js';
|
|
28
|
-
import { buildAgentIdentity, buildEmpathySilenceConstraint, extractUserMessageFromPrompt, assembleHeartbeatChecklist, formatCorePrinciples, formatEvolutionPrinciples, assembleAppendSystemContext, } from './prompt-helpers.js';
|
|
28
|
+
import { buildAgentIdentity, buildEmpathySilenceConstraint, extractUserMessageFromPrompt, assembleHeartbeatChecklist, formatCorePrinciples, formatEvolutionPrinciples, assembleAppendSystemContext, extractPhrasesFromReason, } from './prompt-helpers.js';
|
|
29
29
|
// ---------------------------------------------------------------------------
|
|
30
30
|
// Static file cache — avoids re-reading rarely-changing files every message
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
@@ -305,6 +305,8 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
305
305
|
// Increment turn counter (Finding #3: session.turnCount doesn't exist)
|
|
306
306
|
empathyState.turnCounter++;
|
|
307
307
|
const turnCount = empathyState.turnCounter;
|
|
308
|
+
const painTrigger = wctx.config.get('thresholds.pain_trigger') || 40;
|
|
309
|
+
const highGfiThreshold = Math.max(wctx.config.get('severity_thresholds.high') || 70, painTrigger + 30);
|
|
308
310
|
if (matchResult.matched) {
|
|
309
311
|
const penalty = severityToPenalty(matchResult.severity, DEFAULT_EMPATHY_KEYWORD_CONFIG);
|
|
310
312
|
// trackFriction signature: (sessionId, deltaF: number, hash: string, workspaceDir?, options?)
|
|
@@ -314,8 +316,6 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
314
316
|
logger?.info?.(`[PD:Empathy] MATCH: "${matchResult.matchedTerms.join(', ')}" → severity=${matchResult.severity}, score=${matchResult.score.toFixed(2)}, penalty=${penalty}`);
|
|
315
317
|
const currentSession = getSession(sessionId);
|
|
316
318
|
const currentGfi = currentSession?.currentGfi ?? 0;
|
|
317
|
-
const painTrigger = wctx.config.get('thresholds.pain_trigger') || 40;
|
|
318
|
-
const highGfiThreshold = Math.max(wctx.config.get('severity_thresholds.high') || 70, painTrigger + 30);
|
|
319
319
|
if (currentGfi >= highGfiThreshold) {
|
|
320
320
|
const gfiPainScore = Math.min(Math.round(currentGfi), 60);
|
|
321
321
|
logger?.info?.(`[PD:Empathy] GFI-TRIGGERED: currentGfi=${currentGfi.toFixed(1)} >= highGfi=${highGfiThreshold}, emitting pain signal (score=${gfiPainScore})`);
|
|
@@ -451,7 +451,9 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
451
451
|
}
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
|
-
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// ── Pipeline: Observer only runs when keyword matcher misses ──
|
|
455
457
|
const observer = resolveEmpathyObserver(wctx, logger);
|
|
456
458
|
if (observer) {
|
|
457
459
|
const scheduler = new AgentScheduler();
|
|
@@ -460,14 +462,40 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
460
462
|
mode: 'realtime',
|
|
461
463
|
runner: observer,
|
|
462
464
|
});
|
|
463
|
-
logger?.info?.(`[PD:Empathy] Triggering background Empathy Observer
|
|
465
|
+
logger?.info?.(`[PD:Empathy] Pipeline: Triggering background Empathy Observer for unmatched message: "${msgPreview}"`);
|
|
464
466
|
void scheduler.dispatch('empathy-observer', { userMessage: latestUserMessage })
|
|
465
467
|
.then(async (result) => {
|
|
466
468
|
if (result.damageDetected) {
|
|
467
|
-
logger?.info?.(`[PD:Empathy] Background Empathy Observer detected damage. Severity: ${result.severity}, Reason: ${result.reason}`);
|
|
469
|
+
logger?.info?.(`[PD:Empathy] Pipeline: Background Empathy Observer detected damage. Severity: ${result.severity}, Reason: ${result.reason}`);
|
|
468
470
|
// ── Persistence Contract ──
|
|
469
471
|
const painScore = scoreFromSeverityForSpec(result.severity, wctx);
|
|
470
472
|
trackFriction(sessionId, painScore, `observer_empathy_${result.severity}`, workspaceDir, { source: 'user_empathy' });
|
|
473
|
+
// ── Pipeline: feed back newly detected expressions into keyword store ──
|
|
474
|
+
if (result.reason) {
|
|
475
|
+
const rawLang = wctx.config.get('language');
|
|
476
|
+
const lang = rawLang === 'en' ? 'en' : 'zh';
|
|
477
|
+
const phrases = extractPhrasesFromReason(result.reason, lang);
|
|
478
|
+
if (phrases.length > 0) {
|
|
479
|
+
const updates = {};
|
|
480
|
+
for (const phrase of phrases) {
|
|
481
|
+
if (!Object.hasOwn(keywordStore.terms, phrase)) {
|
|
482
|
+
updates[phrase] = {
|
|
483
|
+
action: 'add',
|
|
484
|
+
weight: 0.4,
|
|
485
|
+
falsePositiveRate: 0.3,
|
|
486
|
+
reasoning: `Discovered by empathy observer: ${result.reason.substring(0, 100)}`,
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (Object.keys(updates).length > 0) {
|
|
491
|
+
const { added } = applyKeywordUpdates(keywordStore, updates);
|
|
492
|
+
if (added > 0) {
|
|
493
|
+
saveKeywordStore(wctx.stateDir, keywordStore);
|
|
494
|
+
logger?.info?.(`[PD:Empathy] Pipeline: added ${added} new terms from observer feedback`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
}
|
|
471
499
|
const eventId = `emp_obs_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
|
|
472
500
|
wctx.eventLog.recordPainSignal(sessionId, {
|
|
473
501
|
score: painScore,
|
|
@@ -524,7 +552,7 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
524
552
|
const sourceKind = resolveSourceKind(rawObs);
|
|
525
553
|
const triage = evaluateEvidenceTriage(sourceKind, freshGfiPainScore);
|
|
526
554
|
if (triage.decision !== 'admit') {
|
|
527
|
-
logger?.info?.(`[PD:Empathy]
|
|
555
|
+
logger?.info?.(`[PD:Empathy] Pipeline observer triage ${triage.decision}: ${triage.reason}`);
|
|
528
556
|
}
|
|
529
557
|
else {
|
|
530
558
|
const cooldownActive = isSharedCooldownActive(sourceKind, sessionId, 'empathy_gfi_observer');
|
|
@@ -538,7 +566,7 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
538
566
|
});
|
|
539
567
|
if (triggerDecision.shouldCreateDiagnosticTask) {
|
|
540
568
|
markSharedEpisodeAsDiagnosed(sourceKind, sessionId, 'empathy_gfi_observer');
|
|
541
|
-
logger?.info?.(`[PD:Empathy] Gate B approved (observer), calling emitPainDetectedEvent...`);
|
|
569
|
+
logger?.info?.(`[PD:Empathy] Pipeline Gate B approved (observer), calling emitPainDetectedEvent...`);
|
|
542
570
|
try {
|
|
543
571
|
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
544
572
|
// PRI-453: Reuse observerPainId for lineage consistency —
|
|
@@ -562,11 +590,11 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
562
590
|
}, { recordObservability: false });
|
|
563
591
|
}
|
|
564
592
|
catch (emitErr) {
|
|
565
|
-
logger?.error?.(`[PD:Empathy] FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
593
|
+
logger?.error?.(`[PD:Empathy] Pipeline FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
566
594
|
}
|
|
567
595
|
}
|
|
568
596
|
else {
|
|
569
|
-
logger?.info?.(`[PD:Empathy] Gate B skipped (observer): ${triggerDecision.reason}`);
|
|
597
|
+
logger?.info?.(`[PD:Empathy] Pipeline Gate B skipped (observer): ${triggerDecision.reason}`);
|
|
570
598
|
}
|
|
571
599
|
}
|
|
572
600
|
}
|
|
@@ -586,7 +614,7 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
586
614
|
},
|
|
587
615
|
});
|
|
588
616
|
if (gate.shouldDiagnose) {
|
|
589
|
-
logger?.info?.(`[PD:Empathy] GFI threshold crossed after background observer. Emitting pain signal...`);
|
|
617
|
+
logger?.info?.(`[PD:Empathy] Pipeline GFI threshold crossed after background observer. Emitting pain signal...`);
|
|
590
618
|
try {
|
|
591
619
|
const evidence = buildTrajectoryEvidence(wctx, sessionId);
|
|
592
620
|
// PRI-453: Reuse observerPainId for lineage consistency —
|
|
@@ -610,22 +638,20 @@ export async function handleBeforePromptBuild(event, ctx) {
|
|
|
610
638
|
}, { recordObservability: false });
|
|
611
639
|
}
|
|
612
640
|
catch (emitErr) {
|
|
613
|
-
logger?.error?.(`[PD:Empathy] FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
641
|
+
logger?.error?.(`[PD:Empathy] Pipeline FAILED to emit observer-triggered pain event: ${String(emitErr)}`);
|
|
614
642
|
}
|
|
615
643
|
}
|
|
616
644
|
}
|
|
617
645
|
}
|
|
618
646
|
}
|
|
619
647
|
else {
|
|
620
|
-
logger?.info?.(`[PD:Empathy] Background Empathy Observer did not detect any damage.`);
|
|
648
|
+
logger?.info?.(`[PD:Empathy] Pipeline: Background Empathy Observer did not detect any damage.`);
|
|
621
649
|
}
|
|
622
650
|
})
|
|
623
651
|
.catch((err) => {
|
|
624
|
-
logger?.warn?.(`[PD:Empathy] Background analysis failed or rejected: ${String(err)}`);
|
|
652
|
+
logger?.warn?.(`[PD:Empathy] Pipeline: Background analysis failed or rejected: ${String(err)}`);
|
|
625
653
|
});
|
|
626
654
|
}
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
655
|
// Log unmatched messages periodically for coverage analysis
|
|
630
656
|
if (turnCount > 0 && turnCount % 50 === 0) {
|
|
631
657
|
const sampleMsg = latestUserMessage.substring(0, 80).replace(/\n/g, ' ');
|
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.154.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onCapabilities": [
|
|
8
8
|
"hook"
|