principles-disciple 1.34.2 → 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/src/index.ts CHANGED
@@ -16,9 +16,7 @@ import type {
16
16
  PluginHookSubagentSpawningResult,
17
17
  PluginHookSubagentContext,
18
18
  } from './openclaw-sdk.js';
19
- import * as crypto from 'crypto';
20
19
  import * as path from 'path';
21
- import type { WorkerProfile } from './core/model-deployment-registry.js';
22
20
  import { classifyTask } from './core/local-worker-routing.js';
23
21
  import { completeShadowObservation, recordShadowRouting } from './core/shadow-observation-registry.js';
24
22
  import { getCommandDescription } from './i18n/commands.js';
@@ -59,83 +57,25 @@ import { migrateDirectoryStructure } from './core/migration.js';
59
57
  import { SystemLogger } from './core/system-logger.js';
60
58
  import { createDeepReflectTool } from './tools/deep-reflect.js';
61
59
  import { createWritePainFlagTool } from './tools/write-pain-flag.js';
62
- import { PathResolver, resolveWorkspaceDirFromApi } from './core/path-resolver.js';
63
- import { validateWorkspaceDir } from './core/workspace-dir-validation.js';
64
- import { resolveRequiredWorkspaceDir, resolveWorkspaceDir, type WorkspaceResolutionContext } from './core/workspace-dir-service.js';
60
+ import { PathResolver } from './core/path-resolver.js';
65
61
  import { createPrinciplesConsoleRoute } from './http/principles-console-route.js';
66
62
  import { extractAgentIdFromSessionKey } from './utils/session-key.js';
63
+ import { resolveCommandWorkspaceDir, resolveToolHookWorkspaceDirSafe } from './utils/workspace-resolver.js';
64
+ import { computeRuntimeShadowTaskFingerprint, PD_LOCAL_PROFILES } from './utils/shadow-fingerprint.js';
65
+ import type { WorkerProfile } from './core/model-deployment-registry.js';
66
+ import { validateWorkspaceDir } from './core/workspace-dir-validation.js';
67
+ import { resolveWorkspaceDirFromApi } from './core/path-resolver.js';
68
+ import { resolveRequiredWorkspaceDir } from './core/workspace-dir-service.js';
67
69
 
68
70
  // Track initialization to avoid repeated calls
69
71
  let workspaceInitialized = false;
70
72
  // Track started evolution workers — one per workspace
71
73
  const startedWorkspaces = new Set<string>();
72
74
 
73
- /**
74
- * Resolve workspaceDir for slash commands.
75
- * Chain: ctx.workspaceDir → resolveWorkspaceDirFromApi (official OpenClaw API + env vars)
76
- *
77
- * CRITICAL: Throws if workspaceDir cannot be resolved. Silent failures are dangerous
78
- * because commands might operate on the wrong directory.
79
- */
80
- function resolveCommandWorkspaceDir(
81
- api: OpenClawPluginApi,
82
- ctx: { workspaceDir?: string },
83
- ): string {
84
- // 1. Direct from command context (most reliable — set by OpenClaw for current session)
85
- if (ctx.workspaceDir) {
86
- const issue = validateWorkspaceDir(ctx.workspaceDir);
87
- if (!issue) return ctx.workspaceDir;
88
- api.logger.error(`[PD:Command] ctx.workspaceDir="${ctx.workspaceDir}" is invalid: ${issue}`);
89
- }
90
-
91
- // 2. Official OpenClaw API → env vars → config file
92
- const resolved = resolveWorkspaceDirFromApi(api);
93
- if (resolved) return resolved;
94
-
95
- // CRITICAL FAILURE: Cannot determine workspace directory
96
- const errorMsg = `[PD:Command] CRITICAL: Cannot resolve workspace directory. ` +
97
- `ctx.workspaceDir="${ctx.workspaceDir}" is invalid, and all fallbacks failed. ` +
98
- `Commands will NOT execute to prevent data corruption.`;
99
- api.logger.error(errorMsg);
100
-
101
- throw new Error(errorMsg);
102
- }
103
-
104
75
  // Map from childSessionKey → shadowObservationId
105
76
  // Used to complete shadow observations when subagent ends
106
77
  const pendingShadowObservations = new Map<string, string>();
107
78
 
108
- // PD local worker profiles that are managed by the shadow routing policy
109
- const PD_LOCAL_PROFILES = new Set<WorkerProfile>(['local-reader', 'local-editor']);
110
-
111
- function computeRuntimeShadowTaskFingerprint(event: PluginHookSubagentSpawningEvent): string {
112
- const payload = {
113
- childSessionKey: event.childSessionKey,
114
- agentId: event.agentId,
115
- label: event.label ?? '',
116
- mode: event.mode,
117
- threadRequested: event.threadRequested,
118
- requesterChannel: event.requester?.channel ?? '',
119
- requesterThreadId: event.requester?.threadId ?? '',
120
- };
121
- return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex').slice(0, 16);
122
- }
123
-
124
- function _resolveCommandWorkspaceDirStrict(
125
- api: OpenClawPluginApi,
126
- ctx: WorkspaceResolutionContext,
127
- ): string {
128
- return resolveRequiredWorkspaceDir(api, ctx, { source: 'command' });
129
- }
130
-
131
- function resolveToolHookWorkspaceDirSafe(
132
- ctx: WorkspaceResolutionContext,
133
- api: OpenClawPluginApi,
134
- source: string,
135
- ): string | undefined {
136
- return resolveWorkspaceDir(api, ctx, { source });
137
- }
138
-
139
79
  const plugin = {
140
80
  name: "Principles Disciple",
141
81
  description: "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
@@ -421,7 +361,7 @@ const plugin = {
421
361
 
422
362
  // ── Slash Commands ──
423
363
  // Register command with optional short alias
424
- // eslint-disable-next-line @typescript-eslint/max-params, @typescript-eslint/no-explicit-any
364
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
425
365
  const registerCommandWithAlias = (name: string, alias: string | null, desc: string, handler: any, opts?: { acceptsArgs?: boolean }) => {
426
366
  const base = {
427
367
  name,
@@ -7,18 +7,13 @@
7
7
 
8
8
  import type { OpenClawPluginService, OpenClawPluginServiceContext, PluginLogger } from '../openclaw-sdk.js';
9
9
  import { CentralDatabase } from './central-database.js';
10
+ import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
10
11
 
11
12
  let syncInterval: ReturnType<typeof setInterval> | null = null;
12
13
  let logger: PluginLogger | undefined = undefined;
13
14
  let centralDb: CentralDatabase | null = null;
14
15
 
15
- /**
16
- * Default sync interval: 5 minutes.
17
- * Can be overridden via config: intervals.central_sync_ms
18
- */
19
- const DEFAULT_SYNC_INTERVAL_MS = 5 * 60 * 1000;
20
-
21
- // eslint-disable-next-line complexity -- complexity 12, refactor candidate
16
+
22
17
  async function runSyncCycle(): Promise<void> {
23
18
  if (!centralDb) {
24
19
  logger?.warn?.('[PD:CentralSync] CentralDatabase not initialized, skipping sync');
@@ -51,7 +46,7 @@ export const CentralSyncService: OpenClawPluginService = {
51
46
  logger = ctxLogger;
52
47
 
53
48
  const { intervals } = config as { intervals?: { central_sync_ms?: number } };
54
- const intervalMs = intervals?.central_sync_ms ?? DEFAULT_SYNC_INTERVAL_MS;
49
+ const intervalMs = intervals?.central_sync_ms ?? WORKFLOW_TTL_MS;
55
50
 
56
51
  // Initialize CentralDatabase
57
52
  centralDb = new CentralDatabase();
@@ -22,11 +22,11 @@ import type {
22
22
  CorrectionObserverPayload,
23
23
  CorrectionObserverResult,
24
24
  } from './correction-observer-types.js';
25
+ import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
25
26
 
26
27
  const WORKFLOW_SESSION_PREFIX = 'agent:main:subagent:workflow-correction-';
27
28
 
28
29
  const DEFAULT_TIMEOUT_MS = 30_000;
29
- const DEFAULT_TTL_MS = 5 * 60 * 1000;
30
30
 
31
31
  // ── Options ─────────────────────────────────────────────────────────────────
32
32
 
@@ -162,7 +162,7 @@ export class CorrectionObserverWorkflowManager extends WorkflowManagerBase {
162
162
  workflowType: 'correction_observer',
163
163
  sessionPrefix: WORKFLOW_SESSION_PREFIX,
164
164
  defaultTimeoutMs: DEFAULT_TIMEOUT_MS,
165
- defaultTtlMs: DEFAULT_TTL_MS,
165
+ defaultTtlMs: WORKFLOW_TTL_MS,
166
166
  });
167
167
  }
168
168
 
@@ -16,10 +16,11 @@ import { getEvolutionLogger } from '../core/evolution-logger.js';
16
16
  import type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
17
17
  export type { TaskKind, TaskPriority } from '../core/trajectory-types.js';
18
18
  import { LockUnavailableError } from '../config/index.js';
19
+ import { PAIN_QUEUE_DEDUP_WINDOW_MS } from '../config/defaults/runtime.js';
19
20
  import { checkWorkspaceIdle, checkCooldown } from './nocturnal-runtime.js';
20
21
  import { loadNocturnalConfig } from './nocturnal-config.js';
21
22
  import { WorkflowStore } from './subagent-workflow/workflow-store.js';
22
- import type { WorkflowRow } from './subagent-workflow/types.js';
23
+ import type { WorkflowRow, RecentPainContext } from './subagent-workflow/types.js';
23
24
  import { EmpathyObserverWorkflowManager } from './subagent-workflow/empathy-observer-workflow-manager.js';
24
25
  import { DeepReflectWorkflowManager } from './subagent-workflow/deep-reflect-workflow-manager.js';
25
26
  import { NocturnalWorkflowManager, nocturnalWorkflowSpec } from './subagent-workflow/nocturnal-workflow-manager.js';
@@ -36,8 +37,7 @@ import type { CorrectionObserverPayload } from './correction-observer-types.js';
36
37
  import { KeywordOptimizationService } from './keyword-optimization-service.js';
37
38
  import { TrajectoryRegistry } from '../core/trajectory.js';
38
39
  import { CorrectionCueLearner } from '../core/correction-cue-learner.js';
39
-
40
- const WORKFLOW_TTL_MS = 5 * 60 * 1000; // 5 minutes default TTL for helper workflows
40
+ import { WORKFLOW_TTL_MS } from '../config/defaults/runtime.js';
41
41
  import { OpenClawTrinityRuntimeAdapter } from '../core/nocturnal-trinity.js';
42
42
 
43
43
  /**
@@ -209,27 +209,6 @@ let timeoutId: NodeJS.Timeout | null = null;
209
209
  export type QueueStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'canceled';
210
210
  export type TaskResolution = 'marker_detected' | 'auto_completed_timeout' | 'failed_max_retries' | 'runtime_unavailable' | 'canceled' | 'late_marker_principle_created' | 'late_marker_no_principle' | 'stub_fallback' | 'skipped_thin_violation';
211
211
 
212
- /**
213
- * Recent pain context attached to sleep_reflection tasks.
214
- * Carries explicit recent pain signal metadata without being a separate task kind.
215
- * Used by NocturnalTargetSelector for ranking bias and context enrichment.
216
- */
217
- export interface RecentPainContext {
218
- /** Most recent unresolved pain event */
219
- mostRecent: {
220
- score: number;
221
- source: string;
222
- reason: string;
223
- timestamp: string;
224
- /** Session ID where the pain occurred */
225
- sessionId: string;
226
- } | null;
227
- /** Count of pain events in the recent window (for signal strength) */
228
- recentPainCount: number;
229
- /** Highest pain score in the recent window */
230
- recentMaxPainScore: number;
231
- }
232
-
233
212
  export interface EvolutionQueueItem {
234
213
  // Core identity
235
214
  id: string;
@@ -435,7 +414,6 @@ function buildFallbackNocturnalSnapshot(
435
414
  };
436
415
  }
437
416
 
438
- const PAIN_QUEUE_DEDUP_WINDOW_MS = 30 * 60 * 1000;
439
417
 
440
418
  // P0 fix: File lock constants and helper for queue operations (prevents TOCTOU race)
441
419
  export const EVOLUTION_QUEUE_LOCK_SUFFIX = '.lock';
@@ -692,7 +670,7 @@ function shouldSkipForDedup(
692
670
  * Load and migrate the evolution queue. Returns empty array if file doesn't exist.
693
671
  */
694
672
  function loadEvolutionQueue(queuePath: string): EvolutionQueueItem[] {
695
- // eslint-disable-next-line no-useless-assignment
673
+
696
674
  let rawQueue: RawQueueItem[] = [];
697
675
  try {
698
676
  rawQueue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
@@ -2014,10 +1992,14 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2014
1992
  };
2015
1993
 
2016
1994
  // Dispatch LLM subagent via CorrectionObserverWorkflowManager
1995
+ const subagent = api?.runtime?.subagent;
1996
+ if (!subagent) {
1997
+ throw new Error('[PD:EvolutionWorker] subagent runtime not available for keyword_optimization');
1998
+ }
2017
1999
  const manager = new CorrectionObserverWorkflowManager({
2018
2000
  workspaceDir: wctx.workspaceDir,
2019
2001
  logger,
2020
- subagent: api?.runtime?.subagent!,
2002
+ subagent,
2021
2003
  agentSession: api?.runtime?.agent?.session,
2022
2004
  });
2023
2005
 
@@ -2031,7 +2013,11 @@ async function processEvolutionQueue(wctx: WorkspaceContext, logger: PluginLogge
2031
2013
  workflowId = handle.workflowId;
2032
2014
  koTask.resultRef = workflowId;
2033
2015
  } else {
2034
- workflowId = koTask.resultRef!;
2016
+ // isPolling implies resultRef exists (checked above)
2017
+ workflowId = koTask.resultRef;
2018
+ if (!workflowId) {
2019
+ throw new Error(`[PD:EvolutionWorker] keyword_optimization task ${koTask.id} has no resultRef in polling mode`);
2020
+ }
2035
2021
  }
2036
2022
 
2037
2023
  // Poll workflow state
@@ -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,
@@ -122,6 +123,7 @@ function incrementGeneratedSampleCount(stateDir: string, principleId: string): v
122
123
  state.generatedSampleCount += 1;
123
124
  setPrincipleState(stateDir, state);
124
125
  } catch (err) {
126
+ // eslint-disable-next-line no-console -- Non-critical warning in helper function
125
127
  console.warn(`[nocturnal-service] Failed to sync generatedSampleCount for ${principleId}:`, err instanceof Error ? err.stack : err);
126
128
  }
127
129
  }
@@ -268,6 +270,12 @@ export interface NocturnalServiceOptions {
268
270
  * When omitted, a deterministic local candidate is synthesized.
269
271
  */
270
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;
271
279
  }
272
280
 
273
281
  // ---------------------------------------------------------------------------
@@ -299,16 +307,16 @@ function invokeStubReflector(
299
307
  const hasPain = snapshot.stats.totalPainEvents > 0;
300
308
  const hasFailures = (snapshot.stats.failureCount ?? 0) > 0;
301
309
 
302
- // eslint-disable-next-line @typescript-eslint/init-declarations
310
+
303
311
  let badDecision: string;
304
- // eslint-disable-next-line @typescript-eslint/init-declarations
312
+
305
313
  let betterDecision: string;
306
- // eslint-disable-next-line @typescript-eslint/init-declarations
314
+
307
315
  let rationale: string;
308
316
 
309
317
  if (hasGateBlocks && snapshot.gateBlocks.length > 0) {
310
318
  // Use actual gate block content
311
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
319
+
312
320
  const block = snapshot.gateBlocks[0];
313
321
  const tool = block.toolName ?? 'a tool';
314
322
  const file = block.filePath ? ` on ${block.filePath}` : '';
@@ -317,7 +325,7 @@ function invokeStubReflector(
317
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.`;
318
326
  } else if (hasPain && snapshot.painEvents.length > 0) {
319
327
  // Use actual pain event content
320
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
328
+
321
329
  const pain = snapshot.painEvents[0];
322
330
  const painSource = pain.source ?? 'unknown';
323
331
  const painReason = pain.reason ? `: ${pain.reason}` : '';
@@ -413,7 +421,7 @@ function buildGateBlockRefs(snapshot: NocturnalSessionSnapshot): string[] {
413
421
  }
414
422
 
415
423
 
416
- // eslint-disable-next-line @typescript-eslint/max-params
424
+
417
425
  function buildDefaultArtificerOutput(
418
426
  ruleId: string,
419
427
  artifact: NocturnalArtifact,
@@ -463,7 +471,7 @@ function buildDefaultArtificerOutput(
463
471
  }
464
472
 
465
473
 
466
- // eslint-disable-next-line @typescript-eslint/max-params
474
+
467
475
  function persistCodeCandidate(
468
476
  workspaceDir: string,
469
477
  stateDir: string,
@@ -521,6 +529,7 @@ function persistCodeCandidate(
521
529
  try {
522
530
  refreshPrincipleLifecycle(workspaceDir, stateDir);
523
531
  } catch (err) {
532
+ // eslint-disable-next-line no-console -- Non-critical warning in helper function
524
533
  console.warn('[nocturnal-service] Lifecycle refresh failed after code candidate persistence:', err instanceof Error ? err.stack : err);
525
534
  }
526
535
  return {
@@ -560,7 +569,7 @@ function persistCodeCandidate(
560
569
  }
561
570
 
562
571
 
563
- // eslint-disable-next-line @typescript-eslint/max-params
572
+
564
573
  function maybePersistArtificerCandidate(
565
574
  workspaceDir: string,
566
575
  stateDir: string,
@@ -698,6 +707,11 @@ export function executeNocturnalReflection(
698
707
  stateDir: string,
699
708
  options: NocturnalServiceOptions = {}
700
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
+
701
715
  const diagnostics: NocturnalRunDiagnostics = {
702
716
  preflight: null,
703
717
  selection: null,
@@ -797,18 +811,18 @@ export function executeNocturnalReflection(
797
811
  // The async version would be used in real worker integration
798
812
  const config = loadNocturnalConfig(stateDir);
799
813
  void recordRunStart(stateDir, selectedPrincipleId, config.cooldown_ms).catch((err) => {
800
- console.warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
814
+ warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
801
815
  });
802
816
 
803
817
  // -------------------------------------------------------------------------
804
818
  // Step 5: Artifact generation (Trinity or single-reflector)
805
819
  // -------------------------------------------------------------------------
806
820
 
807
- // eslint-disable-next-line no-useless-assignment
821
+
808
822
  let trinityArtifact: TrinityDraftArtifact | null = null;
809
823
  let trinityResult: TrinityResult | null = null;
810
824
 
811
- // eslint-disable-next-line @typescript-eslint/init-declarations
825
+
812
826
  let rawJson: string;
813
827
 
814
828
  if (options.skipReflector) {
@@ -834,7 +848,7 @@ export function executeNocturnalReflection(
834
848
  // Trinity failed — fail closed (same semantics as production)
835
849
  const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
836
850
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity override failed: ${failures.join('; ')}` }).catch((err) => {
837
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
851
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
838
852
  });
839
853
  // Emit threshold signals: malformed Trinity override is a strong signal
840
854
  adjustThresholdsFromSignals(stateDir, {
@@ -857,7 +871,7 @@ export function executeNocturnalReflection(
857
871
  if (!draftValidation.valid) {
858
872
  const {failures} = draftValidation;
859
873
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
860
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
874
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
861
875
  });
862
876
  // Emit threshold signals: malformed draft content is a strong signal
863
877
  adjustThresholdsFromSignals(stateDir, {
@@ -908,7 +922,7 @@ export function executeNocturnalReflection(
908
922
  // Trinity draft invalid — fail closed
909
923
  const {failures} = draftValidation;
910
924
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
911
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
925
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
912
926
  });
913
927
  // Emit threshold signals: malformed draft content is a strong signal
914
928
  adjustThresholdsFromSignals(stateDir, {
@@ -935,7 +949,7 @@ export function executeNocturnalReflection(
935
949
  // Phase 6 requirement: malformed Trinity stage output fails closed
936
950
  const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
937
951
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity chain failed: ${failures.join('; ')}` }).catch((err) => {
938
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
952
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
939
953
  });
940
954
  // Emit threshold signals: malformed Trinity is the strongest signal for tightening schema threshold
941
955
  adjustThresholdsFromSignals(stateDir, {
@@ -979,7 +993,7 @@ export function executeNocturnalReflection(
979
993
  if (!arbiterResult.passed || !arbiterResult.artifact) {
980
994
  const failures = arbiterResult.failures.map((f) => f.reason);
981
995
  void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
982
- 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)}`);
983
997
  });
984
998
  // Emit threshold signals: arbiter rejection indicates principle alignment issues
985
999
  adjustThresholdsFromSignals(stateDir, {
@@ -1005,7 +1019,7 @@ export function executeNocturnalReflection(
1005
1019
  if (!execResult.executable) {
1006
1020
  const failures = execResult.failures.map((f) => f.reason);
1007
1021
  void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
1008
- 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)}`);
1009
1023
  });
1010
1024
  // Emit threshold signals: executability rejection indicates action quality issues
1011
1025
  adjustThresholdsFromSignals(stateDir, {
@@ -1033,7 +1047,7 @@ export function executeNocturnalReflection(
1033
1047
  };
1034
1048
 
1035
1049
 
1036
- // eslint-disable-next-line @typescript-eslint/init-declarations
1050
+
1037
1051
  let persistedPath: string;
1038
1052
  try {
1039
1053
  persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
@@ -1041,7 +1055,7 @@ export function executeNocturnalReflection(
1041
1055
  diagnostics.persistedPath = persistedPath;
1042
1056
  } catch (err) {
1043
1057
  void recordRunEnd(stateDir, 'failed', { reason: `persistence error: ${String(err)}` }).catch((e) => {
1044
- 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)}`);
1045
1059
  });
1046
1060
  return {
1047
1061
  success: false,
@@ -1066,7 +1080,7 @@ export function executeNocturnalReflection(
1066
1080
  } catch (err) {
1067
1081
  // Non-fatal: artifact is persisted, registry is secondary.
1068
1082
  // Log but don't fail the run.
1069
- 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)}`);
1070
1084
  }
1071
1085
 
1072
1086
  try {
@@ -1084,7 +1098,7 @@ export function executeNocturnalReflection(
1084
1098
  createdAt: arbiterResult.artifact.createdAt,
1085
1099
  });
1086
1100
  } catch (err) {
1087
- console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1101
+ warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1088
1102
  }
1089
1103
 
1090
1104
  diagnostics.artificer = maybePersistArtificerCandidate(
@@ -1101,7 +1115,7 @@ export function executeNocturnalReflection(
1101
1115
  // Step 9: Record run success
1102
1116
  // -------------------------------------------------------------------------
1103
1117
  void recordRunEnd(stateDir, 'success', { sampleCount: 1 }).catch((err) => {
1104
- console.warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
1118
+ warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
1105
1119
  });
1106
1120
 
1107
1121
  // -------------------------------------------------------------------------
@@ -1161,7 +1175,7 @@ export async function executeNocturnalReflectionAsync(
1161
1175
  // If runtime adapter is provided, use async Trinity path
1162
1176
  if (options.runtimeAdapter) {
1163
1177
 
1164
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
1178
+
1165
1179
  return executeNocturnalReflectionWithAdapter(workspaceDir, stateDir, options);
1166
1180
  }
1167
1181
 
@@ -1178,6 +1192,11 @@ async function executeNocturnalReflectionWithAdapter(
1178
1192
  stateDir: string,
1179
1193
  options: NocturnalServiceOptions
1180
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
+
1181
1200
  const diagnostics: NocturnalRunDiagnostics = {
1182
1201
  preflight: null,
1183
1202
  selection: null,
@@ -1219,13 +1238,13 @@ async function executeNocturnalReflectionWithAdapter(
1219
1238
 
1220
1239
  // Step 2: Target selection (or use override to skip)
1221
1240
 
1222
- // eslint-disable-next-line @typescript-eslint/init-declarations
1241
+
1223
1242
  let selectedPrincipleId: string | undefined;
1224
1243
 
1225
- // eslint-disable-next-line @typescript-eslint/init-declarations
1244
+
1226
1245
  let selectedSessionId: string | undefined;
1227
1246
 
1228
- // eslint-disable-next-line no-useless-assignment
1247
+
1229
1248
  let snapshot: NocturnalSessionSnapshot | null = null;
1230
1249
 
1231
1250
  if (options.principleIdOverride && options.snapshotOverride) {
@@ -1248,7 +1267,7 @@ async function executeNocturnalReflectionWithAdapter(
1248
1267
  // Skip Selector: use provided principleId and snapshot directly
1249
1268
  selectedPrincipleId = options.principleIdOverride;
1250
1269
  selectedSessionId = snapshotValidation.snapshot.sessionId;
1251
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
1270
+
1252
1271
  snapshot = snapshotValidation.snapshot;
1253
1272
  // Calculate violation density from snapshot stats for meaningful diagnostics
1254
1273
  const snapStats = snapshotValidation.snapshot.stats;
@@ -1305,10 +1324,10 @@ async function executeNocturnalReflectionWithAdapter(
1305
1324
  }
1306
1325
 
1307
1326
 
1308
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
1327
+
1309
1328
  selectedPrincipleId = selection.selectedPrincipleId;
1310
1329
 
1311
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
1330
+
1312
1331
  selectedSessionId = selection.selectedSessionId;
1313
1332
 
1314
1333
  if (!selectedPrincipleId || !selectedSessionId) {
@@ -1338,16 +1357,16 @@ async function executeNocturnalReflectionWithAdapter(
1338
1357
  // Step 3: Record run start
1339
1358
  const config = loadNocturnalConfig(stateDir);
1340
1359
  void recordRunStart(stateDir, selectedPrincipleId, config.cooldown_ms).catch((err) => {
1341
- console.warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
1360
+ warn(`[nocturnal-service] Failed to record run start: ${String(err)}`);
1342
1361
  });
1343
1362
 
1344
1363
  // Step 4: Trinity execution via adapter (async)
1345
1364
 
1346
- // eslint-disable-next-line no-useless-assignment
1365
+
1347
1366
  let trinityArtifact: TrinityDraftArtifact | null = null;
1348
1367
  let trinityResult: TrinityResult | null = null;
1349
1368
 
1350
- // eslint-disable-next-line @typescript-eslint/init-declarations
1369
+
1351
1370
  let rawJson: string;
1352
1371
 
1353
1372
  if (options.skipReflector) {
@@ -1370,7 +1389,7 @@ async function executeNocturnalReflectionWithAdapter(
1370
1389
  if (!trinityResult.success) {
1371
1390
  const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
1372
1391
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity override failed: ${failures.join('; ')}` }).catch((err) => {
1373
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1392
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1374
1393
  });
1375
1394
  adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
1376
1395
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Trinity override failed: ${failures.join('; ')}`], snapshot, diagnostics };
@@ -1397,7 +1416,7 @@ async function executeNocturnalReflectionWithAdapter(
1397
1416
  if (!draftValidation.valid) {
1398
1417
  const {failures} = draftValidation;
1399
1418
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity draft invalid: ${failures.join('; ')}` }).catch((err) => {
1400
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1419
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1401
1420
  });
1402
1421
  adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
1403
1422
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, snapshot, diagnostics };
@@ -1408,7 +1427,7 @@ async function executeNocturnalReflectionWithAdapter(
1408
1427
  } else {
1409
1428
  const failures = trinityResult.failures.map((f) => `${f.stage}: ${f.reason}`);
1410
1429
  void recordRunEnd(stateDir, 'failed', { reason: `Trinity chain failed: ${failures.join('; ')}` }).catch((err) => {
1411
- console.warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1430
+ warn(`[nocturnal-service] Failed to record run end: ${String(err)}`);
1412
1431
  });
1413
1432
  adjustThresholdsFromSignals(stateDir, { malformedRate: 1.0, arbiterRejectRate: 0.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
1414
1433
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Trinity chain failed: ${failures.join('; ')}`], snapshot, diagnostics };
@@ -1433,7 +1452,7 @@ async function executeNocturnalReflectionWithAdapter(
1433
1452
  if (!arbiterResult.passed || !arbiterResult.artifact) {
1434
1453
  const failures = arbiterResult.failures.map((f) => f.reason);
1435
1454
  void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
1436
- 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)}`);
1437
1456
  });
1438
1457
  adjustThresholdsFromSignals(stateDir, { malformedRate: 0.0, arbiterRejectRate: 1.0, executabilityRejectRate: 0.0, qualityDelta: 0.0 });
1439
1458
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, diagnostics };
@@ -1444,7 +1463,7 @@ async function executeNocturnalReflectionWithAdapter(
1444
1463
  if (!execResult.executable) {
1445
1464
  const failures = execResult.failures.map((f) => f.reason);
1446
1465
  void recordRunEnd(stateDir, 'failed', { reason: failures.join('; ') }).catch((err) => {
1447
- 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)}`);
1448
1467
  });
1449
1468
  adjustThresholdsFromSignals(stateDir, { malformedRate: 0.0, arbiterRejectRate: 0.0, executabilityRejectRate: 1.0, qualityDelta: 0.0 });
1450
1469
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: failures, diagnostics };
@@ -1454,7 +1473,7 @@ async function executeNocturnalReflectionWithAdapter(
1454
1473
  // Step 7: Persist artifact
1455
1474
  const artifactWithBoundedAction = { ...arbiterResult.artifact, boundedAction: execResult.boundedAction };
1456
1475
 
1457
- // eslint-disable-next-line @typescript-eslint/init-declarations
1476
+
1458
1477
  let persistedPath: string;
1459
1478
  try {
1460
1479
  persistedPath = persistArtifact(workspaceDir, artifactWithBoundedAction);
@@ -1462,7 +1481,7 @@ async function executeNocturnalReflectionWithAdapter(
1462
1481
  diagnostics.persistedPath = persistedPath;
1463
1482
  } catch (err) {
1464
1483
  void recordRunEnd(stateDir, 'failed', { reason: `persistence error: ${String(err)}` }).catch((e) => {
1465
- 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)}`);
1466
1485
  });
1467
1486
  return { success: false, noTargetSelected: false, validationFailed: true, validationFailures: [`Failed to persist artifact: ${String(err)}`], snapshot, diagnostics };
1468
1487
  }
@@ -1474,7 +1493,7 @@ async function executeNocturnalReflectionWithAdapter(
1474
1493
  incrementGeneratedSampleCount(stateDir, arbiterResult.artifact.principleId);
1475
1494
  }
1476
1495
  } catch (err) {
1477
- 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)}`);
1478
1497
  }
1479
1498
 
1480
1499
  try {
@@ -1492,7 +1511,7 @@ async function executeNocturnalReflectionWithAdapter(
1492
1511
  createdAt: arbiterResult.artifact.createdAt,
1493
1512
  });
1494
1513
  } catch (err) {
1495
- console.warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1514
+ warn(`[nocturnal-service] Failed to append behavioral artifact lineage: ${String(err)}`);
1496
1515
  }
1497
1516
 
1498
1517
  diagnostics.artificer = maybePersistArtificerCandidate(
@@ -1507,7 +1526,7 @@ async function executeNocturnalReflectionWithAdapter(
1507
1526
 
1508
1527
  // Step 9: Record run success
1509
1528
  void recordRunEnd(stateDir, 'success', { sampleCount: 1 }).catch((err) => {
1510
- console.warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
1529
+ warn(`[nocturnal-service] Failed to record run end (success): ${String(err)}`);
1511
1530
  });
1512
1531
 
1513
1532
  // Step 10: Adaptive threshold adjustment