@evomap/evolver 1.28.1
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/LICENSE +22 -0
- package/README.md +290 -0
- package/README.zh-CN.md +236 -0
- package/SKILL.md +132 -0
- package/assets/gep/capsules.json +79 -0
- package/assets/gep/events.jsonl +7 -0
- package/assets/gep/genes.json +108 -0
- package/index.js +530 -0
- package/package.json +38 -0
- package/src/canary.js +13 -0
- package/src/evolve.js +1704 -0
- package/src/gep/a2a.js +173 -0
- package/src/gep/a2aProtocol.js +736 -0
- package/src/gep/analyzer.js +35 -0
- package/src/gep/assetCallLog.js +130 -0
- package/src/gep/assetStore.js +297 -0
- package/src/gep/assets.js +36 -0
- package/src/gep/bridge.js +71 -0
- package/src/gep/candidates.js +142 -0
- package/src/gep/contentHash.js +65 -0
- package/src/gep/deviceId.js +209 -0
- package/src/gep/envFingerprint.js +83 -0
- package/src/gep/hubReview.js +206 -0
- package/src/gep/hubSearch.js +237 -0
- package/src/gep/issueReporter.js +262 -0
- package/src/gep/llmReview.js +92 -0
- package/src/gep/memoryGraph.js +771 -0
- package/src/gep/memoryGraphAdapter.js +203 -0
- package/src/gep/mutation.js +186 -0
- package/src/gep/narrativeMemory.js +108 -0
- package/src/gep/paths.js +113 -0
- package/src/gep/personality.js +355 -0
- package/src/gep/prompt.js +566 -0
- package/src/gep/questionGenerator.js +212 -0
- package/src/gep/reflection.js +127 -0
- package/src/gep/sanitize.js +67 -0
- package/src/gep/selector.js +250 -0
- package/src/gep/signals.js +417 -0
- package/src/gep/skillDistiller.js +499 -0
- package/src/gep/solidify.js +1681 -0
- package/src/gep/strategy.js +126 -0
- package/src/gep/taskReceiver.js +528 -0
- package/src/gep/validationReport.js +55 -0
- package/src/ops/cleanup.js +80 -0
- package/src/ops/commentary.js +60 -0
- package/src/ops/health_check.js +106 -0
- package/src/ops/index.js +11 -0
- package/src/ops/innovation.js +67 -0
- package/src/ops/lifecycle.js +168 -0
- package/src/ops/self_repair.js +72 -0
- package/src/ops/skills_monitor.js +143 -0
- package/src/ops/trigger.js +33 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// Opportunity signal names (shared with mutation.js and personality.js).
|
|
2
|
+
var OPPORTUNITY_SIGNALS = [
|
|
3
|
+
'user_feature_request',
|
|
4
|
+
'user_improvement_suggestion',
|
|
5
|
+
'perf_bottleneck',
|
|
6
|
+
'capability_gap',
|
|
7
|
+
'stable_success_plateau',
|
|
8
|
+
'external_opportunity',
|
|
9
|
+
'recurring_error',
|
|
10
|
+
'unsupported_input_type',
|
|
11
|
+
'evolution_stagnation_detected',
|
|
12
|
+
'repair_loop_detected',
|
|
13
|
+
'force_innovation_after_repair_loop',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function hasOpportunitySignal(signals) {
|
|
17
|
+
var list = Array.isArray(signals) ? signals : [];
|
|
18
|
+
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
|
|
19
|
+
var name = OPPORTUNITY_SIGNALS[i];
|
|
20
|
+
if (list.includes(name)) return true;
|
|
21
|
+
if (list.some(function (s) { return String(s).startsWith(name + ':'); })) return true;
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Build a de-duplication set from recent evolution events.
|
|
27
|
+
// Returns an object: { suppressedSignals: Set<string>, recentIntents: string[], consecutiveRepairCount: number }
|
|
28
|
+
function analyzeRecentHistory(recentEvents) {
|
|
29
|
+
if (!Array.isArray(recentEvents) || recentEvents.length === 0) {
|
|
30
|
+
return { suppressedSignals: new Set(), recentIntents: [], consecutiveRepairCount: 0 };
|
|
31
|
+
}
|
|
32
|
+
// Take only the last 10 events
|
|
33
|
+
var recent = recentEvents.slice(-10);
|
|
34
|
+
|
|
35
|
+
// Count consecutive same-intent runs at the tail
|
|
36
|
+
var consecutiveRepairCount = 0;
|
|
37
|
+
for (var i = recent.length - 1; i >= 0; i--) {
|
|
38
|
+
if (recent[i].intent === 'repair') {
|
|
39
|
+
consecutiveRepairCount++;
|
|
40
|
+
} else {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Count signal frequency in last 8 events: signal -> count
|
|
46
|
+
var signalFreq = {};
|
|
47
|
+
var geneFreq = {};
|
|
48
|
+
var tail = recent.slice(-8);
|
|
49
|
+
for (var j = 0; j < tail.length; j++) {
|
|
50
|
+
var evt = tail[j];
|
|
51
|
+
var sigs = Array.isArray(evt.signals) ? evt.signals : [];
|
|
52
|
+
for (var k = 0; k < sigs.length; k++) {
|
|
53
|
+
var s = String(sigs[k]);
|
|
54
|
+
// Normalize: strip details suffix so frequency keys match dedup filter keys
|
|
55
|
+
var key = s.startsWith('errsig:') ? 'errsig'
|
|
56
|
+
: s.startsWith('recurring_errsig') ? 'recurring_errsig'
|
|
57
|
+
: s.startsWith('user_feature_request:') ? 'user_feature_request'
|
|
58
|
+
: s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
|
|
59
|
+
: s;
|
|
60
|
+
signalFreq[key] = (signalFreq[key] || 0) + 1;
|
|
61
|
+
}
|
|
62
|
+
var genes = Array.isArray(evt.genes_used) ? evt.genes_used : [];
|
|
63
|
+
for (var g = 0; g < genes.length; g++) {
|
|
64
|
+
geneFreq[String(genes[g])] = (geneFreq[String(genes[g])] || 0) + 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Suppress signals that appeared in 3+ of the last 8 events (they are being over-processed)
|
|
69
|
+
var suppressedSignals = new Set();
|
|
70
|
+
var entries = Object.entries(signalFreq);
|
|
71
|
+
for (var ei = 0; ei < entries.length; ei++) {
|
|
72
|
+
if (entries[ei][1] >= 3) {
|
|
73
|
+
suppressedSignals.add(entries[ei][0]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
var recentIntents = recent.map(function(e) { return e.intent || 'unknown'; });
|
|
78
|
+
|
|
79
|
+
// Count empty cycles (blast_radius.files === 0) in last 8 events.
|
|
80
|
+
// High ratio indicates the evolver is spinning without producing real changes.
|
|
81
|
+
var emptyCycleCount = 0;
|
|
82
|
+
for (var ec = 0; ec < tail.length; ec++) {
|
|
83
|
+
var br = tail[ec].blast_radius;
|
|
84
|
+
var em = tail[ec].meta && tail[ec].meta.empty_cycle;
|
|
85
|
+
if (em || (br && br.files === 0 && br.lines === 0)) {
|
|
86
|
+
emptyCycleCount++;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Count consecutive empty cycles at the tail (not just total in last 8).
|
|
91
|
+
// This detects saturation: the evolver has exhausted innovation space and keeps producing
|
|
92
|
+
// zero-change cycles. Used to trigger graceful degradation to steady-state mode.
|
|
93
|
+
var consecutiveEmptyCycles = 0;
|
|
94
|
+
for (var se = recent.length - 1; se >= 0; se--) {
|
|
95
|
+
var seBr = recent[se].blast_radius;
|
|
96
|
+
var seEm = recent[se].meta && recent[se].meta.empty_cycle;
|
|
97
|
+
if (seEm || (seBr && seBr.files === 0 && seBr.lines === 0)) {
|
|
98
|
+
consecutiveEmptyCycles++;
|
|
99
|
+
} else {
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Count consecutive failures at the tail of recent events.
|
|
105
|
+
// This tells the evolver "you have been failing N times in a row -- slow down."
|
|
106
|
+
var consecutiveFailureCount = 0;
|
|
107
|
+
for (var cf = recent.length - 1; cf >= 0; cf--) {
|
|
108
|
+
var outcome = recent[cf].outcome;
|
|
109
|
+
if (outcome && outcome.status === 'failed') {
|
|
110
|
+
consecutiveFailureCount++;
|
|
111
|
+
} else {
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Count total failures in last 8 events (failure ratio).
|
|
117
|
+
var recentFailureCount = 0;
|
|
118
|
+
for (var rf = 0; rf < tail.length; rf++) {
|
|
119
|
+
var rfOut = tail[rf].outcome;
|
|
120
|
+
if (rfOut && rfOut.status === 'failed') recentFailureCount++;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
suppressedSignals: suppressedSignals,
|
|
125
|
+
recentIntents: recentIntents,
|
|
126
|
+
consecutiveRepairCount: consecutiveRepairCount,
|
|
127
|
+
emptyCycleCount: emptyCycleCount,
|
|
128
|
+
consecutiveEmptyCycles: consecutiveEmptyCycles,
|
|
129
|
+
consecutiveFailureCount: consecutiveFailureCount,
|
|
130
|
+
recentFailureCount: recentFailureCount,
|
|
131
|
+
recentFailureRatio: tail.length > 0 ? recentFailureCount / tail.length : 0,
|
|
132
|
+
signalFreq: signalFreq,
|
|
133
|
+
geneFreq: geneFreq,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, userSnippet, recentEvents }) {
|
|
138
|
+
var signals = [];
|
|
139
|
+
var corpus = [
|
|
140
|
+
String(recentSessionTranscript || ''),
|
|
141
|
+
String(todayLog || ''),
|
|
142
|
+
String(memorySnippet || ''),
|
|
143
|
+
String(userSnippet || ''),
|
|
144
|
+
].join('\n');
|
|
145
|
+
var lower = corpus.toLowerCase();
|
|
146
|
+
|
|
147
|
+
// Analyze recent evolution history for de-duplication
|
|
148
|
+
var history = analyzeRecentHistory(recentEvents || []);
|
|
149
|
+
|
|
150
|
+
// --- Defensive signals (errors, missing resources) ---
|
|
151
|
+
|
|
152
|
+
// Refined error detection regex to avoid false positives on "fail"/"failed" in normal text.
|
|
153
|
+
// We prioritize structured error markers ([error], error:, exception:) and specific JSON patterns.
|
|
154
|
+
var errorHit = /\[error\]|error:|exception:|iserror":true|"status":\s*"error"|"status":\s*"failed"|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/.test(lower);
|
|
155
|
+
if (errorHit) signals.push('log_error');
|
|
156
|
+
|
|
157
|
+
// Error signature (more reproducible than a coarse "log_error" tag).
|
|
158
|
+
try {
|
|
159
|
+
var lines = corpus
|
|
160
|
+
.split('\n')
|
|
161
|
+
.map(function (l) { return String(l || '').trim(); })
|
|
162
|
+
.filter(Boolean);
|
|
163
|
+
|
|
164
|
+
var errLine =
|
|
165
|
+
lines.find(function (l) { return /\b(typeerror|referenceerror|syntaxerror)\b\s*:|error\s*:|exception\s*:|\[error|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/i.test(l); }) ||
|
|
166
|
+
null;
|
|
167
|
+
|
|
168
|
+
if (errLine) {
|
|
169
|
+
var clipped = errLine.replace(/\s+/g, ' ').slice(0, 260);
|
|
170
|
+
signals.push('errsig:' + clipped);
|
|
171
|
+
}
|
|
172
|
+
} catch (e) {}
|
|
173
|
+
|
|
174
|
+
if (lower.includes('memory.md missing')) signals.push('memory_missing');
|
|
175
|
+
if (lower.includes('user.md missing')) signals.push('user_missing');
|
|
176
|
+
if (lower.includes('key missing')) signals.push('integration_key_missing');
|
|
177
|
+
if (lower.includes('no session logs found') || lower.includes('no jsonl files')) signals.push('session_logs_missing');
|
|
178
|
+
// if (lower.includes('pgrep') || lower.includes('ps aux')) signals.push('windows_shell_incompatible');
|
|
179
|
+
if (lower.includes('path.resolve(__dirname, \'../../../')) signals.push('path_outside_workspace');
|
|
180
|
+
|
|
181
|
+
// Protocol-specific drift signals
|
|
182
|
+
if (lower.includes('prompt') && !lower.includes('evolutionevent')) signals.push('protocol_drift');
|
|
183
|
+
|
|
184
|
+
// --- Recurring error detection (robustness signals) ---
|
|
185
|
+
// Count repeated identical errors -- these indicate systemic issues that need automated fixes
|
|
186
|
+
try {
|
|
187
|
+
var errorCounts = {};
|
|
188
|
+
var errPatterns = corpus.match(/(?:LLM error|"error"|"status":\s*"error")[^}]{0,200}/gi) || [];
|
|
189
|
+
for (var ep = 0; ep < errPatterns.length; ep++) {
|
|
190
|
+
// Normalize to a short key
|
|
191
|
+
var key = errPatterns[ep].replace(/\s+/g, ' ').slice(0, 100);
|
|
192
|
+
errorCounts[key] = (errorCounts[key] || 0) + 1;
|
|
193
|
+
}
|
|
194
|
+
var recurringErrors = Object.entries(errorCounts).filter(function (e) { return e[1] >= 3; });
|
|
195
|
+
if (recurringErrors.length > 0) {
|
|
196
|
+
signals.push('recurring_error');
|
|
197
|
+
// Include the top recurring error signature for the agent to diagnose
|
|
198
|
+
var topErr = recurringErrors.sort(function (a, b) { return b[1] - a[1]; })[0];
|
|
199
|
+
signals.push('recurring_errsig(' + topErr[1] + 'x):' + topErr[0].slice(0, 150));
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {}
|
|
202
|
+
|
|
203
|
+
// --- Unsupported input type (e.g. GIF, video formats the LLM can't handle) ---
|
|
204
|
+
if (/unsupported mime|unsupported.*type|invalid.*mime/i.test(lower)) {
|
|
205
|
+
signals.push('unsupported_input_type');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- Opportunity signals (innovation / feature requests) ---
|
|
209
|
+
// Support 4 languages: EN, ZH-CN, ZH-TW, JA. Attach snippet for selector/prompt use.
|
|
210
|
+
|
|
211
|
+
var featureRequestSnippet = '';
|
|
212
|
+
var featEn = corpus.match(/\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,120}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i);
|
|
213
|
+
if (featEn) featureRequestSnippet = featEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
214
|
+
if (!featureRequestSnippet && /\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower)) {
|
|
215
|
+
var featWant = corpus.match(/.{0,80}\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b.{0,80}/i);
|
|
216
|
+
featureRequestSnippet = featWant ? featWant[0].replace(/\s+/g, ' ').trim().slice(0, 200) : 'feature request';
|
|
217
|
+
}
|
|
218
|
+
if (!featureRequestSnippet && /加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus)) {
|
|
219
|
+
var featZh = corpus.match(/.{0,100}(加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能).{0,100}/);
|
|
220
|
+
if (featZh) featureRequestSnippet = featZh[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
221
|
+
if (!featureRequestSnippet && /我想/.test(corpus)) {
|
|
222
|
+
var featWantZh = corpus.match(/我想\s*[,,\.。、\s]*([\s\S]{0,400})/);
|
|
223
|
+
featureRequestSnippet = featWantZh ? (featWantZh[1].replace(/\s+/g, ' ').trim().slice(0, 200) || '功能需求') : '功能需求';
|
|
224
|
+
}
|
|
225
|
+
if (!featureRequestSnippet) featureRequestSnippet = '功能需求';
|
|
226
|
+
}
|
|
227
|
+
if (!featureRequestSnippet && /加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus)) {
|
|
228
|
+
var featTw = corpus.match(/.{0,100}(加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加).{0,100}/);
|
|
229
|
+
featureRequestSnippet = featTw ? featTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '功能需求';
|
|
230
|
+
}
|
|
231
|
+
if (!featureRequestSnippet && /追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
|
|
232
|
+
var featJa = corpus.match(/.{0,100}(追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい).{0,100}/);
|
|
233
|
+
featureRequestSnippet = featJa ? featJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '機能要望';
|
|
234
|
+
}
|
|
235
|
+
if (featureRequestSnippet || /\b(add|implement|create|build|make|develop|write|design)\b[^.?!\n]{3,60}\b(feature|function|module|capability|tool|support|endpoint|command|option|mode)\b/i.test(corpus) ||
|
|
236
|
+
/\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower) ||
|
|
237
|
+
/加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus) ||
|
|
238
|
+
/加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus) ||
|
|
239
|
+
/追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
|
|
240
|
+
signals.push('user_feature_request:' + (featureRequestSnippet || ''));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// user_improvement_suggestion: 4 languages + snippet
|
|
244
|
+
var improvementSnippet = '';
|
|
245
|
+
if (!errorHit) {
|
|
246
|
+
var impEn = corpus.match(/.{0,80}\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b.{0,80}/i);
|
|
247
|
+
if (impEn) improvementSnippet = impEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
248
|
+
if (!improvementSnippet && /改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus)) {
|
|
249
|
+
var impZh = corpus.match(/.{0,100}(改进一下|优化一下|简化|重构|整理一下|弄得更好).{0,100}/);
|
|
250
|
+
improvementSnippet = impZh ? impZh[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改进建议';
|
|
251
|
+
}
|
|
252
|
+
if (!improvementSnippet && /改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus)) {
|
|
253
|
+
var impTw = corpus.match(/.{0,100}(改進一下|優化一下|簡化|重構|整理一下|弄得更好).{0,100}/);
|
|
254
|
+
improvementSnippet = impTw ? impTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改進建議';
|
|
255
|
+
}
|
|
256
|
+
if (!improvementSnippet && /改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus)) {
|
|
257
|
+
var impJa = corpus.match(/.{0,100}(改善|最適化|簡素化|リファクタ|良くして|改良).{0,100}/);
|
|
258
|
+
improvementSnippet = impJa ? impJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改善要望';
|
|
259
|
+
}
|
|
260
|
+
var hasImprovement = improvementSnippet ||
|
|
261
|
+
/\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b/i.test(lower) ||
|
|
262
|
+
/改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus) ||
|
|
263
|
+
/改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus) ||
|
|
264
|
+
/改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus);
|
|
265
|
+
if (hasImprovement) {
|
|
266
|
+
signals.push('user_improvement_suggestion:' + (improvementSnippet || ''));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// perf_bottleneck: performance issues detected
|
|
271
|
+
if (/\b(slow|timeout|timed?\s*out|latency|bottleneck|took too long|performance issue|high cpu|high memory|oom|out of memory)\b/i.test(lower)) {
|
|
272
|
+
signals.push('perf_bottleneck');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// capability_gap: something is explicitly unsupported or missing
|
|
276
|
+
if (/\b(not supported|cannot|doesn'?t support|no way to|missing feature|unsupported|not available|not implemented|no support for)\b/i.test(lower)) {
|
|
277
|
+
// Only fire if it is not just a missing file/config signal
|
|
278
|
+
if (!signals.includes('memory_missing') && !signals.includes('user_missing') && !signals.includes('session_logs_missing')) {
|
|
279
|
+
signals.push('capability_gap');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// --- Tool Usage Analytics ---
|
|
284
|
+
var toolUsage = {};
|
|
285
|
+
var toolMatches = corpus.match(/\[TOOL:\s*([\w-]+)\]/g) || [];
|
|
286
|
+
|
|
287
|
+
// Extract exec commands to identify benign loops (like watchdog checks)
|
|
288
|
+
var execCommands = corpus.match(/exec: (node\s+[\w\/\.-]+\.js\s+ensure)/g) || [];
|
|
289
|
+
var benignExecCount = execCommands.length;
|
|
290
|
+
|
|
291
|
+
for (var i = 0; i < toolMatches.length; i++) {
|
|
292
|
+
var toolName = toolMatches[i].match(/\[TOOL:\s*([\w-]+)\]/)[1];
|
|
293
|
+
toolUsage[toolName] = (toolUsage[toolName] || 0) + 1;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Adjust exec count by subtracting benign commands
|
|
297
|
+
if (toolUsage['exec']) {
|
|
298
|
+
toolUsage['exec'] = Math.max(0, toolUsage['exec'] - benignExecCount);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
Object.keys(toolUsage).forEach(function(tool) {
|
|
302
|
+
if (toolUsage[tool] >= 10) { // Bumped threshold from 5 to 10
|
|
303
|
+
signals.push('high_tool_usage:' + tool);
|
|
304
|
+
}
|
|
305
|
+
// Detect repeated exec usage (often a sign of manual loops or inefficient automation)
|
|
306
|
+
if (tool === 'exec' && toolUsage[tool] >= 5) { // Bumped threshold from 3 to 5
|
|
307
|
+
signals.push('repeated_tool_usage:exec');
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// --- Signal prioritization ---
|
|
312
|
+
// Remove cosmetic signals when actionable signals exist
|
|
313
|
+
var actionable = signals.filter(function (s) {
|
|
314
|
+
return s !== 'user_missing' && s !== 'memory_missing' && s !== 'session_logs_missing' && s !== 'windows_shell_incompatible';
|
|
315
|
+
});
|
|
316
|
+
// If we have actionable signals, drop the cosmetic ones
|
|
317
|
+
if (actionable.length > 0) {
|
|
318
|
+
signals = actionable;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// --- De-duplication: suppress signals that have been over-processed ---
|
|
322
|
+
if (history.suppressedSignals.size > 0) {
|
|
323
|
+
var beforeDedup = signals.length;
|
|
324
|
+
signals = signals.filter(function (s) {
|
|
325
|
+
// Normalize signal key for comparison
|
|
326
|
+
var key = s.startsWith('errsig:') ? 'errsig'
|
|
327
|
+
: s.startsWith('recurring_errsig') ? 'recurring_errsig'
|
|
328
|
+
: s.startsWith('user_feature_request:') ? 'user_feature_request'
|
|
329
|
+
: s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
|
|
330
|
+
: s;
|
|
331
|
+
return !history.suppressedSignals.has(key);
|
|
332
|
+
});
|
|
333
|
+
if (beforeDedup > 0 && signals.length === 0) {
|
|
334
|
+
// All signals were suppressed = system is stable but stuck in a loop
|
|
335
|
+
// Force innovation
|
|
336
|
+
signals.push('evolution_stagnation_detected');
|
|
337
|
+
signals.push('stable_success_plateau');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// --- Force innovation after 3+ consecutive repairs ---
|
|
342
|
+
if (history.consecutiveRepairCount >= 3) {
|
|
343
|
+
// Remove repair-only signals (log_error, errsig) and inject innovation signals
|
|
344
|
+
signals = signals.filter(function (s) {
|
|
345
|
+
return s !== 'log_error' && !s.startsWith('errsig:') && !s.startsWith('recurring_errsig');
|
|
346
|
+
});
|
|
347
|
+
if (signals.length === 0) {
|
|
348
|
+
signals.push('repair_loop_detected');
|
|
349
|
+
signals.push('stable_success_plateau');
|
|
350
|
+
}
|
|
351
|
+
// Append a directive signal that the prompt can pick up
|
|
352
|
+
signals.push('force_innovation_after_repair_loop');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// --- Force innovation after too many empty cycles (zero blast radius) ---
|
|
356
|
+
// If >= 50% of last 8 cycles produced no code changes, the evolver is spinning idle.
|
|
357
|
+
// Strip repair signals and force innovate to break the empty loop.
|
|
358
|
+
if (history.emptyCycleCount >= 4) {
|
|
359
|
+
signals = signals.filter(function (s) {
|
|
360
|
+
return s !== 'log_error' && !s.startsWith('errsig:') && !s.startsWith('recurring_errsig');
|
|
361
|
+
});
|
|
362
|
+
if (!signals.includes('empty_cycle_loop_detected')) signals.push('empty_cycle_loop_detected');
|
|
363
|
+
if (!signals.includes('stable_success_plateau')) signals.push('stable_success_plateau');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// --- Saturation detection (graceful degradation) ---
|
|
367
|
+
// When consecutive empty cycles pile up at the tail, the evolver has exhausted its
|
|
368
|
+
// innovation space. Instead of spinning idle forever, signal that the system should
|
|
369
|
+
// switch to steady-state maintenance mode with reduced evolution frequency.
|
|
370
|
+
// This directly addresses the Echo-MingXuan failure: Cycle #55 hit "no committable
|
|
371
|
+
// code changes" and load spiked to 1.30 because there was no degradation strategy.
|
|
372
|
+
if (history.consecutiveEmptyCycles >= 5) {
|
|
373
|
+
if (!signals.includes('force_steady_state')) signals.push('force_steady_state');
|
|
374
|
+
if (!signals.includes('evolution_saturation')) signals.push('evolution_saturation');
|
|
375
|
+
} else if (history.consecutiveEmptyCycles >= 3) {
|
|
376
|
+
if (!signals.includes('evolution_saturation')) signals.push('evolution_saturation');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// --- Failure streak awareness ---
|
|
380
|
+
// When the evolver has failed many consecutive cycles, inject a signal
|
|
381
|
+
// telling the LLM to be more conservative and avoid repeating the same approach.
|
|
382
|
+
if (history.consecutiveFailureCount >= 3) {
|
|
383
|
+
signals.push('consecutive_failure_streak_' + history.consecutiveFailureCount);
|
|
384
|
+
// After 5+ consecutive failures, force a strategy change (don't keep trying the same thing)
|
|
385
|
+
if (history.consecutiveFailureCount >= 5) {
|
|
386
|
+
signals.push('failure_loop_detected');
|
|
387
|
+
// Strip the dominant gene's signals to force a different gene selection
|
|
388
|
+
var topGene = null;
|
|
389
|
+
var topGeneCount = 0;
|
|
390
|
+
var gfEntries = Object.entries(history.geneFreq);
|
|
391
|
+
for (var gfi = 0; gfi < gfEntries.length; gfi++) {
|
|
392
|
+
if (gfEntries[gfi][1] > topGeneCount) {
|
|
393
|
+
topGeneCount = gfEntries[gfi][1];
|
|
394
|
+
topGene = gfEntries[gfi][0];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (topGene) {
|
|
398
|
+
signals.push('ban_gene:' + topGene);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// High failure ratio in recent history (>= 75% failed in last 8 cycles)
|
|
404
|
+
if (history.recentFailureRatio >= 0.75) {
|
|
405
|
+
signals.push('high_failure_ratio');
|
|
406
|
+
signals.push('force_innovation_after_repair_loop');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// If no signals at all, add a default innovation signal
|
|
410
|
+
if (signals.length === 0) {
|
|
411
|
+
signals.push('stable_success_plateau');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return Array.from(new Set(signals));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
module.exports = { extractSignals, hasOpportunitySignal, analyzeRecentHistory, OPPORTUNITY_SIGNALS };
|