@tetsuo-ai/agenc 0.17.0 → 0.19.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.
@@ -73,6 +73,8 @@ const DARWIN_ACL_KNOWN_TOKENS = new Set([
73
73
  ...DARWIN_ACL_INHERITANCE_FLAGS,
74
74
  ...DARWIN_ACL_MUTATION_RIGHTS,
75
75
  ]);
76
+ const DARWIN_ACL_VERDICT_CACHE_LIMIT = 512;
77
+ const darwinAclVerdicts = new Map();
76
78
 
77
79
  const WINDOWS_SECURITY_SCRIPT = String.raw`
78
80
  $ErrorActionPreference = 'Stop'
@@ -225,6 +227,17 @@ function throwIfExpired(context, path, cause) {
225
227
  }
226
228
  }
227
229
 
230
+ function reportProgress(context, phase) {
231
+ try {
232
+ const result = context.onProgress?.(phase);
233
+ if (result !== undefined) {
234
+ void Promise.resolve(result).catch(() => {});
235
+ }
236
+ } catch {
237
+ // Diagnostics must never change lock acquisition or release semantics.
238
+ }
239
+ }
240
+
228
241
  function processLockRegistry() {
229
242
  const current = process[REGISTRY_SYMBOL];
230
243
  if (current !== undefined) {
@@ -410,10 +423,45 @@ function validateDarwinAclListing(stdout, path, role) {
410
423
  }
411
424
 
412
425
  async function assertDarwinPathSecurity(path, role, context) {
426
+ return assertDarwinPathSecurityWithIO(
427
+ path,
428
+ role,
429
+ context,
430
+ execFileUtf8,
431
+ (candidate) => lstat(candidate, { bigint: true }),
432
+ );
433
+ }
434
+
435
+ // HFS+ reports change time in whole seconds, so an ACL edit within the same
436
+ // second can leave the identity unchanged. Only sub-second change times are
437
+ // precise enough to cache a verdict.
438
+ function hasSubsecondChangeTime(stats) {
439
+ if (typeof stats.ctimeNs === "bigint") return stats.ctimeNs % 1_000_000_000n !== 0n;
440
+ return Number.isFinite(stats.ctimeMs) && stats.ctimeMs % 1000 !== 0;
441
+ }
442
+
443
+ function darwinAclIdentity(stats) {
444
+ return [
445
+ stats.dev, stats.ino, stats.ctimeNs ?? stats.ctimeMs,
446
+ stats.mode, stats.uid, stats.gid,
447
+ process.getuid?.(), process.geteuid?.(),
448
+ ].join(":");
449
+ }
450
+
451
+ async function assertDarwinPathSecurityWithIO(path, role, context, lister, statPath) {
413
452
  throwIfExpired(context, path);
453
+ // A Darwin ACL edit advances ctime. Re-stat on every lookup so an old
454
+ // successful verdict cannot authorize a changed inode, owner, or ACL.
455
+ const before = darwinAclIdentity(await statPath(path));
456
+ const cacheKey = `${role}\0${path}`;
457
+ if (darwinAclVerdicts.get(cacheKey) === before) {
458
+ throwIfExpired(context, path);
459
+ return;
460
+ }
461
+ darwinAclVerdicts.delete(cacheKey);
414
462
  let result;
415
463
  try {
416
- result = await execFileUtf8(
464
+ result = await lister(
417
465
  "/bin/ls",
418
466
  ["-ldeq", path],
419
467
  {
@@ -431,6 +479,16 @@ async function assertDarwinPathSecurity(path, role, context) {
431
479
  }
432
480
  validateDarwinAclListing(result.stdout, path, role);
433
481
  throwIfExpired(context, path);
482
+ // A path changed while ls ran must never seed a verdict for its new state.
483
+ const afterStats = await statPath(path);
484
+ const after = darwinAclIdentity(afterStats);
485
+ throwIfExpired(context, path);
486
+ if (after === before && hasSubsecondChangeTime(afterStats)) {
487
+ if (darwinAclVerdicts.size >= DARWIN_ACL_VERDICT_CACHE_LIMIT) {
488
+ darwinAclVerdicts.delete(darwinAclVerdicts.keys().next().value);
489
+ }
490
+ darwinAclVerdicts.set(cacheKey, before);
491
+ }
434
492
  }
435
493
 
436
494
  function trustedWindowsPowerShellPath(
@@ -706,6 +764,7 @@ export async function assertLocalPrivateDirectory(
706
764
  label = "AgenC operation",
707
765
  deadline: suppliedDeadline,
708
766
  allowTrustedStickyLeaf = false,
767
+ onProgress,
709
768
  } = {},
710
769
  ) {
711
770
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
@@ -717,6 +776,9 @@ export async function assertLocalPrivateDirectory(
717
776
  if (typeof allowTrustedStickyLeaf !== "boolean") {
718
777
  throw new TypeError("allowTrustedStickyLeaf must be boolean");
719
778
  }
779
+ if (onProgress !== undefined && typeof onProgress !== "function") {
780
+ throw new TypeError("lock onProgress must be a function");
781
+ }
720
782
  const context = {
721
783
  deadline: Math.min(
722
784
  suppliedDeadline ?? Number.POSITIVE_INFINITY,
@@ -724,7 +786,9 @@ export async function assertLocalPrivateDirectory(
724
786
  ),
725
787
  label,
726
788
  timeoutMs,
789
+ onProgress,
727
790
  };
791
+ reportProgress(context, "private directory validation started");
728
792
  const absolute = resolve(requestedPath);
729
793
  throwIfExpired(context, absolute);
730
794
  const canonical = await realpath(absolute);
@@ -764,6 +828,7 @@ export async function assertLocalPrivateDirectory(
764
828
  identityFromStats(stats, path);
765
829
  }
766
830
  if (process.platform === "win32") {
831
+ reportProgress(context, "Windows ancestor ACL validation started");
767
832
  await assertWindowsPathSecurity(
768
833
  ancestors.map((path, index) => ({
769
834
  path,
@@ -771,6 +836,7 @@ export async function assertLocalPrivateDirectory(
771
836
  })),
772
837
  context,
773
838
  );
839
+ reportProgress(context, "Windows ancestor ACL validation complete");
774
840
  } else {
775
841
  await assertLocalFilesystem(canonical, context);
776
842
  if (process.platform === "darwin") {
@@ -794,6 +860,7 @@ export async function assertLocalPrivateDirectory(
794
860
  throw new Error(`agenc: protected directory identity changed during validation: ${path}`);
795
861
  }
796
862
  }
863
+ reportProgress(context, "private directory validation complete");
797
864
  return canonical;
798
865
  }
799
866
 
@@ -840,6 +907,7 @@ export async function assertLocalPrivateFile(
840
907
  timeoutMs = 60_000,
841
908
  label = "AgenC operation",
842
909
  deadline: suppliedDeadline,
910
+ onProgress,
843
911
  } = {},
844
912
  ) {
845
913
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
@@ -848,6 +916,9 @@ export async function assertLocalPrivateFile(
848
916
  if (suppliedDeadline !== undefined && !Number.isFinite(suppliedDeadline)) {
849
917
  throw new TypeError("lock deadline must be finite");
850
918
  }
919
+ if (onProgress !== undefined && typeof onProgress !== "function") {
920
+ throw new TypeError("lock onProgress must be a function");
921
+ }
851
922
  const context = {
852
923
  deadline: Math.min(
853
924
  suppliedDeadline ?? Number.POSITIVE_INFINITY,
@@ -855,13 +926,16 @@ export async function assertLocalPrivateFile(
855
926
  ),
856
927
  label,
857
928
  timeoutMs,
929
+ onProgress,
858
930
  };
931
+ reportProgress(context, "private file validation started");
859
932
  const absolute = resolve(requestedPath);
860
933
  const parent = dirname(absolute);
861
934
  const canonicalParent = await assertLocalPrivateDirectory(parent, {
862
935
  timeoutMs,
863
936
  label,
864
937
  deadline: context.deadline,
938
+ ...(onProgress === undefined ? {} : { onProgress }),
865
939
  });
866
940
  if (canonicalParent !== parent) {
867
941
  throw new Error(`agenc: protected file parent must use its canonical path: ${parent}`);
@@ -886,7 +960,9 @@ export async function assertLocalPrivateFile(
886
960
  throw new Error(`agenc: protected file must use its canonical path: ${absolute}`);
887
961
  }
888
962
  if (process.platform === "win32") {
963
+ reportProgress(context, "Windows file ACL validation started");
889
964
  await assertWindowsPathSecurity([{ path: canonical, role: "file" }], context);
965
+ reportProgress(context, "Windows file ACL validation complete");
890
966
  } else if (process.platform === "darwin") {
891
967
  await assertDarwinPathSecurity(canonical, "file", context);
892
968
  }
@@ -898,21 +974,30 @@ export async function assertLocalPrivateFile(
898
974
  ) {
899
975
  throw new Error(`agenc: protected file identity changed during validation: ${canonical}`);
900
976
  }
977
+ reportProgress(context, "private file validation complete");
901
978
  return canonical;
902
979
  }
903
980
 
904
981
  async function prepareLockPath(requestedPath, context) {
905
982
  const absolute = resolve(requestedPath);
983
+ reportProgress(context, "lock path preparation started");
906
984
  throwIfExpired(context, absolute);
907
985
  await mkdir(dirname(absolute), { recursive: true, mode: 0o700 });
986
+ reportProgress(context, "lock parent creation complete");
908
987
  throwIfExpired(context, absolute);
909
988
  const parent = await realpath(dirname(absolute));
989
+ reportProgress(context, "lock parent canonicalization complete");
910
990
  throwIfExpired(context, absolute);
991
+ reportProgress(context, "lock parent security validation started");
911
992
  const validatedParent = await assertLocalPrivateDirectory(parent, {
912
993
  timeoutMs: context.timeoutMs,
913
994
  label: context.label,
914
995
  deadline: context.deadline,
996
+ ...(context.onProgress === undefined
997
+ ? {}
998
+ : { onProgress: context.onProgress }),
915
999
  });
1000
+ reportProgress(context, "lock parent security validation complete");
916
1001
  if (validatedParent !== parent) {
917
1002
  throw new Error(`agenc: lock database parent must use its canonical path: ${parent}`);
918
1003
  }
@@ -935,6 +1020,7 @@ async function prepareLockPath(requestedPath, context) {
935
1020
  } catch (error) {
936
1021
  if (error?.code !== "EEXIST") throw error;
937
1022
  }
1023
+ reportProgress(context, "lock database file creation complete");
938
1024
  throwIfExpired(context, path);
939
1025
  const pathStats = await lstat(path, { bigint: true });
940
1026
  assertRegularSingleLink(pathStats, path);
@@ -949,10 +1035,12 @@ async function prepareLockPath(requestedPath, context) {
949
1035
  await assertDarwinPathSecurity(canonical, "lock database file", context);
950
1036
  }
951
1037
  } else {
1038
+ reportProgress(context, "lock database ACL validation started");
952
1039
  await assertWindowsPathSecurity([
953
1040
  { path: parent, role: "leafDirectory" },
954
1041
  { path: canonical, role: "file" },
955
1042
  ], context);
1043
+ reportProgress(context, "lock database ACL validation complete");
956
1044
  }
957
1045
  throwIfExpired(context, canonical);
958
1046
  const securedStats = await lstat(canonical, { bigint: true });
@@ -967,7 +1055,11 @@ async function prepareLockPath(requestedPath, context) {
967
1055
  };
968
1056
  }
969
1057
 
970
- async function revalidatePreparedLock(prepared, context) {
1058
+ async function revalidatePreparedLock(
1059
+ prepared,
1060
+ context,
1061
+ { validateWindowsAcl = false } = {},
1062
+ ) {
971
1063
  throwIfExpired(context, prepared.path);
972
1064
  const parentStats = await lstat(prepared.parent, { bigint: true });
973
1065
  if (!parentStats.isDirectory() || parentStats.isSymbolicLink()) {
@@ -980,7 +1072,7 @@ async function revalidatePreparedLock(prepared, context) {
980
1072
  if (stats.dev !== prepared.dev || stats.ino !== prepared.ino) {
981
1073
  throw new Error(`agenc: lock database identity changed during acquisition: ${prepared.path}`);
982
1074
  }
983
- if (process.platform === "win32") {
1075
+ if (process.platform === "win32" && validateWindowsAcl) {
984
1076
  await assertWindowsPathSecurity([
985
1077
  { path: prepared.parent, role: "leafDirectory" },
986
1078
  { path: prepared.path, role: "file" },
@@ -1076,18 +1168,27 @@ function busyTransitionError(path, mode) {
1076
1168
  );
1077
1169
  }
1078
1170
 
1079
- function beginAndValidateLock(database, path) {
1171
+ function beginAndValidateLock(database, path, context) {
1080
1172
  for (let phase = 0; phase < 8; phase += 1) {
1173
+ reportProgress(context, "SQLite transaction begin started");
1081
1174
  database.exec("BEGIN IMMEDIATE");
1175
+ reportProgress(context, "SQLite transaction begin complete");
1176
+ reportProgress(context, "SQLite lock database inspection started");
1082
1177
  const state = inspectLockDatabase(database, path);
1178
+ reportProgress(context, "SQLite lock database inspection complete");
1179
+ reportProgress(context, "SQLite journal mode inspection started");
1083
1180
  const journalMode = pragmaText(database, "journal_mode");
1181
+ reportProgress(context, "SQLite journal mode inspection complete");
1084
1182
  if (journalMode !== "delete") {
1183
+ reportProgress(context, "SQLite journal mode reset started");
1085
1184
  database.exec("ROLLBACK");
1086
1185
  const selected = pragmaText(database, "journal_mode=DELETE");
1087
1186
  if (selected !== "delete") throw busyTransitionError(path, selected);
1187
+ reportProgress(context, "SQLite journal mode reset complete");
1088
1188
  continue;
1089
1189
  }
1090
1190
  if (state === "empty") {
1191
+ reportProgress(context, "SQLite lock database initialization started");
1091
1192
  database.exec(`
1092
1193
  PRAGMA application_id = ${LOCK_APPLICATION_ID};
1093
1194
  CREATE TABLE agenc_local_process_lock (
@@ -1098,6 +1199,7 @@ function beginAndValidateLock(database, path) {
1098
1199
  VALUES (1, ${LOCK_FORMAT_VERSION});
1099
1200
  COMMIT;
1100
1201
  `);
1202
+ reportProgress(context, "SQLite lock database initialization complete");
1101
1203
  continue;
1102
1204
  }
1103
1205
  return;
@@ -1134,7 +1236,32 @@ async function waitForBusyRetry(context, path, attempt, cause) {
1134
1236
  if (remaining <= 0) throw timeoutError(context, path, cause);
1135
1237
  const exponentialCap = Math.min(MAX_BUSY_RETRY_MS, 2 ** Math.min(attempt, 6));
1136
1238
  const jitter = Math.max(1, Math.floor(Math.random() * (exponentialCap + 1)));
1137
- await delay(Math.min(remaining, jitter));
1239
+ const waitMs = Math.min(remaining, jitter);
1240
+ const signal = context.retryWakeSignal;
1241
+ if (signal === undefined || context.retryWakeConsumed) {
1242
+ await delay(waitMs);
1243
+ } else {
1244
+ // A hint only shortens one sleep. The next attempt must still validate
1245
+ // the path and acquire the SQLite transaction against the same deadline.
1246
+ let timer;
1247
+ let wake;
1248
+ try {
1249
+ await new Promise((resolve) => {
1250
+ wake = () => {
1251
+ context.retryWakeConsumed = true;
1252
+ resolve();
1253
+ };
1254
+ timer = setTimeout(resolve, waitMs);
1255
+ signal.addEventListener("abort", wake, { once: true });
1256
+ // Covers readiness before subscription as well as before acquisition.
1257
+ if (signal.aborted) wake();
1258
+ reportProgress(context, "SQLite busy retry wait started");
1259
+ });
1260
+ } finally {
1261
+ clearTimeout(timer);
1262
+ if (wake !== undefined) signal.removeEventListener("abort", wake);
1263
+ }
1264
+ }
1138
1265
  throwIfExpired(context, path, cause);
1139
1266
  }
1140
1267
 
@@ -1143,16 +1270,29 @@ async function acquireSqliteDatabase(DatabaseSync, prepared, context) {
1143
1270
  let lastBusy;
1144
1271
  while (true) {
1145
1272
  throwIfExpired(context, prepared.path, lastBusy);
1146
- await revalidatePreparedLock(prepared, context);
1273
+ reportProgress(context, "SQLite pre-open lock validation started");
1274
+ await revalidatePreparedLock(prepared, context, {
1275
+ // The creation path already validated the complete ancestor chain and
1276
+ // the exact parent/file ACLs. Revalidate ACLs after contention because
1277
+ // another holder and an unbounded interval have crossed this attempt.
1278
+ validateWindowsAcl: attempt > 0,
1279
+ });
1280
+ reportProgress(context, "SQLite pre-open lock validation complete");
1147
1281
  let database;
1148
1282
  try {
1283
+ reportProgress(context, "SQLite database open started");
1149
1284
  database = new DatabaseSync(prepared.path, {
1150
1285
  allowExtension: false,
1151
1286
  timeout: 0,
1152
1287
  });
1288
+ reportProgress(context, "SQLite database open complete");
1289
+ reportProgress(context, "SQLite connection hardening started");
1153
1290
  configureLocalSqliteLockConnection(database);
1291
+ reportProgress(context, "SQLite connection hardening complete");
1292
+ reportProgress(context, "SQLite post-open lock validation started");
1154
1293
  await revalidatePreparedLock(prepared, context);
1155
- beginAndValidateLock(database, prepared.path);
1294
+ reportProgress(context, "SQLite post-open lock validation complete");
1295
+ beginAndValidateLock(database, prepared.path, context);
1156
1296
  throwIfExpired(context, prepared.path, lastBusy);
1157
1297
  return database;
1158
1298
  } catch (error) {
@@ -1207,6 +1347,8 @@ export async function acquireLocalSqliteLocks(
1207
1347
  timeoutMs = 60_000,
1208
1348
  label = "AgenC operation",
1209
1349
  deadline: suppliedDeadline,
1350
+ onProgress,
1351
+ retryWakeSignal,
1210
1352
  } = {},
1211
1353
  ) {
1212
1354
  if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
@@ -1218,6 +1360,12 @@ export async function acquireLocalSqliteLocks(
1218
1360
  if (!Array.isArray(requestedPaths)) {
1219
1361
  throw new TypeError("lock paths must be an array");
1220
1362
  }
1363
+ if (onProgress !== undefined && typeof onProgress !== "function") {
1364
+ throw new TypeError("lock onProgress must be a function");
1365
+ }
1366
+ if (retryWakeSignal !== undefined && !(retryWakeSignal instanceof AbortSignal)) {
1367
+ throw new TypeError("lock retryWakeSignal must be an AbortSignal");
1368
+ }
1221
1369
  if (requestedPaths.length === 0) return () => {};
1222
1370
 
1223
1371
  const startedAt = performance.now();
@@ -1228,6 +1376,9 @@ export async function acquireLocalSqliteLocks(
1228
1376
  ),
1229
1377
  label,
1230
1378
  timeoutMs,
1379
+ onProgress,
1380
+ retryWakeSignal,
1381
+ retryWakeConsumed: false,
1231
1382
  };
1232
1383
  const firstDisplayPath = resolve(requestedPaths[0]);
1233
1384
  throwIfExpired(context, firstDisplayPath);
@@ -1238,6 +1389,7 @@ export async function acquireLocalSqliteLocks(
1238
1389
  const prepared = await prepareLockPath(requestedPath, context);
1239
1390
  preparedByIdentity.set(prepared.identityKey, prepared);
1240
1391
  }
1392
+ reportProgress(context, "lock path preparation complete");
1241
1393
  const preparedLocks = [...preparedByIdentity.values()].sort((left, right) =>
1242
1394
  left.identityKey < right.identityKey ? -1 : left.identityKey > right.identityKey ? 1 : 0);
1243
1395
  const pendingLocal = [];
@@ -1249,8 +1401,11 @@ export async function acquireLocalSqliteLocks(
1249
1401
  const release = await acquireInProcessLock(prepared, context);
1250
1402
  pendingLocal.push({ prepared, release });
1251
1403
  }
1404
+ reportProgress(context, "in-process lock acquisition complete");
1252
1405
  throwIfExpired(context, currentPath);
1406
+ reportProgress(context, "SQLite module import started");
1253
1407
  const { DatabaseSync } = await import("node:sqlite");
1408
+ reportProgress(context, "SQLite module import complete");
1254
1409
  throwIfExpired(context, currentPath);
1255
1410
  for (const { prepared, release } of pendingLocal) {
1256
1411
  currentPath = prepared.path;
@@ -1260,7 +1415,9 @@ export async function acquireLocalSqliteLocks(
1260
1415
  inProcessReleased: false,
1261
1416
  };
1262
1417
  acquired.push(item);
1418
+ reportProgress(context, "SQLite transaction acquisition started");
1263
1419
  item.database = await acquireSqliteDatabase(DatabaseSync, prepared, context);
1420
+ reportProgress(context, "SQLite transaction acquisition complete");
1264
1421
  }
1265
1422
  } catch (error) {
1266
1423
  const cleanupErrors = [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tetsuo-ai/agenc",
3
- "version": "0.17.0",
3
+ "version": "0.19.0",
4
4
  "description": "Public AgenC CLI launcher package",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -21,6 +21,8 @@
21
21
  "src/launcher.mjs",
22
22
  "lib/activation-lock-identity.d.mts",
23
23
  "lib/activation-lock-identity.mjs",
24
+ "lib/home-authority.d.mts",
25
+ "lib/home-authority.mjs",
24
26
  "lib/generated-wrapper.d.mts",
25
27
  "lib/generated-wrapper.mjs",
26
28
  "lib/runtime-archive.d.mts",
@@ -32,11 +34,12 @@
32
34
  "lib/sqlite-lock.d.mts",
33
35
  "lib/sqlite-lock.mjs",
34
36
  "scripts/postinstall.mjs",
35
- "generated/agenc-runtime-manifest-v2.json"
37
+ "generated/agenc-runtime-manifest-v2.json",
38
+ "generated/startup-preflight.mjs"
36
39
  ],
37
40
  "scripts": {
38
41
  "postinstall": "node ./scripts/postinstall.mjs",
39
- "prepack": "node ./scripts/check-package-ready.mjs && node ../../scripts/canonicalize-package-modes.mjs .",
42
+ "prepack": "node ../../runtime/scripts/check-launcher-preflight.mjs --check && node ./scripts/check-package-ready.mjs && node ../../scripts/canonicalize-package-modes.mjs .",
40
43
  "build:runtime-tarball": "node ./scripts/build-runtime-tarball.mjs",
41
44
  "gen:manifest": "node ./scripts/gen-manifest.mjs",
42
45
  "test": "node --test test/*.test.mjs"
@@ -2,6 +2,9 @@
2
2
  // Runs on `npm install @tetsuo-ai/agenc`. Pre-fetches the platform runtime so
3
3
  // the first `agenc` invocation is fast. Best-effort: a failure here (offline
4
4
  // install, CI, etc.) is NOT fatal — the launcher fetches lazily on first run.
5
+ import { spawnSync } from "node:child_process";
6
+ import { existsSync } from "node:fs";
7
+ import { dirname, join } from "node:path";
5
8
 
6
9
  // Skip in obviously non-interactive / packaging contexts.
7
10
  if (process.env.AGENC_SKIP_POSTINSTALL === "1" || process.env.CI === "true") {
@@ -9,8 +12,26 @@ if (process.env.AGENC_SKIP_POSTINSTALL === "1" || process.env.CI === "true") {
9
12
  }
10
13
 
11
14
  try {
12
- const { ensureRuntime } = await import("../lib/runtime-manager.mjs");
13
- await ensureRuntime();
15
+ const { ensureRuntimeLaunch } = await import("../lib/runtime-manager.mjs");
16
+ const launch = await ensureRuntimeLaunch();
17
+ const helper = join(dirname(launch.runtimeBin), "prepare-peer-credentials.mjs");
18
+ if (existsSync(helper)) {
19
+ // Use the installed runtime's Node ABI and private library directory.
20
+ // Preparation only warms the user cache; signed package bytes stay intact.
21
+ const result = spawnSync(launch.nodeBin, [helper], {
22
+ env: {
23
+ ...process.env,
24
+ ...(launch.nodeLibraryPath === undefined
25
+ ? {}
26
+ : { LD_LIBRARY_PATH: launch.nodeLibraryPath }),
27
+ },
28
+ stdio: "inherit",
29
+ timeout: 30_000,
30
+ });
31
+ if (result.error !== undefined || result.status !== 0) {
32
+ process.stderr.write("agenc: native peer credential preparation skipped; startup will retry.\n");
33
+ }
34
+ }
14
35
  } catch (err) {
15
36
  process.stderr.write(
16
37
  `agenc: runtime pre-fetch skipped (${err?.message ?? err}); it will be fetched on first run.\n`,