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.
- package/dist/commands/context.js +7 -3
- 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.js +9 -6
- package/dist/commands/pain.js +8 -0
- package/dist/commands/principle-rollback.d.ts +4 -0
- package/dist/commands/principle-rollback.js +22 -0
- package/dist/commands/samples.d.ts +2 -0
- package/dist/commands/samples.js +55 -0
- package/dist/core/config.d.ts +5 -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 +2 -1
- package/dist/core/event-log.js +3 -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/path-resolver.js +75 -36
- 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 +117 -48
- package/dist/hooks/llm.js +114 -69
- package/dist/hooks/pain.js +105 -5
- package/dist/hooks/prompt.d.ts +11 -14
- package/dist/hooks/prompt.js +283 -57
- package/dist/hooks/subagent.js +27 -1
- package/dist/http/principles-console-route.d.ts +2 -0
- package/dist/http/principles-console-route.js +257 -0
- package/dist/i18n/commands.js +16 -0
- package/dist/index.js +83 -4
- package/dist/service/control-ui-query-service.d.ts +217 -0
- package/dist/service/control-ui-query-service.js +537 -0
- package/dist/service/evolution-worker.d.ts +9 -0
- package/dist/service/evolution-worker.js +152 -22
- 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 +210 -121
- package/dist/types/event-types.d.ts +9 -2
- package/dist/types.d.ts +10 -0
- package/dist/types.js +5 -0
- package/openclaw.plugin.json +43 -11
- package/package.json +14 -4
- package/templates/langs/zh/skills/pd-daily/SKILL.md +97 -13
package/dist/hooks/prompt.js
CHANGED
|
@@ -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
|
-
|
|
9
|
+
/**
|
|
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) {
|
|
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
|
-
|
|
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
|
-
*
|
|
158
|
+
* 濡ょ姴鐭侀惁澶娢熼垾宕団偓椋庘偓娑欘殘椤戜焦绋夐崣澶屽鐎殿喖绻戝Σ鎼佸触閿旇儻绀?"provider/model"
|
|
26
159
|
*/
|
|
27
160
|
function isValidModelFormat(model) {
|
|
28
|
-
//
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @internal
|
|
238
|
+
* 闁兼儳鍢茶ぐ鍥╂嫚婵犲啯鐒介悗娑欏姈濞呫倝鎳楅幋鎺旂Ъ閹煎瓨鏌ф繛鍥偨閵娧勭暠婵☆垪鈧磭鈧?
|
|
239
|
+
* 濞村吋锚閸樻稓鐥缁辩殜ubagents.model > 濞戞挾绮啯闁?
|
|
240
|
+
* 濠碘€冲€归悘澶愭焾閼恒儳姊鹃柡鍫濐樀閸樸倗绱旈鍡欑闁硅埖绋戦崵顓㈡煥濞嗘帩鍤?
|
|
241
|
+
* @internal 閻庣數鍘ч崵顓熺閸涱剛杩旀繛鏉戭儓閻︻垱鎷呯捄銊︽殢
|
|
101
242
|
*/
|
|
102
243
|
export function getDiagnosticianModel(api, logger) {
|
|
103
|
-
//
|
|
104
|
-
// 1.
|
|
105
|
-
// 2.
|
|
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
|
-
//
|
|
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
|
-
//
|
|
341
|
+
// 闁崇儤鍔忛弲鏌ュ煛?0. Manual Pain Clearance 闁崇儤鍔忛弲鏌ュ煛?
|
|
170
342
|
if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
|
|
171
343
|
resetFriction(sessionId, workspaceDir);
|
|
172
344
|
}
|
|
173
|
-
//
|
|
174
|
-
prependSystemContext = `##
|
|
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
|
|
182
|
-
2. Principles Override
|
|
183
|
-
3. Learn from Pain
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
409
|
+
if (trigger === 'user') {
|
|
410
|
+
evolutionDirective = `<evolution_task priority="high">
|
|
232
411
|
TASK: ${escapedTask}
|
|
233
412
|
|
|
234
413
|
REQUIRED ACTION:
|
|
235
|
-
|
|
236
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
252
|
-
if
|
|
253
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
###
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
//
|
|
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;
|
package/dist/hooks/subagent.js
CHANGED
|
@@ -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
|
|
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') {
|