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.
- package/openclaw.plugin.json +4 -4
- package/package.json +1 -1
- package/scripts/diagnose-nocturnal.mjs +11 -4
- package/src/service/evolution-worker.ts +3 -0
- package/src/service/nocturnal-runtime.ts +14 -9
- package/src/service/nocturnal-service.ts +11 -4
- package/src/service/nocturnal-target-selector.ts +4 -2
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +8 -2
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.25.0",
|
|
6
6
|
"skills": [
|
|
7
7
|
"./skills"
|
|
8
8
|
],
|
|
@@ -76,8 +76,8 @@
|
|
|
76
76
|
}
|
|
77
77
|
},
|
|
78
78
|
"buildFingerprint": {
|
|
79
|
-
"gitSha": "
|
|
80
|
-
"bundleMd5": "
|
|
81
|
-
"builtAt": "2026-04-
|
|
79
|
+
"gitSha": "6c8a61389b46",
|
|
80
|
+
"bundleMd5": "2803099a748622cc50286ad4c55be551",
|
|
81
|
+
"builtAt": "2026-04-13T02:02:51.311Z"
|
|
82
82
|
}
|
|
83
83
|
}
|
package/package.json
CHANGED
|
@@ -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
|
-
|
|
322
|
-
|
|
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 (
|
|
575
|
-
|
|
576
|
-
|
|
575
|
+
if (!skipGatesForManualTrigger) {
|
|
576
|
+
if (cooldown.globalCooldownActive) {
|
|
577
|
+
blockers.push(`Global cooldown active until ${cooldown.globalCooldownUntil}`);
|
|
578
|
+
}
|
|
577
579
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
580
|
+
if (cooldown.principleCooldownActive) {
|
|
581
|
+
blockers.push(`Principle cooldown active until ${cooldown.principleCooldownUntil}`);
|
|
582
|
+
}
|
|
581
583
|
|
|
582
|
-
|
|
583
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
176
|
-
|
|
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
|
}
|