principles-disciple 1.24.0 → 1.25.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.24.0",
5
+ "version": "1.25.0",
6
6
  "skills": [
7
7
  "./skills"
8
8
  ],
@@ -76,8 +76,8 @@
76
76
  }
77
77
  },
78
78
  "buildFingerprint": {
79
- "gitSha": "ebbaa40d6e3a",
80
- "bundleMd5": "7c84860901894f7c049b54028d489ed4",
81
- "builtAt": "2026-04-12T15:51:34.724Z"
79
+ "gitSha": "6c8a61389b46",
80
+ "bundleMd5": "2803099a748622cc50286ad4c55be551",
81
+ "builtAt": "2026-04-13T02:02:51.311Z"
82
82
  }
83
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "principles-disciple",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Native OpenClaw plugin for Principles Disciple",
5
5
  "type": "module",
6
6
  "main": "./dist/bundle.js",
@@ -126,6 +126,9 @@ function main() {
126
126
  if (data.sessionId && data.lastActivityAt) validSessions++;
127
127
  } catch { /* corrupted, skip */ }
128
128
  }
129
+ if (validSessions === 0) {
130
+ return { status: 'warn', detail: `${files.length} session file(s) found but none are valid JSON — idle check will likely report idle immediately` };
131
+ }
129
132
  return `${files.length} session files, ${validSessions} valid with sessionId+lastActivityAt`;
130
133
  });
131
134
 
@@ -221,7 +224,7 @@ function main() {
221
224
  if (active.length > 0) {
222
225
  return { status: 'warn', detail: `${active.length} workflow(s) still active — may be in progress or stuck. IDs: ${active.map(w => w.workflow_id).join(', ')}` };
223
226
  }
224
- return `${workflows.length} total: ${completed} completed, ${errored} errored, ${expired} expired`;
227
+ return `${workflows.length} total: ${completed.length} completed, ${errored.length} errored, ${expired.length} expired`;
225
228
  });
226
229
 
227
230
  // ─────────────────────────────────────────────────────────
@@ -318,10 +321,14 @@ function main() {
318
321
  fields[line.substring(0, colonIdx).trim()] = line.substring(colonIdx + 1).trim();
319
322
  }
320
323
  }
321
- if (!fields.score || !fields.reason) {
322
- return { status: 'warn', detail: 'Pain flag exists but is missing required fields (score, reason)' };
324
+ // Accept either the legacy contract (fields.score + fields.reason)
325
+ // or the current contract (active.source.score)
326
+ const legacy = fields.score && fields.reason;
327
+ const current = fields.active && fields.source && fields.score;
328
+ if (!legacy && !current) {
329
+ return { status: 'warn', detail: 'Pain flag exists but is missing required fields (need score + reason, or active.source.score)' };
323
330
  }
324
- return `Pain flag active (score: ${fields.score}, source: ${fields.source || 'unknown'}, session: ${fields.session_id || 'none'})`;
331
+ return `Pain flag active (score: ${fields.score}, source: ${fields.source || fields.active?.source || 'unknown'}, session: ${fields.session_id || 'none'})`;
325
332
  });
326
333
 
327
334
  // ─────────────────────────────────────────────────────────
@@ -1003,6 +1003,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1003
1003
  logger: api?.logger || logger,
1004
1004
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- Reason: api is guaranteed non-null in this recovery path where runtimeAdapter is required
1005
1005
  runtimeAdapter: new OpenClawTrinityRuntimeAdapter(api!),
1006
+ subagent: api?.runtime?.subagent,
1006
1007
  });
1007
1008
  try {
1008
1009
  // Force-expire this specific workflow regardless of TTL
@@ -1596,6 +1597,7 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
1596
1597
  stateDir: wctx.stateDir,
1597
1598
  logger: api.logger,
1598
1599
  runtimeAdapter: new OpenClawTrinityRuntimeAdapter(api),
1600
+ subagent: api.runtime.subagent,
1599
1601
  });
1600
1602
 
1601
1603
  if (!isPollingTask) {
@@ -2096,6 +2098,7 @@ export const EvolutionWorkerService: ExtendedEvolutionWorkerService = {
2096
2098
  stateDir: wctx.stateDir,
2097
2099
  logger: api.logger,
2098
2100
  runtimeAdapter: new OpenClawTrinityRuntimeAdapter(api),
2101
+ subagent: api.runtime.subagent,
2099
2102
  });
2100
2103
  swept += await nocturnalMgr.sweepExpiredWorkflows(WORKFLOW_TTL_MS, subagentRuntime, agentSession);
2101
2104
  nocturnalMgr.dispose();
@@ -560,7 +560,8 @@ export function checkPreflight(
560
560
  stateDir: string,
561
561
  principleId?: string,
562
562
  trajectoryLastActivityAt?: number,
563
- idleCheckOverride?: IdleCheckResult
563
+ idleCheckOverride?: IdleCheckResult,
564
+ skipGatesForManualTrigger?: boolean
564
565
  ): PreflightCheckResult {
565
566
  const idle = idleCheckOverride ?? checkWorkspaceIdle(workspaceDir, {}, trajectoryLastActivityAt);
566
567
  const cooldown = checkCooldown(stateDir, principleId);
@@ -571,16 +572,20 @@ export function checkPreflight(
571
572
  blockers.push(`Workspace not idle (active for ${idle.idleForMs}ms, threshold=${DEFAULT_IDLE_THRESHOLD_MS}ms)`);
572
573
  }
573
574
 
574
- if (cooldown.globalCooldownActive) {
575
- blockers.push(`Global cooldown active until ${cooldown.globalCooldownUntil}`);
576
- }
575
+ if (!skipGatesForManualTrigger) {
576
+ if (cooldown.globalCooldownActive) {
577
+ blockers.push(`Global cooldown active until ${cooldown.globalCooldownUntil}`);
578
+ }
577
579
 
578
- if (cooldown.principleCooldownActive) {
579
- blockers.push(`Principle cooldown active until ${cooldown.principleCooldownUntil}`);
580
- }
580
+ if (cooldown.principleCooldownActive) {
581
+ blockers.push(`Principle cooldown active until ${cooldown.principleCooldownUntil}`);
582
+ }
581
583
 
582
- if (cooldown.quotaExhausted) {
583
- blockers.push(`Quota exhausted (${DEFAULT_MAX_RUNS_PER_WINDOW} runs per ${DEFAULT_QUOTA_WINDOW_MS / 3600000}h window)`);
584
+ if (cooldown.quotaExhausted) {
585
+ blockers.push(`Quota exhausted (${DEFAULT_MAX_RUNS_PER_WINDOW} runs per ${DEFAULT_QUOTA_WINDOW_MS / 3600000}h window)`);
586
+ }
587
+ } else if (cooldown.globalCooldownActive || cooldown.principleCooldownActive || cooldown.quotaExhausted) {
588
+ // Log that gates are being bypassed for manual trigger
584
589
  }
585
590
 
586
591
  if (idle.abandonedSessionIds.length > 0 && idle.userActiveSessions === 0) {
@@ -686,7 +686,8 @@ export function executeNocturnalReflection(
686
686
  stateDir,
687
687
  undefined, // principleId
688
688
  undefined, // trajectoryLastActivityAt
689
- options.idleCheckOverride
689
+ options.idleCheckOverride,
690
+ !!options.idleCheckOverride // skip cooldown/quota gates for manual/test triggers
690
691
  );
691
692
  diagnostics.preflight = preflight;
692
693
 
@@ -920,11 +921,15 @@ export function executeNocturnalReflection(
920
921
  // -------------------------------------------------------------------------
921
922
  // Step 6: Arbiter validation
922
923
  // -------------------------------------------------------------------------
924
+ // #256: Use 0 for thinkingModelDeltaMin — Trinity chain (Dreamer→Philosopher→Scribe)
925
+ // already ensures quality. A delta of 0 is valid when both bad and better decisions
926
+ // show equally well-reasoned thinking (the Scribe's job is to contrast decisions,
927
+ // not to make one sound more "cognitive" than the other).
923
928
  const arbiterResult = parseAndValidateArtifact(rawJson, {
924
929
  expectedPrincipleId: selectedPrincipleId,
925
930
  expectedSessionId: selectedSessionId,
926
931
  qualityThresholds: {
927
- thinkingModelDeltaMin: 0.01,
932
+ thinkingModelDeltaMin: 0,
928
933
  planningRatioGainMin: -0.5,
929
934
  },
930
935
  });
@@ -1153,7 +1158,8 @@ async function executeNocturnalReflectionWithAdapter(
1153
1158
  stateDir,
1154
1159
  undefined,
1155
1160
  undefined,
1156
- options.idleCheckOverride
1161
+ options.idleCheckOverride,
1162
+ !!options.idleCheckOverride // skip cooldown/quota gates for manual/test triggers
1157
1163
  );
1158
1164
  diagnostics.preflight = preflight;
1159
1165
 
@@ -1354,11 +1360,12 @@ async function executeNocturnalReflectionWithAdapter(
1354
1360
  }
1355
1361
 
1356
1362
  // Step 5: Arbiter validation
1363
+ // #256: Use 0 for thinkingModelDeltaMin — Trinity chain already ensures quality
1357
1364
  const arbiterResult = parseAndValidateArtifact(rawJson, {
1358
1365
  expectedPrincipleId: selectedPrincipleId,
1359
1366
  expectedSessionId: selectedSessionId,
1360
1367
  qualityThresholds: {
1361
- thinkingModelDeltaMin: 0.01,
1368
+ thinkingModelDeltaMin: 0,
1362
1369
  planningRatioGainMin: -0.5,
1363
1370
  },
1364
1371
  });
@@ -347,11 +347,13 @@ export class NocturnalTargetSelector {
347
347
  }
348
348
 
349
349
  // Step 2: Cooldown and quota check
350
+ // #256: Skip cooldown/quota for manual/test triggers (idleCheckOverride present)
351
+ const skipGates = !!this.idleCheckOverride;
350
352
  const cooldownResult = checkCooldown(this.stateDir);
351
353
  diagnostics.cooldownCheckPassed = !cooldownResult.globalCooldownActive;
352
354
  diagnostics.quotaCheckPassed = !cooldownResult.quotaExhausted;
353
355
 
354
- if (cooldownResult.globalCooldownActive) {
356
+ if (!skipGates && cooldownResult.globalCooldownActive) {
355
357
  return {
356
358
  decision: 'skip',
357
359
  skipReason: 'global_cooldown_active',
@@ -359,7 +361,7 @@ export class NocturnalTargetSelector {
359
361
  };
360
362
  }
361
363
 
362
- if (cooldownResult.quotaExhausted) {
364
+ if (!skipGates && cooldownResult.quotaExhausted) {
363
365
  return {
364
366
  decision: 'skip',
365
367
  skipReason: 'quota_exhausted',
@@ -41,6 +41,7 @@ import type { RecentPainContext } from '../evolution-worker.js';
41
41
  import * as fs from 'fs';
42
42
  import * as path from 'path';
43
43
  import { validateNocturnalSnapshotIngress } from '../../core/nocturnal-snapshot-contract.js';
44
+ import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
44
45
 
45
46
  // ─────────────────────────────────────────────────────────────────────────────
46
47
  // NocturnalResult Type Alias
@@ -65,6 +66,8 @@ export interface NocturnalWorkflowOptions {
65
66
  logger: PluginLogger;
66
67
  /** Trinity runtime adapter for subagent execution */
67
68
  runtimeAdapter: TrinityRuntimeAdapter;
69
+ /** Subagent runtime for availability probing (#254) */
70
+ subagent?: { run?: unknown };
68
71
  }
69
72
 
70
73
  // ─────────────────────────────────────────────────────────────────────────────
@@ -138,6 +141,7 @@ export class NocturnalWorkflowManager implements WorkflowManager {
138
141
  private readonly stateDir: string;
139
142
  private readonly logger: PluginLogger;
140
143
  private readonly runtimeAdapter: TrinityRuntimeAdapter;
144
+ private readonly subagent: { run?: unknown } | undefined;
141
145
  private readonly store: WorkflowStore;
142
146
 
143
147
  /** Tracks completion timestamps for idempotency */
@@ -156,6 +160,7 @@ export class NocturnalWorkflowManager implements WorkflowManager {
156
160
  this.stateDir = opts.stateDir;
157
161
  this.logger = opts.logger;
158
162
  this.runtimeAdapter = opts.runtimeAdapter;
163
+ this.subagent = opts.subagent;
159
164
  this.store = new WorkflowStore({ workspaceDir: opts.workspaceDir });
160
165
  }
161
166
 
@@ -172,8 +177,9 @@ export class NocturnalWorkflowManager implements WorkflowManager {
172
177
  metadata?: Record<string, unknown>;
173
178
  }
174
179
  ): Promise<WorkflowHandle> {
175
- const runtimeAvailable = this.runtimeAdapter.isRuntimeAvailable();
176
- if (!runtimeAvailable) {
180
+ // #254: Use isSubagentRuntimeAvailable instead of runtimeAdapter.isRuntimeAvailable()
181
+ // (which always returns true in OpenClawTrinityRuntimeAdapter)
182
+ if (!isSubagentRuntimeAvailable(this.subagent)) {
177
183
  this.logger.warn(`[PD:NocturnalWorkflow] Subagent runtime unavailable, skipping workflow`);
178
184
  throw new Error(`NocturnalWorkflowManager: subagent runtime unavailable`);
179
185
  }