agentsmesh 0.35.0 → 0.37.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/CHANGELOG.md +44 -0
- package/README.md +2 -0
- package/dist/canonical.js +242 -123
- package/dist/canonical.js.map +1 -1
- package/dist/cli.js +257 -250
- package/dist/engine.js +398 -157
- package/dist/engine.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +419 -166
- package/dist/index.js.map +1 -1
- package/dist/{init-C3pMnqoU.d.ts → init-BtgoRmIs.d.ts} +5 -6
- package/dist/lessons.d.ts +2 -2
- package/dist/lessons.js +198 -61
- package/dist/lessons.js.map +1 -1
- package/dist/targets.js +231 -103
- package/dist/targets.js.map +1 -1
- package/package.json +2 -2
|
@@ -84,6 +84,8 @@ interface LessonsQuery {
|
|
|
84
84
|
interface MatchedLesson {
|
|
85
85
|
readonly id: string;
|
|
86
86
|
readonly lesson: Lesson;
|
|
87
|
+
/** Set when the lesson was reached by its wording, not a trigger (lexical retrieval). */
|
|
88
|
+
readonly lexical?: true;
|
|
87
89
|
}
|
|
88
90
|
/**
|
|
89
91
|
* Recall primitive — returns every active lesson whose triggers match any of
|
|
@@ -98,6 +100,8 @@ interface RankReason {
|
|
|
98
100
|
readonly bm25: number;
|
|
99
101
|
readonly specificity: number;
|
|
100
102
|
readonly topicCoherence: number;
|
|
103
|
+
/** Reached by rule wording rather than a trigger (see lexical-retrieval.ts). */
|
|
104
|
+
readonly lexical?: true;
|
|
101
105
|
}
|
|
102
106
|
interface RankedLesson {
|
|
103
107
|
readonly id: string;
|
|
@@ -379,13 +383,8 @@ declare function importLegacyLessons(projectRoot: string, options: ImportLegacyO
|
|
|
379
383
|
/**
|
|
380
384
|
* Cross-platform process lock backed by an atomic mkdir.
|
|
381
385
|
*
|
|
382
|
-
* Acquire semantics: mkdir with no `recursive` option is atomic per POSIX and
|
|
383
|
-
* returns EEXIST if the directory already exists. That makes it a reliable
|
|
384
|
-
* cross-process mutex without any third-party dependency.
|
|
385
|
-
*
|
|
386
386
|
* Stale recovery: the holder writes its PID and start timestamp into the lock
|
|
387
|
-
* dir.
|
|
388
|
-
* once. A live holder (or one on another host, whose PID we cannot probe) is
|
|
387
|
+
* dir. A dead same-host holder is evicted at once. A live or remote holder is
|
|
389
388
|
* evicted only past `staleMs` — an hours-long bound that catches a hung
|
|
390
389
|
* process or a recycled PID, never a slow but healthy run.
|
|
391
390
|
*/
|
package/dist/lessons.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { o as RankedLesson, j as LessonsQuery, A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult } from './init-
|
|
2
|
-
export { c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, X as DEFAULT_RECALL_MAX_TOKENS, I as ImportLegacyOptions, d as ImportLegacyReport, Y as LESSONS_LOCK_FILENAME, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, Z as LessonsGraphExistsError, h as LessonsGraphSchema, i as LessonsPaths, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, _ as lessonsLockPath, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-
|
|
1
|
+
import { o as RankedLesson, j as LessonsQuery, A as AddLessonInput, a as AddLessonOptions, b as AddLessonResult } from './init-BtgoRmIs.js';
|
|
2
|
+
export { c as AddLessonTriggers, D as DEFAULT_RECALL_LIMIT, X as DEFAULT_RECALL_MAX_TOKENS, I as ImportLegacyOptions, d as ImportLegacyReport, Y as LESSONS_LOCK_FILENAME, L as LESSONS_PROCEDURAL_RULE, e as Lesson, f as LessonStatus, g as LessonsGraph, Z as LessonsGraphExistsError, h as LessonsGraphSchema, i as LessonsPaths, M as MatchedLesson, k as MergeLessonsOptions, l as MergeLessonsResult, m as MutateOptions, R as RankOptions, n as RankReason, S as ScaffoldLessonsResult, p as StripMarkersOptions, q as StripMarkersReport, T as Topic, r as Trigger, s as TriggerKind, U as UnknownTopicError, V as ValidationFinding, t as ValidationLevel, u as ValidationReport, v as acquireLessonsLock, w as addLesson, x as graphFilePath, y as importLegacyLessons, _ as lessonsLockPath, z as lessonsPaths, B as loadLessonsGraph, C as mergeLessons, E as mutateLessonsGraph, F as parseGraph, G as queryLessons, H as rankLessons, J as scaffoldLessons, K as serializeGraph, N as stripLegacyMarkers, O as stripMarkersInGraph, P as toRelPath, Q as tryLoadLessonsGraph, W as validateLessonsGraph } from './init-BtgoRmIs.js';
|
|
3
3
|
import 'zod';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/lessons.js
CHANGED
|
@@ -2,11 +2,11 @@ import { z } from 'zod';
|
|
|
2
2
|
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, statSync, renameSync, readdirSync, realpathSync, appendFileSync } from 'fs';
|
|
3
3
|
import { resolve, dirname, join, relative, sep, basename, extname } from 'path';
|
|
4
4
|
import { stringify, parse, parseDocument, YAMLSeq, YAMLMap } from 'yaml';
|
|
5
|
-
import { createHash } from 'crypto';
|
|
5
|
+
import { createHash, randomUUID } from 'crypto';
|
|
6
6
|
import picomatch from 'picomatch';
|
|
7
|
-
import { mkdir, rm, writeFile, readFile, stat, lstat,
|
|
7
|
+
import { mkdir, rm, writeFile, readFile, stat, lstat, open, rename } from 'fs/promises';
|
|
8
8
|
import { tmpdir, hostname } from 'os';
|
|
9
|
-
import 'timers/promises';
|
|
9
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
10
10
|
|
|
11
11
|
// src/lessons/graph-schema.ts
|
|
12
12
|
var CURRENT_GRAPH_VERSION = 2;
|
|
@@ -1190,8 +1190,7 @@ async function acquireProcessLock(lockPath, opts = {}) {
|
|
|
1190
1190
|
if (acquired) return acquired;
|
|
1191
1191
|
const existing = await inspectLock(lockPath);
|
|
1192
1192
|
if (existing !== "young" && isStale(existing, stale)) {
|
|
1193
|
-
await rm(lockPath, { recursive: true, force: true })
|
|
1194
|
-
});
|
|
1193
|
+
await rm(lockPath, { recursive: true, force: true });
|
|
1195
1194
|
continue;
|
|
1196
1195
|
}
|
|
1197
1196
|
if (attempt >= retries) {
|
|
@@ -1215,7 +1214,13 @@ async function tryAcquire(lockPath) {
|
|
|
1215
1214
|
started: Date.now(),
|
|
1216
1215
|
hostname: getHostname()
|
|
1217
1216
|
};
|
|
1218
|
-
|
|
1217
|
+
try {
|
|
1218
|
+
await writeFile(metadataPath, JSON.stringify(metadata), "utf-8");
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
await rm(lockPath, { recursive: true, force: true }).catch(() => {
|
|
1221
|
+
});
|
|
1222
|
+
throw error;
|
|
1223
|
+
}
|
|
1219
1224
|
let released = false;
|
|
1220
1225
|
const cleanup = () => {
|
|
1221
1226
|
if (released) return;
|
|
@@ -2169,6 +2174,42 @@ function safeRealpath(path) {
|
|
|
2169
2174
|
}
|
|
2170
2175
|
}
|
|
2171
2176
|
|
|
2177
|
+
// src/lessons/lexical-retrieval.ts
|
|
2178
|
+
var LEXICAL_LIMIT = 3;
|
|
2179
|
+
var LEXICAL_MIN_TERMS = 2;
|
|
2180
|
+
var GENERIC = new Set(
|
|
2181
|
+
"top left right bottom before after first last next new old use used using run make keep set get add all any each every not no never always only one two more less same other than then into from over under out up down off via per so if else when where which what who how why do does did done can could should would will may might has have had are was were been being also still just even very both either such these those they them their we you your our he she his her my me our us own same here there now then again once about above below between during through until while because but by need needs needed way ways thing things".split(" ")
|
|
2182
|
+
);
|
|
2183
|
+
function isKeywordOnlyQuery(query) {
|
|
2184
|
+
return query.keyword !== void 0 && query.file === void 0 && query.command === void 0;
|
|
2185
|
+
}
|
|
2186
|
+
function matchLessons(graph, query) {
|
|
2187
|
+
const triggered = queryLessons(graph, query);
|
|
2188
|
+
const lexical = isKeywordOnlyQuery(query) && query.keyword !== void 0 ? lexicalCandidates(
|
|
2189
|
+
graph,
|
|
2190
|
+
query.keyword,
|
|
2191
|
+
triggered.map((m) => m.id)
|
|
2192
|
+
) : [];
|
|
2193
|
+
return { matches: [...triggered, ...lexical], lexicalCount: lexical.length };
|
|
2194
|
+
}
|
|
2195
|
+
function lexicalCandidates(graph, keyword, excludeIds) {
|
|
2196
|
+
const terms = [...new Set(tokenize(keyword))];
|
|
2197
|
+
if (terms.length < LEXICAL_MIN_TERMS) return [];
|
|
2198
|
+
const excluded = new Set(excludeIds);
|
|
2199
|
+
const corpus = buildCorpus(graph);
|
|
2200
|
+
const scored = [];
|
|
2201
|
+
for (const [id, lesson] of Object.entries(graph.lessons)) {
|
|
2202
|
+
if (lesson.status !== "active" || lesson.scope === "always" || excluded.has(id)) continue;
|
|
2203
|
+
const ruleTerms = new Set(tokenize(lesson.rule));
|
|
2204
|
+
let shared = 0;
|
|
2205
|
+
for (const t of terms) if (!GENERIC.has(t) && ruleTerms.has(t)) shared += 1;
|
|
2206
|
+
if (shared < LEXICAL_MIN_TERMS) continue;
|
|
2207
|
+
scored.push({ id, lesson, score: bm25(terms, lesson.rule, corpus) });
|
|
2208
|
+
}
|
|
2209
|
+
scored.sort((a, b) => b.score !== a.score ? b.score - a.score : a.id < b.id ? -1 : 1);
|
|
2210
|
+
return scored.slice(0, LEXICAL_LIMIT).map(({ id, lesson }) => ({ id, lesson, lexical: true }));
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2172
2213
|
// src/lessons/ranking-signals.ts
|
|
2173
2214
|
function buildFanout(graph) {
|
|
2174
2215
|
const fanout = /* @__PURE__ */ new Map();
|
|
@@ -2235,7 +2276,7 @@ function rankLessons(graph, query, matches, options = {}) {
|
|
|
2235
2276
|
const narrowness = buildNarrowness(graph);
|
|
2236
2277
|
const coherence = buildTopicCoherence(matches);
|
|
2237
2278
|
const matchedTriggerIds = collectMatchedTriggerIds(graph, query);
|
|
2238
|
-
const scored = matches.map(({ id, lesson }) => {
|
|
2279
|
+
const scored = matches.map(({ id, lesson, lexical }) => {
|
|
2239
2280
|
const hitTriggers = lesson.triggers.filter((t) => matchedTriggerIds.has(t));
|
|
2240
2281
|
let specificity = 0;
|
|
2241
2282
|
for (const t of hitTriggers) {
|
|
@@ -2251,7 +2292,8 @@ function rankLessons(graph, query, matches, options = {}) {
|
|
|
2251
2292
|
matchedTriggers: hitTriggers,
|
|
2252
2293
|
// Absent from the log ⇒ neutral 1, so a lesson with no outcome data never
|
|
2253
2294
|
// sinks below one that has merely been recorded.
|
|
2254
|
-
effectiveness: options.effectiveness?.get(id) ?? 1
|
|
2295
|
+
effectiveness: options.effectiveness?.get(id) ?? 1,
|
|
2296
|
+
lexical
|
|
2255
2297
|
};
|
|
2256
2298
|
});
|
|
2257
2299
|
const bm25Ranks = rankMap(scored.map((s) => ({ id: s.id, value: s.bm25 })));
|
|
@@ -2266,7 +2308,8 @@ function rankLessons(graph, query, matches, options = {}) {
|
|
|
2266
2308
|
matchedTriggers: s.matchedTriggers,
|
|
2267
2309
|
bm25: s.bm25,
|
|
2268
2310
|
specificity: s.specificity,
|
|
2269
|
-
topicCoherence: s.topicCoherence
|
|
2311
|
+
topicCoherence: s.topicCoherence,
|
|
2312
|
+
...s.lexical === true ? { lexical: true } : {}
|
|
2270
2313
|
}
|
|
2271
2314
|
})).sort((a, b) => {
|
|
2272
2315
|
if (b.score !== a.score) return b.score - a.score;
|
|
@@ -2324,6 +2367,33 @@ function loadRecallConfig(projectRoot) {
|
|
|
2324
2367
|
return fallback;
|
|
2325
2368
|
}
|
|
2326
2369
|
}
|
|
2370
|
+
|
|
2371
|
+
// src/lessons/recall-telemetry.ts
|
|
2372
|
+
function recordRecallTelemetry(projectRoot, graph, query, matches, lessons, options = {}) {
|
|
2373
|
+
if (!isTelemetryEnabled(process.env, projectRoot)) return;
|
|
2374
|
+
const byKind = collectMatchedTriggersByKind(graph, query);
|
|
2375
|
+
const countVia = (set) => matches.filter(({ lesson }) => lesson.triggers.some((t) => set.has(t))).length;
|
|
2376
|
+
const session = options.session ?? sessionId();
|
|
2377
|
+
appendRecallRecord(projectRoot, {
|
|
2378
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2379
|
+
hasFile: query.file !== void 0,
|
|
2380
|
+
hasCommand: query.command !== void 0,
|
|
2381
|
+
hasKeyword: query.keyword !== void 0,
|
|
2382
|
+
totalMatches: matches.length,
|
|
2383
|
+
returnedCount: lessons.length,
|
|
2384
|
+
returnedTokens: lessons.reduce((sum, l) => sum + estTokens(l.lesson.rule), 0),
|
|
2385
|
+
truncated: matches.length > lessons.length,
|
|
2386
|
+
matchedByKind: {
|
|
2387
|
+
file: countVia(byKind.file_glob),
|
|
2388
|
+
command: countVia(byKind.command_pattern),
|
|
2389
|
+
keyword: countVia(byKind.keyword),
|
|
2390
|
+
...options.lexical !== void 0 ? { text: options.lexical } : {}
|
|
2391
|
+
},
|
|
2392
|
+
lessonIds: lessons.map((l) => l.id),
|
|
2393
|
+
bypassed: options.bypassed === true,
|
|
2394
|
+
...session !== void 0 ? { session } : {}
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2327
2397
|
function outcomeLogPath(projectRoot) {
|
|
2328
2398
|
return join(lessonsPaths(projectRoot).base, "outcome-log.jsonl");
|
|
2329
2399
|
}
|
|
@@ -2490,7 +2560,7 @@ async function recallLessons(projectRoot, query, options = {}) {
|
|
|
2490
2560
|
const graph = load.graph;
|
|
2491
2561
|
refreshCommandFastpath(projectRoot, graph, preReadStamp);
|
|
2492
2562
|
const matchQuery = query.file === void 0 ? query : { ...query, file: normalizeRecallFile(query.file, projectRoot) };
|
|
2493
|
-
const matches =
|
|
2563
|
+
const { matches, lexicalCount } = matchLessons(graph, matchQuery);
|
|
2494
2564
|
const dedup = openSessionDedup({
|
|
2495
2565
|
explicit: options.sessionId,
|
|
2496
2566
|
disabled: options.noDedup,
|
|
@@ -2515,34 +2585,11 @@ async function recallLessons(projectRoot, query, options = {}) {
|
|
|
2515
2585
|
);
|
|
2516
2586
|
recordRecallTelemetry(projectRoot, graph, matchQuery, matches, lessons, {
|
|
2517
2587
|
bypassed: false,
|
|
2518
|
-
session: options.sessionId
|
|
2588
|
+
session: options.sessionId,
|
|
2589
|
+
lexical: lexicalCount
|
|
2519
2590
|
});
|
|
2520
2591
|
return { lessons, totalMatches: matches.length, suppressed: matches.length - forRank.length };
|
|
2521
2592
|
}
|
|
2522
|
-
function recordRecallTelemetry(projectRoot, graph, query, matches, lessons, options = {}) {
|
|
2523
|
-
if (!isTelemetryEnabled(process.env, projectRoot)) return;
|
|
2524
|
-
const byKind = collectMatchedTriggersByKind(graph, query);
|
|
2525
|
-
const countVia = (set) => matches.filter(({ lesson }) => lesson.triggers.some((t) => set.has(t))).length;
|
|
2526
|
-
const session = options.session ?? sessionId();
|
|
2527
|
-
appendRecallRecord(projectRoot, {
|
|
2528
|
-
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2529
|
-
hasFile: query.file !== void 0,
|
|
2530
|
-
hasCommand: query.command !== void 0,
|
|
2531
|
-
hasKeyword: query.keyword !== void 0,
|
|
2532
|
-
totalMatches: matches.length,
|
|
2533
|
-
returnedCount: lessons.length,
|
|
2534
|
-
returnedTokens: lessons.reduce((sum, l) => sum + estTokens(l.lesson.rule), 0),
|
|
2535
|
-
truncated: matches.length > lessons.length,
|
|
2536
|
-
matchedByKind: {
|
|
2537
|
-
file: countVia(byKind.file_glob),
|
|
2538
|
-
command: countVia(byKind.command_pattern),
|
|
2539
|
-
keyword: countVia(byKind.keyword)
|
|
2540
|
-
},
|
|
2541
|
-
lessonIds: lessons.map((l) => l.id),
|
|
2542
|
-
bypassed: options.bypassed === true,
|
|
2543
|
-
...session !== void 0 ? { session } : {}
|
|
2544
|
-
});
|
|
2545
|
-
}
|
|
2546
2593
|
|
|
2547
2594
|
// src/lessons/prune.ts
|
|
2548
2595
|
function planPrune(graph, options = {}) {
|
|
@@ -2996,6 +3043,91 @@ function shouldNormalizeLineEndings(path) {
|
|
|
2996
3043
|
const base = basename(path).toLowerCase();
|
|
2997
3044
|
return TEXT_DOTFILES.has(base);
|
|
2998
3045
|
}
|
|
3046
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
3047
|
+
".png",
|
|
3048
|
+
".jpg",
|
|
3049
|
+
".jpeg",
|
|
3050
|
+
".gif",
|
|
3051
|
+
".bmp",
|
|
3052
|
+
".ico",
|
|
3053
|
+
".webp",
|
|
3054
|
+
".avif",
|
|
3055
|
+
".tiff",
|
|
3056
|
+
".pdf",
|
|
3057
|
+
".zip",
|
|
3058
|
+
".gz",
|
|
3059
|
+
".tgz",
|
|
3060
|
+
".bz2",
|
|
3061
|
+
".xz",
|
|
3062
|
+
".7z",
|
|
3063
|
+
".rar",
|
|
3064
|
+
".jar",
|
|
3065
|
+
".woff",
|
|
3066
|
+
".woff2",
|
|
3067
|
+
".ttf",
|
|
3068
|
+
".otf",
|
|
3069
|
+
".eot",
|
|
3070
|
+
".mp3",
|
|
3071
|
+
".mp4",
|
|
3072
|
+
".wav",
|
|
3073
|
+
".ogg",
|
|
3074
|
+
".webm",
|
|
3075
|
+
".mov",
|
|
3076
|
+
".wasm",
|
|
3077
|
+
".bin",
|
|
3078
|
+
".dat",
|
|
3079
|
+
".db",
|
|
3080
|
+
".sqlite",
|
|
3081
|
+
".so",
|
|
3082
|
+
".dylib",
|
|
3083
|
+
".dll",
|
|
3084
|
+
".exe",
|
|
3085
|
+
".class",
|
|
3086
|
+
".pyc",
|
|
3087
|
+
// Office and design documents are zip or proprietary containers.
|
|
3088
|
+
".xlsx",
|
|
3089
|
+
".xls",
|
|
3090
|
+
".docx",
|
|
3091
|
+
".doc",
|
|
3092
|
+
".pptx",
|
|
3093
|
+
".ppt",
|
|
3094
|
+
".odt",
|
|
3095
|
+
".ods",
|
|
3096
|
+
".psd",
|
|
3097
|
+
".ai",
|
|
3098
|
+
".sketch",
|
|
3099
|
+
".fig",
|
|
3100
|
+
".heic",
|
|
3101
|
+
".heif",
|
|
3102
|
+
// Archives, columnar data, models and compressed payloads.
|
|
3103
|
+
".tar",
|
|
3104
|
+
".zst",
|
|
3105
|
+
".br",
|
|
3106
|
+
".lz4",
|
|
3107
|
+
".parquet",
|
|
3108
|
+
".avro",
|
|
3109
|
+
".orc",
|
|
3110
|
+
".pkl",
|
|
3111
|
+
".npy",
|
|
3112
|
+
".npz",
|
|
3113
|
+
".onnx",
|
|
3114
|
+
".pt",
|
|
3115
|
+
".safetensors",
|
|
3116
|
+
".gguf",
|
|
3117
|
+
".sqlite3",
|
|
3118
|
+
".avi",
|
|
3119
|
+
".mkv",
|
|
3120
|
+
".flac",
|
|
3121
|
+
".aac",
|
|
3122
|
+
".m4a",
|
|
3123
|
+
".ttc"
|
|
3124
|
+
]);
|
|
3125
|
+
function isBinaryPayloadPath(path) {
|
|
3126
|
+
return BINARY_EXTENSIONS.has(extname(path).toLowerCase());
|
|
3127
|
+
}
|
|
3128
|
+
function payloadEncodingFor(path) {
|
|
3129
|
+
return isBinaryPayloadPath(path) ? "latin1" : "utf-8";
|
|
3130
|
+
}
|
|
2999
3131
|
function normalizeLineEndings(content) {
|
|
3000
3132
|
return content.replace(/\r\n?/g, "\n");
|
|
3001
3133
|
}
|
|
@@ -3003,11 +3135,28 @@ var EXECUTABLE_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([".sh", ".bash", ".zs
|
|
|
3003
3135
|
function executableModeFor(path) {
|
|
3004
3136
|
return EXECUTABLE_SCRIPT_EXTENSIONS.has(extname(path).toLowerCase()) ? 493 : void 0;
|
|
3005
3137
|
}
|
|
3138
|
+
var TRANSIENT_RENAME_CODES = /* @__PURE__ */ new Set(["EPERM", "EACCES", "EBUSY", "ENOTEMPTY", "EEXIST"]);
|
|
3139
|
+
async function renameWithRetry(from, to, options = {}) {
|
|
3140
|
+
const attempts = options.attempts ?? 5;
|
|
3141
|
+
const delayMs = options.delayMs ?? 50;
|
|
3142
|
+
for (let attempt = 0; ; attempt++) {
|
|
3143
|
+
try {
|
|
3144
|
+
await rename(from, to);
|
|
3145
|
+
return;
|
|
3146
|
+
} catch (err) {
|
|
3147
|
+
const code = err.code;
|
|
3148
|
+
const transient = code !== void 0 && TRANSIENT_RENAME_CODES.has(code);
|
|
3149
|
+
if (!transient || attempt >= attempts - 1) throw err;
|
|
3150
|
+
await setTimeout$1(delayMs * 2 ** attempt);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3006
3154
|
|
|
3007
3155
|
// src/utils/filesystem/fs.ts
|
|
3008
3156
|
async function readFileSafe(path) {
|
|
3009
3157
|
try {
|
|
3010
|
-
const data = await readFile(path,
|
|
3158
|
+
const data = await readFile(path, payloadEncodingFor(path));
|
|
3159
|
+
if (isBinaryPayloadPath(path)) return data;
|
|
3011
3160
|
return data.startsWith(UTF8_BOM) ? data.slice(UTF8_BOM.length) : data;
|
|
3012
3161
|
} catch (err) {
|
|
3013
3162
|
const e = err;
|
|
@@ -3031,40 +3180,28 @@ async function writeFileAtomic(path, content, options) {
|
|
|
3031
3180
|
{ errnoCode: "EISDIR" }
|
|
3032
3181
|
);
|
|
3033
3182
|
}
|
|
3034
|
-
if (info.isSymbolicLink()) {
|
|
3035
|
-
await unlink(path).catch((e) => {
|
|
3036
|
-
if (e.code !== "ENOENT") throw e;
|
|
3037
|
-
});
|
|
3038
|
-
}
|
|
3039
3183
|
} catch (err) {
|
|
3040
3184
|
if (err instanceof FileSystemError) throw err;
|
|
3041
3185
|
const e = err;
|
|
3042
3186
|
if (e.code !== "ENOENT") throw err;
|
|
3043
3187
|
}
|
|
3044
|
-
const tmpPath = `${path}.tmp`;
|
|
3188
|
+
const tmpPath = `${path}.tmp-${randomUUID()}`;
|
|
3045
3189
|
const payload = shouldNormalizeLineEndings(path) ? normalizeLineEndings(content) : content;
|
|
3046
3190
|
const mode = executableModeFor(path);
|
|
3191
|
+
let handle;
|
|
3192
|
+
let ownsTemporaryFile = false;
|
|
3047
3193
|
try {
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
}
|
|
3056
|
-
const writeOpts = {
|
|
3057
|
-
encoding: "utf-8",
|
|
3058
|
-
flag: "w"
|
|
3059
|
-
};
|
|
3060
|
-
if (mode !== void 0) writeOpts.mode = mode;
|
|
3061
|
-
await writeFile(tmpPath, payload, writeOpts);
|
|
3062
|
-
await rename(tmpPath, path);
|
|
3063
|
-
if (mode !== void 0) {
|
|
3064
|
-
await chmod(path, mode);
|
|
3065
|
-
}
|
|
3194
|
+
handle = await open(tmpPath, "wx", mode);
|
|
3195
|
+
ownsTemporaryFile = true;
|
|
3196
|
+
await handle.writeFile(payload, payloadEncodingFor(path));
|
|
3197
|
+
if (mode !== void 0) await handle.chmod(mode);
|
|
3198
|
+
await handle.close();
|
|
3199
|
+
handle = void 0;
|
|
3200
|
+
await renameWithRetry(tmpPath, path);
|
|
3066
3201
|
} catch (err) {
|
|
3067
|
-
await
|
|
3202
|
+
await handle?.close().catch(() => {
|
|
3203
|
+
});
|
|
3204
|
+
if (ownsTemporaryFile) await rm(tmpPath, { force: true }).catch(() => {
|
|
3068
3205
|
});
|
|
3069
3206
|
const e = err;
|
|
3070
3207
|
throw new FileSystemError(
|