@evomap/evolver-core 2.0.0-beta.2 → 2.0.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/algo/candidateAssembly.js +9 -6
  2. package/dist/algo/cycleEngine.d.ts +11 -0
  3. package/dist/algo/cycleEngine.js +12 -6
  4. package/dist/algo/cycleFailureClassifier.d.ts +1 -1
  5. package/dist/algo/geneSelection.d.ts +12 -1
  6. package/dist/algo/geneSelection.js +24 -8
  7. package/dist/algo/index.d.ts +1 -0
  8. package/dist/algo/index.js +1 -0
  9. package/dist/algo/memoryGraph.d.ts +62 -0
  10. package/dist/algo/memoryGraph.js +86 -0
  11. package/dist/algo/orchestrator.d.ts +3 -0
  12. package/dist/algo/orchestrator.js +14 -2
  13. package/dist/assetstore/assetSidecarRecords.d.ts +23 -0
  14. package/dist/assetstore/assetSidecarRecords.js +142 -0
  15. package/dist/assetstore/assetSidecarRecovery.d.ts +48 -0
  16. package/dist/assetstore/assetSidecarRecovery.js +288 -0
  17. package/dist/assetstore/assetStoreHealth.d.ts +75 -0
  18. package/dist/assetstore/assetStoreHealth.js +277 -0
  19. package/dist/assetstore/assetStoreLayout.d.ts +2 -0
  20. package/dist/assetstore/assetStoreLayout.js +6 -0
  21. package/dist/assetstore/assetStoreStorage.d.ts +42 -0
  22. package/dist/assetstore/assetStoreStorage.js +318 -0
  23. package/dist/assetstore/assetSyncLedger.d.ts +5 -1
  24. package/dist/assetstore/assetSyncLedger.js +44 -64
  25. package/dist/assetstore/index.d.ts +2 -0
  26. package/dist/assetstore/index.js +2 -0
  27. package/dist/assetstore/localJsonl.d.ts +1 -0
  28. package/dist/assetstore/localJsonl.js +36 -32
  29. package/dist/assetstore/provenance.d.ts +13 -0
  30. package/dist/assetstore/provenance.js +60 -84
  31. package/dist/assetstore/provider.d.ts +2 -0
  32. package/dist/assetstore/reviewFilter.js +3 -1
  33. package/dist/assetstore/reviewLedger.d.ts +8 -2
  34. package/dist/assetstore/reviewLedger.js +71 -45
  35. package/dist/benchmark/index.d.ts +2 -1
  36. package/dist/benchmark/index.js +2 -1
  37. package/dist/benchmark/triggerShift.d.ts +62 -0
  38. package/dist/benchmark/triggerShift.js +106 -0
  39. package/dist/events/ingest.d.ts +1 -1
  40. package/dist/events/ingest.js +2 -0
  41. package/dist/events/paths.d.ts +1 -1
  42. package/dist/events/paths.js +2 -2
  43. package/dist/exec/autoExec.d.ts +6 -1
  44. package/dist/exec/autoExec.js +31 -0
  45. package/dist/exec/autonomousCycle.d.ts +2 -0
  46. package/dist/exec/autonomousCycle.js +5 -0
  47. package/dist/exec/claudeBridge.d.ts +7 -2
  48. package/dist/exec/claudeBridge.js +92 -14
  49. package/dist/exec/prompt.js +9 -0
  50. package/dist/exec/runnerRegistry.d.ts +56 -12
  51. package/dist/exec/runnerRegistry.js +272 -22
  52. package/dist/hub/bindings.js +12 -2
  53. package/dist/ops/savingsCore.js +1 -2
  54. package/dist/ops/selfUpdate.d.ts +10 -1
  55. package/dist/ops/selfUpdate.js +64 -15
  56. package/dist/util/fileLock.d.ts +19 -2
  57. package/dist/util/fileLock.js +166 -31
  58. package/package.json +5 -1
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
2
+ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
3
3
  import { basename, dirname, join, resolve } from 'node:path';
4
4
  export function syncSleep(ms) {
5
5
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
@@ -7,11 +7,21 @@ export function syncSleep(ms) {
7
7
  const ownedLocks = new Map();
8
8
  export class LockTimeoutError extends Error {
9
9
  code = 'LOCK_TIMEOUT';
10
- constructor(lockPath) {
11
- super(`获取文件锁超时: ${lockPath}`);
10
+ constructor(_lockPath) {
11
+ super('获取文件锁超时');
12
12
  this.name = 'LockTimeoutError';
13
13
  }
14
14
  }
15
+ export class UnsafeLockPathError extends Error {
16
+ reason;
17
+ code = 'UNSAFE_LOCK_PATH';
18
+ constructor(reason) {
19
+ super(`不安全的文件锁路径: ${reason}`);
20
+ this.reason = reason;
21
+ this.name = 'UnsafeLockPathError';
22
+ }
23
+ }
24
+ export const MAX_LOCK_OWNER_BYTES = 4096;
15
25
  function lockKey(lockPath) {
16
26
  return resolve(lockPath);
17
27
  }
@@ -24,6 +34,73 @@ function serializeOwner(owner) {
24
34
  function isErrno(error, code) {
25
35
  return typeof error === 'object' && error !== null && error.code === code;
26
36
  }
37
+ function noFollowFlag() {
38
+ return constants['O_NOFOLLOW'] ?? 0;
39
+ }
40
+ function assertRegularOwnerStat(stat) {
41
+ if (stat.isSymbolicLink())
42
+ throw new UnsafeLockPathError('symlink');
43
+ if (!stat.isFile())
44
+ throw new UnsafeLockPathError('not_regular_file');
45
+ if (stat.size > BigInt(MAX_LOCK_OWNER_BYTES))
46
+ throw new UnsafeLockPathError('owner_too_large');
47
+ }
48
+ function currentOwnerStat(path) {
49
+ try {
50
+ const stat = lstatSync(path, { bigint: true });
51
+ assertRegularOwnerStat(stat);
52
+ return stat;
53
+ }
54
+ catch (error) {
55
+ if (isErrno(error, 'ENOENT'))
56
+ return null;
57
+ if (isErrno(error, 'EACCES') || isErrno(error, 'EPERM')) {
58
+ throw new UnsafeLockPathError('permission_denied');
59
+ }
60
+ throw error;
61
+ }
62
+ }
63
+ function readOwnerBounded(path) {
64
+ const before = currentOwnerStat(path);
65
+ if (before === null)
66
+ return null;
67
+ let fd;
68
+ try {
69
+ fd = openSync(path, constants.O_RDONLY | noFollowFlag());
70
+ }
71
+ catch (error) {
72
+ if (isErrno(error, 'ENOENT'))
73
+ return null;
74
+ if (isErrno(error, 'ELOOP'))
75
+ throw new UnsafeLockPathError('symlink');
76
+ if (isErrno(error, 'EACCES') || isErrno(error, 'EPERM')) {
77
+ throw new UnsafeLockPathError('permission_denied');
78
+ }
79
+ throw error;
80
+ }
81
+ try {
82
+ const opened = fstatSync(fd, { bigint: true });
83
+ assertRegularOwnerStat(opened);
84
+ const current = currentOwnerStat(path);
85
+ if (current === null || current.dev !== opened.dev || current.ino !== opened.ino) {
86
+ throw new UnsafeLockPathError('path_changed');
87
+ }
88
+ const buffer = Buffer.alloc(MAX_LOCK_OWNER_BYTES + 1);
89
+ let total = 0;
90
+ while (total < buffer.length) {
91
+ const read = readSync(fd, buffer, total, buffer.length - total, total);
92
+ if (read === 0)
93
+ break;
94
+ total += read;
95
+ }
96
+ if (total > MAX_LOCK_OWNER_BYTES)
97
+ throw new UnsafeLockPathError('owner_too_large');
98
+ return buffer.subarray(0, total).toString('utf8');
99
+ }
100
+ finally {
101
+ closeSync(fd);
102
+ }
103
+ }
27
104
  /** True if pid is a live process (cross-platform via signal 0; EPERM = exists but owned by another user). */
28
105
  function pidAlive(pid) {
29
106
  if (!Number.isInteger(pid) || pid <= 0)
@@ -73,10 +150,13 @@ function parseOwner(raw) {
73
150
  /** Owner recorded in the lock file; null if missing/unparseable. */
74
151
  function lockOwner(lockPath) {
75
152
  try {
76
- return parseOwner(readFileSync(lockPath, 'utf8'));
153
+ const raw = readOwnerBounded(lockPath);
154
+ return raw === null ? null : parseOwner(raw);
77
155
  }
78
156
  catch (error) {
79
- if (isErrno(error, 'ENOENT'))
157
+ // A valid owner can release and another waiter can acquire between lstat/open/fstat. Treat that snapshot as
158
+ // transiently unavailable: callers remain conservative (no steal/delete) and the acquire loop retries.
159
+ if (error instanceof UnsafeLockPathError && error.reason === 'path_changed')
80
160
  return null;
81
161
  throw error;
82
162
  }
@@ -100,17 +180,30 @@ function removeFileIfExists(path) {
100
180
  }
101
181
  }
102
182
  function createLockFile(lockPath, owner, trackOwnership) {
103
- try {
104
- writeFileSync(lockPath, serializeOwner(owner), { flag: 'wx', mode: 0o600 });
105
- if (trackOwnership)
106
- ownedLocks.set(lockKey(lockPath), owner);
107
- return true;
108
- }
109
- catch (error) {
110
- if (isErrno(error, 'EEXIST'))
111
- return false;
112
- throw error;
183
+ for (let permissionAttempt = 0; permissionAttempt < 2; permissionAttempt++) {
184
+ try {
185
+ writeFileSync(lockPath, serializeOwner(owner), { flag: 'wx', mode: 0o600 });
186
+ if (trackOwnership)
187
+ ownedLocks.set(lockKey(lockPath), { owner, releasePending: false });
188
+ return true;
189
+ }
190
+ catch (error) {
191
+ if (isErrno(error, 'EEXIST'))
192
+ return false;
193
+ if (isErrno(error, 'EACCES') || isErrno(error, 'EPERM')) {
194
+ // Windows can report EPERM instead of EEXIST during contention. A regular owner is busy; if the owner
195
+ // disappeared before lstat, retry create once to distinguish that release race from a persistent ACL
196
+ // failure. Unsafe/inaccessible paths still throw from currentOwnerStat and remain fail closed.
197
+ if (currentOwnerStat(lockPath) !== null)
198
+ return false;
199
+ if (permissionAttempt === 0)
200
+ continue;
201
+ throw new UnsafeLockPathError('permission_denied');
202
+ }
203
+ throw error;
204
+ }
113
205
  }
206
+ throw new UnsafeLockPathError('permission_denied');
114
207
  }
115
208
  function tryAcquireMutationGuard(lockPath) {
116
209
  const guardPath = mutationGuardPath(lockPath);
@@ -185,6 +278,12 @@ function reclaimStaleLock(lockPath) {
185
278
  * synchronous critical sections (append-only writes).
186
279
  */
187
280
  export function acquireLock(lockPath, opts = {}) {
281
+ const pending = ownedLocks.get(lockKey(lockPath));
282
+ if (pending?.releasePending) {
283
+ const released = releaseLock(lockPath);
284
+ if (!released.released)
285
+ throw new LockReleaseError(released.reason);
286
+ }
188
287
  const maxTries = opts.maxTries ?? 300;
189
288
  const waitMs = opts.waitMs ?? 10;
190
289
  for (let i = 0; i < maxTries; i++) {
@@ -200,28 +299,64 @@ export function acquireLock(lockPath, opts = {}) {
200
299
  }
201
300
  syncSleep(waitMs);
202
301
  }
203
- throw new LockTimeoutError(lockPath);
302
+ throw new LockTimeoutError();
204
303
  }
205
304
  export function releaseLock(lockPath) {
206
305
  const key = lockKey(lockPath);
207
- const owner = ownedLocks.get(key);
208
- if (owner === undefined)
209
- return;
306
+ const owned = ownedLocks.get(key);
307
+ if (owned === undefined)
308
+ return { released: true, reason: 'not_owned' };
309
+ const failed = (reason) => {
310
+ owned.releasePending = true;
311
+ return { released: false, reason };
312
+ };
313
+ // Release must not use lockOwner(): that helper intentionally folds path_changed into a conservative null
314
+ // for acquire retries. Here, an inconclusive read marks a pending release so the next acquire can retry or
315
+ // surface LOCK_RELEASE_FAILED instead of silently timing out behind this process's own PID.
316
+ let raw;
210
317
  try {
211
- if (!sameOwner(lockOwner(lockPath), owner))
212
- return;
213
- const releasedPath = sidecarPath(lockPath, 'released');
214
- try {
215
- renameSync(lockPath, releasedPath);
216
- }
217
- catch (error) {
218
- if (isErrno(error, 'ENOENT'))
219
- return;
220
- throw error;
318
+ raw = readOwnerBounded(lockPath);
319
+ }
320
+ catch (error) {
321
+ return failed(error instanceof UnsafeLockPathError ? error.reason : 'release_failed');
322
+ }
323
+ if (raw === null) {
324
+ ownedLocks.delete(key);
325
+ return { released: true, reason: 'missing' };
326
+ }
327
+ const current = parseOwner(raw);
328
+ if (current === null)
329
+ return failed('invalid_owner');
330
+ if (!sameOwner(current, owned.owner)) {
331
+ ownedLocks.delete(key);
332
+ return { released: true, reason: 'ownership_changed' };
333
+ }
334
+ const releasedPath = sidecarPath(lockPath, 'released');
335
+ try {
336
+ renameSync(lockPath, releasedPath);
337
+ }
338
+ catch (error) {
339
+ if (isErrno(error, 'ENOENT')) {
340
+ ownedLocks.delete(key);
341
+ return { released: true, reason: 'missing' };
221
342
  }
343
+ return failed('release_failed');
344
+ }
345
+ ownedLocks.delete(key);
346
+ try {
222
347
  removeFileIfExists(releasedPath);
223
348
  }
224
- finally {
225
- ownedLocks.delete(key);
349
+ catch {
350
+ return { released: true, reason: 'released_with_cleanup_error' };
351
+ }
352
+ return { released: true, reason: 'released' };
353
+ }
354
+ export class LockReleaseError extends Error {
355
+ reason;
356
+ code = 'LOCK_RELEASE_FAILED';
357
+ constructor(reason) {
358
+ super(`文件锁释放未完成: ${reason}`);
359
+ this.reason = reason;
360
+ this.name = 'LockReleaseError';
226
361
  }
227
362
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@evomap/evolver-core",
3
- "version": "2.0.0-beta.2",
3
+ "version": "2.0.0-beta.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "hub-无关核心: 算法引擎/原材料/mailbox/资产库/workflow",
@@ -17,6 +17,10 @@
17
17
  "zod": "^3.24.1",
18
18
  "ulid": "^2.3.0"
19
19
  },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/EvoMap/evolver.git"
23
+ },
20
24
  "publishConfig": {
21
25
  "access": "public",
22
26
  "tag": "v2-beta"