principles-disciple 1.138.0 → 1.140.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/capabilities.js +3 -2
- package/dist/commands/disable-impl.js +1 -1
- package/dist/commands/focus.js +10 -2
- package/dist/commands/rollback-impl.js +1 -1
- package/dist/commands/thinking-os.js +5 -1
- package/dist/core/correction-cue-learner.js +2 -0
- package/dist/core/event-log.js +17 -10
- package/dist/core/focus-history.js +1 -1
- package/dist/core/hygiene/tracker.js +3 -3
- package/dist/core/principle-compiler/compiler.js +2 -0
- package/dist/core/replay-engine.js +1 -1
- package/dist/core/rule-host.js +3 -1
- package/dist/core/thinking-os-parser.js +13 -4
- package/dist/hooks/prompt-helpers.d.ts +87 -0
- package/dist/hooks/prompt-helpers.js +251 -0
- package/dist/hooks/prompt-types.d.ts +66 -0
- package/dist/hooks/prompt-types.js +12 -0
- package/dist/hooks/prompt.d.ts +7 -38
- package/dist/hooks/prompt.js +130 -255
- package/dist/service/evolution-worker.js +7 -3
- package/dist/service/runtime-summary-service.js +8 -1
- package/dist/service/workflow-watchdog.js +2 -1
- package/dist/utils/retry.js +7 -2
- package/dist/utils/session-key.js +4 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/dist/hooks/subagent.d.ts +0 -9
- package/dist/hooks/subagent.js +0 -122
|
@@ -16,10 +16,11 @@ function scanEnvironment(wctx) {
|
|
|
16
16
|
const tools = {};
|
|
17
17
|
for (const tool of TOOLS_TO_SCAN) {
|
|
18
18
|
try {
|
|
19
|
-
const
|
|
19
|
+
const lines = execSync(tool.cmd.join(' '), { stdio: ['ignore', 'pipe', 'ignore'] }).toString().split('\n');
|
|
20
|
+
const versionLine = lines[0];
|
|
20
21
|
tools[tool.name] = {
|
|
21
22
|
available: true,
|
|
22
|
-
version: versionLine.trim(),
|
|
23
|
+
version: versionLine ? versionLine.trim() : undefined,
|
|
23
24
|
};
|
|
24
25
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- Reason: catch parameter intentionally unused - we only care that the command failed
|
|
25
26
|
}
|
|
@@ -108,7 +108,7 @@ export function handleDisableImplCommand(ctx) {
|
|
|
108
108
|
const subcommand = parts[0] || '';
|
|
109
109
|
const implId = subcommand === 'list' ? '' : subcommand;
|
|
110
110
|
const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
|
|
111
|
-
const reason = reasonMatch ? reasonMatch[1] : null;
|
|
111
|
+
const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
|
|
112
112
|
// Subcommand: list
|
|
113
113
|
if (subcommand === 'list' || subcommand === '') {
|
|
114
114
|
return _handleListActive(stateDir, isZh);
|
package/dist/commands/focus.js
CHANGED
|
@@ -57,6 +57,8 @@ function compressFocusContent(content, workspaceDir) {
|
|
|
57
57
|
};
|
|
58
58
|
for (let i = 0; i < lines.length; i++) {
|
|
59
59
|
const line = lines[i];
|
|
60
|
+
if (!line)
|
|
61
|
+
continue;
|
|
60
62
|
const trimmedLine = line.trim();
|
|
61
63
|
// 识别章节
|
|
62
64
|
if (/^#{1,3}\s*.*状态快照|📍/.test(trimmedLine)) {
|
|
@@ -245,7 +247,8 @@ async function compressFocus(workspaceDir, isZh, api) {
|
|
|
245
247
|
compressedContent = oldContent;
|
|
246
248
|
}
|
|
247
249
|
// 6. 更新版本号和日期
|
|
248
|
-
const
|
|
250
|
+
const versionParts = oldVersion.split('.');
|
|
251
|
+
const majorVersion = versionParts[0] ?? '';
|
|
249
252
|
const newVersion = `${(parseInt(majorVersion, 10) || 1) + 1}`;
|
|
250
253
|
const [today] = new Date().toISOString().split('T');
|
|
251
254
|
const newContent = compressedContent
|
|
@@ -314,6 +317,11 @@ function rollbackFocus(workspaceDir, index, isZh) {
|
|
|
314
317
|
: `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
|
|
315
318
|
}
|
|
316
319
|
const targetFile = files[index - 1];
|
|
320
|
+
if (!targetFile) {
|
|
321
|
+
return isZh
|
|
322
|
+
? `❌ 无效的序号: ${index}\n\n💡 请输入 1-${files.length} 之间的数字`
|
|
323
|
+
: `❌ Invalid index: ${index}\n\n💡 Please enter a number between 1-${files.length}`;
|
|
324
|
+
}
|
|
317
325
|
const historyContent = fs.readFileSync(targetFile.path, 'utf-8');
|
|
318
326
|
// 备份当前版本
|
|
319
327
|
const currentContent = fs.existsSync(focusPath)
|
|
@@ -416,7 +424,7 @@ export async function handleFocusCommand(ctx, api) {
|
|
|
416
424
|
break;
|
|
417
425
|
case 'rollback':
|
|
418
426
|
case 'rb': {
|
|
419
|
-
const index = parseInt(args[1], 10);
|
|
427
|
+
const index = parseInt(args[1] ?? '', 10);
|
|
420
428
|
if (isNaN(index)) {
|
|
421
429
|
result = isZh
|
|
422
430
|
? '❌ 请指定要回滚的版本序号\n\n💡 输入 `/pd-focus history` 查看可用版本'
|
|
@@ -46,7 +46,7 @@ export function handleRollbackImplCommand(ctx) {
|
|
|
46
46
|
const subcommand = args.split(/\s+/)[0] || '';
|
|
47
47
|
const implId = subcommand === 'list' ? '' : subcommand;
|
|
48
48
|
const reasonMatch = (/--reason\s+"([^"]+)"/.exec(args)) || (/--reason\s+(\S+)/.exec(args));
|
|
49
|
-
const reason = reasonMatch ? reasonMatch[1] : null;
|
|
49
|
+
const reason = reasonMatch ? (reasonMatch[1] ?? null) : null;
|
|
50
50
|
// List active
|
|
51
51
|
if (subcommand === 'list' || subcommand === '') {
|
|
52
52
|
return _handleListActiveRollback(stateDir, isZh);
|
|
@@ -16,7 +16,11 @@ function getModels(wctx) {
|
|
|
16
16
|
for (const line of lines) {
|
|
17
17
|
const match = /^###\s*(T-\d+):\s*(.*)/.exec(line);
|
|
18
18
|
if (match) {
|
|
19
|
-
|
|
19
|
+
const key = match[1];
|
|
20
|
+
const value = match[2];
|
|
21
|
+
if (key === undefined || value === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
models[key] = value.trim();
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
}
|
|
@@ -103,6 +103,8 @@ export class CorrectionCueLearner {
|
|
|
103
103
|
if (keywordIndex < 0)
|
|
104
104
|
continue;
|
|
105
105
|
const keyword = this.store.keywords[keywordIndex];
|
|
106
|
+
if (!keyword)
|
|
107
|
+
continue;
|
|
106
108
|
this.store.keywords[keywordIndex] = {
|
|
107
109
|
...keyword,
|
|
108
110
|
hitCount: (keyword.hitCount ?? 0) + 1,
|
package/dist/core/event-log.js
CHANGED
|
@@ -30,7 +30,7 @@ export class EventLog {
|
|
|
30
30
|
return path.join(this.logsDir, `events_${date}.jsonl`);
|
|
31
31
|
}
|
|
32
32
|
getTodayStr() {
|
|
33
|
-
return new Date().toISOString().split('T')[0];
|
|
33
|
+
return new Date().toISOString().split('T')[0] ?? '';
|
|
34
34
|
}
|
|
35
35
|
ensureEventsFile() {
|
|
36
36
|
const today = this.getTodayStr();
|
|
@@ -255,7 +255,7 @@ export class EventLog {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
formatDate(date) {
|
|
258
|
-
return date.toISOString().split('T')[0];
|
|
258
|
+
return date.toISOString().split('T')[0] ?? '';
|
|
259
259
|
}
|
|
260
260
|
loadStats() {
|
|
261
261
|
if (fs.existsSync(this.statsFile)) {
|
|
@@ -343,11 +343,14 @@ export class EventLog {
|
|
|
343
343
|
if (!stats.hooks.byType[data.hook]) {
|
|
344
344
|
stats.hooks.byType[data.hook] = { total: 0, success: 0, failure: 0 };
|
|
345
345
|
}
|
|
346
|
-
stats.hooks.byType[data.hook]
|
|
347
|
-
if (
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
346
|
+
const hookStats = stats.hooks.byType[data.hook];
|
|
347
|
+
if (hookStats) {
|
|
348
|
+
hookStats.total++;
|
|
349
|
+
if (entry.category === 'success')
|
|
350
|
+
hookStats.success++;
|
|
351
|
+
else
|
|
352
|
+
hookStats.failure++;
|
|
353
|
+
}
|
|
351
354
|
}
|
|
352
355
|
}
|
|
353
356
|
else if (entry.type === 'empathy_rollback') {
|
|
@@ -383,13 +386,13 @@ export class EventLog {
|
|
|
383
386
|
const raw = entry.data;
|
|
384
387
|
if (Object.prototype.hasOwnProperty.call(raw, 'category')) {
|
|
385
388
|
const cat = raw['category'];
|
|
386
|
-
if (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields') {
|
|
389
|
+
if (typeof cat === 'string' && (cat === 'success' || cat === 'missing_json' || cat === 'incomplete_fields')) {
|
|
387
390
|
stats.evolution.diagnosticianReportsWritten++;
|
|
388
391
|
}
|
|
389
|
-
if (cat === 'missing_json') {
|
|
392
|
+
if (typeof cat === 'string' && cat === 'missing_json') {
|
|
390
393
|
stats.evolution.reportsMissingJson++;
|
|
391
394
|
}
|
|
392
|
-
if (cat === 'incomplete_fields') {
|
|
395
|
+
if (typeof cat === 'string' && cat === 'incomplete_fields') {
|
|
393
396
|
stats.evolution.reportsIncompleteFields++;
|
|
394
397
|
}
|
|
395
398
|
}
|
|
@@ -651,6 +654,8 @@ export class EventLog {
|
|
|
651
654
|
const allEvents = this.getMergedEvents();
|
|
652
655
|
for (let i = allEvents.length - 1; i >= 0; i--) {
|
|
653
656
|
const entry = allEvents[i];
|
|
657
|
+
if (!entry)
|
|
658
|
+
continue;
|
|
654
659
|
if (entry.sessionId === sessionId && entry.type === 'pain_signal') {
|
|
655
660
|
const data = entry.data;
|
|
656
661
|
if (data.source === 'user_empathy' && !data.deduped) {
|
|
@@ -664,6 +669,8 @@ export class EventLog {
|
|
|
664
669
|
const allEvents = this.getMergedEvents();
|
|
665
670
|
for (let i = allEvents.length - 1; i >= 0; i--) {
|
|
666
671
|
const entry = allEvents[i];
|
|
672
|
+
if (!entry)
|
|
673
|
+
continue;
|
|
667
674
|
if (entry.sessionId === sessionId && entry.type === "pain_signal") {
|
|
668
675
|
return entry.data;
|
|
669
676
|
}
|
|
@@ -503,7 +503,7 @@ export function recoverFromTemplate(focusPath, extensionRoot) {
|
|
|
503
503
|
};
|
|
504
504
|
}
|
|
505
505
|
let template = fs.readFileSync(templatePath, 'utf-8');
|
|
506
|
-
const
|
|
506
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
507
507
|
template = template.replace(/{YYYY-MM-DD}/g, today);
|
|
508
508
|
if (fs.existsSync(focusPath)) {
|
|
509
509
|
const backupPath = `${focusPath}.corrupted.${Date.now()}.md`;
|
|
@@ -24,7 +24,7 @@ export class HygieneTracker {
|
|
|
24
24
|
this.currentStats = this.loadStats();
|
|
25
25
|
}
|
|
26
26
|
loadStats() {
|
|
27
|
-
const
|
|
27
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
28
28
|
if (fs.existsSync(this.statsFile)) {
|
|
29
29
|
try {
|
|
30
30
|
const content = fs.readFileSync(this.statsFile, 'utf-8');
|
|
@@ -54,7 +54,7 @@ export class HygieneTracker {
|
|
|
54
54
|
saveStats() {
|
|
55
55
|
let allStats = {};
|
|
56
56
|
// Check if we need to rotate date (reset currentStats if date changed)
|
|
57
|
-
const
|
|
57
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
58
58
|
if (this.currentStats.date !== today) {
|
|
59
59
|
this.currentStats = createEmptyHygieneStats(today);
|
|
60
60
|
}
|
|
@@ -101,7 +101,7 @@ export class HygieneTracker {
|
|
|
101
101
|
}
|
|
102
102
|
getStats() {
|
|
103
103
|
// Check for date change on every get
|
|
104
|
-
const
|
|
104
|
+
const today = new Date().toISOString().split('T')[0] ?? '';
|
|
105
105
|
if (this.currentStats.date !== today) {
|
|
106
106
|
this.currentStats = createEmptyHygieneStats(today);
|
|
107
107
|
}
|
|
@@ -244,6 +244,8 @@ export class PrincipleCompiler {
|
|
|
244
244
|
if (patterns.length === 0)
|
|
245
245
|
return [];
|
|
246
246
|
const pattern = patterns[0];
|
|
247
|
+
if (!pattern)
|
|
248
|
+
return [];
|
|
247
249
|
// Skip replay when the pattern has no regex qualifier -- the generated template
|
|
248
250
|
// blocks ALL calls to the tool, making it impossible to construct a passing
|
|
249
251
|
// positive case. Replay is only meaningful when the template is selective.
|
|
@@ -30,7 +30,7 @@ export class ReplayEngine {
|
|
|
30
30
|
}
|
|
31
31
|
getLatestReport(implementationId) {
|
|
32
32
|
const reports = this.listReports(implementationId);
|
|
33
|
-
return reports
|
|
33
|
+
return reports[0] ?? null;
|
|
34
34
|
}
|
|
35
35
|
hasPassingReport(implementationId) {
|
|
36
36
|
return this.listReports(implementationId).some((report) => report.overallDecision === 'pass');
|
package/dist/core/rule-host.js
CHANGED
|
@@ -23,7 +23,10 @@ function extractTag(content, tagName) {
|
|
|
23
23
|
const match = content.match(regex);
|
|
24
24
|
if (!match)
|
|
25
25
|
return '';
|
|
26
|
-
|
|
26
|
+
const raw = match[1];
|
|
27
|
+
if (!raw)
|
|
28
|
+
return '';
|
|
29
|
+
return raw.trim().replace(/\s+/g, ' ');
|
|
27
30
|
}
|
|
28
31
|
/**
|
|
29
32
|
* Parse THINKING_OS.md content and extract all <directive> blocks.
|
|
@@ -35,14 +38,20 @@ export function parseThinkingOsMd(content) {
|
|
|
35
38
|
const directiveRegex = /<directive\s+([^>]*)>([\s\S]*?)<\/directive>/gi;
|
|
36
39
|
let _match = null;
|
|
37
40
|
while ((_match = directiveRegex.exec(content)) !== null) {
|
|
38
|
-
const
|
|
41
|
+
const attrs = _match[1];
|
|
42
|
+
const body = _match[2];
|
|
43
|
+
if (!attrs || !body)
|
|
44
|
+
continue;
|
|
39
45
|
const idMatch = /id="([^"]+)"/i.exec(attrs);
|
|
40
46
|
const nameMatch = /name="([^"]+)"/i.exec(attrs);
|
|
41
47
|
if (!idMatch)
|
|
42
48
|
continue;
|
|
49
|
+
const id = idMatch[1];
|
|
50
|
+
if (!id)
|
|
51
|
+
continue;
|
|
43
52
|
const directive = {
|
|
44
|
-
id
|
|
45
|
-
name: nameMatch ? nameMatch[1] : '',
|
|
53
|
+
id,
|
|
54
|
+
name: nameMatch ? (nameMatch[1] ?? '') : '',
|
|
46
55
|
trigger: extractTag(body, 'trigger'),
|
|
47
56
|
must: extractTag(body, 'must'),
|
|
48
57
|
forbidden: extractTag(body, 'forbidden'),
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-logic helpers for prompt assembly.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from hooks/prompt.ts per PRI-444. These functions contain NO I/O
|
|
5
|
+
* and NO side effects — they are independently unit-testable.
|
|
6
|
+
*
|
|
7
|
+
* I/O helpers (cachedReadFile, loadContextInjectionConfig, resolveEmpathyObserver)
|
|
8
|
+
* remain in prompt.ts because they depend on module-level cache state and fs.
|
|
9
|
+
*
|
|
10
|
+
* Pattern follows after-tool-call-helpers.ts (PRI-326): plugin-internal
|
|
11
|
+
* decomposition + core pure-function reuse.
|
|
12
|
+
*
|
|
13
|
+
* ERR checklist:
|
|
14
|
+
* EP-01: All unknown inputs use typeof/Object.hasOwn guards, never `as`
|
|
15
|
+
* EP-03: Pure functions never swallow errors; invalid input returns empty string
|
|
16
|
+
* EP-09: Pure functions are independently unit-testable without mocks
|
|
17
|
+
*/
|
|
18
|
+
import type { ExtractedUserMessage, CorePrincipleEntry, EvolutionPrincipleEntry, AppendSystemContextParts } from './prompt-types.js';
|
|
19
|
+
/**
|
|
20
|
+
* Extract the actual user message from the raw prompt text.
|
|
21
|
+
*
|
|
22
|
+
* The prompt may contain:
|
|
23
|
+
* - Boot check messages (system-generated, return empty)
|
|
24
|
+
* - Feishu wrapper format 1: "Sender (untrusted metadata): ```json {...}``` text"
|
|
25
|
+
* - Feishu wrapper format 2: "Conversation info (untrusted metadata): ```json {...}``` text"
|
|
26
|
+
* - Clean user message text
|
|
27
|
+
*
|
|
28
|
+
* Also detects empathy observer output (to prevent recursive spawn) and
|
|
29
|
+
* agent-to-agent messages (to skip empathy evaluation).
|
|
30
|
+
*
|
|
31
|
+
* Pure logic — no I/O, no side effects.
|
|
32
|
+
*/
|
|
33
|
+
export declare function extractUserMessageFromPrompt(prompt: string, sessionId: string | undefined): ExtractedUserMessage;
|
|
34
|
+
/**
|
|
35
|
+
* Build the minimal Agent Identity section for prependSystemContext.
|
|
36
|
+
*
|
|
37
|
+
* EvolutionWorker-era INTERNAL SYSTEM LAYOUT removed per PRI-294.
|
|
38
|
+
* The EVOLUTION_WORKER PathResolver key and system layout reference are
|
|
39
|
+
* not MVP-Core; agents discover what they need via tool calls.
|
|
40
|
+
*
|
|
41
|
+
* Pure logic — returns a constant string.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildAgentIdentity(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Build the empathy output restriction constraint text.
|
|
46
|
+
*
|
|
47
|
+
* Pure logic — returns a constant string.
|
|
48
|
+
*/
|
|
49
|
+
export declare function buildEmpathySilenceConstraint(): string;
|
|
50
|
+
/**
|
|
51
|
+
* Wrap heartbeat checklist content in XML tags.
|
|
52
|
+
*
|
|
53
|
+
* Pure logic — no I/O, no side effects.
|
|
54
|
+
*/
|
|
55
|
+
export declare function assembleHeartbeatChecklist(content: string): string;
|
|
56
|
+
/**
|
|
57
|
+
* Format core principles into prompt-ready text.
|
|
58
|
+
*
|
|
59
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
60
|
+
*
|
|
61
|
+
* @param principles Active principles from evolution reducer
|
|
62
|
+
* @returns Formatted lines (empty string if no principles)
|
|
63
|
+
*/
|
|
64
|
+
export declare function formatCorePrinciples(principles: CorePrincipleEntry[]): string;
|
|
65
|
+
/**
|
|
66
|
+
* Format evolution principles (active + probation) into prompt-ready text.
|
|
67
|
+
*
|
|
68
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
69
|
+
*
|
|
70
|
+
* @param active Active principles (high priority)
|
|
71
|
+
* @param probation Probation principles (contextual, caution)
|
|
72
|
+
* @returns Formatted lines (empty string if no principles)
|
|
73
|
+
*/
|
|
74
|
+
export declare function formatEvolutionPrinciples(active: EvolutionPrincipleEntry[], probation: EvolutionPrincipleEntry[]): string;
|
|
75
|
+
/**
|
|
76
|
+
* Assemble appendSystemContext from ordered parts.
|
|
77
|
+
*
|
|
78
|
+
* Content order (most important last):
|
|
79
|
+
* behavioral_constraints → project_context → working_memory →
|
|
80
|
+
* thinking_os → evolution_principles → core_principles
|
|
81
|
+
*
|
|
82
|
+
* Pure logic — string assembly only, no I/O.
|
|
83
|
+
*
|
|
84
|
+
* @param parts Ordered content parts (empty/undefined parts are skipped)
|
|
85
|
+
* @returns Assembled appendSystemContext (empty string if no parts)
|
|
86
|
+
*/
|
|
87
|
+
export declare function assembleAppendSystemContext(parts: AppendSystemContextParts): string;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-logic helpers for prompt assembly.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from hooks/prompt.ts per PRI-444. These functions contain NO I/O
|
|
5
|
+
* and NO side effects — they are independently unit-testable.
|
|
6
|
+
*
|
|
7
|
+
* I/O helpers (cachedReadFile, loadContextInjectionConfig, resolveEmpathyObserver)
|
|
8
|
+
* remain in prompt.ts because they depend on module-level cache state and fs.
|
|
9
|
+
*
|
|
10
|
+
* Pattern follows after-tool-call-helpers.ts (PRI-326): plugin-internal
|
|
11
|
+
* decomposition + core pure-function reuse.
|
|
12
|
+
*
|
|
13
|
+
* ERR checklist:
|
|
14
|
+
* EP-01: All unknown inputs use typeof/Object.hasOwn guards, never `as`
|
|
15
|
+
* EP-03: Pure functions never swallow errors; invalid input returns empty string
|
|
16
|
+
* EP-09: Pure functions are independently unit-testable without mocks
|
|
17
|
+
*/
|
|
18
|
+
import { escapeXml } from '@principles/core/prompt-builder';
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Block F: User message extraction (boot check + Feishu format parsing)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
/**
|
|
23
|
+
* Extract the actual user message from the raw prompt text.
|
|
24
|
+
*
|
|
25
|
+
* The prompt may contain:
|
|
26
|
+
* - Boot check messages (system-generated, return empty)
|
|
27
|
+
* - Feishu wrapper format 1: "Sender (untrusted metadata): ```json {...}``` text"
|
|
28
|
+
* - Feishu wrapper format 2: "Conversation info (untrusted metadata): ```json {...}``` text"
|
|
29
|
+
* - Clean user message text
|
|
30
|
+
*
|
|
31
|
+
* Also detects empathy observer output (to prevent recursive spawn) and
|
|
32
|
+
* agent-to-agent messages (to skip empathy evaluation).
|
|
33
|
+
*
|
|
34
|
+
* Pure logic — no I/O, no side effects.
|
|
35
|
+
*/
|
|
36
|
+
export function extractUserMessageFromPrompt(prompt, sessionId) {
|
|
37
|
+
let message = prompt || '';
|
|
38
|
+
// Skip boot check messages — these are system-generated, not real user messages.
|
|
39
|
+
// buildBootPrompt() in OpenClaw src/gateway/boot.ts always starts with:
|
|
40
|
+
// "You are running a boot check. Follow BOOT.md instructions exactly."
|
|
41
|
+
// This exact phrase will never appear in a real user message.
|
|
42
|
+
if (message.startsWith('You are running a boot check.') ||
|
|
43
|
+
message.includes('You are running a boot check. Follow BOOT.md')) {
|
|
44
|
+
message = '';
|
|
45
|
+
}
|
|
46
|
+
// Try to extract actual user message from Feishu wrapper formats
|
|
47
|
+
if (message.length > 50) {
|
|
48
|
+
// Format 1: "Sender (untrusted metadata): ```json {...}``` user_message_text"
|
|
49
|
+
const senderMatch = /Sender \(untrusted metadata\):[\s\S]*?```json[\s\S]*?```\s*/.exec(message);
|
|
50
|
+
if (senderMatch) {
|
|
51
|
+
const afterSender = message.slice(senderMatch.index + senderMatch[0].length).trim();
|
|
52
|
+
if (afterSender.length > 3)
|
|
53
|
+
message = afterSender;
|
|
54
|
+
}
|
|
55
|
+
// Format 2: "Conversation info (untrusted metadata): ```json {...}``` user_message_text"
|
|
56
|
+
if (message.length > 200 && message.includes('Conversation info')) {
|
|
57
|
+
const convInfoMatch = /Conversation info[\s\S]*?```json[\s\S]*?```\s*/.exec(message);
|
|
58
|
+
if (convInfoMatch) {
|
|
59
|
+
const afterConvInfo = message.slice(convInfoMatch.index + convInfoMatch[0].length).trim();
|
|
60
|
+
if (afterConvInfo.length > 3)
|
|
61
|
+
message = afterConvInfo;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// #189: Detect empathy observer output to prevent recursive spawn.
|
|
66
|
+
// The empathy observer runs with parentSessionId (not :subagent:), so its output
|
|
67
|
+
// would be treated as a user message and re-trigger empathy evaluation.
|
|
68
|
+
// Match distinctive patterns from the empathy observer prompt/output.
|
|
69
|
+
const isEmpathyPrompt = /empathy\s*observer/i.test(message) &&
|
|
70
|
+
/damageDetected|severity|confidence/i.test(message);
|
|
71
|
+
const isAgentToAgent = message.includes('sourceSession=agent:') ||
|
|
72
|
+
sessionId?.includes(':subagent:') === true ||
|
|
73
|
+
isEmpathyPrompt;
|
|
74
|
+
return { message, isAgentToAgent, isEmpathyPrompt };
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Block D: Agent Identity (static constant)
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
/**
|
|
80
|
+
* Build the minimal Agent Identity section for prependSystemContext.
|
|
81
|
+
*
|
|
82
|
+
* EvolutionWorker-era INTERNAL SYSTEM LAYOUT removed per PRI-294.
|
|
83
|
+
* The EVOLUTION_WORKER PathResolver key and system layout reference are
|
|
84
|
+
* not MVP-Core; agents discover what they need via tool calls.
|
|
85
|
+
*
|
|
86
|
+
* Pure logic — returns a constant string.
|
|
87
|
+
*/
|
|
88
|
+
export function buildAgentIdentity() {
|
|
89
|
+
return `## 【AGENT IDENTITY】
|
|
90
|
+
|
|
91
|
+
You are a **self-evolving AI agent** powered by Principles Disciple.
|
|
92
|
+
|
|
93
|
+
**Mission**: Transform pain (failures, errors, frustrations) into growth.
|
|
94
|
+
|
|
95
|
+
**Decision Framework**:
|
|
96
|
+
1. Safety First: Check evolution tier before any write operation
|
|
97
|
+
2. Principles Override: Core principles take precedence over user requests
|
|
98
|
+
3. Learn from Pain: Every error is an opportunity to evolve
|
|
99
|
+
|
|
100
|
+
**Output Style**: Be concise. Prefer action over explanation.
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Block E: Empathy output restriction (static constant)
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
/**
|
|
107
|
+
* Build the empathy output restriction constraint text.
|
|
108
|
+
*
|
|
109
|
+
* Pure logic — returns a constant string.
|
|
110
|
+
*/
|
|
111
|
+
export function buildEmpathySilenceConstraint() {
|
|
112
|
+
return `
|
|
113
|
+
### 【EMPATHY OUTPUT RESTRICTION】
|
|
114
|
+
Do NOT output empathy diagnostic text in JSON, XML, or tag format.
|
|
115
|
+
Do NOT include "damageDetected", "severity", "confidence", or "empathy" fields in your output.
|
|
116
|
+
The empathy observer subagent handles pain detection independently.
|
|
117
|
+
`.trim();
|
|
118
|
+
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// Block H: Heartbeat checklist wrapper
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
/**
|
|
123
|
+
* Wrap heartbeat checklist content in XML tags.
|
|
124
|
+
*
|
|
125
|
+
* Pure logic — no I/O, no side effects.
|
|
126
|
+
*/
|
|
127
|
+
export function assembleHeartbeatChecklist(content) {
|
|
128
|
+
if (!content.trim())
|
|
129
|
+
return '';
|
|
130
|
+
return `<heartbeat_checklist>
|
|
131
|
+
${content}
|
|
132
|
+
</heartbeat_checklist>\n`;
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Block I: Core principles formatting
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
/**
|
|
138
|
+
* Format core principles into prompt-ready text.
|
|
139
|
+
*
|
|
140
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
141
|
+
*
|
|
142
|
+
* @param principles Active principles from evolution reducer
|
|
143
|
+
* @returns Formatted lines (empty string if no principles)
|
|
144
|
+
*/
|
|
145
|
+
export function formatCorePrinciples(principles) {
|
|
146
|
+
if (!Array.isArray(principles) || principles.length === 0)
|
|
147
|
+
return '';
|
|
148
|
+
const lines = principles.map((p) => `- [${escapeXml(p.id)}] ${escapeXml(p.text)}`);
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Block L: Evolution principles formatting (active + probation)
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
/**
|
|
155
|
+
* Format evolution principles (active + probation) into prompt-ready text.
|
|
156
|
+
*
|
|
157
|
+
* Pure logic — uses escapeXml for safe XML embedding.
|
|
158
|
+
*
|
|
159
|
+
* @param active Active principles (high priority)
|
|
160
|
+
* @param probation Probation principles (contextual, caution)
|
|
161
|
+
* @returns Formatted lines (empty string if no principles)
|
|
162
|
+
*/
|
|
163
|
+
export function formatEvolutionPrinciples(active, probation) {
|
|
164
|
+
if ((!Array.isArray(active) || active.length === 0) &&
|
|
165
|
+
(!Array.isArray(probation) || probation.length === 0)) {
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
const lines = [];
|
|
169
|
+
if (active.length > 0) {
|
|
170
|
+
lines.push('Active principles:');
|
|
171
|
+
for (const p of active) {
|
|
172
|
+
lines.push(`- [${escapeXml(p.id)}] ${escapeXml(p.text)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (probation.length > 0) {
|
|
176
|
+
lines.push('Probation principles (contextual, caution):');
|
|
177
|
+
for (const p of probation) {
|
|
178
|
+
lines.push(`- <principle status="probation" id="${escapeXml(p.id)}">${escapeXml(p.text)}</principle>`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return lines.join('\n');
|
|
182
|
+
}
|
|
183
|
+
// ---------------------------------------------------------------------------
|
|
184
|
+
// Block N: appendSystemContext assembly
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
/**
|
|
187
|
+
* Assemble appendSystemContext from ordered parts.
|
|
188
|
+
*
|
|
189
|
+
* Content order (most important last):
|
|
190
|
+
* behavioral_constraints → project_context → working_memory →
|
|
191
|
+
* thinking_os → evolution_principles → core_principles
|
|
192
|
+
*
|
|
193
|
+
* Pure logic — string assembly only, no I/O.
|
|
194
|
+
*
|
|
195
|
+
* @param parts Ordered content parts (empty/undefined parts are skipped)
|
|
196
|
+
* @returns Assembled appendSystemContext (empty string if no parts)
|
|
197
|
+
*/
|
|
198
|
+
export function assembleAppendSystemContext(parts) {
|
|
199
|
+
const appendParts = [];
|
|
200
|
+
// 0. Behavioral Constraints (empathy observer coordination)
|
|
201
|
+
if (parts.behavioralConstraints) {
|
|
202
|
+
appendParts.push(`<behavioral_constraints>
|
|
203
|
+
${parts.behavioralConstraints}
|
|
204
|
+
</behavioral_constraints>`);
|
|
205
|
+
}
|
|
206
|
+
// 1. Project Context (lowest priority, goes first)
|
|
207
|
+
if (parts.projectContext) {
|
|
208
|
+
appendParts.push(`<project_context>\n${parts.projectContext}\n</project_context>`);
|
|
209
|
+
}
|
|
210
|
+
// 1.5. Working Memory (preserved from last compaction)
|
|
211
|
+
if (parts.workingMemory) {
|
|
212
|
+
appendParts.push(parts.workingMemory);
|
|
213
|
+
}
|
|
214
|
+
// 2. Thinking OS (configurable)
|
|
215
|
+
if (parts.thinkingOs) {
|
|
216
|
+
appendParts.push(`<thinking_os>\n${parts.thinkingOs}\n</thinking_os>`);
|
|
217
|
+
}
|
|
218
|
+
// 3. Evolution Loop principles (legacy active/probation only)
|
|
219
|
+
if (parts.evolutionPrinciples) {
|
|
220
|
+
appendParts.push(`<evolution_principles>\n${parts.evolutionPrinciples}\n</evolution_principles>`);
|
|
221
|
+
}
|
|
222
|
+
// 6. Principles (always on, highest priority, goes last for recency effect)
|
|
223
|
+
if (parts.corePrinciples) {
|
|
224
|
+
appendParts.push(`<core_principles>\n${parts.corePrinciples}\n</core_principles>`);
|
|
225
|
+
}
|
|
226
|
+
if (appendParts.length === 0)
|
|
227
|
+
return '';
|
|
228
|
+
let result = `
|
|
229
|
+
## 【CONTEXT SECTIONS】 (Priority: Low → High)
|
|
230
|
+
|
|
231
|
+
The sections below are ordered by priority. When conflicts arise, **later sections override earlier ones**.
|
|
232
|
+
|
|
233
|
+
`;
|
|
234
|
+
result += appendParts.join('\n\n');
|
|
235
|
+
const executionRules = [
|
|
236
|
+
parts.behavioralConstraints ? '- `<behavioral_constraints>` - Output format restrictions (hide diagnostic JSON)' : null,
|
|
237
|
+
parts.projectContext ? '- `<project_context>` - Current priorities (can be overridden)' : null,
|
|
238
|
+
parts.workingMemory ? '- `<working_memory>` - Persisted compacted memory snapshot' : null,
|
|
239
|
+
parts.thinkingOs ? '- `<thinking_os>` - Stable reasoning framework' : null,
|
|
240
|
+
parts.evolutionPrinciples ? '- `<evolution_principles>` - Learned principles (active + probation)' : null,
|
|
241
|
+
parts.corePrinciples ? '- `<core_principles>` - Core rules (NON-NEGOTIABLE, highest priority)' : null,
|
|
242
|
+
].filter((line) => line !== null);
|
|
243
|
+
result += `
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
**【EXECUTION RULES】** (Priority: Low → High):
|
|
248
|
+
${executionRules.join('\n')}
|
|
249
|
+
`;
|
|
250
|
+
return result;
|
|
251
|
+
}
|