principles-disciple 1.34.1 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dependency-cruiser.json +19 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +6 -3
- package/src/config/defaults/runtime.ts +100 -24
- package/src/core/event-log.ts +87 -20
- package/src/core/nocturnal-candidate-scoring.ts +6 -6
- package/src/core/nocturnal-trinity-types.ts +94 -0
- package/src/core/nocturnal-trinity.ts +35 -99
- package/src/core/session-tracker.ts +7 -6
- package/src/core/system-logger.ts +104 -12
- package/src/core/workspace-dir-service.ts +40 -6
- package/src/core/workspace-dir-validation.ts +5 -37
- package/src/hooks/trajectory-collector.ts +7 -7
- package/src/index.ts +8 -68
- package/src/service/central-sync-service.ts +3 -8
- package/src/service/correction-observer-workflow-manager.ts +2 -2
- package/src/service/evolution-worker.ts +30 -35
- package/src/service/nocturnal-service.ts +72 -47
- package/src/service/subagent-workflow/deep-reflect-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/empathy-observer-workflow-manager.ts +4 -4
- package/src/service/subagent-workflow/nocturnal-workflow-manager.ts +2 -2
- package/src/service/subagent-workflow/types.ts +69 -3
- package/src/utils/shadow-fingerprint.ts +42 -0
- package/src/utils/workspace-resolver.ts +54 -0
- package/tests/core/workspace-dir-validation.test.ts +1 -1
- package/tests/integration/tool-hooks-workspace-dir.e2e.test.ts +3 -3
- package/vitest.config.ts +53 -6
|
@@ -584,7 +584,7 @@ export class OpenClawTrinityRuntimeAdapter implements TrinityRuntimeAdapter {
|
|
|
584
584
|
this.cleanupStaleTempDirs();
|
|
585
585
|
}
|
|
586
586
|
|
|
587
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
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
|
-
|
|
1851
|
+
|
|
1916
1852
|
let sixDScores: Philosopher6DScores;
|
|
1917
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 -
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
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(
|
|
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
|
-
|
|
10
|
-
|
|
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
|
-
|
|
234
|
+
|
|
235
235
|
content = msg.content;
|
|
236
236
|
} else if (Array.isArray(msg.content)) {
|
|
237
237
|
content = msg.content
|