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.
- package/dist/commands/context.d.ts +5 -0
- package/dist/commands/context.js +312 -0
- package/dist/commands/evolution-status.d.ts +4 -0
- package/dist/commands/evolution-status.js +138 -0
- package/dist/commands/export.d.ts +2 -0
- package/dist/commands/export.js +45 -0
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +582 -0
- package/dist/commands/pain.js +143 -6
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/rollback.d.ts +19 -0
- package/dist/commands/rollback.js +119 -0
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/core/config.d.ts +37 -0
- package/dist/core/config.js +47 -0
- package/dist/core/control-ui-db.d.ts +68 -0
- package/dist/core/control-ui-db.js +274 -0
- package/dist/core/detection-funnel.d.ts +1 -1
- package/dist/core/detection-funnel.js +4 -0
- package/dist/core/dictionary.d.ts +2 -0
- package/dist/core/dictionary.js +13 -0
- package/dist/core/event-log.d.ts +22 -1
- package/dist/core/event-log.js +319 -0
- package/dist/core/evolution-engine.d.ts +5 -5
- package/dist/core/evolution-engine.js +18 -18
- package/dist/core/evolution-migration.d.ts +5 -0
- package/dist/core/evolution-migration.js +65 -0
- package/dist/core/evolution-reducer.d.ts +69 -0
- package/dist/core/evolution-reducer.js +369 -0
- package/dist/core/evolution-types.d.ts +103 -0
- package/dist/core/focus-history.d.ts +65 -0
- package/dist/core/focus-history.js +266 -0
- package/dist/core/init.js +30 -7
- package/dist/core/migration.js +0 -2
- package/dist/core/path-resolver.d.ts +3 -0
- package/dist/core/path-resolver.js +90 -31
- package/dist/core/paths.d.ts +7 -8
- package/dist/core/paths.js +48 -40
- package/dist/core/profile.js +1 -1
- package/dist/core/session-tracker.d.ts +4 -0
- package/dist/core/session-tracker.js +15 -0
- package/dist/core/thinking-models.d.ts +38 -0
- package/dist/core/thinking-models.js +170 -0
- package/dist/core/trajectory.d.ts +184 -0
- package/dist/core/trajectory.js +817 -0
- package/dist/core/trust-engine.d.ts +2 -0
- package/dist/core/trust-engine.js +30 -4
- package/dist/core/workspace-context.d.ts +13 -0
- package/dist/core/workspace-context.js +50 -7
- package/dist/hooks/gate.js +301 -30
- package/dist/hooks/llm.d.ts +8 -0
- package/dist/hooks/llm.js +347 -69
- package/dist/hooks/message-sanitize.d.ts +3 -0
- package/dist/hooks/message-sanitize.js +37 -0
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +20 -11
- package/dist/hooks/prompt.js +558 -158
- package/dist/hooks/subagent.d.ts +9 -2
- package/dist/hooks/subagent.js +40 -3
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +48 -20
- package/dist/index.js +264 -8
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/empathy-observer-manager.d.ts +42 -0
- package/dist/service/empathy-observer-manager.js +147 -0
- package/dist/service/evolution-worker.d.ts +10 -0
- package/dist/service/evolution-worker.js +156 -24
- package/dist/service/trajectory-service.d.ts +2 -0
- package/dist/service/trajectory-service.js +15 -0
- package/dist/tools/agent-spawn.d.ts +27 -6
- package/dist/tools/agent-spawn.js +339 -87
- package/dist/tools/deep-reflect.d.ts +27 -7
- package/dist/tools/deep-reflect.js +282 -113
- package/dist/types/event-types.d.ts +84 -2
- package/dist/types/event-types.js +33 -0
- package/dist/types.d.ts +52 -0
- package/dist/types.js +24 -1
- package/openclaw.plugin.json +43 -11
- package/package.json +16 -6
- package/templates/langs/zh/core/HEARTBEAT.md +28 -4
- package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
- package/templates/pain_settings.json +54 -2
- package/templates/workspace/.principles/PROFILE.json +2 -0
- package/templates/workspace/okr/CURRENT_FOCUS.md +57 -0
package/dist/hooks/prompt.js
CHANGED
|
@@ -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
|
-
*
|
|
10
|
+
* OpenClaw API 闁规亽鍎辫ぐ娑氣偓瑙勭煯缁犵喖鏁嶉崷顧竜mpt Hook 闁圭鍋撻梻鍥e亾闂侇喓鍔岄崹搴ㄦ晬?
|
|
11
|
+
*/
|
|
12
|
+
function escapeXml(input) {
|
|
13
|
+
return input
|
|
14
|
+
.replace(/&/g, '&')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.replace(/"/g, '"')
|
|
18
|
+
.replace(/'/g, ''');
|
|
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
|
-
//
|
|
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
|
-
*
|
|
16
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
58
|
-
*
|
|
59
|
-
|
|
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.
|
|
64
|
-
// 2.
|
|
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;
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
134
|
-
|
|
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
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
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
|
|
437
|
+
logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
|
|
148
438
|
}
|
|
149
439
|
}
|
|
150
|
-
//
|
|
151
|
-
if (
|
|
152
|
-
|
|
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
|
|
155
|
-
|
|
156
|
-
|
|
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
|
|
464
|
+
logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
|
|
161
465
|
}
|
|
162
466
|
}
|
|
163
467
|
}
|
|
164
|
-
//
|
|
165
|
-
let
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
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
|
-
|
|
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
|
|
514
|
+
logger?.error(`[PD:Prompt] Failed to read PRINCIPLES: ${String(e)}`);
|
|
207
515
|
}
|
|
208
516
|
}
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
517
|
+
let thinkingOsContent = '';
|
|
518
|
+
if (contextConfig.thinkingOs) {
|
|
519
|
+
const thinkingOsPath = wctx.resolve('THINKING_OS');
|
|
520
|
+
if (fs.existsSync(thinkingOsPath)) {
|
|
212
521
|
try {
|
|
213
|
-
|
|
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
|
|
525
|
+
logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
|
|
218
526
|
}
|
|
219
527
|
}
|
|
220
528
|
}
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
-
|
|
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
|
|
538
|
+
logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
|
|
231
539
|
}
|
|
232
540
|
}
|
|
233
541
|
}
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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,
|