@indigoai-us/hq-cloud 6.18.92 → 6.18.93
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 +6 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +126 -4
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-telemetry.d.ts +9 -3
- package/dist/bin/sync-runner-telemetry.d.ts.map +1 -1
- package/dist/bin/sync-runner-telemetry.js +7 -1
- package/dist/bin/sync-runner-telemetry.js.map +1 -1
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +177 -7
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +18 -3
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js +103 -34
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/cli/reindex.d.ts.map +1 -1
- package/dist/cli/reindex.js +26 -0
- package/dist/cli/reindex.js.map +1 -1
- package/dist/cli/sync.d.ts +17 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +33 -1
- package/dist/cli/sync.js.map +1 -1
- package/dist/ignore.d.ts +7 -0
- package/dist/ignore.d.ts.map +1 -1
- package/dist/ignore.js +17 -0
- package/dist/ignore.js.map +1 -1
- package/dist/lib/machine-id.d.ts.map +1 -1
- package/dist/lib/machine-id.js +16 -0
- package/dist/lib/machine-id.js.map +1 -1
- package/dist/personal-vault.d.ts +22 -5
- package/dist/personal-vault.d.ts.map +1 -1
- package/dist/personal-vault.js +60 -8
- package/dist/personal-vault.js.map +1 -1
- package/dist/skill-telemetry.d.ts +5 -0
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +20 -1
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/sync/state-store.d.ts +20 -1
- package/dist/sync/state-store.d.ts.map +1 -1
- package/dist/sync/state-store.js +36 -5
- package/dist/sync/state-store.js.map +1 -1
- package/dist/telemetry-admission.d.ts +106 -0
- package/dist/telemetry-admission.d.ts.map +1 -0
- package/dist/telemetry-admission.js +142 -0
- package/dist/telemetry-admission.js.map +1 -0
- package/dist/telemetry-scan-cap.d.ts +10 -0
- package/dist/telemetry-scan-cap.d.ts.map +1 -0
- package/dist/telemetry-scan-cap.js +23 -0
- package/dist/telemetry-scan-cap.js.map +1 -0
- package/dist/telemetry.d.ts +6 -0
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +14 -1
- package/dist/telemetry.js.map +1 -1
- package/package.json +1 -1
|
@@ -3757,6 +3757,38 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
3757
3757
|
}
|
|
3758
3758
|
}
|
|
3759
3759
|
const routePushFailureBackoff = new RoutePushFailureBackoff();
|
|
3760
|
+
/**
|
|
3761
|
+
* Same 5 s doubling to 5 min schedule, for a route's scoped (watcher) pull.
|
|
3762
|
+
* Kept apart from the push state so a failing pull never holds an upload
|
|
3763
|
+
* and a failing push never resets the pull's step. Retry timers use the
|
|
3764
|
+
* `pull:<route>` key in the shared scheduler.
|
|
3765
|
+
*/
|
|
3766
|
+
const routePullFailureBackoff = new RoutePushFailureBackoff();
|
|
3767
|
+
/**
|
|
3768
|
+
* Routes with a scoped pull running now. Coordinator watcher lanes and the
|
|
3769
|
+
* pending-batch drain can run concurrently; without this, both could pass
|
|
3770
|
+
* the backoff check before either pull failed and run the same route's pull
|
|
3771
|
+
* twice.
|
|
3772
|
+
*/
|
|
3773
|
+
const scopedPullsInFlight = new Set();
|
|
3774
|
+
/**
|
|
3775
|
+
* Pending watcher paths whose push already succeeded and that wait only
|
|
3776
|
+
* for their route's scoped pull (backing off, running, or deferred by
|
|
3777
|
+
* admission). Later drains skip the push for them, so a route whose pull
|
|
3778
|
+
* keeps failing does not re-run a no-op push pass on every wake. A fresh
|
|
3779
|
+
* watcher event for a path removes it, so a new edit is still pushed.
|
|
3780
|
+
*/
|
|
3781
|
+
const pullOnlyWatcherPaths = new Set();
|
|
3782
|
+
/**
|
|
3783
|
+
* One set per running scoped drain, holding paths that got a fresh watcher
|
|
3784
|
+
* event while that drain ran. Each set lives only as long as its drain.
|
|
3785
|
+
*/
|
|
3786
|
+
const activeDrainFreshPaths = new Set();
|
|
3787
|
+
const noteFreshWatcherEvent = (absolutePath) => {
|
|
3788
|
+
pullOnlyWatcherPaths.delete(absolutePath);
|
|
3789
|
+
for (const freshPaths of activeDrainFreshPaths)
|
|
3790
|
+
freshPaths.add(absolutePath);
|
|
3791
|
+
};
|
|
3760
3792
|
const pushRetryScheduler = deps.pushRetryScheduler ?? systemPushRetryScheduler;
|
|
3761
3793
|
const routePushRetryTimerHandles = new Map();
|
|
3762
3794
|
const clearRoutePushRetry = (route) => {
|
|
@@ -4612,7 +4644,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4612
4644
|
return false;
|
|
4613
4645
|
};
|
|
4614
4646
|
let emitScopedPushFailureTelemetry = null;
|
|
4615
|
-
const
|
|
4647
|
+
const runScopedDrainOnce = async (freshDuringThisDrain, batch, watcherBatchGeneration, _wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
|
|
4616
4648
|
if (batch) {
|
|
4617
4649
|
// Read each routed quarantine shard once for this watcher batch. Filter
|
|
4618
4650
|
// before resolving delete snapshots or submitting work to the coordinator
|
|
@@ -4694,6 +4726,20 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4694
4726
|
emitScopedPushFailureTelemetry?.(event);
|
|
4695
4727
|
}
|
|
4696
4728
|
};
|
|
4729
|
+
/**
|
|
4730
|
+
* Routes whose push did not run in this drain because the route's failure
|
|
4731
|
+
* backoff held it. Their paths are back in the pending batch with a retry
|
|
4732
|
+
* scheduled, so the paired scoped pull for the
|
|
4733
|
+
* same paths waits for that retry. Without this, every wake of the loop
|
|
4734
|
+
* (a receiver pull for any company, a retry timer, another watcher batch)
|
|
4735
|
+
* re-took the restored paths, skipped the held push and still ran the
|
|
4736
|
+
* pull leg. A route with a deterministic error then produced one errored
|
|
4737
|
+
* runner session and `company_sync_leg_completed` per wake, all day,
|
|
4738
|
+
* however long the push backoff was. A push that runs and fails still
|
|
4739
|
+
* gets its pull in the same drain, as before, so a pull that would clear
|
|
4740
|
+
* the failure is not starved: at most one pull per push attempt.
|
|
4741
|
+
*/
|
|
4742
|
+
const routesAwaitingPushRetry = new Set();
|
|
4697
4743
|
/**
|
|
4698
4744
|
* The push never ran: a chore held the slot past the admission deadline.
|
|
4699
4745
|
* Put the paths back and retry without counting a failure, so the route
|
|
@@ -4704,8 +4750,51 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4704
4750
|
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
4705
4751
|
scheduleRoutePushRetry(routeId, REALTIME_ADMISSION_RETRY_MS);
|
|
4706
4752
|
};
|
|
4753
|
+
/** Paths whose push succeeded (or needed nothing) in this drain. */
|
|
4754
|
+
const pushSettledThisDrain = new Set();
|
|
4755
|
+
/**
|
|
4756
|
+
* Keep a group's paths pending for its pull only. A path qualifies when
|
|
4757
|
+
* its push is already settled and no fresh watcher event arrived for it
|
|
4758
|
+
* since this drain started.
|
|
4759
|
+
*/
|
|
4760
|
+
const keepForPullOnly = (group) => {
|
|
4761
|
+
for (const absolutePath of group.paths.keys()) {
|
|
4762
|
+
if (freshDuringThisDrain.has(absolutePath))
|
|
4763
|
+
continue;
|
|
4764
|
+
if (!watcherDrainRunsPush ||
|
|
4765
|
+
pushSettledThisDrain.has(absolutePath) ||
|
|
4766
|
+
pullOnlyWatcherPaths.has(absolutePath)) {
|
|
4767
|
+
pullOnlyWatcherPaths.add(absolutePath);
|
|
4768
|
+
}
|
|
4769
|
+
}
|
|
4770
|
+
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
4771
|
+
};
|
|
4772
|
+
/** The part of a group that still needs a push, or null when none does. */
|
|
4773
|
+
const withoutPullOnlyPaths = (group) => {
|
|
4774
|
+
const pushPaths = [...group.paths.entries()].filter(([absolutePath]) => !pullOnlyWatcherPaths.has(absolutePath));
|
|
4775
|
+
if (pushPaths.length === group.paths.size)
|
|
4776
|
+
return group;
|
|
4777
|
+
if (pushPaths.length === 0)
|
|
4778
|
+
return null;
|
|
4779
|
+
const paths = new Map(pushPaths);
|
|
4780
|
+
const changes = new Map([...group.changes.entries()].filter(([absolutePath]) => paths.has(absolutePath)));
|
|
4781
|
+
return {
|
|
4782
|
+
route: group.route,
|
|
4783
|
+
paths,
|
|
4784
|
+
changes,
|
|
4785
|
+
relPaths: [...new Set(paths.values())],
|
|
4786
|
+
deleteScopeRoots: [
|
|
4787
|
+
...new Set([...changes.values()].flatMap((item) => (item.deleteSnapshots ?? []).flatMap((snapshot) => snapshot.deleteScopeRoot === undefined ? [] : [snapshot.deleteScopeRoot]))),
|
|
4788
|
+
],
|
|
4789
|
+
};
|
|
4790
|
+
};
|
|
4707
4791
|
if (watcherDrainRunsPush && grouped) {
|
|
4708
|
-
for (const
|
|
4792
|
+
for (const routeGroup of grouped.values()) {
|
|
4793
|
+
// Paths already pushed that wait only for their pull skip the push;
|
|
4794
|
+
// the pull half below still covers them.
|
|
4795
|
+
const group = withoutPullOnlyPaths(routeGroup);
|
|
4796
|
+
if (group === null)
|
|
4797
|
+
continue;
|
|
4709
4798
|
// Paused workspaces: drop the scoped push without uploading or
|
|
4710
4799
|
// publishing event-sync metadata (path/hash must not leak while Off).
|
|
4711
4800
|
if (isPausedRoute(group.route)) {
|
|
@@ -4716,6 +4805,7 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4716
4805
|
if (retryState && retryState.retryAt > monotonicNow()) {
|
|
4717
4806
|
scheduleRoutePushRetry(routeId, retryState.retryAt - monotonicNow());
|
|
4718
4807
|
restorePendingWatcherPaths(group.paths, watcherBatchGeneration, group.changes);
|
|
4808
|
+
routesAwaitingPushRetry.add(routeId);
|
|
4719
4809
|
continue;
|
|
4720
4810
|
}
|
|
4721
4811
|
clearRoutePushRetry(routeId);
|
|
@@ -4824,6 +4914,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4824
4914
|
if (execution.accepted && pushExit === 0) {
|
|
4825
4915
|
routePushFailureBackoff.succeeded(routeId);
|
|
4826
4916
|
clearRoutePushRetry(routeId);
|
|
4917
|
+
for (const absolutePath of group.paths.keys())
|
|
4918
|
+
pushSettledThisDrain.add(absolutePath);
|
|
4827
4919
|
const pushPathResults = typeof result === "number" ? [] : (result.result?.pushPathResults ?? []);
|
|
4828
4920
|
const refusedPaths = new Set(pushPathResults
|
|
4829
4921
|
.filter((pathResult) => pathResult.status === "refused")
|
|
@@ -4883,6 +4975,8 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4883
4975
|
}
|
|
4884
4976
|
else if (pushExit === UNRESOLVED_COMPANY_EXIT) {
|
|
4885
4977
|
acknowledgeDurableWatcherUploads(group.relPaths);
|
|
4978
|
+
for (const absolutePath of group.paths.keys())
|
|
4979
|
+
pushSettledThisDrain.add(absolutePath);
|
|
4886
4980
|
routePushFailureBackoff.succeeded(routeId);
|
|
4887
4981
|
clearRoutePushRetry(routeId);
|
|
4888
4982
|
if (group.route.kind === "company") {
|
|
@@ -4928,6 +5022,39 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4928
5022
|
// Paused workspaces sync in NO direction while Off.
|
|
4929
5023
|
if (isPausedRoute(group.route))
|
|
4930
5024
|
continue;
|
|
5025
|
+
// The push for these paths is pending a scheduled retry; the pull
|
|
5026
|
+
// runs with it then. Report the drain as deferred, not failed, so
|
|
5027
|
+
// callers neither re-queue the paths again nor wake the loop early.
|
|
5028
|
+
if (routesAwaitingPushRetry.has(routeKey(group.route))) {
|
|
5029
|
+
// The push half restored the paths it held. Paths that were
|
|
5030
|
+
// waiting only for this pull were not part of that push; keep them.
|
|
5031
|
+
keepForPullOnly(group);
|
|
5032
|
+
deferred = true;
|
|
5033
|
+
continue;
|
|
5034
|
+
}
|
|
5035
|
+
// A failed scoped pull backs off per route on the same schedule as
|
|
5036
|
+
// the push. Until its retry is due, keep the paths pending and skip
|
|
5037
|
+
// the pull. Before this, a failed pull made the callers restore the
|
|
5038
|
+
// paths and wake the loop at once, so a route whose pull always
|
|
5039
|
+
// failed re-ran it every few seconds for as long as the machine was
|
|
5040
|
+
// awake (usage-only-pricing US-001).
|
|
5041
|
+
const pullRouteId = routeKey(group.route);
|
|
5042
|
+
const pullRetryKey = `pull:${pullRouteId}`;
|
|
5043
|
+
const pullRetryState = routePullFailureBackoff.state(pullRouteId);
|
|
5044
|
+
if (pullRetryState && pullRetryState.retryAt > monotonicNow()) {
|
|
5045
|
+
keepForPullOnly(group);
|
|
5046
|
+
scheduleRoutePushRetry(pullRetryKey, pullRetryState.retryAt - monotonicNow());
|
|
5047
|
+
deferred = true;
|
|
5048
|
+
continue;
|
|
5049
|
+
}
|
|
5050
|
+
if (scopedPullsInFlight.has(pullRouteId)) {
|
|
5051
|
+
// The running pull decides the backoff. Keep these paths and look
|
|
5052
|
+
// again after the admission retry interval.
|
|
5053
|
+
keepForPullOnly(group);
|
|
5054
|
+
scheduleRoutePushRetry(pullRetryKey, REALTIME_ADMISSION_RETRY_MS);
|
|
5055
|
+
deferred = true;
|
|
5056
|
+
continue;
|
|
5057
|
+
}
|
|
4931
5058
|
const exactFileUpserts = [...group.paths.entries()].flatMap(([absolutePath, relativePath]) => {
|
|
4932
5059
|
const kind = group.changes.get(absolutePath)?.kind;
|
|
4933
5060
|
// Only create/change events are file upserts. Keep unlinks on the
|
|
@@ -4948,26 +5075,50 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4948
5075
|
const lane = isFastLocalBatch(group.paths.keys(), group.changes)
|
|
4949
5076
|
? "fast"
|
|
4950
5077
|
: "slow";
|
|
4951
|
-
|
|
4952
|
-
|
|
5078
|
+
scopedPullsInFlight.add(pullRouteId);
|
|
5079
|
+
const execution = await (parallelCoordinatorEnabled
|
|
5080
|
+
? runRealtimePass("watcher", "pull", group.relPaths, lane, (slice, telemetry, coordinatorFenced) => {
|
|
4953
5081
|
const slicePaths = slice.paths.map((path) => path.canonicalPath);
|
|
4954
5082
|
return runGuarded(buildTargetedPullArgv(group.route, passArgv, slicePaths, exactFileUpserts.filter((relativePath) => slicePaths.includes(relativePath))), undefined, lane, "realtime", false, slice.signal, false, telemetry, coordinatorFenced, undefined, routeKey(group.route), undefined, coordinatorFenced === true);
|
|
4955
5083
|
})
|
|
4956
|
-
:
|
|
5084
|
+
: runGuarded(pullArgv, undefined, lane, "realtime").then((result) => ({ result, accepted: true }), (error) => {
|
|
4957
5085
|
if (error instanceof RealtimeAdmissionTimeoutError) {
|
|
4958
5086
|
return { accepted: false, admissionTimeout: error };
|
|
4959
5087
|
}
|
|
4960
5088
|
throw error;
|
|
4961
|
-
})
|
|
5089
|
+
})).finally(() => {
|
|
5090
|
+
scopedPullsInFlight.delete(pullRouteId);
|
|
5091
|
+
});
|
|
4962
5092
|
if ("admissionTimeout" in execution && execution.admissionTimeout) {
|
|
4963
5093
|
// The pull never ran. Keep its paths and retry, without the
|
|
4964
5094
|
// pass-failure handling the shared exit code would get.
|
|
4965
|
-
|
|
5095
|
+
keepForPullOnly(group);
|
|
4966
5096
|
scheduleRoutePushRetry(`pull:${routeKey(group.route)}`, REALTIME_ADMISSION_RETRY_MS);
|
|
4967
5097
|
deferred = true;
|
|
4968
5098
|
continue;
|
|
4969
5099
|
}
|
|
4970
5100
|
const exit = scopedRouteDrainExit(execution);
|
|
5101
|
+
if (exit === 0) {
|
|
5102
|
+
routePullFailureBackoff.succeeded(pullRouteId);
|
|
5103
|
+
clearRoutePushRetry(pullRetryKey);
|
|
5104
|
+
for (const absolutePath of group.paths.keys())
|
|
5105
|
+
pullOnlyWatcherPaths.delete(absolutePath);
|
|
5106
|
+
}
|
|
5107
|
+
else if (exit !== runtime.authRequiredPassExit) {
|
|
5108
|
+
// Auth-required keeps its exit so the loop can park for sign-in.
|
|
5109
|
+
// Every other failure keeps the paths, schedules the retry and
|
|
5110
|
+
// reports the drain deferred, so no caller re-queues the paths or
|
|
5111
|
+
// wakes the loop early.
|
|
5112
|
+
const state = routePullFailureBackoff.failed(pullRouteId, monotonicNow());
|
|
5113
|
+
keepForPullOnly(group);
|
|
5114
|
+
scheduleRoutePushRetry(pullRetryKey, state.delayMs);
|
|
5115
|
+
if (state.shouldReport) {
|
|
5116
|
+
(deps.stderr ?? process.stderr).write(`hq-sync-runner: scoped pull for ${pullRouteId} failed (exit=${exit}, ` +
|
|
5117
|
+
`consecutive=${state.consecutiveFailures}); retrying in ${state.delayMs}ms\n`);
|
|
5118
|
+
}
|
|
5119
|
+
deferred = true;
|
|
5120
|
+
continue;
|
|
5121
|
+
}
|
|
4971
5122
|
if (exit !== 0 && worstExit === 0)
|
|
4972
5123
|
worstExit = exit;
|
|
4973
5124
|
}
|
|
@@ -4979,6 +5130,16 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
4979
5130
|
// when a peer change arrives between those reconciles.
|
|
4980
5131
|
return 0;
|
|
4981
5132
|
};
|
|
5133
|
+
const runScopedDrain = async (batch, watcherBatchGeneration, wakeOnPushFailure = false, allowFastRealtimeOvertake = false) => {
|
|
5134
|
+
const freshDuringThisDrain = new Set();
|
|
5135
|
+
activeDrainFreshPaths.add(freshDuringThisDrain);
|
|
5136
|
+
try {
|
|
5137
|
+
return await runScopedDrainOnce(freshDuringThisDrain, batch, watcherBatchGeneration, wakeOnPushFailure, allowFastRealtimeOvertake);
|
|
5138
|
+
}
|
|
5139
|
+
finally {
|
|
5140
|
+
activeDrainFreshPaths.delete(freshDuringThisDrain);
|
|
5141
|
+
}
|
|
5142
|
+
};
|
|
4982
5143
|
/**
|
|
4983
5144
|
* Overflow drain: the watcher hit its backlog cap, so per-path detail is
|
|
4984
5145
|
* gone — but when every dropped path still carried route evidence
|
|
@@ -5219,6 +5380,15 @@ export async function runWatchLoop(argv, parsed, deps, runtime) {
|
|
|
5219
5380
|
catch {
|
|
5220
5381
|
/* diagnostics must never affect sync */
|
|
5221
5382
|
}
|
|
5383
|
+
// A fresh event means the path needs a push again, even if it was
|
|
5384
|
+
// waiting only for its route's pull.
|
|
5385
|
+
if (batch) {
|
|
5386
|
+
for (const absolutePath of batch.paths.keys())
|
|
5387
|
+
noteFreshWatcherEvent(absolutePath);
|
|
5388
|
+
}
|
|
5389
|
+
else if (changedRelPath) {
|
|
5390
|
+
noteFreshWatcherEvent(localPathForVaultKey(hqRoot, changedRelPath));
|
|
5391
|
+
}
|
|
5222
5392
|
// Peel a bounded realtime sub-batch from a mixed watcher batch while a
|
|
5223
5393
|
// bulk pass is in flight. The residue enters the ordinary pending drain
|
|
5224
5394
|
// immediately; a latest path appears in exactly one of these maps.
|