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.
@@ -584,7 +584,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
584
584
  this.cleanupStaleTempDirs();
585
585
  }
586
586
 
587
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
587
+
588
588
  isRuntimeAvailable(): boolean {
589
589
  return true;
590
590
  }
@@ -682,7 +682,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
682
682
  /**
683
683
  * Extract text from runEmbeddedPiAgent result.
684
684
  */
685
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
685
+
686
686
  private extractPayloadText(result: { payloads?: { isError?: boolean; text?: string }[] }): string {
687
687
  return (result.payloads ?? [])
688
688
  .filter(p => !p.isError)
@@ -692,13 +692,13 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
692
692
  }
693
693
 
694
694
  /** Clamp a value to [0, 1] range — used for LLM-produced scores that may be out of range */
695
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
695
+
696
696
  private clamp01(val: unknown, fallback = 0): number {
697
697
  if (typeof val !== 'number' || !Number.isFinite(val)) return fallback;
698
698
  return Math.min(1, Math.max(0, val));
699
699
  }
700
700
 
701
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
701
+
702
702
  private classifyRuntimeError(error: unknown): TrinityRuntimeFailureCode {
703
703
  const detail = error instanceof Error ? error.message : String(error);
704
704
  return /timeout/i.test(detail) ? 'runtime_timeout' : 'runtime_run_failed';
@@ -797,7 +797,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
797
797
  }
798
798
 
799
799
 
800
- // eslint-disable-next-line @typescript-eslint/max-params
800
+
801
801
  async invokeScribe(
802
802
  dreamerOutput: DreamerOutput,
803
803
  philosopherOutput: PhilosopherOutput,
@@ -865,7 +865,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
865
865
  // ---------------------------------------------------------------------------
866
866
 
867
867
 
868
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
868
+
869
869
  private buildDreamerPrompt(
870
870
  snapshot: NocturnalSessionSnapshot,
871
871
  principleId: string,
@@ -962,7 +962,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
962
962
  }
963
963
 
964
964
 
965
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
965
+
966
966
  private buildPhilosopherPrompt(
967
967
  dreamerOutput: DreamerOutput,
968
968
  principleId: string,
@@ -1048,7 +1048,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
1048
1048
 
1049
1049
 
1050
1050
 
1051
- // eslint-disable-next-line @typescript-eslint/max-params, @typescript-eslint/class-methods-use-this
1051
+
1052
1052
  private buildScribePrompt(
1053
1053
  dreamerOutput: DreamerOutput,
1054
1054
  philosopherOutput: PhilosopherOutput,
@@ -1262,7 +1262,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
1262
1262
  implementationComplexity: (risks.implementationComplexity as string) ?? 'medium',
1263
1263
  breakingChangeRisk: Boolean(risks.breakingChangeRisk),
1264
1264
  };
1265
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
1265
+
1266
1266
  if (hasFp) risksObj.falsePositiveEstimate = this.clamp01(fp as number);
1267
1267
  return { risks: risksObj };
1268
1268
  })() : {}),
@@ -1306,7 +1306,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
1306
1306
  }
1307
1307
 
1308
1308
 
1309
- // eslint-disable-next-line @typescript-eslint/max-params
1309
+
1310
1310
  private parseScribeOutput(
1311
1311
  text: string,
1312
1312
  snapshot: NocturnalSessionSnapshot,
@@ -1375,7 +1375,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
1375
1375
  * Extract JSON object from text that may contain markdown code blocks.
1376
1376
  */
1377
1377
 
1378
- // eslint-disable-next-line @typescript-eslint/class-methods-use-this
1378
+
1379
1379
  private extractJson(text: string): string | null {
1380
1380
  // Try direct parse first
1381
1381
  try {
@@ -1470,89 +1470,25 @@ export interface TrinityConfig {
1470
1470
  // Trinity Intermediate Contracts
1471
1471
  // ---------------------------------------------------------------------------
1472
1472
 
1473
- /**
1474
- * Dreamer output — multiple candidate corrections.
1475
- * Each candidate represents an alternative "better decision" approach.
1476
- */
1477
- export interface DreamerCandidate {
1478
- /** Unique index for this candidate within the Dreamer output */
1479
- candidateIndex: number;
1480
- /** The bad decision this candidate addresses */
1481
- badDecision: string;
1482
- /** The alternative/better decision */
1483
- betterDecision: string;
1484
- /** Why this alternative is better (brief) */
1485
- rationale: string;
1486
- /** Confidence that this candidate is valid (0-1) */
1487
- confidence: number;
1488
- /** Risk level of this candidate's approach -- LLM-judged per D-02 */
1489
- riskLevel?: "low" | "medium" | "high";
1490
- /** Which strategic perspective this candidate embodies per D-01 */
1491
- strategicPerspective?: "conservative_fix" | "structural_improvement" | "paradigm_shift";
1492
- }
1493
-
1494
- export interface DreamerOutput {
1495
- /** Whether Dreamer succeeded */
1496
- valid: boolean;
1497
- /** List of candidate corrections */
1498
- candidates: DreamerCandidate[];
1499
- /** Why Dreamer could not generate (if valid === false) */
1500
- reason?: string;
1501
- /** Timestamp of generation */
1502
- generatedAt: string;
1503
- }
1504
-
1505
- /**
1506
- * Philosopher output — principle-grounded critique and ranking.
1507
- * Philosopher evaluates Dreamer's candidates and ranks them.
1508
- */
1509
- export interface PhilosopherRiskAssessment {
1510
- /** Estimated probability that this candidate is a false positive (0-1) */
1511
- falsePositiveEstimate: number;
1512
- /** How complex is this candidate to implement */
1513
- implementationComplexity: 'low' | 'medium' | 'high';
1514
- /** Whether implementing this candidate risks breaking existing functionality */
1515
- breakingChangeRisk: boolean;
1516
- }
1517
-
1518
- export interface Philosopher6DScores {
1519
- principleAlignment: number;
1520
- specificity: number;
1521
- actionability: number;
1522
- executability: number;
1523
- safetyImpact: number;
1524
- uxImpact: number;
1525
- }
1526
-
1527
- export interface PhilosopherJudgment {
1528
- /** Index of the judged candidate (references DreamerCandidate.candidateIndex) */
1529
- candidateIndex: number;
1530
- /** Principle-grounded critique of this candidate */
1531
- critique: string;
1532
- /** Whether this candidate aligns with the target principle */
1533
- principleAligned: boolean;
1534
- /** Ranking score (higher = better, 0-1) */
1535
- score: number;
1536
- /** Rank among all candidates (1 = best) */
1537
- rank: number;
1538
- /** Per-dimension scores (6D evaluation) — informational, not used for tournament ranking */
1539
- scores?: Philosopher6DScores;
1540
- /** Risk assessment for this candidate — informational, consumed by Scribe (Phase 37) */
1541
- risks?: PhilosopherRiskAssessment;
1542
- }
1543
-
1544
- export interface PhilosopherOutput {
1545
- /** Whether Philosopher succeeded */
1546
- valid: boolean;
1547
- /** Judgments for each candidate */
1548
- judgments: PhilosopherJudgment[];
1549
- /** Overall assessment of the candidate set */
1550
- overallAssessment: string;
1551
- /** Why Philosopher could not judge (if valid === false) */
1552
- reason?: string;
1553
- /** Timestamp of generation */
1554
- generatedAt: string;
1555
- }
1473
+ // Forward-exports from shared types module — single source of truth
1474
+ export type {
1475
+ DreamerCandidate,
1476
+ DreamerOutput,
1477
+ PhilosopherRiskAssessment,
1478
+ Philosopher6DScores,
1479
+ PhilosopherJudgment,
1480
+ PhilosopherOutput,
1481
+ } from './nocturnal-trinity-types.js';
1482
+
1483
+ // Import all types for local use in this file
1484
+ import type {
1485
+ DreamerCandidate,
1486
+ DreamerOutput,
1487
+ PhilosopherRiskAssessment,
1488
+ Philosopher6DScores,
1489
+ PhilosopherJudgment,
1490
+ PhilosopherOutput,
1491
+ } from './nocturnal-trinity-types.js';
1556
1492
 
1557
1493
  /**
1558
1494
  * Analysis of a rejected candidate — why it lost the tournament.
@@ -1912,9 +1848,9 @@ export function invokeStubPhilosopher(
1912
1848
 
1913
1849
  // Deterministic 6D scores based on strategic perspective (Phase 35 D-07 mapping)
1914
1850
  const perspective = candidate.strategicPerspective;
1915
- // eslint-disable-next-line @typescript-eslint/init-declarations
1851
+
1916
1852
  let sixDScores: Philosopher6DScores;
1917
- // eslint-disable-next-line @typescript-eslint/init-declarations
1853
+
1918
1854
  let riskAssessment: PhilosopherRiskAssessment;
1919
1855
 
1920
1856
  if (perspective === 'conservative_fix') {
@@ -2014,7 +1950,7 @@ export function invokeStubPhilosopher(
2014
1950
  * The stub uses tournament selection (scoring + thresholds) to pick the winner.
2015
1951
  */
2016
1952
 
2017
- // eslint-disable-next-line @typescript-eslint/max-params
1953
+
2018
1954
  export function invokeStubScribe(
2019
1955
  dreamerOutput: DreamerOutput,
2020
1956
  philosopherOutput: PhilosopherOutput,
@@ -2093,7 +2029,7 @@ export function runTrinity(options: RunTrinityOptions): TrinityResult {
2093
2029
  // Stub path: use synchronous stub implementations
2094
2030
  if (config.useStubs) {
2095
2031
 
2096
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
2032
+
2097
2033
  return runTrinityWithStubs(snapshot, principleId, config);
2098
2034
  }
2099
2035
 
@@ -2133,7 +2069,7 @@ export async function runTrinityAsync(options: RunTrinityOptions): Promise<Trini
2133
2069
  if (config.useStubs) {
2134
2070
  // Stub path: use synchronous stubs
2135
2071
 
2136
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
2072
+
2137
2073
  return runTrinityWithStubs(snapshot, principleId, config);
2138
2074
  }
2139
2075
 
@@ -2,6 +2,7 @@ import * as path from 'path';
2
2
  import * as fs from 'fs';
3
3
  import type { PainConfig } from './config.js';
4
4
  import { SystemLogger } from './system-logger.js';
5
+ import { TWO_HOURS_MS } from '../config/defaults/runtime.js';
5
6
 
6
7
  export interface TokenUsage {
7
8
  input?: number;
@@ -84,7 +85,7 @@ export function initPersistence(stateDir: string): void {
84
85
 
85
86
  // Load all existing sessions
86
87
 
87
- // eslint-disable-next-line @typescript-eslint/no-use-before-define
88
+
88
89
  loadAllSessions();
89
90
  }
90
91
 
@@ -107,7 +108,7 @@ function loadAllSessions(): void {
107
108
  try {
108
109
  const files = fs.readdirSync(persistDir).filter(f => f.endsWith('.json'));
109
110
  const now = Date.now();
110
- const twoHoursAgo = now - 2 * 60 * 60 * 1000;
111
+ const twoHoursAgo = now - TWO_HOURS_MS;
111
112
 
112
113
  for (const file of files) {
113
114
  try {
@@ -182,7 +183,7 @@ export function flushAllSessions(): void {
182
183
  }
183
184
 
184
185
 
185
- // eslint-disable-next-line @typescript-eslint/max-params
186
+
186
187
  function getOrCreateSession(sessionId: string, workspaceDir?: string, sessionKey?: string, trigger?: string): SessionState {
187
188
  let state = sessions.get(sessionId);
188
189
  if (!state) {
@@ -245,7 +246,7 @@ export function trackToolRead(sessionId: string, filePath: string, workspaceDir?
245
246
  }
246
247
 
247
248
 
248
- // eslint-disable-next-line @typescript-eslint/max-params -- complexity 12, refactor candidate
249
+
249
250
  export function trackLlmOutput(sessionId: string, usage: TokenUsage | undefined, config?: PainConfig, workspaceDir?: string, sessionKey?: string, trigger?: string): SessionState {
250
251
  const state = getOrCreateSession(sessionId, workspaceDir, sessionKey, trigger);
251
252
  state.llmTurns += 1;
@@ -285,7 +286,7 @@ export function trackLlmOutput(sessionId: string, usage: TokenUsage | undefined,
285
286
  * Tracks physical friction based on tool execution failures.
286
287
  */
287
288
 
288
- // eslint-disable-next-line @typescript-eslint/max-params -- complexity 11, slightly over threshold
289
+
289
290
  export function trackFriction(
290
291
  sessionId: string,
291
292
  deltaF: number,
@@ -552,7 +553,7 @@ export function decayGfi(sessionId: string, elapsedMinutes: number): SessionStat
552
553
  if (!state || state.currentGfi <= 0 || elapsedMinutes <= 0) return undefined;
553
554
 
554
555
  // Determine decay rate based on current GFI level (segmented)
555
- // eslint-disable-next-line @typescript-eslint/init-declarations
556
+
556
557
  let decayRate: number;
557
558
  if (state.currentGfi >= 70) {
558
559
  decayRate = 0.03; // 3%/min for severe friction
@@ -1,35 +1,127 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
- import { resolvePdPath } from './paths.js';
3
+ import { resolvePdPath, PD_FILES } from './paths.js';
4
4
 
5
5
  /**
6
6
  * System Logger for Principles Disciple
7
- * Writes critical evolutionary events to the project's memory/logs/SYSTEM.log
7
+ *
8
+ * Writes critical evolutionary events to date-stamped log files:
9
+ * memory/logs/SYSTEM_YYYY-MM-DD.log
10
+ *
8
11
  * Uses asynchronous writing to avoid blocking the Node.js event loop.
12
+ * Automatically rotates to a new file at midnight.
13
+ * Old log files are automatically cleaned up based on retention policy.
9
14
  */
15
+
16
+ // Cache for current log file path, invalidated when date changes
17
+ let cachedLogFile: string | undefined;
18
+ let cachedLogDate: string | undefined;
19
+
20
+ /**
21
+ * Log retention: delete SYSTEM logs older than this many days.
22
+ * Set to 0 to disable cleanup.
23
+ */
24
+ const LOG_RETENTION_DAYS = 7;
25
+
26
+ /**
27
+ * Get the system log file path for a given date.
28
+ * Format: memory/logs/SYSTEM_YYYY-MM-DD.log
29
+ */
30
+ function getSystemLogPath(workspaceDir: string, date: Date): string {
31
+ const dateStr = date.toISOString().slice(0, 10); // YYYY-MM-DD
32
+ const logDir = path.dirname(resolvePdPath(workspaceDir, 'SYSTEM_LOG'));
33
+ const baseName = path.basename(PD_FILES.SYSTEM_LOG, '.log');
34
+ return path.join(logDir, `${baseName}_${dateStr}.log`);
35
+ }
36
+
37
+ /**
38
+ * Get today's date string (YYYY-MM-DD).
39
+ */
40
+ function getTodayStr(): string {
41
+ return new Date().toISOString().slice(0, 10);
42
+ }
43
+
44
+ /**
45
+ * Clean up old SYSTEM log files, keeping only LOG_RETENTION_DAYS.
46
+ * Called automatically on first log write of each day.
47
+ */
48
+ function cleanupOldLogs(workspaceDir: string): void {
49
+ if (LOG_RETENTION_DAYS <= 0) return;
50
+
51
+ try {
52
+ const logDir = path.dirname(resolvePdPath(workspaceDir, 'SYSTEM_LOG'));
53
+ const baseName = path.basename(PD_FILES.SYSTEM_LOG, '.log');
54
+ const prefix = `${baseName}_`;
55
+
56
+ if (!fs.existsSync(logDir)) return;
57
+
58
+ const cutoffMs = Date.now() - LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000;
59
+ const files = fs.readdirSync(logDir);
60
+
61
+ for (const file of files) {
62
+ if (!file.startsWith(prefix) || !file.endsWith('.log')) continue;
63
+
64
+ const filePath = path.join(logDir, file);
65
+ const stat = fs.statSync(filePath);
66
+ if (stat.mtimeMs < cutoffMs) {
67
+ fs.unlinkSync(filePath);
68
+ }
69
+ }
70
+ } catch {
71
+ // Silently fail cleanup - don't crash logging for cleanup failures
72
+ }
73
+ }
74
+
75
+ // Track if cleanup has run today
76
+ let lastCleanupDate: string | undefined;
77
+
10
78
  export const SystemLogger = {
11
79
  log(workspaceDir: string | undefined, eventType: string, message: string): void {
12
80
  if (!workspaceDir) return;
13
-
81
+
14
82
  try {
15
- const logFile = resolvePdPath(workspaceDir, 'SYSTEM_LOG');
16
- const logDir = path.dirname(logFile);
17
-
18
- if (!fs.existsSync(logDir)) {
19
- fs.mkdirSync(logDir, { recursive: true });
83
+ const today = getTodayStr();
84
+
85
+ // Check if date changed - invalidate cache and run cleanup
86
+ if (cachedLogDate !== today) {
87
+ cachedLogDate = today;
88
+ cachedLogFile = undefined;
89
+ // Run cleanup once per day when date changes
90
+ if (lastCleanupDate !== today) {
91
+ lastCleanupDate = today;
92
+ cleanupOldLogs(workspaceDir);
93
+ }
94
+ }
95
+
96
+ // Get or create log file path
97
+ if (!cachedLogFile) {
98
+ cachedLogFile = getSystemLogPath(workspaceDir, new Date());
99
+ const logDir = path.dirname(cachedLogFile);
100
+ if (!fs.existsSync(logDir)) {
101
+ fs.mkdirSync(logDir, { recursive: true });
102
+ }
20
103
  }
21
-
104
+
22
105
  const timestamp = new Date().toISOString();
23
-
106
+
24
107
  // Format: [YYYY-MM-DDTHH:mm:ss.sssZ] [EVENT_TYPE] Message
25
108
  const logEntry = `[${timestamp}] [${eventType.padEnd(15)}] ${message}\n`;
26
-
109
+
27
110
  // Use fire-and-forget async append to prevent blocking
28
- fs.appendFile(logFile, logEntry, 'utf8', (_err) => {
111
+ fs.appendFile(cachedLogFile, logEntry, 'utf8', (_err) => {
29
112
  // Silently drop errors (e.g. disk full) to not crash the gateway
30
113
  });
31
114
  } catch (e) { // eslint-disable-line @typescript-eslint/no-unused-vars -- Reason: intentionally unused - silently fail if we can't setup the log
32
115
  // Silently fail if we can't setup the log
33
116
  }
117
+ },
118
+
119
+ /**
120
+ * Force refresh of the cached log file path.
121
+ * Call this at midnight or when the date changes to ensure proper rotation.
122
+ */
123
+ refreshCache(): void {
124
+ cachedLogDate = undefined;
125
+ cachedLogFile = undefined;
34
126
  }
35
127
  };
@@ -1,10 +1,5 @@
1
1
  import type { OpenClawPluginApi, PluginLogger } from '../openclaw-sdk.js';
2
- import { validateWorkspaceDir } from './workspace-dir-validation.js';
3
-
4
- export interface WorkspaceResolutionContext {
5
- workspaceDir?: string;
6
- agentId?: string;
7
- }
2
+ import { validateWorkspaceDir, type WorkspaceResolutionContext } from './workspace-dir-validation.js';
8
3
 
9
4
  export interface WorkspaceResolutionOptions {
10
5
  source?: string;
@@ -83,3 +78,42 @@ export function resolveRequiredWorkspaceDir(
83
78
  ): string {
84
79
  return resolveWorkspaceDir(api, ctx, { ...options, required: true }) as string;
85
80
  }
81
+
82
+ // Re-export helpers that live in workspace-dir-validation.ts for API compatibility
83
+ export { validateWorkspaceDir } from './workspace-dir-validation.js';
84
+ export type { WorkspaceResolutionContext } from './workspace-dir-validation.js';
85
+
86
+ export function resolveValidWorkspaceDir(
87
+ ctx: WorkspaceResolutionContext,
88
+ api: {
89
+ runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
90
+ config: unknown;
91
+ logger: PluginLogger;
92
+ },
93
+ options?: { source?: string; fallbackAgentId?: string },
94
+ ): string | undefined {
95
+ return resolveWorkspaceDir(api as never, ctx, {
96
+ source: options?.source,
97
+ fallbackAgentId: options?.fallbackAgentId,
98
+ logger: api.logger,
99
+ });
100
+ }
101
+
102
+ export function logWorkspaceDirHealth(
103
+ ctx: WorkspaceResolutionContext,
104
+ source: string,
105
+ api: {
106
+ runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
107
+ config: unknown;
108
+ logger: PluginLogger;
109
+ },
110
+ ): void {
111
+ const resolved = resolveValidWorkspaceDir(ctx, api, { source, fallbackAgentId: 'main' });
112
+ const issue = validateWorkspaceDir(resolved);
113
+
114
+ if (issue) {
115
+ api.logger.error(`[PD:health] ${source}: workspaceDir="${resolved}" - ${issue}`);
116
+ } else {
117
+ api.logger.info(`[PD:health] ${source}: workspaceDir="${resolved}" OK`);
118
+ }
119
+ }
@@ -6,8 +6,11 @@
6
6
  */
7
7
 
8
8
  import * as os from 'os';
9
- import type { PluginLogger } from '../openclaw-sdk.js';
10
- import { resolveWorkspaceDir, type WorkspaceResolutionContext } from './workspace-dir-service.js';
9
+
10
+ export interface WorkspaceResolutionContext {
11
+ workspaceDir?: string;
12
+ agentId?: string;
13
+ }
11
14
 
12
15
  export function validateWorkspaceDir(dir: string | undefined): string | null {
13
16
  if (!dir) {
@@ -38,38 +41,3 @@ export function validateWorkspaceDir(dir: string | undefined): string | null {
38
41
 
39
42
  return null;
40
43
  }
41
-
42
- export function resolveValidWorkspaceDir(
43
- ctx: WorkspaceResolutionContext,
44
- api: {
45
- runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
46
- config: unknown;
47
- logger: PluginLogger;
48
- },
49
- options?: { source?: string; fallbackAgentId?: string },
50
- ): string | undefined {
51
- return resolveWorkspaceDir(api as never, ctx, {
52
- source: options?.source,
53
- fallbackAgentId: options?.fallbackAgentId,
54
- logger: api.logger,
55
- });
56
- }
57
-
58
- export function logWorkspaceDirHealth(
59
- ctx: WorkspaceResolutionContext,
60
- source: string,
61
- api: {
62
- runtime: { agent: { resolveAgentWorkspaceDir: (config: unknown, agentId: string) => string } };
63
- config: unknown;
64
- logger: PluginLogger;
65
- },
66
- ): void {
67
- const resolved = resolveValidWorkspaceDir(ctx, api, { source, fallbackAgentId: 'main' });
68
- const issue = validateWorkspaceDir(resolved);
69
-
70
- if (issue) {
71
- api.logger.error(`[PD:health] ${source}: workspaceDir="${resolved}" - ${issue}`);
72
- } else {
73
- api.logger.info(`[PD:health] ${source}: workspaceDir="${resolved}" OK`);
74
- }
75
- }
@@ -7,21 +7,21 @@
7
7
 
8
8
  import * as fs from 'fs';
9
9
  import * as path from 'path';
10
- import type {
11
- PluginHookAfterToolCallEvent,
12
- PluginHookToolContext,
13
- PluginHookLlmOutputEvent,
10
+ import type {
11
+ PluginHookAfterToolCallEvent,
12
+ PluginHookToolContext,
13
+ PluginHookLlmOutputEvent,
14
14
  PluginHookAgentContext,
15
15
  PluginHookBeforeMessageWriteEvent
16
16
  } from '../openclaw-sdk.js';
17
+ import { MAX_STRING_LENGTH } from '../config/defaults/runtime.js';
17
18
 
18
19
  const TRAJECTORY_DIR = 'memory/trajectories/';
19
20
 
20
21
  // 敏感字段匹配正则
21
22
  const SENSITIVE_KEY_PATTERN = /password|token|authorization|secret|api[_-]?key|credential|cookie|session/i;
22
23
 
23
- // 最大字符串长度
24
- const MAX_STRING_LENGTH = 1000;
24
+ // 最大结果长度(不同于 MAX_STRING_LENGTH)
25
25
  const MAX_RESULT_LENGTH = 500;
26
26
 
27
27
  /**
@@ -231,7 +231,7 @@ export function handleBeforeMessageWrite(
231
231
  if (typeof msg.content === 'string') {
232
232
 
233
233
  // Reason: msg.content is string | ContentPart[]; destructuring would require renaming in the else branch
234
- // eslint-disable-next-line @typescript-eslint/prefer-destructuring
234
+
235
235
  content = msg.content;
236
236
  } else if (Array.isArray(msg.content)) {
237
237
  content = msg.content