@indigoai-us/hq-cloud 6.14.27 → 6.14.28

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.
@@ -87,7 +87,8 @@ export interface PersonalVaultOptions {
87
87
  * `PERSONAL_VAULT_EXCLUDED_TOP_LEVEL`.
88
88
  * 2. Every local (non-cloud) `companies/{slug}/` subdir, after excluding
89
89
  * (a) `_template`, (b) slugs already in `opts.teamSyncedSlugs`
90
- * (cloud-backed — they sync to their own team bucket), and (c) any
90
+ * (cloud-backed — they sync to their own team bucket), (c) slugs with
91
+ * `cloud_uid: cmp_*` in `companies/manifest.yaml`, and (d) any
91
92
  * subdir explicitly marked `cloud: true` in its `company.yaml`.
92
93
  * A company with no `company.yaml` (or one without a `cloud:` marker)
93
94
  * is included — local companies sync to the personal vault by default.
@@ -363,12 +364,19 @@ export function computeSessionLogsSyncPaths(hqRoot: string): string[] {
363
364
  * (currently just `_template`).
364
365
  * 3. The slug is NOT in `teamSyncedSlugs` (membership-backed slugs sync
365
366
  * to their own bucket and must not be double-written).
366
- * 4. `companies/{slug}/company.yaml` does NOT contain an explicit
367
+ * 4. The slug is NOT listed with a `cloud_uid: cmp_*` in
368
+ * `companies/manifest.yaml`. Full-HQ agent boxes often run
369
+ * `--personal` alone (no company fanout targets), so
370
+ * `teamSyncedSlugs` is empty even though indigo (etc.) is
371
+ * cloud-backed; without this check the multi-GB company tree is
372
+ * re-mirrored into the agent personal vault every ritual and
373
+ * times out the 180s personal push.
374
+ * 5. `companies/{slug}/company.yaml` does NOT contain an explicit
367
375
  * `cloud: true` line. Local companies sync to the personal vault by
368
376
  * default, so a missing/markerless `company.yaml` is INCLUDED; only an
369
377
  * explicit `cloud: true` marker opts the directory OUT (belt-and-braces
370
- * with `teamSyncedSlugs` for a company designated cloud-backed before
371
- * its membership has resolved).
378
+ * with `teamSyncedSlugs` / manifest `cloud_uid` for a company designated
379
+ * cloud-backed before its membership has resolved).
372
380
  *
373
381
  * Returns absolute paths.
374
382
  */
@@ -383,10 +391,16 @@ export function computePersonalCompanySubdirs(
383
391
  } catch {
384
392
  return [];
385
393
  }
394
+ // Manifest cloud_uid is the durable routing signal for cloud-backed
395
+ // companies. Prefer it over relying solely on runner membership fanout
396
+ // (empty for --personal-only agent ritual) or company.yaml (often missing
397
+ // on full-HQ agent boxes that never ran /designate-team locally).
398
+ const cloudManifestSlugs = readCloudBackedSlugsFromManifest(hqRoot);
386
399
  const eligible: string[] = [];
387
400
  for (const slug of slugs) {
388
401
  if (PERSONAL_VAULT_COMPANY_EXCLUDED_SLUGS.includes(slug)) continue;
389
402
  if (teamSyncedSlugs.has(slug)) continue;
403
+ if (cloudManifestSlugs.has(slug)) continue;
390
404
  const subdir = path.join(companiesRoot, slug);
391
405
  let isDir = false;
392
406
  try {
@@ -401,6 +415,51 @@ export function computePersonalCompanySubdirs(
401
415
  return eligible;
402
416
  }
403
417
 
418
+ /**
419
+ * Slugs whose `companies/manifest.yaml` entry carries a `cloud_uid: cmp_*`.
420
+ * These are cloud-backed team companies and must not personal-vault mirror.
421
+ *
422
+ * Lightweight line parse (no YAML dep): fail-open on missing/unreadable
423
+ * manifest → empty set → companies stay eligible for personal fallback.
424
+ * Only `cmp_*` uids count (same rule as company-resolver / telemetry).
425
+ */
426
+ export function readCloudBackedSlugsFromManifest(hqRoot: string): Set<string> {
427
+ const manifestPath = path.join(hqRoot, "companies", "manifest.yaml");
428
+ let text: string;
429
+ try {
430
+ text = fs.readFileSync(manifestPath, "utf8");
431
+ } catch {
432
+ return new Set();
433
+ }
434
+ const out = new Set<string>();
435
+ let currentSlug: string | null = null;
436
+ let inCompanies = false;
437
+ for (const line of text.split(/\r?\n/)) {
438
+ // Top-level key (no indent). Only track entries under `companies:`.
439
+ if (/^[A-Za-z0-9_.-]+:\s*(?:#.*)?$/.test(line)) {
440
+ inCompanies = /^companies:\s*(?:#.*)?$/.test(line);
441
+ currentSlug = null;
442
+ continue;
443
+ }
444
+ if (!inCompanies) continue;
445
+ // Two-space indent: company slug under companies:
446
+ const slugMatch = /^ {2}([A-Za-z0-9][A-Za-z0-9_-]*):\s*(?:#.*)?$/.exec(line);
447
+ if (slugMatch) {
448
+ currentSlug = slugMatch[1];
449
+ continue;
450
+ }
451
+ if (
452
+ currentSlug &&
453
+ /^\s{2,}cloud_uid:\s*cmp_[A-Za-z0-9]+\b/.test(line)
454
+ ) {
455
+ out.add(currentSlug);
456
+ // Keep currentSlug so a later key on the same company is fine; next
457
+ // sibling slug line will replace it.
458
+ }
459
+ }
460
+ return out;
461
+ }
462
+
404
463
  /**
405
464
  * True iff `{subdir}/company.yaml` exists and contains an explicit
406
465
  * top-level `cloud: true` line — the marker `/designate-team` writes when a