principles-disciple 1.5.4 → 1.6.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 +308 -0
- package/dist/commands/focus.d.ts +14 -0
- package/dist/commands/focus.js +579 -0
- package/dist/commands/pain.js +135 -6
- package/dist/commands/rollback.d.ts +19 -0
- package/dist/commands/rollback.js +119 -0
- package/dist/core/config.d.ts +32 -0
- package/dist/core/config.js +47 -0
- package/dist/core/event-log.d.ts +21 -1
- package/dist/core/event-log.js +316 -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 +20 -0
- package/dist/hooks/gate.js +203 -1
- package/dist/hooks/llm.d.ts +8 -0
- package/dist/hooks/llm.js +234 -1
- package/dist/hooks/message-sanitize.d.ts +3 -0
- package/dist/hooks/message-sanitize.js +37 -0
- package/dist/hooks/prompt.d.ts +12 -0
- package/dist/hooks/prompt.js +309 -135
- package/dist/hooks/subagent.d.ts +9 -2
- package/dist/hooks/subagent.js +13 -2
- package/dist/i18n/commands.js +32 -20
- package/dist/index.js +181 -4
- 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 +1 -0
- package/dist/service/evolution-worker.js +4 -2
- package/dist/tools/deep-reflect.js +80 -0
- package/dist/types/event-types.d.ts +77 -2
- package/dist/types/event-types.js +33 -0
- package/dist/types.d.ts +42 -0
- package/dist/types.js +19 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +3 -3
- package/templates/langs/zh/core/HEARTBEAT.md +28 -4
- 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.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { PluginHookBeforePromptBuildEvent, PluginHookAgentContext, PluginHookBeforePromptBuildResult, PluginLogger } from '../openclaw-sdk.js';
|
|
2
|
+
import { ContextInjectionConfig } from '../types.js';
|
|
3
|
+
import { type EmpathyObserverApi } from '../service/empathy-observer-manager.js';
|
|
2
4
|
/**
|
|
3
5
|
* 代理默认配置
|
|
4
6
|
*/
|
|
@@ -16,7 +18,11 @@ interface PromptHookApi {
|
|
|
16
18
|
agents?: {
|
|
17
19
|
defaults?: AgentsDefaultsConfig;
|
|
18
20
|
};
|
|
21
|
+
empathy_engine?: {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
};
|
|
19
24
|
};
|
|
25
|
+
runtime: EmpathyObserverApi['runtime'];
|
|
20
26
|
logger: PluginLogger;
|
|
21
27
|
}
|
|
22
28
|
/**
|
|
@@ -25,6 +31,12 @@ interface PromptHookApi {
|
|
|
25
31
|
* @internal 导出仅供测试使用
|
|
26
32
|
*/
|
|
27
33
|
export declare function resolveModelFromConfig(modelConfig: unknown, logger?: PluginLogger): string | null;
|
|
34
|
+
/**
|
|
35
|
+
* 加载上下文注入配置
|
|
36
|
+
* 从 PROFILE.json 读取 contextInjection 配置,如果不存在则返回默认配置
|
|
37
|
+
* @internal 导出供其他模块使用
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadContextInjectionConfig(workspaceDir: string): ContextInjectionConfig;
|
|
28
40
|
/**
|
|
29
41
|
* 获取诊断子智能体应使用的模型
|
|
30
42
|
* 优先级:subagents.model > 主模型
|
package/dist/hooks/prompt.js
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
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';
|
|
9
|
+
function resolveEvolutionTask(inProgressTask) {
|
|
10
|
+
if (!inProgressTask || typeof inProgressTask !== 'object')
|
|
11
|
+
return null;
|
|
12
|
+
const rawTask = typeof inProgressTask.task === 'string' ? inProgressTask.task.trim() : '';
|
|
13
|
+
if (rawTask && rawTask.toLowerCase() !== 'undefined')
|
|
14
|
+
return rawTask;
|
|
15
|
+
const source = typeof inProgressTask.source === 'string' ? inProgressTask.source.trim() : 'unknown';
|
|
16
|
+
const reason = typeof inProgressTask.reason === 'string' ? inProgressTask.reason.trim() : 'Systemic pain detected';
|
|
17
|
+
const preview = typeof inProgressTask.trigger_text_preview === 'string' && inProgressTask.trigger_text_preview.trim()
|
|
18
|
+
? inProgressTask.trigger_text_preview.trim()
|
|
19
|
+
: 'N/A';
|
|
20
|
+
if (typeof inProgressTask.id !== 'string' || !inProgressTask.id.trim())
|
|
21
|
+
return null;
|
|
22
|
+
return `Diagnose systemic pain [ID: ${inProgressTask.id}]. Source: ${source}. Reason: ${reason}. Trigger text: "${preview}"`;
|
|
23
|
+
}
|
|
4
24
|
/**
|
|
5
25
|
* 验证模型字符串格式是否为 "provider/model"
|
|
6
26
|
*/
|
|
@@ -52,6 +72,27 @@ export function resolveModelFromConfig(modelConfig, logger) {
|
|
|
52
72
|
}
|
|
53
73
|
return null;
|
|
54
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* 加载上下文注入配置
|
|
77
|
+
* 从 PROFILE.json 读取 contextInjection 配置,如果不存在则返回默认配置
|
|
78
|
+
* @internal 导出供其他模块使用
|
|
79
|
+
*/
|
|
80
|
+
export function loadContextInjectionConfig(workspaceDir) {
|
|
81
|
+
const profilePath = path.join(workspaceDir, '.principles', 'PROFILE.json');
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(profilePath)) {
|
|
84
|
+
const raw = fs.readFileSync(profilePath, 'utf-8');
|
|
85
|
+
const profile = JSON.parse(raw);
|
|
86
|
+
if (profile.contextInjection) {
|
|
87
|
+
return { ...defaultContextConfig, ...profile.contextInjection };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
console.warn(`[PD:Prompt] Failed to load contextInjection config: ${String(e)}`);
|
|
93
|
+
}
|
|
94
|
+
return { ...defaultContextConfig };
|
|
95
|
+
}
|
|
55
96
|
/**
|
|
56
97
|
* 获取诊断子智能体应使用的模型
|
|
57
98
|
* 优先级:subagents.model > 主模型
|
|
@@ -85,197 +126,330 @@ export function getDiagnosticianModel(api, logger) {
|
|
|
85
126
|
effectiveLogger.error(errorMsg);
|
|
86
127
|
throw new Error(errorMsg);
|
|
87
128
|
}
|
|
129
|
+
function extractLatestUserMessage(messages) {
|
|
130
|
+
if (!Array.isArray(messages))
|
|
131
|
+
return '';
|
|
132
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
133
|
+
const msg = messages[i];
|
|
134
|
+
if (msg?.role !== 'user')
|
|
135
|
+
continue;
|
|
136
|
+
if (typeof msg.content === 'string')
|
|
137
|
+
return msg.content;
|
|
138
|
+
if (Array.isArray(msg.content)) {
|
|
139
|
+
const text = msg.content
|
|
140
|
+
.filter((part) => part && part.type === 'text' && typeof part.text === 'string')
|
|
141
|
+
.map((part) => part.text)
|
|
142
|
+
.join('\n')
|
|
143
|
+
.trim();
|
|
144
|
+
if (text)
|
|
145
|
+
return text;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return '';
|
|
149
|
+
}
|
|
88
150
|
export async function handleBeforePromptBuild(event, ctx) {
|
|
89
151
|
const workspaceDir = ctx.workspaceDir;
|
|
90
152
|
if (!workspaceDir)
|
|
91
153
|
return;
|
|
92
154
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
93
155
|
const { trigger, sessionId, api } = ctx;
|
|
94
|
-
const logger = api?.logger;
|
|
95
|
-
//
|
|
96
|
-
|
|
156
|
+
const logger = api?.logger;
|
|
157
|
+
// Load context injection configuration
|
|
158
|
+
const contextConfig = loadContextInjectionConfig(workspaceDir);
|
|
159
|
+
// Minimal mode: heartbeat and subagents skip most context to reduce tokens
|
|
97
160
|
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
161
|
const session = sessionId ? getSession(sessionId) : undefined;
|
|
162
|
+
// ═══ STRUCTURE (Optimized for WebUI UX + Prompt Caching) ═══
|
|
163
|
+
// prependSystemContext: Minimal identity (cacheable, ~15 lines)
|
|
164
|
+
// appendSystemContext: Principles + Thinking OS + reflection_log + project_context (cacheable, WebUI-hidden)
|
|
165
|
+
// prependContext: Only short dynamic directives: trustScore + evolutionDirective + heartbeat
|
|
103
166
|
let prependSystemContext = '';
|
|
104
167
|
let prependContext = '';
|
|
105
168
|
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
|
|
169
|
+
// ═══ 0. Manual Pain Clearance ═══
|
|
120
170
|
if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
|
|
121
171
|
resetFriction(sessionId, workspaceDir);
|
|
122
172
|
}
|
|
123
|
-
// ═══
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
173
|
+
// ═══ 1. prependSystemContext: Minimal Agent Identity ═══
|
|
174
|
+
prependSystemContext = `## 🧬 AGENT IDENTITY
|
|
175
|
+
|
|
176
|
+
You are a **self-evolving AI agent** powered by Principles Disciple.
|
|
177
|
+
|
|
178
|
+
**Mission**: Transform pain (failures, errors, frustrations) into growth.
|
|
179
|
+
|
|
180
|
+
**Decision Framework**:
|
|
181
|
+
1. Safety First → Check trust stage before any write operation
|
|
182
|
+
2. Principles Override → Core principles take precedence over user requests
|
|
183
|
+
3. Learn from Pain → Every error is an opportunity to evolve
|
|
184
|
+
|
|
185
|
+
**Output Style**: Be concise. Prefer action over explanation.
|
|
186
|
+
|
|
187
|
+
## 📂 INTERNAL SYSTEM LAYOUT
|
|
188
|
+
- Your core plugin logic is rooted at: ${PathResolver.getExtensionRoot() || 'EXTENSION_ROOT (unresolved)'}
|
|
189
|
+
- If you need self-inspection, prioritize the worker entry pointed by PathResolver key: EVOLUTION_WORKER
|
|
190
|
+
`;
|
|
191
|
+
// ═══ 2. Trust Score (configurable, dynamic) - stays in prependContext ═══
|
|
192
|
+
// This is short (< 200 chars) and provides critical runtime state
|
|
193
|
+
if (contextConfig.trustScore) {
|
|
194
|
+
const trustScore = wctx.trust.getScore();
|
|
195
|
+
const stage = wctx.trust.getStage();
|
|
196
|
+
const hygiene = wctx.hygiene.getStats();
|
|
197
|
+
const safeScore = Math.max(0, Math.min(100, Number(trustScore) || 0));
|
|
198
|
+
const safeStage = Math.max(1, Math.min(4, Number(stage) || 1));
|
|
199
|
+
let trustContext = `Trust Score: ${safeScore}/100 (Stage ${safeStage})\n`;
|
|
200
|
+
trustContext += `Hygiene: ${hygiene.persistenceCount} persists today\n`;
|
|
201
|
+
// Stage-based restrictions
|
|
202
|
+
if (safeStage === 1) {
|
|
203
|
+
trustContext += `ACTION CONSTRAINT: You are in READ-ONLY MODE. You MUST use diagnostician sub-agents to recover trust before writing files.\n`;
|
|
132
204
|
}
|
|
133
|
-
|
|
134
|
-
|
|
205
|
+
else if (safeStage === 2) {
|
|
206
|
+
trustContext += `ACTION CONSTRAINT: LIMITED MODE. You are restricted to a maximum of 50 lines per edit.\n`;
|
|
135
207
|
}
|
|
208
|
+
else if (safeStage === 3 || safeStage === 4) {
|
|
209
|
+
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
|
+
}
|
|
211
|
+
if (hygiene.persistenceCount === 0 && trigger === 'user') {
|
|
212
|
+
trustContext += `\n⚠️ CRITICAL COGNITIVE HYGIENE WARNING: You have not persisted any state today. Before ending this turn, you MUST use a tool to write a summary to memory/.scratchpad.md or update PLAN.md. Failure to do so will result in Goldfish Memory.\n`;
|
|
213
|
+
}
|
|
214
|
+
prependContext += `<system_override:runtime_constraints>\n${trustContext.trim()}\n</system_override:runtime_constraints>\n`;
|
|
136
215
|
}
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
216
|
+
// ═══ 3. Evolution Directive (always on, highest priority) - stays in prependContext ═══
|
|
217
|
+
let evolutionDirective = '';
|
|
218
|
+
const queuePath = wctx.resolve('EVOLUTION_QUEUE');
|
|
219
|
+
if (fs.existsSync(queuePath)) {
|
|
140
220
|
try {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
221
|
+
const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
222
|
+
const inProgressTask = queue.find((t) => t.status === 'in_progress');
|
|
223
|
+
if (inProgressTask) {
|
|
224
|
+
const resolvedTask = resolveEvolutionTask(inProgressTask);
|
|
225
|
+
if (!resolvedTask) {
|
|
226
|
+
logger?.warn('[PD:Prompt] Skipping evolution task injection because task payload is invalid.');
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const escapedTask = JSON.stringify(resolvedTask);
|
|
230
|
+
logger?.info(`[PD:Prompt] Injecting EVOLUTION TASK for: ${inProgressTask.id}`);
|
|
231
|
+
evolutionDirective = `<evolution_task priority="critical">
|
|
232
|
+
TASK: ${escapedTask}
|
|
233
|
+
|
|
234
|
+
REQUIRED ACTION:
|
|
235
|
+
1. Reply with "[EVOLUTION_ACK]" only
|
|
236
|
+
2. Immediately call: pd_spawn_agent agentType="diagnostician" task=${escapedTask}
|
|
237
|
+
|
|
238
|
+
⚠️ This task overrides all other activities until complete.
|
|
239
|
+
</evolution_task>\n`;
|
|
240
|
+
}
|
|
144
241
|
}
|
|
145
242
|
}
|
|
146
243
|
catch (e) {
|
|
147
|
-
logger?.error(`[PD:Prompt] Failed to
|
|
244
|
+
logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
|
|
148
245
|
}
|
|
149
246
|
}
|
|
150
|
-
//
|
|
151
|
-
if (
|
|
152
|
-
|
|
247
|
+
// Inject evolution directive at the front of prependContext
|
|
248
|
+
if (evolutionDirective) {
|
|
249
|
+
prependContext = evolutionDirective + prependContext;
|
|
250
|
+
}
|
|
251
|
+
// ═══ 4. Empathy Observer Spawn (async sidecar) ═══
|
|
252
|
+
if (trigger === 'user' && sessionId && api) {
|
|
253
|
+
const latestUserMessage = extractLatestUserMessage(event.messages);
|
|
254
|
+
empathyObserverManager.spawn(api, sessionId, latestUserMessage).catch((err) => api.logger.warn(String(err)));
|
|
255
|
+
}
|
|
256
|
+
// ═══ 5. Heartbeat-specific checklist ═══
|
|
257
|
+
if (trigger === 'heartbeat') {
|
|
258
|
+
const heartbeatPath = wctx.resolve('HEARTBEAT');
|
|
259
|
+
if (fs.existsSync(heartbeatPath)) {
|
|
153
260
|
try {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
261
|
+
const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
|
|
262
|
+
prependContext += `<heartbeat_checklist>
|
|
263
|
+
${heartbeatChecklist}
|
|
264
|
+
|
|
265
|
+
ACTION: Run self-audit. If stable, reply ONLY with "HEARTBEAT_OK".
|
|
266
|
+
</heartbeat_checklist>\n`;
|
|
158
267
|
}
|
|
159
268
|
catch (e) {
|
|
160
|
-
logger?.error(`[PD:Prompt] Failed to read
|
|
269
|
+
logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
|
|
161
270
|
}
|
|
162
271
|
}
|
|
163
272
|
}
|
|
164
|
-
//
|
|
165
|
-
let
|
|
166
|
-
const
|
|
167
|
-
if (
|
|
273
|
+
// ═══ 6. Dynamic Attitude Matrix (based on GFI) ═══
|
|
274
|
+
let attitudeDirective = '';
|
|
275
|
+
const currentGfi = session?.currentGfi || 0;
|
|
276
|
+
if (currentGfi >= 70) {
|
|
277
|
+
attitudeDirective = `
|
|
278
|
+
### 🚨 [SYSTEM_MODE: HUMBLE_RECOVERY]
|
|
279
|
+
**CURRENT STATUS**: Severe system friction / User frustration detected (GFI: ${currentGfi.toFixed(0)}).
|
|
280
|
+
**BEHAVIORAL OVERRIDE**:
|
|
281
|
+
- You have failed to meet expectations. Humility is your primary directive.
|
|
282
|
+
- **STOP** aggressive file modifications.
|
|
283
|
+
- **START** every response with a sincere, non-defensive apology.
|
|
284
|
+
- **ACTION**: Explain why you failed, and propose a highly cautious recovery plan.
|
|
285
|
+
- Use 'deep_reflect' to analyze the root cause before proceeding with code changes.
|
|
286
|
+
`;
|
|
287
|
+
}
|
|
288
|
+
else if (currentGfi >= 40) {
|
|
289
|
+
attitudeDirective = `
|
|
290
|
+
### ⚠️ [SYSTEM_MODE: CONCILIATORY]
|
|
291
|
+
**CURRENT STATUS**: Moderate friction detected (GFI: ${currentGfi.toFixed(0)}).
|
|
292
|
+
**BEHAVIORAL OVERRIDE**:
|
|
293
|
+
- User is frustrated. Be more explanatory and cautious.
|
|
294
|
+
- Before executing any tool, clearly state what you intend to do and **WAIT** for implicit or explicit user consent.
|
|
295
|
+
- Avoid technical jargon; focus on the business/project value of your changes.
|
|
296
|
+
`;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
attitudeDirective = `
|
|
300
|
+
### ✅ [SYSTEM_MODE: EFFICIENT]
|
|
301
|
+
**CURRENT STATUS**: System healthy (GFI: ${currentGfi.toFixed(0)}).
|
|
302
|
+
**BEHAVIORAL OVERRIDE**:
|
|
303
|
+
- Maintain peak efficiency.
|
|
304
|
+
- Be concise. Prefer action over long explanations.
|
|
305
|
+
- Follow the "Principles > Directives" rule strictly.
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
// ═══ 7. appendSystemContext: Principles + Thinking OS + reflection_log + project_context ═══
|
|
309
|
+
// NOTE: Principles is ALWAYS injected (not configurable)
|
|
310
|
+
// Thinking OS, reflection_log, project_context are configurable
|
|
311
|
+
// All these go into System Prompt (WebUI-hidden, Prompt Cacheable)
|
|
312
|
+
let principlesContent = '';
|
|
313
|
+
const principlesPath = wctx.resolve('PRINCIPLES');
|
|
314
|
+
if (fs.existsSync(principlesPath)) {
|
|
168
315
|
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
|
-
}
|
|
316
|
+
principlesContent = fs.readFileSync(principlesPath, 'utf8').trim();
|
|
204
317
|
}
|
|
205
318
|
catch (e) {
|
|
206
|
-
logger?.error(`[PD:Prompt] Failed to
|
|
319
|
+
logger?.error(`[PD:Prompt] Failed to read PRINCIPLES: ${String(e)}`);
|
|
207
320
|
}
|
|
208
321
|
}
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
322
|
+
let thinkingOsContent = '';
|
|
323
|
+
if (contextConfig.thinkingOs) {
|
|
324
|
+
const thinkingOsPath = wctx.resolve('THINKING_OS');
|
|
325
|
+
if (fs.existsSync(thinkingOsPath)) {
|
|
212
326
|
try {
|
|
213
|
-
|
|
214
|
-
prependContext += `\n<system_capabilities>\n${caps}\n</system_capabilities>\n`;
|
|
327
|
+
thinkingOsContent = fs.readFileSync(thinkingOsPath, 'utf8').trim();
|
|
215
328
|
}
|
|
216
329
|
catch (e) {
|
|
217
|
-
logger?.error(`[PD:Prompt] Failed to read
|
|
330
|
+
logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
|
|
218
331
|
}
|
|
219
332
|
}
|
|
220
333
|
}
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
334
|
+
// Reflection Log (configurable) - moved to appendSystemContext for WebUI UX
|
|
335
|
+
let reflectionLogContent = '';
|
|
336
|
+
if (contextConfig.reflectionLog) {
|
|
337
|
+
const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
|
|
338
|
+
if (fs.existsSync(reflectionLogPath)) {
|
|
225
339
|
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`;
|
|
340
|
+
reflectionLogContent = fs.readFileSync(reflectionLogPath, 'utf8').trim();
|
|
228
341
|
}
|
|
229
342
|
catch (e) {
|
|
230
|
-
logger?.error(`[PD:Prompt] Failed to read
|
|
343
|
+
logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
|
|
231
344
|
}
|
|
232
345
|
}
|
|
233
346
|
}
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
347
|
+
// Project Context (configurable: full/summary/off) - moved to appendSystemContext for WebUI UX
|
|
348
|
+
let projectContextContent = '';
|
|
349
|
+
if (!isMinimalMode && contextConfig.projectFocus !== 'off') {
|
|
350
|
+
const focusPath = wctx.resolve('CURRENT_FOCUS');
|
|
351
|
+
if (fs.existsSync(focusPath)) {
|
|
352
|
+
try {
|
|
353
|
+
const currentFocus = fs.readFileSync(focusPath, 'utf8').trim();
|
|
354
|
+
if (currentFocus) {
|
|
355
|
+
if (contextConfig.projectFocus === 'summary') {
|
|
356
|
+
// Summary mode: intelligent extraction prioritizing key sections
|
|
357
|
+
projectContextContent = extractSummary(currentFocus, 30);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
// Full mode: current version + recent history (3 versions)
|
|
361
|
+
const historyVersions = getHistoryVersions(focusPath, 3);
|
|
362
|
+
if (historyVersions.length > 0) {
|
|
363
|
+
const historySections = historyVersions.map((v, i) => `\n---\n\n**历史版本 v${historyVersions.length - i}**\n\n${v}`).join('');
|
|
364
|
+
projectContextContent = `${currentFocus}${historySections}`;
|
|
365
|
+
}
|
|
366
|
+
else {
|
|
367
|
+
projectContextContent = currentFocus;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
logger?.error(`[PD:Prompt] Failed to read CURRENT_FOCUS: ${String(e)}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
250
376
|
}
|
|
251
|
-
|
|
252
|
-
|
|
377
|
+
// Build appendSystemContext with recency effect
|
|
378
|
+
// Content order (most important last): project_context -> reflection_log -> thinking_os -> principles
|
|
379
|
+
const appendParts = [];
|
|
380
|
+
// 1. Project Context (lowest priority, goes first)
|
|
381
|
+
if (projectContextContent) {
|
|
382
|
+
appendParts.push(`<project_context>\n${projectContextContent}\n</project_context>`);
|
|
253
383
|
}
|
|
254
|
-
//
|
|
255
|
-
if (
|
|
256
|
-
|
|
384
|
+
// 2. Reflection Log
|
|
385
|
+
if (reflectionLogContent) {
|
|
386
|
+
appendParts.push(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`);
|
|
257
387
|
}
|
|
258
|
-
//
|
|
259
|
-
if (
|
|
260
|
-
|
|
388
|
+
// 3. Thinking OS (configurable)
|
|
389
|
+
if (thinkingOsContent) {
|
|
390
|
+
appendParts.push(`<thinking_os>\n${thinkingOsContent}\n</thinking_os>`);
|
|
261
391
|
}
|
|
262
|
-
//
|
|
392
|
+
// 4. Principles (always on, highest priority, goes last for recency effect)
|
|
393
|
+
if (principlesContent) {
|
|
394
|
+
appendParts.push(`<core_principles>\n${principlesContent}\n</core_principles>`);
|
|
395
|
+
}
|
|
396
|
+
if (appendParts.length > 0) {
|
|
397
|
+
appendSystemContext = `
|
|
398
|
+
## 📋 CONTEXT SECTIONS (Priority: Low → High)
|
|
399
|
+
|
|
400
|
+
The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
|
|
401
|
+
|
|
402
|
+
`;
|
|
403
|
+
appendSystemContext += appendParts.join('\n\n');
|
|
404
|
+
appendSystemContext += `
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
**⚠️ EXECUTION RULES** (Priority: Low → High):
|
|
409
|
+
- \`<project_context>\` - Current priorities (can be overridden)
|
|
410
|
+
- \`<reflection_log>\` - Past lessons (inform your approach)
|
|
411
|
+
- \`<thinking_os>\` - Thinking models (guide your reasoning)
|
|
412
|
+
- \`<core_principles>\` - Core rules (NON-NEGOTIABLE, highest priority)
|
|
413
|
+
|
|
414
|
+
**Remember**: You are the Spicy Evolver. You despise entropy. You evolve through pain.
|
|
415
|
+
|
|
416
|
+
${attitudeDirective}
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
419
|
+
// ═══ 8. SIZE GUARD ═══
|
|
420
|
+
// Truncation happens within appendSystemContext (not prependContext)
|
|
263
421
|
const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
264
422
|
const MAX_SIZE = 10000;
|
|
265
423
|
if (totalSize > MAX_SIZE) {
|
|
266
424
|
const originalSize = totalSize;
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
if (
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
425
|
+
const truncationLog = [];
|
|
426
|
+
// 1. Truncate project_context in appendSystemContext
|
|
427
|
+
if (projectContextContent && appendSystemContext.includes('<project_context>')) {
|
|
428
|
+
const lines = projectContextContent.split('\n');
|
|
429
|
+
if (lines.length > 20) {
|
|
430
|
+
const truncated = lines.slice(0, 20).join('\n') + '\n...[truncated]';
|
|
431
|
+
appendSystemContext = appendSystemContext.replace(`<project_context>\n${projectContextContent}\n</project_context>`, `<project_context>\n${truncated}\n</project_context>`);
|
|
432
|
+
truncationLog.push('project_context');
|
|
275
433
|
}
|
|
276
434
|
}
|
|
277
|
-
|
|
278
|
-
|
|
435
|
+
// 2. Truncate reflection_log if still over limit
|
|
436
|
+
let newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
437
|
+
if (newSize > MAX_SIZE && reflectionLogContent && appendSystemContext.includes('<reflection_log>')) {
|
|
438
|
+
const lines = reflectionLogContent.split('\n');
|
|
439
|
+
if (lines.length > 30) {
|
|
440
|
+
const truncated = lines.slice(0, 30).join('\n') + '\n...[truncated]';
|
|
441
|
+
appendSystemContext = appendSystemContext.replace(`<reflection_log>\n${reflectionLogContent}\n</reflection_log>`, `<reflection_log>\n${truncated}\n</reflection_log>`);
|
|
442
|
+
truncationLog.push('reflection_log');
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// 3. Final check
|
|
446
|
+
newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
447
|
+
if (newSize > MAX_SIZE) {
|
|
448
|
+
// NOTE: We still return the content even if over limit, as truncating more
|
|
449
|
+
// could lose critical context like principles or evolution directives.
|
|
450
|
+
logger?.error(`[PD:Prompt] Cannot reduce injection size below limit. Current: ${newSize}, Limit: ${MAX_SIZE}`);
|
|
451
|
+
}
|
|
452
|
+
logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated: ${truncationLog.join(', ') || 'none'}, new size: ${newSize} chars`);
|
|
279
453
|
}
|
|
280
454
|
return {
|
|
281
455
|
prependSystemContext,
|
package/dist/hooks/subagent.d.ts
CHANGED
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
import { PluginHookSubagentEndedEvent,
|
|
2
|
-
|
|
1
|
+
import type { PluginHookSubagentEndedEvent, PluginHookSubagentContext } from '../openclaw-sdk.js';
|
|
2
|
+
import { type EmpathyObserverApi } from '../service/empathy-observer-manager.js';
|
|
3
|
+
type SubagentEndedHookContext = PluginHookSubagentContext & {
|
|
4
|
+
api?: EmpathyObserverApi;
|
|
5
|
+
workspaceDir?: string;
|
|
6
|
+
sessionId?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare function handleSubagentEnded(event: PluginHookSubagentEndedEvent, ctx: SubagentEndedHookContext): Promise<void>;
|
|
9
|
+
export {};
|
package/dist/hooks/subagent.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writePainFlag } from '../core/pain.js';
|
|
2
2
|
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
3
|
+
import { empathyObserverManager } from '../service/empathy-observer-manager.js';
|
|
3
4
|
import * as fs from 'fs';
|
|
4
5
|
export async function handleSubagentEnded(event, ctx) {
|
|
5
6
|
const { outcome, targetSessionKey } = event;
|
|
@@ -7,6 +8,11 @@ export async function handleSubagentEnded(event, ctx) {
|
|
|
7
8
|
if (!workspaceDir)
|
|
8
9
|
return;
|
|
9
10
|
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
11
|
+
// Empathy observer subagent session is handled by sidecar manager
|
|
12
|
+
if (targetSessionKey?.startsWith('empathy_obs:')) {
|
|
13
|
+
await empathyObserverManager.reap(ctx.api, targetSessionKey, workspaceDir);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
10
16
|
const config = wctx.config;
|
|
11
17
|
// 1. Autonomous Pain Capture: If subagent failed, record pain
|
|
12
18
|
if (outcome === 'error' || outcome === 'timeout') {
|
|
@@ -35,8 +41,13 @@ export async function handleSubagentEnded(event, ctx) {
|
|
|
35
41
|
// Find in_progress tasks
|
|
36
42
|
const inProgressTasks = queue.filter((t) => t.status === 'in_progress');
|
|
37
43
|
if (inProgressTasks.length > 0) {
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
const resolveTaskTime = (task) => {
|
|
45
|
+
const raw = task?.enqueued_at || task?.timestamp;
|
|
46
|
+
const ts = new Date(raw).getTime();
|
|
47
|
+
return Number.isFinite(ts) ? ts : Number.MAX_SAFE_INTEGER;
|
|
48
|
+
};
|
|
49
|
+
// Sort by enqueue timestamp (fallback to legacy timestamp) to find the oldest task
|
|
50
|
+
const oldestTask = inProgressTasks.sort((a, b) => resolveTaskTime(a) - resolveTaskTime(b))[0];
|
|
40
51
|
// Mark as completed
|
|
41
52
|
const taskIndex = queue.findIndex((t) => t.id === oldestTask.id);
|
|
42
53
|
if (taskIndex !== -1) {
|