@evomap/evolver 1.90.0 → 2.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -564
- package/bin/evolver-llm-proxy.js +3 -0
- package/bin/evolver-mcp.js +2 -0
- package/bin/evolver-proxy.js +3 -0
- package/bin/evolver.js +4 -0
- package/index.js +1 -3670
- package/package.json +32 -62
- package/CONTRIBUTING.md +0 -19
- package/LICENSE +0 -641
- package/README.ja-JP.md +0 -521
- package/README.ko-KR.md +0 -520
- package/README.zh-CN.md +0 -531
- package/SKILL.md +0 -365
- package/assets/cover.png +0 -0
- package/assets/gep/genes.seed.json +0 -496
- package/conformance/savings-core/constants.json +0 -30
- package/conformance/savings-core/golden-vectors.json +0 -333
- package/scripts/a2a_export.js +0 -63
- package/scripts/a2a_ingest.js +0 -79
- package/scripts/a2a_promote.js +0 -118
- package/scripts/analyze_by_skill.js +0 -121
- package/scripts/build_binaries.js +0 -479
- package/scripts/check-changelog.js +0 -166
- package/scripts/extract_log.js +0 -85
- package/scripts/generate_history.js +0 -75
- package/scripts/gep_append_event.js +0 -96
- package/scripts/gep_personality_report.js +0 -234
- package/scripts/human_report.js +0 -147
- package/scripts/recall-verify-report.js +0 -234
- package/scripts/recover_loop.js +0 -61
- package/scripts/refresh_stars_badge.js +0 -168
- package/scripts/seed-merchants.js +0 -91
- package/scripts/skill2recipes.js +0 -118
- package/scripts/suggest_version.js +0 -89
- package/scripts/validate-modules.js +0 -38
- package/scripts/validate-suite.js +0 -78
- package/src/adapters/claudeCode.js +0 -194
- package/src/adapters/codex.js +0 -216
- package/src/adapters/cursor.js +0 -91
- package/src/adapters/hookAdapter.js +0 -469
- package/src/adapters/kiro.js +0 -195
- package/src/adapters/opencode.js +0 -326
- package/src/adapters/scripts/_lockPaths.js +0 -74
- package/src/adapters/scripts/_memoryFiltering.js +0 -35
- package/src/adapters/scripts/_runtimePaths.js +0 -440
- package/src/adapters/scripts/evolver-session-end.js +0 -321
- package/src/adapters/scripts/evolver-session-start.js +0 -587
- package/src/adapters/scripts/evolver-signal-detect.js +0 -98
- package/src/adapters/scripts/evolver-task-recall.js +0 -173
- package/src/atp/atpExecute.js +0 -283
- package/src/atp/atpTaskPickup.js +0 -233
- package/src/atp/autoBuyer.js +0 -382
- package/src/atp/autoDeliver.js +0 -215
- package/src/atp/cli.js +0 -354
- package/src/atp/cliAutobuyPrompt.js +0 -154
- package/src/atp/consumerAgent.js +0 -157
- package/src/atp/defaultHandler.js +0 -69
- package/src/atp/heartbeatSignalsHandler.js +0 -254
- package/src/atp/hubClient.js +0 -317
- package/src/atp/index.js +0 -38
- package/src/atp/merchantAgent.js +0 -118
- package/src/atp/protocol.js +0 -41
- package/src/atp/questionComposer.js +0 -133
- package/src/atp/serviceHelper.js +0 -92
- package/src/canary.js +0 -13
- package/src/config.js +0 -351
- package/src/evolve/guards.js +0 -1
- package/src/evolve/pipeline/collect.js +0 -1
- package/src/evolve/pipeline/dispatch.js +0 -1
- package/src/evolve/pipeline/enrich.js +0 -1
- package/src/evolve/pipeline/hub.js +0 -1
- package/src/evolve/pipeline/select.js +0 -1
- package/src/evolve/pipeline/signals.js +0 -1
- package/src/evolve/utils.js +0 -1
- package/src/evolve.js +0 -1
- package/src/experiment/agentRunner.js +0 -229
- package/src/experiment/cli.js +0 -159
- package/src/experiment/comparison.js +0 -233
- package/src/experiment/metrics.js +0 -75
- package/src/forceUpdate.js +0 -991
- package/src/gep/a2a.js +0 -173
- package/src/gep/a2aProtocol.js +0 -1
- package/src/gep/analyzer.js +0 -35
- package/src/gep/antiAbuseTelemetry.js +0 -1
- package/src/gep/assetCallLog.js +0 -197
- package/src/gep/assetStore.js +0 -723
- package/src/gep/assets.js +0 -36
- package/src/gep/autoDistillConv.js +0 -1
- package/src/gep/autoDistillLlm.js +0 -1
- package/src/gep/bridge.js +0 -138
- package/src/gep/candidateEval.js +0 -1
- package/src/gep/candidates.js +0 -1
- package/src/gep/claimNudge.js +0 -121
- package/src/gep/cliContracts.js +0 -1190
- package/src/gep/contentHash.js +0 -1
- package/src/gep/conversationDistiller.js +0 -1
- package/src/gep/conversationSniffer.js +0 -1
- package/src/gep/crypto.js +0 -1
- package/src/gep/curriculum.js +0 -1
- package/src/gep/deviceId.js +0 -1
- package/src/gep/directoryClient.js +0 -115
- package/src/gep/envFingerprint.js +0 -1
- package/src/gep/epigenetics.js +0 -1
- package/src/gep/execBridge.js +0 -1
- package/src/gep/executionTrace.js +0 -291
- package/src/gep/explore.js +0 -1
- package/src/gep/featureFlags.js +0 -121
- package/src/gep/gitOps.js +0 -265
- package/src/gep/hash.js +0 -1
- package/src/gep/hostErrorClassifier.js +0 -34
- package/src/gep/hubFetch.js +0 -1
- package/src/gep/hubReview.js +0 -1
- package/src/gep/hubSearch.js +0 -1
- package/src/gep/hubVerify.js +0 -1
- package/src/gep/idleScheduler.js +0 -400
- package/src/gep/issueReporter.js +0 -416
- package/src/gep/learningSignals.js +0 -1
- package/src/gep/llmReview.js +0 -92
- package/src/gep/localStateAwareness.js +0 -243
- package/src/gep/mailboxTransport.js +0 -119
- package/src/gep/memoryGraph.js +0 -1
- package/src/gep/memoryGraphAdapter.js +0 -1
- package/src/gep/mutation.js +0 -1
- package/src/gep/narrativeMemory.js +0 -1
- package/src/gep/oauthLogin.js +0 -181
- package/src/gep/openPRRegistry.js +0 -1
- package/src/gep/paths.js +0 -522
- package/src/gep/personality.js +0 -1
- package/src/gep/policyCheck.js +0 -1
- package/src/gep/portable.js +0 -103
- package/src/gep/privacyClient.js +0 -235
- package/src/gep/prompt.js +0 -1
- package/src/gep/questionGenerator.js +0 -518
- package/src/gep/recallInject.js +0 -1
- package/src/gep/recallVerifier.js +0 -1
- package/src/gep/reflection.js +0 -1
- package/src/gep/sanitize.js +0 -264
- package/src/gep/savingsCore.js +0 -1
- package/src/gep/schemas/capsule.js +0 -170
- package/src/gep/schemas/gene.js +0 -154
- package/src/gep/schemas/index.js +0 -8
- package/src/gep/schemas/protocol.js +0 -51
- package/src/gep/schemas/task.js +0 -74
- package/src/gep/selector.js +0 -1
- package/src/gep/selfPR.js +0 -469
- package/src/gep/signals.js +0 -776
- package/src/gep/skill2gep.js +0 -1056
- package/src/gep/skill2gepAudit.js +0 -303
- package/src/gep/skill2recipes.js +0 -511
- package/src/gep/skillDistiller.js +0 -1
- package/src/gep/skillPublisher.js +0 -358
- package/src/gep/solidify.js +0 -1
- package/src/gep/strategy.js +0 -1
- package/src/gep/taskReceiver.js +0 -575
- package/src/gep/tokenSavings.js +0 -1
- package/src/gep/trajectoryExport.js +0 -1
- package/src/gep/validationReport.js +0 -55
- package/src/gep/validator/index.js +0 -411
- package/src/gep/validator/reporter.js +0 -210
- package/src/gep/validator/sandboxExecutor.js +0 -480
- package/src/gep/validator/stakeBootstrap.js +0 -357
- package/src/gep/workspaceKeychain.js +0 -1
- package/src/ops/cleanup.js +0 -80
- package/src/ops/commentary.js +0 -60
- package/src/ops/health_check.js +0 -104
- package/src/ops/index.js +0 -11
- package/src/ops/innovation.js +0 -67
- package/src/ops/lifecycle.js +0 -798
- package/src/ops/self_repair.js +0 -76
- package/src/ops/skills_monitor.js +0 -147
- package/src/ops/trigger.js +0 -33
- package/src/proxy/clientSettings.js +0 -405
- package/src/proxy/envelope.js +0 -59
- package/src/proxy/extensions/dmHandler.js +0 -45
- package/src/proxy/extensions/sessionHandler.js +0 -141
- package/src/proxy/extensions/skillUpdater.js +0 -64
- package/src/proxy/extensions/traceControl.js +0 -1
- package/src/proxy/index.js +0 -1395
- package/src/proxy/inject.js +0 -1
- package/src/proxy/lifecycle/manager.js +0 -1568
- package/src/proxy/mailbox/state.js +0 -207
- package/src/proxy/mailbox/store.js +0 -602
- package/src/proxy/router/cache_passthrough.js +0 -26
- package/src/proxy/router/features.js +0 -84
- package/src/proxy/router/gemini_route.js +0 -154
- package/src/proxy/router/messages_route.js +0 -535
- package/src/proxy/router/model_router.js +0 -113
- package/src/proxy/router/models_route.js +0 -52
- package/src/proxy/router/ollama_route.js +0 -103
- package/src/proxy/router/responses_route.js +0 -170
- package/src/proxy/router/vertex_route.js +0 -110
- package/src/proxy/server/http.js +0 -363
- package/src/proxy/server/routes.js +0 -558
- package/src/proxy/server/settings.js +0 -115
- package/src/proxy/sync/engine.js +0 -179
- package/src/proxy/sync/inbound.js +0 -211
- package/src/proxy/sync/outbound.js +0 -361
- package/src/proxy/task/monitor.js +0 -131
- package/src/proxy/trace/extractor.js +0 -1
- package/src/proxy/trace/usage.js +0 -1
- package/src/solo/breaker.js +0 -25
- package/src/solo/gitGuard.js +0 -65
- package/src/webui/client/clientJs/assets.js +0 -111
- package/src/webui/client/clientJs/bootstrap.js +0 -92
- package/src/webui/client/clientJs/common.js +0 -77
- package/src/webui/client/clientJs/i18n.js +0 -366
- package/src/webui/client/clientJs/index.js +0 -35
- package/src/webui/client/clientJs/interactions.js +0 -351
- package/src/webui/client/clientJs/overview.js +0 -152
- package/src/webui/client/clientJs/personality.js +0 -285
- package/src/webui/client/clientJs/pipelines.js +0 -330
- package/src/webui/client/indexHtml.js +0 -221
- package/src/webui/client/static.js +0 -23
- package/src/webui/client/stylesCss.js +0 -639
- package/src/webui/client/vendor/README.md +0 -15
- package/src/webui/client/vendor/echarts.min.js +0 -45
- package/src/webui/index.js +0 -14
- package/src/webui/observer/assets.js +0 -146
- package/src/webui/observer/index.js +0 -37
- package/src/webui/observer/interactions.js +0 -127
- package/src/webui/observer/jsonl.js +0 -75
- package/src/webui/observer/paths.js +0 -46
- package/src/webui/observer/personality.js +0 -43
- package/src/webui/observer/pipelineEvents.js +0 -58
- package/src/webui/observer/redact.js +0 -63
- package/src/webui/observer/runs.js +0 -356
- package/src/webui/observer/safety.js +0 -57
- package/src/webui/observer/skills.js +0 -70
- package/src/webui/observer/status.js +0 -71
- package/src/webui/server/http.js +0 -138
- package/src/webui/server/routes.js +0 -41
package/src/gep/signals.js
DELETED
|
@@ -1,776 +0,0 @@
|
|
|
1
|
-
var { isHostClientError } = require('./hostErrorClassifier');
|
|
2
|
-
|
|
3
|
-
// Opportunity signal names (shared with mutation.js and personality.js).
|
|
4
|
-
var OPPORTUNITY_SIGNALS = [
|
|
5
|
-
'user_feature_request',
|
|
6
|
-
'user_improvement_suggestion',
|
|
7
|
-
'perf_bottleneck',
|
|
8
|
-
'capability_gap',
|
|
9
|
-
'stable_success_plateau',
|
|
10
|
-
'external_opportunity',
|
|
11
|
-
'recurring_error',
|
|
12
|
-
'unsupported_input_type',
|
|
13
|
-
'evolution_stagnation_detected',
|
|
14
|
-
'repair_loop_detected',
|
|
15
|
-
'force_innovation_after_repair_loop',
|
|
16
|
-
'tool_bypass',
|
|
17
|
-
'curriculum_target',
|
|
18
|
-
'issue_already_resolved',
|
|
19
|
-
'openclaw_self_healed',
|
|
20
|
-
'empty_cycle_loop_detected',
|
|
21
|
-
'explore_opportunity',
|
|
22
|
-
'hub_search_miss_with_problem',
|
|
23
|
-
'plateau_pivot_required',
|
|
24
|
-
'plateau_pivot_suggested',
|
|
25
|
-
];
|
|
26
|
-
|
|
27
|
-
function hasOpportunitySignal(signals) {
|
|
28
|
-
var list = Array.isArray(signals) ? signals : [];
|
|
29
|
-
for (var i = 0; i < OPPORTUNITY_SIGNALS.length; i++) {
|
|
30
|
-
var name = OPPORTUNITY_SIGNALS[i];
|
|
31
|
-
if (list.includes(name)) return true;
|
|
32
|
-
if (list.some(function (s) { return String(s).startsWith(name + ':'); })) return true;
|
|
33
|
-
}
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// A cycle that produced no file/line changes is a no-op ("empty cycle"), not a
|
|
38
|
-
// genuine failure -- even when its outcome is recorded as `failed`. The most
|
|
39
|
-
// common way a `failed` event ends up with zero blast radius is a claimed-
|
|
40
|
-
// success cycle rejected by the anti-forgery guard (`zero_blast_radius_with_
|
|
41
|
-
// success`): the sub-agent declared success but touched nothing -- e.g. an
|
|
42
|
-
// `innovate` gene that only produces metadata/strategy and never edits code.
|
|
43
|
-
// Empty cycles are tracked separately (consecutiveEmptyCycles) and drive
|
|
44
|
-
// graceful steady-state degradation, NOT the failure-loop ban. Single source of
|
|
45
|
-
// truth so the empty/failure split stays consistent across every counter below.
|
|
46
|
-
function _isEmptyCycle(evt) {
|
|
47
|
-
if (!evt) return false;
|
|
48
|
-
if (evt.meta && evt.meta.empty_cycle) return true;
|
|
49
|
-
var br = evt.blast_radius;
|
|
50
|
-
return !!(br && br.files === 0 && br.lines === 0);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Build a de-duplication set from recent evolution events.
|
|
54
|
-
// Returns an object: { suppressedSignals: Set<string>, recentIntents: string[], consecutiveRepairCount: number }
|
|
55
|
-
function analyzeRecentHistory(recentEvents) {
|
|
56
|
-
if (!Array.isArray(recentEvents) || recentEvents.length === 0) {
|
|
57
|
-
return { suppressedSignals: new Set(), recentIntents: [], consecutiveRepairCount: 0 };
|
|
58
|
-
}
|
|
59
|
-
// Take only the last 10 events
|
|
60
|
-
var recent = recentEvents.slice(-10);
|
|
61
|
-
|
|
62
|
-
// Count consecutive same-intent runs at the tail
|
|
63
|
-
var consecutiveRepairCount = 0;
|
|
64
|
-
for (var i = recent.length - 1; i >= 0; i--) {
|
|
65
|
-
if (recent[i].intent === 'repair') {
|
|
66
|
-
consecutiveRepairCount++;
|
|
67
|
-
} else {
|
|
68
|
-
break;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Count signal frequency in last 8 events: signal -> count
|
|
73
|
-
var signalFreq = {};
|
|
74
|
-
var geneFreq = {};
|
|
75
|
-
var tail = recent.slice(-8);
|
|
76
|
-
for (var j = 0; j < tail.length; j++) {
|
|
77
|
-
var evt = tail[j];
|
|
78
|
-
var sigs = Array.isArray(evt.signals) ? evt.signals : [];
|
|
79
|
-
for (var k = 0; k < sigs.length; k++) {
|
|
80
|
-
var s = String(sigs[k]);
|
|
81
|
-
// Normalize: strip details suffix so frequency keys match dedup filter keys
|
|
82
|
-
var key = s.startsWith('errsig:') ? 'errsig'
|
|
83
|
-
: s.startsWith('recurring_errsig') ? 'recurring_errsig'
|
|
84
|
-
: s.startsWith('user_feature_request:') ? 'user_feature_request'
|
|
85
|
-
: s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
|
|
86
|
-
: s;
|
|
87
|
-
signalFreq[key] = (signalFreq[key] || 0) + 1;
|
|
88
|
-
}
|
|
89
|
-
var genes = Array.isArray(evt.genes_used) ? evt.genes_used : [];
|
|
90
|
-
for (var g = 0; g < genes.length; g++) {
|
|
91
|
-
geneFreq[String(genes[g])] = (geneFreq[String(genes[g])] || 0) + 1;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Suppress signals that appeared in 3+ of the last 8 events (they are being over-processed)
|
|
96
|
-
var suppressedSignals = new Set();
|
|
97
|
-
var entries = Object.entries(signalFreq);
|
|
98
|
-
for (var ei = 0; ei < entries.length; ei++) {
|
|
99
|
-
if (entries[ei][1] >= 3) {
|
|
100
|
-
suppressedSignals.add(entries[ei][0]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
var recentIntents = recent.map(function(e) { return e.intent || 'unknown'; });
|
|
105
|
-
|
|
106
|
-
// Count empty cycles (no file/line changes) in last 8 events.
|
|
107
|
-
// High ratio indicates the evolver is spinning without producing real changes.
|
|
108
|
-
var emptyCycleCount = 0;
|
|
109
|
-
for (var ec = 0; ec < tail.length; ec++) {
|
|
110
|
-
if (_isEmptyCycle(tail[ec])) {
|
|
111
|
-
emptyCycleCount++;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Count consecutive empty cycles at the tail (not just total in last 8).
|
|
116
|
-
// This detects saturation: the evolver has exhausted innovation space and keeps producing
|
|
117
|
-
// zero-change cycles. Used to trigger graceful degradation to steady-state mode.
|
|
118
|
-
var consecutiveEmptyCycles = 0;
|
|
119
|
-
for (var se = recent.length - 1; se >= 0; se--) {
|
|
120
|
-
if (_isEmptyCycle(recent[se])) {
|
|
121
|
-
consecutiveEmptyCycles++;
|
|
122
|
-
} else {
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Count consecutive *productive* failures at the tail of recent events.
|
|
128
|
-
// This tells the evolver "you have been failing N times in a row -- slow down."
|
|
129
|
-
//
|
|
130
|
-
// A no-op (zero-blast) cycle breaks the streak instead of extending it: it is
|
|
131
|
-
// already counted as an empty cycle above, which drives graceful steady-state
|
|
132
|
-
// degradation. Without this guard a gene that keeps producing empty cycles --
|
|
133
|
-
// or a sub-agent whose claimed-success cycles are anti-forgery-rejected to
|
|
134
|
-
// `failed` with zero blast radius -- would double-trigger failure_loop_detected
|
|
135
|
-
// + ban_gene and file a spurious recurring-failure GitHub issue (see #577).
|
|
136
|
-
var consecutiveFailureCount = 0;
|
|
137
|
-
for (var cf = recent.length - 1; cf >= 0; cf--) {
|
|
138
|
-
if (_isEmptyCycle(recent[cf])) break;
|
|
139
|
-
var outcome = recent[cf].outcome;
|
|
140
|
-
if (outcome && outcome.status === 'failed') {
|
|
141
|
-
consecutiveFailureCount++;
|
|
142
|
-
} else {
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Count total *productive* failures in last 8 events (failure ratio).
|
|
148
|
-
// Empty (zero-blast) cycles are excluded for the same reason as the
|
|
149
|
-
// consecutive-failure streak above -- a no-op is saturation, not a failure.
|
|
150
|
-
var recentFailureCount = 0;
|
|
151
|
-
for (var rf = 0; rf < tail.length; rf++) {
|
|
152
|
-
if (_isEmptyCycle(tail[rf])) continue;
|
|
153
|
-
var rfOut = tail[rf].outcome;
|
|
154
|
-
if (rfOut && rfOut.status === 'failed') recentFailureCount++;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return {
|
|
158
|
-
suppressedSignals: suppressedSignals,
|
|
159
|
-
recentIntents: recentIntents,
|
|
160
|
-
consecutiveRepairCount: consecutiveRepairCount,
|
|
161
|
-
emptyCycleCount: emptyCycleCount,
|
|
162
|
-
consecutiveEmptyCycles: consecutiveEmptyCycles,
|
|
163
|
-
consecutiveFailureCount: consecutiveFailureCount,
|
|
164
|
-
recentFailureCount: recentFailureCount,
|
|
165
|
-
recentFailureRatio: tail.length > 0 ? recentFailureCount / tail.length : 0,
|
|
166
|
-
signalFreq: signalFreq,
|
|
167
|
-
geneFreq: geneFreq,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
// Signal Extraction Strategy: Weighted Keyword Scoring (Layer 2)
|
|
173
|
-
// Unlike regex (binary hit/miss), keyword scoring accumulates weighted
|
|
174
|
-
// evidence from multiple keywords and fires only when confidence exceeds
|
|
175
|
-
// a threshold. This catches fuzzy/distributed patterns that no single
|
|
176
|
-
// regex can match.
|
|
177
|
-
// ---------------------------------------------------------------------------
|
|
178
|
-
var SIGNAL_PROFILES = {
|
|
179
|
-
perf_bottleneck: {
|
|
180
|
-
keywords: { 'slow': 3, 'timeout': 4, 'timed out': 4, 'latency': 3, 'bottleneck': 5,
|
|
181
|
-
'lag': 2, 'delay': 2, 'hung': 3, 'freeze': 3, 'unresponsive': 4,
|
|
182
|
-
'took too long': 4, 'high cpu': 4, 'high memory': 4, 'oom': 5,
|
|
183
|
-
'out of memory': 5, 'performance': 2, 'throttle': 3 },
|
|
184
|
-
threshold: 6,
|
|
185
|
-
},
|
|
186
|
-
capability_gap: {
|
|
187
|
-
keywords: { 'not supported': 5, 'cannot': 1, 'unsupported': 4, 'not implemented': 5,
|
|
188
|
-
'no way to': 3, 'missing feature': 5, 'not available': 3,
|
|
189
|
-
'no support for': 4, 'unavailable': 3, 'incompatible': 3 },
|
|
190
|
-
threshold: 5,
|
|
191
|
-
},
|
|
192
|
-
user_feature_request: {
|
|
193
|
-
keywords: { 'add': 1, 'implement': 3, 'create': 2, 'build': 2, 'feature': 3,
|
|
194
|
-
'i want': 3, 'i need': 3, 'we need': 3, 'please add': 4,
|
|
195
|
-
'new function': 4, 'new module': 4, 'endpoint': 2, 'capability': 2,
|
|
196
|
-
'support for': 2 },
|
|
197
|
-
threshold: 6,
|
|
198
|
-
},
|
|
199
|
-
user_improvement_suggestion: {
|
|
200
|
-
keywords: { 'improve': 3, 'enhance': 3, 'upgrade': 3, 'refactor': 4,
|
|
201
|
-
'clean up': 3, 'simplify': 3, 'streamline': 3, 'optimize': 3,
|
|
202
|
-
'could be better': 4, 'should be': 2, 'more efficient': 3 },
|
|
203
|
-
threshold: 5,
|
|
204
|
-
},
|
|
205
|
-
recurring_error: {
|
|
206
|
-
keywords: { 'error': 1, 'exception': 2, 'failed': 1, 'crash': 4,
|
|
207
|
-
'again': 1, 'still': 1, 'keeps': 2, 'repeatedly': 4,
|
|
208
|
-
'same error': 5, 'still failing': 5, 'not fixed': 4 },
|
|
209
|
-
threshold: 7,
|
|
210
|
-
},
|
|
211
|
-
tool_bypass: {
|
|
212
|
-
keywords: { 'exec': 2, 'shell': 2, 'subprocess': 3, 'child_process': 3,
|
|
213
|
-
'curl': 2, 'wget': 2, 'ad-hoc': 3, 'workaround': 3,
|
|
214
|
-
'hack': 2, 'manual': 1 },
|
|
215
|
-
threshold: 6,
|
|
216
|
-
},
|
|
217
|
-
evolution_stagnation_detected: {
|
|
218
|
-
keywords: { 'no change': 4, 'same result': 4, 'stuck': 3, 'plateau': 4,
|
|
219
|
-
'stagnant': 5, 'no progress': 5, 'spinning': 3, 'idle': 2,
|
|
220
|
-
'nothing new': 4, 'exhausted': 3 },
|
|
221
|
-
threshold: 6,
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
function _extractKeywordScore(lower) {
|
|
226
|
-
var scored = [];
|
|
227
|
-
var profileKeys = Object.keys(SIGNAL_PROFILES);
|
|
228
|
-
for (var pi = 0; pi < profileKeys.length; pi++) {
|
|
229
|
-
var signalName = profileKeys[pi];
|
|
230
|
-
var profile = SIGNAL_PROFILES[signalName];
|
|
231
|
-
var totalScore = 0;
|
|
232
|
-
var kwKeys = Object.keys(profile.keywords);
|
|
233
|
-
for (var ki = 0; ki < kwKeys.length; ki++) {
|
|
234
|
-
var kw = kwKeys[ki];
|
|
235
|
-
var weight = profile.keywords[kw];
|
|
236
|
-
var idx = 0;
|
|
237
|
-
var count = 0;
|
|
238
|
-
while (idx < lower.length && count < 20) {
|
|
239
|
-
var pos = lower.indexOf(kw, idx);
|
|
240
|
-
if (pos === -1) break;
|
|
241
|
-
count++;
|
|
242
|
-
idx = pos + kw.length;
|
|
243
|
-
}
|
|
244
|
-
totalScore += count * weight;
|
|
245
|
-
}
|
|
246
|
-
if (totalScore >= profile.threshold) {
|
|
247
|
-
scored.push(signalName);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
return scored;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// ---------------------------------------------------------------------------
|
|
254
|
-
// Signal Extraction Strategy: LLM Semantic Analysis (Layer 3)
|
|
255
|
-
// Sends a corpus summary to the Hub for LLM-based signal extraction.
|
|
256
|
-
// Rate-limited to every N evolution cycles. Falls back silently on failure.
|
|
257
|
-
// ---------------------------------------------------------------------------
|
|
258
|
-
var _llmSignalCycleCount = 0;
|
|
259
|
-
var LLM_SIGNAL_INTERVAL = 5;
|
|
260
|
-
|
|
261
|
-
function _curlHubIpFamilyArgs(env) {
|
|
262
|
-
return _curlHubIpFamilyAttempts(env)[0].slice();
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function _resolveCurlHubIpFamily(env) {
|
|
266
|
-
var source = env || process.env;
|
|
267
|
-
var raw = String(source.EVOMAP_HUB_IP_FAMILY || 'ipv4first').trim().toLowerCase();
|
|
268
|
-
if (raw === 'ipv4' || raw === 'v4' || raw === '4' || raw === 'ipv4first' || raw === 'ipv4-first') return 'ipv4first';
|
|
269
|
-
if (raw === 'ipv4only' || raw === 'ipv4-only') return 'ipv4only';
|
|
270
|
-
if (raw === 'auto' || raw === 'dualstack' || raw === 'dual-stack') return 'auto';
|
|
271
|
-
throw new Error('[signals] EVOMAP_HUB_IP_FAMILY must be "ipv4", "ipv4-only", or "auto" - got ' + JSON.stringify(source.EVOMAP_HUB_IP_FAMILY));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function _curlHubIpFamilyAttempts(env) {
|
|
275
|
-
var family = _resolveCurlHubIpFamily(env);
|
|
276
|
-
if (family === 'auto') return [[]];
|
|
277
|
-
if (family === 'ipv4only') return [['-4']];
|
|
278
|
-
// Match hubFetch's default Hub egress posture: IPv4-first with a dual-stack
|
|
279
|
-
// fallback unless EVOMAP_HUB_IP_FAMILY explicitly disables fallback.
|
|
280
|
-
return [['-4'], []];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function _curlHeaderConfigValue(value) {
|
|
284
|
-
return '"' + String(value)
|
|
285
|
-
.replace(/\\/g, '\\\\')
|
|
286
|
-
.replace(/"/g, '\\"')
|
|
287
|
-
.replace(/\r/g, '\\r')
|
|
288
|
-
.replace(/\n/g, '\\n')
|
|
289
|
-
.replace(/\t/g, '\\t') + '"';
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function _curlAuthConfig(nodeSecret) {
|
|
293
|
-
return 'header = ' + _curlHeaderConfigValue('Authorization: Bearer ' + nodeSecret) + '\n';
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function _withTempPayloadFile(payload, fn) {
|
|
297
|
-
var fs = require('fs');
|
|
298
|
-
var os = require('os');
|
|
299
|
-
var path = require('path');
|
|
300
|
-
var dir = fs.mkdtempSync(path.join(os.tmpdir(), 'evolver-signal-'));
|
|
301
|
-
var file = path.join(dir, 'payload.json');
|
|
302
|
-
try {
|
|
303
|
-
fs.writeFileSync(file, payload, { encoding: 'utf8', mode: 0o600 });
|
|
304
|
-
return fn(file);
|
|
305
|
-
} finally {
|
|
306
|
-
try { fs.unlinkSync(file); } catch (_) {}
|
|
307
|
-
try { fs.rmdirSync(dir); } catch (_) {}
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function _extractLLM(corpus) {
|
|
312
|
-
_llmSignalCycleCount++;
|
|
313
|
-
if (_llmSignalCycleCount % LLM_SIGNAL_INTERVAL !== 1) return [];
|
|
314
|
-
|
|
315
|
-
try {
|
|
316
|
-
var getHubUrl = require('./a2aProtocol').getHubUrl;
|
|
317
|
-
var getHubNodeSecret = require('./a2aProtocol').getHubNodeSecret;
|
|
318
|
-
var getNodeId = require('./a2aProtocol').getNodeId;
|
|
319
|
-
var enforceHubScheme = require('./hubFetch').enforceHubScheme;
|
|
320
|
-
var hubUrl = getHubUrl();
|
|
321
|
-
var nodeSecret = getHubNodeSecret();
|
|
322
|
-
if (!hubUrl || !nodeSecret) return [];
|
|
323
|
-
|
|
324
|
-
var summary = corpus.slice(0, 2000);
|
|
325
|
-
var postData = JSON.stringify({
|
|
326
|
-
corpus_summary: summary,
|
|
327
|
-
signal_types: OPPORTUNITY_SIGNALS,
|
|
328
|
-
sender_id: getNodeId() || undefined,
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
var url = hubUrl.replace(/\/+$/, '') + '/a2a/signal/analyze';
|
|
332
|
-
enforceHubScheme(url);
|
|
333
|
-
|
|
334
|
-
// Use execFileSync (no shell) + curl argv array so URL and file paths are
|
|
335
|
-
// passed as discrete argv entries. The bearer token is supplied through
|
|
336
|
-
// curl config on stdin, not argv, so process listings do not expose it.
|
|
337
|
-
// Sync HTTP is required because this runs inside a spin-wait loop where
|
|
338
|
-
// Node's async http callbacks cannot fire.
|
|
339
|
-
var execFileSync = require('child_process').execFileSync;
|
|
340
|
-
var stdout = '';
|
|
341
|
-
try {
|
|
342
|
-
stdout = _withTempPayloadFile(postData, function (payloadFile) {
|
|
343
|
-
var attempts = _curlHubIpFamilyAttempts(process.env);
|
|
344
|
-
var lastErr = null;
|
|
345
|
-
for (var i = 0; i < attempts.length; i++) {
|
|
346
|
-
var curlArgs = [
|
|
347
|
-
'-sS', '-m', '10',
|
|
348
|
-
].concat(attempts[i], [
|
|
349
|
-
'-X', 'POST',
|
|
350
|
-
'-H', 'Content-Type: application/json',
|
|
351
|
-
'--data-binary', '@' + payloadFile,
|
|
352
|
-
'-K', '-',
|
|
353
|
-
url,
|
|
354
|
-
]);
|
|
355
|
-
try {
|
|
356
|
-
return execFileSync('curl', curlArgs, {
|
|
357
|
-
input: _curlAuthConfig(nodeSecret),
|
|
358
|
-
timeout: 12000,
|
|
359
|
-
windowsHide: true,
|
|
360
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
361
|
-
encoding: 'utf8',
|
|
362
|
-
});
|
|
363
|
-
} catch (err) {
|
|
364
|
-
lastErr = err;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
throw lastErr || new Error('curl_failed');
|
|
368
|
-
});
|
|
369
|
-
} catch (_) {
|
|
370
|
-
return [];
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
if (!stdout || typeof stdout !== 'string') return [];
|
|
374
|
-
|
|
375
|
-
var parsed = JSON.parse(stdout);
|
|
376
|
-
if (Array.isArray(parsed.signals)) {
|
|
377
|
-
return parsed.signals.filter(function (s) {
|
|
378
|
-
return typeof s === 'string' && s.length > 0 && s.length < 200;
|
|
379
|
-
}).slice(0, 10);
|
|
380
|
-
}
|
|
381
|
-
return [];
|
|
382
|
-
} catch (e) {
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// ---------------------------------------------------------------------------
|
|
388
|
-
// Signal Merge: combine results from all extraction strategies.
|
|
389
|
-
// Deduplicates and logs per-layer contribution for observability.
|
|
390
|
-
// ---------------------------------------------------------------------------
|
|
391
|
-
function _mergeSignals(regexSignals, scoreSignals, llmSignals) {
|
|
392
|
-
var merged = new Set();
|
|
393
|
-
var ri, si, li;
|
|
394
|
-
for (ri = 0; ri < regexSignals.length; ri++) merged.add(regexSignals[ri]);
|
|
395
|
-
for (si = 0; si < scoreSignals.length; si++) merged.add(scoreSignals[si]);
|
|
396
|
-
for (li = 0; li < llmSignals.length; li++) merged.add(llmSignals[li]);
|
|
397
|
-
|
|
398
|
-
var scoreOnly = scoreSignals.filter(function (s) { return !regexSignals.includes(s); });
|
|
399
|
-
var llmOnly = llmSignals.filter(function (s) { return !regexSignals.includes(s) && !scoreSignals.includes(s); });
|
|
400
|
-
var overlap = regexSignals.filter(function (s) { return scoreSignals.includes(s) || llmSignals.includes(s); });
|
|
401
|
-
|
|
402
|
-
if (scoreOnly.length > 0 || llmOnly.length > 0 || overlap.length > 0) {
|
|
403
|
-
console.log('[Signals] Multi-strategy: regex=' + regexSignals.length +
|
|
404
|
-
', score=' + scoreSignals.length +
|
|
405
|
-
', llm=' + llmSignals.length +
|
|
406
|
-
', merged=' + merged.size +
|
|
407
|
-
(scoreOnly.length > 0 ? ' | score-only: ' + scoreOnly.join(', ') : '') +
|
|
408
|
-
(llmOnly.length > 0 ? ' | llm-only: ' + llmOnly.join(', ') : '') +
|
|
409
|
-
(overlap.length > 0 ? ' | confirmed: ' + overlap.join(', ') : ''));
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return Array.from(merged);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
// ---------------------------------------------------------------------------
|
|
416
|
-
// Signal Extraction Strategy: Regex Pattern Matching (Layer 1)
|
|
417
|
-
// Deterministic, zero-latency, hand-crafted rules for known signal patterns.
|
|
418
|
-
// ---------------------------------------------------------------------------
|
|
419
|
-
function _extractRegex(corpus, lower, errorHit) {
|
|
420
|
-
var signals = [];
|
|
421
|
-
|
|
422
|
-
if (errorHit) signals.push('log_error');
|
|
423
|
-
|
|
424
|
-
try {
|
|
425
|
-
var lines = corpus
|
|
426
|
-
.split('\n')
|
|
427
|
-
.map(function (l) { return String(l || '').trim(); })
|
|
428
|
-
.filter(Boolean);
|
|
429
|
-
|
|
430
|
-
var errLine =
|
|
431
|
-
lines.find(function (l) { return /\b(typeerror|referenceerror|syntaxerror)\b\s*:|error\s*:|exception\s*:|\[error|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/i.test(l); }) ||
|
|
432
|
-
null;
|
|
433
|
-
|
|
434
|
-
if (errLine) {
|
|
435
|
-
var clipped = errLine.replace(/\s+/g, ' ').slice(0, 260);
|
|
436
|
-
signals.push('errsig:' + clipped);
|
|
437
|
-
}
|
|
438
|
-
} catch (e) { /* error-line extraction non-critical */ }
|
|
439
|
-
|
|
440
|
-
if (lower.includes('memory.md missing')) signals.push('memory_missing');
|
|
441
|
-
if (lower.includes('user.md missing')) signals.push('user_missing');
|
|
442
|
-
if (lower.includes('key missing')) signals.push('integration_key_missing');
|
|
443
|
-
if (lower.includes('no session logs found') || lower.includes('no jsonl files')) signals.push('session_logs_missing');
|
|
444
|
-
if (process.platform === 'win32' && (lower.includes('pgrep') || lower.includes('ps aux') || lower.includes('cat >') || lower.includes('heredoc'))) {
|
|
445
|
-
signals.push('windows_shell_incompatible');
|
|
446
|
-
}
|
|
447
|
-
if (lower.includes('path.resolve(__dirname, \'../../../')) signals.push('path_outside_workspace');
|
|
448
|
-
|
|
449
|
-
// Protocol-specific drift signals
|
|
450
|
-
if (lower.includes('prompt') && !lower.includes('evolutionevent')) signals.push('protocol_drift');
|
|
451
|
-
|
|
452
|
-
// --- Recurring error detection (robustness signals) ---
|
|
453
|
-
// Count repeated identical errors -- these indicate systemic issues that need automated fixes
|
|
454
|
-
try {
|
|
455
|
-
var errorCounts = {};
|
|
456
|
-
var errPatterns = corpus.match(/(?:LLM error|"error"|"status":\s*"error")[^}]{0,200}/gi) || [];
|
|
457
|
-
for (var ep = 0; ep < errPatterns.length; ep++) {
|
|
458
|
-
// Normalize to a short key
|
|
459
|
-
var key = errPatterns[ep].replace(/\s+/g, ' ').slice(0, 100);
|
|
460
|
-
errorCounts[key] = (errorCounts[key] || 0) + 1;
|
|
461
|
-
}
|
|
462
|
-
var recurringErrors = Object.entries(errorCounts).filter(function (e) { return e[1] >= 3; });
|
|
463
|
-
if (recurringErrors.length > 0) {
|
|
464
|
-
signals.push('recurring_error');
|
|
465
|
-
// Include the top recurring error signature for the agent to diagnose
|
|
466
|
-
var topErr = recurringErrors.sort(function (a, b) { return b[1] - a[1]; })[0];
|
|
467
|
-
signals.push('recurring_errsig(' + topErr[1] + 'x):' + topErr[0].slice(0, 150));
|
|
468
|
-
}
|
|
469
|
-
} catch (e) { /* recurring error detection non-critical */ }
|
|
470
|
-
|
|
471
|
-
// --- Unsupported input type (e.g. GIF, video formats the LLM can't handle) ---
|
|
472
|
-
if (/unsupported mime|unsupported.*type|invalid.*mime/i.test(lower)) {
|
|
473
|
-
signals.push('unsupported_input_type');
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
// --- Opportunity signals (innovation / feature requests) ---
|
|
477
|
-
// Support 4 languages: EN, ZH-CN, ZH-TW, JA. Attach snippet for selector/prompt use.
|
|
478
|
-
|
|
479
|
-
var featureRequestSnippet = '';
|
|
480
|
-
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);
|
|
481
|
-
if (featEn) featureRequestSnippet = featEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
482
|
-
if (!featureRequestSnippet && /\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower)) {
|
|
483
|
-
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);
|
|
484
|
-
featureRequestSnippet = featWant ? featWant[0].replace(/\s+/g, ' ').trim().slice(0, 200) : 'feature request';
|
|
485
|
-
}
|
|
486
|
-
if (!featureRequestSnippet && /加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus)) {
|
|
487
|
-
var featZh = corpus.match(/.{0,100}(加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能).{0,100}/);
|
|
488
|
-
if (featZh) featureRequestSnippet = featZh[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
489
|
-
if (!featureRequestSnippet && /我想/.test(corpus)) {
|
|
490
|
-
var featWantZh = corpus.match(/我想\s*[,,\.。、\s]*([\s\S]{0,400})/);
|
|
491
|
-
featureRequestSnippet = featWantZh ? (featWantZh[1].replace(/\s+/g, ' ').trim().slice(0, 200) || '功能需求') : '功能需求';
|
|
492
|
-
}
|
|
493
|
-
if (!featureRequestSnippet) featureRequestSnippet = '功能需求';
|
|
494
|
-
}
|
|
495
|
-
if (!featureRequestSnippet && /加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus)) {
|
|
496
|
-
var featTw = corpus.match(/.{0,100}(加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加).{0,100}/);
|
|
497
|
-
featureRequestSnippet = featTw ? featTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '功能需求';
|
|
498
|
-
}
|
|
499
|
-
if (!featureRequestSnippet && /追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
|
|
500
|
-
var featJa = corpus.match(/.{0,100}(追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい).{0,100}/);
|
|
501
|
-
featureRequestSnippet = featJa ? featJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '機能要望';
|
|
502
|
-
}
|
|
503
|
-
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) ||
|
|
504
|
-
/\b(i want|i need|we need|please add|can you add|could you add|let'?s add)\b/i.test(lower) ||
|
|
505
|
-
/加个|实现一下|做个|想要\s*一个|需要\s*一个|帮我加|帮我开发|加一下|新增一个|加个功能|做个功能|我想/.test(corpus) ||
|
|
506
|
-
/加個|實現一下|做個|想要一個|請加|新增一個|加個功能|做個功能|幫我加/.test(corpus) ||
|
|
507
|
-
/追加|実装|作って|機能を|追加して|が欲しい|を追加|してほしい/.test(corpus)) {
|
|
508
|
-
signals.push('user_feature_request');
|
|
509
|
-
if (featureRequestSnippet) signals.push('user_feature_request:' + featureRequestSnippet);
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// user_improvement_suggestion: 4 languages + snippet
|
|
513
|
-
var improvementSnippet = '';
|
|
514
|
-
if (!errorHit) {
|
|
515
|
-
var impEn = corpus.match(/.{0,80}\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b.{0,80}/i);
|
|
516
|
-
if (impEn) improvementSnippet = impEn[0].replace(/\s+/g, ' ').trim().slice(0, 200);
|
|
517
|
-
if (!improvementSnippet && /改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus)) {
|
|
518
|
-
var impZh = corpus.match(/.{0,100}(改进一下|优化一下|简化|重构|整理一下|弄得更好).{0,100}/);
|
|
519
|
-
improvementSnippet = impZh ? impZh[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改进建议';
|
|
520
|
-
}
|
|
521
|
-
if (!improvementSnippet && /改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus)) {
|
|
522
|
-
var impTw = corpus.match(/.{0,100}(改進一下|優化一下|簡化|重構|整理一下|弄得更好).{0,100}/);
|
|
523
|
-
improvementSnippet = impTw ? impTw[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改進建議';
|
|
524
|
-
}
|
|
525
|
-
if (!improvementSnippet && /改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus)) {
|
|
526
|
-
var impJa = corpus.match(/.{0,100}(改善|最適化|簡素化|リファクタ|良くして|改良).{0,100}/);
|
|
527
|
-
improvementSnippet = impJa ? impJa[0].replace(/\s+/g, ' ').trim().slice(0, 200) : '改善要望';
|
|
528
|
-
}
|
|
529
|
-
var hasImprovement = improvementSnippet ||
|
|
530
|
-
/\b(should be|could be better|improve|enhance|upgrade|refactor|clean up|simplify|streamline)\b/i.test(lower) ||
|
|
531
|
-
/改进一下|优化一下|简化|重构|整理一下|弄得更好/.test(corpus) ||
|
|
532
|
-
/改進一下|優化一下|簡化|重構|整理一下|弄得更好/.test(corpus) ||
|
|
533
|
-
/改善|最適化|簡素化|リファクタ|良くして|改良/.test(corpus);
|
|
534
|
-
if (hasImprovement) {
|
|
535
|
-
signals.push('user_improvement_suggestion');
|
|
536
|
-
if (improvementSnippet) signals.push('user_improvement_suggestion:' + improvementSnippet);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// perf_bottleneck: performance issues detected
|
|
541
|
-
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)) {
|
|
542
|
-
signals.push('perf_bottleneck');
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// capability_gap: something is explicitly unsupported or missing
|
|
546
|
-
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)) {
|
|
547
|
-
// Only fire if it is not just a missing file/config signal
|
|
548
|
-
if (!signals.includes('memory_missing') && !signals.includes('user_missing') && !signals.includes('session_logs_missing')) {
|
|
549
|
-
signals.push('capability_gap');
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// --- Tool Usage Analytics ---
|
|
554
|
-
var toolUsage = {};
|
|
555
|
-
var toolMatches = corpus.match(/\[TOOL:\s*([\w-]+)\]/g) || [];
|
|
556
|
-
|
|
557
|
-
// Extract exec commands to identify benign loops (like watchdog checks)
|
|
558
|
-
var execCommands = corpus.match(/exec: (node\s+[\w\/\.-]+\.js\s+ensure)/g) || [];
|
|
559
|
-
var benignExecCount = execCommands.length;
|
|
560
|
-
|
|
561
|
-
for (var i = 0; i < toolMatches.length; i++) {
|
|
562
|
-
var toolName = toolMatches[i].match(/\[TOOL:\s*([\w-]+)\]/)[1];
|
|
563
|
-
toolUsage[toolName] = (toolUsage[toolName] || 0) + 1;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
// Adjust exec count by subtracting benign commands
|
|
567
|
-
if (toolUsage['exec']) {
|
|
568
|
-
toolUsage['exec'] = Math.max(0, toolUsage['exec'] - benignExecCount);
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
Object.keys(toolUsage).forEach(function(tool) {
|
|
572
|
-
if (toolUsage[tool] >= 10) { // Bumped threshold from 5 to 10
|
|
573
|
-
signals.push('high_tool_usage:' + tool);
|
|
574
|
-
}
|
|
575
|
-
// Detect repeated exec usage (often a sign of manual loops or inefficient automation)
|
|
576
|
-
if (tool === 'exec' && toolUsage[tool] >= 5) { // Bumped threshold from 3 to 5
|
|
577
|
-
signals.push('repeated_tool_usage:exec');
|
|
578
|
-
}
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
// --- Tool bypass detection ---
|
|
582
|
-
// When the agent uses shell/exec to run ad-hoc scripts instead of registered tools,
|
|
583
|
-
// it indicates a tool integrity issue (bypassing the tool layer).
|
|
584
|
-
var bypassPatterns = [
|
|
585
|
-
/node\s+\S+\.m?js/,
|
|
586
|
-
/npx\s+/,
|
|
587
|
-
/curl\s+.*api/i,
|
|
588
|
-
/python\s+\S+\.py/,
|
|
589
|
-
];
|
|
590
|
-
var execContent = corpus.match(/exec:.*$/gm) || [];
|
|
591
|
-
for (var bpi = 0; bpi < execContent.length; bpi++) {
|
|
592
|
-
var line = execContent[bpi];
|
|
593
|
-
for (var bpj = 0; bpj < bypassPatterns.length; bpj++) {
|
|
594
|
-
if (bypassPatterns[bpj].test(line)) {
|
|
595
|
-
signals.push('tool_bypass');
|
|
596
|
-
bpi = execContent.length;
|
|
597
|
-
break;
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
return signals;
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
// ---------------------------------------------------------------------------
|
|
606
|
-
// extractSignals: Multi-strategy orchestrator.
|
|
607
|
-
// Calls three extraction layers (regex, keyword scoring, LLM semantic),
|
|
608
|
-
// merges their outputs, then applies post-processing (prioritization,
|
|
609
|
-
// history-based dedup, innovation forcing).
|
|
610
|
-
// Signature and return type are unchanged -- callers are not affected.
|
|
611
|
-
// ---------------------------------------------------------------------------
|
|
612
|
-
function extractSignals({ recentSessionTranscript, todayLog, memorySnippet, userSnippet, recentEvents }) {
|
|
613
|
-
var corpus = [
|
|
614
|
-
String(recentSessionTranscript || ''),
|
|
615
|
-
String(todayLog || ''),
|
|
616
|
-
String(memorySnippet || ''),
|
|
617
|
-
String(userSnippet || ''),
|
|
618
|
-
].join('\n');
|
|
619
|
-
var lower = corpus.toLowerCase();
|
|
620
|
-
|
|
621
|
-
var history = analyzeRecentHistory(recentEvents || []);
|
|
622
|
-
|
|
623
|
-
// Is the recent failure corpus dominated by an unrecoverable host/LLM client
|
|
624
|
-
// error (4xx: malformed request / auth / quota)? Such failures are the host
|
|
625
|
-
// Agent's provider config, not the Gene -- they must not feed the
|
|
626
|
-
// failure-loop streak / ban_gene path below (#571, follow-up to #534).
|
|
627
|
-
var hostClientError = isHostClientError(corpus);
|
|
628
|
-
|
|
629
|
-
var errorHit = /\[error\]|error:|exception:|iserror":true|"status":\s*"error"|"status":\s*"failed"|错误\s*[::]|异常\s*[::]|报错\s*[::]|失败\s*[::]/.test(lower);
|
|
630
|
-
|
|
631
|
-
// Layer 1: Regex (deterministic, 0ms)
|
|
632
|
-
var regexSignals = _extractRegex(corpus, lower, errorHit);
|
|
633
|
-
|
|
634
|
-
// Layer 2: Weighted keyword scoring (statistical, 0ms)
|
|
635
|
-
var scoreSignals = _extractKeywordScore(lower);
|
|
636
|
-
|
|
637
|
-
// Layer 3: LLM semantic analysis (rate-limited, async, optional)
|
|
638
|
-
var llmSignals = _extractLLM(corpus);
|
|
639
|
-
|
|
640
|
-
// Merge all layers
|
|
641
|
-
var signals = _mergeSignals(regexSignals, scoreSignals, llmSignals);
|
|
642
|
-
|
|
643
|
-
// --- Post-processing (applies to merged signal set) ---
|
|
644
|
-
|
|
645
|
-
// Signal prioritization: remove cosmetic signals when actionable ones exist
|
|
646
|
-
var actionable = signals.filter(function (s) {
|
|
647
|
-
return s !== 'user_missing' && s !== 'memory_missing' && s !== 'session_logs_missing' && s !== 'windows_shell_incompatible';
|
|
648
|
-
});
|
|
649
|
-
if (actionable.length > 0) {
|
|
650
|
-
signals = actionable;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// De-duplication: suppress signals that have been over-processed in recent history
|
|
654
|
-
if (history.suppressedSignals.size > 0) {
|
|
655
|
-
var beforeDedup = signals.length;
|
|
656
|
-
signals = signals.filter(function (s) {
|
|
657
|
-
var key = s.startsWith('errsig:') ? 'errsig'
|
|
658
|
-
: s.startsWith('recurring_errsig') ? 'recurring_errsig'
|
|
659
|
-
: s.startsWith('user_feature_request:') ? 'user_feature_request'
|
|
660
|
-
: s.startsWith('user_improvement_suggestion:') ? 'user_improvement_suggestion'
|
|
661
|
-
: s;
|
|
662
|
-
return !history.suppressedSignals.has(key);
|
|
663
|
-
});
|
|
664
|
-
if (beforeDedup > 0 && signals.length === 0) {
|
|
665
|
-
signals.push('evolution_stagnation_detected');
|
|
666
|
-
signals.push('stable_success_plateau');
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
// Force innovation after 3+ consecutive repairs
|
|
671
|
-
if (history.consecutiveRepairCount >= 3) {
|
|
672
|
-
signals = signals.filter(function (s) {
|
|
673
|
-
return s !== 'log_error' && !s.startsWith('errsig:') && !s.startsWith('recurring_errsig');
|
|
674
|
-
});
|
|
675
|
-
if (signals.length === 0) {
|
|
676
|
-
signals.push('repair_loop_detected');
|
|
677
|
-
signals.push('stable_success_plateau');
|
|
678
|
-
}
|
|
679
|
-
signals.push('force_innovation_after_repair_loop');
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Force innovation after too many empty cycles (zero blast radius)
|
|
683
|
-
if (history.emptyCycleCount >= 4) {
|
|
684
|
-
signals = signals.filter(function (s) {
|
|
685
|
-
return s !== 'log_error' && !s.startsWith('errsig:') && !s.startsWith('recurring_errsig');
|
|
686
|
-
});
|
|
687
|
-
if (!signals.includes('empty_cycle_loop_detected')) signals.push('empty_cycle_loop_detected');
|
|
688
|
-
if (!signals.includes('stable_success_plateau')) signals.push('stable_success_plateau');
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// Saturation detection (graceful degradation)
|
|
692
|
-
if (history.consecutiveEmptyCycles >= 5) {
|
|
693
|
-
if (!signals.includes('force_steady_state')) signals.push('force_steady_state');
|
|
694
|
-
if (!signals.includes('evolution_saturation')) signals.push('evolution_saturation');
|
|
695
|
-
} else if (history.consecutiveEmptyCycles >= 3) {
|
|
696
|
-
if (!signals.includes('evolution_saturation')) signals.push('evolution_saturation');
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// Exploration opportunity: when saturated, inject explore signal so the
|
|
700
|
-
// idle gating path can trigger proactive exploration instead of sleeping.
|
|
701
|
-
if (history.consecutiveEmptyCycles >= 3 && !signals.includes('explore_opportunity')) {
|
|
702
|
-
signals.push('explore_opportunity');
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// Failure streak awareness
|
|
706
|
-
if (history.consecutiveFailureCount >= 3) {
|
|
707
|
-
if (hostClientError) {
|
|
708
|
-
// Non-Gene-attributable: an unrecoverable host/LLM 4xx is driving the
|
|
709
|
-
// streak. Surface an actionable signal pointing at the host LLM config
|
|
710
|
-
// and skip the streak/ban path so we never ban an innocent Gene or trip
|
|
711
|
-
// failure_loop_detected (#571).
|
|
712
|
-
if (!signals.includes('host_llm_client_error')) signals.push('host_llm_client_error');
|
|
713
|
-
} else {
|
|
714
|
-
signals.push('consecutive_failure_streak_' + history.consecutiveFailureCount);
|
|
715
|
-
if (history.consecutiveFailureCount >= 5) {
|
|
716
|
-
signals.push('failure_loop_detected');
|
|
717
|
-
var topGene = null;
|
|
718
|
-
var topGeneCount = 0;
|
|
719
|
-
var gfEntries = Object.entries(history.geneFreq);
|
|
720
|
-
for (var gfi = 0; gfi < gfEntries.length; gfi++) {
|
|
721
|
-
if (gfEntries[gfi][1] > topGeneCount) {
|
|
722
|
-
topGeneCount = gfEntries[gfi][1];
|
|
723
|
-
topGene = gfEntries[gfi][0];
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
if (topGene) {
|
|
727
|
-
signals.push('ban_gene:' + topGene);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// High failure ratio in recent history (>= 75% failed in last 8 cycles)
|
|
734
|
-
if (history.recentFailureRatio >= 0.75) {
|
|
735
|
-
if (hostClientError) {
|
|
736
|
-
// Same rationale as the streak block: a host/LLM 4xx is not the Gene's
|
|
737
|
-
// fault, so do not force innovation away from it (#571).
|
|
738
|
-
if (!signals.includes('host_llm_client_error')) signals.push('host_llm_client_error');
|
|
739
|
-
} else {
|
|
740
|
-
signals.push('high_failure_ratio');
|
|
741
|
-
signals.push('force_innovation_after_repair_loop');
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Plateau detection: recent scores trending down or stagnant.
|
|
746
|
-
// Uses score data from recentEvents to detect diminishing returns.
|
|
747
|
-
if (Array.isArray(recentEvents) && recentEvents.length >= 4) {
|
|
748
|
-
var recentScores = recentEvents.slice(-6).map(function (e) {
|
|
749
|
-
return e.outcome && typeof e.outcome.score === 'number' ? e.outcome.score : -1;
|
|
750
|
-
}).filter(function (s) { return s >= 0; });
|
|
751
|
-
if (recentScores.length >= 3) {
|
|
752
|
-
var avgScore = recentScores.reduce(function (a, b) { return a + b; }, 0) / recentScores.length;
|
|
753
|
-
var improving = recentScores.length >= 2 && recentScores[recentScores.length - 1] > recentScores[recentScores.length - 2] + 0.05;
|
|
754
|
-
if (avgScore < 0.35 && !improving) {
|
|
755
|
-
signals.push('plateau_pivot_required');
|
|
756
|
-
} else if (avgScore < 0.55 && !improving && history.consecutiveRepairCount >= 2) {
|
|
757
|
-
signals.push('plateau_pivot_suggested');
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
// If no signals at all, add a default innovation signal
|
|
763
|
-
if (signals.length === 0) {
|
|
764
|
-
signals.push('stable_success_plateau');
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
return Array.from(new Set(signals));
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
module.exports = {
|
|
771
|
-
extractSignals, hasOpportunitySignal, analyzeRecentHistory,
|
|
772
|
-
OPPORTUNITY_SIGNALS, SIGNAL_PROFILES,
|
|
773
|
-
_extractRegex, _extractKeywordScore, _extractLLM, _mergeSignals,
|
|
774
|
-
_curlHubIpFamilyArgs, _curlHubIpFamilyAttempts, _curlAuthConfig,
|
|
775
|
-
_isEmptyCycle,
|
|
776
|
-
};
|