principles-disciple 1.5.4 → 1.6.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.
Files changed (43) hide show
  1. package/dist/commands/context.d.ts +5 -0
  2. package/dist/commands/context.js +308 -0
  3. package/dist/commands/focus.d.ts +14 -0
  4. package/dist/commands/focus.js +579 -0
  5. package/dist/commands/pain.js +135 -6
  6. package/dist/commands/rollback.d.ts +19 -0
  7. package/dist/commands/rollback.js +119 -0
  8. package/dist/core/config.d.ts +32 -0
  9. package/dist/core/config.js +47 -0
  10. package/dist/core/event-log.d.ts +21 -1
  11. package/dist/core/event-log.js +316 -0
  12. package/dist/core/focus-history.d.ts +65 -0
  13. package/dist/core/focus-history.js +266 -0
  14. package/dist/core/init.js +30 -7
  15. package/dist/core/migration.js +0 -2
  16. package/dist/core/path-resolver.d.ts +3 -0
  17. package/dist/core/path-resolver.js +20 -0
  18. package/dist/hooks/gate.js +203 -1
  19. package/dist/hooks/llm.d.ts +8 -0
  20. package/dist/hooks/llm.js +234 -1
  21. package/dist/hooks/message-sanitize.d.ts +3 -0
  22. package/dist/hooks/message-sanitize.js +37 -0
  23. package/dist/hooks/prompt.d.ts +12 -0
  24. package/dist/hooks/prompt.js +309 -135
  25. package/dist/hooks/subagent.d.ts +9 -2
  26. package/dist/hooks/subagent.js +13 -2
  27. package/dist/i18n/commands.js +32 -20
  28. package/dist/index.js +181 -4
  29. package/dist/service/empathy-observer-manager.d.ts +42 -0
  30. package/dist/service/empathy-observer-manager.js +147 -0
  31. package/dist/service/evolution-worker.d.ts +1 -0
  32. package/dist/service/evolution-worker.js +4 -2
  33. package/dist/tools/deep-reflect.js +80 -0
  34. package/dist/types/event-types.d.ts +77 -2
  35. package/dist/types/event-types.js +33 -0
  36. package/dist/types.d.ts +42 -0
  37. package/dist/types.js +19 -1
  38. package/openclaw.plugin.json +1 -1
  39. package/package.json +3 -3
  40. package/templates/langs/zh/core/HEARTBEAT.md +28 -4
  41. package/templates/pain_settings.json +54 -2
  42. package/templates/workspace/.principles/PROFILE.json +2 -0
  43. package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
@@ -15,48 +15,60 @@ export function normalizeLanguage(lang) {
15
15
  }
16
16
  export const commandDescriptions = {
17
17
  'pd-init': {
18
- zh: '初始化战略访谈和OKR',
19
- en: 'Initialize strategy interview and OKRs'
18
+ zh: '初始化工作区(生成 PRINCIPLES.md、THINKING_OS.md 等)',
19
+ en: 'Initialize workspace (generate PRINCIPLES.md, THINKING_OS.md, etc.)'
20
20
  },
21
21
  'pd-okr': {
22
- zh: '目标与关键结果管理',
23
- en: 'Manage OKRs and align goals'
22
+ zh: '管理 OKR 目标与关键结果',
23
+ en: 'Manage OKR goals and key results'
24
24
  },
25
25
  'pd-bootstrap': {
26
- zh: '环境工具扫描与升级',
27
- en: 'Scan and upgrade environment tools'
26
+ zh: '扫描环境工具并建议升级',
27
+ en: 'Scan environment tools and suggest upgrades'
28
28
  },
29
29
  'pd-research': {
30
- zh: '发起工具升级研究',
31
- en: 'Research tool upgrades'
30
+ zh: '研究工具升级方案',
31
+ en: 'Research tool upgrade solutions'
32
32
  },
33
33
  'pd-thinking': {
34
- zh: '管理思维模型与候选方案',
35
- en: 'Manage Thinking OS mental models'
34
+ zh: '管理思维模型 [status|propose|audit]',
35
+ en: 'Manage Thinking OS [status|propose|audit]'
36
36
  },
37
37
  'pd-evolve': {
38
- zh: '执行完整进化循环',
39
- en: 'Run full evolution loop'
38
+ zh: '执行进化循环处理 Pain 信号',
39
+ en: 'Run evolution loop to process Pain signals'
40
40
  },
41
41
  'pd-daily': {
42
42
  zh: '配置并发送进化日报',
43
- en: 'Configure and send daily report'
43
+ en: 'Configure and send daily evolution report'
44
44
  },
45
45
  'pd-grooming': {
46
- zh: '工作区数字大扫除',
46
+ zh: '工作区清理与大扫除',
47
47
  en: 'Workspace cleanup and grooming'
48
48
  },
49
49
  'pd-trust': {
50
- zh: '查看信任积分与安全等级',
51
- en: 'View trust score and security stage'
50
+ zh: '查看信任分数和权限等级 (1-4)',
51
+ en: 'View trust score and permission stage (1-4)'
52
52
  },
53
53
  'pd-help': {
54
- zh: '获取交互式命令引导',
55
- en: 'Get interactive command guidance'
54
+ zh: '显示所有命令和使用指南',
55
+ en: 'Show all commands and usage guide'
56
56
  },
57
57
  'pd-status': {
58
- zh: '查看数字神经系统状态(GFI和痛苦词典)',
59
- en: 'View Digital Nerve System status (GFI and Pain Dictionary)'
58
+ zh: '查看系统状态(GFI、Pain 词典)',
59
+ en: 'View system status (GFI, Pain dictionary)'
60
+ },
61
+ 'pd-context': {
62
+ zh: '控制上下文注入 [status|thinking|trust|reflection|focus|preset] - 输入 /pd-context help 查看详情',
63
+ en: 'Control context injection [status|thinking|trust|reflection|focus|preset] - Type /pd-context help for details'
64
+ },
65
+ 'pd-focus': {
66
+ zh: '管理 CURRENT_FOCUS.md [status|history|compress|rollback] - 查看/压缩/回滚焦点文件',
67
+ en: 'Manage CURRENT_FOCUS.md [status|history|compress|rollback] - View/compress/rollback focus file'
68
+ },
69
+ 'pd-rollback': {
70
+ zh: '回滚情绪事件惩罚 <event-id>|last',
71
+ en: 'Rollback empathy event penalty <event-id>|last'
60
72
  }
61
73
  };
62
74
  /**
package/dist/index.js CHANGED
@@ -5,18 +5,23 @@ import { handleAfterToolCall } from './hooks/pain.js';
5
5
  import { handleBeforeReset, handleBeforeCompaction, handleAfterCompaction } from './hooks/lifecycle.js';
6
6
  import { handleLlmOutput } from './hooks/llm.js';
7
7
  import { handleSubagentEnded } from './hooks/subagent.js';
8
+ import { handleBeforeMessageWrite } from './hooks/message-sanitize.js';
8
9
  import { handleInitStrategy, handleManageOkr } from './commands/strategy.js';
9
10
  import { handleBootstrapTools, handleResearchTools } from './commands/capabilities.js';
10
11
  import { handleThinkingOs } from './commands/thinking-os.js';
11
12
  import { handleEvolveTask } from './commands/evolver.js';
12
13
  import { handleTrustCommand } from './commands/trust.js';
13
14
  import { handlePainCommand } from './commands/pain.js';
15
+ import { handleContextCommand } from './commands/context.js';
16
+ import { handleFocusCommand } from './commands/focus.js';
17
+ import { handleRollbackCommand } from './commands/rollback.js';
14
18
  import { EvolutionWorkerService } from './service/evolution-worker.js';
15
19
  import { ensureWorkspaceTemplates } from './core/init.js';
16
20
  import { migrateDirectoryStructure } from './core/migration.js';
17
21
  import { SystemLogger } from './core/system-logger.js';
18
22
  import { deepReflectTool } from './tools/deep-reflect.js';
19
23
  import { agentSpawnTool } from './tools/agent-spawn.js';
24
+ import { PathResolver } from './core/path-resolver.js';
20
25
  // Track initialization to avoid repeated calls
21
26
  let workspaceInitialized = false;
22
27
  const plugin = {
@@ -24,6 +29,7 @@ const plugin = {
24
29
  description: "Evolutionary programming agent framework with strategic guardrails and reflection loops.",
25
30
  register(api) {
26
31
  api.logger.info("Principles Disciple Plugin registered.");
32
+ PathResolver.setExtensionRoot(api.rootDir);
27
33
  const language = api.pluginConfig?.language || 'en';
28
34
  // ── Hook: Prompt Building ──
29
35
  api.on('before_prompt_build', async (event, ctx) => {
@@ -75,6 +81,15 @@ const plugin = {
75
81
  api.logger.error(`[PD] Error in llm_output: ${String(err)}`);
76
82
  }
77
83
  });
84
+ // ── Hook: Message Sanitization ──
85
+ api.on('before_message_write', (event) => {
86
+ try {
87
+ return handleBeforeMessageWrite(event);
88
+ }
89
+ catch (err) {
90
+ api.logger.error(`[PD] Error in before_message_write: ${String(err)}`);
91
+ }
92
+ });
78
93
  // ── Hook: Subagent Loop Closure ──
79
94
  api.on('subagent_spawning', (_event, _ctx) => {
80
95
  // No-op for now, just to satisfy the interface expected by tests.
@@ -83,7 +98,7 @@ const plugin = {
83
98
  api.on('subagent_ended', (event, ctx) => {
84
99
  try {
85
100
  const workspaceDir = api.resolvePath('.');
86
- handleSubagentEnded(event, { ...ctx, workspaceDir });
101
+ handleSubagentEnded(event, { ...ctx, workspaceDir, api });
87
102
  }
88
103
  catch (err) {
89
104
  api.logger.error(`[PD] Error in subagent_ended: ${String(err)}`);
@@ -165,9 +180,120 @@ const plugin = {
165
180
  name: "pd-help",
166
181
  description: getCommandDescription('pd-help', language),
167
182
  handler: (_ctx) => {
168
- return { text: language === 'zh'
169
- ? "我是你的麻辣导师。你可以输入 '执行 pd-mentor 技能' 来获取手把手的流程引导,或者直接问我关于 DNS、Trust Engine 或进化循环的深度问题。记住:痛是进化的燃料,而我是点火的人。"
170
- : "I'm your Spicy Mentor. You can execute the 'pd-mentor' skill for step-by-step guidance, or ask me directly about DNS, Trust Engine, or the evolution loop. Remember: Pain is the fuel of evolution, and I'm the one who lights the fire." };
183
+ if (language === 'zh') {
184
+ return { text: `
185
+ 📖 **Principles Disciple 命令大全**
186
+
187
+ ## 🚀 快速开始
188
+ | 命令 | 用途 | 使用时机 |
189
+ |------|------|----------|
190
+ | \`/pd-init\` | 初始化工作区 | 新项目开始时 |
191
+ | \`/pd-bootstrap\` | 环境工具扫描 | 缺少开发工具时 |
192
+
193
+ ## 📊 状态查询
194
+ | 命令 | 用途 | 使用时机 |
195
+ |------|------|----------|
196
+ | \`/pd-status\` | 查看进化状态 | 想了解当前 GFI 和 Pain 情况 |
197
+ | \`/pd-trust\` | 查看信任分数 | 想知道自己的权限等级 |
198
+ | \`/pd-focus\` | 焦点文件管理 | 查看/压缩/回滚历史版本 |
199
+
200
+ ## ⚙️ 配置管理
201
+ | 命令 | 用途 | 使用时机 |
202
+ |------|------|----------|
203
+ | \`/pd-context\` | 控制上下文注入 | 想减少/增加注入内容 |
204
+ | \`/pd-okr\` | OKR 目标管理 | 设置战略目标 |
205
+
206
+ ## 🧠 进化相关
207
+ | 命令 | 用途 | 使用时机 |
208
+ |------|------|----------|
209
+ | \`/pd-evolve\` | 执行进化循环 | 有 Pain 需要处理时 |
210
+ | \`/pd-thinking\` | 思维模型管理 | 更新 Thinking OS |
211
+ | \`/pd-daily\` | 进化日报 | 每日回顾时 |
212
+ | \`/pd-grooming\` | 工作区大扫除 | 定期清理 |
213
+
214
+ ## 💡 常用命令示例
215
+
216
+ **减少 token 消耗:**
217
+ \`\`\`
218
+ /pd-context minimal
219
+ \`\`\`
220
+
221
+ **恢复完整上下文:**
222
+ \`\`\`
223
+ /pd-context full
224
+ \`\`\`
225
+
226
+ **查看当前配置:**
227
+ \`\`\`
228
+ /pd-context status
229
+ \`\`\`
230
+
231
+ **查看信任分数:**
232
+ \`\`\`
233
+ /pd-trust
234
+ \`\`\`
235
+
236
+ ---
237
+ 🔍 输入任意命令后加 \`help\` 可查看详细帮助,如 \`/pd-context help\`
238
+ `.trim() };
239
+ }
240
+ else {
241
+ return { text: `
242
+ 📖 **Principles Disciple Command Reference**
243
+
244
+ ## 🚀 Quick Start
245
+ | Command | Purpose | When to Use |
246
+ |---------|---------|-------------|
247
+ | \`/pd-init\` | Initialize workspace | Starting a new project |
248
+ | \`/pd-bootstrap\` | Scan environment tools | Missing dev tools |
249
+
250
+ ## 📊 Status Query
251
+ | Command | Purpose | When to Use |
252
+ |---------|---------|-------------|
253
+ | \`/pd-status\` | View evolution status | Check GFI and Pain status |
254
+ | \`/pd-trust\` | View trust score | Check your permission level |
255
+ | \`/pd-focus\` | Focus file management | View/compress/rollback history |
256
+
257
+ ## ⚙️ Configuration
258
+ | Command | Purpose | When to Use |
259
+ |---------|---------|-------------|
260
+ | \`/pd-context\` | Control context injection | Reduce/increase injected content |
261
+ | \`/pd-okr\` | OKR goal management | Set strategic goals |
262
+
263
+ ## 🧠 Evolution
264
+ | Command | Purpose | When to Use |
265
+ |---------|---------|-------------|
266
+ | \`/pd-evolve\` | Run evolution loop | Process Pain signals |
267
+ | \`/pd-thinking\` | Mental model management | Update Thinking OS |
268
+ | \`/pd-daily\` | Evolution report | Daily review |
269
+ | \`/pd-grooming\` | Workspace cleanup | Periodic cleanup |
270
+
271
+ ## 💡 Common Examples
272
+
273
+ **Reduce token usage:**
274
+ \`\`\`
275
+ /pd-context minimal
276
+ \`\`\`
277
+
278
+ **Restore full context:**
279
+ \`\`\`
280
+ /pd-context full
281
+ \`\`\`
282
+
283
+ **View current config:**
284
+ \`\`\`
285
+ /pd-context status
286
+ \`\`\`
287
+
288
+ **Check trust score:**
289
+ \`\`\`
290
+ /pd-trust
291
+ \`\`\`
292
+
293
+ ---
294
+ 🔍 Add \`help\` after any command for details, e.g., \`/pd-context help\`
295
+ `.trim() };
296
+ }
171
297
  }
172
298
  });
173
299
  api.registerCommand({
@@ -196,6 +322,57 @@ const plugin = {
196
322
  }
197
323
  }
198
324
  });
325
+ api.registerCommand({
326
+ name: "pd-context",
327
+ description: getCommandDescription('pd-context', language),
328
+ acceptsArgs: true,
329
+ handler: (ctx) => {
330
+ try {
331
+ const workspaceDir = api.resolvePath('.');
332
+ if (ctx.config)
333
+ ctx.config.workspaceDir = workspaceDir;
334
+ return handleContextCommand(ctx);
335
+ }
336
+ catch (err) {
337
+ api.logger.error(`[PD] Command /pd-context failed: ${String(err)}`);
338
+ return { text: language === 'zh' ? "命令执行失败,请检查日志。" : "Command failed. Check logs." };
339
+ }
340
+ }
341
+ });
342
+ api.registerCommand({
343
+ name: "pd-focus",
344
+ description: getCommandDescription('pd-focus', language),
345
+ acceptsArgs: true,
346
+ handler: (ctx) => {
347
+ try {
348
+ const workspaceDir = api.resolvePath('.');
349
+ if (ctx.config)
350
+ ctx.config.workspaceDir = workspaceDir;
351
+ return handleFocusCommand(ctx, api);
352
+ }
353
+ catch (err) {
354
+ api.logger.error(`[PD] Command /pd-focus failed: ${String(err)}`);
355
+ return { text: language === 'zh' ? "命令执行失败,请检查日志。" : "Command failed. Check logs." };
356
+ }
357
+ }
358
+ });
359
+ api.registerCommand({
360
+ name: "pd-rollback",
361
+ description: getCommandDescription('pd-rollback', language),
362
+ acceptsArgs: true,
363
+ handler: (ctx) => {
364
+ try {
365
+ const workspaceDir = api.resolvePath('.');
366
+ if (ctx.config)
367
+ ctx.config.workspaceDir = workspaceDir;
368
+ return handleRollbackCommand(ctx);
369
+ }
370
+ catch (err) {
371
+ api.logger.error(`[PD] Command /pd-rollback failed: ${String(err)}`);
372
+ return { text: language === 'zh' ? "命令执行失败,请检查日志。" : "Command failed. Check logs." };
373
+ }
374
+ }
375
+ });
199
376
  // ── Tools ──
200
377
  api.registerTool(deepReflectTool);
201
378
  api.registerTool(agentSpawnTool);
@@ -0,0 +1,42 @@
1
+ import type { PluginLogger } from '../openclaw-sdk.js';
2
+ export interface EmpathyObserverApi {
3
+ config?: {
4
+ empathy_engine?: {
5
+ enabled?: boolean;
6
+ };
7
+ };
8
+ runtime: {
9
+ subagent: {
10
+ run: (params: {
11
+ sessionKey: string;
12
+ message: string;
13
+ lane?: string;
14
+ deliver?: boolean;
15
+ idempotencyKey?: string;
16
+ }) => Promise<unknown>;
17
+ getSessionMessages: (params: {
18
+ sessionKey: string;
19
+ limit?: number;
20
+ }) => Promise<{
21
+ messages: unknown[];
22
+ assistantTexts?: string[];
23
+ }>;
24
+ };
25
+ };
26
+ logger: PluginLogger;
27
+ }
28
+ export declare class EmpathyObserverManager {
29
+ private static instance;
30
+ private sessionLocks;
31
+ private constructor();
32
+ static getInstance(): EmpathyObserverManager;
33
+ shouldTrigger(api: EmpathyObserverApi | null | undefined, sessionId: string): boolean;
34
+ spawn(api: EmpathyObserverApi | null | undefined, sessionId: string, userMessage: string): Promise<string | null>;
35
+ reap(api: EmpathyObserverApi | null | undefined, targetSessionKey: string, workspaceDir: string): Promise<void>;
36
+ private isObserverSession;
37
+ private extractParentSessionId;
38
+ private parseJsonPayload;
39
+ private extractAssistantText;
40
+ private scoreFromSeverity;
41
+ }
42
+ export declare const empathyObserverManager: EmpathyObserverManager;
@@ -0,0 +1,147 @@
1
+ import { WorkspaceContext } from '../core/workspace-context.js';
2
+ import { trackFriction } from '../core/session-tracker.js';
3
+ const OBSERVER_SESSION_PREFIX = 'empathy_obs:';
4
+ export class EmpathyObserverManager {
5
+ static instance;
6
+ sessionLocks = new Map();
7
+ constructor() { }
8
+ static getInstance() {
9
+ if (!EmpathyObserverManager.instance) {
10
+ EmpathyObserverManager.instance = new EmpathyObserverManager();
11
+ }
12
+ return EmpathyObserverManager.instance;
13
+ }
14
+ shouldTrigger(api, sessionId) {
15
+ if (!api || !sessionId)
16
+ return false;
17
+ const enabled = api.config?.empathy_engine?.enabled !== false;
18
+ if (!enabled)
19
+ return false;
20
+ return !this.sessionLocks.has(sessionId);
21
+ }
22
+ async spawn(api, sessionId, userMessage) {
23
+ if (!api)
24
+ return null;
25
+ if (!this.shouldTrigger(api, sessionId))
26
+ return null;
27
+ if (!userMessage?.trim())
28
+ return null;
29
+ const timestamp = Date.now();
30
+ const sessionKey = `${OBSERVER_SESSION_PREFIX}${sessionId}:${timestamp}`;
31
+ this.sessionLocks.set(sessionId, sessionKey);
32
+ const prompt = [
33
+ 'You are an empathy observer.',
34
+ 'Analyze ONLY the user message and return strict JSON (no markdown):',
35
+ '{"damageDetected": boolean, "severity": "mild|moderate|severe", "confidence": number, "reason": string}',
36
+ `User message: ${JSON.stringify(userMessage.trim())}`,
37
+ ].join('\n');
38
+ try {
39
+ await api.runtime.subagent.run({
40
+ sessionKey,
41
+ message: prompt,
42
+ lane: 'subagent',
43
+ deliver: false,
44
+ idempotencyKey: `${sessionId}:${timestamp}`,
45
+ });
46
+ api.logger.info(`[PD:EmpathyObserver] Spawned observer ${sessionKey}`);
47
+ return sessionKey;
48
+ }
49
+ catch (error) {
50
+ this.sessionLocks.delete(sessionId);
51
+ api.logger.warn(`[PD:EmpathyObserver] Failed to spawn observer for ${sessionId}: ${String(error)}`);
52
+ return null;
53
+ }
54
+ }
55
+ async reap(api, targetSessionKey, workspaceDir) {
56
+ if (!api || !workspaceDir || !this.isObserverSession(targetSessionKey))
57
+ return;
58
+ const sessionId = this.extractParentSessionId(targetSessionKey);
59
+ const unlock = () => {
60
+ if (sessionId && this.sessionLocks.get(sessionId) === targetSessionKey) {
61
+ this.sessionLocks.delete(sessionId);
62
+ }
63
+ };
64
+ try {
65
+ const messages = await api.runtime.subagent.getSessionMessages({
66
+ sessionKey: targetSessionKey,
67
+ limit: 20,
68
+ });
69
+ const rawText = this.extractAssistantText(messages.messages, messages.assistantTexts);
70
+ const parsed = this.parseJsonPayload(rawText, api.logger);
71
+ if (parsed?.damageDetected && sessionId) {
72
+ const wctx = WorkspaceContext.fromHookContext({ workspaceDir });
73
+ const score = this.scoreFromSeverity(parsed.severity, wctx.config);
74
+ trackFriction(sessionId, score, `observer_empathy_${parsed.severity || 'mild'}`, workspaceDir);
75
+ api.logger.info(`[PD:EmpathyObserver] Applied GFI +${score} for ${sessionId}`);
76
+ }
77
+ }
78
+ catch (error) {
79
+ api.logger.warn(`[PD:EmpathyObserver] Failed to reap ${targetSessionKey}: ${String(error)}`);
80
+ }
81
+ finally {
82
+ unlock();
83
+ }
84
+ }
85
+ isObserverSession(sessionKey) {
86
+ return typeof sessionKey === 'string' && sessionKey.startsWith(OBSERVER_SESSION_PREFIX);
87
+ }
88
+ extractParentSessionId(sessionKey) {
89
+ if (!this.isObserverSession(sessionKey))
90
+ return null;
91
+ const rest = sessionKey.slice(OBSERVER_SESSION_PREFIX.length);
92
+ const marker = rest.lastIndexOf(':');
93
+ if (marker <= 0)
94
+ return null;
95
+ return rest.slice(0, marker);
96
+ }
97
+ parseJsonPayload(rawText, logger) {
98
+ if (!rawText?.trim())
99
+ return null;
100
+ try {
101
+ return JSON.parse(rawText.trim());
102
+ }
103
+ catch {
104
+ const match = rawText.match(/\{[\s\S]*\}/);
105
+ if (!match) {
106
+ logger?.warn('[PD:EmpathyObserver] Observer payload is not valid JSON, skipping.');
107
+ return null;
108
+ }
109
+ try {
110
+ return JSON.parse(match[0]);
111
+ }
112
+ catch {
113
+ logger?.warn('[PD:EmpathyObserver] Failed to parse observer JSON payload, skipping.');
114
+ return null;
115
+ }
116
+ }
117
+ }
118
+ extractAssistantText(messages, assistantTexts) {
119
+ if (assistantTexts && assistantTexts.length > 0) {
120
+ return assistantTexts[assistantTexts.length - 1] || '';
121
+ }
122
+ for (let i = messages.length - 1; i >= 0; i--) {
123
+ const msg = messages[i];
124
+ if (msg?.role !== 'assistant')
125
+ continue;
126
+ if (typeof msg.content === 'string')
127
+ return msg.content;
128
+ if (Array.isArray(msg.content)) {
129
+ const txt = msg.content
130
+ .filter((part) => part?.type === 'text' && typeof part.text === 'string')
131
+ .map((part) => part.text)
132
+ .join('\n');
133
+ if (txt)
134
+ return txt;
135
+ }
136
+ }
137
+ return '';
138
+ }
139
+ scoreFromSeverity(severity, config) {
140
+ if (severity === 'severe')
141
+ return Number(config.get('empathy_engine.penalties.severe') ?? 40);
142
+ if (severity === 'moderate')
143
+ return Number(config.get('empathy_engine.penalties.moderate') ?? 25);
144
+ return Number(config.get('empathy_engine.penalties.mild') ?? 10);
145
+ }
146
+ }
147
+ export const empathyObserverManager = EmpathyObserverManager.getInstance();
@@ -1,6 +1,7 @@
1
1
  import type { OpenClawPluginServiceContext, OpenClawPluginApi } from '../openclaw-sdk.js';
2
2
  export interface EvolutionQueueItem {
3
3
  id: string;
4
+ task?: string;
4
5
  score: number;
5
6
  source: string;
6
7
  reason: string;
@@ -89,13 +89,15 @@ function processEvolutionQueue(wctx, logger, eventLog) {
89
89
  if (pendingTasks.length > 0) {
90
90
  const directivePath = wctx.resolve('EVOLUTION_DIRECTIVE');
91
91
  const highestScoreTask = pendingTasks.sort((a, b) => b.score - a.score)[0];
92
+ const taskDescription = `Diagnose systemic pain [ID: ${highestScoreTask.id}]. Source: ${highestScoreTask.source}. Reason: ${highestScoreTask.reason}. ` +
93
+ `Trigger text: "${highestScoreTask.trigger_text_preview || 'N/A'}"`;
92
94
  const directive = {
93
95
  active: true,
94
- task: `Diagnose systemic pain [ID: ${highestScoreTask.id}]. Source: ${highestScoreTask.source}. Reason: ${highestScoreTask.reason}. ` +
95
- `Trigger text: "${highestScoreTask.trigger_text_preview || 'N/A'}"`,
96
+ task: taskDescription,
96
97
  timestamp: new Date().toISOString()
97
98
  };
98
99
  fs.writeFileSync(directivePath, JSON.stringify(directive, null, 2), 'utf8');
100
+ highestScoreTask.task = taskDescription;
99
101
  highestScoreTask.status = 'in_progress';
100
102
  queueChanged = true;
101
103
  if (eventLog) {
@@ -1,9 +1,81 @@
1
1
  import { Type } from '@sinclair/typebox';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import * as fs from 'fs';
4
+ import * as path from 'node:path';
4
5
  import { EventLogService } from '../core/event-log.js';
5
6
  import { buildCritiquePromptV2 } from './critique-prompt.js';
6
7
  import { resolvePdPath } from '../core/paths.js';
8
+ import { reflectionLogRetentionDays } from '../types.js';
9
+ /**
10
+ * Write reflection result to reflection-log.md
11
+ * Uses atomic write (temp file + rename) to prevent race conditions
12
+ */
13
+ function writeToReflectionLog(workspaceDir, context, insights, modelId, depth) {
14
+ const reflectionLogPath = resolvePdPath(workspaceDir, 'REFLECTION_LOG');
15
+ const memoryDir = path.dirname(reflectionLogPath);
16
+ // Ensure memory directory exists
17
+ if (!fs.existsSync(memoryDir)) {
18
+ fs.mkdirSync(memoryDir, { recursive: true });
19
+ }
20
+ const timestamp = new Date().toISOString();
21
+ const entry = `
22
+ ---
23
+ ## Reflection at ${timestamp}
24
+ **Model**: ${modelId || 'auto-select'}
25
+ **Depth**: ${depth || 2}
26
+
27
+ ### Context
28
+ ${context.substring(0, 500)}${context.length > 500 ? '...' : ''}
29
+
30
+ ### Insights
31
+ ${insights}
32
+
33
+ `;
34
+ const header = `# Reflection Log\n\n> Auto-generated by Deep Reflection Tool\n> Retention: ${reflectionLogRetentionDays} days\n`;
35
+ // Read existing content
36
+ let existingContent = '';
37
+ if (fs.existsSync(reflectionLogPath)) {
38
+ existingContent = fs.readFileSync(reflectionLogPath, 'utf8');
39
+ }
40
+ const newContent = header + entry + existingContent.replace(header, '');
41
+ // Atomic write: write to temp file first, then rename
42
+ const tempPath = `${reflectionLogPath}.tmp`;
43
+ fs.writeFileSync(tempPath, newContent, 'utf8');
44
+ fs.renameSync(tempPath, reflectionLogPath);
45
+ }
46
+ /**
47
+ * Clean up reflection log entries older than retention period
48
+ */
49
+ function cleanupReflectionLog(workspaceDir) {
50
+ const reflectionLogPath = resolvePdPath(workspaceDir, 'REFLECTION_LOG');
51
+ if (!fs.existsSync(reflectionLogPath))
52
+ return;
53
+ const content = fs.readFileSync(reflectionLogPath, 'utf8');
54
+ const cutoffDate = new Date();
55
+ cutoffDate.setDate(cutoffDate.getDate() - reflectionLogRetentionDays);
56
+ // Use more precise regex to match ISO timestamp
57
+ // Pattern: ---\n## Reflection at 2024-01-15T10:30:00
58
+ const entryPattern = /---\n## Reflection at (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})/g;
59
+ const parts = content.split(entryPattern);
60
+ // parts[0] = header, then alternates: timestamp, content, timestamp, content...
61
+ const header = parts[0];
62
+ const validEntries = [];
63
+ for (let i = 1; i < parts.length; i += 2) {
64
+ const timestamp = parts[i];
65
+ const entryContent = parts[i + 1] || '';
66
+ const entryDate = new Date(timestamp);
67
+ if (entryDate >= cutoffDate) {
68
+ validEntries.push(`---\n## Reflection at ${timestamp}${entryContent}`);
69
+ }
70
+ }
71
+ if (validEntries.length < (parts.length - 1) / 2) {
72
+ const newContent = header + validEntries.join('');
73
+ // Atomic write
74
+ const tempPath = `${reflectionLogPath}.tmp`;
75
+ fs.writeFileSync(tempPath, newContent, 'utf8');
76
+ fs.renameSync(tempPath, reflectionLogPath);
77
+ }
78
+ }
7
79
  const DEFAULT_CONFIG = {
8
80
  enabled: true,
9
81
  mode: 'auto',
@@ -130,6 +202,14 @@ export const deepReflectTool = {
130
202
  timeout: false
131
203
  });
132
204
  }
205
+ // Write to reflection log and cleanup old entries
206
+ try {
207
+ writeToReflectionLog(effectiveWorkspaceDir, context, insights, model_id, depth);
208
+ cleanupReflectionLog(effectiveWorkspaceDir);
209
+ }
210
+ catch (logErr) {
211
+ safeLog(api, 'warn', `[DeepReflect] Failed to write reflection log: ${String(logErr)}`);
212
+ }
133
213
  return `
134
214
  # 💎 Deep Reflection Insights
135
215
  ---