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.
- package/dist/commands/context.d.ts +5 -0
- package/dist/commands/context.js +308 -0
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +579 -0
- package/dist/commands/pain.js +135 -6
- package/dist/commands/rollback.d.ts +19 -0
- package/dist/commands/rollback.js +119 -0
- package/dist/core/config.d.ts +32 -0
- package/dist/core/config.js +47 -0
- package/dist/core/event-log.d.ts +21 -1
- package/dist/core/event-log.js +316 -0
- package/dist/core/focus-history.d.ts +65 -0
- package/dist/core/focus-history.js +266 -0
- package/dist/core/init.js +30 -7
- package/dist/core/migration.js +0 -2
- package/dist/core/path-resolver.d.ts +3 -0
- package/dist/core/path-resolver.js +20 -0
- package/dist/hooks/gate.js +203 -1
- package/dist/hooks/llm.d.ts +8 -0
- package/dist/hooks/llm.js +234 -1
- package/dist/hooks/message-sanitize.d.ts +3 -0
- package/dist/hooks/message-sanitize.js +37 -0
- package/dist/hooks/prompt.d.ts +12 -0
- package/dist/hooks/prompt.js +309 -135
- package/dist/hooks/subagent.d.ts +9 -2
- package/dist/hooks/subagent.js +13 -2
- package/dist/i18n/commands.js +32 -20
- package/dist/index.js +181 -4
- package/dist/service/empathy-observer-manager.d.ts +42 -0
- package/dist/service/empathy-observer-manager.js +147 -0
- package/dist/service/evolution-worker.d.ts +1 -0
- package/dist/service/evolution-worker.js +4 -2
- package/dist/tools/deep-reflect.js +80 -0
- package/dist/types/event-types.d.ts +77 -2
- package/dist/types/event-types.js +33 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +19 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/templates/langs/zh/core/HEARTBEAT.md +28 -4
- package/templates/pain_settings.json +54 -2
- package/templates/workspace/.principles/PROFILE.json +2 -0
- package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
package/dist/i18n/commands.js
CHANGED
|
@@ -15,48 +15,60 @@ export function normalizeLanguage(lang) {
|
|
|
15
15
|
}
|
|
16
16
|
export const commandDescriptions = {
|
|
17
17
|
'pd-init': {
|
|
18
|
-
zh: '
|
|
19
|
-
en: 'Initialize
|
|
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
|
|
22
|
+
zh: '管理 OKR 目标与关键结果',
|
|
23
|
+
en: 'Manage OKR goals and key results'
|
|
24
24
|
},
|
|
25
25
|
'pd-bootstrap': {
|
|
26
|
-
zh: '
|
|
27
|
-
en: 'Scan and
|
|
26
|
+
zh: '扫描环境工具并建议升级',
|
|
27
|
+
en: 'Scan environment tools and suggest upgrades'
|
|
28
28
|
},
|
|
29
29
|
'pd-research': {
|
|
30
|
-
zh: '
|
|
31
|
-
en: 'Research tool
|
|
30
|
+
zh: '研究工具升级方案',
|
|
31
|
+
en: 'Research tool upgrade solutions'
|
|
32
32
|
},
|
|
33
33
|
'pd-thinking': {
|
|
34
|
-
zh: '
|
|
35
|
-
en: 'Manage Thinking OS
|
|
34
|
+
zh: '管理思维模型 [status|propose|audit]',
|
|
35
|
+
en: 'Manage Thinking OS [status|propose|audit]'
|
|
36
36
|
},
|
|
37
37
|
'pd-evolve': {
|
|
38
|
-
zh: '
|
|
39
|
-
en: 'Run
|
|
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
|
|
50
|
+
zh: '查看信任分数和权限等级 (1-4)',
|
|
51
|
+
en: 'View trust score and permission stage (1-4)'
|
|
52
52
|
},
|
|
53
53
|
'pd-help': {
|
|
54
|
-
zh: '
|
|
55
|
-
en: '
|
|
54
|
+
zh: '显示所有命令和使用指南',
|
|
55
|
+
en: 'Show all commands and usage guide'
|
|
56
56
|
},
|
|
57
57
|
'pd-status': {
|
|
58
|
-
zh: '
|
|
59
|
-
en: 'View
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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();
|
|
@@ -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:
|
|
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
|
---
|