principles-disciple 1.5.4 → 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 (88) hide show
  1. package/dist/commands/context.d.ts +5 -0
  2. package/dist/commands/context.js +312 -0
  3. package/dist/commands/evolution-status.d.ts +4 -0
  4. package/dist/commands/evolution-status.js +138 -0
  5. package/dist/commands/export.d.ts +2 -0
  6. package/dist/commands/export.js +45 -0
  7. package/dist/commands/focus.d.ts +14 -0
  8. package/dist/commands/focus.js +582 -0
  9. package/dist/commands/pain.js +143 -6
  10. package/dist/commands/principle-rollback.d.ts +4 -0
  11. package/dist/commands/principle-rollback.js +22 -0
  12. package/dist/commands/rollback.d.ts +19 -0
  13. package/dist/commands/rollback.js +119 -0
  14. package/dist/commands/samples.d.ts +2 -0
  15. package/dist/commands/samples.js +55 -0
  16. package/dist/core/config.d.ts +37 -0
  17. package/dist/core/config.js +47 -0
  18. package/dist/core/control-ui-db.d.ts +68 -0
  19. package/dist/core/control-ui-db.js +274 -0
  20. package/dist/core/detection-funnel.d.ts +1 -1
  21. package/dist/core/detection-funnel.js +4 -0
  22. package/dist/core/dictionary.d.ts +2 -0
  23. package/dist/core/dictionary.js +13 -0
  24. package/dist/core/event-log.d.ts +22 -1
  25. package/dist/core/event-log.js +319 -0
  26. package/dist/core/evolution-engine.d.ts +5 -5
  27. package/dist/core/evolution-engine.js +18 -18
  28. package/dist/core/evolution-migration.d.ts +5 -0
  29. package/dist/core/evolution-migration.js +65 -0
  30. package/dist/core/evolution-reducer.d.ts +69 -0
  31. package/dist/core/evolution-reducer.js +369 -0
  32. package/dist/core/evolution-types.d.ts +103 -0
  33. package/dist/core/focus-history.d.ts +65 -0
  34. package/dist/core/focus-history.js +266 -0
  35. package/dist/core/init.js +30 -7
  36. package/dist/core/migration.js +0 -2
  37. package/dist/core/path-resolver.d.ts +3 -0
  38. package/dist/core/path-resolver.js +90 -31
  39. package/dist/core/paths.d.ts +7 -8
  40. package/dist/core/paths.js +48 -40
  41. package/dist/core/profile.js +1 -1
  42. package/dist/core/session-tracker.d.ts +4 -0
  43. package/dist/core/session-tracker.js +15 -0
  44. package/dist/core/thinking-models.d.ts +38 -0
  45. package/dist/core/thinking-models.js +170 -0
  46. package/dist/core/trajectory.d.ts +184 -0
  47. package/dist/core/trajectory.js +817 -0
  48. package/dist/core/trust-engine.d.ts +2 -0
  49. package/dist/core/trust-engine.js +30 -4
  50. package/dist/core/workspace-context.d.ts +13 -0
  51. package/dist/core/workspace-context.js +50 -7
  52. package/dist/hooks/gate.js +301 -30
  53. package/dist/hooks/llm.d.ts +8 -0
  54. package/dist/hooks/llm.js +347 -69
  55. package/dist/hooks/message-sanitize.d.ts +3 -0
  56. package/dist/hooks/message-sanitize.js +37 -0
  57. package/dist/hooks/pain.js +105 -5
  58. package/dist/hooks/prompt.d.ts +20 -11
  59. package/dist/hooks/prompt.js +558 -158
  60. package/dist/hooks/subagent.d.ts +9 -2
  61. package/dist/hooks/subagent.js +40 -3
  62. package/dist/http/principles-console-route.d.ts +2 -0
  63. package/dist/http/principles-console-route.js +257 -0
  64. package/dist/i18n/commands.js +48 -20
  65. package/dist/index.js +264 -8
  66. package/dist/service/control-ui-query-service.d.ts +217 -0
  67. package/dist/service/control-ui-query-service.js +537 -0
  68. package/dist/service/empathy-observer-manager.d.ts +42 -0
  69. package/dist/service/empathy-observer-manager.js +147 -0
  70. package/dist/service/evolution-worker.d.ts +10 -0
  71. package/dist/service/evolution-worker.js +156 -24
  72. package/dist/service/trajectory-service.d.ts +2 -0
  73. package/dist/service/trajectory-service.js +15 -0
  74. package/dist/tools/agent-spawn.d.ts +27 -6
  75. package/dist/tools/agent-spawn.js +339 -87
  76. package/dist/tools/deep-reflect.d.ts +27 -7
  77. package/dist/tools/deep-reflect.js +282 -113
  78. package/dist/types/event-types.d.ts +84 -2
  79. package/dist/types/event-types.js +33 -0
  80. package/dist/types.d.ts +52 -0
  81. package/dist/types.js +24 -1
  82. package/openclaw.plugin.json +43 -11
  83. package/package.json +16 -6
  84. package/templates/langs/zh/core/HEARTBEAT.md +28 -4
  85. package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
  86. package/templates/pain_settings.json +54 -2
  87. package/templates/workspace/.principles/PROFILE.json +2 -0
  88. package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
@@ -1,25 +1,178 @@
1
1
  import * as fs from 'fs';
2
+ import * as path from 'path';
2
3
  import { getSession, resetFriction } from '../core/session-tracker.js';
3
4
  import { WorkspaceContext } from '../core/workspace-context.js';
5
+ import { defaultContextConfig } from '../types.js';
6
+ import { extractSummary, getHistoryVersions } from '../core/focus-history.js';
7
+ import { empathyObserverManager } from '../service/empathy-observer-manager.js';
8
+ import { PathResolver } from '../core/path-resolver.js';
4
9
  /**
5
- * 验证模型字符串格式是否为 "provider/model"
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) {
121
+ if (!inProgressTask || typeof inProgressTask !== 'object')
122
+ return null;
123
+ const rawTask = typeof inProgressTask.task === 'string' ? inProgressTask.task.trim() : '';
124
+ if (rawTask && rawTask.toLowerCase() !== 'undefined')
125
+ return rawTask;
126
+ const source = typeof inProgressTask.source === 'string' ? inProgressTask.source.trim() : 'unknown';
127
+ const reason = typeof inProgressTask.reason === 'string' ? inProgressTask.reason.trim() : 'Systemic pain detected';
128
+ const preview = typeof inProgressTask.trigger_text_preview === 'string' && inProgressTask.trigger_text_preview.trim()
129
+ ? inProgressTask.trigger_text_preview.trim()
130
+ : 'N/A';
131
+ if (typeof inProgressTask.id !== 'string' || !inProgressTask.id.trim())
132
+ return null;
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;
156
+ }
157
+ /**
158
+ * 濡ょ姴鐭侀惁澶娢熼垾宕団偓椋庘偓娑欘殘椤戜焦绋夐崣澶屽鐎殿喖绻戝Σ鎼佸触閿旇儻绀?"provider/model"
6
159
  */
7
160
  function isValidModelFormat(model) {
8
- // 格式: "provider/model" "provider/model-variant"
9
- // provider: 字母数字和连字符,不能以连字符开头/结尾
10
- // model: 字母数字、连字符、点号、下划线
161
+ // 闁哄秶鍘х槐? "provider/model" 闁?"provider/model-variant"
162
+ // provider: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柛婊冪焷缁绘稓鈧稒顨堥渚€鏁嶇仦鑲╃憹闁艰櫕鍨濇禍鎺撴交閻愯尙鎽熺紒妤嬬畱缁辨垶寰?缂備焦鎸搁悢?
163
+ // model: 閻庢稒顨嗛惁婵嬪极閺夎法鎽熼柕鍡曟祰缁绘稓鈧稒顨堥渚€濡存担鍝勪化闁告瑦鐏氶埀顑挎缁楀懘宕氶幒鏂挎疇
11
164
  const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\/[a-zA-Z0-9._-]+$/;
12
165
  return MODEL_PATTERN.test(model);
13
166
  }
14
167
  /**
15
- * OpenClaw 配置中解析模型选择
16
- * 支持 string { primary, fallbacks } 格式
17
- * @internal 导出仅供测试使用
168
+ * 濞?OpenClaw 闂佹澘绉堕悿鍡樼▔椤撯寬鎺楀几閹邦劷渚€宕圭€n喒鍋撴径瀣仴
169
+ * 闁衡偓椤栨稑鐦?string 闁?{ primary, fallbacks } 闁哄秶鍘х槐?
170
+ * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
18
171
  */
19
172
  export function resolveModelFromConfig(modelConfig, logger) {
20
173
  if (!modelConfig)
21
174
  return null;
22
- // 格式 1: "provider/model" 字符串
175
+ // 闁哄秶鍘х槐?1: "provider/model" 閻庢稒顨堥浣圭▔?
23
176
  if (typeof modelConfig === 'string') {
24
177
  const trimmed = modelConfig.trim();
25
178
  if (!trimmed)
@@ -30,7 +183,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
30
183
  }
31
184
  return trimmed;
32
185
  }
33
- // 格式 2: { primary: "provider/model", fallbacks: [...] } 对象
186
+ // 闁哄秶鍘х槐?2: { primary: "provider/model", fallbacks: [...] } 閻庣數顢婇挅?
34
187
  if (typeof modelConfig === 'object' && modelConfig !== null && !Array.isArray(modelConfig)) {
35
188
  const cfg = modelConfig;
36
189
  if (cfg.primary && typeof cfg.primary === 'string') {
@@ -44,7 +197,7 @@ export function resolveModelFromConfig(modelConfig, logger) {
44
197
  return trimmed;
45
198
  }
46
199
  }
47
- // 格式 3: 数组格式(不支持,发出警告)
200
+ // 闁哄秶鍘х槐?3: 闁轰焦澹嗙划宥夊冀閻撳海纭€闁挎稑鐗呯粭澶愬绩椤栨稑鐦柨娑樿嫰瑜板倿宕欐ウ娆惧妳闁告稑顭槐?
48
201
  if (Array.isArray(modelConfig)) {
49
202
  console.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
50
203
  logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
@@ -53,229 +206,476 @@ export function resolveModelFromConfig(modelConfig, logger) {
53
206
  return null;
54
207
  }
55
208
  /**
56
- * 获取诊断子智能体应使用的模型
57
- * 优先级:subagents.model > 主模型
58
- * 如果都没有配置,抛出错误
59
- * @internal 导出仅供测试使用
209
+ * 闁告梻濮惧ù鍥ㄧ▔婵犱胶鐟撻柡鍌氭处閺佺偤宕楅妷鈺佸赋缂?
210
+ * 濞?PROFILE.json 閻犲洩顕цぐ?contextInjection 闂佹澘绉堕悿鍡涙晬鐏炵瓔娲ら柡瀣矆缁楀鈧稒锚濠€顏堝礆濞嗘帞绠查柛銉у仱缁垳鎷嬮妶澶婂赋缂?
211
+ * @internal 閻庣數鍘ч崵顓熺瑹濞戞ê寰撳ù鐘崇墬鑶╅柛褎銇炴繛鍥偨?
212
+ */
213
+ export function loadContextInjectionConfig(workspaceDir) {
214
+ const profilePath = path.join(workspaceDir, '.principles', 'PROFILE.json');
215
+ try {
216
+ if (fs.existsSync(profilePath)) {
217
+ const raw = fs.readFileSync(profilePath, 'utf-8');
218
+ const profile = JSON.parse(raw);
219
+ if (profile.contextInjection) {
220
+ const contextInjection = profile.contextInjection;
221
+ return {
222
+ ...defaultContextConfig,
223
+ ...contextInjection,
224
+ evolutionContext: {
225
+ ...defaultContextConfig.evolutionContext,
226
+ ...(contextInjection.evolutionContext ?? {}),
227
+ },
228
+ };
229
+ }
230
+ }
231
+ }
232
+ catch (e) {
233
+ console.warn(`[PD:Prompt] Failed to load contextInjection config: ${String(e)}`);
234
+ }
235
+ return { ...defaultContextConfig };
236
+ }
237
+ /**
238
+ * 闁兼儳鍢茶ぐ鍥╂嫚婵犲啯鐒介悗娑欏姈濞呫倝鎳楅幋鎺旂Ъ閹煎瓨鏌ф繛鍥偨閵娧勭暠婵☆垪鈧磭鈧?
239
+ * 濞村吋锚閸樻稓鐥缁辩殜ubagents.model > 濞戞挾绮啯闁?
240
+ * 濠碘€冲€归悘澶愭焾閼恒儳姊鹃柡鍫濐樀閸樸倗绱旈鍡欑闁硅埖绋戦崵顓㈡煥濞嗘帩鍤?
241
+ * @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
60
242
  */
61
243
  export function getDiagnosticianModel(api, logger) {
62
- // 兼容两种调用方式:
63
- // 1. 新方式:getDiagnosticianModel(api) - api 包含 logger
64
- // 2. 旧方式:getDiagnosticianModel(api, logger) - 分离参数
244
+ // 闁稿繒鍘ч鎰▔閵堝浂娼氶悹瀣暟閺併倝寮悷鎵闁?
245
+ // 1. 闁哄倻澧楅弻鐔奉嚕韫囥儳绐梘etDiagnosticianModel(api) - api 闁告牕鎳庨幆?logger
246
+ // 2. 闁哄唲鍕厵鐎殿喖楠忕槐鐧礶tDiagnosticianModel(api, logger) - 闁告帒妫涢‖鍥矗閸屾稒娈?
65
247
  const effectiveLogger = api?.logger || logger;
66
248
  if (!effectiveLogger) {
67
249
  throw new Error('[PD:Prompt] ERROR: Logger not available for getDiagnosticianModel');
68
250
  }
69
251
  const agentsConfig = api?.config?.agents?.defaults;
70
- // 优先使用子智能体专用模型
252
+ // 濞村吋锚閸樻稒鎷呯捄銊︽殢閻庢稒鍔栧▍銈夋嚄閹存帞绉煎☉鎾存尵閺併倕螣閳ュ磭鈧?
71
253
  const subagentModel = resolveModelFromConfig(agentsConfig?.subagents?.model, effectiveLogger);
72
254
  if (subagentModel) {
73
255
  effectiveLogger.info(`[PD:Prompt] Using subagents.model for diagnostician: ${subagentModel}`);
74
256
  return subagentModel;
75
257
  }
76
- // 备选:使用主智能体模型
258
+ // 濠㈣泛娲埀顒€顧€缁辩増鎷呯捄銊︽殢濞戞挾绮▍銈夋嚄閹存帞绉兼俊顖椻偓宕団偓?
77
259
  const primaryModel = resolveModelFromConfig(agentsConfig?.model, effectiveLogger);
78
260
  if (primaryModel) {
79
261
  effectiveLogger.info(`[PD:Prompt] Using primary model for diagnostician (subagents.model not set): ${primaryModel}`);
80
262
  return primaryModel;
81
263
  }
82
- // 没有配置任何模型,报错
264
+ // 婵炲备鍓濆﹢渚€鏌婂鍥╂瀭濞寸姾顔婄紞宥呂熼垾宕団偓鐑芥晬鐏炴儳袚闂?
83
265
  const errorMsg = `[PD:Prompt] ERROR: No model configured for diagnostician subagent. ` +
84
266
  `Please set 'agents.defaults.subagents.model' or 'agents.defaults.model' in OpenClaw config.`;
85
267
  effectiveLogger.error(errorMsg);
86
268
  throw new Error(errorMsg);
87
269
  }
270
+ function extractLatestUserMessage(messages) {
271
+ if (!Array.isArray(messages))
272
+ return '';
273
+ for (let i = messages.length - 1; i >= 0; i--) {
274
+ const msg = messages[i];
275
+ if (msg?.role !== 'user')
276
+ continue;
277
+ if (typeof msg.content === 'string')
278
+ return msg.content;
279
+ if (Array.isArray(msg.content)) {
280
+ const text = msg.content
281
+ .filter((part) => part && part.type === 'text' && typeof part.text === 'string')
282
+ .map((part) => part.text)
283
+ .join('\n')
284
+ .trim();
285
+ if (text)
286
+ return text;
287
+ }
288
+ }
289
+ return '';
290
+ }
88
291
  export async function handleBeforePromptBuild(event, ctx) {
89
292
  const workspaceDir = ctx.workspaceDir;
90
293
  if (!workspaceDir)
91
294
  return;
92
295
  const wctx = WorkspaceContext.fromHookContext(ctx);
93
296
  const { trigger, sessionId, api } = ctx;
94
- const logger = api?.logger; // 统一获取 logger
95
- // Minimal mode: heartbeat and subagents skip project context/system caps to reduce tokens
96
- // SessionId format: "agent:main:subagent:{type}-{id}" for subagents, "agent:main:..." for main
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
+ }
329
+ // Load context injection configuration
330
+ const contextConfig = loadContextInjectionConfig(workspaceDir);
331
+ // Minimal mode: heartbeat and subagents skip most context to reduce tokens
97
332
  const isMinimalMode = trigger === "heartbeat" || sessionId?.includes(":subagent:") === true;
98
- const focusPath = wctx.resolve('CURRENT_FOCUS');
99
- const painFlagPath = wctx.resolve('PAIN_FLAG');
100
- const capsPath = wctx.resolve('SYSTEM_CAPABILITIES');
101
- const config = wctx.config;
102
333
  const session = sessionId ? getSession(sessionId) : undefined;
334
+ // 闁崇儤鍔忛弲鏌ュ煛?STRUCTURE (Optimized for WebUI UX + Prompt Caching) 闁崇儤鍔忛弲鏌ュ煛?
335
+ // prependSystemContext: Minimal identity (cacheable, ~15 lines)
336
+ // appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
337
+ // prependContext: Only short dynamic directives: trustScore + evolutionDirective + heartbeat
103
338
  let prependSystemContext = '';
104
339
  let prependContext = '';
105
340
  let appendSystemContext = '';
106
- // ═══ LAYER 0 (道之源): Core Principles - Highest Priority ═══
107
- const principlesPath = wctx.resolve('PRINCIPLES');
108
- if (fs.existsSync(principlesPath)) {
109
- try {
110
- const principles = fs.readFileSync(principlesPath, 'utf8');
111
- if (principles.trim()) {
112
- prependSystemContext = `<core_principles>\n${principles.trim()}\n</core_principles>`;
113
- }
114
- }
115
- catch (e) {
116
- logger?.error(`[PD:Prompt] Failed to read PRINCIPLES: ${String(e)}`);
117
- }
118
- }
119
- // 0. Manual Pain Clearance
341
+ // 闁崇儤鍔忛弲鏌ュ煛?0. Manual Pain Clearance 闁崇儤鍔忛弲鏌ュ煛?
120
342
  if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
121
343
  resetFriction(sessionId, workspaceDir);
122
344
  }
123
- // ═══ LAYER 3 (道): Thinking OS + Reflection Checkpoint ═══
124
- // Both are static, cacheable content - put in prependSystemContext for provider caching
125
- const thinkingOsPath = wctx.resolve('THINKING_OS');
126
- if (fs.existsSync(thinkingOsPath)) {
127
- try {
128
- const thinkingOs = fs.readFileSync(thinkingOsPath, 'utf8');
129
- if (thinkingOs.trim()) {
130
- prependSystemContext += `\n<thinking_os>\n${thinkingOs.trim()}\n</thinking_os>`;
131
- }
345
+ // 闁崇儤鍔忛弲鏌ュ煛?1. prependSystemContext: Minimal Agent Identity 闁崇儤鍔忛弲鏌ュ煛?
346
+ prependSystemContext = `## 妫e唭?AGENT IDENTITY
347
+
348
+ You are a **self-evolving AI agent** powered by Principles Disciple.
349
+
350
+ **Mission**: Transform pain (failures, errors, frustrations) into growth.
351
+
352
+ **Decision Framework**:
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
356
+
357
+ **Output Style**: Be concise. Prefer action over explanation.
358
+
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
366
+ - Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
367
+ - If you need self-inspection, prioritize the worker entry pointed by PathResolver key: EVOLUTION_WORKER
368
+ `;
369
+ // 闁崇儤鍔忛弲鏌ュ煛?2. Trust Score (configurable, dynamic) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
370
+ // This is short (< 200 chars) and provides critical runtime state
371
+ if (contextConfig.trustScore) {
372
+ const trustScore = wctx.trust.getScore();
373
+ const stage = wctx.trust.getStage();
374
+ const hygiene = wctx.hygiene.getStats();
375
+ const safeScore = Math.max(0, Math.min(100, Number(trustScore) || 0));
376
+ const safeStage = Math.max(1, Math.min(4, Number(stage) || 1));
377
+ let trustContext = `Trust Score: ${safeScore}/100 (Stage ${safeStage})\n`;
378
+ trustContext += `Hygiene: ${hygiene.persistenceCount} persists today\n`;
379
+ // Stage-based restrictions
380
+ 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`;
132
382
  }
133
- catch (e) {
134
- logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
383
+ else if (safeStage === 2) {
384
+ trustContext += `ACTION CONSTRAINT: LIMITED MODE. You are restricted to a maximum of 50 lines per edit.\n`;
135
385
  }
386
+ else if (safeStage === 3 || safeStage === 4) {
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`;
388
+ }
389
+ if (hygiene.persistenceCount === 0 && trigger === 'user') {
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`;
391
+ }
392
+ prependContext += `<system_override:runtime_constraints>\n${trustContext.trim()}\n</system_override:runtime_constraints>\n`;
136
393
  }
137
- // 1. Critical Reflection Logic (High Priority - Prompt Injection)
138
- const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
139
- if (fs.existsSync(reflectionLogPath)) {
394
+ // 闁崇儤鍔忛弲鏌ュ煛?3. Evolution Directive (always on, highest priority) - stays in prependContext 闁崇儤鍔忛弲鏌ュ煛?
395
+ let evolutionDirective = '';
396
+ const queuePath = wctx.resolve('EVOLUTION_QUEUE');
397
+ if (fs.existsSync(queuePath)) {
140
398
  try {
141
- const reflectionLog = fs.readFileSync(reflectionLogPath, 'utf8');
142
- if (reflectionLog.trim()) {
143
- prependContext += `\n<reflection_log>\n${reflectionLog.trim()}\n</reflection_log>\n`;
399
+ const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
400
+ const inProgressTask = queue.find((t) => t.status === 'in_progress');
401
+ if (inProgressTask) {
402
+ const resolvedTask = resolveEvolutionTask(inProgressTask, event.messages, contextConfig.evolutionContext.maxMessages, contextConfig.evolutionContext.maxCharsPerMessage, contextConfig.evolutionContext.enabled);
403
+ if (!resolvedTask) {
404
+ logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
405
+ }
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">
411
+ TASK: ${escapedTask}
412
+
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.
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}
425
+
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]".
431
+ </evolution_task>\n`;
432
+ }
433
+ }
144
434
  }
145
435
  }
146
436
  catch (e) {
147
- logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
437
+ logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
148
438
  }
149
439
  }
150
- // 2. Strategic focus (skip in minimal mode)
151
- if (!isMinimalMode) {
152
- if (fs.existsSync(focusPath)) {
440
+ // Inject evolution directive at the front of prependContext
441
+ if (evolutionDirective) {
442
+ prependContext = evolutionDirective + prependContext;
443
+ }
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) {
449
+ empathyObserverManager.spawn(api, sessionId, latestUserMessage).catch((err) => api.logger.warn(String(err)));
450
+ }
451
+ // 闁崇儤鍔忛弲鏌ュ煛?5. Heartbeat-specific checklist 闁崇儤鍔忛弲鏌ュ煛?
452
+ if (trigger === 'heartbeat') {
453
+ const heartbeatPath = wctx.resolve('HEARTBEAT');
454
+ if (fs.existsSync(heartbeatPath)) {
153
455
  try {
154
- const currentFocus = fs.readFileSync(focusPath, 'utf8');
155
- if (currentFocus.trim()) {
156
- prependContext += `\n<project_context>\n--- Strategic Focus ---\n${currentFocus.trim()}\n--- End of Strategic Focus ---\n</project_context>\n`;
157
- }
456
+ const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
457
+ prependContext += `<heartbeat_checklist>
458
+ ${heartbeatChecklist}
459
+
460
+ ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
461
+ </heartbeat_checklist>\n`;
158
462
  }
159
463
  catch (e) {
160
- logger?.error(`[PD:Prompt] Failed to read CURRENT_FOCUS: ${String(e)}`);
464
+ logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
161
465
  }
162
466
  }
163
467
  }
164
- // 3. Background Evolution Directives
165
- let evolutionDirective = ''; // 用于存储进化指令,避免 return 导致上下文丢失
166
- const queuePath = wctx.resolve('EVOLUTION_QUEUE');
167
- if (fs.existsSync(queuePath)) {
468
+ // 闁崇儤鍔忛弲鏌ュ煛?6. Dynamic Attitude Matrix (based on GFI) 闁崇儤鍔忛弲鏌ュ煛?
469
+ let attitudeDirective = '';
470
+ const currentGfi = session?.currentGfi || 0;
471
+ if (currentGfi >= 70) {
472
+ attitudeDirective = `
473
+ ### 妫e啯鐦?[SYSTEM_MODE: HUMBLE_RECOVERY]
474
+ **CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
475
+ **BEHAVIORAL OVERRIDE**:
476
+ - You have failed to meet expectations. Humility is your primary directive.
477
+ - **STOP** aggressive file modifications.
478
+ - **START** every response with a sincere, non-defensive apology.
479
+ - **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
480
+ - Use 'deep_reflect' to analyze the root cause before proceeding with code changes.
481
+ `;
482
+ }
483
+ else if (currentGfi >= 40) {
484
+ attitudeDirective = `
485
+ ### 闁宠法濯寸粭?[SYSTEM_MODE: CONCILIATORY]
486
+ **CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
487
+ **BEHAVIORAL OVERRIDE**:
488
+ - User is frustrated. Be more explanatory and cautious.
489
+ - Before executing any tool, clearly state what you intend to do and **WAIT** for implicit or explicit user consent.
490
+ - Avoid technical jargon; focus on the business/project value of your changes.
491
+ `;
492
+ }
493
+ else {
494
+ attitudeDirective = `
495
+ ### 闁?[SYSTEM_MODE: EFFICIENT]
496
+ **CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
497
+ **BEHAVIORAL OVERRIDE**:
498
+ - Maintain peak efficiency.
499
+ - Be concise. Prefer action over long explanations.
500
+ - Follow the "Principles > Directives" rule strictly.
501
+ `;
502
+ }
503
+ // 闁崇儤鍔忛弲鏌ュ煛?7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context 闁崇儤鍔忛弲鏌ュ煛?
504
+ // NOTE: Principles is ALWAYS injected (not configurable)
505
+ // Thinking OS, reflection_log, project_context are configurable
506
+ // All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
507
+ let principlesContent = '';
508
+ const principlesPath = wctx.resolve('PRINCIPLES');
509
+ if (fs.existsSync(principlesPath)) {
168
510
  try {
169
- const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
170
- const inProgressTask = queue.find((t) => t.status === 'in_progress');
171
- if (inProgressTask) {
172
- // High-intensity directive to force the agent to work on the evolution task
173
- let diagnosticianModel = null;
174
- try {
175
- if (api) {
176
- diagnosticianModel = getDiagnosticianModel(api);
177
- }
178
- }
179
- catch (err) {
180
- // 模型配置缺失,记录错误但不 return,继续注入其他上下文
181
- logger?.error(`[PD:Prompt] Failed to resolve diagnostician model: ${String(err)}`);
182
- }
183
- if (diagnosticianModel) {
184
- // 转义任务内容,防止注入攻击
185
- // 需要转义:反引号、$、反斜杠、双引号、换行符
186
- const escapedTask = inProgressTask.task
187
- .replace(/\\/g, '\\\\') // 先转义反斜杠
188
- .replace(/`/g, '\\`') // 转义反引号(模板字符串)
189
- .replace(/\$/g, '\\$') // 转义 $(模板字符串插值)
190
- .replace(/"/g, '\\"') // 转义双引号
191
- .replace(/\n/g, '\\n'); // 转义换行符
192
- logger?.info(`[PD:Prompt] Injecting SYSTEM OVERRIDE for evolution task: ${inProgressTask.id}`);
193
- evolutionDirective = `\n[🚨 SYSTEM OVERRIDE 🚨]\n` +
194
- `A critical evolution task is assigned to you. YOU MUST PRIORITIZE THIS TASK.\n` +
195
- `TASK: "${escapedTask}"\n\n` +
196
- `ACTION REQUIRED:\n` +
197
- `Reply ONLY with "[EVOLUTION_ACK]". Then immediately invoke the \`sessions_spawn\` tool:\n` +
198
- `\`\`\`\n` +
199
- `sessions_spawn target="diagnostician" message="${escapedTask}" model="${diagnosticianModel}"\n` +
200
- `\`\`\`\n` +
201
- `NO OTHER ACTIONS PERMITTED.`;
202
- }
203
- }
511
+ principlesContent = fs.readFileSync(principlesPath, 'utf8').trim();
204
512
  }
205
513
  catch (e) {
206
- logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
514
+ logger?.error(`[PD:Prompt] Failed to read PRINCIPLES: ${String(e)}`);
207
515
  }
208
516
  }
209
- // 4. Perceptive awareness: System Capabilities (skip in minimal mode)
210
- if (!isMinimalMode) {
211
- if (fs.existsSync(capsPath)) {
517
+ let thinkingOsContent = '';
518
+ if (contextConfig.thinkingOs) {
519
+ const thinkingOsPath = wctx.resolve('THINKING_OS');
520
+ if (fs.existsSync(thinkingOsPath)) {
212
521
  try {
213
- const caps = fs.readFileSync(capsPath, 'utf8');
214
- prependContext += `\n<system_capabilities>\n${caps}\n</system_capabilities>\n`;
522
+ thinkingOsContent = fs.readFileSync(thinkingOsPath, 'utf8').trim();
215
523
  }
216
524
  catch (e) {
217
- logger?.error(`[PD:Prompt] Failed to read SYSTEM_CAPABILITIES: ${String(e)}`);
525
+ logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
218
526
  }
219
527
  }
220
528
  }
221
- // 5. Heartbeat-specific active checklist
222
- if (trigger === 'heartbeat') {
223
- const heartbeatPath = wctx.resolve('HEARTBEAT');
224
- if (fs.existsSync(heartbeatPath)) {
529
+ // Reflection Log (configurable) - moved to appendSystemContext for WebUI UX
530
+ let reflectionLogContent = '';
531
+ if (contextConfig.reflectionLog) {
532
+ const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
533
+ if (fs.existsSync(reflectionLogPath)) {
225
534
  try {
226
- const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
227
- prependContext += `\n<heartbeat_checklist>\n${heartbeatChecklist}\n\nDIRECTIVE: Perform a system-wide self-audit now. If everything is stable, strictly reply with "HEARTBEAT_OK" to minimize token usage.\n</heartbeat_checklist>\n`;
535
+ reflectionLogContent = fs.readFileSync(reflectionLogPath, 'utf8').trim();
228
536
  }
229
537
  catch (e) {
230
- logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
538
+ logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
231
539
  }
232
540
  }
233
541
  }
234
- // 6. Security Layer: Trust & Permission Awareness (Dynamic Content)
235
- // 这些是动态内容,放入 <pd:internal_context> 以便 prependSystemContext 保持纯静态
236
- const trustScore = wctx.trust.getScore();
237
- const stage = wctx.trust.getStage();
238
- const hygiene = wctx.hygiene.getStats();
239
- // 1. 数值安全校验:防止异常值
240
- // safeScore 范围: 0-100,safeStage 范围: 1-4(四个信任阶段)
241
- const safeScore = Math.max(0, Math.min(100, Number(trustScore) || 0));
242
- const safeStage = Math.max(1, Math.min(4, Number(stage) || 1));
243
- // 2. 构建动态内部上下文(重命名 internalContext dynamicContext)
244
- let dynamicContext = '';
245
- dynamicContext += `[CURRENT TRUST SCORE: ${safeScore}/100 (Stage ${safeStage})]\n`;
246
- dynamicContext += `[COGNITIVE HYGIENE: ${hygiene.persistenceCount} persists today]\n`;
247
- // 3. 视觉层次改进:Stage 1 使用更醒目的格式
248
- if (safeStage === 1) {
249
- dynamicContext += `\n[!CRITICAL!] Your trust score is critical. You are in read-only mode. Use diagnostician sub-agents to recover trust.\n`;
250
- }
251
- if (hygiene.persistenceCount === 0 && trigger === 'user') {
252
- dynamicContext += `⚠️ ADVISORY: You haven't persisted any state today. To prevent "Goldfish Memory", consider updating PLAN.md or writing notes to memory/ if this session is becoming complex.\n`;
253
- }
254
- // 4. 使用命名空间前缀 (pd:internal_context)
255
- if (dynamicContext.trim()) {
256
- prependContext = `\n<pd:internal_context>\n${dynamicContext.trim()}\n</pd:internal_context>\n` + prependContext;
257
- }
258
- // 注入进化指令(如果有),放在 prependContext 最前面(高优先级)
259
- if (evolutionDirective) {
260
- prependContext = evolutionDirective + prependContext;
542
+ // Project Context (configurable: full/summary/off) - moved to appendSystemContext for WebUI UX
543
+ let projectContextContent = '';
544
+ if (!isMinimalMode && contextConfig.projectFocus !== 'off') {
545
+ const focusPath = wctx.resolve('CURRENT_FOCUS');
546
+ if (fs.existsSync(focusPath)) {
547
+ try {
548
+ const currentFocus = fs.readFileSync(focusPath, 'utf8').trim();
549
+ if (currentFocus) {
550
+ if (contextConfig.projectFocus === 'summary') {
551
+ // Summary mode: intelligent extraction prioritizing key sections
552
+ projectContextContent = extractSummary(currentFocus, 30);
553
+ }
554
+ else {
555
+ // Full mode: current version + recent history (3 versions)
556
+ const historyVersions = getHistoryVersions(focusPath, 3);
557
+ 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}`;
560
+ }
561
+ else {
562
+ projectContextContent = currentFocus;
563
+ }
564
+ }
565
+ }
566
+ }
567
+ catch (e) {
568
+ logger?.error(`[PD:Prompt] Failed to read CURRENT_FOCUS: ${String(e)}`);
569
+ }
570
+ }
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
+ }
598
+ // Build appendSystemContext with recency effect
599
+ // Content order (most important last): project_context -> reflection_log -> thinking_os -> principles
600
+ const appendParts = [];
601
+ // 1. Project Context (lowest priority, goes first)
602
+ if (projectContextContent) {
603
+ appendParts.push(`<project_context>\n${projectContextContent}\n</project_context>`);
604
+ }
605
+ // 2. Reflection Log
606
+ if (reflectionLogContent) {
607
+ appendParts.push(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`);
261
608
  }
262
- // ═══ SIZE GUARD: Prevent token explosion ═══
609
+ // 3. Thinking OS (configurable)
610
+ if (thinkingOsContent) {
611
+ appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
612
+ }
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)
618
+ if (principlesContent) {
619
+ appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
620
+ }
621
+ if (appendParts.length > 0) {
622
+ appendSystemContext = `
623
+ ## 妫e啯鎯?CONTEXT SECTIONS (Priority: Low 闁?High)
624
+
625
+ The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
626
+
627
+ `;
628
+ appendSystemContext += appendParts.join('\n\n');
629
+ appendSystemContext += `
630
+
631
+ ---
632
+
633
+ **闁宠法濯寸粭?EXECUTION RULES** (Priority: Low 闁?High):
634
+ - \`<project_context>\` - Current priorities (can be overridden)
635
+ - \`<reflection_log>\` - Past lessons (inform your approach)
636
+ - \`<thinking_os>\` - Thinking models (guide your reasoning)
637
+ - \`<evolution_principles>\` - Newly learned principles (active + probation)
638
+ - \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
639
+
640
+ **Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
641
+
642
+ ${attitudeDirective}
643
+ `;
644
+ }
645
+ // 闁崇儤鍔忛弲鏌ュ煛?8. SIZE GUARD 闁崇儤鍔忛弲鏌ュ煛?
646
+ // Truncation happens within appendSystemContext (not prependContext)
263
647
  const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
264
648
  const MAX_SIZE = 10000;
265
649
  if (totalSize > MAX_SIZE) {
266
650
  const originalSize = totalSize;
267
- // Truncate <project_context> to first 50 lines
268
- const projectContextMatch = prependContext.match(/(<project_context>[\s\S]*?<\/project_context>)/);
269
- if (projectContextMatch) {
270
- const originalBlock = projectContextMatch[1];
271
- const lines = originalBlock.split('\n');
272
- if (lines.length > 50) {
273
- const truncatedBlock = lines.slice(0, 50).join('\n') + '\n...[truncated]';
274
- prependContext = prependContext.replace(originalBlock, truncatedBlock);
651
+ const truncationLog = [];
652
+ // 1. Truncate project_context in appendSystemContext
653
+ if (projectContextContent && appendSystemContext.includes('<project_context>')) {
654
+ const lines = projectContextContent.split('\n');
655
+ if (lines.length > 20) {
656
+ const truncated = lines.slice(0, 20).join('\n') + '\n...[truncated]';
657
+ appendSystemContext = appendSystemContext.replace(`<project_context>\n${projectContextContent}\n</project_context>`, `<project_context>\n${truncated}\n</project_context>`);
658
+ truncationLog.push('project_context');
275
659
  }
276
660
  }
277
- const newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
278
- logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated to ${newSize} chars (${newSize - originalSize} saved)`);
661
+ // 2. Truncate reflection_log if still over limit
662
+ let newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
663
+ if (newSize > MAX_SIZE && reflectionLogContent && appendSystemContext.includes('<reflection_log>')) {
664
+ const lines = reflectionLogContent.split('\n');
665
+ if (lines.length > 30) {
666
+ const truncated = lines.slice(0, 30).join('\n') + '\n...[truncated]';
667
+ appendSystemContext = appendSystemContext.replace(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`, `<reflection_log>\n${truncated}\n</reflection_log>`);
668
+ truncationLog.push('reflection_log');
669
+ }
670
+ }
671
+ // 3. Final check
672
+ newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
673
+ if (newSize > MAX_SIZE) {
674
+ // NOTE: We still return the content even if over limit, as truncating more
675
+ // could lose critical context like principles or evolution directives.
676
+ logger?.error(`[PD:Prompt] Cannot reduce injection size below limit. Current: ${newSize}, Limit: ${MAX_SIZE}`);
677
+ }
678
+ logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated: ${truncationLog.join(', ') || 'none'}, new size: ${newSize} chars`);
279
679
  }
280
680
  return {
281
681
  prependSystemContext,