principles-disciple 1.6.0 → 1.7.1

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.
Files changed (75) hide show
  1. package/dist/commands/context.js +7 -3
  2. package/dist/commands/evolution-status.d.ts +4 -0
  3. package/dist/commands/evolution-status.js +134 -0
  4. package/dist/commands/export.d.ts +2 -0
  5. package/dist/commands/export.js +45 -0
  6. package/dist/commands/focus.js +9 -6
  7. package/dist/commands/pain.js +8 -0
  8. package/dist/commands/principle-rollback.d.ts +4 -0
  9. package/dist/commands/principle-rollback.js +22 -0
  10. package/dist/commands/rollback.js +9 -3
  11. package/dist/commands/samples.d.ts +2 -0
  12. package/dist/commands/samples.js +55 -0
  13. package/dist/commands/trust.js +64 -81
  14. package/dist/core/config.d.ts +5 -0
  15. package/dist/core/control-ui-db.d.ts +68 -0
  16. package/dist/core/control-ui-db.js +274 -0
  17. package/dist/core/detection-funnel.d.ts +1 -1
  18. package/dist/core/detection-funnel.js +4 -0
  19. package/dist/core/dictionary.d.ts +2 -0
  20. package/dist/core/dictionary.js +13 -0
  21. package/dist/core/event-log.d.ts +7 -1
  22. package/dist/core/event-log.js +10 -0
  23. package/dist/core/evolution-engine.d.ts +5 -5
  24. package/dist/core/evolution-engine.js +18 -18
  25. package/dist/core/evolution-migration.d.ts +5 -0
  26. package/dist/core/evolution-migration.js +65 -0
  27. package/dist/core/evolution-reducer.d.ts +69 -0
  28. package/dist/core/evolution-reducer.js +369 -0
  29. package/dist/core/evolution-types.d.ts +103 -0
  30. package/dist/core/path-resolver.js +75 -36
  31. package/dist/core/paths.d.ts +7 -8
  32. package/dist/core/paths.js +48 -40
  33. package/dist/core/profile.js +1 -1
  34. package/dist/core/session-tracker.d.ts +14 -2
  35. package/dist/core/session-tracker.js +75 -9
  36. package/dist/core/thinking-models.d.ts +38 -0
  37. package/dist/core/thinking-models.js +170 -0
  38. package/dist/core/trajectory.d.ts +184 -0
  39. package/dist/core/trajectory.js +817 -0
  40. package/dist/core/trust-engine.d.ts +6 -0
  41. package/dist/core/trust-engine.js +50 -29
  42. package/dist/core/workspace-context.d.ts +13 -0
  43. package/dist/core/workspace-context.js +50 -7
  44. package/dist/hooks/gate.js +171 -87
  45. package/dist/hooks/llm.js +119 -71
  46. package/dist/hooks/pain.js +105 -5
  47. package/dist/hooks/prompt.d.ts +11 -14
  48. package/dist/hooks/prompt.js +283 -57
  49. package/dist/hooks/subagent.js +69 -28
  50. package/dist/hooks/trajectory-collector.d.ts +32 -0
  51. package/dist/hooks/trajectory-collector.js +256 -0
  52. package/dist/http/principles-console-route.d.ts +2 -0
  53. package/dist/http/principles-console-route.js +257 -0
  54. package/dist/i18n/commands.js +16 -0
  55. package/dist/index.js +105 -4
  56. package/dist/service/control-ui-query-service.d.ts +217 -0
  57. package/dist/service/control-ui-query-service.js +537 -0
  58. package/dist/service/empathy-observer-manager.d.ts +2 -0
  59. package/dist/service/empathy-observer-manager.js +43 -1
  60. package/dist/service/evolution-worker.d.ts +27 -0
  61. package/dist/service/evolution-worker.js +256 -41
  62. package/dist/service/runtime-summary-service.d.ts +79 -0
  63. package/dist/service/runtime-summary-service.js +319 -0
  64. package/dist/service/trajectory-service.d.ts +2 -0
  65. package/dist/service/trajectory-service.js +15 -0
  66. package/dist/tools/agent-spawn.d.ts +27 -6
  67. package/dist/tools/agent-spawn.js +339 -87
  68. package/dist/tools/deep-reflect.d.ts +27 -7
  69. package/dist/tools/deep-reflect.js +210 -121
  70. package/dist/types/event-types.d.ts +10 -2
  71. package/dist/types.d.ts +10 -0
  72. package/dist/types.js +5 -0
  73. package/openclaw.plugin.json +43 -11
  74. package/package.json +14 -4
  75. package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
@@ -0,0 +1,369 @@
1
+ import * as crypto from 'crypto';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { withLock } from '../utils/file-lock.js';
5
+ import { PathResolver } from './path-resolver.js';
6
+ import { SystemLogger } from './system-logger.js';
7
+ import { shouldIgnorePainProtocolText } from './dictionary.js';
8
+ import { TrajectoryRegistry } from './trajectory.js';
9
+ const PROBATION_SUCCESS_THRESHOLD = 3;
10
+ const CIRCUIT_BREAKER_THRESHOLD = 3;
11
+ const PROBATION_MAX_AGE_DAYS = 30;
12
+ export class EvolutionReducerImpl {
13
+ streamPath;
14
+ lockTargetPath;
15
+ blacklistPath;
16
+ workspaceDir;
17
+ memoryEvents = [];
18
+ principles = new Map();
19
+ failureStreak = new Map();
20
+ lastPromotedAt = null;
21
+ isReplaying = false;
22
+ constructor(opts) {
23
+ this.workspaceDir = opts.workspaceDir;
24
+ const resolver = new PathResolver({ workspaceDir: opts.workspaceDir });
25
+ this.streamPath = resolver.resolve('EVOLUTION_STREAM');
26
+ this.lockTargetPath = resolver.resolve('EVOLUTION_LOCK');
27
+ this.blacklistPath = resolver.resolve('PRINCIPLE_BLACKLIST');
28
+ this.ensureDirs();
29
+ this.loadFromStream();
30
+ this.sweepExpiredProbation();
31
+ }
32
+ emit(event) {
33
+ this.emitSync(event);
34
+ }
35
+ emitSync(event) {
36
+ withLock(this.lockTargetPath, () => {
37
+ fs.appendFileSync(this.streamPath, `${JSON.stringify(event)}\n`, 'utf8');
38
+ }, { lockStaleMs: 15000 });
39
+ this.applyEvent(event);
40
+ if (event.type !== 'pain_detected') {
41
+ try {
42
+ TrajectoryRegistry.use(this.workspaceDir, (trajectory) => {
43
+ trajectory.recordPrincipleEvent({
44
+ principleId: 'principleId' in event.data && typeof event.data.principleId === 'string' ? event.data.principleId : null,
45
+ eventType: event.type,
46
+ payload: event.data,
47
+ createdAt: event.ts,
48
+ });
49
+ });
50
+ }
51
+ catch {
52
+ // Keep evolution loop resilient if trajectory storage is unavailable.
53
+ }
54
+ }
55
+ // Performance: sweepExpiredProbation() moved to getProbationPrinciples() for lazy cleanup
56
+ }
57
+ getEventLog() {
58
+ return [...this.memoryEvents];
59
+ }
60
+ getCandidatePrinciples() {
61
+ return this.getByStatus('candidate');
62
+ }
63
+ getProbationPrinciples() {
64
+ // Lazy cleanup: sweep expired probation principles on access
65
+ this.sweepExpiredProbation();
66
+ return this.getByStatus('probation');
67
+ }
68
+ getActivePrinciples() {
69
+ return this.getByStatus('active');
70
+ }
71
+ getPrincipleById(id) {
72
+ return this.principles.get(id) ?? null;
73
+ }
74
+ promote(principleId, reason = 'manual') {
75
+ const p = this.principles.get(principleId);
76
+ if (!p || p.status === 'active' || p.status === 'deprecated')
77
+ return;
78
+ const nextStatus = p.status === 'candidate' ? 'probation' : 'active';
79
+ const event = {
80
+ ts: new Date().toISOString(),
81
+ type: 'principle_promoted',
82
+ data: {
83
+ principleId,
84
+ from: p.status,
85
+ to: nextStatus,
86
+ reason,
87
+ successCount: p.validation.successCount,
88
+ },
89
+ };
90
+ this.emitSync(event);
91
+ }
92
+ deprecate(principleId, reason) {
93
+ const p = this.principles.get(principleId);
94
+ if (!p || p.status === 'deprecated')
95
+ return;
96
+ this.emitSync({
97
+ ts: new Date().toISOString(),
98
+ type: 'principle_deprecated',
99
+ data: {
100
+ principleId,
101
+ reason,
102
+ triggeredBy: 'manual',
103
+ },
104
+ });
105
+ }
106
+ rollbackPrinciple(principleId, reason) {
107
+ const p = this.principles.get(principleId);
108
+ if (!p)
109
+ return;
110
+ this.emitSync({
111
+ ts: new Date().toISOString(),
112
+ type: 'principle_rolled_back',
113
+ data: {
114
+ principleId,
115
+ reason,
116
+ triggeredBy: 'user_command',
117
+ blacklistPattern: p.trigger,
118
+ relatedPainId: p.source.painId,
119
+ },
120
+ });
121
+ }
122
+ recordProbationFeedback(principleId, success) {
123
+ const p = this.principles.get(principleId);
124
+ if (!p || p.status !== 'probation')
125
+ return;
126
+ if (success) {
127
+ p.validation.successCount += 1;
128
+ p.feedbackScore += 10;
129
+ if (p.validation.successCount >= PROBATION_SUCCESS_THRESHOLD) {
130
+ this.promote(principleId, 'auto_threshold');
131
+ }
132
+ return;
133
+ }
134
+ p.validation.conflictCount += 1;
135
+ if (p.validation.conflictCount >= 1) {
136
+ this.deprecate(principleId, 'conflict_detected');
137
+ }
138
+ }
139
+ getStats() {
140
+ return {
141
+ candidateCount: this.getCandidatePrinciples().length,
142
+ probationCount: this.getProbationPrinciples().length,
143
+ activeCount: this.getActivePrinciples().length,
144
+ deprecatedCount: this.getByStatus('deprecated').length,
145
+ lastPromotedAt: this.lastPromotedAt,
146
+ };
147
+ }
148
+ ensureDirs() {
149
+ fs.mkdirSync(path.dirname(this.streamPath), { recursive: true });
150
+ fs.mkdirSync(path.dirname(this.lockTargetPath), { recursive: true });
151
+ fs.mkdirSync(path.dirname(this.blacklistPath), { recursive: true });
152
+ }
153
+ loadFromStream() {
154
+ if (!fs.existsSync(this.streamPath))
155
+ return;
156
+ const raw = fs.readFileSync(this.streamPath, 'utf8').trim();
157
+ if (!raw)
158
+ return;
159
+ this.isReplaying = true;
160
+ for (const line of raw.split('\n')) {
161
+ try {
162
+ const event = JSON.parse(line);
163
+ this.applyEvent(event);
164
+ }
165
+ catch (e) {
166
+ SystemLogger.log(this.workspaceDir, 'EVOLUTION_WARN', `skip malformed event line: ${String(e)}`);
167
+ }
168
+ }
169
+ this.isReplaying = false;
170
+ }
171
+ applyEvent(event) {
172
+ this.memoryEvents.push(event);
173
+ switch (event.type) {
174
+ case 'pain_detected':
175
+ this.updateFailureStreakFromPain(event.data);
176
+ if (!this.isReplaying) {
177
+ this.onPainDetected(event.data, event.ts);
178
+ }
179
+ return;
180
+ case 'candidate_created':
181
+ this.onCandidateCreated(event.data, event.ts);
182
+ return;
183
+ case 'principle_promoted':
184
+ this.onPrinciplePromoted(event.data, event.ts);
185
+ return;
186
+ case 'principle_deprecated':
187
+ this.onPrincipleDeprecated(event.data, event.ts);
188
+ return;
189
+ case 'principle_rolled_back':
190
+ this.onPrincipleRolledBack(event.data, event.ts);
191
+ return;
192
+ case 'circuit_breaker_opened':
193
+ case 'legacy_import':
194
+ return;
195
+ default:
196
+ return;
197
+ }
198
+ }
199
+ onCandidateCreated(data, ts) {
200
+ const existing = this.principles.get(data.principleId);
201
+ if (existing) {
202
+ existing.status = 'candidate';
203
+ return;
204
+ }
205
+ const principle = {
206
+ id: data.principleId,
207
+ version: 1,
208
+ text: `When ${data.trigger}, then ${data.action}.`,
209
+ source: {
210
+ painId: data.painId,
211
+ painType: 'tool_failure',
212
+ timestamp: ts,
213
+ },
214
+ trigger: data.trigger,
215
+ action: data.action,
216
+ contextTags: [],
217
+ validation: { successCount: 0, conflictCount: 0 },
218
+ status: 'candidate',
219
+ feedbackScore: 0,
220
+ usageCount: 0,
221
+ createdAt: ts,
222
+ };
223
+ this.principles.set(principle.id, principle);
224
+ }
225
+ onPrinciplePromoted(data, ts) {
226
+ const p = this.principles.get(data.principleId);
227
+ if (!p)
228
+ return;
229
+ p.status = data.to;
230
+ if (data.to === 'active') {
231
+ p.activatedAt = ts;
232
+ }
233
+ this.lastPromotedAt = ts;
234
+ }
235
+ onPrincipleDeprecated(data, ts) {
236
+ const p = this.principles.get(data.principleId);
237
+ if (!p)
238
+ return;
239
+ p.status = 'deprecated';
240
+ p.deprecatedAt = ts;
241
+ }
242
+ onPrincipleRolledBack(data, ts) {
243
+ const p = this.principles.get(data.principleId);
244
+ if (p) {
245
+ p.status = 'deprecated';
246
+ p.deprecatedAt = ts;
247
+ }
248
+ this.persistBlacklist({
249
+ painId: data.relatedPainId,
250
+ pattern: data.blacklistPattern,
251
+ reason: data.reason,
252
+ rolledBackAt: ts,
253
+ });
254
+ }
255
+ onPainDetected(data, eventTs) {
256
+ const trigger = String(data.reason ?? data.source ?? 'unknown trigger');
257
+ const action = `Prevent recurrence for: ${String(data.source ?? 'unknown')}`;
258
+ // Defense in depth: protocol/system tokens must never become principles,
259
+ // even if a pain_detected event is emitted from a new callsite in the future.
260
+ if (shouldIgnorePainProtocolText(trigger)) {
261
+ return;
262
+ }
263
+ if (this.isBlacklisted(data.painId, trigger)) {
264
+ return;
265
+ }
266
+ const principleId = this.nextPrincipleId();
267
+ const principle = {
268
+ id: principleId,
269
+ version: 1,
270
+ text: `When ${trigger}, then ${action}.`,
271
+ source: {
272
+ painId: data.painId,
273
+ painType: data.painType,
274
+ timestamp: eventTs,
275
+ },
276
+ trigger,
277
+ action,
278
+ contextTags: [data.source],
279
+ validation: { successCount: 0, conflictCount: 0 },
280
+ status: 'candidate',
281
+ feedbackScore: 0,
282
+ usageCount: 0,
283
+ createdAt: eventTs,
284
+ };
285
+ this.principles.set(principleId, principle);
286
+ this.emitSync({
287
+ ts: new Date().toISOString(),
288
+ type: 'candidate_created',
289
+ data: {
290
+ painId: principle.source.painId,
291
+ principleId,
292
+ trigger,
293
+ action,
294
+ status: 'candidate',
295
+ },
296
+ });
297
+ this.promote(principleId, 'auto_from_pain');
298
+ if (data.painType === 'subagent_error') {
299
+ const key = String(data.taskId ?? data.source ?? 'subagent');
300
+ const next = this.failureStreak.get(key) ?? 0;
301
+ if (next >= CIRCUIT_BREAKER_THRESHOLD) {
302
+ const nextRetryAt = new Date(Date.now() + 15 * 60 * 1000).toISOString();
303
+ this.emitSync({
304
+ ts: new Date().toISOString(),
305
+ type: 'circuit_breaker_opened',
306
+ data: {
307
+ taskId: key,
308
+ painId: principle.source.painId,
309
+ failCount: next,
310
+ reason: 'Max retries exceeded',
311
+ requireHuman: true,
312
+ nextRetryAt,
313
+ },
314
+ });
315
+ }
316
+ }
317
+ }
318
+ updateFailureStreakFromPain(data) {
319
+ if (data.painType !== 'subagent_error')
320
+ return;
321
+ const key = String(data.taskId ?? data.source ?? 'subagent');
322
+ const next = (this.failureStreak.get(key) ?? 0) + 1;
323
+ this.failureStreak.set(key, next);
324
+ }
325
+ nextPrincipleId() {
326
+ const ids = [...this.principles.keys()]
327
+ .map((id) => Number(id.replace(/^P_/, '')))
328
+ .filter((n) => Number.isFinite(n));
329
+ const next = (ids.length ? Math.max(...ids) : 0) + 1;
330
+ return `P_${String(next).padStart(3, '0')}`;
331
+ }
332
+ getByStatus(status) {
333
+ return [...this.principles.values()].filter((p) => p.status === status);
334
+ }
335
+ sweepExpiredProbation() {
336
+ const now = Date.now();
337
+ const maxAgeMs = PROBATION_MAX_AGE_DAYS * 24 * 60 * 60 * 1000;
338
+ // Use getByStatus directly to avoid infinite recursion with getProbationPrinciples()
339
+ for (const p of this.getByStatus('probation')) {
340
+ const age = now - new Date(p.createdAt).getTime();
341
+ if (age > maxAgeMs) {
342
+ this.deprecate(p.id, 'probation_expired');
343
+ }
344
+ }
345
+ }
346
+ persistBlacklist(entry) {
347
+ const list = this.loadBlacklist();
348
+ list.push(entry);
349
+ fs.writeFileSync(this.blacklistPath, JSON.stringify(list, null, 2), 'utf8');
350
+ }
351
+ loadBlacklist() {
352
+ if (!fs.existsSync(this.blacklistPath))
353
+ return [];
354
+ try {
355
+ return JSON.parse(fs.readFileSync(this.blacklistPath, 'utf8'));
356
+ }
357
+ catch (e) {
358
+ SystemLogger.log(this.workspaceDir, 'EVOLUTION_WARN', `failed to parse blacklist: ${String(e)}`);
359
+ return [];
360
+ }
361
+ }
362
+ isBlacklisted(painId, trigger) {
363
+ return this.loadBlacklist().some((entry) => (entry.painId && entry.painId === painId) ||
364
+ (entry.pattern && trigger.includes(entry.pattern)));
365
+ }
366
+ }
367
+ export function stableContentHash(input) {
368
+ return crypto.createHash('sha1').update(input).digest('hex');
369
+ }
@@ -124,3 +124,106 @@ export interface TierPromotionEvent {
124
124
  timestamp: string;
125
125
  newPermissions: TierPermissions;
126
126
  }
127
+ export type PrincipleStatus = 'candidate' | 'probation' | 'active' | 'deprecated';
128
+ export interface Principle {
129
+ id: string;
130
+ version: number;
131
+ text: string;
132
+ source: {
133
+ painId: string;
134
+ painType: 'tool_failure' | 'subagent_error' | 'user_frustration';
135
+ timestamp: string;
136
+ };
137
+ trigger: string;
138
+ action: string;
139
+ guardrails?: string[];
140
+ contextTags: string[];
141
+ validation: {
142
+ successCount: number;
143
+ conflictCount: number;
144
+ };
145
+ status: PrincipleStatus;
146
+ feedbackScore: number;
147
+ usageCount: number;
148
+ createdAt: string;
149
+ activatedAt?: string;
150
+ deprecatedAt?: string;
151
+ }
152
+ export type EvolutionLoopEventType = 'pain_detected' | 'candidate_created' | 'principle_promoted' | 'principle_deprecated' | 'principle_rolled_back' | 'circuit_breaker_opened' | 'legacy_import';
153
+ export interface PainDetectedData {
154
+ painId: string;
155
+ painType: 'tool_failure' | 'subagent_error' | 'user_frustration';
156
+ source: string;
157
+ reason: string;
158
+ score?: number;
159
+ sessionId?: string;
160
+ taskId?: string;
161
+ }
162
+ export interface CandidateCreatedData {
163
+ painId: string;
164
+ principleId: string;
165
+ trigger: string;
166
+ action: string;
167
+ status: 'candidate';
168
+ }
169
+ export interface PrinciplePromotedData {
170
+ principleId: string;
171
+ from: 'candidate' | 'probation';
172
+ to: 'probation' | 'active';
173
+ reason: string;
174
+ successCount?: number;
175
+ }
176
+ export interface PrincipleDeprecatedData {
177
+ principleId: string;
178
+ reason: string;
179
+ triggeredBy: 'auto' | 'manual';
180
+ }
181
+ export interface PrincipleRolledBackData {
182
+ principleId: string;
183
+ reason: string;
184
+ triggeredBy: 'user_command' | 'auto_conflict';
185
+ blacklistPattern?: string;
186
+ relatedPainId?: string;
187
+ }
188
+ export interface CircuitBreakerOpenedData {
189
+ taskId: string;
190
+ painId: string;
191
+ failCount: number;
192
+ reason: string;
193
+ requireHuman: boolean;
194
+ nextRetryAt?: string;
195
+ }
196
+ export interface LegacyImportData {
197
+ sourceFile: string;
198
+ content: string;
199
+ contentHash?: string;
200
+ }
201
+ export type EvolutionLoopEvent = {
202
+ ts: string;
203
+ type: 'pain_detected';
204
+ data: PainDetectedData;
205
+ } | {
206
+ ts: string;
207
+ type: 'candidate_created';
208
+ data: CandidateCreatedData;
209
+ } | {
210
+ ts: string;
211
+ type: 'principle_promoted';
212
+ data: PrinciplePromotedData;
213
+ } | {
214
+ ts: string;
215
+ type: 'principle_deprecated';
216
+ data: PrincipleDeprecatedData;
217
+ } | {
218
+ ts: string;
219
+ type: 'principle_rolled_back';
220
+ data: PrincipleRolledBackData;
221
+ } | {
222
+ ts: string;
223
+ type: 'circuit_breaker_opened';
224
+ data: CircuitBreakerOpenedData;
225
+ } | {
226
+ ts: string;
227
+ type: 'legacy_import';
228
+ data: LegacyImportData;
229
+ };
@@ -42,6 +42,21 @@ const PD_CONFIG_LOCATIONS = [
42
42
  path.join(os.homedir(), '.openclaw', PD_CONFIG_FILE),
43
43
  path.join(os.homedir(), '.principles', PD_CONFIG_FILE),
44
44
  ];
45
+ function isWindowsPath(inputPath) {
46
+ return /^[A-Za-z]:[\\/]/.test(inputPath) || inputPath.startsWith('\\\\');
47
+ }
48
+ function isPosixAbsolutePath(inputPath) {
49
+ return inputPath.startsWith('/');
50
+ }
51
+ function getPathApi(inputPath) {
52
+ if (isWindowsPath(inputPath)) {
53
+ return path.win32;
54
+ }
55
+ if (isPosixAbsolutePath(inputPath)) {
56
+ return path.posix;
57
+ }
58
+ return path;
59
+ }
45
60
  function findConfigFile() {
46
61
  for (const loc of PD_CONFIG_LOCATIONS) {
47
62
  if (fs.existsSync(loc)) {
@@ -77,7 +92,9 @@ export class PathResolver {
77
92
  if (!extensionRootPath || !extensionRootPath.trim()) {
78
93
  return;
79
94
  }
80
- PathResolver.extensionRoot = path.resolve(extensionRootPath.trim());
95
+ const trimmed = extensionRootPath.trim();
96
+ const pathApi = getPathApi(trimmed);
97
+ PathResolver.extensionRoot = pathApi.normalize(trimmed);
81
98
  }
82
99
  static getExtensionRoot() {
83
100
  return PathResolver.extensionRoot;
@@ -86,7 +103,13 @@ export class PathResolver {
86
103
  this.logger = options.logger;
87
104
  this.normalizeWorkspace = options.normalizeWorkspace ?? true;
88
105
  if (options.workspaceDir) {
89
- this.workspaceDir = options.workspaceDir;
106
+ const original = options.workspaceDir;
107
+ const normalized = this.normalizeWorkspace ? this.normalizePath(original) : original;
108
+ if (original !== normalized) {
109
+ this.log('info', `Workspace path normalized: ${original} -> ${normalized}`);
110
+ }
111
+ this.workspaceDir = normalized;
112
+ this.initialized = true;
90
113
  }
91
114
  }
92
115
  log(level, msg) {
@@ -132,12 +155,13 @@ export class PathResolver {
132
155
  return defaultWorkspace;
133
156
  }
134
157
  normalizePath(inputPath) {
135
- let normalized = path.resolve(inputPath);
158
+ const pathApi = getPathApi(inputPath);
159
+ let normalized = pathApi === path ? path.resolve(inputPath) : pathApi.normalize(inputPath);
136
160
  if (this.normalizeWorkspace) {
137
- const problematicSuffixes = ['/memory', '/docs'];
161
+ const problematicSuffixes = ['/memory', '/docs', '\\memory', '\\docs'];
138
162
  for (const suffix of problematicSuffixes) {
139
163
  if (normalized.endsWith(suffix)) {
140
- const parent = path.dirname(normalized);
164
+ const parent = pathApi.dirname(normalized);
141
165
  this.log('warn', `Detected subdirectory suffix '${suffix}' in path. Normalized to parent: ${parent}`);
142
166
  normalized = parent;
143
167
  break;
@@ -176,57 +200,72 @@ export class PathResolver {
176
200
  this.log('info', `Using state directory from PD_STATE_DIR: ${this.stateDir}`);
177
201
  return this.stateDir;
178
202
  }
203
+ // If workspaceDir was explicitly provided via constructor, use workspace-based state dir
204
+ // This ensures tests and programmatic usage don't get polluted by global config
205
+ if (this.initialized && this.workspaceDir) {
206
+ const pathApi = getPathApi(this.workspaceDir);
207
+ this.stateDir = pathApi.join(this.workspaceDir, '.state');
208
+ this.log('debug', `Using workspace-based state directory: ${this.stateDir}`);
209
+ return this.stateDir;
210
+ }
179
211
  const fileConfig = loadConfigFromFile();
180
212
  if (fileConfig?.state) {
181
213
  this.stateDir = fileConfig.state;
182
214
  this.log('info', `Using state directory from config file: ${this.stateDir}`);
183
215
  return this.stateDir;
184
216
  }
185
- this.stateDir = path.join(this.getWorkspaceDir(), '.state');
217
+ const workspaceDir = this.getWorkspaceDir();
218
+ const pathApi = getPathApi(workspaceDir);
219
+ this.stateDir = pathApi.join(workspaceDir, '.state');
186
220
  this.log('debug', `Computed state directory: ${this.stateDir}`);
187
221
  return this.stateDir;
188
222
  }
189
223
  resolve(key) {
190
224
  const workspace = this.getWorkspaceDir();
191
225
  const state = this.getStateDir();
192
- const memory = path.join(workspace, 'memory');
226
+ const workspacePath = getPathApi(workspace);
193
227
  const extensionRoot = PathResolver.extensionRoot || path.resolve(process.cwd(), 'packages', 'openclaw-plugin');
194
- const extensionSrc = path.join(extensionRoot, 'src');
195
- const extensionDist = path.join(extensionRoot, 'dist');
228
+ const extensionPath = getPathApi(extensionRoot);
229
+ const memory = workspacePath.join(workspace, 'memory');
230
+ const extensionSrc = extensionPath.join(extensionRoot, 'src');
231
+ const extensionDist = extensionPath.join(extensionRoot, 'dist');
196
232
  const evolutionWorker = fs.existsSync(extensionSrc)
197
- ? path.join(extensionSrc, 'service', 'evolution-worker.ts')
198
- : path.join(extensionDist, 'service', 'evolution-worker.js');
233
+ ? extensionPath.join(extensionSrc, 'service', 'evolution-worker.ts')
234
+ : extensionPath.join(extensionDist, 'service', 'evolution-worker.js');
199
235
  const pathMap = {
200
- 'PROFILE': path.join(workspace, '.principles', 'PROFILE.json'),
201
- 'PRINCIPLES': path.join(workspace, '.principles', 'PRINCIPLES.md'),
202
- 'THINKING_OS': path.join(workspace, '.principles', 'THINKING_OS.md'),
203
- 'KERNEL': path.join(workspace, '.principles', '00-kernel.md'),
204
- 'DECISION_POLICY': path.join(workspace, '.principles', 'DECISION_POLICY.json'),
205
- 'MODELS_DIR': path.join(workspace, '.principles', 'models'),
206
- 'PLAN': path.join(workspace, 'PLAN.md'),
207
- 'AGENT_SCORECARD': path.join(state, 'AGENT_SCORECARD.json'),
208
- 'PAIN_FLAG': path.join(state, '.pain_flag'),
209
- 'EVOLUTION_QUEUE': path.join(state, 'evolution_queue.json'),
210
- 'EVOLUTION_DIRECTIVE': path.join(state, 'evolution_directive.json'),
211
- 'WORKBOARD': path.join(state, 'WORKBOARD.json'),
212
- 'SYSTEM_CAPABILITIES': path.join(state, 'SYSTEM_CAPABILITIES.json'),
213
- 'PAIN_SETTINGS': path.join(state, 'pain_settings.json'),
214
- 'PAIN_CANDIDATES': path.join(state, 'pain_candidates.json'),
215
- 'THINKING_OS_USAGE': path.join(state, 'thinking_os_usage.json'),
216
- 'DICTIONARY': path.join(state, 'pain_dictionary.json'),
236
+ 'PROFILE': workspacePath.join(workspace, '.principles', 'PROFILE.json'),
237
+ 'PRINCIPLES': workspacePath.join(workspace, '.principles', 'PRINCIPLES.md'),
238
+ 'THINKING_OS': workspacePath.join(workspace, '.principles', 'THINKING_OS.md'),
239
+ 'KERNEL': workspacePath.join(workspace, '.principles', '00-kernel.md'),
240
+ 'DECISION_POLICY': workspacePath.join(workspace, '.principles', 'DECISION_POLICY.json'),
241
+ 'MODELS_DIR': workspacePath.join(workspace, '.principles', 'models'),
242
+ 'PLAN': workspacePath.join(workspace, 'PLAN.md'),
243
+ 'AGENT_SCORECARD': workspacePath.join(state, 'AGENT_SCORECARD.json'),
244
+ 'PAIN_FLAG': workspacePath.join(state, '.pain_flag'),
245
+ 'EVOLUTION_QUEUE': workspacePath.join(state, 'evolution_queue.json'),
246
+ 'EVOLUTION_DIRECTIVE': workspacePath.join(state, 'evolution_directive.json'),
247
+ 'WORKBOARD': workspacePath.join(state, 'WORKBOARD.json'),
248
+ 'SYSTEM_CAPABILITIES': workspacePath.join(state, 'SYSTEM_CAPABILITIES.json'),
249
+ 'PAIN_SETTINGS': workspacePath.join(state, 'pain_settings.json'),
250
+ 'PAIN_CANDIDATES': workspacePath.join(state, 'pain_candidates.json'),
251
+ 'THINKING_OS_USAGE': workspacePath.join(state, 'thinking_os_usage.json'),
252
+ 'DICTIONARY': workspacePath.join(state, 'pain_dictionary.json'),
217
253
  'STATE_DIR': state,
218
254
  'EXTENSION_ROOT': extensionRoot,
219
255
  'EXTENSION_SRC': extensionSrc,
220
256
  'EXTENSION_DIST': extensionDist,
221
257
  'EVOLUTION_WORKER': evolutionWorker,
222
- 'LOGS': path.join(memory, 'logs'),
223
- 'SYSTEM_LOG': path.join(memory, 'logs', 'SYSTEM.log'),
224
- 'REFLECTION_LOG': path.join(memory, 'reflection-log.md'),
225
- 'USER_CONTEXT': path.join(memory, 'USER_CONTEXT.md'),
226
- 'OKR_DIR': path.join(memory, 'okr'),
227
- 'CURRENT_FOCUS': path.join(memory, 'okr', 'CURRENT_FOCUS.md'),
228
- 'WEEK_STATE': path.join(memory, 'okr', 'WEEK_STATE.json'),
229
- 'THINKING_OS_CANDIDATES': path.join(memory, 'THINKING_OS_CANDIDATES.md'),
258
+ 'LOGS': workspacePath.join(memory, 'logs'),
259
+ 'SYSTEM_LOG': workspacePath.join(memory, 'logs', 'SYSTEM.log'),
260
+ 'REFLECTION_LOG': workspacePath.join(memory, 'reflection-log.md'),
261
+ 'USER_CONTEXT': workspacePath.join(memory, 'USER_CONTEXT.md'),
262
+ 'OKR_DIR': workspacePath.join(memory, 'okr'),
263
+ 'CURRENT_FOCUS': workspacePath.join(memory, 'okr', 'CURRENT_FOCUS.md'),
264
+ 'WEEK_STATE': workspacePath.join(memory, 'okr', 'WEEK_STATE.json'),
265
+ 'THINKING_OS_CANDIDATES': workspacePath.join(memory, 'THINKING_OS_CANDIDATES.md'),
266
+ 'EVOLUTION_STREAM': workspacePath.join(memory, 'evolution.jsonl'),
267
+ 'EVOLUTION_LOCK': workspacePath.join(memory, '.locks', 'evolution'),
268
+ 'PRINCIPLE_BLACKLIST': workspacePath.join(state, 'principle_blacklist.json'),
230
269
  'MEMORY': memory,
231
270
  };
232
271
  const resolved = pathMap[key];
@@ -3,22 +3,15 @@
3
3
  * Establishing a logical separation between Identity, State, and Memory.
4
4
  */
5
5
  export declare const PD_DIRS: {
6
- /** 🧬 Core configuration, identity, and kernel rules (hidden) */
7
6
  IDENTITY: string;
8
- /** 🧠 Deep Reflection mental models */
9
7
  MODELS: string;
10
- /** ⚡ Volatile operational data, queues, and task status (hidden) */
11
8
  STATE: string;
12
- /** 💾 Historical records, logs, and long-term memory */
13
9
  MEMORY: string;
14
- /** 🎯 Strategic objectives and focus areas */
15
10
  OKR: string;
16
- /** 📁 Internal logs directory */
17
11
  LOGS: string;
18
- /** 🧠 Session persistence directory */
19
12
  SESSIONS: string;
20
- /** 🩹 Semantic pain samples for L3 retrieval */
21
13
  PAIN_SAMPLES: string;
14
+ LOCKS: string;
22
15
  };
23
16
  /**
24
17
  * Standard File Path Mappings
@@ -40,8 +33,12 @@ export declare const PD_FILES: {
40
33
  PAIN_SETTINGS: string;
41
34
  PAIN_CANDIDATES: string;
42
35
  THINKING_OS_USAGE: string;
36
+ TRAJECTORY_DB: string;
37
+ TRAJECTORY_BLOBS_DIR: string;
38
+ EXPORTS_DIR: string;
43
39
  SESSION_DIR: string;
44
40
  DICTIONARY: string;
41
+ PRINCIPLE_BLACKLIST: string;
45
42
  PLAN: string;
46
43
  MEMORY_MD: string;
47
44
  HEARTBEAT: string;
@@ -53,6 +50,8 @@ export declare const PD_FILES: {
53
50
  WEEK_STATE: string;
54
51
  THINKING_OS_CANDIDATES: string;
55
52
  SEMANTIC_PAIN: string;
53
+ EVOLUTION_STREAM: string;
54
+ EVOLUTION_LOCK: string;
56
55
  };
57
56
  /**
58
57
  * Resolves a PD file path within a given workspace.