@indigoai-us/hq-cloud 6.14.2 → 6.14.4
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/dist/bin/sync-runner-company.d.ts +1 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +44 -19
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +25 -36
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +128 -4
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +226 -2
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/reindex.test.js +134 -0
- package/dist/cli/reindex.test.js.map +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +16 -14
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +73 -0
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/personal-vault.d.ts +29 -1
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +48 -2
- package/dist/personal-vault.js.map +1 -1
- package/dist/personal-vault.test.js +44 -1
- package/dist/personal-vault.test.js.map +1 -1
- package/dist/watcher.d.ts +1 -1
- package/dist/watcher.d.ts.map +1 -1
- package/dist/watcher.js +20 -7
- package/dist/watcher.js.map +1 -1
- package/dist/watcher.test.js +73 -0
- package/dist/watcher.test.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/sync-runner-company.ts +50 -22
- package/src/bin/sync-runner.test.ts +147 -4
- package/src/bin/sync-runner.ts +49 -54
- package/src/cli/reindex.test.ts +162 -0
- package/src/cli/reindex.ts +219 -2
- package/src/cli/sync.test.ts +90 -0
- package/src/cli/sync.ts +16 -14
- package/src/index.ts +2 -0
- package/src/personal-vault.test.ts +57 -0
- package/src/personal-vault.ts +48 -2
- package/src/watcher.test.ts +89 -0
- package/src/watcher.ts +23 -8
package/src/personal-vault.ts
CHANGED
|
@@ -26,7 +26,12 @@
|
|
|
26
26
|
* the routing source-of-truth.)
|
|
27
27
|
* - `repos/`, `workspace/`: per user directive — heavy local-only
|
|
28
28
|
* content (cloned remotes, session threads) that has no business in
|
|
29
|
-
* the personal vault.
|
|
29
|
+
* the personal vault. Three explicit sub-paths pierce this exclusion and
|
|
30
|
+
* DO round-trip: `workspace/threads/handoff.json` (+ its active thread) via
|
|
31
|
+
* {@link computeContinuityPointerPaths}, `workspace/agency/` via
|
|
32
|
+
* {@link computeAgencySyncPaths}, and `workspace/.session-logs/` (the
|
|
33
|
+
* reindex-captured Claude Code transcripts) via
|
|
34
|
+
* {@link computeSessionLogsSyncPaths}.
|
|
30
35
|
*
|
|
31
36
|
* Note: `core/`, `data/`, and `personal/` were previously excluded but are
|
|
32
37
|
* INCLUDED as of user directive 2026-05-13. `core/` ships the hq-core
|
|
@@ -134,7 +139,15 @@ export function computePersonalVaultPaths(
|
|
|
134
139
|
);
|
|
135
140
|
const continuity = computeContinuityPointerPaths(hqRoot);
|
|
136
141
|
const agency = computeAgencySyncPaths(hqRoot);
|
|
137
|
-
|
|
142
|
+
const sessionLogs = computeSessionLogsSyncPaths(hqRoot);
|
|
143
|
+
return [
|
|
144
|
+
...topLevel,
|
|
145
|
+
...manifest,
|
|
146
|
+
...companySubdirs,
|
|
147
|
+
...continuity,
|
|
148
|
+
...agency,
|
|
149
|
+
...sessionLogs,
|
|
150
|
+
];
|
|
138
151
|
}
|
|
139
152
|
|
|
140
153
|
/**
|
|
@@ -261,6 +274,39 @@ export function computeAgencySyncPaths(hqRoot: string): string[] {
|
|
|
261
274
|
return [];
|
|
262
275
|
}
|
|
263
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Fixed relative path (forward-slash, hq-root-relative) of the session-logs
|
|
279
|
+
* subtree. See {@link computeSessionLogsSyncPaths}.
|
|
280
|
+
*/
|
|
281
|
+
export const SESSION_LOGS_SYNC_REL = "workspace/.session-logs";
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Compute the absolute path of the session-logs carve-out:
|
|
285
|
+
* `workspace/.session-logs/` (the whole subtree).
|
|
286
|
+
*
|
|
287
|
+
* `hq reindex` copies this HQ root's Claude Code session transcripts here (see
|
|
288
|
+
* reindex's copySessionLogs). Like the agency carve-out above, this pierces the
|
|
289
|
+
* `workspace/` top-level exclusion for ONE specific subtree so those raw
|
|
290
|
+
* transcripts round-trip to the personal vault / S3 — the operator's explicit
|
|
291
|
+
* directive is that the session logs travel with the vault. The rest of
|
|
292
|
+
* `workspace/` (session scratch, locks, reports, worktrees) stays machine-local.
|
|
293
|
+
*
|
|
294
|
+
* Returns the directory path when it exists; share()'s per-file walk handles
|
|
295
|
+
* recursion, and the nested personal-vault exclusions still apply to files
|
|
296
|
+
* inside it. Fail-soft: a missing or unreadable `workspace/.session-logs/`
|
|
297
|
+
* returns []. Callers tolerate empty arrays — same contract as the manifest,
|
|
298
|
+
* continuity, and agency special-cases.
|
|
299
|
+
*/
|
|
300
|
+
export function computeSessionLogsSyncPaths(hqRoot: string): string[] {
|
|
301
|
+
const sessionLogsDir = path.join(hqRoot, "workspace", ".session-logs");
|
|
302
|
+
try {
|
|
303
|
+
if (fs.statSync(sessionLogsDir).isDirectory()) return [sessionLogsDir];
|
|
304
|
+
} catch {
|
|
305
|
+
// No session logs captured on this machine yet — nothing to carry.
|
|
306
|
+
}
|
|
307
|
+
return [];
|
|
308
|
+
}
|
|
309
|
+
|
|
264
310
|
/**
|
|
265
311
|
* Discover `companies/{slug}/` subdirs that should sync to the personal
|
|
266
312
|
* bucket as a fallback for companies the operator has not designated as
|
package/src/watcher.test.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { StaticFlagProvider } from "./sync/feature-flags.js";
|
|
13
13
|
import type { PushEvent } from "./sync/push-event.js";
|
|
14
14
|
import type { PushTransport } from "./sync/push-transport.js";
|
|
15
|
+
import type { TelemetryEventsBatch } from "./vault-client.js";
|
|
15
16
|
|
|
16
17
|
/**
|
|
17
18
|
* US-001 — Phase 1 test harness: watch-triggered push seam + latency assertion.
|
|
@@ -371,6 +372,36 @@ describe("US-002: createWatchPathFilter — personal-vault exclusions", () => {
|
|
|
371
372
|
expect(personal(path.join(ROOT, "workspace/worktrees/x/a.ts"))).toBe(false);
|
|
372
373
|
});
|
|
373
374
|
|
|
375
|
+
// ── workspace/.session-logs subtree carve-out (personal mode) ────────────
|
|
376
|
+
// The personal push uploads workspace/.session-logs/** (reindex-captured
|
|
377
|
+
// Claude Code transcripts, computeSessionLogsSyncPaths), so the personal
|
|
378
|
+
// watcher must emit those changes event-driven. Mirrors the agency carve-out.
|
|
379
|
+
it("DOES emit for workspace/.session-logs files in personalMode (synced subtree)", () => {
|
|
380
|
+
expect(
|
|
381
|
+
personal(path.join(ROOT, "workspace/.session-logs/session.jsonl")),
|
|
382
|
+
).toBe(true);
|
|
383
|
+
expect(
|
|
384
|
+
personal(path.join(ROOT, "workspace/.session-logs/tool-results/r.txt")),
|
|
385
|
+
).toBe(true);
|
|
386
|
+
// The subtree root itself, as a file event, also emits.
|
|
387
|
+
expect(personal(path.join(ROOT, "workspace/.session-logs"))).toBe(true);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it("allows chokidar to DESCEND to workspace/.session-logs in personalMode (dir probes)", () => {
|
|
391
|
+
expect(personal(path.join(ROOT, "workspace"), true)).toBe(true);
|
|
392
|
+
expect(personal(path.join(ROOT, "workspace/.session-logs"), true)).toBe(true);
|
|
393
|
+
expect(
|
|
394
|
+
personal(path.join(ROOT, "workspace/.session-logs/tool-results"), true),
|
|
395
|
+
).toBe(true);
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("session-logs carve-out is exact-segment (does NOT leak to a similarly-named sibling)", () => {
|
|
399
|
+
expect(
|
|
400
|
+
personal(path.join(ROOT, "workspace/.session-logs-old/foo.txt")),
|
|
401
|
+
).toBe(false);
|
|
402
|
+
expect(personal(path.join(ROOT, "workspace/.session-logsX.md"))).toBe(false);
|
|
403
|
+
});
|
|
404
|
+
|
|
374
405
|
it("does not apply the carve-out in non-personal mode", () => {
|
|
375
406
|
// In non-personal mode the excluded-top-level layer never runs, so the
|
|
376
407
|
// carve-out is irrelevant; handoff.json passes via the ordinary ignore
|
|
@@ -706,4 +737,62 @@ describe("PushEventEmitter — directory and delete tombstone handling", () => {
|
|
|
706
737
|
expect(published[0]).not.toHaveProperty("contentHash");
|
|
707
738
|
expect(published[0]).not.toHaveProperty("mtime");
|
|
708
739
|
});
|
|
740
|
+
|
|
741
|
+
it("emits push telemetry only when publication fails", async () => {
|
|
742
|
+
const posts: TelemetryEventsBatch[] = [];
|
|
743
|
+
const telemetryClient = {
|
|
744
|
+
postTelemetryEvents: vi.fn(async (batch: TelemetryEventsBatch) => {
|
|
745
|
+
posts.push(batch);
|
|
746
|
+
return { ok: true, written: batch.events.length, skipped: [] };
|
|
747
|
+
}),
|
|
748
|
+
};
|
|
749
|
+
const changed = path.join(dir, "changed.md");
|
|
750
|
+
fs.writeFileSync(changed, "changed");
|
|
751
|
+
const success = new PushEventEmitter({
|
|
752
|
+
originTenantId: "tenant-indigo",
|
|
753
|
+
originDeviceId: "device-a",
|
|
754
|
+
transport: {
|
|
755
|
+
start: async () => {},
|
|
756
|
+
dispose: async () => {},
|
|
757
|
+
connected: true,
|
|
758
|
+
publish: async () => {},
|
|
759
|
+
},
|
|
760
|
+
flagProvider: new StaticFlagProvider(["tenant-indigo"]),
|
|
761
|
+
telemetryClient,
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
await success.emitForBatch({ paths: new Map([[changed, "changed.md"]]) });
|
|
765
|
+
await flushImmediate();
|
|
766
|
+
expect(posts).toEqual([]);
|
|
767
|
+
|
|
768
|
+
const failed = new PushEventEmitter({
|
|
769
|
+
originTenantId: "tenant-indigo",
|
|
770
|
+
originDeviceId: "device-a",
|
|
771
|
+
transport: {
|
|
772
|
+
start: async () => {},
|
|
773
|
+
dispose: async () => {},
|
|
774
|
+
connected: true,
|
|
775
|
+
publish: async () => {
|
|
776
|
+
throw new Error("network down");
|
|
777
|
+
},
|
|
778
|
+
},
|
|
779
|
+
flagProvider: new StaticFlagProvider(["tenant-indigo"]),
|
|
780
|
+
telemetryClient,
|
|
781
|
+
onError: vi.fn(),
|
|
782
|
+
});
|
|
783
|
+
|
|
784
|
+
await failed.emitForBatch({ paths: new Map([[changed, "changed.md"]]) });
|
|
785
|
+
await flushImmediate();
|
|
786
|
+
expect(posts.flatMap((post) => post.events)).toEqual([
|
|
787
|
+
expect.objectContaining({
|
|
788
|
+
eventName: "push_event_failed",
|
|
789
|
+
properties: {
|
|
790
|
+
kind: "upsert",
|
|
791
|
+
count: 1,
|
|
792
|
+
status: "failure",
|
|
793
|
+
stage: "publish",
|
|
794
|
+
},
|
|
795
|
+
}),
|
|
796
|
+
]);
|
|
797
|
+
});
|
|
709
798
|
});
|
package/src/watcher.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { isPersonalVaultExcluded } from "./personal-vault-exclusions.js";
|
|
|
16
16
|
import {
|
|
17
17
|
AGENCY_SYNC_REL,
|
|
18
18
|
CONTINUITY_POINTER_REL,
|
|
19
|
+
SESSION_LOGS_SYNC_REL,
|
|
19
20
|
PERSONAL_VAULT_EXCLUDED_TOP_LEVEL,
|
|
20
21
|
} from "./personal-vault.js";
|
|
21
22
|
import type { PushEvent } from "./sync/push-event.js";
|
|
@@ -466,6 +467,22 @@ export function createWatchPathFilter(
|
|
|
466
467
|
}
|
|
467
468
|
if (isDir && AGENCY_SYNC_REL.startsWith(rel + "/")) return true;
|
|
468
469
|
|
|
470
|
+
// Session-logs carve-out: `workspace/.session-logs/` (the whole subtree)
|
|
471
|
+
// IS synced via computeSessionLogsSyncPaths (mirrored on the push side),
|
|
472
|
+
// so it must re-include BEFORE the top-level `workspace/` rejection below
|
|
473
|
+
// — identical shape to the agency carve-out just above. Without this, a
|
|
474
|
+
// `--personal --watch` runner never emits reindex-captured session-log
|
|
475
|
+
// changes and they wait for the 60s poll. Paths at/under it emit; the
|
|
476
|
+
// subtree root and its ancestor dir (`workspace`) return true for the dir
|
|
477
|
+
// probe so chokidar descends to reach them instead of pruning `workspace/`.
|
|
478
|
+
if (
|
|
479
|
+
rel === SESSION_LOGS_SYNC_REL ||
|
|
480
|
+
rel.startsWith(SESSION_LOGS_SYNC_REL + "/")
|
|
481
|
+
) {
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
if (isDir && SESSION_LOGS_SYNC_REL.startsWith(rel + "/")) return true;
|
|
485
|
+
|
|
469
486
|
// Layer 3: excluded top-level buckets (.git/companies/repos/workspace).
|
|
470
487
|
const topLevel = rel.split("/")[0];
|
|
471
488
|
if (excludedTopLevel.has(topLevel)) return false;
|
|
@@ -972,7 +989,7 @@ export class PushEventEmitter {
|
|
|
972
989
|
this.onError(err instanceof Error ? err : new Error(String(err)), {
|
|
973
990
|
relativePath,
|
|
974
991
|
});
|
|
975
|
-
this.
|
|
992
|
+
this.emitPublishFailureTelemetry("unknown", "capture");
|
|
976
993
|
return;
|
|
977
994
|
}
|
|
978
995
|
|
|
@@ -1008,31 +1025,29 @@ export class PushEventEmitter {
|
|
|
1008
1025
|
|
|
1009
1026
|
try {
|
|
1010
1027
|
await this.transport.publish(event);
|
|
1011
|
-
this.emitPublishTelemetry("push_event_published", event.kind, "publish");
|
|
1012
1028
|
} catch (err) {
|
|
1013
1029
|
// Push failure (network / non-2xx / timeout). The cadence poll is the
|
|
1014
1030
|
// safety net — log + continue, never throw.
|
|
1015
1031
|
this.onError(err instanceof Error ? err : new Error(String(err)), {
|
|
1016
1032
|
relativePath,
|
|
1017
1033
|
});
|
|
1018
|
-
this.
|
|
1034
|
+
this.emitPublishFailureTelemetry(event.kind, "publish");
|
|
1019
1035
|
}
|
|
1020
1036
|
}
|
|
1021
1037
|
|
|
1022
|
-
private
|
|
1023
|
-
eventName: "push_event_published" | "push_event_failed",
|
|
1038
|
+
private emitPublishFailureTelemetry(
|
|
1024
1039
|
kind: string,
|
|
1025
|
-
stage:
|
|
1040
|
+
stage: "capture" | "publish",
|
|
1026
1041
|
): void {
|
|
1027
1042
|
void emitCloudTelemetry(
|
|
1028
1043
|
this.telemetryClient,
|
|
1029
1044
|
{
|
|
1030
|
-
eventName,
|
|
1045
|
+
eventName: "push_event_failed",
|
|
1031
1046
|
source: "watcher",
|
|
1032
1047
|
properties: {
|
|
1033
1048
|
kind,
|
|
1034
1049
|
count: 1,
|
|
1035
|
-
|
|
1050
|
+
status: "failure",
|
|
1036
1051
|
stage,
|
|
1037
1052
|
},
|
|
1038
1053
|
},
|