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.
@@ -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 './evolution-worker.js';
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
- // eslint-disable-next-line @typescript-eslint/init-declarations
310
+
294
311
  let badDecision: string;
295
- // eslint-disable-next-line @typescript-eslint/init-declarations
312
+
296
313
  let betterDecision: string;
297
- // eslint-disable-next-line @typescript-eslint/init-declarations
314
+
298
315
  let rationale: string;
299
316
 
300
317
  if (hasGateBlocks && snapshot.gateBlocks.length > 0) {
301
318
  // Use actual gate block content
302
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
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
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
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
- // Atomic write: temp file + rename prevents corruption on crash
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
- // eslint-disable-next-line @typescript-eslint/max-params
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
- // eslint-disable-next-line @typescript-eslint/max-params
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
- // eslint-disable-next-line @typescript-eslint/max-params
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
- console.warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
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
- // eslint-disable-next-line no-useless-assignment
821
+
802
822
  let trinityArtifact: TrinityDraftArtifact | null = null;
803
823
  let trinityResult: TrinityResult | null = null;
804
824
 
805
- // eslint-disable-next-line @typescript-eslint/init-declarations
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (arbiter failed): ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (executability failed): ${String(err)}`);
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
- // eslint-disable-next-line @typescript-eslint/init-declarations
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
- console.warn(`[nocturnal-service] Failed to record run end (persistence failed): ${String(e)}`);
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
- console.warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
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
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
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
- // eslint-disable-next-line @typescript-eslint/init-declarations
1241
+
1217
1242
  let selectedPrincipleId: string | undefined;
1218
1243
 
1219
- // eslint-disable-next-line @typescript-eslint/init-declarations
1244
+
1220
1245
  let selectedSessionId: string | undefined;
1221
1246
 
1222
- // eslint-disable-next-line no-useless-assignment
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
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
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
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
1327
+
1303
1328
  selectedPrincipleId = selection.selectedPrincipleId;
1304
1329
 
1305
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
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
- console.warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
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
- // eslint-disable-next-line no-useless-assignment
1365
+
1341
1366
  let trinityArtifact: TrinityDraftArtifact | null = null;
1342
1367
  let trinityResult: TrinityResult | null = null;
1343
1368
 
1344
- // eslint-disable-next-line @typescript-eslint/init-declarations
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (arbiter failed): ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (executability failed): ${String(err)}`);
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
- // eslint-disable-next-line @typescript-eslint/init-declarations
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
- console.warn(`[nocturnal-service] Failed to record run end (persistence failed): ${String(e)}`);
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
- console.warn(`[nocturnal-service] Failed to register sample in dataset registry: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
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
- console.warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
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: DEFAULT_TTL_MS,
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
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
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: 10 * 60 * 1000,
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: DEFAULT_TTL_MS,
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
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
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
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
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 '../evolution-worker.js';
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
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
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
+ }