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.
@@ -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.95.0",
5
+ "version": "1.97.0",
6
6
  "activation": {
7
7
  "onCapabilities": [
8
8
  "hook"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.95.0",
3
+ "version": "1.97.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -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
- if (triage.decision !== 'admit') {
355
- SystemLogger.log(workspaceDir, 'TRIAGE_EVIDENCE_ONLY', JSON.stringify({
356
- sourceKind: triage.sourceKind,
357
- decision: triage.decision,
358
- reason: triage.reason,
359
- nextAction: triage.nextAction,
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: triage.reason,
367
- detail: `sourceKind=${triage.sourceKind}, decision=${triage.decision}, nextAction=${triage.nextAction}`,
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