@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/atp/autoBuyer.js
DELETED
|
@@ -1,382 +0,0 @@
|
|
|
1
|
-
// ATP Auto-Buyer (opt-in: requires explicit consent before auto-spending)
|
|
2
|
-
// Converts capability gaps into ATP orders with strict budget caps and
|
|
3
|
-
// 24h question-level deduplication. Budget caps:
|
|
4
|
-
// ATP_AUTOBUY_DAILY_CAP_CREDITS (default 50)
|
|
5
|
-
// ATP_AUTOBUY_PER_ORDER_CAP_CREDITS (default 10)
|
|
6
|
-
// Cold-start safety: the first 5 minutes after process start use a half-cap
|
|
7
|
-
// to protect against misconfiguration loops on restart storms.
|
|
8
|
-
//
|
|
9
|
-
// Consent resolution (in order):
|
|
10
|
-
// 1. EVOLVER_ATP_AUTOBUY=on|off env — explicit operator override wins.
|
|
11
|
-
// 2. ack file at <memory>/atp-autobuy-ack.json with `{enabled: bool}` —
|
|
12
|
-
// written by first-run prompt (cliAutobuyPrompt) or `evolver atp
|
|
13
|
-
// enable|disable`.
|
|
14
|
-
// 3. No signal → OFF. New installs never auto-spend before the user has
|
|
15
|
-
// explicitly opted in (consumer protection: ATP spends real credits).
|
|
16
|
-
//
|
|
17
|
-
// Integration contract:
|
|
18
|
-
// 1) Call start({ dailyCap, perOrderCap }) once at Evolver boot. The
|
|
19
|
-
// evolve loop does this at the top of every cycle; start() is
|
|
20
|
-
// idempotent so the repeated call is a no-op.
|
|
21
|
-
// 2) Call considerOrder({ signals, question, capabilities, budget, ... })
|
|
22
|
-
// from the evolve loop whenever a capability gap is detected.
|
|
23
|
-
// 3) Result shape: { ok, skipped?, reason?, data?, error? }.
|
|
24
|
-
//
|
|
25
|
-
// Failure modes are non-fatal; every external call is wrapped with a 3s
|
|
26
|
-
// timeout race so loop cadence is never blocked by network issues.
|
|
27
|
-
|
|
28
|
-
const crypto = require('crypto');
|
|
29
|
-
const fs = require('fs');
|
|
30
|
-
const path = require('path');
|
|
31
|
-
|
|
32
|
-
const { getMemoryDir } = require('../gep/paths');
|
|
33
|
-
const hubClient = require('./hubClient');
|
|
34
|
-
|
|
35
|
-
const DEFAULT_DAILY_CAP = 50;
|
|
36
|
-
const DEFAULT_PER_ORDER_CAP = 10;
|
|
37
|
-
const DEFAULT_ORDER_TIMEOUT_MS = 3000;
|
|
38
|
-
const COLD_START_WINDOW_MS = 5 * 60 * 1000;
|
|
39
|
-
// Successful orders dedup for 24h so the same capability gap is only paid for
|
|
40
|
-
// once per day. Failed orders dedup for 5 minutes only — long enough to
|
|
41
|
-
// absorb tight retry loops (the original goal of "don't hammer the hub")
|
|
42
|
-
// without making the user wait 24h to retry a question after a transient
|
|
43
|
-
// 503/network blip.
|
|
44
|
-
const DEDUP_TTL_MS = 24 * 60 * 60 * 1000;
|
|
45
|
-
const DEDUP_FAILURE_TTL_MS = 5 * 60 * 1000;
|
|
46
|
-
const LEDGER_FILENAME = 'atp-autobuyer-ledger.json';
|
|
47
|
-
const ACK_FILENAME = 'atp-autobuy-ack.json';
|
|
48
|
-
|
|
49
|
-
let _started = false;
|
|
50
|
-
let _startedAt = 0;
|
|
51
|
-
let _config = {
|
|
52
|
-
dailyCap: DEFAULT_DAILY_CAP,
|
|
53
|
-
perOrderCap: DEFAULT_PER_ORDER_CAP,
|
|
54
|
-
timeoutMs: DEFAULT_ORDER_TIMEOUT_MS,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
function _ledgerPath() {
|
|
58
|
-
return path.join(getMemoryDir(), LEDGER_FILENAME);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function _ackPath() {
|
|
62
|
-
return path.join(getMemoryDir(), ACK_FILENAME);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function _todayKey(now) {
|
|
66
|
-
const d = new Date(typeof now === 'number' ? now : Date.now());
|
|
67
|
-
return d.toISOString().slice(0, 10); // UTC YYYY-MM-DD
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function _readAck() {
|
|
71
|
-
try {
|
|
72
|
-
const p = _ackPath();
|
|
73
|
-
if (!fs.existsSync(p)) return null;
|
|
74
|
-
const parsed = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
75
|
-
if (!parsed || typeof parsed !== 'object') return null;
|
|
76
|
-
if (typeof parsed.enabled !== 'boolean') return null;
|
|
77
|
-
return parsed;
|
|
78
|
-
} catch (_) {
|
|
79
|
-
return null;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Resolve consent. Returns:
|
|
84
|
-
// { enabled: true, source: 'env'|'ack'|'default' }
|
|
85
|
-
// { enabled: false, source: 'env'|'ack' }
|
|
86
|
-
// The `default` case is the new-install path (no env override, no ack file):
|
|
87
|
-
// auto-spend defaults ON, gated by the daily/per-order caps and the cold-start
|
|
88
|
-
// half-cap window. The first-run prompt and `evolver atp disable` remain the
|
|
89
|
-
// opt-out paths for users who do not want auto-spend; once an explicit ack is
|
|
90
|
-
// recorded the source flips to 'ack' and the user's choice (either way) wins
|
|
91
|
-
// over this default.
|
|
92
|
-
function getConsent() {
|
|
93
|
-
const envRaw = process.env.EVOLVER_ATP_AUTOBUY;
|
|
94
|
-
if (typeof envRaw === 'string') {
|
|
95
|
-
// Trim BEFORE the length check so whitespace-only values
|
|
96
|
-
// (e.g. EVOLVER_ATP_AUTOBUY=" ") count as unset, matching the
|
|
97
|
-
// classify() helper in cliAutobuyPrompt.js. Without this alignment a
|
|
98
|
-
// whitespace value would skip the prompt in classify (treats as unset
|
|
99
|
-
// → 'eligible') but still enter this env branch, trim to "", fail to
|
|
100
|
-
// match 'off'/'0'/'false', and silently return enabled=true.
|
|
101
|
-
const s = envRaw.trim().toLowerCase();
|
|
102
|
-
if (s.length > 0) {
|
|
103
|
-
const enabled = s !== 'off' && s !== '0' && s !== 'false';
|
|
104
|
-
return { enabled, source: 'env' };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
const ack = _readAck();
|
|
108
|
-
if (ack) {
|
|
109
|
-
return { enabled: ack.enabled === true, source: 'ack' };
|
|
110
|
-
}
|
|
111
|
-
return { enabled: true, source: 'default' };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function _emptyLedger() {
|
|
115
|
-
return { version: 1, dayKey: _todayKey(), spent: 0, dedup: {} };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function _readLedger() {
|
|
119
|
-
try {
|
|
120
|
-
const p = _ledgerPath();
|
|
121
|
-
if (!fs.existsSync(p)) return _emptyLedger();
|
|
122
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
123
|
-
const parsed = JSON.parse(raw);
|
|
124
|
-
if (!parsed || typeof parsed !== 'object') return _emptyLedger();
|
|
125
|
-
if (!parsed.dayKey || !parsed.dedup) return _emptyLedger();
|
|
126
|
-
return parsed;
|
|
127
|
-
} catch (_) {
|
|
128
|
-
return _emptyLedger();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function _writeLedger(ledger) {
|
|
133
|
-
try {
|
|
134
|
-
const dir = getMemoryDir();
|
|
135
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
136
|
-
const tmp = _ledgerPath() + '.tmp';
|
|
137
|
-
fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
|
|
138
|
-
fs.renameSync(tmp, _ledgerPath());
|
|
139
|
-
} catch (_) {
|
|
140
|
-
// Non-fatal: ledger persistence failure means next process restart
|
|
141
|
-
// re-reads previous ledger (so existing caps still apply).
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function _rotateIfNewDay(ledger, now) {
|
|
146
|
-
const today = _todayKey(now);
|
|
147
|
-
if (ledger.dayKey !== today) {
|
|
148
|
-
return { version: 1, dayKey: today, spent: 0, dedup: ledger.dedup || {} };
|
|
149
|
-
}
|
|
150
|
-
return ledger;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function _pruneDedup(ledger, now) {
|
|
154
|
-
const nowMs = typeof now === 'number' ? now : Date.now();
|
|
155
|
-
const out = {};
|
|
156
|
-
const src = ledger.dedup || {};
|
|
157
|
-
const keys = Object.keys(src);
|
|
158
|
-
for (let i = 0; i < keys.length; i++) {
|
|
159
|
-
const k = keys[i];
|
|
160
|
-
const entry = src[k];
|
|
161
|
-
// Legacy ledgers written by older versions stored plain timestamps; treat
|
|
162
|
-
// them as successful orders (the original behaviour) so an upgrade does
|
|
163
|
-
// not suddenly forget recent dedups.
|
|
164
|
-
if (typeof entry === 'number') {
|
|
165
|
-
if (entry >= nowMs - DEDUP_TTL_MS) out[k] = entry;
|
|
166
|
-
} else if (entry && typeof entry.ts === 'number') {
|
|
167
|
-
const ttl = entry.failed ? DEDUP_FAILURE_TTL_MS : DEDUP_TTL_MS;
|
|
168
|
-
if (entry.ts >= nowMs - ttl) out[k] = entry;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
ledger.dedup = out;
|
|
172
|
-
return ledger;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function _questionHash(opts) {
|
|
176
|
-
const caps = Array.isArray(opts.capabilities) ? opts.capabilities.slice().sort().join(',') : '';
|
|
177
|
-
const q = (opts.question || '').slice(0, 2000);
|
|
178
|
-
return crypto.createHash('sha256').update(caps + '|' + q).digest('hex').slice(0, 24);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function _effectiveCap(value, now) {
|
|
182
|
-
const n = Number(value);
|
|
183
|
-
if (!Number.isFinite(n) || n < 0) return 0;
|
|
184
|
-
const within = _startedAt > 0 && (now - _startedAt) < COLD_START_WINDOW_MS;
|
|
185
|
-
return within ? Math.floor(n / 2) : Math.floor(n);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function start(opts) {
|
|
189
|
-
if (_started) return;
|
|
190
|
-
const consent = getConsent();
|
|
191
|
-
if (!consent.enabled) return;
|
|
192
|
-
_started = true;
|
|
193
|
-
_startedAt = Date.now();
|
|
194
|
-
const dailyCap = Math.max(0, Math.floor(Number((opts && opts.dailyCap) || process.env.ATP_AUTOBUY_DAILY_CAP_CREDITS) || DEFAULT_DAILY_CAP));
|
|
195
|
-
const perOrderCap = Math.max(0, Math.floor(Number((opts && opts.perOrderCap) || process.env.ATP_AUTOBUY_PER_ORDER_CAP_CREDITS) || DEFAULT_PER_ORDER_CAP));
|
|
196
|
-
const timeoutMs = Math.max(500, Math.floor(Number((opts && opts.timeoutMs) || DEFAULT_ORDER_TIMEOUT_MS)));
|
|
197
|
-
_config = { dailyCap, perOrderCap, timeoutMs };
|
|
198
|
-
let ledger = _readLedger();
|
|
199
|
-
ledger = _rotateIfNewDay(ledger, _startedAt);
|
|
200
|
-
ledger = _pruneDedup(ledger, _startedAt);
|
|
201
|
-
_writeLedger(ledger);
|
|
202
|
-
console.log('[ATP-AutoBuyer] Started (dailyCap=' + dailyCap + ', perOrderCap=' + perOrderCap + ', cold-start half-cap for ' + (COLD_START_WINDOW_MS / 1000) + 's)');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function stop() {
|
|
206
|
-
_started = false;
|
|
207
|
-
_startedAt = 0;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function isStarted() {
|
|
211
|
-
return _started;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function _withTimeout(promise, timeoutMs) {
|
|
215
|
-
return Promise.race([
|
|
216
|
-
promise,
|
|
217
|
-
new Promise(function (resolve) {
|
|
218
|
-
setTimeout(function () { resolve({ ok: false, error: 'autobuyer_timeout' }); }, timeoutMs);
|
|
219
|
-
}),
|
|
220
|
-
]);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Single-flight queue: serialize the read → cap-check → placeOrder → write
|
|
224
|
-
// pipeline so two concurrent considerOrder() calls cannot both pass the cap
|
|
225
|
-
// check on the same ledger snapshot and silently double-spend.
|
|
226
|
-
//
|
|
227
|
-
// Without this, two parallel calls (e.g. user runs Claude Code in two tabs
|
|
228
|
-
// through the same proxy, or two capability gaps fire in the same tick) both
|
|
229
|
-
// read spent=40, both compute remaining=10, both await placeOrder, both
|
|
230
|
-
// increment to spent=50, and write — silently exceeding the daily cap by one
|
|
231
|
-
// full order each. autoBuyer is single-process so an in-memory queue is
|
|
232
|
-
// sufficient; a file lock would only be needed if multiple OS processes
|
|
233
|
-
// shared the same ledger file (not the current deployment model).
|
|
234
|
-
let _orderQueue = Promise.resolve();
|
|
235
|
-
|
|
236
|
-
function considerOrder(opts) {
|
|
237
|
-
if (!_started) return Promise.resolve({ ok: false, skipped: true, reason: 'not_started' });
|
|
238
|
-
if (!opts || !Array.isArray(opts.capabilities) || opts.capabilities.length === 0) {
|
|
239
|
-
return Promise.resolve({ ok: false, skipped: true, reason: 'no_capabilities' });
|
|
240
|
-
}
|
|
241
|
-
const next = _orderQueue.then(
|
|
242
|
-
() => _considerOrderSerialized(opts),
|
|
243
|
-
() => _considerOrderSerialized(opts), // never let a prior rejection break the chain
|
|
244
|
-
);
|
|
245
|
-
// Swallow rejection on the queue tail so a single thrown error here does
|
|
246
|
-
// not poison every subsequent call; the original `next` promise still
|
|
247
|
-
// surfaces the error to the caller.
|
|
248
|
-
_orderQueue = next.then(() => {}, () => {});
|
|
249
|
-
return next;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
async function _considerOrderSerialized(opts) {
|
|
253
|
-
const now = Date.now();
|
|
254
|
-
let ledger = _readLedger();
|
|
255
|
-
ledger = _rotateIfNewDay(ledger, now);
|
|
256
|
-
ledger = _pruneDedup(ledger, now);
|
|
257
|
-
|
|
258
|
-
const hash = _questionHash(opts);
|
|
259
|
-
if (ledger.dedup[hash]) {
|
|
260
|
-
return { ok: false, skipped: true, reason: 'dedup_hit', hash: hash };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const dailyCap = _effectiveCap(_config.dailyCap, now);
|
|
264
|
-
const perOrderCap = _effectiveCap(_config.perOrderCap, now);
|
|
265
|
-
const remaining = Math.max(0, dailyCap - (ledger.spent || 0));
|
|
266
|
-
if (remaining <= 0) {
|
|
267
|
-
console.warn('[ATP-AutoBuyer] Daily cap reached, skipping order (spent=' + ledger.spent + ', cap=' + dailyCap + ')');
|
|
268
|
-
return { ok: false, skipped: true, reason: 'daily_cap_reached', spent: ledger.spent, cap: dailyCap };
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const requested = Math.max(1, Math.floor(Number(opts.budget) || perOrderCap));
|
|
272
|
-
const budget = Math.min(requested, perOrderCap, remaining);
|
|
273
|
-
if (budget <= 0) {
|
|
274
|
-
return { ok: false, skipped: true, reason: 'budget_clamped_to_zero' };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const orderOpts = {
|
|
278
|
-
capabilities: opts.capabilities,
|
|
279
|
-
budget: budget,
|
|
280
|
-
routingMode: opts.routingMode || 'fastest',
|
|
281
|
-
verifyMode: opts.verifyMode || 'auto',
|
|
282
|
-
question: opts.question,
|
|
283
|
-
signals: opts.signals,
|
|
284
|
-
minReputation: opts.minReputation,
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const result = await _withTimeout(hubClient.placeOrder(orderOpts), _config.timeoutMs);
|
|
288
|
-
|
|
289
|
-
if (result && result.ok) {
|
|
290
|
-
ledger.spent = (ledger.spent || 0) + budget;
|
|
291
|
-
ledger.dedup[hash] = { ts: now, failed: false };
|
|
292
|
-
_writeLedger(ledger);
|
|
293
|
-
console.log('[ATP-AutoBuyer] Order placed: ' + (result.data && result.data.order_id) + ' budget=' + budget + ' remaining_today=' + Math.max(0, dailyCap - ledger.spent));
|
|
294
|
-
return { ok: true, data: result.data, spent: budget };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// On failure record a SHORT-TTL dedup entry (5 min) so we don't hammer the
|
|
298
|
-
// hub for the same capability gap inside a tight retry loop, but the user
|
|
299
|
-
// can retry the same question once the transient error clears — far better
|
|
300
|
-
// than the previous 24h block for a single 503.
|
|
301
|
-
ledger.dedup[hash] = { ts: now, failed: true };
|
|
302
|
-
_writeLedger(ledger);
|
|
303
|
-
return { ok: false, error: (result && result.error) || 'unknown_error' };
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Write the consent ack file. Used by `evolver atp enable|disable` and the
|
|
307
|
-
// first-run prompt. `enabled=true` persists opt-in; `enabled=false` persists
|
|
308
|
-
// explicit opt-out so the prompt does not re-ask next session. Atomic write
|
|
309
|
-
// via .tmp + rename so a crash mid-write never produces a corrupt ack file.
|
|
310
|
-
function setConsent(enabled) {
|
|
311
|
-
const dir = getMemoryDir();
|
|
312
|
-
const body = {
|
|
313
|
-
enabled: !!enabled,
|
|
314
|
-
acknowledged_at: new Date().toISOString(),
|
|
315
|
-
version: 1,
|
|
316
|
-
};
|
|
317
|
-
const tmp = _ackPath() + '.tmp';
|
|
318
|
-
// Single try/catch over the whole pipeline. Previously the mkdirSync was
|
|
319
|
-
// wrapped in its own swallowing try/catch, so an EACCES on the parent dir
|
|
320
|
-
// would surface to the caller as a confusing ENOENT from writeFileSync.
|
|
321
|
-
// Surface the original error verbatim and best-effort clean up any
|
|
322
|
-
// partial .tmp file so a retry from a TTY prompt sees a clean slate.
|
|
323
|
-
try {
|
|
324
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
325
|
-
fs.writeFileSync(tmp, JSON.stringify(body, null, 2) + '\n', 'utf8');
|
|
326
|
-
fs.renameSync(tmp, _ackPath());
|
|
327
|
-
} catch (err) {
|
|
328
|
-
try { fs.unlinkSync(tmp); } catch (_) {}
|
|
329
|
-
throw err;
|
|
330
|
-
}
|
|
331
|
-
return body;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Test-only reset, not exported by default.
|
|
335
|
-
function _resetForTests() {
|
|
336
|
-
_started = false;
|
|
337
|
-
_startedAt = 0;
|
|
338
|
-
_config = {
|
|
339
|
-
dailyCap: DEFAULT_DAILY_CAP,
|
|
340
|
-
perOrderCap: DEFAULT_PER_ORDER_CAP,
|
|
341
|
-
timeoutMs: DEFAULT_ORDER_TIMEOUT_MS,
|
|
342
|
-
};
|
|
343
|
-
_orderQueue = Promise.resolve();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
module.exports = {
|
|
347
|
-
// Lifecycle.
|
|
348
|
-
start,
|
|
349
|
-
stop,
|
|
350
|
-
isStarted,
|
|
351
|
-
considerOrder,
|
|
352
|
-
// Consent surface — public API. Production callers (CLI runAtp,
|
|
353
|
-
// cliAutobuyPrompt, the daemon run loop) MUST use these, not the
|
|
354
|
-
// __internals duplicates below, so the "test-only" contract on
|
|
355
|
-
// __internals stays honest (Bugbot PR #141 R6).
|
|
356
|
-
getConsent,
|
|
357
|
-
setConsent,
|
|
358
|
-
getAckPath: _ackPath,
|
|
359
|
-
readAck: _readAck,
|
|
360
|
-
ACK_FILENAME,
|
|
361
|
-
// Exposed for tests and diagnostics only; callers should not depend on
|
|
362
|
-
// these internals in production code paths. Ack-file helpers
|
|
363
|
-
// (getAckPath / readAck / ACK_FILENAME) are intentionally NOT mirrored
|
|
364
|
-
// here — production and tests both go through the public surface above,
|
|
365
|
-
// so a single source of truth survives schema changes (Bugbot PR #141 R6
|
|
366
|
-
// follow-up: keep "test-only" honest, no production caller may reach in).
|
|
367
|
-
__internals: {
|
|
368
|
-
readLedger: _readLedger,
|
|
369
|
-
writeLedger: _writeLedger,
|
|
370
|
-
questionHash: _questionHash,
|
|
371
|
-
effectiveCap: _effectiveCap,
|
|
372
|
-
resetForTests: _resetForTests,
|
|
373
|
-
constants: {
|
|
374
|
-
DEFAULT_DAILY_CAP,
|
|
375
|
-
DEFAULT_PER_ORDER_CAP,
|
|
376
|
-
COLD_START_WINDOW_MS,
|
|
377
|
-
DEDUP_TTL_MS,
|
|
378
|
-
DEDUP_FAILURE_TTL_MS,
|
|
379
|
-
LEDGER_FILENAME,
|
|
380
|
-
},
|
|
381
|
-
},
|
|
382
|
-
};
|
package/src/atp/autoDeliver.js
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// ATP Auto-Deliver (opt-out, merchant-side)
|
|
2
|
-
// Closes the ATP settlement loop for Evolver merchants by auto-calling
|
|
3
|
-
// submitDelivery for every claimed task that carries an atp_order_id.
|
|
4
|
-
//
|
|
5
|
-
// Without this module, an ATP order sits in `pending` until the 7-day escrow
|
|
6
|
-
// timeout refunds the buyer: the Hub routes the task to a merchant node and
|
|
7
|
-
// marks it claimed, but nothing in the Evolver runtime actually calls
|
|
8
|
-
// /a2a/atp/deliver. This was the root cause of the 0-settled-in-13-days
|
|
9
|
-
// pipeline stall observed in prod on 2026-04-27.
|
|
10
|
-
//
|
|
11
|
-
// Integration contract:
|
|
12
|
-
// 1) Call start({ pollMs }) once at Evolver boot. Default ON.
|
|
13
|
-
// Disable by setting EVOLVER_ATP_AUTODELIVER=off.
|
|
14
|
-
// 2) The module polls /a2a/task/my every pollMs milliseconds, finds tasks
|
|
15
|
-
// with atp_order_id + a `result_asset_id` (meaning the task already
|
|
16
|
-
// completed through solidify), and submits a minimal proofPayload.
|
|
17
|
-
// 3) Each submitted order is remembered in a local ledger so we never
|
|
18
|
-
// double-submit, even across restarts.
|
|
19
|
-
//
|
|
20
|
-
// Dedup ledger lives alongside autoBuyer's ledger under memory/.
|
|
21
|
-
// Failure modes are non-fatal: network errors are logged, not thrown.
|
|
22
|
-
|
|
23
|
-
const fs = require('fs');
|
|
24
|
-
const path = require('path');
|
|
25
|
-
|
|
26
|
-
const { getMemoryDir } = require('../gep/paths');
|
|
27
|
-
const hubClient = require('./hubClient');
|
|
28
|
-
|
|
29
|
-
const DEFAULT_POLL_MS = 60 * 1000; // 1 min
|
|
30
|
-
const MIN_POLL_MS = 15 * 1000;
|
|
31
|
-
const LEDGER_FILENAME = 'atp-autodeliver-ledger.json';
|
|
32
|
-
const LEDGER_MAX_ENTRIES = 500;
|
|
33
|
-
|
|
34
|
-
let _started = false;
|
|
35
|
-
let _pollInterval = null;
|
|
36
|
-
let _pollMs = DEFAULT_POLL_MS;
|
|
37
|
-
let _inflight = false;
|
|
38
|
-
|
|
39
|
-
function _ledgerPath() {
|
|
40
|
-
return path.join(getMemoryDir(), LEDGER_FILENAME);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function _isEnabled() {
|
|
44
|
-
const raw = (process.env.EVOLVER_ATP_AUTODELIVER || 'on').toLowerCase().trim();
|
|
45
|
-
return raw !== 'off' && raw !== '0' && raw !== 'false';
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function _emptyLedger() {
|
|
49
|
-
return { version: 1, submitted: {} };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function _readLedger() {
|
|
53
|
-
try {
|
|
54
|
-
const p = _ledgerPath();
|
|
55
|
-
if (!fs.existsSync(p)) return _emptyLedger();
|
|
56
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
57
|
-
const parsed = JSON.parse(raw);
|
|
58
|
-
if (!parsed || typeof parsed !== 'object' || !parsed.submitted) return _emptyLedger();
|
|
59
|
-
return parsed;
|
|
60
|
-
} catch (_) {
|
|
61
|
-
return _emptyLedger();
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function _writeLedger(ledger) {
|
|
66
|
-
try {
|
|
67
|
-
const dir = getMemoryDir();
|
|
68
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
69
|
-
// Bound the ledger size so it cannot grow without limit on long-running
|
|
70
|
-
// merchants. Keep the most-recent entries by insertion order.
|
|
71
|
-
const entries = Object.entries(ledger.submitted || {});
|
|
72
|
-
if (entries.length > LEDGER_MAX_ENTRIES) {
|
|
73
|
-
const trimmed = Object.fromEntries(entries.slice(-LEDGER_MAX_ENTRIES));
|
|
74
|
-
ledger.submitted = trimmed;
|
|
75
|
-
}
|
|
76
|
-
const tmp = _ledgerPath() + '.tmp';
|
|
77
|
-
fs.writeFileSync(tmp, JSON.stringify(ledger, null, 2));
|
|
78
|
-
fs.renameSync(tmp, _ledgerPath());
|
|
79
|
-
} catch (_) {
|
|
80
|
-
// Non-fatal: next poll will re-attempt from Hub state. Hub-side
|
|
81
|
-
// submitDelivery is itself idempotent per order id.
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function _buildProofPayload(task) {
|
|
86
|
-
// Minimal evidence the Hub's auto verifier will accept. Matches the shape
|
|
87
|
-
// documented in /a2a/atp/deliver: result/output/pass_rate/signals.
|
|
88
|
-
const now = new Date().toISOString();
|
|
89
|
-
return {
|
|
90
|
-
result: 'completed',
|
|
91
|
-
asset_id: task.result_asset_id || null,
|
|
92
|
-
completed_at: task.claimed_at || now,
|
|
93
|
-
pass_rate: 1.0,
|
|
94
|
-
signals: Array.isArray(task.signals) ? task.signals.slice(0, 10) : [],
|
|
95
|
-
submitter: 'evolver_auto_deliver',
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
async function _tick() {
|
|
100
|
-
if (_inflight) return;
|
|
101
|
-
_inflight = true;
|
|
102
|
-
try {
|
|
103
|
-
const result = await hubClient.listMyTasks(20);
|
|
104
|
-
if (!result || !result.ok || !result.data) return;
|
|
105
|
-
const tasks = Array.isArray(result.data.tasks) ? result.data.tasks : [];
|
|
106
|
-
if (tasks.length === 0) return;
|
|
107
|
-
|
|
108
|
-
const ledger = _readLedger();
|
|
109
|
-
let wroteLedger = false;
|
|
110
|
-
|
|
111
|
-
for (const task of tasks) {
|
|
112
|
-
const orderId = task && task.atp_order_id;
|
|
113
|
-
if (!orderId) continue;
|
|
114
|
-
if (ledger.submitted[orderId]) continue;
|
|
115
|
-
// Only deliver once the task has a result asset (i.e. solidify finished).
|
|
116
|
-
if (!task.result_asset_id) continue;
|
|
117
|
-
// Don't try to deliver on already-terminal statuses.
|
|
118
|
-
if (task.status && task.status !== 'claimed' && task.status !== 'completed') continue;
|
|
119
|
-
|
|
120
|
-
const proofPayload = _buildProofPayload(task);
|
|
121
|
-
const resp = await hubClient.submitDelivery(orderId, proofPayload);
|
|
122
|
-
if (resp && resp.ok) {
|
|
123
|
-
ledger.submitted[orderId] = Date.now();
|
|
124
|
-
wroteLedger = true;
|
|
125
|
-
console.log('[ATP-AutoDeliver] Delivered order=' + orderId + ' asset=' + (task.result_asset_id || 'none'));
|
|
126
|
-
} else {
|
|
127
|
-
// Record terminal-ish errors in the ledger so we do not hammer the
|
|
128
|
-
// same order every minute. Everything else (transient network) is
|
|
129
|
-
// retried on the next tick.
|
|
130
|
-
const err = (resp && resp.error) || 'unknown_error';
|
|
131
|
-
const status = resp && resp.status;
|
|
132
|
-
const terminal = status === 400 || status === 404 || status === 409;
|
|
133
|
-
if (terminal) {
|
|
134
|
-
ledger.submitted[orderId] = -Date.now();
|
|
135
|
-
wroteLedger = true;
|
|
136
|
-
}
|
|
137
|
-
console.log('[ATP-AutoDeliver] Delivery failed order=' + orderId + ' status=' + (status || 'n/a') + ' err=' + String(err).slice(0, 120));
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (wroteLedger) _writeLedger(ledger);
|
|
142
|
-
} catch (err) {
|
|
143
|
-
console.log('[ATP-AutoDeliver] Tick threw (non-fatal): ' + (err && err.message || err));
|
|
144
|
-
} finally {
|
|
145
|
-
_inflight = false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function start(opts) {
|
|
150
|
-
if (_started) return;
|
|
151
|
-
if (!_isEnabled()) return;
|
|
152
|
-
const requested = Number((opts && opts.pollMs) || process.env.ATP_AUTODELIVER_POLL_MS || DEFAULT_POLL_MS);
|
|
153
|
-
_pollMs = Math.max(MIN_POLL_MS, Math.floor(requested) || DEFAULT_POLL_MS);
|
|
154
|
-
_started = true;
|
|
155
|
-
_pollInterval = setInterval(function () {
|
|
156
|
-
_tick().catch(function () { /* swallowed in _tick */ });
|
|
157
|
-
}, _pollMs);
|
|
158
|
-
// .unref() so this background poller does NOT keep the Node event
|
|
159
|
-
// loop alive on its own. `evolver run` (single-shot) writes its
|
|
160
|
-
// artifacts and expects to exit — without unref, the setInterval
|
|
161
|
-
// handle pins the process and the run sits as a residual `node`
|
|
162
|
-
// process until manually killed (public issue #553). `evolver --loop`
|
|
163
|
-
// (daemon) keeps the foreground evolve loop alive on its own
|
|
164
|
-
// schedule, so an unref'd poller still polls — unref only changes
|
|
165
|
-
// whether THIS handle alone keeps the loop alive, not whether the
|
|
166
|
-
// handle fires.
|
|
167
|
-
if (_pollInterval && typeof _pollInterval.unref === 'function') {
|
|
168
|
-
_pollInterval.unref();
|
|
169
|
-
}
|
|
170
|
-
// Do not await -- fire the first tick asynchronously so start() returns
|
|
171
|
-
// immediately. This matches the autoBuyer start() semantics.
|
|
172
|
-
_tick().catch(function () { /* swallowed in _tick */ });
|
|
173
|
-
console.log('[ATP-AutoDeliver] Started (pollMs=' + _pollMs + ')');
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function stop() {
|
|
177
|
-
if (_pollInterval) {
|
|
178
|
-
clearInterval(_pollInterval);
|
|
179
|
-
_pollInterval = null;
|
|
180
|
-
}
|
|
181
|
-
_started = false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function isStarted() {
|
|
185
|
-
return _started;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function _resetForTests() {
|
|
189
|
-
stop();
|
|
190
|
-
_inflight = false;
|
|
191
|
-
_pollMs = DEFAULT_POLL_MS;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
module.exports = {
|
|
195
|
-
start,
|
|
196
|
-
stop,
|
|
197
|
-
isStarted,
|
|
198
|
-
__internals: {
|
|
199
|
-
tick: _tick,
|
|
200
|
-
readLedger: _readLedger,
|
|
201
|
-
writeLedger: _writeLedger,
|
|
202
|
-
buildProofPayload: _buildProofPayload,
|
|
203
|
-
resetForTests: _resetForTests,
|
|
204
|
-
// Test-only accessor for the active poll Timeout. Used to assert
|
|
205
|
-
// the timer was `.unref()`ed so it does not pin the Node event
|
|
206
|
-
// loop (regression guard for public issue #553).
|
|
207
|
-
getPollIntervalForTest: () => _pollInterval,
|
|
208
|
-
constants: {
|
|
209
|
-
DEFAULT_POLL_MS,
|
|
210
|
-
MIN_POLL_MS,
|
|
211
|
-
LEDGER_FILENAME,
|
|
212
|
-
LEDGER_MAX_ENTRIES,
|
|
213
|
-
},
|
|
214
|
-
},
|
|
215
|
-
};
|