evolcore 0.0.9 → 0.0.10
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/CHANGELOG.md +7 -0
- package/dist/config/contact-book-store.js +1 -1
- package/dist/core/data-migration.js +79 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
|
|
4
4
|
[`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
|
|
5
5
|
|
|
6
|
+
## 0.0.10 (2026-08-06)
|
|
7
|
+
|
|
8
|
+
### 数据迁移
|
|
9
|
+
|
|
10
|
+
- 修复联系人审计在多 Agent 场景下重复迁移的问题,审计记录改为写入各 Agent 自身数据目录。
|
|
11
|
+
- 迁移支持增量合并并复用已完成的迁移证明,同时校验来源内容变化,避免内容已改动时被错误跳过。
|
|
12
|
+
|
|
6
13
|
## 0.0.9 (2026-08-06)
|
|
7
14
|
|
|
8
15
|
### 安装与自启
|
|
@@ -566,7 +566,7 @@ function journalFile(selfAid) {
|
|
|
566
566
|
return path.join(agentDir(selfAid), JOURNAL_NAME);
|
|
567
567
|
}
|
|
568
568
|
function appendAudit(selfAid, request, previousContactRevision, contactRevisionValue) {
|
|
569
|
-
const file = path.join(
|
|
569
|
+
const file = path.join(agentDir(selfAid), 'data', 'contact-audit.jsonl');
|
|
570
570
|
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
571
571
|
fs.appendFileSync(file, `${JSON.stringify({
|
|
572
572
|
timestamp: new Date().toISOString(),
|
|
@@ -54,6 +54,14 @@ function operationTopologyKey(operation) {
|
|
|
54
54
|
sessionChannelKey: operation.sessionChannelKey,
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
function operationProofTopologyKey(operation) {
|
|
58
|
+
if (operation.kind !== 'contact-audit-partition')
|
|
59
|
+
return operationTopologyKey(operation);
|
|
60
|
+
return JSON.stringify({
|
|
61
|
+
kind: operation.kind,
|
|
62
|
+
source: operation.source,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
57
65
|
/**
|
|
58
66
|
* Manifests written before allowExistingDestinationMerge was introduced still
|
|
59
67
|
* need safe recovery. Re-derive that narrow permission from their immutable
|
|
@@ -125,26 +133,40 @@ function acknowledgeCompletedCopies(root, operations) {
|
|
|
125
133
|
if (manifest.planSignature === currentPlanSignature)
|
|
126
134
|
return;
|
|
127
135
|
for (const operation of manifest.operations ?? []) {
|
|
128
|
-
if (operation.kind !== 'copy'
|
|
136
|
+
if ((operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
|
|
137
|
+
|| (operation.status !== 'committed' && operation.status !== 'skipped')
|
|
138
|
+
|| !operation.sourceHash)
|
|
129
139
|
continue;
|
|
130
|
-
const key =
|
|
131
|
-
if (!proofs.has(key))
|
|
132
|
-
proofs.set(key, {
|
|
140
|
+
const key = operationProofTopologyKey(operation);
|
|
141
|
+
if (!proofs.has(key)) {
|
|
142
|
+
proofs.set(key, {
|
|
143
|
+
migrationId: manifest.id,
|
|
144
|
+
sourceHash: operation.sourceHash,
|
|
145
|
+
destinations: operation.kind === 'contact-audit-partition'
|
|
146
|
+
? [...(operation.destinations ?? [])]
|
|
147
|
+
: operation.destination ? [operation.destination] : [],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
133
150
|
}
|
|
134
151
|
}
|
|
135
152
|
}
|
|
136
153
|
catch { }
|
|
137
154
|
for (const operation of operations) {
|
|
138
|
-
if (operation.kind !== 'copy'
|
|
155
|
+
if (operation.kind !== 'copy' && operation.kind !== 'contact-audit-partition')
|
|
139
156
|
continue;
|
|
140
|
-
const proof = proofs.get(
|
|
157
|
+
const proof = proofs.get(operationProofTopologyKey(operation));
|
|
141
158
|
if (!proof)
|
|
142
159
|
continue;
|
|
160
|
+
if (operation.kind === 'copy' && (!operation.destination || !fs.existsSync(operation.destination)))
|
|
161
|
+
continue;
|
|
162
|
+
if (operation.kind === 'contact-audit-partition' && proof.destinations.some(destination => !fs.existsSync(destination)))
|
|
163
|
+
continue;
|
|
143
164
|
try {
|
|
144
165
|
if (treeHash(operation.source, operation.exclude) !== proof.sourceHash)
|
|
145
166
|
continue;
|
|
146
167
|
operation.status = 'acknowledged';
|
|
147
168
|
operation.acknowledgedBy = proof.migrationId;
|
|
169
|
+
operation.sourceHash = proof.sourceHash;
|
|
148
170
|
}
|
|
149
171
|
catch { }
|
|
150
172
|
}
|
|
@@ -1419,6 +1441,18 @@ function atomicWriteText(target, content) {
|
|
|
1419
1441
|
function hashContent(content) {
|
|
1420
1442
|
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
1421
1443
|
}
|
|
1444
|
+
function mergeJsonlContent(existing, incoming, deduplicateExisting = false) {
|
|
1445
|
+
const existingLines = existing.split('\n').filter(line => line.trim());
|
|
1446
|
+
const merged = deduplicateExisting ? [...new Set(existingLines)] : [...existingLines];
|
|
1447
|
+
const lines = new Set(merged);
|
|
1448
|
+
for (const line of incoming.split('\n')) {
|
|
1449
|
+
if (!line.trim() || lines.has(line))
|
|
1450
|
+
continue;
|
|
1451
|
+
lines.add(line);
|
|
1452
|
+
merged.push(line);
|
|
1453
|
+
}
|
|
1454
|
+
return merged.length ? `${merged.join('\n')}\n` : '';
|
|
1455
|
+
}
|
|
1422
1456
|
function appendIssue(operation, issue) {
|
|
1423
1457
|
const issues = operation.issues ?? [];
|
|
1424
1458
|
if (!issues.includes(issue))
|
|
@@ -1457,20 +1491,18 @@ function executeContactAudit(manifest, operation, checkpoint) {
|
|
|
1457
1491
|
for (const [aid, lines] of groups) {
|
|
1458
1492
|
const destination = path.join(manifest.root, 'agents', aid, 'data', 'contact-audit.jsonl');
|
|
1459
1493
|
const content = lines.join('\n') + '\n';
|
|
1460
|
-
const
|
|
1494
|
+
const existing = fs.existsSync(destination) ? fs.readFileSync(destination, 'utf8') : undefined;
|
|
1495
|
+
if (existing !== undefined)
|
|
1496
|
+
strictJsonValidation(destination);
|
|
1497
|
+
const expectedContent = existing === undefined ? content : mergeJsonlContent(existing, content);
|
|
1498
|
+
const expectedHash = hashContent(expectedContent);
|
|
1461
1499
|
operation.destinations.push(destination);
|
|
1462
1500
|
operation.destinationHashes[destination] = expectedHash;
|
|
1463
|
-
if (
|
|
1464
|
-
const existing = fs.readFileSync(destination, 'utf8');
|
|
1501
|
+
if (existing !== undefined) {
|
|
1465
1502
|
if (hashContent(existing) !== expectedHash) {
|
|
1466
|
-
//
|
|
1467
|
-
//
|
|
1468
|
-
|
|
1469
|
-
if (!content.startsWith(existing)) {
|
|
1470
|
-
appendIssue(operation, `destination conflict: ${destination}`);
|
|
1471
|
-
continue;
|
|
1472
|
-
}
|
|
1473
|
-
atomicWriteText(destination, content);
|
|
1503
|
+
// The per-Agent audit may have advanced after an earlier migration.
|
|
1504
|
+
// Preserve its order and append only legacy records not already present.
|
|
1505
|
+
atomicWriteText(destination, expectedContent);
|
|
1474
1506
|
}
|
|
1475
1507
|
continue;
|
|
1476
1508
|
}
|
|
@@ -1600,15 +1632,7 @@ function trackMergeSources(operation) {
|
|
|
1600
1632
|
return sources;
|
|
1601
1633
|
}
|
|
1602
1634
|
function mergeJsonl(target, source) {
|
|
1603
|
-
|
|
1604
|
-
const merged = [...lines];
|
|
1605
|
-
for (const line of fs.readFileSync(source, 'utf8').split('\n')) {
|
|
1606
|
-
if (!line.trim() || lines.has(line))
|
|
1607
|
-
continue;
|
|
1608
|
-
lines.add(line);
|
|
1609
|
-
merged.push(line);
|
|
1610
|
-
}
|
|
1611
|
-
fs.writeFileSync(target, merged.length ? `${merged.join('\n')}\n` : '');
|
|
1635
|
+
fs.writeFileSync(target, mergeJsonlContent(fs.readFileSync(target, 'utf8'), fs.readFileSync(source, 'utf8'), true));
|
|
1612
1636
|
}
|
|
1613
1637
|
function copyTree(source, destination) {
|
|
1614
1638
|
const stat = fs.statSync(source);
|
|
@@ -1741,6 +1765,34 @@ export function readDataMigration(root, id) {
|
|
|
1741
1765
|
throw new Error(`migration manifest not found: ${id}`);
|
|
1742
1766
|
return manifest;
|
|
1743
1767
|
}
|
|
1768
|
+
function completedMigrationMatchesPlan(manifest, plan, planSignature) {
|
|
1769
|
+
if (manifest.state !== 'completed' || manifest.planSignature !== planSignature)
|
|
1770
|
+
return false;
|
|
1771
|
+
const proofs = new Map(manifest.operations.map(operation => [operationProofTopologyKey(operation), operation]));
|
|
1772
|
+
for (const operation of plan.operations.filter(item => item.status !== 'acknowledged')) {
|
|
1773
|
+
const proof = proofs.get(operationProofTopologyKey(operation));
|
|
1774
|
+
if (!proof || (proof.status !== 'committed' && proof.status !== 'skipped' && proof.status !== 'acknowledged'))
|
|
1775
|
+
return false;
|
|
1776
|
+
try {
|
|
1777
|
+
for (const source of operationSources(operation)) {
|
|
1778
|
+
const expected = proof.sourceHashes?.[source]
|
|
1779
|
+
?? (source === proof.source ? proof.sourceHash : undefined);
|
|
1780
|
+
if (!expected || treeHash(source, operation.exclude) !== expected)
|
|
1781
|
+
return false;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
catch {
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
if ((operation.kind === 'copy' || operation.kind === 'merge-copy')
|
|
1788
|
+
&& (!operation.destination || !fs.existsSync(operation.destination)))
|
|
1789
|
+
return false;
|
|
1790
|
+
if (operation.kind === 'contact-audit-partition'
|
|
1791
|
+
&& (proof.destinations ?? []).some(destination => !fs.existsSync(destination)))
|
|
1792
|
+
return false;
|
|
1793
|
+
}
|
|
1794
|
+
return true;
|
|
1795
|
+
}
|
|
1744
1796
|
/**
|
|
1745
1797
|
* Startup gate for the explicit migration workflow. Legacy source files are
|
|
1746
1798
|
* deliberately retained after success, so their mere presence is not enough
|
|
@@ -1763,7 +1815,7 @@ export function inspectDataMigrationRequirement(root) {
|
|
|
1763
1815
|
if (!entry.isFile() || !entry.name.endsWith('.json'))
|
|
1764
1816
|
continue;
|
|
1765
1817
|
const manifest = readJson(path.join(migrationDir(root), entry.name));
|
|
1766
|
-
if (manifest
|
|
1818
|
+
if (!manifest || !completedMigrationMatchesPlan(manifest, plan, planSignature))
|
|
1767
1819
|
continue;
|
|
1768
1820
|
completedMigrationId = manifest.id;
|
|
1769
1821
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolcore",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.10",
|
|
4
4
|
"description": "AI Agent gateway connecting Claude, Codex, Gemini, and the bundled ecagent runner to messaging channels with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|