@fml-inc/fml 0.8.6 → 0.8.7
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/.claude-plugin/plugin.json +1 -1
- package/README.md +16 -5
- package/dist/{chunk-5TOOBX7I.js → chunk-4YLSKFDU.js} +1140 -111
- package/dist/{chunk-V2XC7JTB.js → chunk-5JJQWV4G.js} +381 -95
- package/dist/{chunk-YFHZJPTC.js → chunk-6BTC3S7C.js} +4 -2
- package/dist/chunk-F57IFY3K.js +21 -0
- package/dist/{chunk-2DQQXBWM.js → chunk-LPSZCTTJ.js} +37 -1
- package/dist/chunk-XFALHTDL.js +277 -0
- package/dist/cli.js +168 -126
- package/dist/hook-handler.js +214 -22
- package/dist/observer-server.js +108 -22
- package/dist/sync-cycle.js +3 -2
- package/package.json +1 -1
- package/dist/chunk-E2MATWOU.js +0 -1225
|
@@ -183,10 +183,10 @@ function setSyncEnabled(settingsFile, enabled) {
|
|
|
183
183
|
}
|
|
184
184
|
|
|
185
185
|
// ../../packages/panopticon/src/artifacts.ts
|
|
186
|
-
import { createHash as
|
|
187
|
-
import
|
|
186
|
+
import { createHash as createHash5, randomUUID as randomUUID3 } from "crypto";
|
|
187
|
+
import fs8 from "fs";
|
|
188
188
|
import os3 from "os";
|
|
189
|
-
import
|
|
189
|
+
import path18 from "path";
|
|
190
190
|
|
|
191
191
|
// ../../packages/agent-targets/src/artifacts/targets/claude.ts
|
|
192
192
|
import path8 from "path";
|
|
@@ -375,6 +375,7 @@ function canonicalGitHubRepository(remote) {
|
|
|
375
375
|
// ../../packages/agent-targets/src/artifacts/repository-context.ts
|
|
376
376
|
var MAX_ARTIFACT_CONTEXT_PREFIX_BYTES = 256 * 1024;
|
|
377
377
|
var MAX_ARTIFACT_CONTEXT_RECORDS = 64;
|
|
378
|
+
var ARTIFACT_CONTEXT_READ_CHUNK_BYTES = 32 * 1024;
|
|
378
379
|
var MAX_CWD_BYTES = 4096;
|
|
379
380
|
var MAX_BRANCH_BYTES = 1024;
|
|
380
381
|
function utf8Bytes2(value) {
|
|
@@ -402,24 +403,39 @@ function readJsonlContextRecords(sourcePath) {
|
|
|
402
403
|
const stat = fs5.fstatSync(file);
|
|
403
404
|
const byteLimit = Math.min(stat.size, MAX_ARTIFACT_CONTEXT_PREFIX_BYTES);
|
|
404
405
|
if (byteLimit <= 0) return [];
|
|
405
|
-
const buffer = Buffer.allocUnsafe(byteLimit);
|
|
406
|
-
const bytesRead = fs5.readSync(file, buffer, 0, byteLimit, 0);
|
|
407
|
-
let content = buffer.subarray(0, bytesRead).toString("utf8");
|
|
408
|
-
if (bytesRead < stat.size) {
|
|
409
|
-
const lastNewline = content.lastIndexOf("\n");
|
|
410
|
-
if (lastNewline < 0) return [];
|
|
411
|
-
content = content.slice(0, lastNewline + 1);
|
|
412
|
-
}
|
|
413
406
|
const records = [];
|
|
414
|
-
|
|
415
|
-
if (records.length >= MAX_ARTIFACT_CONTEXT_RECORDS) break;
|
|
407
|
+
const parseLine = (line) => {
|
|
416
408
|
const jsonLine = line.startsWith("\uFEFF") ? line.slice(1) : line;
|
|
417
|
-
if (!jsonLine.trim())
|
|
409
|
+
if (!jsonLine.trim()) return;
|
|
418
410
|
try {
|
|
419
411
|
const record = recordValue(JSON.parse(jsonLine));
|
|
420
412
|
if (record) records.push(record);
|
|
421
413
|
} catch {
|
|
422
414
|
}
|
|
415
|
+
};
|
|
416
|
+
let carry = Buffer.alloc(0);
|
|
417
|
+
let position = 0;
|
|
418
|
+
const chunk = Buffer.allocUnsafe(ARTIFACT_CONTEXT_READ_CHUNK_BYTES);
|
|
419
|
+
while (position < byteLimit && records.length < MAX_ARTIFACT_CONTEXT_RECORDS) {
|
|
420
|
+
const want = Math.min(chunk.length, byteLimit - position);
|
|
421
|
+
const bytesRead = fs5.readSync(file, chunk, 0, want, position);
|
|
422
|
+
if (bytesRead <= 0) break;
|
|
423
|
+
position += bytesRead;
|
|
424
|
+
const buffered = Buffer.concat([carry, chunk.subarray(0, bytesRead)]);
|
|
425
|
+
const lastNewline = buffered.lastIndexOf(10);
|
|
426
|
+
if (lastNewline < 0) {
|
|
427
|
+
carry = buffered;
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const complete = buffered.subarray(0, lastNewline + 1).toString("utf8");
|
|
431
|
+
carry = Buffer.from(buffered.subarray(lastNewline + 1));
|
|
432
|
+
for (const line of complete.split(/\r?\n/)) {
|
|
433
|
+
if (records.length >= MAX_ARTIFACT_CONTEXT_RECORDS) break;
|
|
434
|
+
parseLine(line);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (carry.length > 0 && position >= stat.size && records.length < MAX_ARTIFACT_CONTEXT_RECORDS) {
|
|
438
|
+
parseLine(carry.toString("utf8"));
|
|
423
439
|
}
|
|
424
440
|
return records;
|
|
425
441
|
} catch {
|
|
@@ -922,41 +938,78 @@ function pruneMaterializedCache(input) {
|
|
|
922
938
|
|
|
923
939
|
// ../../packages/panopticon/src/repository-context.ts
|
|
924
940
|
import { execFileSync } from "child_process";
|
|
941
|
+
import { createHash as createHash4 } from "crypto";
|
|
942
|
+
import fs7 from "fs";
|
|
943
|
+
import path17 from "path";
|
|
925
944
|
var MAX_CWD_BYTES2 = 4096;
|
|
926
945
|
var GIT_OUTPUT_BYTES = 16 * 1024;
|
|
946
|
+
var GIT_CONFIG_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
927
947
|
var GIT_TIMEOUT_MS = 1e3;
|
|
928
948
|
var DEFAULT_GITHUB_ORIGIN_CACHE_MAX_ENTRIES = 512;
|
|
949
|
+
var ReadOnlyGitCommandError = class extends Error {
|
|
950
|
+
constructor() {
|
|
951
|
+
super("read-only git command failed");
|
|
952
|
+
}
|
|
953
|
+
};
|
|
929
954
|
function validCwd(cwd) {
|
|
930
955
|
const trimmed = cwd.trim();
|
|
931
956
|
return trimmed === cwd && trimmed.length > 0 && !new RegExp("\\p{Cc}", "u").test(trimmed) && Buffer.byteLength(trimmed, "utf8") <= MAX_CWD_BYTES2;
|
|
932
957
|
}
|
|
933
|
-
|
|
958
|
+
function runReadOnlyGit(cwd, args, maxBuffer = GIT_OUTPUT_BYTES) {
|
|
934
959
|
if (!validCwd(cwd)) return null;
|
|
935
960
|
try {
|
|
936
961
|
return execFileSync("git", ["-C", cwd, ...args], {
|
|
937
962
|
encoding: "utf8",
|
|
938
|
-
maxBuffer
|
|
963
|
+
maxBuffer,
|
|
939
964
|
timeout: GIT_TIMEOUT_MS,
|
|
940
965
|
stdio: ["ignore", "pipe", "ignore"],
|
|
941
966
|
windowsHide: true
|
|
942
967
|
}).trim() || null;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
const status = error && typeof error === "object" && "status" in error ? error.status : void 0;
|
|
970
|
+
if (status === 2 && args.length === 3 && args[0] === "remote" && args[1] === "get-url" && args[2] === "origin") {
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
throw new ReadOnlyGitCommandError();
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
function resolveGitHubOriginResult(cwd, runGit = runReadOnlyGit) {
|
|
977
|
+
let remote;
|
|
978
|
+
try {
|
|
979
|
+
remote = runGit(cwd, ["remote", "get-url", "origin"]);
|
|
943
980
|
} catch {
|
|
944
|
-
return null;
|
|
981
|
+
return { origin: null, definitive: false };
|
|
945
982
|
}
|
|
946
|
-
};
|
|
947
|
-
function resolveGitHubOrigin(cwd, runGit = runReadOnlyGit) {
|
|
948
|
-
const remote = runGit(cwd, ["remote", "get-url", "origin"]);
|
|
949
983
|
const repository = remote ? canonicalGitHubRepository(remote) : null;
|
|
950
|
-
if (!repository) return null;
|
|
951
|
-
|
|
984
|
+
if (!repository) return { origin: null, definitive: true };
|
|
985
|
+
let branchResult;
|
|
986
|
+
try {
|
|
987
|
+
branchResult = runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
988
|
+
} catch {
|
|
989
|
+
return { origin: { repository }, definitive: false };
|
|
990
|
+
}
|
|
952
991
|
const branch = branchResult === "HEAD" ? null : branchResult;
|
|
953
|
-
|
|
992
|
+
let commitHash;
|
|
993
|
+
try {
|
|
994
|
+
commitHash = runGit(cwd, ["rev-parse", "HEAD"]);
|
|
995
|
+
} catch {
|
|
996
|
+
return {
|
|
997
|
+
origin: { repository, ...branch ? { branch } : {} },
|
|
998
|
+
definitive: false
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
954
1001
|
return {
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1002
|
+
origin: {
|
|
1003
|
+
repository,
|
|
1004
|
+
...branch ? { branch } : {},
|
|
1005
|
+
...commitHash ? { commitHash } : {}
|
|
1006
|
+
},
|
|
1007
|
+
definitive: true
|
|
958
1008
|
};
|
|
959
1009
|
}
|
|
1010
|
+
function resolveGitHubOrigin(cwd, runGit = runReadOnlyGit) {
|
|
1011
|
+
return resolveGitHubOriginResult(cwd, runGit).origin;
|
|
1012
|
+
}
|
|
960
1013
|
function createMeasuredGitHubOriginResolver(resolve = (cwd) => resolveGitHubOrigin(cwd), nowMs = () => performance.now(), options = {}) {
|
|
961
1014
|
const cache = /* @__PURE__ */ new Map();
|
|
962
1015
|
const maxCacheEntries = options.maxCacheEntries ?? DEFAULT_GITHUB_ORIGIN_CACHE_MAX_ENTRIES;
|
|
@@ -1023,11 +1076,172 @@ function createMeasuredGitHubOriginResolver(resolve = (cwd) => resolveGitHubOrig
|
|
|
1023
1076
|
})
|
|
1024
1077
|
};
|
|
1025
1078
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1079
|
+
var PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION = 2;
|
|
1080
|
+
var DEFAULT_PERSISTENT_ORIGIN_CACHE_MAX_ENTRIES = 512;
|
|
1081
|
+
function fileContentIdentity(filePath) {
|
|
1082
|
+
try {
|
|
1083
|
+
const contents = fs7.readFileSync(filePath);
|
|
1084
|
+
return `sha256:${createHash4("sha256").update(contents).digest("hex")}`;
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
1087
|
+
return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
function packedRefsIdentity(filePath) {
|
|
1091
|
+
try {
|
|
1092
|
+
const stat = fs7.statSync(filePath, { bigint: true });
|
|
1093
|
+
if (!stat.isFile() || stat.ino === 0n) return null;
|
|
1094
|
+
return [stat.dev, stat.ino, stat.size, stat.mtimeNs, stat.ctimeNs].join(
|
|
1095
|
+
":"
|
|
1096
|
+
);
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
const code = error && typeof error === "object" && "code" in error ? error.code : void 0;
|
|
1099
|
+
return code === "ENOENT" || code === "ENOTDIR" ? "missing" : null;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
function effectiveConfigIdentity(cwd) {
|
|
1103
|
+
if (["GIT_CONFIG", "GIT_DIR", "GIT_COMMON_DIR", "GIT_WORK_TREE"].some(
|
|
1104
|
+
(name) => process.env[name] !== void 0
|
|
1105
|
+
)) {
|
|
1106
|
+
return null;
|
|
1107
|
+
}
|
|
1108
|
+
try {
|
|
1109
|
+
const config = runReadOnlyGit(
|
|
1110
|
+
cwd,
|
|
1111
|
+
["config", "--includes", "--null", "--list"],
|
|
1112
|
+
GIT_CONFIG_OUTPUT_BYTES
|
|
1113
|
+
);
|
|
1114
|
+
return createHash4("sha256").update(config ?? "").digest("hex");
|
|
1115
|
+
} catch {
|
|
1116
|
+
return null;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
function readSmallText(filePath) {
|
|
1120
|
+
try {
|
|
1121
|
+
return fs7.readFileSync(filePath, "utf8").trim();
|
|
1122
|
+
} catch {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function findGitDirectory(cwd) {
|
|
1127
|
+
let current = path17.resolve(cwd);
|
|
1128
|
+
for (; ; ) {
|
|
1129
|
+
const candidate = path17.join(current, ".git");
|
|
1130
|
+
let stat;
|
|
1131
|
+
try {
|
|
1132
|
+
stat = fs7.statSync(candidate);
|
|
1133
|
+
} catch {
|
|
1134
|
+
stat = void 0;
|
|
1135
|
+
}
|
|
1136
|
+
if (stat?.isDirectory()) {
|
|
1137
|
+
return { gitDir: candidate, commonDir: candidate };
|
|
1138
|
+
}
|
|
1139
|
+
if (stat?.isFile()) {
|
|
1140
|
+
const pointer = readSmallText(candidate);
|
|
1141
|
+
const match = pointer ? /^gitdir:\s*(.+)$/u.exec(pointer) : null;
|
|
1142
|
+
if (!match?.[1]) return null;
|
|
1143
|
+
const gitDir = path17.resolve(current, match[1]);
|
|
1144
|
+
const common = readSmallText(path17.join(gitDir, "commondir"));
|
|
1145
|
+
return {
|
|
1146
|
+
gitDir,
|
|
1147
|
+
commonDir: common ? path17.resolve(gitDir, common) : gitDir
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
const parent = path17.dirname(current);
|
|
1151
|
+
if (parent === current) return null;
|
|
1152
|
+
current = parent;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
function checkoutSignature(layout, cwd) {
|
|
1156
|
+
const headPath = path17.join(layout.gitDir, "HEAD");
|
|
1157
|
+
const head = readSmallText(headPath);
|
|
1158
|
+
const configIdentity = effectiveConfigIdentity(cwd);
|
|
1159
|
+
if (head === null || configIdentity === null) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
const parts = [layout.gitDir, head, configIdentity];
|
|
1163
|
+
const ref = /^ref:\s*(.+)$/u.exec(head)?.[1];
|
|
1164
|
+
if (ref) {
|
|
1165
|
+
const looseRefPath = path17.join(layout.commonDir, ref);
|
|
1166
|
+
if (/^ref:/u.test(readSmallText(looseRefPath) ?? "")) return null;
|
|
1167
|
+
const looseRefIdentity = fileContentIdentity(looseRefPath);
|
|
1168
|
+
const packedIdentity = packedRefsIdentity(
|
|
1169
|
+
path17.join(layout.commonDir, "packed-refs")
|
|
1170
|
+
);
|
|
1171
|
+
const reftableIdentity = fileContentIdentity(
|
|
1172
|
+
path17.join(layout.commonDir, "reftable", "tables.list")
|
|
1173
|
+
);
|
|
1174
|
+
if (!looseRefIdentity || !packedIdentity || !reftableIdentity || looseRefIdentity === "missing" && packedIdentity === "missing" && reftableIdentity === "missing") {
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1177
|
+
parts.push(looseRefIdentity, packedIdentity, reftableIdentity);
|
|
1178
|
+
}
|
|
1179
|
+
return parts.join("\0");
|
|
1180
|
+
}
|
|
1181
|
+
function readPersistentOriginCache(cacheFile) {
|
|
1182
|
+
try {
|
|
1183
|
+
const parsed = JSON.parse(fs7.readFileSync(cacheFile, "utf8"));
|
|
1184
|
+
if (parsed && typeof parsed === "object" && parsed.schemaVersion === PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION && parsed.entries && typeof parsed.entries === "object") {
|
|
1185
|
+
return parsed;
|
|
1186
|
+
}
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
return { schemaVersion: PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION, entries: {} };
|
|
1190
|
+
}
|
|
1191
|
+
function writePersistentOriginCache(cacheFile, cache) {
|
|
1192
|
+
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
1193
|
+
try {
|
|
1194
|
+
fs7.mkdirSync(path17.dirname(cacheFile), { recursive: true, mode: 448 });
|
|
1195
|
+
fs7.writeFileSync(temporary, `${JSON.stringify(cache)}
|
|
1196
|
+
`, { mode: 384 });
|
|
1197
|
+
fs7.renameSync(temporary, cacheFile);
|
|
1198
|
+
} catch {
|
|
1199
|
+
try {
|
|
1200
|
+
fs7.rmSync(temporary, { force: true });
|
|
1201
|
+
} catch {
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function createPersistentGitHubOriginResolver(options) {
|
|
1206
|
+
const maxEntries = options.maxEntries ?? DEFAULT_PERSISTENT_ORIGIN_CACHE_MAX_ENTRIES;
|
|
1207
|
+
const cache = readPersistentOriginCache(options.cacheFile);
|
|
1208
|
+
const entries = new Map(Object.entries(cache.entries));
|
|
1209
|
+
const remember = (cwd, entry) => {
|
|
1210
|
+
entries.delete(cwd);
|
|
1211
|
+
entries.set(cwd, entry);
|
|
1212
|
+
while (entries.size > maxEntries) {
|
|
1213
|
+
const oldest = entries.keys().next().value;
|
|
1214
|
+
if (oldest === void 0) break;
|
|
1215
|
+
entries.delete(oldest);
|
|
1216
|
+
}
|
|
1217
|
+
writePersistentOriginCache(options.cacheFile, {
|
|
1218
|
+
schemaVersion: PERSISTENT_ORIGIN_CACHE_SCHEMA_VERSION,
|
|
1219
|
+
entries: Object.fromEntries(entries)
|
|
1220
|
+
});
|
|
1221
|
+
};
|
|
1222
|
+
return (cwd) => {
|
|
1223
|
+
if (!validCwd(cwd)) return null;
|
|
1224
|
+
const layout = findGitDirectory(cwd);
|
|
1225
|
+
if (!layout) return null;
|
|
1226
|
+
const signature = checkoutSignature(layout, cwd);
|
|
1227
|
+
const cached = entries.get(cwd);
|
|
1228
|
+
if (signature !== null && cached && cached.signature === signature) {
|
|
1229
|
+
return cached.origin;
|
|
1230
|
+
}
|
|
1231
|
+
try {
|
|
1232
|
+
const result = options.resolve ? { origin: options.resolve(cwd), definitive: true } : resolveGitHubOriginResult(cwd);
|
|
1233
|
+
if (signature !== null && result.definitive) {
|
|
1234
|
+
remember(cwd, { signature, origin: result.origin });
|
|
1235
|
+
}
|
|
1236
|
+
return result.origin;
|
|
1237
|
+
} catch {
|
|
1238
|
+
return null;
|
|
1239
|
+
}
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
function createArtifactDiscoveryGitHubOriginResolver(options = {}) {
|
|
1243
|
+
const resolve = options.cacheFile ? createPersistentGitHubOriginResolver({ cacheFile: options.cacheFile }) : (cwd) => resolveGitHubOrigin(cwd);
|
|
1244
|
+
return createMeasuredGitHubOriginResolver(resolve, () => performance.now());
|
|
1031
1245
|
}
|
|
1032
1246
|
|
|
1033
1247
|
// ../../packages/panopticon/src/artifacts.ts
|
|
@@ -1071,20 +1285,20 @@ function isAbortError(error) {
|
|
|
1071
1285
|
}
|
|
1072
1286
|
function safeStat(filePath) {
|
|
1073
1287
|
try {
|
|
1074
|
-
return
|
|
1288
|
+
return fs8.statSync(filePath);
|
|
1075
1289
|
} catch {
|
|
1076
1290
|
return null;
|
|
1077
1291
|
}
|
|
1078
1292
|
}
|
|
1079
1293
|
function normalizeFilePath2(filePath) {
|
|
1080
|
-
return
|
|
1294
|
+
return path18.resolve(filePath);
|
|
1081
1295
|
}
|
|
1082
1296
|
function artifactReadPath(artifact) {
|
|
1083
1297
|
return normalizeFilePath2(artifact.readPath ?? artifact.sourcePath);
|
|
1084
1298
|
}
|
|
1085
1299
|
function statCandidateSource(sourcePath, transientStatErrors) {
|
|
1086
1300
|
try {
|
|
1087
|
-
return
|
|
1301
|
+
return fs8.statSync(sourcePath);
|
|
1088
1302
|
} catch (error) {
|
|
1089
1303
|
if (error.code === "ENOENT") return null;
|
|
1090
1304
|
if (transientStatErrors === "throw") throw error;
|
|
@@ -1092,7 +1306,7 @@ function statCandidateSource(sourcePath, transientStatErrors) {
|
|
|
1092
1306
|
}
|
|
1093
1307
|
}
|
|
1094
1308
|
function contentTypeForPath(filePath) {
|
|
1095
|
-
const ext =
|
|
1309
|
+
const ext = path18.extname(filePath).toLowerCase();
|
|
1096
1310
|
if (ext === ".json" || ext === ".jsonl") return "application/json";
|
|
1097
1311
|
if (ext === ".md") return "text/markdown";
|
|
1098
1312
|
if (ext === ".toml") return "application/toml";
|
|
@@ -1122,7 +1336,9 @@ function buildArtifactChunkId(args) {
|
|
|
1122
1336
|
)}`;
|
|
1123
1337
|
}
|
|
1124
1338
|
function discoverArtifacts(options = {}) {
|
|
1125
|
-
const repositoryResolution = createArtifactDiscoveryGitHubOriginResolver(
|
|
1339
|
+
const repositoryResolution = createArtifactDiscoveryGitHubOriginResolver(
|
|
1340
|
+
options.githubOriginCacheFile ? { cacheFile: options.githubOriginCacheFile } : {}
|
|
1341
|
+
);
|
|
1126
1342
|
const env = options.env ?? process.env;
|
|
1127
1343
|
const homeDirInput = options.homeDir ?? env.HOME ?? os3.homedir();
|
|
1128
1344
|
if (!homeDirInput) {
|
|
@@ -1202,7 +1418,7 @@ function readUploadThrottles(value) {
|
|
|
1202
1418
|
}
|
|
1203
1419
|
function readArtifactQueue(queueFile) {
|
|
1204
1420
|
try {
|
|
1205
|
-
const parsed = JSON.parse(
|
|
1421
|
+
const parsed = JSON.parse(fs8.readFileSync(queueFile, "utf8"));
|
|
1206
1422
|
if (!parsed || typeof parsed !== "object") return emptyQueue();
|
|
1207
1423
|
const record = parsed;
|
|
1208
1424
|
if (record.schemaVersion !== RAW_ARTIFACT_SCHEMA_VERSION) {
|
|
@@ -1219,15 +1435,15 @@ function readArtifactQueue(queueFile) {
|
|
|
1219
1435
|
}
|
|
1220
1436
|
}
|
|
1221
1437
|
function writeArtifactQueue(queueFile, queue) {
|
|
1222
|
-
|
|
1438
|
+
fs8.mkdirSync(path18.dirname(queueFile), { recursive: true, mode: 448 });
|
|
1223
1439
|
const tempFile = `${queueFile}.${process.pid}.${Date.now()}.tmp`;
|
|
1224
|
-
|
|
1440
|
+
fs8.writeFileSync(tempFile, `${JSON.stringify(queue, null, 2)}
|
|
1225
1441
|
`, {
|
|
1226
1442
|
mode: 384
|
|
1227
1443
|
});
|
|
1228
|
-
|
|
1444
|
+
fs8.renameSync(tempFile, queueFile);
|
|
1229
1445
|
try {
|
|
1230
|
-
|
|
1446
|
+
fs8.chmodSync(queueFile, 384);
|
|
1231
1447
|
} catch {
|
|
1232
1448
|
}
|
|
1233
1449
|
}
|
|
@@ -1248,7 +1464,7 @@ function shouldStartNewGeneration(input) {
|
|
|
1248
1464
|
if (input.nextSizeBytes < input.existing.sizeBytes) return true;
|
|
1249
1465
|
if ((input.nextReadPath !== artifactReadPath(input.existing) || input.existing.readPath !== void 0) && input.nextSizeBytes === input.existing.sizeBytes && input.existing.uploadedSizeBytes >= input.nextSizeBytes && input.existing.contentSha256) {
|
|
1250
1466
|
try {
|
|
1251
|
-
const contentSha256 =
|
|
1467
|
+
const contentSha256 = createHash5("sha256").update(fs8.readFileSync(input.nextReadPath)).digest("hex");
|
|
1252
1468
|
if (contentSha256 === input.existing.contentSha256) return false;
|
|
1253
1469
|
} catch {
|
|
1254
1470
|
}
|
|
@@ -1582,7 +1798,7 @@ function sameSourceRevision(left, right) {
|
|
|
1582
1798
|
}
|
|
1583
1799
|
function safeSourceRevision(filePath) {
|
|
1584
1800
|
try {
|
|
1585
|
-
const stat =
|
|
1801
|
+
const stat = fs8.statSync(filePath, { bigint: true });
|
|
1586
1802
|
return stat.isFile() ? sourceRevisionFromStat(stat) : null;
|
|
1587
1803
|
} catch {
|
|
1588
1804
|
return null;
|
|
@@ -1594,7 +1810,7 @@ function readExactRange(fd, startByte, endByte) {
|
|
|
1594
1810
|
const buffer = Buffer.alloc(sizeBytes);
|
|
1595
1811
|
let offset = 0;
|
|
1596
1812
|
while (offset < sizeBytes) {
|
|
1597
|
-
const bytesRead =
|
|
1813
|
+
const bytesRead = fs8.readSync(
|
|
1598
1814
|
fd,
|
|
1599
1815
|
buffer,
|
|
1600
1816
|
offset,
|
|
@@ -1613,19 +1829,19 @@ function readFileRangeAtRevision(filePath, startByte, endByte, expectedRevision)
|
|
|
1613
1829
|
}
|
|
1614
1830
|
let fd;
|
|
1615
1831
|
try {
|
|
1616
|
-
fd =
|
|
1832
|
+
fd = fs8.openSync(filePath, "r");
|
|
1617
1833
|
} catch {
|
|
1618
1834
|
return null;
|
|
1619
1835
|
}
|
|
1620
1836
|
try {
|
|
1621
1837
|
const openedRevision = sourceRevisionFromStat(
|
|
1622
|
-
|
|
1838
|
+
fs8.fstatSync(fd, { bigint: true })
|
|
1623
1839
|
);
|
|
1624
1840
|
if (!sameSourceRevision(pathRevisionBefore, openedRevision)) return null;
|
|
1625
1841
|
const bytes = readExactRange(fd, startByte, endByte);
|
|
1626
1842
|
if (!bytes) return null;
|
|
1627
1843
|
const revisionAfterRead = sourceRevisionFromStat(
|
|
1628
|
-
|
|
1844
|
+
fs8.fstatSync(fd, { bigint: true })
|
|
1629
1845
|
);
|
|
1630
1846
|
const pathRevisionAfter = safeSourceRevision(filePath);
|
|
1631
1847
|
if (!sameSourceRevision(openedRevision, revisionAfterRead) || !pathRevisionAfter || !sameSourceRevision(openedRevision, pathRevisionAfter)) {
|
|
@@ -1635,7 +1851,7 @@ function readFileRangeAtRevision(filePath, startByte, endByte, expectedRevision)
|
|
|
1635
1851
|
} catch {
|
|
1636
1852
|
return null;
|
|
1637
1853
|
} finally {
|
|
1638
|
-
|
|
1854
|
+
fs8.closeSync(fd);
|
|
1639
1855
|
}
|
|
1640
1856
|
}
|
|
1641
1857
|
function uploadMetadata(input) {
|
|
@@ -1767,18 +1983,18 @@ function validateCheckpointPrefix(input) {
|
|
|
1767
1983
|
}
|
|
1768
1984
|
let fd;
|
|
1769
1985
|
try {
|
|
1770
|
-
fd =
|
|
1986
|
+
fd = fs8.openSync(input.sourcePath, "r");
|
|
1771
1987
|
} catch {
|
|
1772
1988
|
return { status: "changed" };
|
|
1773
1989
|
}
|
|
1774
1990
|
try {
|
|
1775
1991
|
const openedRevision = sourceRevisionFromStat(
|
|
1776
|
-
|
|
1992
|
+
fs8.fstatSync(fd, { bigint: true })
|
|
1777
1993
|
);
|
|
1778
1994
|
if (!sameSourceRevision(pathRevisionBefore, openedRevision)) {
|
|
1779
1995
|
return { status: "changed" };
|
|
1780
1996
|
}
|
|
1781
|
-
const contentHash =
|
|
1997
|
+
const contentHash = createHash5("sha256");
|
|
1782
1998
|
let checkpointMismatch = false;
|
|
1783
1999
|
for (const checkpoint of checkpoints) {
|
|
1784
2000
|
const chunk = readExactRange(
|
|
@@ -1793,7 +2009,7 @@ function validateCheckpointPrefix(input) {
|
|
|
1793
2009
|
contentHash.update(chunk);
|
|
1794
2010
|
}
|
|
1795
2011
|
const revisionAfterRead = sourceRevisionFromStat(
|
|
1796
|
-
|
|
2012
|
+
fs8.fstatSync(fd, { bigint: true })
|
|
1797
2013
|
);
|
|
1798
2014
|
const pathRevisionAfter = safeSourceRevision(input.sourcePath);
|
|
1799
2015
|
if (!sameSourceRevision(openedRevision, revisionAfterRead) || !pathRevisionAfter || !sameSourceRevision(openedRevision, pathRevisionAfter)) {
|
|
@@ -1808,7 +2024,7 @@ function validateCheckpointPrefix(input) {
|
|
|
1808
2024
|
} catch {
|
|
1809
2025
|
return { status: "changed" };
|
|
1810
2026
|
} finally {
|
|
1811
|
-
|
|
2027
|
+
fs8.closeSync(fd);
|
|
1812
2028
|
}
|
|
1813
2029
|
}
|
|
1814
2030
|
function validatedPrefixStillApplies(prefix, entry, uploadDestinationId, revision) {
|
|
@@ -1942,14 +2158,14 @@ function defaultIsPidAlive(pid) {
|
|
|
1942
2158
|
}
|
|
1943
2159
|
function readQueueLockHolder(lockFile) {
|
|
1944
2160
|
try {
|
|
1945
|
-
const parsed = JSON.parse(
|
|
2161
|
+
const parsed = JSON.parse(fs8.readFileSync(lockFile, "utf8"));
|
|
1946
2162
|
if (!parsed || typeof parsed !== "object") return null;
|
|
1947
2163
|
const record = parsed;
|
|
1948
2164
|
if (typeof record.pid !== "number" || typeof record.timestampMs !== "number") {
|
|
1949
2165
|
return null;
|
|
1950
2166
|
}
|
|
1951
2167
|
const ownerToken = typeof record.ownerToken === "string" ? record.ownerToken : void 0;
|
|
1952
|
-
const timestampMs = ownerToken ? Math.max(record.timestampMs,
|
|
2168
|
+
const timestampMs = ownerToken ? Math.max(record.timestampMs, fs8.statSync(lockFile).mtimeMs) : record.timestampMs;
|
|
1953
2169
|
return { pid: record.pid, timestampMs, ownerToken };
|
|
1954
2170
|
} catch {
|
|
1955
2171
|
return null;
|
|
@@ -1957,8 +2173,8 @@ function readQueueLockHolder(lockFile) {
|
|
|
1957
2173
|
}
|
|
1958
2174
|
function queueLockFileMatchesOwner(lockFile, fd, ownerToken) {
|
|
1959
2175
|
try {
|
|
1960
|
-
const openStat =
|
|
1961
|
-
const pathStat =
|
|
2176
|
+
const openStat = fs8.fstatSync(fd);
|
|
2177
|
+
const pathStat = fs8.statSync(lockFile);
|
|
1962
2178
|
if (openStat.dev !== pathStat.dev || openStat.ino !== pathStat.ino) {
|
|
1963
2179
|
return false;
|
|
1964
2180
|
}
|
|
@@ -1969,14 +2185,14 @@ function queueLockFileMatchesOwner(lockFile, fd, ownerToken) {
|
|
|
1969
2185
|
}
|
|
1970
2186
|
function acquireQueueLock(queueFile, deps) {
|
|
1971
2187
|
const lockFile = queueLockFile(queueFile);
|
|
1972
|
-
|
|
2188
|
+
fs8.mkdirSync(path18.dirname(lockFile), { recursive: true, mode: 448 });
|
|
1973
2189
|
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
1974
2190
|
try {
|
|
1975
|
-
const fd =
|
|
2191
|
+
const fd = fs8.openSync(lockFile, "wx+", 384);
|
|
1976
2192
|
const ownerToken = randomUUID3();
|
|
1977
2193
|
let released = false;
|
|
1978
2194
|
try {
|
|
1979
|
-
|
|
2195
|
+
fs8.writeSync(
|
|
1980
2196
|
fd,
|
|
1981
2197
|
`${JSON.stringify({
|
|
1982
2198
|
pid: process.pid,
|
|
@@ -1986,11 +2202,11 @@ function acquireQueueLock(queueFile, deps) {
|
|
|
1986
2202
|
`
|
|
1987
2203
|
);
|
|
1988
2204
|
const heartbeatAt = new Date(deps.nowMs());
|
|
1989
|
-
|
|
2205
|
+
fs8.futimesSync(fd, heartbeatAt, heartbeatAt);
|
|
1990
2206
|
} catch (error) {
|
|
1991
|
-
|
|
2207
|
+
fs8.closeSync(fd);
|
|
1992
2208
|
try {
|
|
1993
|
-
|
|
2209
|
+
fs8.unlinkSync(lockFile);
|
|
1994
2210
|
} catch {
|
|
1995
2211
|
}
|
|
1996
2212
|
throw error;
|
|
@@ -1998,7 +2214,7 @@ function acquireQueueLock(queueFile, deps) {
|
|
|
1998
2214
|
const close = () => {
|
|
1999
2215
|
if (released) return;
|
|
2000
2216
|
released = true;
|
|
2001
|
-
|
|
2217
|
+
fs8.closeSync(fd);
|
|
2002
2218
|
};
|
|
2003
2219
|
return {
|
|
2004
2220
|
heartbeat() {
|
|
@@ -2007,7 +2223,7 @@ function acquireQueueLock(queueFile, deps) {
|
|
|
2007
2223
|
return false;
|
|
2008
2224
|
try {
|
|
2009
2225
|
const heartbeatAt = new Date(deps.nowMs());
|
|
2010
|
-
|
|
2226
|
+
fs8.futimesSync(fd, heartbeatAt, heartbeatAt);
|
|
2011
2227
|
return true;
|
|
2012
2228
|
} catch {
|
|
2013
2229
|
return false;
|
|
@@ -2017,7 +2233,7 @@ function acquireQueueLock(queueFile, deps) {
|
|
|
2017
2233
|
if (released) return;
|
|
2018
2234
|
if (queueLockFileMatchesOwner(lockFile, fd, ownerToken)) {
|
|
2019
2235
|
try {
|
|
2020
|
-
|
|
2236
|
+
fs8.unlinkSync(lockFile);
|
|
2021
2237
|
} catch {
|
|
2022
2238
|
}
|
|
2023
2239
|
}
|
|
@@ -2030,7 +2246,7 @@ function acquireQueueLock(queueFile, deps) {
|
|
|
2030
2246
|
const stale = !holder || !deps.isPidAlive(holder.pid) || deps.nowMs() - holder.timestampMs > QUEUE_LOCK_STALE_MS;
|
|
2031
2247
|
if (!stale) return null;
|
|
2032
2248
|
try {
|
|
2033
|
-
|
|
2249
|
+
fs8.unlinkSync(lockFile);
|
|
2034
2250
|
} catch {
|
|
2035
2251
|
}
|
|
2036
2252
|
}
|
|
@@ -2088,7 +2304,7 @@ async function postRawArtifact(url, metadata, content, headers, fetchImpl = fetc
|
|
|
2088
2304
|
form.append(
|
|
2089
2305
|
"file",
|
|
2090
2306
|
new Blob([new Uint8Array(content)], { type: metadata.contentType }),
|
|
2091
|
-
|
|
2307
|
+
path18.basename(metadata.sourcePath) || "raw-artifact"
|
|
2092
2308
|
);
|
|
2093
2309
|
const abort = new AbortController();
|
|
2094
2310
|
const timeout = setTimeout(() => abort.abort(), timeoutMs);
|
|
@@ -2977,10 +3193,11 @@ async function uploadPendingArtifactsLocked(options, run) {
|
|
|
2977
3193
|
}
|
|
2978
3194
|
|
|
2979
3195
|
// ../../packages/panopticon/src/raw-sync.ts
|
|
2980
|
-
import
|
|
3196
|
+
import fs10 from "fs";
|
|
3197
|
+
import path19 from "path";
|
|
2981
3198
|
|
|
2982
3199
|
// ../../packages/panopticon/src/artifact-materializer.ts
|
|
2983
|
-
import
|
|
3200
|
+
import fs9 from "fs";
|
|
2984
3201
|
function entryNeedsBytes(entry, amendmentPending) {
|
|
2985
3202
|
if (entry.uploadedSizeBytes < entry.sizeBytes) return true;
|
|
2986
3203
|
return amendmentPending(entry);
|
|
@@ -2995,7 +3212,7 @@ function createArtifactMaterializer(input) {
|
|
|
2995
3212
|
const unsupportedReason = (input.supportsCompression ?? supportsCompressedArtifacts)() ? void 0 : `compressed artifacts need Node 22.15 or newer (running ${process.version})`;
|
|
2996
3213
|
const chargeExisting = (cachePath) => {
|
|
2997
3214
|
try {
|
|
2998
|
-
const stat =
|
|
3215
|
+
const stat = fs9.statSync(cachePath);
|
|
2999
3216
|
if (!stat.isFile()) return false;
|
|
3000
3217
|
if (!retained.has(cachePath)) {
|
|
3001
3218
|
retained.add(cachePath);
|
|
@@ -3006,6 +3223,11 @@ function createArtifactMaterializer(input) {
|
|
|
3006
3223
|
return false;
|
|
3007
3224
|
}
|
|
3008
3225
|
};
|
|
3226
|
+
for (const entry of input.entries) {
|
|
3227
|
+
if (entry.readPath && entryNeedsBytes(entry, input.amendmentPending)) {
|
|
3228
|
+
chargeExisting(entry.readPath);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3009
3231
|
return {
|
|
3010
3232
|
unsupportedReason,
|
|
3011
3233
|
materialize: (sourcePath) => {
|
|
@@ -3033,19 +3255,30 @@ function createArtifactMaterializer(input) {
|
|
|
3033
3255
|
var DEFAULT_RAW_SYNC_MAX_DRAIN_PASSES = 10;
|
|
3034
3256
|
var DEFAULT_RAW_SYNC_MAX_DRAIN_BYTES = 100 * 1024 * 1024;
|
|
3035
3257
|
var DEFAULT_RAW_SYNC_MAX_DRAIN_MS = 6e4;
|
|
3258
|
+
var DISCOVERY_CACHE_SCHEMA_VERSION = 1;
|
|
3036
3259
|
function getRawArtifactSyncPaths(stateDir) {
|
|
3037
3260
|
if (stateDir.length === 0) {
|
|
3038
3261
|
throw new Error("raw artifact sync state directory must not be empty");
|
|
3039
3262
|
}
|
|
3040
|
-
const root =
|
|
3263
|
+
const root = path19.resolve(stateDir);
|
|
3041
3264
|
const managed = getManagedClientPaths({ PANOPTICON_HOME: root });
|
|
3042
3265
|
return {
|
|
3043
3266
|
root,
|
|
3044
3267
|
settingsFile: managed.syncSettingsFile,
|
|
3045
3268
|
queueFile: managed.artifactQueue,
|
|
3046
|
-
artifactCacheDir: managed.artifactCache
|
|
3269
|
+
artifactCacheDir: managed.artifactCache,
|
|
3270
|
+
discoveryCacheFile: path19.join(root, "discovery.cache.json"),
|
|
3271
|
+
githubOriginCacheFile: path19.join(root, "github-origins.cache.json")
|
|
3047
3272
|
};
|
|
3048
3273
|
}
|
|
3274
|
+
function invalidateRawArtifactDiscoveryCache(stateDir) {
|
|
3275
|
+
try {
|
|
3276
|
+
fs10.rmSync(getRawArtifactSyncPaths(stateDir).discoveryCacheFile, {
|
|
3277
|
+
force: true
|
|
3278
|
+
});
|
|
3279
|
+
} catch {
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3049
3282
|
function summarizeArtifactQueue(queue) {
|
|
3050
3283
|
let pending = 0;
|
|
3051
3284
|
let uploaded = 0;
|
|
@@ -3115,7 +3348,7 @@ function createRawArtifactSync(options) {
|
|
|
3115
3348
|
settings: getSettings(),
|
|
3116
3349
|
queue: summarizeArtifactQueue(readArtifactQueue(paths.queueFile))
|
|
3117
3350
|
});
|
|
3118
|
-
const
|
|
3351
|
+
const createMaterializer = () => {
|
|
3119
3352
|
const materializer = createArtifactMaterializer({
|
|
3120
3353
|
cacheDir: paths.artifactCacheDir,
|
|
3121
3354
|
entries: readArtifactQueue(paths.queueFile).artifacts,
|
|
@@ -3123,17 +3356,81 @@ function createRawArtifactSync(options) {
|
|
|
3123
3356
|
maxTotalBytes: options.maxArtifactCacheBytes
|
|
3124
3357
|
});
|
|
3125
3358
|
lastMaterializer = materializer;
|
|
3359
|
+
return materializer;
|
|
3360
|
+
};
|
|
3361
|
+
const discover = () => {
|
|
3362
|
+
const materializer = createMaterializer();
|
|
3126
3363
|
const discovery = discoverArtifacts({
|
|
3127
3364
|
homeDir: options.homeDir,
|
|
3128
3365
|
projectRoots: options.projectRoots,
|
|
3129
3366
|
maxFileBytes: options.maxFileBytes,
|
|
3130
|
-
materializeArtifact: materializer.materialize
|
|
3367
|
+
materializeArtifact: materializer.materialize,
|
|
3368
|
+
githubOriginCacheFile: paths.githubOriginCacheFile
|
|
3131
3369
|
});
|
|
3132
3370
|
return materializer.unsupportedReason ? {
|
|
3133
3371
|
...discovery,
|
|
3134
3372
|
compressedArtifactsUnsupportedReason: materializer.unsupportedReason
|
|
3135
3373
|
} : discovery;
|
|
3136
3374
|
};
|
|
3375
|
+
const discoveryInputs = () => JSON.stringify([
|
|
3376
|
+
options.homeDir ?? null,
|
|
3377
|
+
options.projectRoots ?? null,
|
|
3378
|
+
options.maxFileBytes ?? null
|
|
3379
|
+
]);
|
|
3380
|
+
const readReusableDiscovery = (nowMs) => {
|
|
3381
|
+
const reuseMs = options.discoveryReuseMs ?? 0;
|
|
3382
|
+
if (reuseMs <= 0) return null;
|
|
3383
|
+
try {
|
|
3384
|
+
const parsed = JSON.parse(
|
|
3385
|
+
fs10.readFileSync(paths.discoveryCacheFile, "utf8")
|
|
3386
|
+
);
|
|
3387
|
+
if (parsed.schemaVersion !== DISCOVERY_CACHE_SCHEMA_VERSION || typeof parsed.createdAtMs !== "number" || parsed.inputs !== discoveryInputs() || !parsed.discovery || typeof parsed.discovery !== "object") {
|
|
3388
|
+
return null;
|
|
3389
|
+
}
|
|
3390
|
+
const ageMs = nowMs - parsed.createdAtMs;
|
|
3391
|
+
if (ageMs < 0 || ageMs >= reuseMs) return null;
|
|
3392
|
+
return parsed.discovery;
|
|
3393
|
+
} catch {
|
|
3394
|
+
return null;
|
|
3395
|
+
}
|
|
3396
|
+
};
|
|
3397
|
+
const rememberDiscovery = (discovery, nowMs) => {
|
|
3398
|
+
if ((options.discoveryReuseMs ?? 0) <= 0) return;
|
|
3399
|
+
const temporary = `${paths.discoveryCacheFile}.${process.pid}.tmp`;
|
|
3400
|
+
try {
|
|
3401
|
+
fs10.mkdirSync(path19.dirname(paths.discoveryCacheFile), {
|
|
3402
|
+
recursive: true,
|
|
3403
|
+
mode: 448
|
|
3404
|
+
});
|
|
3405
|
+
fs10.writeFileSync(
|
|
3406
|
+
temporary,
|
|
3407
|
+
`${JSON.stringify({
|
|
3408
|
+
schemaVersion: DISCOVERY_CACHE_SCHEMA_VERSION,
|
|
3409
|
+
createdAtMs: nowMs,
|
|
3410
|
+
inputs: discoveryInputs(),
|
|
3411
|
+
discovery
|
|
3412
|
+
})}
|
|
3413
|
+
`,
|
|
3414
|
+
{ mode: 384 }
|
|
3415
|
+
);
|
|
3416
|
+
fs10.renameSync(temporary, paths.discoveryCacheFile);
|
|
3417
|
+
} catch {
|
|
3418
|
+
try {
|
|
3419
|
+
fs10.rmSync(temporary, { force: true });
|
|
3420
|
+
} catch {
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
};
|
|
3424
|
+
const discoverForRun = (nowMs) => {
|
|
3425
|
+
const reusable = readReusableDiscovery(nowMs);
|
|
3426
|
+
if (reusable) {
|
|
3427
|
+
createMaterializer();
|
|
3428
|
+
return reusable;
|
|
3429
|
+
}
|
|
3430
|
+
const discovery = discover();
|
|
3431
|
+
rememberDiscovery(discovery, nowMs);
|
|
3432
|
+
return discovery;
|
|
3433
|
+
};
|
|
3137
3434
|
return {
|
|
3138
3435
|
paths,
|
|
3139
3436
|
getSettings,
|
|
@@ -3166,7 +3463,7 @@ function createRawArtifactSync(options) {
|
|
|
3166
3463
|
status: status()
|
|
3167
3464
|
};
|
|
3168
3465
|
}
|
|
3169
|
-
const discovery =
|
|
3466
|
+
const discovery = discoverForRun((options.nowMs ?? Date.now)());
|
|
3170
3467
|
const syncOptions = {
|
|
3171
3468
|
queueFile: paths.queueFile,
|
|
3172
3469
|
candidates: discovery.candidates,
|
|
@@ -3261,10 +3558,17 @@ function createRawArtifactSync(options) {
|
|
|
3261
3558
|
}
|
|
3262
3559
|
|
|
3263
3560
|
// src/sync/runner.ts
|
|
3561
|
+
var OBSERVER_DISCOVERY_REUSE_MS = 6e4;
|
|
3562
|
+
function invalidateCliRawSyncDiscoveryCache() {
|
|
3563
|
+
invalidateRawArtifactDiscoveryCache(FML_OBSERVER_DIR);
|
|
3564
|
+
}
|
|
3264
3565
|
function createCliRawSync(opts = {}) {
|
|
3265
3566
|
return createRawArtifactSync({
|
|
3266
3567
|
stateDir: FML_OBSERVER_DIR,
|
|
3267
3568
|
retryExhausted: opts.continuous ?? false,
|
|
3569
|
+
// Interactive `fml sync` always discovers fresh; only the observer's
|
|
3570
|
+
// back-to-back cycles reuse a recent discovery.
|
|
3571
|
+
...opts.continuous ? { discoveryReuseMs: OBSERVER_DISCOVERY_REUSE_MS } : {},
|
|
3268
3572
|
observerIdentity: collectObserverIdentity(FML_INSTALLATION_ID_FILE) ?? void 0
|
|
3269
3573
|
});
|
|
3270
3574
|
}
|
|
@@ -3304,30 +3608,12 @@ async function runRawSyncOnce(opts = {}) {
|
|
|
3304
3608
|
};
|
|
3305
3609
|
}
|
|
3306
3610
|
|
|
3307
|
-
// src/package-root.ts
|
|
3308
|
-
import fs9 from "fs";
|
|
3309
|
-
import path19 from "path";
|
|
3310
|
-
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3311
|
-
function resolvePackageRoot(startDir = path19.dirname(fileURLToPath2(import.meta.url))) {
|
|
3312
|
-
let directory = startDir;
|
|
3313
|
-
while (true) {
|
|
3314
|
-
if (fs9.existsSync(path19.join(directory, "package.json")) && fs9.existsSync(path19.join(directory, "bin"))) {
|
|
3315
|
-
return directory;
|
|
3316
|
-
}
|
|
3317
|
-
const parent = path19.dirname(directory);
|
|
3318
|
-
if (parent === directory) {
|
|
3319
|
-
throw new Error(`Could not resolve FML package root from ${startDir}`);
|
|
3320
|
-
}
|
|
3321
|
-
directory = parent;
|
|
3322
|
-
}
|
|
3323
|
-
}
|
|
3324
|
-
|
|
3325
3611
|
export {
|
|
3326
3612
|
claudeConfigDirectory,
|
|
3327
3613
|
geminiConfigDirectory,
|
|
3328
3614
|
initializeSyncSettings,
|
|
3329
3615
|
createRawArtifactSync,
|
|
3330
|
-
|
|
3616
|
+
invalidateCliRawSyncDiscoveryCache,
|
|
3331
3617
|
createCliRawSync,
|
|
3332
3618
|
runRawSyncOnce
|
|
3333
3619
|
};
|