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,26 @@
|
|
|
1
|
+
import { PainDictionary } from './dictionary.js';
|
|
2
|
+
let dictionary = null;
|
|
3
|
+
let lastStateDir = null;
|
|
4
|
+
/**
|
|
5
|
+
* Singleton service to manage the Pain Dictionary.
|
|
6
|
+
*/
|
|
7
|
+
export const DictionaryService = {
|
|
8
|
+
/**
|
|
9
|
+
* Gets or initializes the Pain Dictionary instance.
|
|
10
|
+
* @param stateDir The directory where the dictionary JSON is stored.
|
|
11
|
+
*/
|
|
12
|
+
get(stateDir) {
|
|
13
|
+
if (!dictionary || lastStateDir !== stateDir) {
|
|
14
|
+
dictionary = new PainDictionary(stateDir);
|
|
15
|
+
dictionary.load();
|
|
16
|
+
lastStateDir = stateDir;
|
|
17
|
+
}
|
|
18
|
+
return dictionary;
|
|
19
|
+
},
|
|
20
|
+
/**
|
|
21
|
+
* Resets the singleton instance (primarily for testing).
|
|
22
|
+
*/
|
|
23
|
+
reset() {
|
|
24
|
+
dictionary = null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type RuleType = 'regex' | 'exact_match';
|
|
2
|
+
export interface PainRule {
|
|
3
|
+
type: RuleType;
|
|
4
|
+
pattern?: string;
|
|
5
|
+
phrases?: string[];
|
|
6
|
+
severity: number;
|
|
7
|
+
hits: number;
|
|
8
|
+
status: 'active' | 'dormant' | 'archived';
|
|
9
|
+
}
|
|
10
|
+
export interface PainDictionaryData {
|
|
11
|
+
rules: Record<string, PainRule>;
|
|
12
|
+
}
|
|
13
|
+
export declare class PainDictionary {
|
|
14
|
+
private stateDir;
|
|
15
|
+
private data;
|
|
16
|
+
private filePath;
|
|
17
|
+
private compiledRegex;
|
|
18
|
+
constructor(stateDir: string);
|
|
19
|
+
load(): void;
|
|
20
|
+
private compile;
|
|
21
|
+
getRule(id: string): PainRule | undefined;
|
|
22
|
+
getAllRules(): Record<string, PainRule>;
|
|
23
|
+
/**
|
|
24
|
+
* Adds a new rule or updates an existing one.
|
|
25
|
+
*/
|
|
26
|
+
addRule(id: string, rule: Omit<PainRule, 'hits'>): void;
|
|
27
|
+
match(text: string): {
|
|
28
|
+
ruleId: string;
|
|
29
|
+
severity: number;
|
|
30
|
+
} | undefined;
|
|
31
|
+
flush(): void;
|
|
32
|
+
getStats(): {
|
|
33
|
+
totalRules: number;
|
|
34
|
+
totalHits: number;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
const DEFAULT_RULES = {
|
|
4
|
+
'P_CONFUSION_ZH': {
|
|
5
|
+
type: 'regex',
|
|
6
|
+
pattern: '我(似乎|好像)(不确定|不太确定|不清楚|困惑)',
|
|
7
|
+
severity: 35,
|
|
8
|
+
hits: 0,
|
|
9
|
+
status: 'active'
|
|
10
|
+
},
|
|
11
|
+
'P_CONFUSION_EN': {
|
|
12
|
+
type: 'regex',
|
|
13
|
+
pattern: 'i am (not sure|unsure|confused|uncertain|struggling to)',
|
|
14
|
+
severity: 35,
|
|
15
|
+
hits: 0,
|
|
16
|
+
status: 'active'
|
|
17
|
+
},
|
|
18
|
+
'P_LOOP_ZH': {
|
|
19
|
+
type: 'regex',
|
|
20
|
+
pattern: '似乎(陷入了?循环|回到了?原点|原地打转)',
|
|
21
|
+
severity: 45,
|
|
22
|
+
hits: 0,
|
|
23
|
+
status: 'active'
|
|
24
|
+
},
|
|
25
|
+
'P_LOOP_EN': {
|
|
26
|
+
type: 'exact_match',
|
|
27
|
+
phrases: ['going in circles', 'back to square one', 'looping'],
|
|
28
|
+
severity: 45,
|
|
29
|
+
hits: 0,
|
|
30
|
+
status: 'active'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
export class PainDictionary {
|
|
34
|
+
stateDir;
|
|
35
|
+
data = { rules: {} };
|
|
36
|
+
filePath;
|
|
37
|
+
compiledRegex = new Map();
|
|
38
|
+
constructor(stateDir) {
|
|
39
|
+
this.stateDir = stateDir;
|
|
40
|
+
this.filePath = path.join(stateDir, 'pain_dictionary.json');
|
|
41
|
+
}
|
|
42
|
+
load() {
|
|
43
|
+
if (fs.existsSync(this.filePath)) {
|
|
44
|
+
try {
|
|
45
|
+
this.data = JSON.parse(fs.readFileSync(this.filePath, 'utf8'));
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
console.error('[PD] Failed to parse pain_dictionary.json, using defaults.');
|
|
49
|
+
this.data = { rules: { ...DEFAULT_RULES } };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
this.data = { rules: { ...DEFAULT_RULES } };
|
|
54
|
+
console.log(`[PD:Dictionary] Dictionary not found at ${this.filePath}, creating with default rules`);
|
|
55
|
+
this.flush();
|
|
56
|
+
}
|
|
57
|
+
this.compile();
|
|
58
|
+
}
|
|
59
|
+
compile() {
|
|
60
|
+
this.compiledRegex.clear();
|
|
61
|
+
for (const [id, rule] of Object.entries(this.data.rules)) {
|
|
62
|
+
if (rule.type === 'regex' && rule.pattern) {
|
|
63
|
+
this.compiledRegex.set(id, new RegExp(rule.pattern, 'i'));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
getRule(id) {
|
|
68
|
+
return this.data.rules[id];
|
|
69
|
+
}
|
|
70
|
+
getAllRules() {
|
|
71
|
+
return this.data.rules;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Adds a new rule or updates an existing one.
|
|
75
|
+
*/
|
|
76
|
+
addRule(id, rule) {
|
|
77
|
+
this.data.rules[id] = {
|
|
78
|
+
...rule,
|
|
79
|
+
hits: this.data.rules[id]?.hits || 0
|
|
80
|
+
};
|
|
81
|
+
// Re-compile if it's a regex rule
|
|
82
|
+
if (rule.type === 'regex' && rule.pattern) {
|
|
83
|
+
this.compiledRegex.set(id, new RegExp(rule.pattern, 'i'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
match(text) {
|
|
87
|
+
let bestMatch = undefined;
|
|
88
|
+
for (const [id, rule] of Object.entries(this.data.rules)) {
|
|
89
|
+
if (rule.status !== 'active')
|
|
90
|
+
continue;
|
|
91
|
+
let matched = false;
|
|
92
|
+
if (rule.type === 'regex') {
|
|
93
|
+
const re = this.compiledRegex.get(id);
|
|
94
|
+
if (re) {
|
|
95
|
+
re.lastIndex = 0;
|
|
96
|
+
if (re.test(text))
|
|
97
|
+
matched = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else if (rule.type === 'exact_match' && rule.phrases) {
|
|
101
|
+
const lowerText = text.toLowerCase();
|
|
102
|
+
if (rule.phrases.some(p => lowerText.includes(p.toLowerCase()))) {
|
|
103
|
+
matched = true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (matched) {
|
|
107
|
+
rule.hits++;
|
|
108
|
+
if (!bestMatch || rule.severity > bestMatch.severity) {
|
|
109
|
+
bestMatch = { ruleId: id, severity: rule.severity };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return bestMatch;
|
|
114
|
+
}
|
|
115
|
+
flush() {
|
|
116
|
+
try {
|
|
117
|
+
if (!fs.existsSync(this.stateDir)) {
|
|
118
|
+
fs.mkdirSync(this.stateDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
fs.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), 'utf8');
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
console.error('[PD] Failed to flush pain_dictionary.json:', e);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
getStats() {
|
|
127
|
+
let totalHits = 0;
|
|
128
|
+
for (const rule of Object.values(this.data.rules)) {
|
|
129
|
+
totalHits += rule.hits || 0;
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
totalRules: Object.keys(this.data.rules).length,
|
|
133
|
+
totalHits
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { DailyStats, ToolCallEventData, PainSignalEventData, RuleMatchEventData, RulePromotionEventData, HookExecutionEventData, GateBlockEventData, PlanApprovalEventData, EvolutionTaskEventData, DeepReflectionEventData, TrustChangeEventData } from '../types/event-types.js';
|
|
2
|
+
import type { PluginLogger } from '../openclaw-sdk.js';
|
|
3
|
+
/**
|
|
4
|
+
* EventLog - Structured event logging with daily statistics aggregation.
|
|
5
|
+
*/
|
|
6
|
+
export declare class EventLog {
|
|
7
|
+
private readonly eventsFile;
|
|
8
|
+
private readonly statsFile;
|
|
9
|
+
private readonly logger?;
|
|
10
|
+
private statsCache;
|
|
11
|
+
private eventBuffer;
|
|
12
|
+
private readonly maxBufferSize;
|
|
13
|
+
private readonly flushIntervalMs;
|
|
14
|
+
private flushTimer?;
|
|
15
|
+
constructor(stateDir: string, logger?: PluginLogger);
|
|
16
|
+
recordToolCall(sessionId: string | undefined, data: ToolCallEventData): void;
|
|
17
|
+
recordPainSignal(sessionId: string | undefined, data: PainSignalEventData): void;
|
|
18
|
+
recordRuleMatch(sessionId: string | undefined, data: RuleMatchEventData): void;
|
|
19
|
+
recordRulePromotion(data: RulePromotionEventData): void;
|
|
20
|
+
recordHookExecution(data: HookExecutionEventData): void;
|
|
21
|
+
recordGateBlock(sessionId: string | undefined, data: GateBlockEventData): void;
|
|
22
|
+
recordPlanApproval(sessionId: string | undefined, data: PlanApprovalEventData): void;
|
|
23
|
+
recordEvolutionTask(data: EvolutionTaskEventData): void;
|
|
24
|
+
recordDeepReflection(sessionId: string | undefined, data: DeepReflectionEventData): void;
|
|
25
|
+
recordTrustChange(sessionId: string | undefined, data: TrustChangeEventData): void;
|
|
26
|
+
recordError(sessionId: string | undefined, message: string, context?: Record<string, unknown>): void;
|
|
27
|
+
recordWarn(sessionId: string | undefined, message: string, context?: Record<string, unknown>): void;
|
|
28
|
+
private record;
|
|
29
|
+
private formatDate;
|
|
30
|
+
private loadStats;
|
|
31
|
+
private updateStats;
|
|
32
|
+
private startFlushTimer;
|
|
33
|
+
flush(): void;
|
|
34
|
+
private flushEvents;
|
|
35
|
+
private flushStats;
|
|
36
|
+
/**
|
|
37
|
+
* Get daily statistics for a specific date.
|
|
38
|
+
* Returns empty stats if no events recorded for that date.
|
|
39
|
+
*/
|
|
40
|
+
getDailyStats(date: string): DailyStats;
|
|
41
|
+
/**
|
|
42
|
+
* Dispose of the EventLog, flushing pending data and clearing timer.
|
|
43
|
+
*/
|
|
44
|
+
dispose(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Service to manage multiple EventLog instances by stateDir.
|
|
48
|
+
*/
|
|
49
|
+
export declare class EventLogService {
|
|
50
|
+
private static instances;
|
|
51
|
+
static get(stateDir: string, logger?: PluginLogger): EventLog;
|
|
52
|
+
static flushAll(): void;
|
|
53
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createEmptyDailyStats } from '../types/event-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* EventLog - Structured event logging with daily statistics aggregation.
|
|
6
|
+
*/
|
|
7
|
+
export class EventLog {
|
|
8
|
+
eventsFile;
|
|
9
|
+
statsFile;
|
|
10
|
+
logger;
|
|
11
|
+
statsCache = new Map();
|
|
12
|
+
eventBuffer = [];
|
|
13
|
+
maxBufferSize = 20;
|
|
14
|
+
flushIntervalMs = 30000;
|
|
15
|
+
flushTimer;
|
|
16
|
+
constructor(stateDir, logger) {
|
|
17
|
+
const logsDir = path.join(stateDir, 'logs');
|
|
18
|
+
if (!fs.existsSync(logsDir)) {
|
|
19
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
this.eventsFile = path.join(logsDir, 'events.jsonl');
|
|
22
|
+
this.statsFile = path.join(logsDir, 'daily-stats.json');
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
this.loadStats();
|
|
25
|
+
this.startFlushTimer();
|
|
26
|
+
}
|
|
27
|
+
recordToolCall(sessionId, data) {
|
|
28
|
+
const category = data.error ? 'failure' : 'success';
|
|
29
|
+
this.record('tool_call', category, sessionId, data);
|
|
30
|
+
}
|
|
31
|
+
recordPainSignal(sessionId, data) {
|
|
32
|
+
this.record('pain_signal', 'detected', sessionId, data);
|
|
33
|
+
}
|
|
34
|
+
recordRuleMatch(sessionId, data) {
|
|
35
|
+
this.record('rule_match', 'detected', sessionId, data);
|
|
36
|
+
}
|
|
37
|
+
recordRulePromotion(data) {
|
|
38
|
+
this.record('rule_promotion', 'promoted', undefined, data);
|
|
39
|
+
}
|
|
40
|
+
recordHookExecution(data) {
|
|
41
|
+
const category = data.error ? 'failure' : 'success';
|
|
42
|
+
this.record('hook_execution', category, undefined, data);
|
|
43
|
+
}
|
|
44
|
+
recordGateBlock(sessionId, data) {
|
|
45
|
+
this.record('gate_block', 'blocked', sessionId, data);
|
|
46
|
+
}
|
|
47
|
+
recordPlanApproval(sessionId, data) {
|
|
48
|
+
this.record('plan_approval', 'approved', sessionId, data);
|
|
49
|
+
}
|
|
50
|
+
recordEvolutionTask(data) {
|
|
51
|
+
this.record('evolution_task', 'enqueued', undefined, data);
|
|
52
|
+
}
|
|
53
|
+
recordDeepReflection(sessionId, data) {
|
|
54
|
+
const category = data.passed ? 'passed' : data.timeout ? 'failure' : 'completed';
|
|
55
|
+
this.record('deep_reflection', category, sessionId, data);
|
|
56
|
+
}
|
|
57
|
+
recordTrustChange(sessionId, data) {
|
|
58
|
+
this.record('trust_change', 'changed', sessionId, data);
|
|
59
|
+
}
|
|
60
|
+
recordError(sessionId, message, context) {
|
|
61
|
+
this.record('error', 'failure', sessionId, { message, ...context });
|
|
62
|
+
}
|
|
63
|
+
recordWarn(sessionId, message, context) {
|
|
64
|
+
this.record('warn', 'failure', sessionId, { message, ...context });
|
|
65
|
+
}
|
|
66
|
+
record(type, category, sessionId, data) {
|
|
67
|
+
const now = new Date();
|
|
68
|
+
const date = this.formatDate(now);
|
|
69
|
+
const entry = {
|
|
70
|
+
ts: now.toISOString(),
|
|
71
|
+
date,
|
|
72
|
+
type,
|
|
73
|
+
category,
|
|
74
|
+
sessionId,
|
|
75
|
+
data: data,
|
|
76
|
+
};
|
|
77
|
+
this.eventBuffer.push(entry);
|
|
78
|
+
this.updateStats(entry);
|
|
79
|
+
if (this.eventBuffer.length >= this.maxBufferSize) {
|
|
80
|
+
this.flushEvents();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
formatDate(date) {
|
|
84
|
+
return date.toISOString().split('T')[0];
|
|
85
|
+
}
|
|
86
|
+
loadStats() {
|
|
87
|
+
if (fs.existsSync(this.statsFile)) {
|
|
88
|
+
try {
|
|
89
|
+
const data = JSON.parse(fs.readFileSync(this.statsFile, 'utf-8'));
|
|
90
|
+
for (const date in data) {
|
|
91
|
+
this.statsCache.set(date, data[date]);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch (e) {
|
|
95
|
+
if (this.logger)
|
|
96
|
+
this.logger.error(`[PD] Failed to load daily-stats.json: ${String(e)}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
updateStats(entry) {
|
|
101
|
+
let stats = this.statsCache.get(entry.date);
|
|
102
|
+
if (!stats) {
|
|
103
|
+
stats = createEmptyDailyStats(entry.date);
|
|
104
|
+
this.statsCache.set(entry.date, stats);
|
|
105
|
+
}
|
|
106
|
+
if (entry.type === 'tool_call') {
|
|
107
|
+
const data = entry.data;
|
|
108
|
+
stats.tools.total++;
|
|
109
|
+
if (entry.category === 'success')
|
|
110
|
+
stats.tools.success++;
|
|
111
|
+
else
|
|
112
|
+
stats.tools.failure++;
|
|
113
|
+
}
|
|
114
|
+
else if (entry.type === 'pain_signal') {
|
|
115
|
+
const data = entry.data;
|
|
116
|
+
stats.pain.signalsDetected++;
|
|
117
|
+
stats.pain.maxScore = Math.max(stats.pain.maxScore, data.score);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
startFlushTimer() {
|
|
121
|
+
this.flushTimer = setInterval(() => this.flush(), this.flushIntervalMs);
|
|
122
|
+
}
|
|
123
|
+
flush() {
|
|
124
|
+
this.flushEvents();
|
|
125
|
+
this.flushStats();
|
|
126
|
+
}
|
|
127
|
+
flushEvents() {
|
|
128
|
+
if (this.eventBuffer.length === 0)
|
|
129
|
+
return;
|
|
130
|
+
const lines = this.eventBuffer.map(e => JSON.stringify(e)).join('\n') + '\n';
|
|
131
|
+
try {
|
|
132
|
+
fs.appendFileSync(this.eventsFile, lines, 'utf-8');
|
|
133
|
+
this.eventBuffer = [];
|
|
134
|
+
}
|
|
135
|
+
catch (e) {
|
|
136
|
+
if (this.logger)
|
|
137
|
+
this.logger.error(`[PD] Failed to flush events.jsonl: ${String(e)}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
flushStats() {
|
|
141
|
+
if (this.statsCache.size === 0)
|
|
142
|
+
return;
|
|
143
|
+
const data = {};
|
|
144
|
+
this.statsCache.forEach((stats, date) => {
|
|
145
|
+
data[date] = stats;
|
|
146
|
+
});
|
|
147
|
+
try {
|
|
148
|
+
fs.writeFileSync(this.statsFile, JSON.stringify(data, null, 2), 'utf-8');
|
|
149
|
+
}
|
|
150
|
+
catch (e) {
|
|
151
|
+
if (this.logger)
|
|
152
|
+
this.logger.error(`[PD] Failed to flush daily-stats.json: ${String(e)}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Get daily statistics for a specific date.
|
|
157
|
+
* Returns empty stats if no events recorded for that date.
|
|
158
|
+
*/
|
|
159
|
+
getDailyStats(date) {
|
|
160
|
+
let stats = this.statsCache.get(date);
|
|
161
|
+
if (!stats) {
|
|
162
|
+
stats = createEmptyDailyStats(date);
|
|
163
|
+
this.statsCache.set(date, stats);
|
|
164
|
+
}
|
|
165
|
+
return stats;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Dispose of the EventLog, flushing pending data and clearing timer.
|
|
169
|
+
*/
|
|
170
|
+
dispose() {
|
|
171
|
+
if (this.flushTimer) {
|
|
172
|
+
clearInterval(this.flushTimer);
|
|
173
|
+
this.flushTimer = undefined;
|
|
174
|
+
}
|
|
175
|
+
this.flush();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Service to manage multiple EventLog instances by stateDir.
|
|
180
|
+
*/
|
|
181
|
+
export class EventLogService {
|
|
182
|
+
static instances = new Map();
|
|
183
|
+
static get(stateDir, logger) {
|
|
184
|
+
let instance = this.instances.get(stateDir);
|
|
185
|
+
if (!instance) {
|
|
186
|
+
instance = new EventLog(stateDir, logger);
|
|
187
|
+
this.instances.set(stateDir, instance);
|
|
188
|
+
}
|
|
189
|
+
return instance;
|
|
190
|
+
}
|
|
191
|
+
static flushAll() {
|
|
192
|
+
for (const instance of this.instances.values()) {
|
|
193
|
+
instance.flush();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evolution Engine V2.0 - MVP
|
|
3
|
+
*
|
|
4
|
+
* 成长驱动的进化积分系统,替代惩罚驱动的 Trust Engine。
|
|
5
|
+
*
|
|
6
|
+
* 核心原则:
|
|
7
|
+
* 1. 起点0分,只能增加,不扣分
|
|
8
|
+
* 2. 失败记录教训,不扣分
|
|
9
|
+
* 3. 同类任务失败后首次成功 = 双倍奖励(1小时冷却)
|
|
10
|
+
* 4. 高等级做低级任务积分衰减
|
|
11
|
+
* 5. 原子写入防止并发损坏
|
|
12
|
+
*/
|
|
13
|
+
import { EvolutionTier, EvolutionScorecard, EvolutionStats, EvolutionConfig, TaskDifficulty, TierDefinition, TierPermissions, GateDecision, ToolCallContext } from './evolution-types.js';
|
|
14
|
+
export declare class EvolutionEngine {
|
|
15
|
+
private scorecard;
|
|
16
|
+
private workspaceDir;
|
|
17
|
+
private stateDir;
|
|
18
|
+
private config;
|
|
19
|
+
private storagePath;
|
|
20
|
+
constructor(workspaceDir: string, config?: Partial<EvolutionConfig>);
|
|
21
|
+
/** 获取当前积分 */
|
|
22
|
+
getPoints(): number;
|
|
23
|
+
/** 获取可用积分 */
|
|
24
|
+
getAvailablePoints(): number;
|
|
25
|
+
/** 获取当前等级 */
|
|
26
|
+
getTier(): EvolutionTier;
|
|
27
|
+
/** 获取当前等级定义 */
|
|
28
|
+
getTierDefinition(): TierDefinition;
|
|
29
|
+
/** 获取完整积分卡 */
|
|
30
|
+
getScorecard(): EvolutionScorecard;
|
|
31
|
+
/** 获取统计信息 */
|
|
32
|
+
getStats(): EvolutionStats;
|
|
33
|
+
/** 获取状态摘要 */
|
|
34
|
+
getStatusSummary(): {
|
|
35
|
+
tier: EvolutionTier;
|
|
36
|
+
tierName: string;
|
|
37
|
+
totalPoints: number;
|
|
38
|
+
availablePoints: number;
|
|
39
|
+
permissions: TierPermissions;
|
|
40
|
+
stats: EvolutionStats;
|
|
41
|
+
nextTier: {
|
|
42
|
+
tier: EvolutionTier;
|
|
43
|
+
name: string;
|
|
44
|
+
pointsNeeded: number;
|
|
45
|
+
} | null;
|
|
46
|
+
};
|
|
47
|
+
recordSuccess(toolName: string, options?: {
|
|
48
|
+
filePath?: string;
|
|
49
|
+
difficulty?: TaskDifficulty;
|
|
50
|
+
reason?: string;
|
|
51
|
+
sessionId?: string;
|
|
52
|
+
}): {
|
|
53
|
+
pointsAwarded: number;
|
|
54
|
+
isDoubleReward: boolean;
|
|
55
|
+
newTier?: EvolutionTier;
|
|
56
|
+
};
|
|
57
|
+
recordFailure(toolName: string, options?: {
|
|
58
|
+
filePath?: string;
|
|
59
|
+
difficulty?: TaskDifficulty;
|
|
60
|
+
reason?: string;
|
|
61
|
+
sessionId?: string;
|
|
62
|
+
}): {
|
|
63
|
+
pointsAwarded: number;
|
|
64
|
+
lessonRecorded: boolean;
|
|
65
|
+
};
|
|
66
|
+
/** 工具调用前检查 */
|
|
67
|
+
beforeToolCall(context: ToolCallContext): GateDecision;
|
|
68
|
+
private calculatePoints;
|
|
69
|
+
private getDifficultyPenalty;
|
|
70
|
+
private canReceiveDoubleReward;
|
|
71
|
+
private checkAndApplyPromotion;
|
|
72
|
+
private inferDifficulty;
|
|
73
|
+
private computeTaskHash;
|
|
74
|
+
private createEvent;
|
|
75
|
+
private addEvent;
|
|
76
|
+
private loadOrCreateScorecard;
|
|
77
|
+
private createNewScorecard;
|
|
78
|
+
/** 获取文件锁,返回释放函数 */
|
|
79
|
+
private acquireFileLock;
|
|
80
|
+
/** 检查锁文件是否过期(死锁检测) */
|
|
81
|
+
private isLockStale;
|
|
82
|
+
/** 持久化评分卡(含锁保护) */
|
|
83
|
+
private saveScorecard;
|
|
84
|
+
/** 保存失败后的重试队列 */
|
|
85
|
+
private static retryQueue;
|
|
86
|
+
private static retryTimer;
|
|
87
|
+
/** 调度重试保存 */
|
|
88
|
+
private static scheduleRetrySave;
|
|
89
|
+
/** 处理重试队列 */
|
|
90
|
+
private static processRetryQueue;
|
|
91
|
+
/** 无锁快速保存(用于重试) */
|
|
92
|
+
private saveScorecardImmediate;
|
|
93
|
+
private generateId;
|
|
94
|
+
}
|
|
95
|
+
/** 获取指定 workspace 的引擎实例 */
|
|
96
|
+
export declare function getEvolutionEngine(workspaceDir: string): EvolutionEngine;
|
|
97
|
+
/** 记录成功(便捷函数) */
|
|
98
|
+
export declare function recordEvolutionSuccess(workspaceDir: string, toolName: string, options?: {
|
|
99
|
+
filePath?: string;
|
|
100
|
+
difficulty?: TaskDifficulty;
|
|
101
|
+
reason?: string;
|
|
102
|
+
sessionId?: string;
|
|
103
|
+
}): {
|
|
104
|
+
pointsAwarded: number;
|
|
105
|
+
isDoubleReward: boolean;
|
|
106
|
+
newTier?: EvolutionTier;
|
|
107
|
+
};
|
|
108
|
+
/** 记录失败(便捷函数) */
|
|
109
|
+
export declare function recordEvolutionFailure(workspaceDir: string, toolName: string, options?: {
|
|
110
|
+
filePath?: string;
|
|
111
|
+
difficulty?: TaskDifficulty;
|
|
112
|
+
reason?: string;
|
|
113
|
+
sessionId?: string;
|
|
114
|
+
}): {
|
|
115
|
+
pointsAwarded: number;
|
|
116
|
+
lessonRecorded: boolean;
|
|
117
|
+
};
|
|
118
|
+
/** Gate 检查(便捷函数) */
|
|
119
|
+
export declare function checkEvolutionGate(workspaceDir: string, context: ToolCallContext): GateDecision;
|