principles-disciple 1.95.0 → 1.97.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
CHANGED
package/package.json
CHANGED
|
@@ -88,6 +88,21 @@ export function resetPainDiagnosticGateForTest(): void {
|
|
|
88
88
|
lastDiagnosedAtByEpisode.clear();
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Check whether cooldown is currently active for a given episode.
|
|
93
|
+
* Used by the trigger controller (PEAT-B2) to align its cooldown decision
|
|
94
|
+
* with the PainDiagnosticGate's cooldown state.
|
|
95
|
+
*/
|
|
96
|
+
export function isCooldownActiveForEpisode(
|
|
97
|
+
source: string,
|
|
98
|
+
sessionId: string | undefined,
|
|
99
|
+
errorHash: string | undefined,
|
|
100
|
+
cooldownMs?: number,
|
|
101
|
+
): boolean {
|
|
102
|
+
const episodeKey = buildEpisodeKey({ source, sessionId, errorHash } as PainDiagnosticGateInput);
|
|
103
|
+
return withinCooldown({ source, sessionId, errorHash, cooldownMs } as PainDiagnosticGateInput, episodeKey);
|
|
104
|
+
}
|
|
105
|
+
|
|
91
106
|
export function evaluatePainDiagnosticGate(input: PainDiagnosticGateInput): PainDiagnosticGateDecision {
|
|
92
107
|
const source = normalizedSource(input.source);
|
|
93
108
|
const episodeKey = buildEpisodeKey(input);
|
|
@@ -23,10 +23,11 @@ import { WorkspaceContext } from '../core/workspace-context.js';
|
|
|
23
23
|
import { getEvolutionLogger, createTraceId } from '../core/evolution-logger.js';
|
|
24
24
|
import { recordEvolutionSuccess, recordEvolutionFailure } from '../core/evolution-engine.js';
|
|
25
25
|
import type { PluginHookAfterToolCallEvent } from '../openclaw-sdk.js';
|
|
26
|
-
import { evaluatePainDiagnosticGate } from '../core/pain-diagnostic-gate.js';
|
|
26
|
+
import { evaluatePainDiagnosticGate, isCooldownActiveForEpisode } from '../core/pain-diagnostic-gate.js';
|
|
27
27
|
import { sanitizeForEvidence, sanitizeToolParamsForEvidence } from './message-sanitize.js';
|
|
28
28
|
import { loadFeatureFlagFromConfig } from '../core/pd-config-loader.js';
|
|
29
29
|
import { resolveSourceKindFromToolFailure, evaluateEvidenceTriage } from './triage-adapter.js';
|
|
30
|
+
import { evaluateTriggerController } from '@principles/core/runtime-v2';
|
|
30
31
|
import { buildTrajectoryEvidence } from './trajectory-evidence.js';
|
|
31
32
|
import type { ToolCallOutcome, ToolCallObservation, PainAdmissionDecision } from './after-tool-call-types.js';
|
|
32
33
|
|
|
@@ -347,24 +348,44 @@ export function evaluatePainAdmissionForToolCall(
|
|
|
347
348
|
const failureSource = outcome.failureSource ?? 'tool_failure';
|
|
348
349
|
|
|
349
350
|
// PEAT-B1: Evidence triage (feature-flagged)
|
|
351
|
+
// PEAT-B2: Trigger controller adds structured outcome + cooldown awareness
|
|
350
352
|
const painTriageFlag = loadFeatureFlagFromConfig(workspaceDir, 'painEvidenceAdmission');
|
|
351
353
|
if (painTriageFlag.enabled) {
|
|
352
354
|
const sourceKind = resolveSourceKindFromToolFailure(event.toolName, failureSource);
|
|
353
355
|
const triage = evaluateEvidenceTriage(sourceKind, observation.painScore);
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
356
|
+
|
|
357
|
+
// PEAT-B2: Evaluate trigger controller for structured decision
|
|
358
|
+
// Compute real cooldown state from PainDiagnosticGate's episode map
|
|
359
|
+
// so trigger decision aligns with the gate's cooldown logic (EP-07).
|
|
360
|
+
const cooldownActive = isCooldownActiveForEpisode(
|
|
361
|
+
failureSource,
|
|
362
|
+
sessionId,
|
|
363
|
+
latestFailureState?.lastErrorHash,
|
|
364
|
+
);
|
|
365
|
+
const triggerDecision = evaluateTriggerController({
|
|
366
|
+
triageResult: triage,
|
|
367
|
+
isOwnerManual: false, // tool failures are never owner manual
|
|
368
|
+
isCooldownActive: cooldownActive,
|
|
369
|
+
isValid: true,
|
|
370
|
+
score: observation.painScore,
|
|
371
|
+
sessionId,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
if (!triggerDecision.shouldCreateDiagnosticTask) {
|
|
375
|
+
SystemLogger.log(workspaceDir, 'TRIGGER_DECISION', JSON.stringify({
|
|
376
|
+
outcome: triggerDecision.outcome,
|
|
377
|
+
sourceKind: triggerDecision.sourceKind,
|
|
378
|
+
reason: triggerDecision.reason,
|
|
379
|
+
nextAction: triggerDecision.nextAction,
|
|
380
|
+
triageDecision: triggerDecision.triageDecision,
|
|
360
381
|
tool: event.toolName,
|
|
361
382
|
path: observation.relPath,
|
|
362
383
|
}));
|
|
363
384
|
return {
|
|
364
385
|
admitted: false,
|
|
365
386
|
stage: 'triage_evidence_only',
|
|
366
|
-
reason:
|
|
367
|
-
detail: `
|
|
387
|
+
reason: triggerDecision.reason,
|
|
388
|
+
detail: `outcome=${triggerDecision.outcome}, sourceKind=${triggerDecision.sourceKind}, nextAction=${triggerDecision.nextAction}`,
|
|
368
389
|
};
|
|
369
390
|
}
|
|
370
391
|
}
|
package/src/hooks/pain.ts
CHANGED
|
@@ -74,6 +74,20 @@ export async function emitPainDetectedEvent(wctx: WorkspaceContext, event: Evolu
|
|
|
74
74
|
const painData = event.data as PainDetectedData;
|
|
75
75
|
try {
|
|
76
76
|
const service = createPainToPrincipleService(wctx);
|
|
77
|
+
const isManual = painData.source === 'manual' || painData.source === 'pain' || painData.source === 'skill:pain';
|
|
78
|
+
|
|
79
|
+
// PEAT-B2: Record trigger decision for observability
|
|
80
|
+
if (isManual) {
|
|
81
|
+
SystemLogger.log(wctx.workspaceDir, 'TRIGGER_DECISION', JSON.stringify({
|
|
82
|
+
outcome: 'manual_owner_admitted',
|
|
83
|
+
sourceKind: 'owner_reported',
|
|
84
|
+
reason: 'Owner explicit manual pain. Bypasses triage and cooldown.',
|
|
85
|
+
nextAction: 'create_diagnostic_task',
|
|
86
|
+
painId: painData.painId,
|
|
87
|
+
score: painData.score,
|
|
88
|
+
}));
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
const result = await service.recordPain({
|
|
78
92
|
painId: painData.painId,
|
|
79
93
|
painType: painData.painType,
|
|
@@ -307,6 +321,20 @@ function handleManualPain(
|
|
|
307
321
|
payload = JSON.stringify({ reason: gate.reason, detail: '(log serialization failed)' });
|
|
308
322
|
}
|
|
309
323
|
SystemLogger.log(workspaceDir, 'PAIN_GATE_REJECTED', payload);
|
|
324
|
+
|
|
325
|
+
// PEAT-B2: Record trigger decision even when cooldown blocks manual pain
|
|
326
|
+
const painTriageFlag = loadPdConfigForPlugin(workspaceDir);
|
|
327
|
+
if (painTriageFlag.effective) {
|
|
328
|
+
SystemLogger.log(workspaceDir, 'TRIGGER_DECISION', JSON.stringify({
|
|
329
|
+
outcome: 'cooldown_skipped',
|
|
330
|
+
sourceKind: 'owner_reported',
|
|
331
|
+
reason: `Manual pain within cooldown: ${gate.detail}`,
|
|
332
|
+
nextAction: 'wait_for_cooldown_or_manual_retrigger',
|
|
333
|
+
isOwnerManual: true,
|
|
334
|
+
sessionId,
|
|
335
|
+
score: 100,
|
|
336
|
+
}));
|
|
337
|
+
}
|
|
310
338
|
return;
|
|
311
339
|
}
|
|
312
340
|
|