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.
- package/dist/commands/context.js +7 -3
- package/dist/commands/evolution-status.d.ts +4 -0
- package/dist/commands/evolution-status.js +134 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +45 -0
- package/dist/commands/focus.js +9 -6
- package/dist/commands/pain.js +8 -0
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/rollback.js +9 -3
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/commands/trust.js +64 -81
- package/dist/core/config.d.ts +5 -0
- package/dist/core/control-ui-db.d.ts +68 -0
- package/dist/core/control-ui-db.js +274 -0
- package/dist/core/detection-funnel.d.ts +1 -1
- package/dist/core/detection-funnel.js +4 -0
- package/dist/core/dictionary.d.ts +2 -0
- package/dist/core/dictionary.js +13 -0
- package/dist/core/event-log.d.ts +7 -1
- package/dist/core/event-log.js +10 -0
- package/dist/core/evolution-engine.d.ts +5 -5
- package/dist/core/evolution-engine.js +18 -18
- package/dist/core/evolution-migration.d.ts +5 -0
- package/dist/core/evolution-migration.js +65 -0
- package/dist/core/evolution-reducer.d.ts +69 -0
- package/dist/core/evolution-reducer.js +369 -0
- package/dist/core/evolution-types.d.ts +103 -0
- package/dist/core/path-resolver.js +75 -36
- package/dist/core/paths.d.ts +7 -8
- package/dist/core/paths.js +48 -40
- package/dist/core/profile.js +1 -1
- package/dist/core/session-tracker.d.ts +14 -2
- package/dist/core/session-tracker.js +75 -9
- package/dist/core/thinking-models.d.ts +38 -0
- package/dist/core/thinking-models.js +170 -0
- package/dist/core/trajectory.d.ts +184 -0
- package/dist/core/trajectory.js +817 -0
- package/dist/core/trust-engine.d.ts +6 -0
- package/dist/core/trust-engine.js +50 -29
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +171 -87
- package/dist/hooks/llm.js +119 -71
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +11 -14
- package/dist/hooks/prompt.js +283 -57
- package/dist/hooks/subagent.js +69 -28
- package/dist/hooks/trajectory-collector.d.ts +32 -0
- package/dist/hooks/trajectory-collector.js +256 -0
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +16 -0
- package/dist/index.js +105 -4
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/empathy-observer-manager.d.ts +2 -0
- package/dist/service/empathy-observer-manager.js +43 -1
- package/dist/service/evolution-worker.d.ts +27 -0
- package/dist/service/evolution-worker.js +256 -41
- package/dist/service/runtime-summary-service.d.ts +79 -0
- package/dist/service/runtime-summary-service.js +319 -0
- package/dist/service/trajectory-service.d.ts +2 -0
- package/dist/service/trajectory-service.js +15 -0
- package/dist/tools/agent-spawn.d.ts +27 -6
- package/dist/tools/agent-spawn.js +339 -87
- package/dist/tools/deep-reflect.d.ts +27 -7
- package/dist/tools/deep-reflect.js +210 -121
- package/dist/types/event-types.d.ts +10 -2
- package/dist/types.d.ts +10 -0
- package/dist/types.js +5 -0
- package/openclaw.plugin.json +43 -11
- package/package.json +14 -4
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
226
|
+
const workspacePath = getPathApi(workspace);
|
|
193
227
|
const extensionRoot = PathResolver.extensionRoot || path.resolve(process.cwd(), 'packages', 'openclaw-plugin');
|
|
194
|
-
const
|
|
195
|
-
const
|
|
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
|
-
?
|
|
198
|
-
:
|
|
233
|
+
? extensionPath.join(extensionSrc, 'service', 'evolution-worker.ts')
|
|
234
|
+
: extensionPath.join(extensionDist, 'service', 'evolution-worker.js');
|
|
199
235
|
const pathMap = {
|
|
200
|
-
'PROFILE':
|
|
201
|
-
'PRINCIPLES':
|
|
202
|
-
'THINKING_OS':
|
|
203
|
-
'KERNEL':
|
|
204
|
-
'DECISION_POLICY':
|
|
205
|
-
'MODELS_DIR':
|
|
206
|
-
'PLAN':
|
|
207
|
-
'AGENT_SCORECARD':
|
|
208
|
-
'PAIN_FLAG':
|
|
209
|
-
'EVOLUTION_QUEUE':
|
|
210
|
-
'EVOLUTION_DIRECTIVE':
|
|
211
|
-
'WORKBOARD':
|
|
212
|
-
'SYSTEM_CAPABILITIES':
|
|
213
|
-
'PAIN_SETTINGS':
|
|
214
|
-
'PAIN_CANDIDATES':
|
|
215
|
-
'THINKING_OS_USAGE':
|
|
216
|
-
'DICTIONARY':
|
|
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':
|
|
223
|
-
'SYSTEM_LOG':
|
|
224
|
-
'REFLECTION_LOG':
|
|
225
|
-
'USER_CONTEXT':
|
|
226
|
-
'OKR_DIR':
|
|
227
|
-
'CURRENT_FOCUS':
|
|
228
|
-
'WEEK_STATE':
|
|
229
|
-
'THINKING_OS_CANDIDATES':
|
|
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];
|
package/dist/core/paths.d.ts
CHANGED
|
@@ -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.
|