principles-disciple 1.176.0 → 1.178.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.
@@ -1,4 +1,4 @@
1
- import type { EventLogEntry, DailyStats, EmpathyEventStats, ToolCallEventData, PainSignalEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, EmpathyRollbackEventData, DiagnosisTaskEventData, HeartbeatDiagnosisEventData, DiagnosticianReportEventData, PrincipleCandidateEventData, RuleEnforcedEventData, RuleHostEvaluatedEventData, RuleHostBlockedEventData, RuleHostRequireApprovalEventData, RuleHostAutoCorrectProposedEventData, RuleHostAutoCorrectAppliedEventData, RuntimeV2PromptActivationsInjectedEventData, RuleHostUnhealthyEventData } from '../types/event-types.js';
1
+ import type { EventLogEntry, DailyStats, EmpathyEventStats, ToolCallEventData, PainSignalEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, EmpathyRollbackEventData, DiagnosisTaskEventData, HeartbeatDiagnosisEventData, DiagnosticianReportEventData, PrincipleCandidateEventData, RuleEnforcedEventData, RuleHostEvaluatedEventData, RuleHostBlockedEventData, RuleHostRequireApprovalEventData, RuleHostAutoCorrectProposedEventData, RuleHostAutoCorrectAppliedEventData, RuntimeV2PromptActivationsInjectedEventData, RuleHostUnhealthyEventData, RuleHostSkippedEventData } from '../types/event-types.js';
2
2
  import type { PluginLogger } from '../openclaw-sdk.js';
3
3
  export declare class EventLog {
4
4
  private readonly logsDir;
@@ -50,6 +50,16 @@ export declare class EventLog {
50
50
  * ERR-002: degradation includes a reason and nextAction (not silent).
51
51
  */
52
52
  recordRuleHostUnhealthy(data: RuleHostUnhealthyEventData): void;
53
+ /**
54
+ * PRI-491: Record that an active activation was skipped at load time for a
55
+ * structured reason (flag-off v2, unsupported action, unsupported context
56
+ * version, missing target_ref). Unlike rulehost_unhealthy (compile/load
57
+ * failures), these are configuration/flag issues — the RuleCode itself may
58
+ * be valid, but the runtime chose not to execute it.
59
+ *
60
+ * ERR-002: degradation/suspension includes reason + nextAction (rc-9).
61
+ */
62
+ recordRuleHostSkipped(data: RuleHostSkippedEventData): void;
53
63
  /**
54
64
  * Redact telemetry-sensitive string values in event data before persistence.
55
65
  * Applies redactTelemetryString to known high-risk fields (filePath, command,
@@ -152,6 +152,18 @@ export class EventLog {
152
152
  recordRuleHostUnhealthy(data) {
153
153
  this.record('rulehost_unhealthy', 'failure', undefined, data);
154
154
  }
155
+ /**
156
+ * PRI-491: Record that an active activation was skipped at load time for a
157
+ * structured reason (flag-off v2, unsupported action, unsupported context
158
+ * version, missing target_ref). Unlike rulehost_unhealthy (compile/load
159
+ * failures), these are configuration/flag issues — the RuleCode itself may
160
+ * be valid, but the runtime chose not to execute it.
161
+ *
162
+ * ERR-002: degradation/suspension includes reason + nextAction (rc-9).
163
+ */
164
+ recordRuleHostSkipped(data) {
165
+ this.record('rulehost_skipped', 'failure', undefined, data);
166
+ }
155
167
  /**
156
168
  * Redact telemetry-sensitive string values in event data before persistence.
157
169
  * Applies redactTelemetryString to known high-risk fields (filePath, command,
@@ -171,6 +183,7 @@ export class EventLog {
171
183
  'rulehost_requireApproval',
172
184
  'rulehost_auto_correct_proposed',
173
185
  'rulehost_auto_correct_applied',
186
+ 'rulehost_skipped',
174
187
  'rule_enforced',
175
188
  'hook_execution',
176
189
  'gate_block',
@@ -30,12 +30,41 @@ export interface RuleHostOptions {
30
30
  /** Workspace directory for SQLite access. Required for RuleHost to load active code_tool_hook activations. */
31
31
  workspaceDir?: string;
32
32
  }
33
+ type RuleHostActivationMode = 'shadow' | 'live';
33
34
  export interface RuleHostObservedDecision extends RuleHostResult {
34
35
  readonly activationId: string;
35
36
  }
37
+ /**
38
+ * PRI-491 — A structured record of an activation that was skipped at load
39
+ * time (flag-off v2, unsupported action, unsupported context version,
40
+ * missing target_ref, content_json not an object, no implementationCode).
41
+ *
42
+ * Unlike compile/load failures (which emit rulehost_unhealthy), skipped
43
+ * activations have a configuration/flag reason — the RuleCode itself may be
44
+ * valid, but the runtime chose not to execute it.
45
+ *
46
+ * ERR-002 (rc-9): every skip carries a reason + nextAction, never silent.
47
+ */
48
+ export interface SkippedActivation {
49
+ readonly activationId: string;
50
+ readonly ruleId: string;
51
+ /**
52
+ * The mode the activation WOULD have had if loaded. Optional for cases
53
+ * where the action itself is unrecognized (neither shadow nor live).
54
+ */
55
+ readonly mode?: RuleHostActivationMode;
56
+ readonly reason: string;
57
+ readonly nextAction: string;
58
+ }
36
59
  export interface RuleHostEvaluationReport {
37
60
  readonly liveDecision: RuleHostResult | undefined;
38
61
  readonly shadowDecisions: readonly RuleHostObservedDecision[];
62
+ /**
63
+ * PRI-491 — Activations that were skipped at load time. Empty when all
64
+ * active activations loaded successfully. Each entry carries a structured
65
+ * reason + nextAction so the owner can act without reading SQLite rows.
66
+ */
67
+ readonly skippedActivations: readonly SkippedActivation[];
39
68
  }
40
69
  export declare class RuleHost {
41
70
  private readonly stateDir;
@@ -44,6 +73,12 @@ export declare class RuleHost {
44
73
  private readonly implementationSources;
45
74
  private activationFingerprint;
46
75
  private cachedImplementations;
76
+ /**
77
+ * PRI-491: Cached skipped activations from the last load. Returned alongside
78
+ * cachedImplementations on fingerprint hit so evaluateDetailed can surface
79
+ * them without re-scanning SQLite.
80
+ */
81
+ private cachedSkipped;
47
82
  private sqliteConnection;
48
83
  /**
49
84
  * R2-RH-002: Guards the "armed but empty" warn so it fires at most once per
@@ -125,4 +160,16 @@ export declare class RuleHost {
125
160
  * Failures in EventLog recording are caught and logged (never throw).
126
161
  */
127
162
  private _recordUnhealthy;
163
+ /**
164
+ * PRI-491: Record a skipped activation to EventLog.
165
+ *
166
+ * Unlike _recordUnhealthy (compile/load failures), skipped activations have
167
+ * a configuration/flag reason — the RuleCode itself may be valid, but the
168
+ * runtime chose not to execute it (flag-off v2, unsupported context version,
169
+ * unsupported action, content_json not object, no implementationCode).
170
+ *
171
+ * ERR-002: degradation includes a reason and nextAction (rc-9-no-silent-fallback).
172
+ * Failures in EventLog recording are caught and logged (never throw).
173
+ */
174
+ private _recordSkipped;
128
175
  }
@@ -50,6 +50,12 @@ export class RuleHost {
50
50
  implementationSources = new Map();
51
51
  activationFingerprint = null;
52
52
  cachedImplementations = [];
53
+ /**
54
+ * PRI-491: Cached skipped activations from the last load. Returned alongside
55
+ * cachedImplementations on fingerprint hit so evaluateDetailed can surface
56
+ * them without re-scanning SQLite.
57
+ */
58
+ cachedSkipped = [];
53
59
  sqliteConnection = null;
54
60
  /**
55
61
  * R2-RH-002: Guards the "armed but empty" warn so it fires at most once per
@@ -93,13 +99,14 @@ export class RuleHost {
93
99
  this.sqliteConnection?.close();
94
100
  this.sqliteConnection = null;
95
101
  this.cachedImplementations = [];
102
+ this.cachedSkipped = [];
96
103
  this.activationFingerprint = null;
97
104
  this.implementationSources.clear();
98
105
  this.emptyLoadWarnEmitted = false;
99
106
  }
100
107
  evaluateDetailed(input) {
101
108
  try {
102
- const activeImpls = this._loadActiveCodeImplementations(input.context?.version === 2);
109
+ const { loaded: activeImpls, skipped } = this._loadActiveCodeImplementations(input.context?.version === 2);
103
110
  const liveImpls = activeImpls.filter((impl) => impl.activationMode === 'live');
104
111
  const shadowImpls = activeImpls.filter((impl) => impl.activationMode === 'shadow');
105
112
  const shadowDecisions = [];
@@ -128,12 +135,12 @@ export class RuleHost {
128
135
  this._recordUnhealthy(source.activationId, source.artifactId, source.ruleId, reason, 'Fix the RuleCode runtime error or return shape, then re-activate the rule');
129
136
  },
130
137
  });
131
- return { liveDecision, shadowDecisions };
138
+ return { liveDecision, shadowDecisions, skippedActivations: skipped };
132
139
  }
133
140
  catch (hostError) {
134
141
  // Conservative degradation: log and return undefined (D-08)
135
142
  this.logger.warn?.(`[RuleHost] Host evaluation failed, degrading conservatively: ${String(hostError)}`);
136
- return { liveDecision: undefined, shadowDecisions: [] };
143
+ return { liveDecision: undefined, shadowDecisions: [], skippedActivations: [] };
137
144
  }
138
145
  }
139
146
  /**
@@ -149,18 +156,18 @@ export class RuleHost {
149
156
  _loadActiveCodeImplementations(supportsContextV2) {
150
157
  if (!this.workspaceDir) {
151
158
  this._emitEmptyLoadWarn('workspaceDir not configured — RuleHost cannot load active code_tool_hook rules', 'Provide workspaceDir when constructing RuleHost (required for code_tool_hook channel)');
152
- return [];
159
+ return { loaded: [], skipped: [] };
153
160
  }
154
161
  try {
155
- const loaded = this._loadFromActivationsTable(this.workspaceDir, supportsContextV2);
162
+ const { loaded, skipped } = this._loadFromActivationsTable(this.workspaceDir, supportsContextV2);
156
163
  if (loaded.length === 0) {
157
164
  this._emitEmptyLoadWarn('armed but empty — 0 active code_tool_hook activations loaded (RuleHost will not block or require approval)', 'If this is unexpected, run `pd runtime activation list --channel code_tool_hook` to inspect activations, or `pd runtime activation promote` to enable a live rule');
158
165
  }
159
- return loaded;
166
+ return { loaded, skipped };
160
167
  }
161
168
  catch (activationError) {
162
169
  this.logger.warn?.(`[RuleHost] Failed to load code_tool_hook activations: ${String(activationError)}`);
163
- return [];
170
+ return { loaded: [], skipped: [] };
164
171
  }
165
172
  }
166
173
  /**
@@ -210,7 +217,7 @@ export class RuleHost {
210
217
  `).all();
211
218
  if (!Array.isArray(rows)) {
212
219
  this.logger.warn?.('[RuleHost] Activations table query returned non-array, skipping');
213
- return [];
220
+ return { loaded: [], skipped: [] };
214
221
  }
215
222
  const fingerprintParts = [supportsContextV2 ? 'context-v2' : 'context-v1'];
216
223
  for (const row of rows) {
@@ -223,7 +230,7 @@ export class RuleHost {
223
230
  }
224
231
  const fingerprint = fingerprintParts.join('\u0002');
225
232
  if (fingerprint === this.activationFingerprint) {
226
- return [...this.cachedImplementations];
233
+ return { loaded: [...this.cachedImplementations], skipped: [...this.cachedSkipped] };
227
234
  }
228
235
  this.implementationSources.clear();
229
236
  // PRI-436: Group active rows by target_ref to detect duplicates.
@@ -283,6 +290,7 @@ export class RuleHost {
283
290
  }
284
291
  }
285
292
  const loaded = [];
293
+ const skipped = [];
286
294
  for (const r of validRows) {
287
295
  const activationId = typeof r['activation_id'] === 'string' ? r['activation_id'] : '';
288
296
  const artifactId = typeof r['artifact_id'] === 'string' ? r['artifact_id'] : '';
@@ -305,8 +313,17 @@ export class RuleHost {
305
313
  ? 'live'
306
314
  : null;
307
315
  if (!activationMode) {
308
- this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported action ${action || '(missing)'}, skipping. ` +
309
- 'nextAction=deactivate and recreate the activation through RuleHostWriter');
316
+ const reason = `unsupported action: ${action || '(missing)'}`;
317
+ const nextAction = 'Deactivate and recreate the activation through RuleHostWriter with action code_tool_hook_shadow_activate or code_tool_hook_live_activate';
318
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: ${reason}, skipping. nextAction=${nextAction}`);
319
+ const skipRecord = {
320
+ activationId,
321
+ ruleId: sourceRuleId ?? artifactId,
322
+ reason,
323
+ nextAction,
324
+ };
325
+ skipped.push(skipRecord);
326
+ this._recordSkipped(activationId, artifactId, sourceRuleId ?? artifactId, undefined, reason, nextAction);
310
327
  continue;
311
328
  }
312
329
  if (activationMode === 'shadow') {
@@ -327,25 +344,63 @@ export class RuleHost {
327
344
  try {
328
345
  const content = JSON.parse(contentJson);
329
346
  if (!content || typeof content !== 'object' || Array.isArray(content)) {
330
- this.logger.warn?.(`[RuleHost] Activation ${activationId}: content_json is not an object, skipping`);
347
+ const reason = 'content_json is not a valid object';
348
+ const nextAction = 'Regenerate the rule artifact with valid JSON content_json';
349
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: ${reason}, skipping. nextAction=${nextAction}`);
350
+ skipped.push({
351
+ activationId,
352
+ ruleId: sourceRuleId ?? artifactId,
353
+ mode: activationMode,
354
+ reason,
355
+ nextAction,
356
+ });
357
+ this._recordSkipped(activationId, artifactId, sourceRuleId ?? artifactId, activationMode, reason, nextAction);
331
358
  continue;
332
359
  }
333
360
  const contentObj = content;
334
361
  if (Object.hasOwn(contentObj, 'requiresContextVersion')) {
335
362
  if (contentObj['requiresContextVersion'] !== 2) {
336
- this.logger.warn?.(`[RuleHost] Activation ${activationId}: unsupported context version; skipping. ` +
337
- 'nextAction=regenerate the rule artifact with requiresContextVersion: 2 or omit the field for v1');
363
+ const reason = `unsupported context version: ${String(contentObj['requiresContextVersion'])}`;
364
+ const nextAction = 'Regenerate the rule artifact with requiresContextVersion: 2 or omit the field for v1';
365
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: ${reason}, skipping. nextAction=${nextAction}`);
366
+ skipped.push({
367
+ activationId,
368
+ ruleId: typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId),
369
+ mode: activationMode,
370
+ reason,
371
+ nextAction,
372
+ });
373
+ this._recordSkipped(activationId, artifactId, typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId), activationMode, reason, nextAction);
338
374
  continue;
339
375
  }
340
376
  if (!supportsContextV2) {
341
- this.logger.warn?.(`[RuleHost] Activation ${activationId}: suspended because rulecode_context_v2 is disabled or unavailable; skipping. ` +
342
- 'nextAction=enable rulecode_context_v2 with valid config or deactivate this activation');
377
+ const reason = 'suspended_by_flag: rulecode_context_v2 is disabled or unavailable';
378
+ const nextAction = 'Enable rulecode_context_v2 with valid config, or deactivate this activation';
379
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: ${reason}, skipping. nextAction=${nextAction}`);
380
+ skipped.push({
381
+ activationId,
382
+ ruleId: typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId),
383
+ mode: activationMode,
384
+ reason,
385
+ nextAction,
386
+ });
387
+ this._recordSkipped(activationId, artifactId, typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId), activationMode, reason, nextAction);
343
388
  continue;
344
389
  }
345
390
  }
346
391
  const implementationCode = contentObj['implementationCode'];
347
392
  if (typeof implementationCode !== 'string' || implementationCode.length === 0) {
348
- this.logger.warn?.(`[RuleHost] Activation ${activationId}: no implementationCode in artifact, skipping`);
393
+ const reason = 'no implementationCode in artifact';
394
+ const nextAction = 'Regenerate the rule artifact with a non-empty implementationCode field';
395
+ this.logger.warn?.(`[RuleHost] Activation ${activationId}: ${reason}, skipping. nextAction=${nextAction}`);
396
+ skipped.push({
397
+ activationId,
398
+ ruleId: typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId),
399
+ mode: activationMode,
400
+ reason,
401
+ nextAction,
402
+ });
403
+ this._recordSkipped(activationId, artifactId, typeof contentObj['ruleId'] === 'string' ? contentObj['ruleId'] : (sourceRuleId ?? artifactId), activationMode, reason, nextAction);
349
404
  continue;
350
405
  }
351
406
  const ruleId = typeof contentObj['ruleId'] === 'string'
@@ -421,7 +476,8 @@ export class RuleHost {
421
476
  }
422
477
  this.activationFingerprint = fingerprint;
423
478
  this.cachedImplementations = loaded;
424
- return loaded;
479
+ this.cachedSkipped = skipped;
480
+ return { loaded, skipped };
425
481
  }
426
482
  }
427
483
  /**
@@ -454,4 +510,32 @@ export class RuleHost {
454
510
  this.logger.warn?.(`[RuleHost] Failed to record unhealthy event for activation ${activationId}: ${String(recordError)}`);
455
511
  }
456
512
  }
513
+ /**
514
+ * PRI-491: Record a skipped activation to EventLog.
515
+ *
516
+ * Unlike _recordUnhealthy (compile/load failures), skipped activations have
517
+ * a configuration/flag reason — the RuleCode itself may be valid, but the
518
+ * runtime chose not to execute it (flag-off v2, unsupported context version,
519
+ * unsupported action, content_json not object, no implementationCode).
520
+ *
521
+ * ERR-002: degradation includes a reason and nextAction (rc-9-no-silent-fallback).
522
+ * Failures in EventLog recording are caught and logged (never throw).
523
+ */
524
+ _recordSkipped(activationId, artifactId, ruleId, mode, reason, nextAction) {
525
+ try {
526
+ const eventLog = EventLogService.get(this.stateDir);
527
+ eventLog.recordRuleHostSkipped({
528
+ activationId,
529
+ artifactId,
530
+ ruleId,
531
+ mode,
532
+ reason,
533
+ nextAction,
534
+ });
535
+ }
536
+ catch (recordError) {
537
+ // EventLog recording must never break RuleHost evaluation
538
+ this.logger.warn?.(`[RuleHost] Failed to record skipped event for activation ${activationId}: ${String(recordError)}`);
539
+ }
540
+ }
457
541
  }
@@ -201,14 +201,18 @@ export class SignalCollectorHost {
201
201
  * 不用 strongPainScore(70),因为 WEAK 单条不该直接顶满 GFI。
202
202
  */
203
203
  routeWeak(output, sessionId) {
204
- // 单向摘要,不暴露 sessionId 明文(CodeRabbit #4)
205
- const hash = createHash('sha256')
206
- .update(`${output.evidence.detectedAt}:${sessionId}`)
207
- .digest('hex')
208
- .slice(0, 32);
209
- trackFriction(sessionId, 20, hash, this.wctx.workspaceDir, {
210
- source: 'user_empathy',
211
- });
204
+ try {
205
+ const hash = createHash('sha256')
206
+ .update(`${output.evidence.detectedAt}:${sessionId}`)
207
+ .digest('hex')
208
+ .slice(0, 32);
209
+ trackFriction(sessionId, 20, hash, this.wctx.workspaceDir, {
210
+ source: 'user_empathy',
211
+ });
212
+ }
213
+ catch (e) {
214
+ SystemLogger.log(this.wctx.workspaceDir, 'SIGNAL_TRACK_FRICTION_FAIL', `trackFriction threw: ${String(e)}`);
215
+ }
212
216
  }
213
217
  /**
214
218
  * STRONG rate limit 门控:成功消耗一个名额返回 true;超限返回 false。
@@ -1,2 +1,2 @@
1
- export type { EventType, EventCategory, EventLogEntry, ToolCallEventData, PainSignalEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, EmpathyRollbackEventData, HeartbeatDiagnosisEventData, DiagnosisTaskEventData, DiagnosticianReportEventData, PrincipleCandidateEventData, RuleEnforcedEventData, RuleHostEvaluatedEventData, RuleHostBlockedEventData, RuleHostRequireApprovalEventData, RuleHostAutoCorrectProposedEventData, RuleHostAutoCorrectAppliedEventData, RuntimeV2PromptActivationsInjectedEventData, RuleHostUnhealthyEventData, ToolCallStats, ErrorStats, EmpathyEventStats, GfiStats, EvolutionStats as EventEvolutionStats, HookStats, DailyStats, } from '@principles/core/runtime-v2';
1
+ export type { EventType, EventCategory, EventLogEntry, ToolCallEventData, PainSignalEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, GateBypassEventData, PlanApprovalEventData, EvolutionTaskEventData, EmpathyRollbackEventData, HeartbeatDiagnosisEventData, DiagnosisTaskEventData, DiagnosticianReportEventData, PrincipleCandidateEventData, RuleEnforcedEventData, RuleHostEvaluatedEventData, RuleHostBlockedEventData, RuleHostRequireApprovalEventData, RuleHostAutoCorrectProposedEventData, RuleHostAutoCorrectAppliedEventData, RuntimeV2PromptActivationsInjectedEventData, RuleHostUnhealthyEventData, RuleHostSkippedEventData, ToolCallStats, ErrorStats, EmpathyEventStats, GfiStats, EvolutionStats as EventEvolutionStats, HookStats, DailyStats, } from '@principles/core/runtime-v2';
2
2
  export { createEmptyDailyStats, } from '@principles/core/runtime-v2';
@@ -4,7 +4,7 @@ import { createHash } from 'crypto';
4
4
  * to allow for consistent loop detection even if the raw text varies slightly.
5
5
  */
6
6
  export function denoiseError(text) {
7
- if (!text)
7
+ if (!text || typeof text !== 'string')
8
8
  return '';
9
9
  return text
10
10
  // Strip ISO timestamps and common date formats
@@ -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.176.0",
5
+ "version": "1.178.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.176.0",
3
+ "version": "1.178.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",