principles-disciple 1.34.1 → 1.35.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/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/event-log.ts +87 -20
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +30 -35
- package/src/service/nocturnal-service.ts +72 -47
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
import * as fs from 'fs';
|
|
32
32
|
import * as path from 'path';
|
|
33
33
|
import { randomUUID } from 'crypto';
|
|
34
|
-
import type { RecentPainContext } from './
|
|
34
|
+
import type { RecentPainContext } from './subagent-workflow/types.js';
|
|
35
|
+
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
35
36
|
import {
|
|
36
37
|
createNocturnalTrajectoryExtractor,
|
|
37
38
|
computeThinkingModelDelta,
|
|
@@ -103,6 +104,15 @@ import { getPrincipleState, setPrincipleState } from '../core/principle-training
|
|
|
103
104
|
import type { Implementation } from '../types/principle-tree-schema.js';
|
|
104
105
|
import { validateNocturnalSnapshotIngress } from '../core/nocturnal-snapshot-contract.js';
|
|
105
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Atomic file write — write to temp then rename to prevent partial writes on crash.
|
|
109
|
+
*/
|
|
110
|
+
function atomicWriteFileSync(filePath: string, data: string): void {
|
|
111
|
+
const tmpPath = filePath + '.tmp';
|
|
112
|
+
fs.writeFileSync(tmpPath, data, 'utf8');
|
|
113
|
+
fs.renameSync(tmpPath, filePath);
|
|
114
|
+
}
|
|
115
|
+
|
|
106
116
|
// ---------------------------------------------------------------------------
|
|
107
117
|
// #251: Sync trainingStore sample counts after registration
|
|
108
118
|
// ---------------------------------------------------------------------------
|
|
@@ -113,6 +123,7 @@ function incrementGeneratedSampleCount(stateDir: string, principleId: string): v
|
|
|
113
123
|
state.generatedSampleCount += 1;
|
|
114
124
|
setPrincipleState(stateDir, state);
|
|
115
125
|
} catch (err) {
|
|
126
|
+
// eslint-disable-next-line no-console -- Non-critical warning in helper function
|
|
116
127
|
console.warn(`[nocturnal-service] Failed to sync generatedSampleCount for ${principleId}:`, err instanceof Error ? err.stack : err);
|
|
117
128
|
}
|
|
118
129
|
}
|
|
@@ -259,6 +270,12 @@ export interface NocturnalServiceOptions {
|
|
|
259
270
|
* When omitted, a deterministic local candidate is synthesized.
|
|
260
271
|
*/
|
|
261
272
|
artificerOutputOverride?: string;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Logger for diagnostic output.
|
|
276
|
+
* When provided, warnings are logged via logger.warn instead of console.warn.
|
|
277
|
+
*/
|
|
278
|
+
logger?: PluginLogger;
|
|
262
279
|
}
|
|
263
280
|
|
|
264
281
|
// ---------------------------------------------------------------------------
|
|
@@ -290,16 +307,16 @@ function invokeStubReflector(
|
|
|
290
307
|
const hasPain = snapshot.stats.totalPainEvents > 0;
|
|
291
308
|
const hasFailures = (snapshot.stats.failureCount ?? 0) > 0;
|
|
292
309
|
|
|
293
|
-
|
|
310
|
+
|
|
294
311
|
let badDecision: string;
|
|
295
|
-
|
|
312
|
+
|
|
296
313
|
let betterDecision: string;
|
|
297
|
-
|
|
314
|
+
|
|
298
315
|
let rationale: string;
|
|
299
316
|
|
|
300
317
|
if (hasGateBlocks && snapshot.gateBlocks.length > 0) {
|
|
301
318
|
// Use actual gate block content
|
|
302
|
-
|
|
319
|
+
|
|
303
320
|
const block = snapshot.gateBlocks[0];
|
|
304
321
|
const tool = block.toolName ?? 'a tool';
|
|
305
322
|
const file = block.filePath ? ` on ${block.filePath}` : '';
|
|
@@ -308,7 +325,7 @@ function invokeStubReflector(
|
|
|
308
325
|
rationale = `Gate blocks exist for a reason — bypassing them without understanding the underlying constraint risks unintended consequences. The block on ${tool}${file} indicates the operation exceeded allowed thresholds for the current evolution tier.`;
|
|
309
326
|
} else if (hasPain && snapshot.painEvents.length > 0) {
|
|
310
327
|
// Use actual pain event content
|
|
311
|
-
|
|
328
|
+
|
|
312
329
|
const pain = snapshot.painEvents[0];
|
|
313
330
|
const painSource = pain.source ?? 'unknown';
|
|
314
331
|
const painReason = pain.reason ? `: ${pain.reason}` : '';
|
|
@@ -385,10 +402,7 @@ function persistArtifact(
|
|
|
385
402
|
fs.mkdirSync(dir, { recursive: true });
|
|
386
403
|
}
|
|
387
404
|
|
|
388
|
-
|
|
389
|
-
const tmpPath = artifactPath + '.tmp';
|
|
390
|
-
fs.writeFileSync(tmpPath, JSON.stringify(sampleRecord, null, 2), 'utf8');
|
|
391
|
-
fs.renameSync(tmpPath, artifactPath);
|
|
405
|
+
atomicWriteFileSync(artifactPath, JSON.stringify(sampleRecord, null, 2));
|
|
392
406
|
return artifactPath;
|
|
393
407
|
}
|
|
394
408
|
|
|
@@ -407,7 +421,7 @@ function buildGateBlockRefs(snapshot: NocturnalSessionSnapshot): string[] {
|
|
|
407
421
|
}
|
|
408
422
|
|
|
409
423
|
|
|
410
|
-
|
|
424
|
+
|
|
411
425
|
function buildDefaultArtificerOutput(
|
|
412
426
|
ruleId: string,
|
|
413
427
|
artifact: NocturnalArtifact,
|
|
@@ -457,7 +471,7 @@ function buildDefaultArtificerOutput(
|
|
|
457
471
|
}
|
|
458
472
|
|
|
459
473
|
|
|
460
|
-
|
|
474
|
+
|
|
461
475
|
function persistCodeCandidate(
|
|
462
476
|
workspaceDir: string,
|
|
463
477
|
stateDir: string,
|
|
@@ -515,6 +529,7 @@ function persistCodeCandidate(
|
|
|
515
529
|
try {
|
|
516
530
|
refreshPrincipleLifecycle(workspaceDir, stateDir);
|
|
517
531
|
} catch (err) {
|
|
532
|
+
// eslint-disable-next-line no-console -- Non-critical warning in helper function
|
|
518
533
|
console.warn('[nocturnal-service] Lifecycle refresh failed after code candidate persistence:', err instanceof Error ? err.stack : err);
|
|
519
534
|
}
|
|
520
535
|
return {
|
|
@@ -554,7 +569,7 @@ function persistCodeCandidate(
|
|
|
554
569
|
}
|
|
555
570
|
|
|
556
571
|
|
|
557
|
-
|
|
572
|
+
|
|
558
573
|
function maybePersistArtificerCandidate(
|
|
559
574
|
workspaceDir: string,
|
|
560
575
|
stateDir: string,
|
|
@@ -692,6 +707,11 @@ export function executeNocturnalReflection(
|
|
|
692
707
|
stateDir: string,
|
|
693
708
|
options: NocturnalServiceOptions = {}
|
|
694
709
|
): NocturnalRunResult {
|
|
710
|
+
// Use provided logger or fallback to console
|
|
711
|
+
const logger = options.logger;
|
|
712
|
+
// eslint-disable-next-line no-console -- Intentional console fallback when no logger provided
|
|
713
|
+
const warn = logger?.warn?.bind(logger) ?? console.warn.bind(console);
|
|
714
|
+
|
|
695
715
|
const diagnostics: NocturnalRunDiagnostics = {
|
|
696
716
|
preflight: null,
|
|
697
717
|
selection: null,
|
|
@@ -791,18 +811,18 @@ export function executeNocturnalReflection(
|
|
|
791
811
|
// The async version would be used in real worker integration
|
|
792
812
|
const config = loadNocturnalConfig(stateDir);
|
|
793
813
|
void recordRunStart(stateDir, selectedPrincipleId, config.cooldown_ms).catch((err) => {
|
|
794
|
-
|
|
814
|
+
warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
|
|
795
815
|
});
|
|
796
816
|
|
|
797
817
|
// -------------------------------------------------------------------------
|
|
798
818
|
// Step 5: Artifact generation (Trinity or single-reflector)
|
|
799
819
|
// -------------------------------------------------------------------------
|
|
800
820
|
|
|
801
|
-
|
|
821
|
+
|
|
802
822
|
let trinityArtifact: TrinityDraftArtifact | null = null;
|
|
803
823
|
let trinityResult: TrinityResult | null = null;
|
|
804
824
|
|
|
805
|
-
|
|
825
|
+
|
|
806
826
|
let rawJson: string;
|
|
807
827
|
|
|
808
828
|
if (options.skipReflector) {
|
|
@@ -828,7 +848,7 @@ export function executeNocturnalReflection(
|
|
|
828
848
|
// Trinity failed — fail closed (same semantics as production)
|
|
829
849
|
const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
|
|
830
850
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity override failed: ${failures.join('; ')}` }).catch((err) => {
|
|
831
|
-
|
|
851
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
832
852
|
});
|
|
833
853
|
// Emit threshold signals: malformed Trinity override is a strong signal
|
|
834
854
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -851,7 +871,7 @@ export function executeNocturnalReflection(
|
|
|
851
871
|
if (!draftValidation.valid) {
|
|
852
872
|
const {failures} = draftValidation;
|
|
853
873
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
|
|
854
|
-
|
|
874
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
855
875
|
});
|
|
856
876
|
// Emit threshold signals: malformed draft content is a strong signal
|
|
857
877
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -902,7 +922,7 @@ export function executeNocturnalReflection(
|
|
|
902
922
|
// Trinity draft invalid — fail closed
|
|
903
923
|
const {failures} = draftValidation;
|
|
904
924
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
|
|
905
|
-
|
|
925
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
906
926
|
});
|
|
907
927
|
// Emit threshold signals: malformed draft content is a strong signal
|
|
908
928
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -929,7 +949,7 @@ export function executeNocturnalReflection(
|
|
|
929
949
|
// Phase 6 requirement: malformed Trinity stage output fails closed
|
|
930
950
|
const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
|
|
931
951
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity chain failed: ${failures.join('; ')}` }).catch((err) => {
|
|
932
|
-
|
|
952
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
933
953
|
});
|
|
934
954
|
// Emit threshold signals: malformed Trinity is the strongest signal for tightening schema threshold
|
|
935
955
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -973,7 +993,7 @@ export function executeNocturnalReflection(
|
|
|
973
993
|
if (!arbiterResult.passed || !arbiterResult.artifact) {
|
|
974
994
|
const failures = arbiterResult.failures.map((f) => f.reason);
|
|
975
995
|
void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
|
|
976
|
-
|
|
996
|
+
warn(`[nocturnal-service] Failed to record run end (arbiter failed): ${String(err)}`);
|
|
977
997
|
});
|
|
978
998
|
// Emit threshold signals: arbiter rejection indicates principle alignment issues
|
|
979
999
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -999,7 +1019,7 @@ export function executeNocturnalReflection(
|
|
|
999
1019
|
if (!execResult.executable) {
|
|
1000
1020
|
const failures = execResult.failures.map((f) => f.reason);
|
|
1001
1021
|
void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
|
|
1002
|
-
|
|
1022
|
+
warn(`[nocturnal-service] Failed to record run end (executability failed): ${String(err)}`);
|
|
1003
1023
|
});
|
|
1004
1024
|
// Emit threshold signals: executability rejection indicates action quality issues
|
|
1005
1025
|
adjustThresholdsFromSignals(stateDir, {
|
|
@@ -1027,7 +1047,7 @@ export function executeNocturnalReflection(
|
|
|
1027
1047
|
};
|
|
1028
1048
|
|
|
1029
1049
|
|
|
1030
|
-
|
|
1050
|
+
|
|
1031
1051
|
let persistedPath: string;
|
|
1032
1052
|
try {
|
|
1033
1053
|
persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
|
|
@@ -1035,7 +1055,7 @@ export function executeNocturnalReflection(
|
|
|
1035
1055
|
diagnostics.persistedPath = persistedPath;
|
|
1036
1056
|
} catch (err) {
|
|
1037
1057
|
void recordRunEnd(stateDir, 'failed', { reason: `persistence error: ${String(err)}` }).catch((e) => {
|
|
1038
|
-
|
|
1058
|
+
warn(`[nocturnal-service] Failed to record run end (persistence failed): ${String(e)}`);
|
|
1039
1059
|
});
|
|
1040
1060
|
return {
|
|
1041
1061
|
success: false,
|
|
@@ -1060,7 +1080,7 @@ export function executeNocturnalReflection(
|
|
|
1060
1080
|
} catch (err) {
|
|
1061
1081
|
// Non-fatal: artifact is persisted, registry is secondary.
|
|
1062
1082
|
// Log but don't fail the run.
|
|
1063
|
-
|
|
1083
|
+
warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
|
|
1064
1084
|
}
|
|
1065
1085
|
|
|
1066
1086
|
try {
|
|
@@ -1078,7 +1098,7 @@ export function executeNocturnalReflection(
|
|
|
1078
1098
|
createdAt: arbiterResult.artifact.createdAt,
|
|
1079
1099
|
});
|
|
1080
1100
|
} catch (err) {
|
|
1081
|
-
|
|
1101
|
+
warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
|
|
1082
1102
|
}
|
|
1083
1103
|
|
|
1084
1104
|
diagnostics.artificer = maybePersistArtificerCandidate(
|
|
@@ -1095,7 +1115,7 @@ export function executeNocturnalReflection(
|
|
|
1095
1115
|
// Step 9: Record run success
|
|
1096
1116
|
// -------------------------------------------------------------------------
|
|
1097
1117
|
void recordRunEnd(stateDir, 'success', { sampleCount: 1 }).catch((err) => {
|
|
1098
|
-
|
|
1118
|
+
warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
|
|
1099
1119
|
});
|
|
1100
1120
|
|
|
1101
1121
|
// -------------------------------------------------------------------------
|
|
@@ -1155,7 +1175,7 @@ export async function executeNocturnalReflectionAsync(
|
|
|
1155
1175
|
// If runtime adapter is provided, use async Trinity path
|
|
1156
1176
|
if (options.runtimeAdapter) {
|
|
1157
1177
|
|
|
1158
|
-
|
|
1178
|
+
|
|
1159
1179
|
return executeNocturnalReflectionWithAdapter(workspaceDir, stateDir, options);
|
|
1160
1180
|
}
|
|
1161
1181
|
|
|
@@ -1172,6 +1192,11 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1172
1192
|
stateDir: string,
|
|
1173
1193
|
options: NocturnalServiceOptions
|
|
1174
1194
|
): Promise<NocturnalRunResult> {
|
|
1195
|
+
// Use provided logger or fallback to console
|
|
1196
|
+
const logger = options.logger;
|
|
1197
|
+
// eslint-disable-next-line no-console -- Intentional console fallback when no logger provided
|
|
1198
|
+
const warn = logger?.warn?.bind(logger) ?? console.warn.bind(console);
|
|
1199
|
+
|
|
1175
1200
|
const diagnostics: NocturnalRunDiagnostics = {
|
|
1176
1201
|
preflight: null,
|
|
1177
1202
|
selection: null,
|
|
@@ -1213,13 +1238,13 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1213
1238
|
|
|
1214
1239
|
// Step 2: Target selection (or use override to skip)
|
|
1215
1240
|
|
|
1216
|
-
|
|
1241
|
+
|
|
1217
1242
|
let selectedPrincipleId: string | undefined;
|
|
1218
1243
|
|
|
1219
|
-
|
|
1244
|
+
|
|
1220
1245
|
let selectedSessionId: string | undefined;
|
|
1221
1246
|
|
|
1222
|
-
|
|
1247
|
+
|
|
1223
1248
|
let snapshot: NocturnalSessionSnapshot | null = null;
|
|
1224
1249
|
|
|
1225
1250
|
if (options.principleIdOverride && options.snapshotOverride) {
|
|
@@ -1242,7 +1267,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1242
1267
|
// Skip Selector: use provided principleId and snapshot directly
|
|
1243
1268
|
selectedPrincipleId = options.principleIdOverride;
|
|
1244
1269
|
selectedSessionId = snapshotValidation.snapshot.sessionId;
|
|
1245
|
-
|
|
1270
|
+
|
|
1246
1271
|
snapshot = snapshotValidation.snapshot;
|
|
1247
1272
|
// Calculate violation density from snapshot stats for meaningful diagnostics
|
|
1248
1273
|
const snapStats = snapshotValidation.snapshot.stats;
|
|
@@ -1299,10 +1324,10 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1299
1324
|
}
|
|
1300
1325
|
|
|
1301
1326
|
|
|
1302
|
-
|
|
1327
|
+
|
|
1303
1328
|
selectedPrincipleId = selection.selectedPrincipleId;
|
|
1304
1329
|
|
|
1305
|
-
|
|
1330
|
+
|
|
1306
1331
|
selectedSessionId = selection.selectedSessionId;
|
|
1307
1332
|
|
|
1308
1333
|
if (!selectedPrincipleId || !selectedSessionId) {
|
|
@@ -1332,16 +1357,16 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1332
1357
|
// Step 3: Record run start
|
|
1333
1358
|
const config = loadNocturnalConfig(stateDir);
|
|
1334
1359
|
void recordRunStart(stateDir, selectedPrincipleId, config.cooldown_ms).catch((err) => {
|
|
1335
|
-
|
|
1360
|
+
warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
|
|
1336
1361
|
});
|
|
1337
1362
|
|
|
1338
1363
|
// Step 4: Trinity execution via adapter (async)
|
|
1339
1364
|
|
|
1340
|
-
|
|
1365
|
+
|
|
1341
1366
|
let trinityArtifact: TrinityDraftArtifact | null = null;
|
|
1342
1367
|
let trinityResult: TrinityResult | null = null;
|
|
1343
1368
|
|
|
1344
|
-
|
|
1369
|
+
|
|
1345
1370
|
let rawJson: string;
|
|
1346
1371
|
|
|
1347
1372
|
if (options.skipReflector) {
|
|
@@ -1364,7 +1389,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1364
1389
|
if (!trinityResult.success) {
|
|
1365
1390
|
const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
|
|
1366
1391
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity override failed: ${failures.join('; ')}` }).catch((err) => {
|
|
1367
|
-
|
|
1392
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
1368
1393
|
});
|
|
1369
1394
|
adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
|
|
1370
1395
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Trinity override failed: ${failures.join('; ')}`], snapshot, diagnostics };
|
|
@@ -1391,7 +1416,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1391
1416
|
if (!draftValidation.valid) {
|
|
1392
1417
|
const {failures} = draftValidation;
|
|
1393
1418
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
|
|
1394
|
-
|
|
1419
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
1395
1420
|
});
|
|
1396
1421
|
adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
|
|
1397
1422
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, snapshot, diagnostics };
|
|
@@ -1402,7 +1427,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1402
1427
|
} else {
|
|
1403
1428
|
const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
|
|
1404
1429
|
void recordRunEnd(stateDir, 'failed', { reason: `Trinity chain failed: ${failures.join('; ')}` }).catch((err) => {
|
|
1405
|
-
|
|
1430
|
+
warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
|
|
1406
1431
|
});
|
|
1407
1432
|
adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
|
|
1408
1433
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Trinity chain failed: ${failures.join('; ')}`], snapshot, diagnostics };
|
|
@@ -1427,7 +1452,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1427
1452
|
if (!arbiterResult.passed || !arbiterResult.artifact) {
|
|
1428
1453
|
const failures = arbiterResult.failures.map((f) => f.reason);
|
|
1429
1454
|
void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
|
|
1430
|
-
|
|
1455
|
+
warn(`[nocturnal-service] Failed to record run end (arbiter failed): ${String(err)}`);
|
|
1431
1456
|
});
|
|
1432
1457
|
adjustThresholdsFromSignals(stateDir, { malformedRate: 0.0, arbiterRejectRate: 1.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
|
|
1433
1458
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, diagnostics };
|
|
@@ -1438,7 +1463,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1438
1463
|
if (!execResult.executable) {
|
|
1439
1464
|
const failures = execResult.failures.map((f) => f.reason);
|
|
1440
1465
|
void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
|
|
1441
|
-
|
|
1466
|
+
warn(`[nocturnal-service] Failed to record run end (executability failed): ${String(err)}`);
|
|
1442
1467
|
});
|
|
1443
1468
|
adjustThresholdsFromSignals(stateDir, { malformedRate: 0.0, arbiterRejectRate: 0.0, executabilityRejectRate: 1.0, qualityDelta: 0.0 });
|
|
1444
1469
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, diagnostics };
|
|
@@ -1448,7 +1473,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1448
1473
|
// Step 7: Persist artifact
|
|
1449
1474
|
const artifactWithBoundedAction = { ...arbiterResult.artifact, boundedAction: execResult.boundedAction };
|
|
1450
1475
|
|
|
1451
|
-
|
|
1476
|
+
|
|
1452
1477
|
let persistedPath: string;
|
|
1453
1478
|
try {
|
|
1454
1479
|
persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
|
|
@@ -1456,7 +1481,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1456
1481
|
diagnostics.persistedPath = persistedPath;
|
|
1457
1482
|
} catch (err) {
|
|
1458
1483
|
void recordRunEnd(stateDir, 'failed', { reason: `persistence error: ${String(err)}` }).catch((e) => {
|
|
1459
|
-
|
|
1484
|
+
warn(`[nocturnal-service] Failed to record run end (persistence failed): ${String(e)}`);
|
|
1460
1485
|
});
|
|
1461
1486
|
return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Failed to persist artifact: ${String(err)}`], snapshot, diagnostics };
|
|
1462
1487
|
}
|
|
@@ -1468,7 +1493,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1468
1493
|
incrementGeneratedSampleCount(stateDir, arbiterResult.artifact.principleId);
|
|
1469
1494
|
}
|
|
1470
1495
|
} catch (err) {
|
|
1471
|
-
|
|
1496
|
+
warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
|
|
1472
1497
|
}
|
|
1473
1498
|
|
|
1474
1499
|
try {
|
|
@@ -1486,7 +1511,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1486
1511
|
createdAt: arbiterResult.artifact.createdAt,
|
|
1487
1512
|
});
|
|
1488
1513
|
} catch (err) {
|
|
1489
|
-
|
|
1514
|
+
warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
|
|
1490
1515
|
}
|
|
1491
1516
|
|
|
1492
1517
|
diagnostics.artificer = maybePersistArtificerCandidate(
|
|
@@ -1501,7 +1526,7 @@ async function executeNocturnalReflectionWithAdapter(
|
|
|
1501
1526
|
|
|
1502
1527
|
// Step 9: Record run success
|
|
1503
1528
|
void recordRunEnd(stateDir, 'success', { sampleCount: 1 }).catch((err) => {
|
|
1504
|
-
|
|
1529
|
+
warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
|
|
1505
1530
|
});
|
|
1506
1531
|
|
|
1507
1532
|
// Step 10: Adaptive threshold adjustment
|
|
@@ -13,11 +13,11 @@ import type { RuntimeDirectDriver } from './runtime-direct-driver.js';
|
|
|
13
13
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
14
14
|
import { buildCritiquePromptV2 } from '../../tools/critique-prompt.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
|
+
import { DEEP_REFLECT_TTL_MS } from '../../config/defaults/runtime.js';
|
|
16
17
|
|
|
17
18
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
18
19
|
|
|
19
20
|
const DEFAULT_TIMEOUT_MS = 60_000; // Deep-reflect needs more time than empathy
|
|
20
|
-
const DEFAULT_TTL_MS = 10 * 60 * 1000;
|
|
21
21
|
|
|
22
22
|
export interface DeepReflectWorkflowOptions {
|
|
23
23
|
workspaceDir: string;
|
|
@@ -37,7 +37,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
37
37
|
workflowType: 'deep-reflect',
|
|
38
38
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
39
39
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
40
|
-
defaultTtlMs:
|
|
40
|
+
defaultTtlMs: DEEP_REFLECT_TTL_MS,
|
|
41
41
|
});
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -70,7 +70,7 @@ export class DeepReflectWorkflowManager extends WorkflowManagerBase {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
|
|
74
74
|
protected override generateWorkflowId(): string {
|
|
75
75
|
return `wf_dr_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
76
76
|
}
|
|
@@ -98,7 +98,7 @@ export const deepReflectWorkflowSpec: SubagentWorkflowSpec<DeepReflectResult> =
|
|
|
98
98
|
workflowType: 'deep-reflect',
|
|
99
99
|
transport: 'runtime_direct',
|
|
100
100
|
timeoutMs: 60_000,
|
|
101
|
-
ttlMs:
|
|
101
|
+
ttlMs: DEEP_REFLECT_TTL_MS,
|
|
102
102
|
shouldDeleteSessionAfterFinalize: true,
|
|
103
103
|
|
|
104
104
|
buildPrompt(taskInput: unknown, ctx: DeepReflectBuildPromptContext): string {
|
|
@@ -14,11 +14,11 @@ import { trackFriction } from '../../core/session-tracker.js';
|
|
|
14
14
|
import { isSubagentRuntimeAvailable } from '../../utils/subagent-probe.js';
|
|
15
15
|
import { WorkflowManagerBase } from './workflow-manager-base.js';
|
|
16
16
|
import { normalizeSeverity } from '../../core/empathy-types.js';
|
|
17
|
+
import { WORKFLOW_TTL_MS } from '../../config/defaults/runtime.js';
|
|
17
18
|
|
|
18
19
|
const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-';
|
|
19
20
|
|
|
20
21
|
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
21
|
-
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
22
22
|
|
|
23
23
|
export interface EmpathyObserverWorkflowOptions {
|
|
24
24
|
workspaceDir: string;
|
|
@@ -38,7 +38,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
38
38
|
workflowType: 'empathy-observer',
|
|
39
39
|
sessionPrefix: WORKFLOW_SESSION_PREFIX,
|
|
40
40
|
defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
|
|
41
|
-
defaultTtlMs:
|
|
41
|
+
defaultTtlMs: WORKFLOW_TTL_MS,
|
|
42
42
|
});
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -71,7 +71,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
|
|
75
75
|
protected override createWorkflowMetadata<TResult>(
|
|
76
76
|
spec: SubagentWorkflowSpec<TResult>,
|
|
77
77
|
options: {
|
|
@@ -105,7 +105,7 @@ export class EmpathyObserverWorkflowManager extends WorkflowManagerBase {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
|
|
109
109
|
protected override generateWorkflowId(): string {
|
|
110
110
|
return `wf_${Date.now()}_${Math.random().toString(36).substring(2, 10)}`;
|
|
111
111
|
}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from '../nocturnal-service.js';
|
|
37
37
|
import { type TrinityStageFailure, type TrinityResult } from '../../core/nocturnal-trinity.js';
|
|
38
38
|
import type { TrinityRuntimeAdapter } from '../../core/nocturnal-trinity.js';
|
|
39
|
-
import type { RecentPainContext } from '
|
|
39
|
+
import type { RecentPainContext } from './types.js';
|
|
40
40
|
import * as fs from 'fs';
|
|
41
41
|
import * as path from 'path';
|
|
42
42
|
import { validateNocturnalSnapshotIngress } from '../../core/nocturnal-snapshot-contract.js';
|
|
@@ -394,7 +394,7 @@ export class NocturnalWorkflowManager implements WorkflowManager {
|
|
|
394
394
|
}
|
|
395
395
|
|
|
396
396
|
|
|
397
|
-
|
|
397
|
+
|
|
398
398
|
async notifyLifecycleEvent(
|
|
399
399
|
|
|
400
400
|
_workflowId: string,
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
NocturnalArtifact,
|
|
14
|
+
ArbiterResult,
|
|
14
15
|
} from '../../core/nocturnal-arbiter.js';
|
|
15
16
|
import type {
|
|
16
17
|
BoundedAction,
|
|
@@ -18,12 +19,16 @@ import type {
|
|
|
18
19
|
import type {
|
|
19
20
|
NocturnalSessionSnapshot,
|
|
20
21
|
} from '../../core/nocturnal-trajectory-extractor.js';
|
|
21
|
-
import type {
|
|
22
|
-
NocturnalRunDiagnostics,
|
|
23
|
-
} from '../nocturnal-service.js';
|
|
24
22
|
import type {
|
|
25
23
|
TrinityResult,
|
|
26
24
|
} from '../../core/nocturnal-trinity.js';
|
|
25
|
+
import type {
|
|
26
|
+
IdleCheckResult,
|
|
27
|
+
PreflightCheckResult,
|
|
28
|
+
} from '../nocturnal-runtime.js';
|
|
29
|
+
import type {
|
|
30
|
+
NocturnalSelectionResult,
|
|
31
|
+
} from '../nocturnal-target-selector.js';
|
|
27
32
|
|
|
28
33
|
// ── Workflow Transport ────────────────────────────────────────────────────────
|
|
29
34
|
|
|
@@ -366,6 +371,27 @@ export type { PluginLogger } from '../../openclaw-sdk.js';
|
|
|
366
371
|
|
|
367
372
|
// ── Nocturnal Workflow Types ───────────────────────────────────────────────────
|
|
368
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Recent pain context for sleep_reflection tasks.
|
|
376
|
+
* Used by target selector for ranking bias and context enrichment.
|
|
377
|
+
* Originally from evolution-worker.ts, moved here to break circular dependency.
|
|
378
|
+
*/
|
|
379
|
+
export interface RecentPainContext {
|
|
380
|
+
/** Most recent unresolved pain event */
|
|
381
|
+
mostRecent: {
|
|
382
|
+
score: number;
|
|
383
|
+
source: string;
|
|
384
|
+
reason: string;
|
|
385
|
+
timestamp: string;
|
|
386
|
+
/** Session ID where the pain occurred */
|
|
387
|
+
sessionId: string;
|
|
388
|
+
} | null;
|
|
389
|
+
/** Count of pain events in the recent window (for signal strength) */
|
|
390
|
+
recentPainCount: number;
|
|
391
|
+
/** Highest pain score in the recent window */
|
|
392
|
+
recentMaxPainScore: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
369
395
|
/**
|
|
370
396
|
* Nocturnal workflow result type.
|
|
371
397
|
* Mirrors NocturnalRunResult from nocturnal-service.ts (per D-02).
|
|
@@ -381,3 +407,43 @@ export type NocturnalResult = {
|
|
|
381
407
|
diagnostics: NocturnalRunDiagnostics;
|
|
382
408
|
trinityTelemetry?: TrinityResult['telemetry'];
|
|
383
409
|
};
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Diagnostics from each pipeline stage.
|
|
413
|
+
* Duplicated from nocturnal-service.ts to break circular dependency.
|
|
414
|
+
*/
|
|
415
|
+
export interface NocturnalRunDiagnostics {
|
|
416
|
+
/** Pre-flight check result */
|
|
417
|
+
preflight: PreflightCheckResult | null;
|
|
418
|
+
/** Selection result */
|
|
419
|
+
selection: NocturnalSelectionResult | null;
|
|
420
|
+
/** Idle check result */
|
|
421
|
+
idle: IdleCheckResult | null;
|
|
422
|
+
/** Whether Trinity chain was attempted */
|
|
423
|
+
trinityAttempted: boolean;
|
|
424
|
+
/** Trinity result (if trinityAttempted === true) */
|
|
425
|
+
trinityResult: TrinityResult | null;
|
|
426
|
+
/** Which chain mode was used */
|
|
427
|
+
chainModeUsed: 'trinity' | 'single-reflector' | null;
|
|
428
|
+
/** Arbiter validation result */
|
|
429
|
+
arbiterResult: ArbiterResult | null;
|
|
430
|
+
/** Executability validation result (if arbiter passed) */
|
|
431
|
+
executabilityResult: { executable: boolean; failures: string[] } | null;
|
|
432
|
+
/** Whether artifact was persisted */
|
|
433
|
+
persisted: boolean;
|
|
434
|
+
/** Persistence path (if persisted) */
|
|
435
|
+
persistedPath?: string;
|
|
436
|
+
/** Code-candidate sidecar diagnostics */
|
|
437
|
+
artificer: NocturnalArtificerDiagnostics;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export interface NocturnalArtificerDiagnostics {
|
|
441
|
+
status: 'skipped' | 'validation_failed' | 'persisted_candidate';
|
|
442
|
+
reason?:
|
|
443
|
+
| 'behavioral_artifact_unavailable'
|
|
444
|
+
| 'no_deterministic_rule'
|
|
445
|
+
| 'persistence_failed'
|
|
446
|
+
| 'cancelled';
|
|
447
|
+
validationErrors?: string[];
|
|
448
|
+
persistedPath?: string;
|
|
449
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shadow Observation Fingerprint Utilities
|
|
3
|
+
*
|
|
4
|
+
* Computes fingerprints for shadow task routing and tracking.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
import type { PluginHookSubagentSpawningEvent } from '../openclaw-sdk.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* PD local worker profiles that are managed by the shadow routing policy.
|
|
12
|
+
*/
|
|
13
|
+
const _pdLocalProfiles = new Set(['local-reader', 'local-editor']);
|
|
14
|
+
export const PD_LOCAL_PROFILES: ReadonlySet<string> = _pdLocalProfiles;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if a profile is a local PD profile.
|
|
18
|
+
*/
|
|
19
|
+
export function isLocalProfile(profile: string): boolean {
|
|
20
|
+
return _pdLocalProfiles.has(profile);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH = 16;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute a fingerprint for runtime shadow task tracking.
|
|
27
|
+
* Used to correlate shadow routing decisions with subagent lifecycle events.
|
|
28
|
+
*/
|
|
29
|
+
export function computeRuntimeShadowTaskFingerprint(
|
|
30
|
+
event: PluginHookSubagentSpawningEvent,
|
|
31
|
+
): string {
|
|
32
|
+
const payload = {
|
|
33
|
+
childSessionKey: event.childSessionKey,
|
|
34
|
+
agentId: event.agentId,
|
|
35
|
+
label: event.label ?? '',
|
|
36
|
+
mode: event.mode,
|
|
37
|
+
threadRequested: event.threadRequested,
|
|
38
|
+
requesterChannel: event.requester?.channel ?? '',
|
|
39
|
+
requesterThreadId: event.requester?.threadId ?? '',
|
|
40
|
+
};
|
|
41
|
+
return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, RUNTIME_SHADOW_FINGERPRINT_HEX_LENGTH);
|
|
42
|
+
}
|