principles-disciple 1.5.4
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.d.ts +3 -0
- package/dist/commands/capabilities.js +73 -0
- package/dist/commands/evolver.d.ts +9 -0
- package/dist/commands/evolver.js +26 -0
- package/dist/commands/pain.d.ts +5 -0
- package/dist/commands/pain.js +114 -0
- package/dist/commands/strategy.d.ts +3 -0
- package/dist/commands/strategy.js +29 -0
- package/dist/commands/thinking-os.d.ts +2 -0
- package/dist/commands/thinking-os.js +162 -0
- package/dist/commands/trust.d.ts +4 -0
- package/dist/commands/trust.js +95 -0
- package/dist/core/agent-loader.d.ts +44 -0
- package/dist/core/agent-loader.js +147 -0
- package/dist/core/config-service.d.ts +15 -0
- package/dist/core/config-service.js +26 -0
- package/dist/core/config.d.ts +103 -0
- package/dist/core/config.js +186 -0
- package/dist/core/detection-funnel.d.ts +33 -0
- package/dist/core/detection-funnel.js +100 -0
- package/dist/core/detection-service.d.ts +15 -0
- package/dist/core/detection-service.js +28 -0
- package/dist/core/dictionary-service.d.ts +15 -0
- package/dist/core/dictionary-service.js +26 -0
- package/dist/core/dictionary.d.ts +36 -0
- package/dist/core/dictionary.js +136 -0
- package/dist/core/event-log.d.ts +53 -0
- package/dist/core/event-log.js +196 -0
- package/dist/core/evolution-engine.d.ts +119 -0
- package/dist/core/evolution-engine.js +542 -0
- package/dist/core/evolution-types.d.ts +126 -0
- package/dist/core/evolution-types.js +56 -0
- package/dist/core/hygiene/tracker.d.ts +22 -0
- package/dist/core/hygiene/tracker.js +106 -0
- package/dist/core/init.d.ts +12 -0
- package/dist/core/init.js +117 -0
- package/dist/core/migration.d.ts +6 -0
- package/dist/core/migration.js +90 -0
- package/dist/core/pain.d.ts +4 -0
- package/dist/core/pain.js +70 -0
- package/dist/core/path-resolver.d.ts +43 -0
- package/dist/core/path-resolver.js +259 -0
- package/dist/core/paths.d.ts +60 -0
- package/dist/core/paths.js +67 -0
- package/dist/core/profile.d.ts +62 -0
- package/dist/core/profile.js +210 -0
- package/dist/core/risk-calculator.d.ts +7 -0
- package/dist/core/risk-calculator.js +39 -0
- package/dist/core/session-tracker.d.ts +76 -0
- package/dist/core/session-tracker.js +286 -0
- package/dist/core/system-logger.d.ts +8 -0
- package/dist/core/system-logger.js +31 -0
- package/dist/core/trust-engine.d.ts +91 -0
- package/dist/core/trust-engine.js +284 -0
- package/dist/core/workspace-context.d.ts +64 -0
- package/dist/core/workspace-context.js +134 -0
- package/dist/hooks/gate.d.ts +6 -0
- package/dist/hooks/gate.js +487 -0
- package/dist/hooks/lifecycle.d.ts +5 -0
- package/dist/hooks/lifecycle.js +180 -0
- package/dist/hooks/llm.d.ts +4 -0
- package/dist/hooks/llm.js +153 -0
- package/dist/hooks/pain.d.ts +5 -0
- package/dist/hooks/pain.js +173 -0
- package/dist/hooks/prompt.d.ts +38 -0
- package/dist/hooks/prompt.js +285 -0
- package/dist/hooks/subagent.d.ts +2 -0
- package/dist/hooks/subagent.js +70 -0
- package/dist/i18n/commands.d.ts +26 -0
- package/dist/i18n/commands.js +88 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +204 -0
- package/dist/service/evolution-worker.d.ts +17 -0
- package/dist/service/evolution-worker.js +293 -0
- package/dist/tools/agent-spawn.d.ts +33 -0
- package/dist/tools/agent-spawn.js +170 -0
- package/dist/tools/critique-prompt.d.ts +14 -0
- package/dist/tools/critique-prompt.js +81 -0
- package/dist/tools/deep-reflect.d.ts +19 -0
- package/dist/tools/deep-reflect.js +174 -0
- package/dist/tools/model-index.d.ts +9 -0
- package/dist/tools/model-index.js +82 -0
- package/dist/types/event-types.d.ts +229 -0
- package/dist/types/event-types.js +73 -0
- package/dist/types/hygiene-types.d.ts +20 -0
- package/dist/types/hygiene-types.js +12 -0
- package/dist/types.d.ts +1 -0
- package/dist/types.js +1 -0
- package/dist/utils/file-lock.d.ts +64 -0
- package/dist/utils/file-lock.js +270 -0
- package/dist/utils/glob-match.d.ts +28 -0
- package/dist/utils/glob-match.js +49 -0
- package/dist/utils/hashing.d.ts +9 -0
- package/dist/utils/hashing.js +25 -0
- package/dist/utils/io.d.ts +6 -0
- package/dist/utils/io.js +106 -0
- package/dist/utils/nlp.d.ts +9 -0
- package/dist/utils/nlp.js +59 -0
- package/dist/utils/plugin-logger.d.ts +39 -0
- package/dist/utils/plugin-logger.js +70 -0
- package/openclaw.plugin.json +46 -0
- package/package.json +63 -0
- package/templates/langs/en/core/AGENTS.md +206 -0
- package/templates/langs/en/core/BOOT.md +60 -0
- package/templates/langs/en/core/BOOTSTRAP.md +250 -0
- package/templates/langs/en/core/HEARTBEAT.md +74 -0
- package/templates/langs/en/core/IDENTITY.md +8 -0
- package/templates/langs/en/core/PRINCIPLES.md +10 -0
- package/templates/langs/en/core/SOUL.md +76 -0
- package/templates/langs/en/core/TOOLS.md +53 -0
- package/templates/langs/en/core/USER.md +10 -0
- package/templates/langs/en/pain/00_seed_samples.md +23 -0
- package/templates/langs/en/pain_dictionary.json +22 -0
- package/templates/langs/en/skills/admin/SKILL.md +40 -0
- package/templates/langs/en/skills/bootstrap-tools/SKILL.md +53 -0
- package/templates/langs/en/skills/deductive-audit/SKILL.md +36 -0
- package/templates/langs/en/skills/evolution-framework-update/SKILL.md +31 -0
- package/templates/langs/en/skills/evolve-system/SKILL.md +46 -0
- package/templates/langs/en/skills/evolve-task/SKILL.md +83 -0
- package/templates/langs/en/skills/feedback/SKILL.md +51 -0
- package/templates/langs/en/skills/init-strategy/SKILL.md +54 -0
- package/templates/langs/en/skills/inject-rule/SKILL.md +19 -0
- package/templates/langs/en/skills/manage-okr/SKILL.md +96 -0
- package/templates/langs/en/skills/pain/SKILL.md +19 -0
- package/templates/langs/en/skills/pd-daily/SKILL.md +199 -0
- package/templates/langs/en/skills/pd-grooming/SKILL.md +46 -0
- package/templates/langs/en/skills/pd-mentor/SKILL.md +230 -0
- package/templates/langs/en/skills/plan-script/SKILL.md +32 -0
- package/templates/langs/en/skills/profile/SKILL.md +24 -0
- package/templates/langs/en/skills/reflection/SKILL.md +40 -0
- package/templates/langs/en/skills/reflection-log/SKILL.md +37 -0
- package/templates/langs/en/skills/report/SKILL.md +13 -0
- package/templates/langs/en/skills/root-cause/SKILL.md +33 -0
- package/templates/langs/en/skills/triage/SKILL.md +29 -0
- package/templates/langs/en/skills/watch-evolution/SKILL.md +33 -0
- package/templates/langs/zh/core/AGENTS.md +207 -0
- package/templates/langs/zh/core/BOOT.md +60 -0
- package/templates/langs/zh/core/BOOTSTRAP.md +250 -0
- package/templates/langs/zh/core/HEARTBEAT.md +74 -0
- package/templates/langs/zh/core/IDENTITY.md +8 -0
- package/templates/langs/zh/core/SOUL.md +76 -0
- package/templates/langs/zh/core/TOOLS.md +53 -0
- package/templates/langs/zh/core/USER.md +10 -0
- package/templates/langs/zh/pain/00_seed_samples.md +24 -0
- package/templates/langs/zh/pain_dictionary.json +18 -0
- package/templates/langs/zh/skills/admin/SKILL.md +42 -0
- package/templates/langs/zh/skills/bootstrap-tools/SKILL.md +52 -0
- package/templates/langs/zh/skills/deductive-audit/SKILL.md +36 -0
- package/templates/langs/zh/skills/evolution-framework-update/SKILL.md +31 -0
- package/templates/langs/zh/skills/evolve-system/SKILL.md +46 -0
- package/templates/langs/zh/skills/evolve-task/SKILL.md +83 -0
- package/templates/langs/zh/skills/feedback/SKILL.md +53 -0
- package/templates/langs/zh/skills/init-strategy/SKILL.md +54 -0
- package/templates/langs/zh/skills/inject-rule/SKILL.md +19 -0
- package/templates/langs/zh/skills/manage-okr/SKILL.md +109 -0
- package/templates/langs/zh/skills/pain/SKILL.md +19 -0
- package/templates/langs/zh/skills/pd-daily/SKILL.md +199 -0
- package/templates/langs/zh/skills/pd-grooming/SKILL.md +46 -0
- package/templates/langs/zh/skills/pd-mentor/SKILL.md +230 -0
- package/templates/langs/zh/skills/plan-script/SKILL.md +32 -0
- package/templates/langs/zh/skills/profile/SKILL.md +24 -0
- package/templates/langs/zh/skills/reflection/SKILL.md +40 -0
- package/templates/langs/zh/skills/reflection-log/SKILL.md +37 -0
- package/templates/langs/zh/skills/report/SKILL.md +13 -0
- package/templates/langs/zh/skills/root-cause/SKILL.md +33 -0
- package/templates/langs/zh/skills/triage/SKILL.md +29 -0
- package/templates/langs/zh/skills/watch-evolution/SKILL.md +33 -0
- package/templates/pain_dictionary.json +36 -0
- package/templates/pain_settings.json +77 -0
- package/templates/workspace/.principles/00-kernel.md +51 -0
- package/templates/workspace/.principles/DECISION_POLICY.json +44 -0
- package/templates/workspace/.principles/PRINCIPLES.md +20 -0
- package/templates/workspace/.principles/PROFILE.json +52 -0
- package/templates/workspace/.principles/PROFILE.schema.json +56 -0
- package/templates/workspace/.principles/THINKING_OS.md +64 -0
- package/templates/workspace/.principles/THINKING_OS_ARCHIVE.md +7 -0
- package/templates/workspace/.principles/THINKING_OS_CANDIDATES.md +9 -0
- package/templates/workspace/.principles/models/_INDEX.md +27 -0
- package/templates/workspace/.principles/models/first_principles.md +62 -0
- package/templates/workspace/.principles/models/marketing_4p.md +52 -0
- package/templates/workspace/.principles/models/porter_five.md +63 -0
- package/templates/workspace/.principles/models/swot.md +60 -0
- package/templates/workspace/.principles/models/user_story_map.md +63 -0
- package/templates/workspace/.state/WORKBOARD.json +4 -0
- package/templates/workspace/AUDIT.md +15 -0
- package/templates/workspace/PLAN.md +2 -0
- package/templates/workspace/okr/RECOVERY_PROTOCOL.md +56 -0
- package/templates/workspace/okr/TASK_CHANGES.jsonl +6 -0
- package/templates/workspace/okr/WEEK_TASKS.json +6 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { trackLlmOutput, recordThinkingCheckpoint } from '../core/session-tracker.js';
|
|
4
|
+
import { writePainFlag } from '../core/pain.js';
|
|
5
|
+
import { DetectionService } from '../core/detection-service.js';
|
|
6
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
7
|
+
export function handleLlmOutput(event, ctx) {
|
|
8
|
+
if (!ctx.workspaceDir || !ctx.sessionId)
|
|
9
|
+
return;
|
|
10
|
+
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
11
|
+
const config = wctx.config;
|
|
12
|
+
const eventLog = wctx.eventLog;
|
|
13
|
+
// Track this turn in the core session memory
|
|
14
|
+
const state = trackLlmOutput(ctx.sessionId, event.usage, config, ctx.workspaceDir);
|
|
15
|
+
// We need actual assistant text to analyze
|
|
16
|
+
if (!event.assistantTexts || event.assistantTexts.length === 0)
|
|
17
|
+
return;
|
|
18
|
+
const text = event.assistantTexts.join('\n');
|
|
19
|
+
// ── Track B: Semantic Pain Detection (V1.3.0 Funnel) ──
|
|
20
|
+
const detectionService = DetectionService.get(wctx.stateDir);
|
|
21
|
+
const detection = detectionService.detect(text);
|
|
22
|
+
if (detection.detected) {
|
|
23
|
+
eventLog.recordRuleMatch(ctx.sessionId, {
|
|
24
|
+
ruleId: detection.ruleId || detection.source,
|
|
25
|
+
layer: detection.source === 'l1_exact' ? 'L1' : (detection.source === 'l2_cache' ? 'L2' : 'L3'),
|
|
26
|
+
severity: detection.severity || 0,
|
|
27
|
+
textPreview: text.substring(0, 100)
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
let painScore = detection.detected ? (detection.severity || 0) : 0;
|
|
31
|
+
let source = detection.detected
|
|
32
|
+
? (detection.ruleId ? `llm_${detection.ruleId.toLowerCase()}` : `llm_${detection.source}`)
|
|
33
|
+
: '';
|
|
34
|
+
let matchedReason = detection.detected
|
|
35
|
+
? `Agent triggered pain detection (Source: ${detection.source}${detection.ruleId ? `, Rule: ${detection.ruleId}` : ''})`
|
|
36
|
+
: '';
|
|
37
|
+
// 3. Paralysis Check (from session state tracker)
|
|
38
|
+
const stuckThreshold = config.get('thresholds.stuck_loops_trigger') || 3;
|
|
39
|
+
const inputThreshold = config.get('thresholds.cognitive_paralysis_input') || 4000;
|
|
40
|
+
const paralysisScore = config.get('scores.paralysis') || 40;
|
|
41
|
+
if (state.stuckLoops >= stuckThreshold && state.totalInputTokens > inputThreshold && painScore < paralysisScore) {
|
|
42
|
+
painScore = paralysisScore;
|
|
43
|
+
source = 'llm_paralysis';
|
|
44
|
+
matchedReason = `Agent is stuck in low-output loops (${state.stuckLoops} consecutive turns with tiny output but huge context), indicating cognitive paralysis.`;
|
|
45
|
+
}
|
|
46
|
+
// If a pain threshold is crossed, write the autonomous pain flag
|
|
47
|
+
const painTriggerThreshold = config.get('thresholds.pain_trigger') || 30;
|
|
48
|
+
if (painScore >= painTriggerThreshold) {
|
|
49
|
+
// Inject the actual text snippet that triggered this for the diagnostician to read later
|
|
50
|
+
const snippet = text.length > 200 ? text.substring(0, 100) + '...' + text.substring(text.length - 100) : text;
|
|
51
|
+
writePainFlag(ctx.workspaceDir, {
|
|
52
|
+
source,
|
|
53
|
+
score: String(painScore),
|
|
54
|
+
time: new Date().toISOString(),
|
|
55
|
+
reason: matchedReason,
|
|
56
|
+
is_risky: 'false',
|
|
57
|
+
trigger_text_preview: snippet
|
|
58
|
+
});
|
|
59
|
+
eventLog.recordPainSignal(ctx.sessionId, {
|
|
60
|
+
score: painScore,
|
|
61
|
+
source: source,
|
|
62
|
+
reason: matchedReason,
|
|
63
|
+
isRisky: false
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
// ═══ Thinking OS: Mental Model Usage Tracking ═══
|
|
67
|
+
trackThinkingModelUsage(text, wctx, ctx.sessionId);
|
|
68
|
+
}
|
|
69
|
+
const THINKING_MODEL_SIGNALS = {
|
|
70
|
+
'T-01': [
|
|
71
|
+
/let me (first )?(understand|map|outline|survey|review the (structure|architecture|dependencies))/i,
|
|
72
|
+
/让我先(梳理|了解|画出|理解|查看)(一下)?(结构|架构|依赖|全貌)/,
|
|
73
|
+
],
|
|
74
|
+
'T-02': [
|
|
75
|
+
/(type|test|contract|schema|interface) (constraint|requirement|check|validation)/i,
|
|
76
|
+
/we (must|need to) (respect|follow|adhere to) the/i,
|
|
77
|
+
/(必须|需要).*?(遵守|符合|满足).*?(类型|测试|契约|接口|规范)/,
|
|
78
|
+
],
|
|
79
|
+
'T-03': [
|
|
80
|
+
/based on (the |this )?(evidence|logs?|output|error|stack trace|test result)/i,
|
|
81
|
+
/let me (check|verify|confirm|read|look at) (the |)(actual|source|code|file|log)/i,
|
|
82
|
+
/根据(日志|证据|输出|报错|堆栈|测试结果)/,
|
|
83
|
+
],
|
|
84
|
+
'T-04': [
|
|
85
|
+
/this (is|would be) (irreversible|destructive|permanent|not easily undone)/i,
|
|
86
|
+
/(reversible|can be undone|safely roll back)/i,
|
|
87
|
+
/(不可逆|破坏性|永久的|无法回滚|可以回滚|安全地撤销)/,
|
|
88
|
+
],
|
|
89
|
+
'T-05': [
|
|
90
|
+
/we (must|should) (not|never|avoid|prevent|ensure we don't)/i,
|
|
91
|
+
/(critical|important) (not to|that we don't|to avoid)/i,
|
|
92
|
+
/(绝不能|必须避免|不可以|禁止|确保不会)/,
|
|
93
|
+
],
|
|
94
|
+
'T-06': [
|
|
95
|
+
/(simpl(er|est|ify)|minimal|straightforward|lean) (approach|solution|fix|implementation)/i,
|
|
96
|
+
/(simple is better|keep it simple|no need to over)/i,
|
|
97
|
+
/(最简(单|洁)|精简|没有必要(过度|额外))/,
|
|
98
|
+
],
|
|
99
|
+
'T-07': [
|
|
100
|
+
/(minimal|smallest|narrowest|least) (change|diff|modification|impact)/i,
|
|
101
|
+
/only (change|modify|touch|edit) (the |what)/i,
|
|
102
|
+
/(最小(改动|变更|修改)|只(改|动|修))/,
|
|
103
|
+
],
|
|
104
|
+
'T-08': [
|
|
105
|
+
/this (error|failure|issue) (tells us|indicates|signals|suggests|means)/i,
|
|
106
|
+
/let me (stop|pause|step back|reconsider|rethink)/i,
|
|
107
|
+
/这个(错误|失败|问题)(告诉我们|表明|说明|意味)/,
|
|
108
|
+
/让我(停下|暂停|退一步|重新(考虑|思考|审视))/,
|
|
109
|
+
],
|
|
110
|
+
'T-09': [
|
|
111
|
+
/(break|split|decompose|divide) (this |the task |it )?(into|down)/i,
|
|
112
|
+
/(step 1|first,? (we|i|let's)|phase 1)/i,
|
|
113
|
+
/(拆分|分解|分步|分阶段|第一步)/,
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
function trackThinkingModelUsage(text, wctx, sessionId) {
|
|
117
|
+
const logPath = wctx.resolve('THINKING_OS_USAGE');
|
|
118
|
+
const logDir = path.dirname(logPath);
|
|
119
|
+
if (!fs.existsSync(logDir))
|
|
120
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
121
|
+
let usageLog = {};
|
|
122
|
+
if (fs.existsSync(logPath)) {
|
|
123
|
+
try {
|
|
124
|
+
usageLog = JSON.parse(fs.readFileSync(logPath, 'utf8'));
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
console.error(`[PD:LLM] Failed to parse thinking OS usage log: ${String(e)}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let anyMatch = false;
|
|
131
|
+
for (const [modelId, patterns] of Object.entries(THINKING_MODEL_SIGNALS)) {
|
|
132
|
+
for (const pattern of patterns) {
|
|
133
|
+
if (pattern.test(text)) {
|
|
134
|
+
usageLog[modelId] = (usageLog[modelId] || 0) + 1;
|
|
135
|
+
anyMatch = true;
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
usageLog['_total_turns'] = (usageLog['_total_turns'] || 0) + 1;
|
|
141
|
+
if (anyMatch) {
|
|
142
|
+
// Record thinking checkpoint for gate enforcement
|
|
143
|
+
if (sessionId) {
|
|
144
|
+
recordThinkingCheckpoint(sessionId, wctx.workspaceDir);
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
fs.writeFileSync(logPath, JSON.stringify(usageLog, null, 2), 'utf8');
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
console.error(`[PD:LLM] Failed to write thinking OS usage log: ${String(e)}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PluginHookAfterToolCallEvent, PluginHookToolContext, OpenClawPluginApi } from '../openclaw-sdk.js';
|
|
2
|
+
export declare function handleAfterToolCall(event: PluginHookAfterToolCallEvent, ctx: PluginHookToolContext & {
|
|
3
|
+
workspaceDir?: string;
|
|
4
|
+
pluginConfig?: Record<string, unknown>;
|
|
5
|
+
}, api?: OpenClawPluginApi): void;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { isRisky, normalizePath } from '../utils/io.js';
|
|
3
|
+
import { normalizeProfile } from '../core/profile.js';
|
|
4
|
+
import { computePainScore, writePainFlag } from '../core/pain.js';
|
|
5
|
+
import { trackFriction, resetFriction } from '../core/session-tracker.js';
|
|
6
|
+
import { denoiseError, computeHash } from '../utils/hashing.js';
|
|
7
|
+
import { SystemLogger } from '../core/system-logger.js';
|
|
8
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
9
|
+
const WRITE_TOOLS = ['write', 'edit', 'apply_patch', 'write_file', 'edit_file', 'replace'];
|
|
10
|
+
export function handleAfterToolCall(event, ctx, api) {
|
|
11
|
+
const effectiveWorkspaceDir = ctx.workspaceDir || api?.workspaceDir || api?.resolvePath?.('.');
|
|
12
|
+
if (!effectiveWorkspaceDir) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const wctx = WorkspaceContext.fromHookContext({ ...ctx, workspaceDir: effectiveWorkspaceDir });
|
|
16
|
+
const config = wctx.config;
|
|
17
|
+
const eventLog = wctx.eventLog;
|
|
18
|
+
const trust = wctx.trust;
|
|
19
|
+
const sessionId = ctx.sessionId || 'unknown';
|
|
20
|
+
const params = event.params;
|
|
21
|
+
// ── Track A: Empirical Friction (GFI) ──
|
|
22
|
+
// 0. Special Case: Manual Pain Intervention
|
|
23
|
+
if (event.toolName === 'pain' || event.toolName === 'skill:pain') {
|
|
24
|
+
const reason = params.input || params.arguments || 'Manual intervention';
|
|
25
|
+
trackFriction(sessionId, 100, 'manual_pain', effectiveWorkspaceDir);
|
|
26
|
+
SystemLogger.log(effectiveWorkspaceDir, 'MANUAL_PAIN', `User manually triggered pain: ${reason}`);
|
|
27
|
+
eventLog.recordPainSignal(sessionId, {
|
|
28
|
+
score: 100,
|
|
29
|
+
source: 'manual',
|
|
30
|
+
reason: `User intervention: ${reason}`,
|
|
31
|
+
isRisky: true
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// 1. Determine if this was a failure
|
|
36
|
+
const exitCode = (event.result && typeof event.result === 'object') ? event.result.exitCode : 0;
|
|
37
|
+
const isFailure = !!event.error || (exitCode !== 0 && exitCode !== undefined);
|
|
38
|
+
if (isFailure) {
|
|
39
|
+
const errorText = event.error || (typeof event.result === 'string' ? event.result : JSON.stringify(event.result));
|
|
40
|
+
const denoised = denoiseError(errorText);
|
|
41
|
+
const hash = computeHash(denoised);
|
|
42
|
+
const deltaF = config.get('scores.tool_failure_friction') || 30;
|
|
43
|
+
const updatedState = trackFriction(sessionId, deltaF, hash, effectiveWorkspaceDir);
|
|
44
|
+
// ── Trust Engine: Record failure ──
|
|
45
|
+
const errorType = extractErrorType(event.error || errorText);
|
|
46
|
+
const filePath = params.file_path || params.path || params.file;
|
|
47
|
+
const relPath = typeof filePath === 'string' ? normalizePath(filePath, effectiveWorkspaceDir) : 'unknown';
|
|
48
|
+
// Load profile for risk_paths check
|
|
49
|
+
const profilePath = wctx.resolve('PROFILE');
|
|
50
|
+
let profile = normalizeProfile({});
|
|
51
|
+
if (fs.existsSync(profilePath)) {
|
|
52
|
+
try {
|
|
53
|
+
profile = normalizeProfile(JSON.parse(fs.readFileSync(profilePath, 'utf8')));
|
|
54
|
+
}
|
|
55
|
+
catch (_e) { }
|
|
56
|
+
}
|
|
57
|
+
const isRisk = isRisky(relPath, profile.risk_paths);
|
|
58
|
+
trust.recordFailure(isRisk ? 'risky' : 'tool', {
|
|
59
|
+
sessionId,
|
|
60
|
+
api,
|
|
61
|
+
toolName: event.toolName // 👈 NEW: Pass toolName for classification
|
|
62
|
+
});
|
|
63
|
+
// Record tool call failure event
|
|
64
|
+
eventLog.recordToolCall(sessionId, {
|
|
65
|
+
toolName: event.toolName,
|
|
66
|
+
filePath: typeof filePath === 'string' ? filePath : undefined,
|
|
67
|
+
error: event.error ? String(event.error).substring(0, 200) : undefined,
|
|
68
|
+
errorType,
|
|
69
|
+
gfi: updatedState.currentGfi,
|
|
70
|
+
consecutiveErrors: updatedState.consecutiveErrors,
|
|
71
|
+
exitCode,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// ── SUCCESS BRANCH ──
|
|
76
|
+
resetFriction(sessionId, effectiveWorkspaceDir);
|
|
77
|
+
// 👈 Record success to reset failure streak and earn minor trust (if constructive)
|
|
78
|
+
trust.recordSuccess('tool_success', {
|
|
79
|
+
sessionId,
|
|
80
|
+
api,
|
|
81
|
+
toolName: event.toolName // 👈 NEW: Pass toolName for classification
|
|
82
|
+
});
|
|
83
|
+
if (WRITE_TOOLS.includes(event.toolName)) {
|
|
84
|
+
const filePath = params.file_path || params.path || params.file;
|
|
85
|
+
eventLog.recordToolCall(sessionId, {
|
|
86
|
+
toolName: event.toolName,
|
|
87
|
+
filePath: typeof filePath === 'string' ? filePath : undefined,
|
|
88
|
+
gfi: 0,
|
|
89
|
+
});
|
|
90
|
+
// ── Hygiene Tracking: Record persistence actions ──
|
|
91
|
+
if (typeof filePath === 'string') {
|
|
92
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
93
|
+
const isMemory = /(?:^|\/)memory\//.test(normalized) || normalized.endsWith('/MEMORY.md') || normalized === 'MEMORY.md';
|
|
94
|
+
const isPlan = normalized === 'PLAN.md' || normalized.endsWith('/PLAN.md');
|
|
95
|
+
if (isMemory || isPlan) {
|
|
96
|
+
const content = params.content || params.new_string || '';
|
|
97
|
+
wctx.hygiene.recordPersistence({
|
|
98
|
+
ts: new Date().toISOString(),
|
|
99
|
+
tool: event.toolName,
|
|
100
|
+
path: filePath,
|
|
101
|
+
type: isMemory ? 'memory' : 'plan',
|
|
102
|
+
contentLength: content.length,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Special case for memory_store tool (Success only)
|
|
108
|
+
if (event.toolName === 'memory_store') {
|
|
109
|
+
const text = params.text || '';
|
|
110
|
+
wctx.hygiene.recordPersistence({
|
|
111
|
+
ts: new Date().toISOString(),
|
|
112
|
+
tool: event.toolName,
|
|
113
|
+
path: 'DATABASE',
|
|
114
|
+
type: 'memory',
|
|
115
|
+
contentLength: text.length,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// ── Legacy/Risky Write Pain Logic (Unified WRITE_TOOLS) ──
|
|
120
|
+
if (!WRITE_TOOLS.includes(event.toolName) || !isFailure) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const filePath = params.file_path || params.path || params.file;
|
|
124
|
+
const relPath = typeof filePath === 'string' ? normalizePath(filePath, effectiveWorkspaceDir) : 'unknown';
|
|
125
|
+
const profilePath = wctx.resolve('PROFILE');
|
|
126
|
+
let profile = normalizeProfile({});
|
|
127
|
+
if (fs.existsSync(profilePath)) {
|
|
128
|
+
try {
|
|
129
|
+
profile = normalizeProfile(JSON.parse(fs.readFileSync(profilePath, 'utf8')));
|
|
130
|
+
}
|
|
131
|
+
catch (_e) { }
|
|
132
|
+
}
|
|
133
|
+
const isRisk = isRisky(relPath, profile.risk_paths);
|
|
134
|
+
const painScore = computePainScore(1, false, false, isRisk ? 20 : 0, effectiveWorkspaceDir);
|
|
135
|
+
const painData = {
|
|
136
|
+
score: String(painScore),
|
|
137
|
+
source: 'tool_failure',
|
|
138
|
+
time: new Date().toISOString(),
|
|
139
|
+
reason: `Tool ${event.toolName} failed on ${relPath}. Error: ${event.error ?? 'Non-zero exit code'}`,
|
|
140
|
+
is_risky: String(isRisk),
|
|
141
|
+
};
|
|
142
|
+
writePainFlag(effectiveWorkspaceDir, painData);
|
|
143
|
+
eventLog.recordPainSignal(sessionId, {
|
|
144
|
+
score: painScore,
|
|
145
|
+
source: 'tool_failure',
|
|
146
|
+
reason: `Tool ${event.toolName} failed on ${relPath}`,
|
|
147
|
+
isRisky: isRisk,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function extractErrorType(error) {
|
|
151
|
+
if (!error)
|
|
152
|
+
return 'Unknown';
|
|
153
|
+
const msg = String(error);
|
|
154
|
+
if (msg.includes('EACCES') || msg.includes('permission denied'))
|
|
155
|
+
return 'EACCES';
|
|
156
|
+
if (msg.includes('ENOENT') || msg.includes('no such file'))
|
|
157
|
+
return 'ENOENT';
|
|
158
|
+
if (msg.includes('EISDIR'))
|
|
159
|
+
return 'EISDIR';
|
|
160
|
+
if (msg.includes('ENOSPC'))
|
|
161
|
+
return 'ENOSPC';
|
|
162
|
+
if (msg.includes('SyntaxError'))
|
|
163
|
+
return 'SyntaxError';
|
|
164
|
+
if (msg.includes('TypeError'))
|
|
165
|
+
return 'TypeError';
|
|
166
|
+
if (msg.includes('ReferenceError'))
|
|
167
|
+
return 'ReferenceError';
|
|
168
|
+
if (msg.includes('timeout') || msg.includes('ETIMEDOUT'))
|
|
169
|
+
return 'Timeout';
|
|
170
|
+
if (msg.includes('network') || msg.includes('ECONNREFUSED'))
|
|
171
|
+
return 'Network';
|
|
172
|
+
return 'Other';
|
|
173
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { PluginHookBeforePromptBuildEvent, PluginHookAgentContext, PluginHookBeforePromptBuildResult, PluginLogger } from '../openclaw-sdk.js';
|
|
2
|
+
/**
|
|
3
|
+
* 代理默认配置
|
|
4
|
+
*/
|
|
5
|
+
interface AgentsDefaultsConfig {
|
|
6
|
+
model?: unknown;
|
|
7
|
+
subagents?: {
|
|
8
|
+
model?: unknown;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* OpenClaw API 接口定义(Prompt Hook 所需部分)
|
|
13
|
+
*/
|
|
14
|
+
interface PromptHookApi {
|
|
15
|
+
config?: {
|
|
16
|
+
agents?: {
|
|
17
|
+
defaults?: AgentsDefaultsConfig;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
logger: PluginLogger;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 从 OpenClaw 配置中解析模型选择
|
|
24
|
+
* 支持 string 或 { primary, fallbacks } 格式
|
|
25
|
+
* @internal 导出仅供测试使用
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveModelFromConfig(modelConfig: unknown, logger?: PluginLogger): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* 获取诊断子智能体应使用的模型
|
|
30
|
+
* 优先级:subagents.model > 主模型
|
|
31
|
+
* 如果都没有配置,抛出错误
|
|
32
|
+
* @internal 导出仅供测试使用
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDiagnosticianModel(api: PromptHookApi | null, logger?: PluginLogger): string;
|
|
35
|
+
export declare function handleBeforePromptBuild(event: PluginHookBeforePromptBuildEvent, ctx: PluginHookAgentContext & {
|
|
36
|
+
api?: PromptHookApi;
|
|
37
|
+
}): Promise<PluginHookBeforePromptBuildResult | void>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { getSession, resetFriction } from '../core/session-tracker.js';
|
|
3
|
+
import { WorkspaceContext } from '../core/workspace-context.js';
|
|
4
|
+
/**
|
|
5
|
+
* 验证模型字符串格式是否为 "provider/model"
|
|
6
|
+
*/
|
|
7
|
+
function isValidModelFormat(model) {
|
|
8
|
+
// 格式: "provider/model" 或 "provider/model-variant"
|
|
9
|
+
// provider: 字母数字和连字符,不能以连字符开头/结尾
|
|
10
|
+
// model: 字母数字、连字符、点号、下划线
|
|
11
|
+
const MODEL_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]\/[a-zA-Z0-9._-]+$/;
|
|
12
|
+
return MODEL_PATTERN.test(model);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 从 OpenClaw 配置中解析模型选择
|
|
16
|
+
* 支持 string 或 { primary, fallbacks } 格式
|
|
17
|
+
* @internal 导出仅供测试使用
|
|
18
|
+
*/
|
|
19
|
+
export function resolveModelFromConfig(modelConfig, logger) {
|
|
20
|
+
if (!modelConfig)
|
|
21
|
+
return null;
|
|
22
|
+
// 格式 1: "provider/model" 字符串
|
|
23
|
+
if (typeof modelConfig === 'string') {
|
|
24
|
+
const trimmed = modelConfig.trim();
|
|
25
|
+
if (!trimmed)
|
|
26
|
+
return null;
|
|
27
|
+
if (!isValidModelFormat(trimmed)) {
|
|
28
|
+
logger?.warn(`[PD:Prompt] Invalid model format: "${trimmed}". Expected "provider/model" format.`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return trimmed;
|
|
32
|
+
}
|
|
33
|
+
// 格式 2: { primary: "provider/model", fallbacks: [...] } 对象
|
|
34
|
+
if (typeof modelConfig === 'object' && modelConfig !== null && !Array.isArray(modelConfig)) {
|
|
35
|
+
const cfg = modelConfig;
|
|
36
|
+
if (cfg.primary && typeof cfg.primary === 'string') {
|
|
37
|
+
const trimmed = cfg.primary.trim();
|
|
38
|
+
if (!trimmed)
|
|
39
|
+
return null;
|
|
40
|
+
if (!isValidModelFormat(trimmed)) {
|
|
41
|
+
logger?.warn(`[PD:Prompt] Invalid primary model format: "${trimmed}". Expected "provider/model" format.`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
return trimmed;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// 格式 3: 数组格式(不支持,发出警告)
|
|
48
|
+
if (Array.isArray(modelConfig)) {
|
|
49
|
+
console.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
|
|
50
|
+
logger?.warn(`[PD:Prompt] Array model config not supported. Expected "provider/model" string or { primary: "..." } object.`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 获取诊断子智能体应使用的模型
|
|
57
|
+
* 优先级:subagents.model > 主模型
|
|
58
|
+
* 如果都没有配置,抛出错误
|
|
59
|
+
* @internal 导出仅供测试使用
|
|
60
|
+
*/
|
|
61
|
+
export function getDiagnosticianModel(api, logger) {
|
|
62
|
+
// 兼容两种调用方式:
|
|
63
|
+
// 1. 新方式:getDiagnosticianModel(api) - api 包含 logger
|
|
64
|
+
// 2. 旧方式:getDiagnosticianModel(api, logger) - 分离参数
|
|
65
|
+
const effectiveLogger = api?.logger || logger;
|
|
66
|
+
if (!effectiveLogger) {
|
|
67
|
+
throw new Error('[PD:Prompt] ERROR: Logger not available for getDiagnosticianModel');
|
|
68
|
+
}
|
|
69
|
+
const agentsConfig = api?.config?.agents?.defaults;
|
|
70
|
+
// 优先使用子智能体专用模型
|
|
71
|
+
const subagentModel = resolveModelFromConfig(agentsConfig?.subagents?.model, effectiveLogger);
|
|
72
|
+
if (subagentModel) {
|
|
73
|
+
effectiveLogger.info(`[PD:Prompt] Using subagents.model for diagnostician: ${subagentModel}`);
|
|
74
|
+
return subagentModel;
|
|
75
|
+
}
|
|
76
|
+
// 备选:使用主智能体模型
|
|
77
|
+
const primaryModel = resolveModelFromConfig(agentsConfig?.model, effectiveLogger);
|
|
78
|
+
if (primaryModel) {
|
|
79
|
+
effectiveLogger.info(`[PD:Prompt] Using primary model for diagnostician (subagents.model not set): ${primaryModel}`);
|
|
80
|
+
return primaryModel;
|
|
81
|
+
}
|
|
82
|
+
// 没有配置任何模型,报错
|
|
83
|
+
const errorMsg = `[PD:Prompt] ERROR: No model configured for diagnostician subagent. ` +
|
|
84
|
+
`Please set 'agents.defaults.subagents.model' or 'agents.defaults.model' in OpenClaw config.`;
|
|
85
|
+
effectiveLogger.error(errorMsg);
|
|
86
|
+
throw new Error(errorMsg);
|
|
87
|
+
}
|
|
88
|
+
export async function handleBeforePromptBuild(event, ctx) {
|
|
89
|
+
const workspaceDir = ctx.workspaceDir;
|
|
90
|
+
if (!workspaceDir)
|
|
91
|
+
return;
|
|
92
|
+
const wctx = WorkspaceContext.fromHookContext(ctx);
|
|
93
|
+
const { trigger, sessionId, api } = ctx;
|
|
94
|
+
const logger = api?.logger; // 统一获取 logger
|
|
95
|
+
// Minimal mode: heartbeat and subagents skip project context/system caps to reduce tokens
|
|
96
|
+
// SessionId format: "agent:main:subagent:{type}-{id}" for subagents, "agent:main:..." for main
|
|
97
|
+
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
|
+
const session = sessionId ? getSession(sessionId) : undefined;
|
|
103
|
+
let prependSystemContext = '';
|
|
104
|
+
let prependContext = '';
|
|
105
|
+
let appendSystemContext = '';
|
|
106
|
+
// ═══ LAYER 0 (道之源): Core Principles - Highest Priority ═══
|
|
107
|
+
const principlesPath = wctx.resolve('PRINCIPLES');
|
|
108
|
+
if (fs.existsSync(principlesPath)) {
|
|
109
|
+
try {
|
|
110
|
+
const principles = fs.readFileSync(principlesPath, 'utf8');
|
|
111
|
+
if (principles.trim()) {
|
|
112
|
+
prependSystemContext = `<core_principles>\n${principles.trim()}\n</core_principles>`;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch (e) {
|
|
116
|
+
logger?.error(`[PD:Prompt] Failed to read PRINCIPLES: ${String(e)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// 0. Manual Pain Clearance
|
|
120
|
+
if (trigger === 'user' && sessionId && session && session.currentGfi >= 100) {
|
|
121
|
+
resetFriction(sessionId, workspaceDir);
|
|
122
|
+
}
|
|
123
|
+
// ═══ LAYER 3 (道): Thinking OS + Reflection Checkpoint ═══
|
|
124
|
+
// Both are static, cacheable content - put in prependSystemContext for provider caching
|
|
125
|
+
const thinkingOsPath = wctx.resolve('THINKING_OS');
|
|
126
|
+
if (fs.existsSync(thinkingOsPath)) {
|
|
127
|
+
try {
|
|
128
|
+
const thinkingOs = fs.readFileSync(thinkingOsPath, 'utf8');
|
|
129
|
+
if (thinkingOs.trim()) {
|
|
130
|
+
prependSystemContext += `\n<thinking_os>\n${thinkingOs.trim()}\n</thinking_os>`;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
logger?.error(`[PD:Prompt] Failed to read THINKING_OS: ${String(e)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// 1. Critical Reflection Logic (High Priority - Prompt Injection)
|
|
138
|
+
const reflectionLogPath = wctx.resolve('REFLECTION_LOG');
|
|
139
|
+
if (fs.existsSync(reflectionLogPath)) {
|
|
140
|
+
try {
|
|
141
|
+
const reflectionLog = fs.readFileSync(reflectionLogPath, 'utf8');
|
|
142
|
+
if (reflectionLog.trim()) {
|
|
143
|
+
prependContext += `\n<reflection_log>\n${reflectionLog.trim()}\n</reflection_log>\n`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch (e) {
|
|
147
|
+
logger?.error(`[PD:Prompt] Failed to read REFLECTION_LOG: ${String(e)}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 2. Strategic focus (skip in minimal mode)
|
|
151
|
+
if (!isMinimalMode) {
|
|
152
|
+
if (fs.existsSync(focusPath)) {
|
|
153
|
+
try {
|
|
154
|
+
const currentFocus = fs.readFileSync(focusPath, 'utf8');
|
|
155
|
+
if (currentFocus.trim()) {
|
|
156
|
+
prependContext += `\n<project_context>\n--- Strategic Focus ---\n${currentFocus.trim()}\n--- End of Strategic Focus ---\n</project_context>\n`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (e) {
|
|
160
|
+
logger?.error(`[PD:Prompt] Failed to read CURRENT_FOCUS: ${String(e)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// 3. Background Evolution Directives
|
|
165
|
+
let evolutionDirective = ''; // 用于存储进化指令,避免 return 导致上下文丢失
|
|
166
|
+
const queuePath = wctx.resolve('EVOLUTION_QUEUE');
|
|
167
|
+
if (fs.existsSync(queuePath)) {
|
|
168
|
+
try {
|
|
169
|
+
const queue = JSON.parse(fs.readFileSync(queuePath, 'utf8'));
|
|
170
|
+
const inProgressTask = queue.find((t) => t.status === 'in_progress');
|
|
171
|
+
if (inProgressTask) {
|
|
172
|
+
// High-intensity directive to force the agent to work on the evolution task
|
|
173
|
+
let diagnosticianModel = null;
|
|
174
|
+
try {
|
|
175
|
+
if (api) {
|
|
176
|
+
diagnosticianModel = getDiagnosticianModel(api);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
// 模型配置缺失,记录错误但不 return,继续注入其他上下文
|
|
181
|
+
logger?.error(`[PD:Prompt] Failed to resolve diagnostician model: ${String(err)}`);
|
|
182
|
+
}
|
|
183
|
+
if (diagnosticianModel) {
|
|
184
|
+
// 转义任务内容,防止注入攻击
|
|
185
|
+
// 需要转义:反引号、$、反斜杠、双引号、换行符
|
|
186
|
+
const escapedTask = inProgressTask.task
|
|
187
|
+
.replace(/\\/g, '\\\\') // 先转义反斜杠
|
|
188
|
+
.replace(/`/g, '\\`') // 转义反引号(模板字符串)
|
|
189
|
+
.replace(/\$/g, '\\$') // 转义 $(模板字符串插值)
|
|
190
|
+
.replace(/"/g, '\\"') // 转义双引号
|
|
191
|
+
.replace(/\n/g, '\\n'); // 转义换行符
|
|
192
|
+
logger?.info(`[PD:Prompt] Injecting SYSTEM OVERRIDE for evolution task: ${inProgressTask.id}`);
|
|
193
|
+
evolutionDirective = `\n[🚨 SYSTEM OVERRIDE 🚨]\n` +
|
|
194
|
+
`A critical evolution task is assigned to you. YOU MUST PRIORITIZE THIS TASK.\n` +
|
|
195
|
+
`TASK: "${escapedTask}"\n\n` +
|
|
196
|
+
`ACTION REQUIRED:\n` +
|
|
197
|
+
`Reply ONLY with "[EVOLUTION_ACK]". Then immediately invoke the \`sessions_spawn\` tool:\n` +
|
|
198
|
+
`\`\`\`\n` +
|
|
199
|
+
`sessions_spawn target="diagnostician" message="${escapedTask}" model="${diagnosticianModel}"\n` +
|
|
200
|
+
`\`\`\`\n` +
|
|
201
|
+
`NO OTHER ACTIONS PERMITTED.`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
logger?.error(`[PD:Prompt] Failed to parse EVOLUTION_QUEUE: ${String(e)}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// 4. Perceptive awareness: System Capabilities (skip in minimal mode)
|
|
210
|
+
if (!isMinimalMode) {
|
|
211
|
+
if (fs.existsSync(capsPath)) {
|
|
212
|
+
try {
|
|
213
|
+
const caps = fs.readFileSync(capsPath, 'utf8');
|
|
214
|
+
prependContext += `\n<system_capabilities>\n${caps}\n</system_capabilities>\n`;
|
|
215
|
+
}
|
|
216
|
+
catch (e) {
|
|
217
|
+
logger?.error(`[PD:Prompt] Failed to read SYSTEM_CAPABILITIES: ${String(e)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 5. Heartbeat-specific active checklist
|
|
222
|
+
if (trigger === 'heartbeat') {
|
|
223
|
+
const heartbeatPath = wctx.resolve('HEARTBEAT');
|
|
224
|
+
if (fs.existsSync(heartbeatPath)) {
|
|
225
|
+
try {
|
|
226
|
+
const heartbeatChecklist = fs.readFileSync(heartbeatPath, 'utf8');
|
|
227
|
+
prependContext += `\n<heartbeat_checklist>\n${heartbeatChecklist}\n\nDIRECTIVE: Perform a system-wide self-audit now. If everything is stable, strictly reply with "HEARTBEAT_OK" to minimize token usage.\n</heartbeat_checklist>\n`;
|
|
228
|
+
}
|
|
229
|
+
catch (e) {
|
|
230
|
+
logger?.error(`[PD:Prompt] Failed to read HEARTBEAT: ${String(e)}`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// 6. Security Layer: Trust & Permission Awareness (Dynamic Content)
|
|
235
|
+
// 这些是动态内容,放入 <pd:internal_context> 以便 prependSystemContext 保持纯静态
|
|
236
|
+
const trustScore = wctx.trust.getScore();
|
|
237
|
+
const stage = wctx.trust.getStage();
|
|
238
|
+
const hygiene = wctx.hygiene.getStats();
|
|
239
|
+
// 1. 数值安全校验:防止异常值
|
|
240
|
+
// safeScore 范围: 0-100,safeStage 范围: 1-4(四个信任阶段)
|
|
241
|
+
const safeScore = Math.max(0, Math.min(100, Number(trustScore) || 0));
|
|
242
|
+
const safeStage = Math.max(1, Math.min(4, Number(stage) || 1));
|
|
243
|
+
// 2. 构建动态内部上下文(重命名 internalContext → dynamicContext)
|
|
244
|
+
let dynamicContext = '';
|
|
245
|
+
dynamicContext += `[CURRENT TRUST SCORE: ${safeScore}/100 (Stage ${safeStage})]\n`;
|
|
246
|
+
dynamicContext += `[COGNITIVE HYGIENE: ${hygiene.persistenceCount} persists today]\n`;
|
|
247
|
+
// 3. 视觉层次改进:Stage 1 使用更醒目的格式
|
|
248
|
+
if (safeStage === 1) {
|
|
249
|
+
dynamicContext += `\n[!CRITICAL!] Your trust score is critical. You are in read-only mode. Use diagnostician sub-agents to recover trust.\n`;
|
|
250
|
+
}
|
|
251
|
+
if (hygiene.persistenceCount === 0 && trigger === 'user') {
|
|
252
|
+
dynamicContext += `⚠️ ADVISORY: You haven't persisted any state today. To prevent "Goldfish Memory", consider updating PLAN.md or writing notes to memory/ if this session is becoming complex.\n`;
|
|
253
|
+
}
|
|
254
|
+
// 4. 使用命名空间前缀 (pd:internal_context)
|
|
255
|
+
if (dynamicContext.trim()) {
|
|
256
|
+
prependContext = `\n<pd:internal_context>\n${dynamicContext.trim()}\n</pd:internal_context>\n` + prependContext;
|
|
257
|
+
}
|
|
258
|
+
// 注入进化指令(如果有),放在 prependContext 最前面(高优先级)
|
|
259
|
+
if (evolutionDirective) {
|
|
260
|
+
prependContext = evolutionDirective + prependContext;
|
|
261
|
+
}
|
|
262
|
+
// ═══ SIZE GUARD: Prevent token explosion ═══
|
|
263
|
+
const totalSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
264
|
+
const MAX_SIZE = 10000;
|
|
265
|
+
if (totalSize > MAX_SIZE) {
|
|
266
|
+
const originalSize = totalSize;
|
|
267
|
+
// Truncate <project_context> to first 50 lines
|
|
268
|
+
const projectContextMatch = prependContext.match(/(<project_context>[\s\S]*?<\/project_context>)/);
|
|
269
|
+
if (projectContextMatch) {
|
|
270
|
+
const originalBlock = projectContextMatch[1];
|
|
271
|
+
const lines = originalBlock.split('\n');
|
|
272
|
+
if (lines.length > 50) {
|
|
273
|
+
const truncatedBlock = lines.slice(0, 50).join('\n') + '\n...[truncated]';
|
|
274
|
+
prependContext = prependContext.replace(originalBlock, truncatedBlock);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const newSize = prependSystemContext.length + prependContext.length + appendSystemContext.length;
|
|
278
|
+
logger?.warn(`[PD:Prompt] Injection size exceeded: ${originalSize} chars (limit: ${MAX_SIZE}), truncated to ${newSize} chars (${newSize - originalSize} saved)`);
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
prependSystemContext,
|
|
282
|
+
prependContext,
|
|
283
|
+
appendSystemContext
|
|
284
|
+
};
|
|
285
|
+
}
|