principles-disciple 1.7.3 → 1.7.5

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 (67) hide show
  1. package/dist/commands/evolution-status.js +4 -2
  2. package/dist/commands/focus.js +30 -155
  3. package/dist/constants/diagnostician.d.ts +16 -0
  4. package/dist/constants/diagnostician.js +60 -0
  5. package/dist/constants/tools.d.ts +2 -2
  6. package/dist/constants/tools.js +1 -1
  7. package/dist/core/config.d.ts +23 -0
  8. package/dist/core/config.js +26 -1
  9. package/dist/core/evolution-engine.js +1 -1
  10. package/dist/core/evolution-logger.d.ts +137 -0
  11. package/dist/core/evolution-logger.js +256 -0
  12. package/dist/core/evolution-reducer.d.ts +23 -0
  13. package/dist/core/evolution-reducer.js +73 -29
  14. package/dist/core/evolution-types.d.ts +6 -0
  15. package/dist/core/focus-history.d.ts +145 -0
  16. package/dist/core/focus-history.js +919 -0
  17. package/dist/core/init.js +24 -0
  18. package/dist/core/profile.js +1 -1
  19. package/dist/core/risk-calculator.d.ts +15 -0
  20. package/dist/core/risk-calculator.js +48 -0
  21. package/dist/core/trajectory.d.ts +73 -0
  22. package/dist/core/trajectory.js +206 -0
  23. package/dist/hooks/gate.js +130 -20
  24. package/dist/hooks/lifecycle.js +104 -0
  25. package/dist/hooks/pain.js +31 -0
  26. package/dist/hooks/prompt.js +136 -38
  27. package/dist/hooks/subagent.d.ts +1 -0
  28. package/dist/hooks/subagent.js +200 -18
  29. package/dist/http/principles-console-route.d.ts +7 -0
  30. package/dist/http/principles-console-route.js +301 -1
  31. package/dist/index.js +0 -2
  32. package/dist/service/central-database.d.ts +104 -0
  33. package/dist/service/central-database.js +648 -0
  34. package/dist/service/control-ui-query-service.d.ts +2 -0
  35. package/dist/service/control-ui-query-service.js +4 -0
  36. package/dist/service/empathy-observer-manager.d.ts +8 -0
  37. package/dist/service/empathy-observer-manager.js +40 -0
  38. package/dist/service/evolution-query-service.d.ts +155 -0
  39. package/dist/service/evolution-query-service.js +258 -0
  40. package/dist/service/evolution-worker.d.ts +4 -0
  41. package/dist/service/evolution-worker.js +185 -63
  42. package/dist/service/phase3-input-filter.d.ts +37 -0
  43. package/dist/service/phase3-input-filter.js +106 -0
  44. package/dist/service/runtime-summary-service.d.ts +15 -0
  45. package/dist/service/runtime-summary-service.js +111 -23
  46. package/dist/tools/deep-reflect.js +8 -2
  47. package/dist/utils/subagent-probe.d.ts +34 -0
  48. package/dist/utils/subagent-probe.js +81 -0
  49. package/openclaw.plugin.json +1 -1
  50. package/package.json +6 -4
  51. package/templates/langs/en/core/AGENTS.md +15 -3
  52. package/templates/langs/en/core/BOOTSTRAP.md +24 -1
  53. package/templates/langs/en/core/TOOLS.md +9 -0
  54. package/templates/langs/zh/core/AGENTS.md +15 -3
  55. package/templates/langs/zh/core/BOOTSTRAP.md +24 -1
  56. package/templates/langs/zh/core/TOOLS.md +9 -0
  57. package/templates/langs/zh/skills/pd-auditor/SKILL.md +61 -0
  58. package/templates/langs/zh/skills/pd-diagnostician/SKILL.md +287 -0
  59. package/templates/langs/zh/skills/pd-explorer/SKILL.md +65 -0
  60. package/templates/langs/zh/skills/pd-implementer/SKILL.md +68 -0
  61. package/templates/langs/zh/skills/pd-planner/SKILL.md +65 -0
  62. package/templates/langs/zh/skills/pd-reporter/SKILL.md +78 -0
  63. package/templates/langs/zh/skills/pd-reviewer/SKILL.md +66 -0
  64. package/dist/core/agent-loader.d.ts +0 -44
  65. package/dist/core/agent-loader.js +0 -147
  66. package/dist/tools/agent-spawn.d.ts +0 -54
  67. package/dist/tools/agent-spawn.js +0 -445
@@ -4,6 +4,7 @@ import * as readline from 'readline';
4
4
  import { writePainFlag } from '../core/pain.js';
5
5
  import { WorkspaceContext } from '../core/workspace-context.js';
6
6
  import { PD_DIRS } from '../core/paths.js';
7
+ import { extractWorkingMemory, mergeWorkingMemory } from '../core/focus-history.js';
7
8
  export async function handleBeforeReset(event, ctx) {
8
9
  if (!ctx.workspaceDir || !event.messages || event.messages.length === 0) {
9
10
  return;
@@ -147,6 +148,7 @@ export async function extractPainFromSessionFile(sessionFile, ctx) {
147
148
  export async function handleBeforeCompaction(event, ctx) {
148
149
  if (!ctx.workspaceDir)
149
150
  return;
151
+ const wctx = WorkspaceContext.fromHookContext(ctx);
150
152
  const dateStr = new Date().toISOString().split('T')[0];
151
153
  const checkpointPath = path.join(ctx.workspaceDir, PD_DIRS.MEMORY, `${dateStr}.md`);
152
154
  const log = `\n## [${new Date().toISOString()}] Pre-Compaction Checkpoint\n` +
@@ -161,8 +163,110 @@ export async function handleBeforeCompaction(event, ctx) {
161
163
  catch (_e) {
162
164
  console.error(`[PD:Lifecycle] Failed to write pre-compaction checkpoint: ${String(_e)}`);
163
165
  }
166
+ // 提取工作记忆(从 sessionFile)
164
167
  if (event.sessionFile) {
165
168
  await extractPainFromSessionFile(event.sessionFile, ctx);
169
+ // 新增:提取并保存工作记忆
170
+ await extractAndSaveWorkingMemory(event.sessionFile, ctx, wctx);
171
+ }
172
+ }
173
+ /**
174
+ * 从会话文件提取工作记忆并保存到 CURRENT_FOCUS.md
175
+ */
176
+ async function extractAndSaveWorkingMemory(sessionFile, ctx, wctx) {
177
+ if (!fs.existsSync(sessionFile)) {
178
+ if (ctx.logger?.debug)
179
+ ctx.logger.debug(`[WorkingMemory] Session file not found: ${sessionFile}`);
180
+ return;
181
+ }
182
+ const messages = [];
183
+ let lineCount = 0;
184
+ const MAX_LINES = 1000; // 限制处理的行数,避免内存问题
185
+ // 读取会话文件
186
+ const fileStream = fs.createReadStream(sessionFile);
187
+ const rl = readline.createInterface({
188
+ input: fileStream,
189
+ crlfDelay: Infinity
190
+ });
191
+ try {
192
+ for await (const line of rl) {
193
+ if (lineCount >= MAX_LINES)
194
+ break;
195
+ if (!line.trim())
196
+ continue;
197
+ lineCount++;
198
+ try {
199
+ const msg = JSON.parse(line);
200
+ messages.push(msg);
201
+ }
202
+ catch {
203
+ // 忽略解析错误
204
+ }
205
+ }
206
+ }
207
+ finally {
208
+ try {
209
+ rl.close();
210
+ fileStream.destroy();
211
+ }
212
+ catch {
213
+ // 忽略清理错误
214
+ }
215
+ }
216
+ if (messages.length === 0) {
217
+ if (ctx.logger?.debug)
218
+ ctx.logger.debug(`[WorkingMemory] No messages found in session file`);
219
+ return;
220
+ }
221
+ // 提取工作记忆
222
+ const snapshot = extractWorkingMemory(messages, ctx.workspaceDir);
223
+ // 检查是否有有效内容
224
+ if (snapshot.artifacts.length === 0 &&
225
+ snapshot.activeProblems.length === 0 &&
226
+ snapshot.nextActions.length === 0) {
227
+ if (ctx.logger?.debug)
228
+ ctx.logger.debug(`[WorkingMemory] No working memory to preserve`);
229
+ return;
230
+ }
231
+ // 读取并更新 CURRENT_FOCUS.md
232
+ const focusPath = wctx.resolve('CURRENT_FOCUS');
233
+ try {
234
+ let content = '';
235
+ if (fs.existsSync(focusPath)) {
236
+ content = fs.readFileSync(focusPath, 'utf-8');
237
+ // 备份原文件(防止损坏)
238
+ const backupPath = `${focusPath}.wm-backup`;
239
+ fs.copyFileSync(focusPath, backupPath);
240
+ }
241
+ // 合并工作记忆
242
+ const updatedContent = mergeWorkingMemory(content, snapshot);
243
+ // 确保目录存在
244
+ const focusDir = path.dirname(focusPath);
245
+ if (!fs.existsSync(focusDir)) {
246
+ fs.mkdirSync(focusDir, { recursive: true });
247
+ }
248
+ // 写入文件
249
+ fs.writeFileSync(focusPath, updatedContent, 'utf-8');
250
+ if (ctx.logger) {
251
+ ctx.logger.info(`[WorkingMemory] Preserved ${snapshot.artifacts.length} artifacts, ` +
252
+ `${snapshot.activeProblems.length} problems, ` +
253
+ `${snapshot.nextActions.length} next actions to CURRENT_FOCUS.md`);
254
+ }
255
+ }
256
+ catch (err) {
257
+ console.error(`[PD:Lifecycle] Failed to save working memory: ${String(err)}`);
258
+ // 尝试恢复备份
259
+ const backupPath = `${focusPath}.wm-backup`;
260
+ if (fs.existsSync(backupPath)) {
261
+ try {
262
+ fs.copyFileSync(backupPath, focusPath);
263
+ if (ctx.logger)
264
+ ctx.logger.warn(`[WorkingMemory] Restored from backup after failure`);
265
+ }
266
+ catch {
267
+ // 忽略恢复错误
268
+ }
269
+ }
166
270
  }
167
271
  }
168
272
  export async function handleAfterCompaction(event, ctx) {
@@ -6,6 +6,7 @@ import { getSession, trackFriction, resetFriction, getInjectedProbationIds, clea
6
6
  import { denoiseError, computeHash } from '../utils/hashing.js';
7
7
  import { SystemLogger } from '../core/system-logger.js';
8
8
  import { WorkspaceContext } from '../core/workspace-context.js';
9
+ import { getEvolutionLogger, createTraceId } from '../core/evolution-logger.js';
9
10
  const WRITE_TOOLS = ['write', 'edit', 'apply_patch', 'write_file', 'edit_file', 'replace'];
10
11
  function shouldAttributePrincipleToTool(principle, toolName) {
11
12
  return principle.contextTags.includes(toolName) || principle.trigger.includes(toolName);
@@ -38,6 +39,7 @@ export function handleAfterToolCall(event, ctx, api) {
38
39
  // 0. Special Case: Manual Pain Intervention
39
40
  if (event.toolName === 'pain' || event.toolName === 'skill:pain') {
40
41
  const reason = params.input || params.arguments || 'Manual intervention';
42
+ const traceId = createTraceId();
41
43
  trackFriction(sessionId, 100, 'manual_pain', effectiveWorkspaceDir);
42
44
  SystemLogger.log(effectiveWorkspaceDir, 'MANUAL_PAIN', `User manually triggered pain: ${reason}`);
43
45
  eventLog.recordPainSignal(sessionId, {
@@ -53,6 +55,16 @@ export function handleAfterToolCall(event, ctx, api) {
53
55
  reason: `User intervention: ${reason}`,
54
56
  origin: 'user_manual',
55
57
  });
58
+ // Log to EvolutionLogger
59
+ const evoLogger = getEvolutionLogger(effectiveWorkspaceDir, wctx.trajectory);
60
+ evoLogger.logPainDetected({
61
+ traceId,
62
+ source: 'manual',
63
+ reason: `User intervention: ${reason}`,
64
+ score: 100,
65
+ toolName: event.toolName,
66
+ sessionId,
67
+ });
56
68
  emitPainDetectedEvent(wctx, {
57
69
  ts: new Date().toISOString(),
58
70
  type: 'pain_detected',
@@ -63,6 +75,8 @@ export function handleAfterToolCall(event, ctx, api) {
63
75
  reason: `User intervention: ${reason}`,
64
76
  score: 100,
65
77
  sessionId,
78
+ traceId,
79
+ agentId: ctx.agentId,
66
80
  },
67
81
  });
68
82
  return;
@@ -212,12 +226,16 @@ export function handleAfterToolCall(event, ctx, api) {
212
226
  }
213
227
  const isRisk = isRisky(relPath, profile.risk_paths);
214
228
  const painScore = computePainScore(1, false, false, isRisk ? 20 : 0, effectiveWorkspaceDir);
229
+ const traceId = createTraceId();
215
230
  const painData = {
216
231
  score: String(painScore),
217
232
  source: 'tool_failure',
218
233
  time: new Date().toISOString(),
219
234
  reason: `Tool ${event.toolName} failed on ${relPath}. Error: ${event.error ?? 'Non-zero exit code'}`,
220
235
  is_risky: String(isRisk),
236
+ trace_id: traceId,
237
+ session_id: sessionId,
238
+ agent_id: ctx.agentId || '',
221
239
  };
222
240
  writePainFlag(effectiveWorkspaceDir, painData);
223
241
  eventLog.recordPainSignal(sessionId, {
@@ -234,6 +252,17 @@ export function handleAfterToolCall(event, ctx, api) {
234
252
  severity: painScore >= 70 ? 'severe' : painScore >= 40 ? 'moderate' : 'mild',
235
253
  origin: 'system_infer',
236
254
  });
255
+ // Log to EvolutionLogger
256
+ const evoLogger = getEvolutionLogger(effectiveWorkspaceDir, wctx.trajectory);
257
+ evoLogger.logPainDetected({
258
+ traceId,
259
+ source: 'tool_failure',
260
+ reason: `Tool ${event.toolName} failed on ${relPath}`,
261
+ score: painScore,
262
+ toolName: event.toolName,
263
+ filePath: relPath,
264
+ sessionId,
265
+ });
237
266
  emitPainDetectedEvent(wctx, {
238
267
  ts: new Date().toISOString(),
239
268
  type: 'pain_detected',
@@ -244,6 +273,8 @@ export function handleAfterToolCall(event, ctx, api) {
244
273
  reason: `Tool ${event.toolName} failed on ${relPath}`,
245
274
  score: painScore,
246
275
  sessionId,
276
+ traceId,
277
+ agentId: ctx.agentId,
247
278
  },
248
279
  });
249
280
  }
@@ -3,7 +3,7 @@ import * as path from 'path';
3
3
  import { clearInjectedProbationIds, getSession, resetFriction, setInjectedProbationIds } from '../core/session-tracker.js';
4
4
  import { WorkspaceContext } from '../core/workspace-context.js';
5
5
  import { defaultContextConfig } from '../types.js';
6
- import { extractSummary, getHistoryVersions } from '../core/focus-history.js';
6
+ import { extractSummary, getHistoryVersions, parseWorkingMemorySection, workingMemoryToInjection, autoCompressFocus, safeReadCurrentFocus } from '../core/focus-history.js';
7
7
  import { empathyObserverManager } from '../service/empathy-observer-manager.js';
8
8
  import { PathResolver } from '../core/path-resolver.js';
9
9
  /**
@@ -123,13 +123,15 @@ function resolveEvolutionTask(inProgressTask, messages, maxContextMessages = 4,
123
123
  const rawTask = typeof inProgressTask.task === 'string' ? inProgressTask.task.trim() : '';
124
124
  if (rawTask && rawTask.toLowerCase() !== 'undefined')
125
125
  return rawTask;
126
+ if (typeof inProgressTask.id !== 'string' || !inProgressTask.id.trim())
127
+ return null;
126
128
  const source = typeof inProgressTask.source === 'string' ? inProgressTask.source.trim() : 'unknown';
127
129
  const reason = typeof inProgressTask.reason === 'string' ? inProgressTask.reason.trim() : 'Systemic pain detected';
128
130
  const preview = typeof inProgressTask.trigger_text_preview === 'string' && inProgressTask.trigger_text_preview.trim()
129
131
  ? inProgressTask.trigger_text_preview.trim()
130
132
  : 'N/A';
131
- if (typeof inProgressTask.id !== 'string' || !inProgressTask.id.trim())
132
- return null;
133
+ const sessionId = typeof inProgressTask.session_id === 'string' ? inProgressTask.session_id.trim() : '';
134
+ const agentId = typeof inProgressTask.agent_id === 'string' ? inProgressTask.agent_id.trim() : '';
133
135
  const conversationContext = includeConversationContext
134
136
  ? extractRecentConversationContext(messages, maxContextMessages, maxCharsPerMsg)
135
137
  : '';
@@ -142,16 +144,60 @@ function resolveEvolutionTask(inProgressTask, messages, maxContextMessages = 4,
142
144
  `;
143
145
  taskDescription += `**Trigger Text**: "${preview}"
144
146
  `;
147
+ if (sessionId) {
148
+ taskDescription += `**Session ID**: ${sessionId}
149
+ `;
150
+ }
151
+ if (agentId) {
152
+ taskDescription += `**Agent ID**: ${agentId}
153
+ `;
154
+ }
145
155
  if (conversationContext) {
146
156
  taskDescription += `
147
157
  ---
148
158
  **Recent Conversation Context**:
149
159
  ${conversationContext}`;
160
+ }
161
+ else if (!sessionId) {
162
+ taskDescription += `
163
+ ---
164
+ **Note**: 对话上下文不可用。请主动收集证据:
165
+ 1. 从 Reason 字段提取关键词,搜索相关代码
166
+ 2. 读取 .state/logs/events.jsonl 最近日志
167
+ 3. 基于 Reason 中的文件路径定位问题`;
150
168
  }
151
169
  taskDescription += `
152
170
 
153
171
  ---
154
- Analyze the root cause using 5 Whys methodology. Check evidence in codebase before concluding.`;
172
+ ## 执行指令
173
+
174
+ 使用 5 Whys 方法进行根因分析,输出 JSON 格式结果。
175
+
176
+ ### 必执行步骤:
177
+ 1. **Phase 1 - 证据收集**: 读取日志、搜索代码,记录证据来源
178
+ 2. **Phase 2 - 因果链构建**: 每个 Why 必须有证据支撑,最多 5 层
179
+ 3. **Phase 3 - 根因分类**: 归类为 People/Design/Assumption/Tooling
180
+ 4. **Phase 4 - 原则提炼**: 提炼可复用的防护原则
181
+
182
+ ### 终止条件(满足任一即停止):
183
+ - 找到可修改代码直接解决的问题
184
+ - 找到缺失的门禁规则或检查机制
185
+ - 连续 2 个 Why 无法提出更深假设
186
+
187
+ ### 输出格式:
188
+ \`\`\`json
189
+ {
190
+ "diagnosis_report": {
191
+ "task_id": "...",
192
+ "summary": "一句话总结根因",
193
+ "causal_chain": [...],
194
+ "root_cause": { "category": "Design", "description": "..." },
195
+ "principle": { "trigger_pattern": "...", "action": "..." }
196
+ }
197
+ }
198
+ \`\`\`
199
+
200
+ 详细执行协议请参考你的系统提示词。`;
155
201
  return taskDescription;
156
202
  }
157
203
  /**
@@ -199,7 +245,6 @@ export function resolveModelFromConfig(modelConfig, logger) {
199
245
  }
200
246
  // 闁哄秶鍘х槐?3: 闁轰焦澹嗙划宥夊冀閻撳海纭€闁挎稑鐗呯粭澶愬绩椤栨稑鐦柨娑樿嫰瑜板倿宕欐ウ娆惧妳闁告稑顭槐?
201
247
  if (Array.isArray(modelConfig)) {
202
- console.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
203
248
  logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
204
249
  return null;
205
250
  }
@@ -359,8 +404,8 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
359
404
  **Tool Routing Rules**:
360
405
  - Use the current session for the normal user reply.
361
406
  - Use sessions_send for cross-session messaging.
362
- - Use agents_list / sessions_list / sessions_spawn for peer-agent or session orchestration.
363
- - Use pd_run_worker only for Principles Disciple internal workers such as diagnostician/explorer.
407
+ - Use agents_list / sessions_list / sessions_spawn for peer-agent or peer-session orchestration.
408
+ - Use sessions_spawn with pd-diagnostician/pd-explorer/etc skills for internal worker tasks.
364
409
 
365
410
  ## 妫e啯鎯?INTERNAL SYSTEM LAYOUT
366
411
  - Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
@@ -378,7 +423,7 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
378
423
  trustContext += `Hygiene: ${hygiene.persistenceCount} persists today\n`;
379
424
  // Stage-based restrictions
380
425
  if (safeStage === 1) {
381
- trustContext += `ACTION CONSTRAINT: You are in READ-ONLY MODE. You MUST use the internal pd_run_worker diagnostician worker to recover trust before writing files. Do not use it for peer-session messaging.\n`;
426
+ trustContext += `ACTION CONSTRAINT: You are in READ-ONLY MODE. You MUST use sessions_spawn with the pd-diagnostician skill to recover trust before writing files.\n`;
382
427
  }
383
428
  else if (safeStage === 2) {
384
429
  trustContext += `ACTION CONSTRAINT: LIMITED MODE. You are restricted to a maximum of 50 lines per edit.\n`;
@@ -397,47 +442,71 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
397
442
  if (fs.existsSync(queuePath)) {
398
443
  try {
399
444
  const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
400
- const inProgressTask = queue.find((t) => t.status === 'in_progress');
401
- if (inProgressTask) {
445
+ const inProgressTasks = [...queue]
446
+ .filter((t) => t.status === 'in_progress')
447
+ .sort((a, b) => {
448
+ const scoreA = Number.isFinite(a?.score) ? Number(a.score) : 0;
449
+ const scoreB = Number.isFinite(b?.score) ? Number(b.score) : 0;
450
+ return scoreB - scoreA;
451
+ });
452
+ for (const inProgressTask of inProgressTasks) {
402
453
  const resolvedTask = resolveEvolutionTask(inProgressTask, event.messages, contextConfig.evolutionContext.maxMessages, contextConfig.evolutionContext.maxCharsPerMessage, contextConfig.evolutionContext.enabled);
403
454
  if (!resolvedTask) {
404
- logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
455
+ continue;
405
456
  }
406
- else {
407
- const escapedTask = JSON.stringify(resolvedTask);
408
- logger?.info(`[PD:Prompt] Injecting EVOLUTION TASK for: ${inProgressTask.id}`);
409
- if (trigger === 'user') {
410
- evolutionDirective = `<evolution_task priority="high">
457
+ const escapedTask = JSON.stringify(resolvedTask);
458
+ logger?.info(`[PD:Prompt] Injecting EVOLUTION TASK for: ${inProgressTask.id}`);
459
+ if (trigger === 'user') {
460
+ evolutionDirective = `<evolution_task priority="high">
411
461
  TASK: ${escapedTask}
412
462
 
413
- REQUIRED ACTION:
414
- - First respond to the user's current request normally.
415
- - After finishing the user-facing response, call:
416
- pd_run_worker agentType="diagnostician" task=${escapedTask} runInBackground=true
417
- - Treat pd_run_worker as an internal Principles Disciple worker, not a peer-session messaging tool.
463
+ REQUIRED ACTION (两阶段回复):
464
+
465
+ **阶段 1 - 立即回复用户:**
466
+ 简要告知用户你检测到的问题,例如:
467
+ "我注意到刚才的操作遇到了一些问题(简述问题),我已安排诊断代理在后台分析根因。分析完成后会继续告诉你结果。"
468
+
469
+ 然后调用:
470
+ sessions_spawn(task="使用 pd-diagnostician skill 执行诊断任务。\n\n任务: ${escapedTask}")
471
+
472
+ **阶段 2 - 收到子代理结果后:**
473
+ 当收到诊断代理的完成消息后,用以下格式回复用户:
474
+
475
+ "**诊断完成** 🔍
476
+
477
+ 基于后台分析结果:
478
+ - **根因**: [一句话总结]
479
+ - **分类**: [People/Design/Assumption/Tooling]
480
+ - **建议**: [可执行的改进建议]
481
+
482
+ 相关原则已记录到 PRINCIPLES.md。"
483
+
484
+ IMPORTANT:
418
485
  - Do NOT reply with "[EVOLUTION_ACK]".
419
- - Do NOT let this task interrupt the current user interaction.
486
+ - 子代理结果会作为新消息到达,届时再进行阶段2的回复。
420
487
  </evolution_task>\n`;
421
- }
422
- else {
423
- evolutionDirective = `<evolution_task priority="critical">
488
+ }
489
+ else {
490
+ evolutionDirective = `<evolution_task priority="critical">
424
491
  TASK: ${escapedTask}
425
492
 
426
493
  REQUIRED ACTION:
427
494
  - Start diagnostics immediately by calling:
428
- pd_run_worker agentType="diagnostician" task=${escapedTask} runInBackground=true
429
- - Treat pd_run_worker as an internal Principles Disciple worker, not a peer-session messaging tool.
495
+ sessions_spawn(task="使用 pd-diagnostician skill 执行诊断任务。\n\n任务: ${escapedTask}")
430
496
  - Do NOT reply with "[EVOLUTION_ACK]".
431
497
  </evolution_task>\n`;
432
- }
433
498
  }
499
+ break;
500
+ }
501
+ if (!evolutionDirective && inProgressTasks.length > 0) {
502
+ logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
434
503
  }
435
504
  }
436
505
  catch (e) {
437
506
  logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
438
507
  }
439
508
  }
440
- // Inject evolution directive at the front of prependContext
509
+ // Inject queue-derived evolution task at the front of prependContext
441
510
  if (evolutionDirective) {
442
511
  prependContext = evolutionDirective + prependContext;
443
512
  }
@@ -541,31 +610,56 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
541
610
  }
542
611
  // Project Context (configurable: full/summary/off) - moved to appendSystemContext for WebUI UX
543
612
  let projectContextContent = '';
613
+ let workingMemoryContent = '';
544
614
  if (!isMinimalMode && contextConfig.projectFocus !== 'off') {
545
615
  const focusPath = wctx.resolve('CURRENT_FOCUS');
546
- if (fs.existsSync(focusPath)) {
616
+ const extensionRoot = PathResolver.getExtensionRoot();
617
+ // 🔒 安全读取:自动验证格式,损坏时从模板恢复
618
+ const { content: currentFocus, recovered, validationErrors } = safeReadCurrentFocus(focusPath, extensionRoot || '', logger);
619
+ if (recovered) {
620
+ logger?.info?.(`[PD:Prompt] CURRENT_FOCUS.md was recovered from template`);
621
+ }
622
+ if (validationErrors.length > 0) {
623
+ logger?.warn?.(`[PD:Prompt] CURRENT_FOCUS validation errors: ${validationErrors.join(', ')}`);
624
+ }
625
+ if (currentFocus.trim()) {
547
626
  try {
548
- const currentFocus = fs.readFileSync(focusPath, 'utf8').trim();
549
- if (currentFocus) {
627
+ // 🚀 自动压缩门禁:检查文件大小,超过阈值自动压缩
628
+ const stateDir = wctx.stateDir;
629
+ const compressResult = autoCompressFocus(focusPath, workspaceDir, stateDir);
630
+ if (compressResult.compressed) {
631
+ logger?.info?.(`[PD:Prompt] Auto-compressed CURRENT_FOCUS: ${compressResult.oldLines} → ${compressResult.newLines} lines. Milestones archived: ${compressResult.milestonesArchived}`);
632
+ }
633
+ else if (compressResult.reason === 'Rate limited (24h interval)') {
634
+ logger?.debug?.(`[PD:Prompt] Auto-compress skipped: ${compressResult.reason}`);
635
+ }
636
+ // 重新读取(可能被压缩更新了)
637
+ const finalContent = fs.readFileSync(focusPath, 'utf8').trim();
638
+ if (finalContent) {
639
+ // 解析工作记忆部分(用于独立注入)
640
+ const workingMemorySnapshot = parseWorkingMemorySection(finalContent);
641
+ if (workingMemorySnapshot) {
642
+ workingMemoryContent = workingMemoryToInjection(workingMemorySnapshot);
643
+ }
550
644
  if (contextConfig.projectFocus === 'summary') {
551
645
  // Summary mode: intelligent extraction prioritizing key sections
552
- projectContextContent = extractSummary(currentFocus, 30);
646
+ projectContextContent = extractSummary(finalContent, 30);
553
647
  }
554
648
  else {
555
649
  // Full mode: current version + recent history (3 versions)
556
650
  const historyVersions = getHistoryVersions(focusPath, 3);
557
651
  if (historyVersions.length > 0) {
558
- const historySections = historyVersions.map((v, i) => `\n---\n\n**闁告ê妫楄ぐ鍫曟偋閸喐鎷?v${historyVersions.length - i}**\n\n${v}`).join('');
559
- projectContextContent = `${currentFocus}${historySections}`;
652
+ const historySections = historyVersions.map((v, i) => `\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`).join('');
653
+ projectContextContent = `${finalContent}${historySections}`;
560
654
  }
561
655
  else {
562
- projectContextContent = currentFocus;
656
+ projectContextContent = finalContent;
563
657
  }
564
658
  }
565
659
  }
566
660
  }
567
661
  catch (e) {
568
- logger?.error(`[PD:Prompt] Failed to read CURRENT_FOCUS: ${String(e)}`);
662
+ logger?.error(`[PD:Prompt] Failed to process CURRENT_FOCUS: ${String(e)}`);
569
663
  }
570
664
  }
571
665
  }
@@ -607,12 +701,16 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
607
701
  logger?.warn?.(`[PD:Prompt] Failed to load evolution principles: ${String(e)}`);
608
702
  }
609
703
  // Build appendSystemContext with recency effect
610
- // Content order (most important last): project_context -> reflection_log -> thinking_os -> principles
704
+ // Content order (most important last): project_context -> working_memory -> reflection_log -> thinking_os -> principles
611
705
  const appendParts = [];
612
706
  // 1. Project Context (lowest priority, goes first)
613
707
  if (projectContextContent) {
614
708
  appendParts.push(`<project_context>\n${projectContextContent}\n</project_context>`);
615
709
  }
710
+ // 1.5. Working Memory (preserved from last compaction)
711
+ if (workingMemoryContent) {
712
+ appendParts.push(workingMemoryContent);
713
+ }
616
714
  // 2. Reflection Log
617
715
  if (reflectionLogContent) {
618
716
  appendParts.push(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`);
@@ -4,6 +4,7 @@ type SubagentEndedHookContext = PluginHookSubagentContext & {
4
4
  api?: EmpathyObserverApi;
5
5
  workspaceDir?: string;
6
6
  sessionId?: string;
7
+ agentId?: string;
7
8
  };
8
9
  export declare function handleSubagentEnded(event: PluginHookSubagentEndedEvent, ctx: SubagentEndedHookContext): Promise<void>;
9
10
  export {};