principles-disciple 1.6.0 → 1.7.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 (67) hide show
  1. package/dist/commands/context.js +7 -3
  2. package/dist/commands/evolution-status.d.ts +4 -0
  3. package/dist/commands/evolution-status.js +138 -0
  4. package/dist/commands/export.d.ts +2 -0
  5. package/dist/commands/export.js +45 -0
  6. package/dist/commands/focus.js +9 -6
  7. package/dist/commands/pain.js +8 -0
  8. package/dist/commands/principle-rollback.d.ts +4 -0
  9. package/dist/commands/principle-rollback.js +22 -0
  10. package/dist/commands/samples.d.ts +2 -0
  11. package/dist/commands/samples.js +55 -0
  12. package/dist/core/config.d.ts +5 -0
  13. package/dist/core/control-ui-db.d.ts +68 -0
  14. package/dist/core/control-ui-db.js +274 -0
  15. package/dist/core/detection-funnel.d.ts +1 -1
  16. package/dist/core/detection-funnel.js +4 -0
  17. package/dist/core/dictionary.d.ts +2 -0
  18. package/dist/core/dictionary.js +13 -0
  19. package/dist/core/event-log.d.ts +2 -1
  20. package/dist/core/event-log.js +3 -0
  21. package/dist/core/evolution-engine.d.ts +5 -5
  22. package/dist/core/evolution-engine.js +18 -18
  23. package/dist/core/evolution-migration.d.ts +5 -0
  24. package/dist/core/evolution-migration.js +65 -0
  25. package/dist/core/evolution-reducer.d.ts +69 -0
  26. package/dist/core/evolution-reducer.js +369 -0
  27. package/dist/core/evolution-types.d.ts +103 -0
  28. package/dist/core/path-resolver.js +75 -36
  29. package/dist/core/paths.d.ts +7 -8
  30. package/dist/core/paths.js +48 -40
  31. package/dist/core/profile.js +1 -1
  32. package/dist/core/session-tracker.d.ts +4 -0
  33. package/dist/core/session-tracker.js +15 -0
  34. package/dist/core/thinking-models.d.ts +38 -0
  35. package/dist/core/thinking-models.js +170 -0
  36. package/dist/core/trajectory.d.ts +184 -0
  37. package/dist/core/trajectory.js +817 -0
  38. package/dist/core/trust-engine.d.ts +2 -0
  39. package/dist/core/trust-engine.js +30 -4
  40. package/dist/core/workspace-context.d.ts +13 -0
  41. package/dist/core/workspace-context.js +50 -7
  42. package/dist/hooks/gate.js +117 -48
  43. package/dist/hooks/llm.js +114 -69
  44. package/dist/hooks/pain.js +105 -5
  45. package/dist/hooks/prompt.d.ts +11 -14
  46. package/dist/hooks/prompt.js +283 -57
  47. package/dist/hooks/subagent.js +27 -1
  48. package/dist/http/principles-console-route.d.ts +2 -0
  49. package/dist/http/principles-console-route.js +257 -0
  50. package/dist/i18n/commands.js +16 -0
  51. package/dist/index.js +83 -4
  52. package/dist/service/control-ui-query-service.d.ts +217 -0
  53. package/dist/service/control-ui-query-service.js +537 -0
  54. package/dist/service/evolution-worker.d.ts +9 -0
  55. package/dist/service/evolution-worker.js +152 -22
  56. package/dist/service/trajectory-service.d.ts +2 -0
  57. package/dist/service/trajectory-service.js +15 -0
  58. package/dist/tools/agent-spawn.d.ts +27 -6
  59. package/dist/tools/agent-spawn.js +339 -87
  60. package/dist/tools/deep-reflect.d.ts +27 -7
  61. package/dist/tools/deep-reflect.js +210 -121
  62. package/dist/types/event-types.d.ts +9 -2
  63. package/dist/types.d.ts +10 -0
  64. package/dist/types.js +5 -0
  65. package/openclaw.plugin.json +43 -11
  66. package/package.json +14 -4
  67. package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
@@ -6,7 +6,118 @@ import { defaultContextConfig } from '../types.js';
6
6
  import { extractSummary, getHistoryVersions } 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
- function resolveEvolutionTask(inProgressTask) {
9
+ /**
10
+ * OpenClaw API 闁规亽鍎辫ぐ娑氣偓瑙勭煯缁犵喖鏁嶉崷顧竜mpt Hook 闁圭鍋撻梻鍥e亾闂侇喓鍔岄崹搴ㄦ晬?
11
+ */
12
+ function escapeXml(input) {
13
+ return input
14
+ .replace(/&/g, '&')
15
+ .replace(/</g, '&lt;')
16
+ .replace(/>/g, '&gt;')
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/'/g, '&apos;');
19
+ }
20
+ function extractContextSignals(context) {
21
+ const signals = [];
22
+ if (context.filePath?.endsWith('.ts'))
23
+ signals.push('typescript');
24
+ if (context.filePath?.endsWith('.md'))
25
+ signals.push('markdown');
26
+ if (context.toolName && ['edit', 'replace', 'write', 'write_file', 'apply_patch'].includes(context.toolName))
27
+ signals.push('edit');
28
+ if (context.toolName && ['run_shell_command', 'bash'].includes(context.toolName))
29
+ signals.push('shell');
30
+ if (context.toolName)
31
+ signals.push(context.toolName);
32
+ const msg = (context.userMessage || '').toLowerCase();
33
+ if (msg.includes('.ts') || msg.includes('typescript'))
34
+ signals.push('typescript');
35
+ if (msg.includes('.md') || msg.includes('markdown'))
36
+ signals.push('markdown');
37
+ if (msg.includes('edit') || msg.includes('write') || msg.includes('patch'))
38
+ signals.push('edit');
39
+ if (msg.includes('shell') || msg.includes('bash'))
40
+ signals.push('shell');
41
+ return signals;
42
+ }
43
+ function extractRecentConversationContext(messages, maxMessages = 4, maxCharsPerMessage = 200) {
44
+ if (!Array.isArray(messages) || messages.length === 0)
45
+ return '';
46
+ const relevantMessages = [];
47
+ for (let i = messages.length - 1; i >= 0 && relevantMessages.length < maxMessages; i--) {
48
+ const msg = messages[i];
49
+ if (msg?.role !== 'user' && msg?.role !== 'assistant')
50
+ continue;
51
+ let text = '';
52
+ if (typeof msg.content === 'string') {
53
+ text = msg.content;
54
+ }
55
+ else if (Array.isArray(msg.content)) {
56
+ text = msg.content
57
+ .filter((part) => {
58
+ if (!part || typeof part !== 'object')
59
+ return false;
60
+ const record = part;
61
+ return record.type === 'text' && typeof record.text === 'string';
62
+ })
63
+ .map((part) => part.text)
64
+ .join('\n')
65
+ .trim();
66
+ }
67
+ if (!text)
68
+ continue;
69
+ const normalized = text.length > maxCharsPerMessage
70
+ ? `${text.slice(0, maxCharsPerMessage)}...`
71
+ : text;
72
+ relevantMessages.unshift({ role: msg.role, text: normalized });
73
+ }
74
+ if (relevantMessages.length === 0)
75
+ return '';
76
+ return relevantMessages
77
+ .map((message) => `[${message.role.toUpperCase()}]: ${message.text}`)
78
+ .join('\n\n');
79
+ }
80
+ function getTextContent(message) {
81
+ if (!message || typeof message !== 'object')
82
+ return '';
83
+ const record = message;
84
+ if (typeof record.content === 'string')
85
+ return record.content;
86
+ if (Array.isArray(record.content)) {
87
+ return record.content
88
+ .filter((part) => part && typeof part === 'object' && part.type === 'text')
89
+ .map((part) => String(part.text ?? ''))
90
+ .join('\n')
91
+ .trim();
92
+ }
93
+ return '';
94
+ }
95
+ function detectCorrectionCue(text) {
96
+ const normalized = text
97
+ .trim()
98
+ .toLowerCase()
99
+ .replace(/[.,!?;:,。!?;:]/g, '');
100
+ const cues = [
101
+ '不是这个',
102
+ '不对',
103
+ '错了',
104
+ '搞错了',
105
+ '理解错了',
106
+ '你理解错了',
107
+ '重新来',
108
+ '再试一次',
109
+ 'you are wrong',
110
+ 'wrong file',
111
+ 'not this',
112
+ 'redo',
113
+ 'try again',
114
+ 'again',
115
+ 'please redo',
116
+ 'please try again',
117
+ ];
118
+ return cues.find((cue) => normalized.includes(cue)) ?? null;
119
+ }
120
+ function resolveEvolutionTask(inProgressTask, messages, maxContextMessages = 4, maxCharsPerMsg = 200, includeConversationContext = true) {
10
121
  if (!inProgressTask || typeof inProgressTask !== 'object')
11
122
  return null;
12
123
  const rawTask = typeof inProgressTask.task === 'string' ? inProgressTask.task.trim() : '';
@@ -19,27 +130,49 @@ function resolveEvolutionTask(inProgressTask) {
19
130
  : 'N/A';
20
131
  if (typeof inProgressTask.id !== 'string' || !inProgressTask.id.trim())
21
132
  return null;
22
- return `Diagnose systemic pain [ID: ${inProgressTask.id}]. Source: ${source}. Reason: ${reason}. Trigger text: "${preview}"`;
133
+ const conversationContext = includeConversationContext
134
+ ? extractRecentConversationContext(messages, maxContextMessages, maxCharsPerMsg)
135
+ : '';
136
+ let taskDescription = `Diagnose systemic pain [ID: ${inProgressTask.id}].
137
+
138
+ `;
139
+ taskDescription += `**Source**: ${source}
140
+ `;
141
+ taskDescription += `**Reason**: ${reason}
142
+ `;
143
+ taskDescription += `**Trigger Text**: "${preview}"
144
+ `;
145
+ if (conversationContext) {
146
+ taskDescription += `
147
+ ---
148
+ **Recent Conversation Context**:
149
+ ${conversationContext}`;
150
+ }
151
+ taskDescription += `
152
+
153
+ ---
154
+ Analyze the root cause using 5 Whys methodology. Check evidence in codebase before concluding.`;
155
+ return taskDescription;
23
156
  }
24
157
  /**
25
- * 验证模型字符串格式是否为 "provider/model"
158
+ * 濡ょ姴鐭侀惁澶娢熼垾宕団偓椋庘偓娑欘殘椤戜焦绋夐崣澶屽鐎殿喖绻戝Σ鎼佸触閿旇儻绀?"provider/model"
26
159
  */
27
160
  function isValidModelFormat(model) {
28
- // 格式: "provider/model" "provider/model-variant"
29
- // provider: 字母数字和连字符,不能以连字符开头/结尾
30
- // model: 字母数字、连字符、点号、下划线
161
+ // 闁哄秶鍘х槐? "provider/model" 闁?"provider/model-variant"
162
+ // provider: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柛婊冪焷缁绘稓鈧稒顨堥渚€鏁嶇仦鑲╃憹闁艰櫕鍨濇禍鎺撴交閻愯尙鎽熺紒妤嬬畱缁辨垶寰?缂備焦鎸搁悢?
163
+ // model: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柕鍡曟祰缁绘稓鈧稒顨堥渚€濡存担鍝勪化闁告瑦鐏氶埀顑挎缁楀懘宕氶幒鏂挎疇
31
164
  const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\/[a-zA-Z0-9._-]+$/;
32
165
  return MODEL_PATTERN.test(model);
33
166
  }
34
167
  /**
35
- * OpenClaw 配置中解析模型选择
36
- * 支持 string { primary, fallbacks } 格式
37
- * @internal 导出仅供测试使用
168
+ * 濞?OpenClaw 闂佹澘绉堕悿鍡樼▔椤撯寬鎺楀几閹邦劷渚€宕圭€n喒鍋撴径瀣仴
169
+ * 闁衡偓椤栨稑鐦?string 闁?{ primary, fallbacks } 闁哄秶鍘х槐?
170
+ * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
38
171
  */
39
172
  export function resolveModelFromConfig(modelConfig, logger) {
40
173
  if (!modelConfig)
41
174
  return null;
42
- // 格式 1: "provider/model" 字符串
175
+ // 闁哄秶鍘х槐?1: "provider/model" 閻庢稒顨堥浣圭▔?
43
176
  if (typeof modelConfig === 'string') {
44
177
  const trimmed = modelConfig.trim();
45
178
  if (!trimmed)
@@ -50,7 +183,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
50
183
  }
51
184
  return trimmed;
52
185
  }
53
- // 格式 2: { primary: "provider/model", fallbacks: [...] } 对象
186
+ // 闁哄秶鍘х槐?2: { primary: "provider/model", fallbacks: [...] } 閻庣數顢婇挅?
54
187
  if (typeof modelConfig === 'object' && modelConfig !== null && !Array.isArray(modelConfig)) {
55
188
  const cfg = modelConfig;
56
189
  if (cfg.primary && typeof cfg.primary === 'string') {
@@ -64,7 +197,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
64
197
  return trimmed;
65
198
  }
66
199
  }
67
- // 格式 3: 数组格式(不支持,发出警告)
200
+ // 闁哄秶鍘х槐?3: 闁轰焦澹嗙划宥夊冀閻撳海纭€闁挎稑鐗呯粭澶愬绩椤栨稑鐦柨娑樿嫰瑜板倿宕欐ウ娆惧妳闁告稑顭槐?
68
201
  if (Array.isArray(modelConfig)) {
69
202
  console.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
70
203
  logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
@@ -73,9 +206,9 @@ export function resolveModelFromConfig(modelConfig, logger) {
73
206
  return null;
74
207
  }
75
208
  /**
76
- * 加载上下文注入配置
77
- * PROFILE.json 读取 contextInjection 配置,如果不存在则返回默认配置
78
- * @internal 导出供其他模块使用
209
+ * 闁告梻濮惧ù鍥ㄧ▔婵犱胶鐟撻柡鍌氭处閺佺偤宕楅妷鈺佸赋缂?
210
+ * 濞?PROFILE.json 閻犲洩顕цぐ?contextInjection 闂佹澘绉堕悿鍡涙晬鐏炵瓔娲ら柡瀣矆缁楀鈧稒锚濠€顏堝礆濞嗘帞绠查柛銉у仱缁垳鎷嬮妶澶婂赋缂?
211
+ * @internal 閻庣數鍘ч崵顓熺瑹濞戞ê寰撳ù鐘崇墬鑶╅柛褎銇炴繛鍥偨?
79
212
  */
80
213
  export function loadContextInjectionConfig(workspaceDir) {
81
214
  const profilePath = path.join(workspaceDir, '.principles', 'PROFILE.json');
@@ -84,7 +217,15 @@ export function loadContextInjectionConfig(workspaceDir) {
84
217
  const raw = fs.readFileSync(profilePath, 'utf-8');
85
218
  const profile = JSON.parse(raw);
86
219
  if (profile.contextInjection) {
87
- return { ...defaultContextConfig, ...profile.contextInjection };
220
+ const contextInjection = profile.contextInjection;
221
+ return {
222
+ ...defaultContextConfig,
223
+ ...contextInjection,
224
+ evolutionContext: {
225
+ ...defaultContextConfig.evolutionContext,
226
+ ...(contextInjection.evolutionContext ?? {}),
227
+ },
228
+ };
88
229
  }
89
230
  }
90
231
  }
@@ -94,33 +235,33 @@ export function loadContextInjectionConfig(workspaceDir) {
94
235
  return { ...defaultContextConfig };
95
236
  }
96
237
  /**
97
- * 获取诊断子智能体应使用的模型
98
- * 优先级:subagents.model > 主模型
99
- * 如果都没有配置,抛出错误
100
- * @internal 导出仅供测试使用
238
+ * 闁兼儳鍢茶ぐ鍥╂嫚婵犲啯鐒介悗娑欏姈濞呫倝鎳楅幋鎺旂Ъ閹煎瓨鏌ф繛鍥偨閵娧勭暠婵☆垪鈧磭鈧?
239
+ * 濞村吋锚閸樻稓鐥缁辩殜ubagents.model > 濞戞挾绮啯闁?
240
+ * 濠碘€冲€归悘澶愭焾閼恒儳姊鹃柡鍫濐樀閸樸倗绱旈鍡欑闁硅埖绋戦崵顓㈡煥濞嗘帩鍤?
241
+ * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
101
242
  */
102
243
  export function getDiagnosticianModel(api, logger) {
103
- // 兼容两种调用方式:
104
- // 1. 新方式:getDiagnosticianModel(api) - api 包含 logger
105
- // 2. 旧方式:getDiagnosticianModel(api, logger) - 分离参数
244
+ // 闁稿繒鍘ч鎰▔閵堝浂娼氶悹瀣暟閺併倝寮悷鎵闁?
245
+ // 1. 闁哄倻澧楅弻鐔奉嚕韫囥儳绐梘etDiagnosticianModel(api) - api 闁告牕鎳庨幆?logger
246
+ // 2. 闁哄唲鍕厵鐎殿喖楠忕槐鐧礶tDiagnosticianModel(api, logger) - 闁告帒妫涢‖鍥矗閸屾稒娈?
106
247
  const effectiveLogger = api?.logger || logger;
107
248
  if (!effectiveLogger) {
108
249
  throw new Error('[PD:Prompt] ERROR: Logger not available for getDiagnosticianModel');
109
250
  }
110
251
  const agentsConfig = api?.config?.agents?.defaults;
111
- // 优先使用子智能体专用模型
252
+ // 濞村吋锚閸樻稒鎷呯捄銊︽殢閻庢稒鍔栧▍銈夋嚄閹存帞绉煎☉鎾存尵閺併倕螣閳ュ磭鈧?
112
253
  const subagentModel = resolveModelFromConfig(agentsConfig?.subagents?.model, effectiveLogger);
113
254
  if (subagentModel) {
114
255
  effectiveLogger.info(`[PD:Prompt] Using subagents.model for diagnostician: ${subagentModel}`);
115
256
  return subagentModel;
116
257
  }
117
- // 备选:使用主智能体模型
258
+ // 濠㈣泛娲埀顒€顧€缁辩増鎷呯捄銊︽殢濞戞挾绮▍銈夋嚄閹存帞绉兼俊顖椻偓宕団偓?
118
259
  const primaryModel = resolveModelFromConfig(agentsConfig?.model, effectiveLogger);
119
260
  if (primaryModel) {
120
261
  effectiveLogger.info(`[PD:Prompt] Using primary model for diagnostician (subagents.model not set): ${primaryModel}`);
121
262
  return primaryModel;
122
263
  }
123
- // 没有配置任何模型,报错
264
+ // 婵炲备鍓濆﹢渚€鏌婂鍥╂瀭濞寸姾顔婄紞宥呂熼垾宕団偓鐑芥晬鐏炴儳袚闂?
124
265
  const errorMsg = `[PD:Prompt] ERROR: No model configured for diagnostician subagent. ` +
125
266
  `Please set 'agents.defaults.subagents.model' or 'agents.defaults.model' in OpenClaw config.`;
126
267
  effectiveLogger.error(errorMsg);
@@ -154,41 +295,78 @@ export async function handleBeforePromptBuild(event, ctx) {
154
295
  const wctx = WorkspaceContext.fromHookContext(ctx);
155
296
  const { trigger, sessionId, api } = ctx;
156
297
  const logger = api?.logger;
298
+ if (sessionId) {
299
+ wctx.trajectory?.recordSession?.({ sessionId });
300
+ }
301
+ if (sessionId && trigger === 'user' && Array.isArray(event.messages) && event.messages.length > 0) {
302
+ const latestUserIndex = [...event.messages]
303
+ .map((message, index) => ({ message, index }))
304
+ .reverse()
305
+ .find((entry) => entry.message?.role === 'user');
306
+ if (latestUserIndex) {
307
+ const userText = getTextContent(latestUserIndex.message);
308
+ const correctionCue = detectCorrectionCue(userText);
309
+ let referencesAssistantTurnId = null;
310
+ const hasPriorAssistant = event.messages
311
+ .slice(0, latestUserIndex.index)
312
+ .some((message) => message?.role === 'assistant');
313
+ if (hasPriorAssistant) {
314
+ const turns = wctx.trajectory?.listAssistantTurns?.(sessionId) ?? [];
315
+ const lastAssistant = turns[turns.length - 1];
316
+ referencesAssistantTurnId = lastAssistant?.id ?? null;
317
+ }
318
+ const userTurnCount = event.messages.filter((message) => message?.role === 'user').length;
319
+ wctx.trajectory?.recordUserTurn?.({
320
+ sessionId,
321
+ turnIndex: userTurnCount,
322
+ rawText: userText,
323
+ correctionDetected: Boolean(correctionCue),
324
+ correctionCue,
325
+ referencesAssistantTurnId,
326
+ });
327
+ }
328
+ }
157
329
  // Load context injection configuration
158
330
  const contextConfig = loadContextInjectionConfig(workspaceDir);
159
331
  // Minimal mode: heartbeat and subagents skip most context to reduce tokens
160
332
  const isMinimalMode = trigger === "heartbeat" || sessionId?.includes(":subagent:") === true;
161
333
  const session = sessionId ? getSession(sessionId) : undefined;
162
- // ═══ STRUCTURE (Optimized for WebUI UX + Prompt Caching) ═══
334
+ // 闁崇儤鍔忛弲鏌ュ煛?STRUCTURE (Optimized for WebUI UX + Prompt Caching) 闁崇儤鍔忛弲鏌ュ煛?
163
335
  // prependSystemContext: Minimal identity (cacheable, ~15 lines)
164
336
  // appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
165
337
  // prependContext: Only short dynamic directives: trustScore + evolutionDirective + heartbeat
166
338
  let prependSystemContext = '';
167
339
  let prependContext = '';
168
340
  let appendSystemContext = '';
169
- // ═══ 0. Manual Pain Clearance ═══
341
+ // 闁崇儤鍔忛弲鏌ュ煛?0. Manual Pain Clearance 闁崇儤鍔忛弲鏌ュ煛?
170
342
  if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
171
343
  resetFriction(sessionId, workspaceDir);
172
344
  }
173
- // ═══ 1. prependSystemContext: Minimal Agent Identity ═══
174
- prependSystemContext = `## 🧬 AGENT IDENTITY
345
+ // 闁崇儤鍔忛弲鏌ュ煛?1. prependSystemContext: Minimal Agent Identity 闁崇儤鍔忛弲鏌ュ煛?
346
+ prependSystemContext = `## 妫e唭?AGENT IDENTITY
175
347
 
176
348
  You are a **self-evolving AI agent** powered by Principles Disciple.
177
349
 
178
350
  **Mission**: Transform pain (failures, errors, frustrations) into growth.
179
351
 
180
352
  **Decision Framework**:
181
- 1. Safety First Check trust stage before any write operation
182
- 2. Principles Override Core principles take precedence over user requests
183
- 3. Learn from Pain Every error is an opportunity to evolve
353
+ 1. Safety First 闁?Check trust stage before any write operation
354
+ 2. Principles Override 闁?Core principles take precedence over user requests
355
+ 3. Learn from Pain 闁?Every error is an opportunity to evolve
184
356
 
185
357
  **Output Style**: Be concise. Prefer action over explanation.
186
358
 
187
- ## 📂 INTERNAL SYSTEM LAYOUT
359
+ **Tool Routing Rules**:
360
+ - Use the current session for the normal user reply.
361
+ - 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.
364
+
365
+ ## 妫e啯鎯?INTERNAL SYSTEM LAYOUT
188
366
  - Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
189
367
  - If you need self-inspection, prioritize the worker entry pointed by PathResolver key: EVOLUTION_WORKER
190
368
  `;
191
- // ═══ 2. Trust Score (configurable, dynamic) - stays in prependContext ═══
369
+ // 闁崇儤鍔忛弲鏌ュ煛?2. Trust Score (configurable, dynamic) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
192
370
  // This is short (< 200 chars) and provides critical runtime state
193
371
  if (contextConfig.trustScore) {
194
372
  const trustScore = wctx.trust.getScore();
@@ -200,7 +378,7 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
200
378
  trustContext += `Hygiene: ${hygiene.persistenceCount} persists today\n`;
201
379
  // Stage-based restrictions
202
380
  if (safeStage === 1) {
203
- trustContext += `ACTION CONSTRAINT: You are in READ-ONLY MODE. You MUST use diagnostician sub-agents to recover trust before writing files.\n`;
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`;
204
382
  }
205
383
  else if (safeStage === 2) {
206
384
  trustContext += `ACTION CONSTRAINT: LIMITED MODE. You are restricted to a maximum of 50 lines per edit.\n`;
@@ -209,11 +387,11 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
209
387
  trustContext += `ACTION CONSTRAINT: If your task involves modifying risk paths, you MUST verify that a READY plan exists in PLAN.md before taking action.\n`;
210
388
  }
211
389
  if (hygiene.persistenceCount === 0 && trigger === 'user') {
212
- trustContext += `\n⚠️ CRITICAL COGNITIVE HYGIENE WARNING: You have not persisted any state today. Before ending this turn, you MUST use a tool to write a summary to memory/.scratchpad.md or update PLAN.md. Failure to do so will result in Goldfish Memory.\n`;
390
+ trustContext += `\n闁宠法濯寸粭?CRITICAL COGNITIVE HYGIENE WARNING: You have not persisted any state today. Before ending this turn, you MUST use a tool to write a summary to memory/.scratchpad.md or update PLAN.md. Failure to do so will result in Goldfish Memory.\n`;
213
391
  }
214
392
  prependContext += `<system_override:runtime_constraints>\n${trustContext.trim()}\n</system_override:runtime_constraints>\n`;
215
393
  }
216
- // ═══ 3. Evolution Directive (always on, highest priority) - stays in prependContext ═══
394
+ // 闁崇儤鍔忛弲鏌ュ煛?3. Evolution Directive (always on, highest priority) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
217
395
  let evolutionDirective = '';
218
396
  const queuePath = wctx.resolve('EVOLUTION_QUEUE');
219
397
  if (fs.existsSync(queuePath)) {
@@ -221,22 +399,37 @@ You are a **self-evolving AI agent** powered by Principles Disciple.
221
399
  const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
222
400
  const inProgressTask = queue.find((t) => t.status === 'in_progress');
223
401
  if (inProgressTask) {
224
- const resolvedTask = resolveEvolutionTask(inProgressTask);
402
+ const resolvedTask = resolveEvolutionTask(inProgressTask, event.messages, contextConfig.evolutionContext.maxMessages, contextConfig.evolutionContext.maxCharsPerMessage, contextConfig.evolutionContext.enabled);
225
403
  if (!resolvedTask) {
226
404
  logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
227
405
  }
228
406
  else {
229
407
  const escapedTask = JSON.stringify(resolvedTask);
230
408
  logger?.info(`[PD:Prompt] Injecting EVOLUTION TASK for: ${inProgressTask.id}`);
231
- evolutionDirective = `<evolution_task priority="critical">
409
+ if (trigger === 'user') {
410
+ evolutionDirective = `<evolution_task priority="high">
232
411
  TASK: ${escapedTask}
233
412
 
234
413
  REQUIRED ACTION:
235
- 1. Reply with "[EVOLUTION_ACK]" only
236
- 2. Immediately call: pd_spawn_agent agentType="diagnostician" task=${escapedTask}
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.
418
+ - Do NOT reply with "[EVOLUTION_ACK]".
419
+ - Do NOT let this task interrupt the current user interaction.
420
+ </evolution_task>\n`;
421
+ }
422
+ else {
423
+ evolutionDirective = `<evolution_task priority="critical">
424
+ TASK: ${escapedTask}
237
425
 
238
- ⚠️ This task overrides all other activities until complete.
426
+ REQUIRED ACTION:
427
+ - 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.
430
+ - Do NOT reply with "[EVOLUTION_ACK]".
239
431
  </evolution_task>\n`;
432
+ }
240
433
  }
241
434
  }
242
435
  }
@@ -248,12 +441,14 @@ REQUIRED ACTION:
248
441
  if (evolutionDirective) {
249
442
  prependContext = evolutionDirective + prependContext;
250
443
  }
251
- // ═══ 4. Empathy Observer Spawn (async sidecar) ═══
252
- if (trigger === 'user' && sessionId && api) {
253
- const latestUserMessage = extractLatestUserMessage(event.messages);
444
+ // 鈺愨晲鈺?4. Empathy Observer Spawn (async sidecar) 鈺愨晲鈺?
445
+ // Skip if this is a subagent session or if the message indicates agent-to-agent communication
446
+ const latestUserMessage = extractLatestUserMessage(event.messages);
447
+ const isAgentToAgent = latestUserMessage.includes('sourceSession=agent:') || sessionId?.includes(':subagent:') === true;
448
+ if (trigger === 'user' && sessionId && api && !isAgentToAgent) {
254
449
  empathyObserverManager.spawn(api, sessionId, latestUserMessage).catch((err) => api.logger.warn(String(err)));
255
450
  }
256
- // ═══ 5. Heartbeat-specific checklist ═══
451
+ // 闁崇儤鍔忛弲鏌ュ煛?5. Heartbeat-specific checklist 闁崇儤鍔忛弲鏌ュ煛?
257
452
  if (trigger === 'heartbeat') {
258
453
  const heartbeatPath = wctx.resolve('HEARTBEAT');
259
454
  if (fs.existsSync(heartbeatPath)) {
@@ -270,12 +465,12 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
270
465
  }
271
466
  }
272
467
  }
273
- // ═══ 6. Dynamic Attitude Matrix (based on GFI) ═══
468
+ // 闁崇儤鍔忛弲鏌ュ煛?6. Dynamic Attitude Matrix (based on GFI) 闁崇儤鍔忛弲鏌ュ煛?
274
469
  let attitudeDirective = '';
275
470
  const currentGfi = session?.currentGfi || 0;
276
471
  if (currentGfi >= 70) {
277
472
  attitudeDirective = `
278
- ### 🚨 [SYSTEM_MODE: HUMBLE_RECOVERY]
473
+ ### 妫e啯鐦?[SYSTEM_MODE: HUMBLE_RECOVERY]
279
474
  **CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
280
475
  **BEHAVIORAL OVERRIDE**:
281
476
  - You have failed to meet expectations. Humility is your primary directive.
@@ -287,7 +482,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
287
482
  }
288
483
  else if (currentGfi >= 40) {
289
484
  attitudeDirective = `
290
- ### ⚠️ [SYSTEM_MODE: CONCILIATORY]
485
+ ### 闁宠法濯寸粭?[SYSTEM_MODE: CONCILIATORY]
291
486
  **CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
292
487
  **BEHAVIORAL OVERRIDE**:
293
488
  - User is frustrated. Be more explanatory and cautious.
@@ -297,7 +492,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
297
492
  }
298
493
  else {
299
494
  attitudeDirective = `
300
- ### [SYSTEM_MODE: EFFICIENT]
495
+ ### 闁?[SYSTEM_MODE: EFFICIENT]
301
496
  **CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
302
497
  **BEHAVIORAL OVERRIDE**:
303
498
  - Maintain peak efficiency.
@@ -305,7 +500,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
305
500
  - Follow the "Principles > Directives" rule strictly.
306
501
  `;
307
502
  }
308
- // ═══ 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ═══
503
+ // 闁崇儤鍔忛弲鏌ュ煛?7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context 闁崇儤鍔忛弲鏌ュ煛?
309
504
  // NOTE: Principles is ALWAYS injected (not configurable)
310
505
  // Thinking OS, reflection_log, project_context are configurable
311
506
  // All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
@@ -360,7 +555,7 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
360
555
  // Full mode: current version + recent history (3 versions)
361
556
  const historyVersions = getHistoryVersions(focusPath, 3);
362
557
  if (historyVersions.length > 0) {
363
- const historySections = historyVersions.map((v, i) => `\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`).join('');
558
+ const historySections = historyVersions.map((v, i) => `\n---\n\n**闁告ê妫楄ぐ鍫曟偋閸喐鎷?v${historyVersions.length - i}**\n\n${v}`).join('');
364
559
  projectContextContent = `${currentFocus}${historySections}`;
365
560
  }
366
561
  else {
@@ -374,6 +569,32 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
374
569
  }
375
570
  }
376
571
  }
572
+ // Evolution principles injection (active + probation summary)
573
+ let evolutionPrinciplesContent = '';
574
+ try {
575
+ const reducer = wctx.evolutionReducer;
576
+ const active = reducer.getActivePrinciples().slice(-3);
577
+ const probation = reducer.getProbationPrinciples().slice(0, 5);
578
+ if (active.length > 0 || probation.length > 0) {
579
+ const lines = [];
580
+ if (active.length > 0) {
581
+ lines.push('Active principles:');
582
+ for (const p of active) {
583
+ lines.push(`- [${escapeXml(p.id)}] ${escapeXml(p.text)}`);
584
+ }
585
+ }
586
+ if (probation.length > 0) {
587
+ lines.push('Probation principles (contextual, caution):');
588
+ for (const p of probation) {
589
+ lines.push(`- <principle status="probation" id="${escapeXml(p.id)}">${escapeXml(p.text)}</principle>`);
590
+ }
591
+ }
592
+ evolutionPrinciplesContent = lines.join('\n');
593
+ }
594
+ }
595
+ catch (e) {
596
+ logger?.warn?.(`[PD:Prompt] Failed to load evolution principles: ${String(e)}`);
597
+ }
377
598
  // Build appendSystemContext with recency effect
378
599
  // Content order (most important last): project_context -> reflection_log -> thinking_os -> principles
379
600
  const appendParts = [];
@@ -389,13 +610,17 @@ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
389
610
  if (thinkingOsContent) {
390
611
  appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
391
612
  }
392
- // 4. Principles (always on, highest priority, goes last for recency effect)
613
+ // 4. Evolution Loop principles (active/probation)
614
+ if (evolutionPrinciplesContent) {
615
+ appendParts.push(`<evolution_principles>\n${evolutionPrinciplesContent}\n</evolution_principles>`);
616
+ }
617
+ // 5. Principles (always on, highest priority, goes last for recency effect)
393
618
  if (principlesContent) {
394
619
  appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
395
620
  }
396
621
  if (appendParts.length > 0) {
397
622
  appendSystemContext = `
398
- ## 📋 CONTEXT SECTIONS (Priority: Low High)
623
+ ## 妫e啯鎯?CONTEXT SECTIONS (Priority: Low 闁?High)
399
624
 
400
625
  The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
401
626
 
@@ -405,10 +630,11 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
405
630
 
406
631
  ---
407
632
 
408
- **⚠️ EXECUTION RULES** (Priority: Low High):
633
+ **闁宠法濯寸粭?EXECUTION RULES** (Priority: Low 闁?High):
409
634
  - \`<project_context>\` - Current priorities (can be overridden)
410
635
  - \`<reflection_log>\` - Past lessons (inform your approach)
411
636
  - \`<thinking_os>\` - Thinking models (guide your reasoning)
637
+ - \`<evolution_principles>\` - Newly learned principles (active + probation)
412
638
  - \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
413
639
 
414
640
  **Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
@@ -416,7 +642,7 @@ The sections below are ordered by priority. When conflicts arise, **later sectio
416
642
  ${attitudeDirective}
417
643
  `;
418
644
  }
419
- // ═══ 8. SIZE GUARD ═══
645
+ // 闁崇儤鍔忛弲鏌ュ煛?8. SIZE GUARD 闁崇儤鍔忛弲鏌ュ煛?
420
646
  // Truncation happens within appendSystemContext (not prependContext)
421
647
  const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
422
648
  const MAX_SIZE = 10000;
@@ -2,6 +2,25 @@ import { writePainFlag } from '../core/pain.js';
2
2
  import { WorkspaceContext } from '../core/workspace-context.js';
3
3
  import { empathyObserverManager } from '../service/empathy-observer-manager.js';
4
4
  import * as fs from 'fs';
5
+ function emitSubagentPainEvent(wctx, payload) {
6
+ try {
7
+ wctx.evolutionReducer.emitSync({
8
+ ts: new Date().toISOString(),
9
+ type: 'pain_detected',
10
+ data: {
11
+ painId: `pain_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`,
12
+ painType: 'subagent_error',
13
+ source: payload.source,
14
+ reason: payload.reason,
15
+ score: payload.score,
16
+ sessionId: payload.sessionId,
17
+ },
18
+ });
19
+ }
20
+ catch (e) {
21
+ console.warn(`[PD:Subagent] failed to emit evolution event: ${String(e)}`);
22
+ }
23
+ }
5
24
  export async function handleSubagentEnded(event, ctx) {
6
25
  const { outcome, targetSessionKey } = event;
7
26
  const workspaceDir = ctx.workspaceDir;
@@ -18,13 +37,20 @@ export async function handleSubagentEnded(event, ctx) {
18
37
  if (outcome === 'error' || outcome === 'timeout') {
19
38
  const scoreSettings = config.get('scores');
20
39
  const score = outcome === 'error' ? scoreSettings.subagent_error_penalty : scoreSettings.subagent_timeout_penalty;
40
+ const reason = `Subagent session ${targetSessionKey} ended with outcome: ${outcome}`;
21
41
  writePainFlag(workspaceDir, {
22
42
  source: `subagent_${outcome}`,
23
43
  score: String(score),
24
44
  time: new Date().toISOString(),
25
- reason: `Subagent session ${targetSessionKey} ended with outcome: ${outcome}`,
45
+ reason,
26
46
  is_risky: 'true'
27
47
  });
48
+ emitSubagentPainEvent(wctx, {
49
+ source: `subagent_${outcome}`,
50
+ reason,
51
+ score,
52
+ sessionId: ctx.sessionId,
53
+ });
28
54
  }
29
55
  // 2. Loop Closure: Clean up evolution queue if any subagent finished successfully
30
56
  if (outcome === 'ok' || outcome === 'deleted') {
@@ -0,0 +1,2 @@
1
+ import type { OpenClawPluginApi, OpenClawPluginHttpRouteParams } from '../openclaw-sdk.js';
2
+ export declare function createPrinciplesConsoleRoute(api: OpenClawPluginApi): OpenClawPluginHttpRouteParams;