@canonry/canonry 4.158.0 → 4.159.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.
- package/README.md +2 -2
- package/THIRD_PARTY_NOTICES.md +102 -0
- package/assets/THIRD_PARTY_NOTICES.md +102 -0
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -0
- package/assets/assets/{AuditHistoryPanel-McQfjoU9.js → AuditHistoryPanel-BiloGcQA.js} +1 -1
- package/assets/assets/{BacklinksPage-Da8y1Wv8.js → BacklinksPage-CPM1O2bC.js} +1 -1
- package/assets/assets/{HistoryPage-9MJfhRyT.js → HistoryPage-1YOjtdJy.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BiNAHEJW.js → MeasurementPropertyPage-CVDTY_gC.js} +1 -1
- package/assets/assets/ProjectPage-DnGXMx1n.js +12 -0
- package/assets/assets/{RunRow-DCG2LBlS.js → RunRow-fIaadH4h.js} +1 -1
- package/assets/assets/{RunsPage-ByWA_FSk.js → RunsPage-DfifdM5z.js} +1 -1
- package/assets/assets/{SettingsPage-DdSeKAKF.js → SettingsPage-efA2EEmS.js} +1 -1
- package/assets/assets/SiteHealthSection-BROtQGfu.js +4 -0
- package/assets/assets/{TrafficPage-5u8rPcBt.js → TrafficPage-Wk43Bm_q.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-Dm-fK87u.js → TrafficSourceDetailPage-BhBvoLWu.js} +1 -1
- package/assets/assets/extract-error-message-YGzZFvWs.js +1 -0
- package/assets/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/assets/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/assets/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/assets/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/assets/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/assets/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/assets/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/assets/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/assets/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/assets/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/assets/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/assets/assets/index-CACRaOmZ.css +1 -0
- package/assets/assets/index-DmsIZQAm.js +83 -0
- package/assets/assets/{react-sigma_core.esm.min-ComIyDKi.js → react-sigma_core.esm.min-DM9XptbM.js} +2 -2
- package/assets/assets/{vendor-lucide-BtYdosYk.js → vendor-lucide-nYKv-BB7.js} +1 -1
- package/assets/assets/{vendor-markdown-D274_wsI.js → vendor-markdown-Bn7fRwrx.js} +1 -1
- package/assets/assets/{vendor-radix-BxMXSwbC.js → vendor-radix-4UOaVWza.js} +1 -1
- package/assets/assets/{vendor-recharts-C4ktw_aE.js → vendor-recharts-D8rt5lKn.js} +1 -1
- package/assets/assets/{vendor-tanstack-pFxw0eGP.js → vendor-tanstack-Dock0eH1.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{chunk-YP4K2KXH.js → chunk-5AFWHKKI.js} +776 -32
- package/dist/{chunk-4VZP64GZ.js → chunk-BZPBVOAM.js} +625 -262
- package/dist/{chunk-LK6YFHG4.js → chunk-ZEG43TY2.js} +45 -15
- package/dist/{chunk-DEVAEC2S.js → chunk-ZL3VY435.js} +213 -8
- package/dist/cli.js +42 -4
- package/dist/index.d.ts +9 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-LQ6ZRWMF.js → intelligence-service-DHY5CYWX.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +11 -10
- package/assets/assets/ProjectPage-mWSMjXrR.js +0 -15
- package/assets/assets/extract-error-message-CtxkAd6E.js +0 -1
- package/assets/assets/index-DUfg50Pl.js +0 -83
- package/assets/assets/index-cXb3mfeQ.css +0 -1
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZEG43TY2.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -117,6 +117,7 @@ import {
|
|
|
117
117
|
researchRunQueries,
|
|
118
118
|
researchRuns,
|
|
119
119
|
resolveDestination,
|
|
120
|
+
resolveMeasurementSitemapTarget,
|
|
120
121
|
resolveTrustProxy,
|
|
121
122
|
resolveVercelSyncDeadlineMs,
|
|
122
123
|
resolveWebhookTarget,
|
|
@@ -136,7 +137,7 @@ import {
|
|
|
136
137
|
siteCrawlSnapshots,
|
|
137
138
|
toAlertView,
|
|
138
139
|
usageCounters
|
|
139
|
-
} from "./chunk-
|
|
140
|
+
} from "./chunk-5AFWHKKI.js";
|
|
140
141
|
import {
|
|
141
142
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
142
143
|
AGENT_PROVIDER_IDS,
|
|
@@ -169,7 +170,9 @@ import {
|
|
|
169
170
|
SITE_CRAWL_GRAPH_MAX_NODES,
|
|
170
171
|
SKILL_MANIFEST_FILENAME,
|
|
171
172
|
SchedulableRunKinds,
|
|
173
|
+
SiteHealthTemplateDetections,
|
|
172
174
|
SkillsClients,
|
|
175
|
+
TEMPLATE_LINK_RATIO_THRESHOLD,
|
|
173
176
|
TrafficEventConfidences,
|
|
174
177
|
TrafficEventKinds,
|
|
175
178
|
TrafficEvidenceKinds,
|
|
@@ -192,8 +195,10 @@ import {
|
|
|
192
195
|
computeBacklinkSummaryMetrics,
|
|
193
196
|
contentActionLabel,
|
|
194
197
|
contentBriefDtoSchema,
|
|
198
|
+
createTemplateLinkPairIndex,
|
|
195
199
|
deriveCitedUrlCandidates,
|
|
196
200
|
deriveIndexCoverage,
|
|
201
|
+
deriveSiteHealthState,
|
|
197
202
|
determineAnswerMentioned,
|
|
198
203
|
dollarsToMicros,
|
|
199
204
|
effectiveBrandNames,
|
|
@@ -211,6 +216,8 @@ import {
|
|
|
211
216
|
isBrowserProvider,
|
|
212
217
|
isGhostTelemetryEvent,
|
|
213
218
|
isRetryableHttpError,
|
|
219
|
+
isSelfLink,
|
|
220
|
+
isTemplateLinkRatio,
|
|
214
221
|
isVertexGroundingRedirect,
|
|
215
222
|
mapWithConcurrency,
|
|
216
223
|
normalizeIdTokens,
|
|
@@ -219,6 +226,7 @@ import {
|
|
|
219
226
|
normalizeSiteAuditRunRequest,
|
|
220
227
|
normalizeUrlPath,
|
|
221
228
|
notFound,
|
|
229
|
+
observeTemplateLinkEdges,
|
|
222
230
|
parseMeasurementRunManifestV1,
|
|
223
231
|
parseOriginList,
|
|
224
232
|
providerError,
|
|
@@ -231,12 +239,14 @@ import {
|
|
|
231
239
|
skillsClientSchema,
|
|
232
240
|
splitList,
|
|
233
241
|
startOfDayHourInTimeZone,
|
|
242
|
+
templateLinkDetection,
|
|
243
|
+
templateLinkRatio,
|
|
234
244
|
textContainsBrandAlias,
|
|
235
245
|
textContainsDomain,
|
|
236
246
|
validationError,
|
|
237
247
|
winnabilityClassLabel,
|
|
238
248
|
withRetry
|
|
239
|
-
} from "./chunk-
|
|
249
|
+
} from "./chunk-ZL3VY435.js";
|
|
240
250
|
|
|
241
251
|
// src/telemetry.ts
|
|
242
252
|
import crypto from "crypto";
|
|
@@ -571,7 +581,7 @@ import crypto25 from "crypto";
|
|
|
571
581
|
import fs8 from "fs";
|
|
572
582
|
import path9 from "path";
|
|
573
583
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
574
|
-
import { and as
|
|
584
|
+
import { and as and18, eq as eq24 } from "drizzle-orm";
|
|
575
585
|
import Fastify from "fastify";
|
|
576
586
|
import os5 from "os";
|
|
577
587
|
|
|
@@ -4169,12 +4179,12 @@ var JobRunner = class {
|
|
|
4169
4179
|
error: errorMessage
|
|
4170
4180
|
}).where(eq2(runs.id, runId)).run();
|
|
4171
4181
|
this.flushProviderUsage(providerDispatchCounts, providerReservations);
|
|
4172
|
-
const
|
|
4182
|
+
const abortReason2 = classifyRunAbortReason(errorMessage);
|
|
4173
4183
|
const phases = buildPhases({ startTime, providerCallStart, providerCallEnd });
|
|
4174
|
-
if (
|
|
4184
|
+
if (abortReason2) {
|
|
4175
4185
|
const domainHash = hashDomain(executionContext.canonicalDomain ?? null);
|
|
4176
4186
|
trackEvent("run.aborted", {
|
|
4177
|
-
reason:
|
|
4187
|
+
reason: abortReason2,
|
|
4178
4188
|
providerCount: executionContext.providerCount,
|
|
4179
4189
|
providers: executionContext.providers,
|
|
4180
4190
|
queryCount: executionContext.queryCount,
|
|
@@ -6995,9 +7005,193 @@ function buildDiscoveryInsightTitle(input) {
|
|
|
6995
7005
|
|
|
6996
7006
|
// src/execute-site-audit.ts
|
|
6997
7007
|
import crypto17 from "crypto";
|
|
6998
|
-
import { and as
|
|
7008
|
+
import { and as and12, eq as eq15, sql as sql6 } from "drizzle-orm";
|
|
6999
7009
|
import { runSiteCrawl } from "@canonry/aeo-audit";
|
|
7000
7010
|
|
|
7011
|
+
// src/site-audit-root.ts
|
|
7012
|
+
import http from "http";
|
|
7013
|
+
import https from "https";
|
|
7014
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
7015
|
+
var MAX_REDIRECTS = 5;
|
|
7016
|
+
var USER_AGENT = "Canonry/1.0 (site-health)";
|
|
7017
|
+
async function resolveSiteAuditRootUrl(rawUrl, options) {
|
|
7018
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
7019
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("Site audit root timeout must be positive");
|
|
7020
|
+
const requested = parseHttpUrl2(rawUrl);
|
|
7021
|
+
let current = requested;
|
|
7022
|
+
const redirects = [];
|
|
7023
|
+
const deadlineAt = Date.now() + timeoutMs;
|
|
7024
|
+
const transport = options.transport ?? requestPinnedSiteAuditRoot;
|
|
7025
|
+
for (; ; ) {
|
|
7026
|
+
throwIfAborted(options.signal);
|
|
7027
|
+
const checked = await beforeDeadline(
|
|
7028
|
+
() => options.resolveTarget(current.href),
|
|
7029
|
+
deadlineAt,
|
|
7030
|
+
timeoutMs,
|
|
7031
|
+
options.signal
|
|
7032
|
+
);
|
|
7033
|
+
if (!checked.ok) throw new Error(`Site audit root URL rejected: ${checked.message}`);
|
|
7034
|
+
const checkedUrl = parseHttpUrl2(checked.target.url.href);
|
|
7035
|
+
if (checkedUrl.href !== current.href) {
|
|
7036
|
+
throw new Error("Site audit root target resolver returned a different URL than it validated");
|
|
7037
|
+
}
|
|
7038
|
+
const response = await beforeDeadline(
|
|
7039
|
+
() => transport(checked.target, { timeoutMs: remainingTime(deadlineAt, timeoutMs), signal: options.signal }),
|
|
7040
|
+
deadlineAt,
|
|
7041
|
+
timeoutMs,
|
|
7042
|
+
options.signal
|
|
7043
|
+
);
|
|
7044
|
+
if (!isRedirect(response.status)) {
|
|
7045
|
+
return { requestedUrl: requested.href, effectiveUrl: current.href, redirects };
|
|
7046
|
+
}
|
|
7047
|
+
if (redirects.length >= MAX_REDIRECTS) {
|
|
7048
|
+
throw new Error(`Site audit root has more than ${MAX_REDIRECTS} redirects`);
|
|
7049
|
+
}
|
|
7050
|
+
const location = firstHeader(response.headers.location);
|
|
7051
|
+
if (!location) throw new Error("Site audit root redirect response has no Location header");
|
|
7052
|
+
let next;
|
|
7053
|
+
try {
|
|
7054
|
+
next = parseHttpUrl2(new URL(location, current).href);
|
|
7055
|
+
} catch {
|
|
7056
|
+
throw new Error("Site audit root redirect has an invalid Location header");
|
|
7057
|
+
}
|
|
7058
|
+
assertAllowedRedirect(current, next);
|
|
7059
|
+
redirects.push({ status: response.status, from: current.href, to: next.href });
|
|
7060
|
+
current = next;
|
|
7061
|
+
}
|
|
7062
|
+
}
|
|
7063
|
+
function parseHttpUrl2(rawUrl) {
|
|
7064
|
+
let url;
|
|
7065
|
+
try {
|
|
7066
|
+
url = new URL(rawUrl);
|
|
7067
|
+
} catch {
|
|
7068
|
+
throw new Error("Site audit root must be a valid URL");
|
|
7069
|
+
}
|
|
7070
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
7071
|
+
throw new Error("Site audit root must use HTTP or HTTPS");
|
|
7072
|
+
}
|
|
7073
|
+
if (url.username || url.password) throw new Error("Site audit root must not include credentials");
|
|
7074
|
+
url.hash = "";
|
|
7075
|
+
return url;
|
|
7076
|
+
}
|
|
7077
|
+
function canonicalHostAlias(hostname) {
|
|
7078
|
+
return hostname.toLowerCase().replace(/\.$/, "").replace(/^www\./, "");
|
|
7079
|
+
}
|
|
7080
|
+
function assertAllowedRedirect(current, next) {
|
|
7081
|
+
if (current.protocol === "https:" && next.protocol === "http:") {
|
|
7082
|
+
throw new Error("Site audit root redirect would downgrade HTTPS to HTTP");
|
|
7083
|
+
}
|
|
7084
|
+
if (current.port !== next.port) throw new Error("Site audit root redirect changes port");
|
|
7085
|
+
if (canonicalHostAlias(current.hostname) !== canonicalHostAlias(next.hostname)) {
|
|
7086
|
+
throw new Error("Site audit root redirect points outside the approved site");
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
7089
|
+
function isRedirect(status) {
|
|
7090
|
+
return status >= 300 && status < 400;
|
|
7091
|
+
}
|
|
7092
|
+
function firstHeader(value) {
|
|
7093
|
+
return Array.isArray(value) ? value[0] : value;
|
|
7094
|
+
}
|
|
7095
|
+
function remainingTime(deadlineAt, timeoutMs) {
|
|
7096
|
+
const remaining = deadlineAt - Date.now();
|
|
7097
|
+
if (remaining <= 0) throw new Error(`Site audit root resolution exceeded its ${timeoutMs}ms deadline`);
|
|
7098
|
+
return remaining;
|
|
7099
|
+
}
|
|
7100
|
+
async function beforeDeadline(operation, deadlineAt, timeoutMs, signal) {
|
|
7101
|
+
throwIfAborted(signal);
|
|
7102
|
+
const remaining = remainingTime(deadlineAt, timeoutMs);
|
|
7103
|
+
let timer;
|
|
7104
|
+
let abortListener;
|
|
7105
|
+
try {
|
|
7106
|
+
const value = await Promise.race([
|
|
7107
|
+
operation(),
|
|
7108
|
+
new Promise((_resolve, reject) => {
|
|
7109
|
+
timer = setTimeout(
|
|
7110
|
+
() => reject(new Error(`Site audit root resolution exceeded its ${timeoutMs}ms deadline`)),
|
|
7111
|
+
remaining
|
|
7112
|
+
);
|
|
7113
|
+
}),
|
|
7114
|
+
new Promise((_resolve, reject) => {
|
|
7115
|
+
if (!signal) return;
|
|
7116
|
+
abortListener = () => reject(abortReason(signal));
|
|
7117
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
7118
|
+
if (signal.aborted) abortListener();
|
|
7119
|
+
})
|
|
7120
|
+
]);
|
|
7121
|
+
remainingTime(deadlineAt, timeoutMs);
|
|
7122
|
+
return value;
|
|
7123
|
+
} finally {
|
|
7124
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7125
|
+
if (abortListener) signal?.removeEventListener("abort", abortListener);
|
|
7126
|
+
}
|
|
7127
|
+
}
|
|
7128
|
+
function abortReason(signal) {
|
|
7129
|
+
return signal.reason instanceof Error ? signal.reason : new DOMException("Operation aborted", "AbortError");
|
|
7130
|
+
}
|
|
7131
|
+
function throwIfAborted(signal) {
|
|
7132
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
7133
|
+
}
|
|
7134
|
+
function stripIpv6Brackets(hostname) {
|
|
7135
|
+
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
7136
|
+
}
|
|
7137
|
+
function buildSiteAuditRootRequestOptions(target) {
|
|
7138
|
+
const secure = target.url.protocol === "https:";
|
|
7139
|
+
const requestOptions = {
|
|
7140
|
+
hostname: target.address,
|
|
7141
|
+
family: target.family,
|
|
7142
|
+
port: target.url.port ? Number(target.url.port) : secure ? 443 : 80,
|
|
7143
|
+
method: "GET",
|
|
7144
|
+
path: `${target.url.pathname}${target.url.search}`,
|
|
7145
|
+
headers: {
|
|
7146
|
+
Host: target.url.host,
|
|
7147
|
+
Accept: "text/html,*/*;q=0.1",
|
|
7148
|
+
"User-Agent": USER_AGENT
|
|
7149
|
+
}
|
|
7150
|
+
};
|
|
7151
|
+
if (secure) requestOptions.servername = stripIpv6Brackets(target.url.hostname);
|
|
7152
|
+
return requestOptions;
|
|
7153
|
+
}
|
|
7154
|
+
async function requestPinnedSiteAuditRoot(target, options) {
|
|
7155
|
+
throwIfAborted(options.signal);
|
|
7156
|
+
const secure = target.url.protocol === "https:";
|
|
7157
|
+
const requestOptions = buildSiteAuditRootRequestOptions(target);
|
|
7158
|
+
return await new Promise((resolve, reject) => {
|
|
7159
|
+
let settled = false;
|
|
7160
|
+
let abortListener;
|
|
7161
|
+
const cleanup = () => {
|
|
7162
|
+
clearTimeout(timer);
|
|
7163
|
+
if (abortListener) options.signal?.removeEventListener("abort", abortListener);
|
|
7164
|
+
};
|
|
7165
|
+
const succeed = (response) => {
|
|
7166
|
+
if (settled) return;
|
|
7167
|
+
settled = true;
|
|
7168
|
+
cleanup();
|
|
7169
|
+
resolve(response);
|
|
7170
|
+
};
|
|
7171
|
+
const fail = (error) => {
|
|
7172
|
+
if (settled) return;
|
|
7173
|
+
settled = true;
|
|
7174
|
+
cleanup();
|
|
7175
|
+
reject(error);
|
|
7176
|
+
};
|
|
7177
|
+
const request = (secure ? https.request : http.request)(requestOptions, (response) => {
|
|
7178
|
+
response.destroy();
|
|
7179
|
+
succeed({ status: response.statusCode ?? 0, headers: response.headers });
|
|
7180
|
+
});
|
|
7181
|
+
const timer = setTimeout(
|
|
7182
|
+
() => request.destroy(new Error(`Site audit root request timed out after ${options.timeoutMs}ms`)),
|
|
7183
|
+
options.timeoutMs
|
|
7184
|
+
);
|
|
7185
|
+
if (options.signal) {
|
|
7186
|
+
abortListener = () => request.destroy(abortReason(options.signal));
|
|
7187
|
+
options.signal.addEventListener("abort", abortListener, { once: true });
|
|
7188
|
+
if (options.signal.aborted) abortListener();
|
|
7189
|
+
}
|
|
7190
|
+
request.on("error", fail);
|
|
7191
|
+
request.end();
|
|
7192
|
+
});
|
|
7193
|
+
}
|
|
7194
|
+
|
|
7001
7195
|
// src/site-crawl-graph-layout.ts
|
|
7002
7196
|
import crypto16 from "crypto";
|
|
7003
7197
|
import { createRequire as createRequire3 } from "module";
|
|
@@ -7023,6 +7217,22 @@ var FORCE_ATLAS_SETTINGS = Object.freeze({
|
|
|
7023
7217
|
var RENDER_MAX_NODE_SIZE = 14;
|
|
7024
7218
|
var RENDER_HALF_EXTENT_PX = 420;
|
|
7025
7219
|
var NODE_SIZE_EXTENT_FRACTION = RENDER_MAX_NODE_SIZE / RENDER_HALF_EXTENT_PX;
|
|
7220
|
+
function siteCrawlGraphNodeSize(extent, nodeCount) {
|
|
7221
|
+
if (!Number.isFinite(extent) || extent <= 0 || nodeCount <= 0) return 0;
|
|
7222
|
+
const byExtent = extent * NODE_SIZE_EXTENT_FRACTION;
|
|
7223
|
+
const byArea = Math.sqrt(NOVERLAP_SETTINGS.maxPackingFraction * (2 * extent) ** 2 / nodeCount) / 2;
|
|
7224
|
+
return Math.min(byExtent, byArea);
|
|
7225
|
+
}
|
|
7226
|
+
function siteCrawlGraphExtent(positions) {
|
|
7227
|
+
if (positions.length === 0) return 0;
|
|
7228
|
+
const centerX = positions.reduce((sum, p) => sum + p.x, 0) / positions.length;
|
|
7229
|
+
const centerY = positions.reduce((sum, p) => sum + p.y, 0) / positions.length;
|
|
7230
|
+
let extent = 0;
|
|
7231
|
+
for (const position of positions) {
|
|
7232
|
+
extent = Math.max(extent, Math.abs(position.x - centerX), Math.abs(position.y - centerY));
|
|
7233
|
+
}
|
|
7234
|
+
return extent;
|
|
7235
|
+
}
|
|
7026
7236
|
var NOVERLAP_SETTINGS = Object.freeze({
|
|
7027
7237
|
/** Half a node of clear space between two touching nodes. */
|
|
7028
7238
|
marginFactor: 0.5,
|
|
@@ -7058,26 +7268,17 @@ function noverlapMaxIterations(nodeCount) {
|
|
|
7058
7268
|
}
|
|
7059
7269
|
return NOVERLAP_SETTINGS.iterationsFloor;
|
|
7060
7270
|
}
|
|
7061
|
-
var
|
|
7062
|
-
var OUTLIER_RING_OUTER_FACTOR = 1.75;
|
|
7063
|
-
var OUTLIER_RING_MAX_RINGS = 4;
|
|
7064
|
-
var OUTLIER_RING_TARGET_PER_RING = 16;
|
|
7065
|
-
var OUTLIER_RING_MIN_RADIUS = 1;
|
|
7271
|
+
var COMPONENT_PACKING_SPACING_FACTOR = 3;
|
|
7066
7272
|
function siteCrawlGraphLayoutSettingsFingerprint(settings) {
|
|
7067
7273
|
return crypto16.createHash("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 8);
|
|
7068
7274
|
}
|
|
7069
|
-
var SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM = "site-health-fa2-
|
|
7275
|
+
var SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM = "site-health-fa2-v4";
|
|
7070
7276
|
var SITE_CRAWL_GRAPH_LAYOUT_VERSION = `${SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM}-${siteCrawlGraphLayoutSettingsFingerprint({
|
|
7071
7277
|
forceAtlas: FORCE_ATLAS_SETTINGS,
|
|
7072
7278
|
noverlap: NOVERLAP_SETTINGS,
|
|
7073
7279
|
nodeSizeExtentFraction: NODE_SIZE_EXTENT_FRACTION,
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
outer: OUTLIER_RING_OUTER_FACTOR,
|
|
7077
|
-
maxRings: OUTLIER_RING_MAX_RINGS,
|
|
7078
|
-
targetPerRing: OUTLIER_RING_TARGET_PER_RING,
|
|
7079
|
-
minRadius: OUTLIER_RING_MIN_RADIUS
|
|
7080
|
-
}
|
|
7280
|
+
componentPackingSpacingFactor: COMPONENT_PACKING_SPACING_FACTOR,
|
|
7281
|
+
templateLinkRatioThreshold: TEMPLATE_LINK_RATIO_THRESHOLD
|
|
7081
7282
|
})}`;
|
|
7082
7283
|
var WORKER_SOURCE = String.raw`
|
|
7083
7284
|
const {parentPort, workerData} = require('node:worker_threads');
|
|
@@ -7103,6 +7304,7 @@ try {
|
|
|
7103
7304
|
settings: workerData.settings,
|
|
7104
7305
|
});
|
|
7105
7306
|
}
|
|
7307
|
+
let nodeSize = 0;
|
|
7106
7308
|
// Anti-overlap runs on the spatialization ForceAtlas2 just produced. Node
|
|
7107
7309
|
// size is uniform and derived from the graph's own extent, so the pass is
|
|
7108
7310
|
// scale invariant and stays deterministic for a fixed input.
|
|
@@ -7122,11 +7324,12 @@ try {
|
|
|
7122
7324
|
if (Number.isFinite(extent) && extent > 0) {
|
|
7123
7325
|
// Whichever bound is tighter: one render-sized node, or the largest disc
|
|
7124
7326
|
// that still leaves the graph mostly empty space at this node count.
|
|
7327
|
+
// Mirrors siteCrawlGraphNodeSize; a test asserts the two agree.
|
|
7125
7328
|
const byExtent = extent * workerData.nodeSizeExtentFraction;
|
|
7126
7329
|
const byArea = Math.sqrt(
|
|
7127
7330
|
(workerData.noverlap.maxPackingFraction * Math.pow(2 * extent, 2)) / graph.order,
|
|
7128
7331
|
) / 2;
|
|
7129
|
-
|
|
7332
|
+
nodeSize = Math.min(byExtent, byArea);
|
|
7130
7333
|
graph.forEachNode(function (nodeKey) {
|
|
7131
7334
|
graph.setNodeAttribute(nodeKey, 'size', nodeSize);
|
|
7132
7335
|
});
|
|
@@ -7146,7 +7349,7 @@ try {
|
|
|
7146
7349
|
graph.forEachNode((nodeKey, attributes) => {
|
|
7147
7350
|
positions.push({nodeKey, x: attributes.x, y: attributes.y});
|
|
7148
7351
|
});
|
|
7149
|
-
parentPort.postMessage({ok: true, positions});
|
|
7352
|
+
parentPort.postMessage({ok: true, positions, nodeSize});
|
|
7150
7353
|
} catch (error) {
|
|
7151
7354
|
parentPort.postMessage({
|
|
7152
7355
|
ok: false,
|
|
@@ -7204,7 +7407,8 @@ function adaptiveForceAtlasIterations(nodeCount, edgeCount) {
|
|
|
7204
7407
|
}
|
|
7205
7408
|
async function computeForceAtlasPositions(input, options = {}) {
|
|
7206
7409
|
if (input.nodes.length <= 1 || input.edges.length === 0) {
|
|
7207
|
-
|
|
7410
|
+
const positions = input.nodes.map(({ nodeKey, x, y }) => ({ nodeKey, x, y }));
|
|
7411
|
+
return { positions, nodeSize: siteCrawlGraphNodeSize(siteCrawlGraphExtent(positions), positions.length) };
|
|
7208
7412
|
}
|
|
7209
7413
|
const requireFromPackage = createRequire3(import.meta.url);
|
|
7210
7414
|
const worker = new Worker(WORKER_SOURCE, {
|
|
@@ -7233,7 +7437,7 @@ async function computeForceAtlasPositions(input, options = {}) {
|
|
|
7233
7437
|
options.signal?.removeEventListener("abort", onAbort);
|
|
7234
7438
|
void worker.terminate();
|
|
7235
7439
|
if (result.error) reject(result.error instanceof Error ? result.error : new Error("Graph layout failed with a non-Error reason", { cause: result.error }));
|
|
7236
|
-
else resolve(result.positions ?? []);
|
|
7440
|
+
else resolve({ positions: result.positions ?? [], nodeSize: result.nodeSize ?? 0 });
|
|
7237
7441
|
};
|
|
7238
7442
|
const onAbort = () => finish({ error: options.signal?.reason ?? new DOMException("Layout aborted", "AbortError") });
|
|
7239
7443
|
const timer = setTimeout(() => finish({ error: new GraphLayoutTimeoutError(`Graph layout exceeded ${timeoutMs}ms`) }), timeoutMs);
|
|
@@ -7241,7 +7445,7 @@ async function computeForceAtlasPositions(input, options = {}) {
|
|
|
7241
7445
|
if (options.signal?.aborted) return onAbort();
|
|
7242
7446
|
worker.once("message", (message) => {
|
|
7243
7447
|
if (!message.ok) finish({ error: new Error(message.error ?? "Graph layout worker failed") });
|
|
7244
|
-
else finish({ positions: message.positions });
|
|
7448
|
+
else finish({ positions: message.positions, nodeSize: message.nodeSize });
|
|
7245
7449
|
});
|
|
7246
7450
|
worker.once("error", (error) => finish({ error }));
|
|
7247
7451
|
worker.once("exit", (code) => {
|
|
@@ -7277,48 +7481,60 @@ function siteCrawlGraphComponents(nodeKeys, edges) {
|
|
|
7277
7481
|
if (group) group.push(key);
|
|
7278
7482
|
else members.set(root, [key]);
|
|
7279
7483
|
}
|
|
7280
|
-
|
|
7281
|
-
|
|
7282
|
-
|
|
7283
|
-
|
|
7284
|
-
|
|
7285
|
-
|
|
7286
|
-
const
|
|
7287
|
-
|
|
7288
|
-
|
|
7289
|
-
|
|
7290
|
-
const
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
7297
|
-
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
const
|
|
7301
|
-
|
|
7302
|
-
|
|
7303
|
-
)
|
|
7304
|
-
|
|
7305
|
-
|
|
7306
|
-
|
|
7307
|
-
|
|
7308
|
-
const
|
|
7309
|
-
|
|
7310
|
-
|
|
7311
|
-
|
|
7312
|
-
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7484
|
+
return [...members.values()].sort((left, right) => right.length - left.length || (left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0));
|
|
7485
|
+
}
|
|
7486
|
+
function describeComponent(nodeKeys, byKey) {
|
|
7487
|
+
let sumX = 0;
|
|
7488
|
+
let sumY = 0;
|
|
7489
|
+
for (const nodeKey of nodeKeys) {
|
|
7490
|
+
const position = byKey.get(nodeKey);
|
|
7491
|
+
sumX += position.x;
|
|
7492
|
+
sumY += position.y;
|
|
7493
|
+
}
|
|
7494
|
+
const centerX = sumX / nodeKeys.length;
|
|
7495
|
+
const centerY = sumY / nodeKeys.length;
|
|
7496
|
+
let radius = 0;
|
|
7497
|
+
for (const nodeKey of nodeKeys) {
|
|
7498
|
+
const position = byKey.get(nodeKey);
|
|
7499
|
+
radius = Math.max(radius, Math.hypot(position.x - centerX, position.y - centerY));
|
|
7500
|
+
}
|
|
7501
|
+
return { nodeKeys, centerX, centerY, radius };
|
|
7502
|
+
}
|
|
7503
|
+
function packSiteCrawlGraphComponents(positions, edges, nodeSpacing) {
|
|
7504
|
+
const components = siteCrawlGraphComponents(positions.map((position) => position.nodeKey), edges);
|
|
7505
|
+
if (components.length <= 1) return [...positions];
|
|
7506
|
+
const byKey = new Map(positions.map((position) => [position.nodeKey, position]));
|
|
7507
|
+
if (positions.some((position) => !Number.isFinite(position.x) || !Number.isFinite(position.y))) {
|
|
7508
|
+
return [...positions];
|
|
7509
|
+
}
|
|
7510
|
+
const described = components.map((nodeKeys) => describeComponent(nodeKeys, byKey));
|
|
7511
|
+
const [anchor, ...rest] = described;
|
|
7512
|
+
const spacing = Math.max(nodeSpacing, Number.EPSILON) * COMPONENT_PACKING_SPACING_FACTOR;
|
|
7513
|
+
const ordered = [...rest].sort((left, right) => right.radius - left.radius || (left.nodeKeys[0] < right.nodeKeys[0] ? -1 : left.nodeKeys[0] > right.nodeKeys[0] ? 1 : 0));
|
|
7514
|
+
const moved = /* @__PURE__ */ new Map();
|
|
7515
|
+
let ringRadius = anchor.radius + spacing;
|
|
7516
|
+
let ringPitch = 0;
|
|
7517
|
+
let placedOnRing = 0;
|
|
7518
|
+
let ringCapacity = 0;
|
|
7519
|
+
for (const component of ordered) {
|
|
7520
|
+
if (placedOnRing >= ringCapacity) {
|
|
7521
|
+
ringPitch = 2 * component.radius + spacing;
|
|
7522
|
+
ringRadius += ringPitch / 2 + (placedOnRing === 0 ? 0 : ringPitch / 2);
|
|
7523
|
+
ringCapacity = Math.max(1, Math.floor(2 * Math.PI * ringRadius / ringPitch));
|
|
7524
|
+
placedOnRing = 0;
|
|
7525
|
+
}
|
|
7526
|
+
const angle = placedOnRing / ringCapacity * Math.PI * 2;
|
|
7527
|
+
placedOnRing += 1;
|
|
7528
|
+
const targetX = anchor.centerX + Math.cos(angle) * ringRadius;
|
|
7529
|
+
const targetY = anchor.centerY + Math.sin(angle) * ringRadius;
|
|
7530
|
+
const shiftX = targetX - component.centerX;
|
|
7531
|
+
const shiftY = targetY - component.centerY;
|
|
7532
|
+
for (const nodeKey of component.nodeKeys) {
|
|
7533
|
+
const position = byKey.get(nodeKey);
|
|
7534
|
+
moved.set(nodeKey, { nodeKey, x: position.x + shiftX, y: position.y + shiftY });
|
|
7535
|
+
}
|
|
7536
|
+
}
|
|
7537
|
+
return positions.map((position) => moved.get(position.nodeKey) ?? position);
|
|
7322
7538
|
}
|
|
7323
7539
|
function normalizePositions(seededNodes, computed, rootNodeKey) {
|
|
7324
7540
|
const byKey = new Map(computed.map((position) => [position.nodeKey, position]));
|
|
@@ -7352,29 +7568,40 @@ async function layoutSiteCrawlGraphInput(input, options = {}) {
|
|
|
7352
7568
|
failureCode: "empty-crawl",
|
|
7353
7569
|
totalNodes: input.totalNodes,
|
|
7354
7570
|
totalEdges: input.totalEdges,
|
|
7571
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
7355
7572
|
nodeCount: 0,
|
|
7356
7573
|
edgeCount: 0,
|
|
7357
7574
|
nodes: [],
|
|
7358
7575
|
edges: []
|
|
7359
7576
|
};
|
|
7360
7577
|
}
|
|
7578
|
+
const layoutEdges = input.edges.filter((edge) => !edge.isTemplate);
|
|
7361
7579
|
const seeded = seedSiteCrawlGraphNodes(input.nodes, input.rootNodeKey, input.priorPositions);
|
|
7580
|
+
const timeoutMs = options.timeoutMs ?? SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS;
|
|
7581
|
+
const deadline = Date.now() + timeoutMs;
|
|
7582
|
+
const assertWithinBudget = () => {
|
|
7583
|
+
options.signal?.throwIfAborted();
|
|
7584
|
+
if (Date.now() > deadline) throw new GraphLayoutTimeoutError(`Graph layout exceeded ${timeoutMs}ms`);
|
|
7585
|
+
};
|
|
7362
7586
|
try {
|
|
7363
7587
|
const compute = options.computePositions ?? computeForceAtlasPositions;
|
|
7364
|
-
const positions = await compute({
|
|
7588
|
+
const { positions, nodeSize } = await compute({
|
|
7365
7589
|
nodes: seeded,
|
|
7366
|
-
edges:
|
|
7367
|
-
iterations: adaptiveForceAtlasIterations(input.nodes.length,
|
|
7368
|
-
}, { signal: options.signal, timeoutMs:
|
|
7369
|
-
|
|
7370
|
-
const framed =
|
|
7590
|
+
edges: layoutEdges,
|
|
7591
|
+
iterations: adaptiveForceAtlasIterations(input.nodes.length, layoutEdges.length)
|
|
7592
|
+
}, { signal: options.signal, timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
7593
|
+
assertWithinBudget();
|
|
7594
|
+
const framed = packSiteCrawlGraphComponents(positions, layoutEdges, nodeSize);
|
|
7595
|
+
assertWithinBudget();
|
|
7371
7596
|
return {
|
|
7372
7597
|
state: "ready",
|
|
7373
7598
|
layoutVersion: SITE_CRAWL_GRAPH_LAYOUT_VERSION,
|
|
7374
7599
|
totalNodes: input.totalNodes,
|
|
7375
7600
|
totalEdges: input.totalEdges,
|
|
7601
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
7376
7602
|
nodeCount: input.nodes.length,
|
|
7377
7603
|
edgeCount: input.edges.length,
|
|
7604
|
+
templateLinksExcluded: true,
|
|
7378
7605
|
nodes: normalizePositions(seeded, framed, input.rootNodeKey),
|
|
7379
7606
|
edges: input.edges.map((edge, sampleRank) => ({ ...edge, sampleRank }))
|
|
7380
7607
|
};
|
|
@@ -7386,6 +7613,7 @@ async function layoutSiteCrawlGraphInput(input, options = {}) {
|
|
|
7386
7613
|
failureCode: error instanceof GraphLayoutTimeoutError ? "layout-timeout" : "layout-error",
|
|
7387
7614
|
totalNodes: input.totalNodes,
|
|
7388
7615
|
totalEdges: input.totalEdges,
|
|
7616
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
7389
7617
|
nodeCount: 0,
|
|
7390
7618
|
edgeCount: 0,
|
|
7391
7619
|
nodes: [],
|
|
@@ -7422,6 +7650,7 @@ function loadPriorSiteCrawlGraphPositions(db, scope) {
|
|
|
7422
7650
|
async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
7423
7651
|
let totalNodes = 0;
|
|
7424
7652
|
let totalEdges = 0;
|
|
7653
|
+
let totalTemplateEdges = 0;
|
|
7425
7654
|
try {
|
|
7426
7655
|
const assertNotAborted = () => options.signal?.throwIfAborted();
|
|
7427
7656
|
assertNotAborted();
|
|
@@ -7439,7 +7668,7 @@ async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
|
7439
7668
|
];
|
|
7440
7669
|
totalNodes = db.select({ value: count() }).from(siteCrawlPages).where(and10(...pageScope)).get()?.value ?? 0;
|
|
7441
7670
|
assertNotAborted();
|
|
7442
|
-
|
|
7671
|
+
const countGraphCompatibleEdges = (...extra) => db.select({ value: count() }).from(siteCrawlEdges).innerJoin(graphSourcePage, and10(
|
|
7443
7672
|
eq13(graphSourcePage.projectId, scope.projectId),
|
|
7444
7673
|
eq13(graphSourcePage.runId, scope.runId),
|
|
7445
7674
|
eq13(graphSourcePage.attemptId, scope.attemptId),
|
|
@@ -7449,7 +7678,10 @@ async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
|
7449
7678
|
eq13(graphTargetPage.runId, scope.runId),
|
|
7450
7679
|
eq13(graphTargetPage.attemptId, scope.attemptId),
|
|
7451
7680
|
eq13(graphTargetPage.nodeKey, siteCrawlEdges.targetNodeKey)
|
|
7452
|
-
)).where(and10(...edgeScope)).get()?.value ?? 0;
|
|
7681
|
+
)).where(and10(...edgeScope, ...extra)).get()?.value ?? 0;
|
|
7682
|
+
totalEdges = countGraphCompatibleEdges();
|
|
7683
|
+
assertNotAborted();
|
|
7684
|
+
totalTemplateEdges = countGraphCompatibleEdges(eq13(siteCrawlEdges.isTemplate, true));
|
|
7453
7685
|
assertNotAborted();
|
|
7454
7686
|
const graphSeedColumns = {
|
|
7455
7687
|
nodeKey: siteCrawlPages.nodeKey,
|
|
@@ -7488,7 +7720,8 @@ async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
|
7488
7720
|
edge.source_node_key AS sourceNodeKey,
|
|
7489
7721
|
edge.target_node_key AS targetNodeKey,
|
|
7490
7722
|
edge.followable AS followable,
|
|
7491
|
-
edge.occurrences AS occurrences
|
|
7723
|
+
edge.occurrences AS occurrences,
|
|
7724
|
+
edge.is_template AS isTemplate
|
|
7492
7725
|
FROM site_crawl_edges AS edge
|
|
7493
7726
|
INNER JOIN selected AS source ON source.node_key = edge.source_node_key
|
|
7494
7727
|
INNER JOIN selected AS target ON target.node_key = edge.target_node_key
|
|
@@ -7504,8 +7737,9 @@ async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
|
7504
7737
|
return await layoutSiteCrawlGraphInput({
|
|
7505
7738
|
totalNodes,
|
|
7506
7739
|
totalEdges,
|
|
7740
|
+
totalTemplateEdges,
|
|
7507
7741
|
nodes,
|
|
7508
|
-
edges: edgeRows,
|
|
7742
|
+
edges: edgeRows.map((row) => ({ ...row, isTemplate: Boolean(row.isTemplate) })),
|
|
7509
7743
|
rootNodeKey,
|
|
7510
7744
|
priorPositions
|
|
7511
7745
|
}, options);
|
|
@@ -7517,6 +7751,7 @@ async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
|
7517
7751
|
failureCode: "layout-error",
|
|
7518
7752
|
totalNodes,
|
|
7519
7753
|
totalEdges,
|
|
7754
|
+
totalTemplateEdges,
|
|
7520
7755
|
nodeCount: 0,
|
|
7521
7756
|
edgeCount: 0,
|
|
7522
7757
|
nodes: [],
|
|
@@ -7541,8 +7776,10 @@ function persistSiteCrawlGraphLayout(db, scope, layout, now = (/* @__PURE__ */ n
|
|
|
7541
7776
|
failureCode: layout.state === "unavailable" ? layout.failureCode : null,
|
|
7542
7777
|
totalNodes: layout.totalNodes,
|
|
7543
7778
|
totalEdges: layout.totalEdges,
|
|
7779
|
+
totalTemplateEdges: layout.totalTemplateEdges,
|
|
7544
7780
|
nodeCount: layout.nodeCount,
|
|
7545
7781
|
edgeCount: layout.edgeCount,
|
|
7782
|
+
templateLinksExcluded: layout.state === "ready",
|
|
7546
7783
|
createdAt: now,
|
|
7547
7784
|
updatedAt: now
|
|
7548
7785
|
}).run();
|
|
@@ -7573,6 +7810,7 @@ function persistSiteCrawlGraphLayout(db, scope, layout, now = (/* @__PURE__ */ n
|
|
|
7573
7810
|
targetNodeKey: edge.targetNodeKey,
|
|
7574
7811
|
followable: edge.followable,
|
|
7575
7812
|
occurrences: edge.occurrences,
|
|
7813
|
+
isTemplate: edge.isTemplate,
|
|
7576
7814
|
createdAt: now
|
|
7577
7815
|
}))).run();
|
|
7578
7816
|
}
|
|
@@ -7586,6 +7824,70 @@ function deleteSiteCrawlGraphLayout(db, scope) {
|
|
|
7586
7824
|
)).run();
|
|
7587
7825
|
}
|
|
7588
7826
|
|
|
7827
|
+
// src/site-crawl-template-links.ts
|
|
7828
|
+
import { and as and11, asc as asc2, eq as eq14, gt, inArray as inArray4 } from "drizzle-orm";
|
|
7829
|
+
var TEMPLATE_LINK_READ_BATCH = 2e3;
|
|
7830
|
+
var TEMPLATE_LINK_WRITE_BATCH = 500;
|
|
7831
|
+
function* streamAttemptEdges(db, scope) {
|
|
7832
|
+
let after = "";
|
|
7833
|
+
for (; ; ) {
|
|
7834
|
+
const rows = db.select({
|
|
7835
|
+
edgeKey: siteCrawlEdges.edgeKey,
|
|
7836
|
+
sourceNodeKey: siteCrawlEdges.sourceNodeKey,
|
|
7837
|
+
targetNodeKey: siteCrawlEdges.targetNodeKey,
|
|
7838
|
+
anchors: siteCrawlEdges.anchors
|
|
7839
|
+
}).from(siteCrawlEdges).where(and11(
|
|
7840
|
+
eq14(siteCrawlEdges.projectId, scope.projectId),
|
|
7841
|
+
eq14(siteCrawlEdges.runId, scope.runId),
|
|
7842
|
+
eq14(siteCrawlEdges.attemptId, scope.attemptId),
|
|
7843
|
+
gt(siteCrawlEdges.edgeKey, after)
|
|
7844
|
+
)).orderBy(asc2(siteCrawlEdges.edgeKey)).limit(TEMPLATE_LINK_READ_BATCH).all();
|
|
7845
|
+
if (rows.length === 0) return;
|
|
7846
|
+
yield rows;
|
|
7847
|
+
after = rows[rows.length - 1].edgeKey;
|
|
7848
|
+
if (rows.length < TEMPLATE_LINK_READ_BATCH) return;
|
|
7849
|
+
}
|
|
7850
|
+
}
|
|
7851
|
+
function* chunked(values, size = TEMPLATE_LINK_WRITE_BATCH) {
|
|
7852
|
+
for (let offset = 0; offset < values.length; offset += size) {
|
|
7853
|
+
yield values.slice(offset, offset + size);
|
|
7854
|
+
}
|
|
7855
|
+
}
|
|
7856
|
+
function classifySiteCrawlTemplateLinks(db, scope, pagesFetched) {
|
|
7857
|
+
const scopeFilter = and11(
|
|
7858
|
+
eq14(siteCrawlEdges.projectId, scope.projectId),
|
|
7859
|
+
eq14(siteCrawlEdges.runId, scope.runId),
|
|
7860
|
+
eq14(siteCrawlEdges.attemptId, scope.attemptId)
|
|
7861
|
+
);
|
|
7862
|
+
const detection = templateLinkDetection(pagesFetched);
|
|
7863
|
+
db.update(siteCrawlEdges).set({ isTemplate: false, templateRatio: null }).where(scopeFilter).run();
|
|
7864
|
+
if (detection !== SiteHealthTemplateDetections.applied) {
|
|
7865
|
+
return { detection, templateEdgeCount: 0 };
|
|
7866
|
+
}
|
|
7867
|
+
const index = createTemplateLinkPairIndex();
|
|
7868
|
+
for (const batch of streamAttemptEdges(db, scope)) observeTemplateLinkEdges(index, batch);
|
|
7869
|
+
const edgeKeysByRatio = /* @__PURE__ */ new Map();
|
|
7870
|
+
let templateEdgeCount = 0;
|
|
7871
|
+
for (const batch of streamAttemptEdges(db, scope)) {
|
|
7872
|
+
for (const edge of batch) {
|
|
7873
|
+
const ratio = templateLinkRatio(index, pagesFetched, edge);
|
|
7874
|
+
if (ratio == null) continue;
|
|
7875
|
+
const group = edgeKeysByRatio.get(ratio);
|
|
7876
|
+
if (group) group.push(edge.edgeKey);
|
|
7877
|
+
else edgeKeysByRatio.set(ratio, [edge.edgeKey]);
|
|
7878
|
+
if (isTemplateLinkRatio(ratio)) templateEdgeCount += 1;
|
|
7879
|
+
}
|
|
7880
|
+
}
|
|
7881
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7882
|
+
for (const [ratio, edgeKeys] of edgeKeysByRatio) {
|
|
7883
|
+
const isTemplate = isTemplateLinkRatio(ratio);
|
|
7884
|
+
for (const chunk of chunked(edgeKeys)) {
|
|
7885
|
+
db.update(siteCrawlEdges).set({ isTemplate, templateRatio: ratio, updatedAt: now }).where(and11(scopeFilter, inArray4(siteCrawlEdges.edgeKey, chunk))).run();
|
|
7886
|
+
}
|
|
7887
|
+
}
|
|
7888
|
+
return { detection, templateEdgeCount };
|
|
7889
|
+
}
|
|
7890
|
+
|
|
7589
7891
|
// src/execute-site-audit.ts
|
|
7590
7892
|
var log12 = createLogger("SiteAudit");
|
|
7591
7893
|
var SiteAuditCancelledError = class extends Error {
|
|
@@ -7733,9 +8035,9 @@ function deadLinkCheckedCount(edges, pages) {
|
|
|
7733
8035
|
}
|
|
7734
8036
|
async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
7735
8037
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7736
|
-
const claim = db.update(runs).set({ status: "running", startedAt }).where(
|
|
8038
|
+
const claim = db.update(runs).set({ status: "running", startedAt }).where(and12(eq15(runs.id, runId), eq15(runs.projectId, projectId), eq15(runs.status, "queued"))).run();
|
|
7737
8039
|
if (claim.changes === 0) return;
|
|
7738
|
-
const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(
|
|
8040
|
+
const attemptNumber = (db.select({ value: sql6`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq15(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
|
|
7739
8041
|
const attemptId = crypto17.randomUUID();
|
|
7740
8042
|
db.insert(siteCrawlAttempts).values({
|
|
7741
8043
|
id: attemptId,
|
|
@@ -7770,28 +8072,49 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7770
8072
|
const nodeKey = nodeKeyByUrl.get(url);
|
|
7771
8073
|
if (!nodeKey) continue;
|
|
7772
8074
|
const scope = [
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
8075
|
+
eq15(siteCrawlEdges.projectId, projectId),
|
|
8076
|
+
eq15(siteCrawlEdges.runId, runId),
|
|
8077
|
+
eq15(siteCrawlEdges.attemptId, attemptId)
|
|
7776
8078
|
];
|
|
7777
|
-
tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(
|
|
8079
|
+
tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and12(
|
|
7778
8080
|
...scope,
|
|
7779
|
-
|
|
8081
|
+
eq15(siteCrawlEdges.sourceUrl, url)
|
|
7780
8082
|
)).run();
|
|
7781
|
-
tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(
|
|
8083
|
+
tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and12(
|
|
7782
8084
|
...scope,
|
|
7783
|
-
|
|
7784
|
-
)).run();
|
|
7785
|
-
tx.update(siteCrawlPages).set({ canonicalNodeKey: nodeKey, updatedAt: now }).where(and11(
|
|
7786
|
-
eq14(siteCrawlPages.projectId, projectId),
|
|
7787
|
-
eq14(siteCrawlPages.runId, runId),
|
|
7788
|
-
eq14(siteCrawlPages.attemptId, attemptId),
|
|
7789
|
-
eq14(siteCrawlPages.canonicalUrl, url)
|
|
8085
|
+
eq15(siteCrawlEdges.targetUrl, url)
|
|
7790
8086
|
)).run();
|
|
8087
|
+
const pageScope = [
|
|
8088
|
+
eq15(siteCrawlPages.projectId, projectId),
|
|
8089
|
+
eq15(siteCrawlPages.runId, runId),
|
|
8090
|
+
eq15(siteCrawlPages.attemptId, attemptId)
|
|
8091
|
+
];
|
|
8092
|
+
const canonicalTargets = tx.select({
|
|
8093
|
+
id: siteCrawlPages.id,
|
|
8094
|
+
nodeKey: siteCrawlPages.nodeKey,
|
|
8095
|
+
fetchState: siteCrawlPages.fetchState,
|
|
8096
|
+
indexabilityState: siteCrawlPages.indexabilityState,
|
|
8097
|
+
indexabilityReasons: siteCrawlPages.indexabilityReasons
|
|
8098
|
+
}).from(siteCrawlPages).where(and12(...pageScope, eq15(siteCrawlPages.canonicalUrl, url))).all();
|
|
8099
|
+
for (const target of canonicalTargets) {
|
|
8100
|
+
tx.update(siteCrawlPages).set({
|
|
8101
|
+
canonicalNodeKey: nodeKey,
|
|
8102
|
+
healthState: deriveSiteHealthState({ ...target, canonicalNodeKey: nodeKey }),
|
|
8103
|
+
updatedAt: now
|
|
8104
|
+
}).where(eq15(siteCrawlPages.id, target.id)).run();
|
|
8105
|
+
}
|
|
7791
8106
|
}
|
|
7792
8107
|
};
|
|
7793
8108
|
const persistPage = (tx, page, now) => {
|
|
7794
8109
|
const audit = page.audit;
|
|
8110
|
+
const canonicalNodeKey = page.canonicalUrl ? nodeKeyByUrl.get(page.canonicalUrl) ?? null : null;
|
|
8111
|
+
const healthState = deriveSiteHealthState({
|
|
8112
|
+
fetchState: page.state,
|
|
8113
|
+
indexabilityState: page.indexability.state,
|
|
8114
|
+
indexabilityReasons: page.indexability.reasons,
|
|
8115
|
+
canonicalNodeKey,
|
|
8116
|
+
nodeKey: page.key
|
|
8117
|
+
});
|
|
7795
8118
|
tx.insert(siteCrawlPages).values({
|
|
7796
8119
|
id: crypto17.randomUUID(),
|
|
7797
8120
|
projectId,
|
|
@@ -7816,9 +8139,10 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7816
8139
|
redirectChain: page.redirectChain.map((hop) => hop.to),
|
|
7817
8140
|
directives: { metaRobots: page.metaRobots, xRobots: page.xRobots },
|
|
7818
8141
|
canonicalUrl: page.canonicalUrl,
|
|
7819
|
-
canonicalNodeKey
|
|
8142
|
+
canonicalNodeKey,
|
|
7820
8143
|
indexabilityState: page.indexability.state,
|
|
7821
8144
|
indexabilityReasons: page.indexability.reasons,
|
|
8145
|
+
healthState,
|
|
7822
8146
|
auditState: pageAuditState(page),
|
|
7823
8147
|
auditScore: audit?.overallScore ?? null,
|
|
7824
8148
|
auditFields: audit ? crawlAuditFields(audit) : {},
|
|
@@ -7849,9 +8173,10 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7849
8173
|
redirectChain: page.redirectChain.map((hop) => hop.to),
|
|
7850
8174
|
directives: { metaRobots: page.metaRobots, xRobots: page.xRobots },
|
|
7851
8175
|
canonicalUrl: page.canonicalUrl,
|
|
7852
|
-
canonicalNodeKey
|
|
8176
|
+
canonicalNodeKey,
|
|
7853
8177
|
indexabilityState: page.indexability.state,
|
|
7854
8178
|
indexabilityReasons: page.indexability.reasons,
|
|
8179
|
+
healthState,
|
|
7855
8180
|
auditState: pageAuditState(page),
|
|
7856
8181
|
auditScore: audit?.overallScore ?? null,
|
|
7857
8182
|
auditFields: audit ? crawlAuditFields(audit) : {},
|
|
@@ -7874,6 +8199,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7874
8199
|
bindReferences(tx, changedUrls, now);
|
|
7875
8200
|
};
|
|
7876
8201
|
const persistEdge = (tx, edge, now) => {
|
|
8202
|
+
if (isSelfLink(edge.from, edge.to)) return;
|
|
7877
8203
|
observedEdges.set(edge.key, edge);
|
|
7878
8204
|
tx.insert(siteCrawlEdges).values({
|
|
7879
8205
|
id: crypto17.randomUUID(),
|
|
@@ -7922,20 +8248,20 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7922
8248
|
linkScoreRaw: metrics.linkScoreRaw,
|
|
7923
8249
|
linkScoreNormalized: metrics.linkScore,
|
|
7924
8250
|
updatedAt: now
|
|
7925
|
-
}).where(
|
|
7926
|
-
|
|
7927
|
-
|
|
7928
|
-
|
|
7929
|
-
|
|
8251
|
+
}).where(and12(
|
|
8252
|
+
eq15(siteCrawlPages.projectId, projectId),
|
|
8253
|
+
eq15(siteCrawlPages.runId, runId),
|
|
8254
|
+
eq15(siteCrawlPages.attemptId, attemptId),
|
|
8255
|
+
eq15(siteCrawlPages.nodeKey, key)
|
|
7930
8256
|
)).run();
|
|
7931
8257
|
};
|
|
7932
8258
|
const persistEvent = async (event) => {
|
|
7933
8259
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7934
8260
|
db.transaction((tx) => {
|
|
7935
|
-
const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(
|
|
7936
|
-
|
|
7937
|
-
|
|
7938
|
-
|
|
8261
|
+
const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and12(
|
|
8262
|
+
eq15(siteCrawlEventReceipts.attemptId, attemptId),
|
|
8263
|
+
eq15(siteCrawlEventReceipts.sequence, event.sequence),
|
|
8264
|
+
eq15(siteCrawlEventReceipts.batchId, event.batchId)
|
|
7939
8265
|
)).get();
|
|
7940
8266
|
if (receipt) {
|
|
7941
8267
|
if (receipt.checksum !== event.checksum) {
|
|
@@ -7968,26 +8294,39 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
7968
8294
|
pagesErrored: event.type === "summary" ? observedErrorCount(observedPages.values()) : sql6`${siteCrawlAttempts.pagesErrored}`,
|
|
7969
8295
|
edgesDiscovered: progress.edgesObserved,
|
|
7970
8296
|
updatedAt: now
|
|
7971
|
-
}).where(
|
|
8297
|
+
}).where(eq15(siteCrawlAttempts.id, attemptId)).run();
|
|
7972
8298
|
} else {
|
|
7973
8299
|
tx.update(siteCrawlAttempts).set({
|
|
7974
8300
|
lastEventSequence: event.sequence,
|
|
7975
8301
|
lastEventChecksum: event.checksum,
|
|
7976
8302
|
updatedAt: now
|
|
7977
|
-
}).where(
|
|
8303
|
+
}).where(eq15(siteCrawlAttempts.id, attemptId)).run();
|
|
7978
8304
|
}
|
|
7979
8305
|
});
|
|
7980
8306
|
};
|
|
7981
8307
|
try {
|
|
7982
|
-
const project = db.select().from(projects).where(
|
|
8308
|
+
const project = db.select().from(projects).where(eq15(projects.id, projectId)).get();
|
|
7983
8309
|
if (!project) throw new Error(`Project not found: ${projectId}`);
|
|
7984
8310
|
const homepageUrl = toHomepageUrl(project.canonicalDomain);
|
|
7985
8311
|
const maxPages = clampSiteAuditLimit(opts.maxPages ?? opts.limit);
|
|
7986
8312
|
const maxEdges = clampSiteAuditEdgeLimit(opts.maxEdges);
|
|
7987
|
-
await
|
|
8313
|
+
const resolvedRoot = await resolveSiteAuditRootUrl(homepageUrl, {
|
|
8314
|
+
resolveTarget: resolveMeasurementSitemapTarget,
|
|
8315
|
+
signal: opts.signal
|
|
8316
|
+
});
|
|
8317
|
+
const crawlRootUrl = resolvedRoot.effectiveUrl;
|
|
7988
8318
|
if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
|
|
7989
|
-
log12.info("start", {
|
|
7990
|
-
|
|
8319
|
+
log12.info("start", {
|
|
8320
|
+
runId,
|
|
8321
|
+
projectId,
|
|
8322
|
+
homepageUrl,
|
|
8323
|
+
crawlRootUrl,
|
|
8324
|
+
redirects: resolvedRoot.redirects.length,
|
|
8325
|
+
maxPages,
|
|
8326
|
+
maxEdges,
|
|
8327
|
+
maxDepth: opts.maxDepth ?? null
|
|
8328
|
+
});
|
|
8329
|
+
const report = await runSiteCrawl(crawlRootUrl, {
|
|
7991
8330
|
mode: "summary",
|
|
7992
8331
|
sitemapUrl: opts.sitemapUrl,
|
|
7993
8332
|
maxPages,
|
|
@@ -8002,6 +8341,17 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8002
8341
|
if (crawlSummary.complete && !hasAuditedPages) {
|
|
8003
8342
|
throw emptyCompleteCrawlError(crawlSummary.rootUrl, crawlSummary.finalRootUrl, crawlSummary.pagesObserved);
|
|
8004
8343
|
}
|
|
8344
|
+
const templateLinks = classifySiteCrawlTemplateLinks(
|
|
8345
|
+
db,
|
|
8346
|
+
{ projectId, runId, attemptId },
|
|
8347
|
+
crawlSummary.pagesFetched
|
|
8348
|
+
);
|
|
8349
|
+
log12.info("template-links", {
|
|
8350
|
+
runId,
|
|
8351
|
+
projectId,
|
|
8352
|
+
detection: templateLinks.detection,
|
|
8353
|
+
templateEdges: templateLinks.templateEdgeCount
|
|
8354
|
+
});
|
|
8005
8355
|
const graphLayout = await prepareSiteCrawlGraphLayout(db, {
|
|
8006
8356
|
projectId,
|
|
8007
8357
|
runId,
|
|
@@ -8029,6 +8379,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8029
8379
|
failureCode: "layout-error",
|
|
8030
8380
|
totalNodes: graphLayout.totalNodes,
|
|
8031
8381
|
totalEdges: graphLayout.totalEdges,
|
|
8382
|
+
totalTemplateEdges: graphLayout.totalTemplateEdges,
|
|
8032
8383
|
nodeCount: 0,
|
|
8033
8384
|
edgeCount: 0,
|
|
8034
8385
|
nodes: [],
|
|
@@ -8043,14 +8394,14 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8043
8394
|
}
|
|
8044
8395
|
}
|
|
8045
8396
|
const published = db.transaction((tx) => {
|
|
8046
|
-
const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(
|
|
8397
|
+
const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and12(eq15(runs.id, runId), eq15(runs.status, "running"))).run();
|
|
8047
8398
|
if (claim2.changes === 0) return false;
|
|
8048
8399
|
if (hasAuditedPages) {
|
|
8049
8400
|
tx.insert(siteAuditSnapshots).values({
|
|
8050
8401
|
id: crypto17.randomUUID(),
|
|
8051
8402
|
projectId,
|
|
8052
8403
|
runId,
|
|
8053
|
-
sitemapUrl: opts.sitemapUrl ??
|
|
8404
|
+
sitemapUrl: opts.sitemapUrl ?? new URL("/sitemap.xml", crawlRootUrl).href,
|
|
8054
8405
|
auditedAt: crawlSummary.completedAt,
|
|
8055
8406
|
aggregateScore: crawlSummary.auditRollup.aggregateScore ?? 0,
|
|
8056
8407
|
pagesDiscovered: crawlSummary.pagesDiscovered,
|
|
@@ -8082,7 +8433,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8082
8433
|
projectId,
|
|
8083
8434
|
runId,
|
|
8084
8435
|
attemptId,
|
|
8085
|
-
requestedRootUrl:
|
|
8436
|
+
requestedRootUrl: resolvedRoot.requestedUrl,
|
|
8086
8437
|
rootUrl: crawlSummary.finalRootUrl ?? crawlSummary.rootUrl,
|
|
8087
8438
|
crawlSchemaVersion: crawlSummary.crawlSchemaVersion,
|
|
8088
8439
|
engineVersion: crawlSummary.engineVersion,
|
|
@@ -8113,6 +8464,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8113
8464
|
deadLinkState: report.deadLinks.state,
|
|
8114
8465
|
deadLinksChecked,
|
|
8115
8466
|
deadLinksFound,
|
|
8467
|
+
templateDetection: templateLinks.detection,
|
|
8116
8468
|
createdAt: finishedAt,
|
|
8117
8469
|
updatedAt: finishedAt
|
|
8118
8470
|
}).run();
|
|
@@ -8145,7 +8497,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8145
8497
|
edgesDiscovered: crawlSummary.edgesObserved,
|
|
8146
8498
|
finishedAt,
|
|
8147
8499
|
updatedAt: finishedAt
|
|
8148
|
-
}).where(
|
|
8500
|
+
}).where(eq15(siteCrawlAttempts.id, attemptId)).run();
|
|
8149
8501
|
return true;
|
|
8150
8502
|
});
|
|
8151
8503
|
if (!published) {
|
|
@@ -8158,7 +8510,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8158
8510
|
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
|
|
8159
8511
|
});
|
|
8160
8512
|
}
|
|
8161
|
-
const current = db.select({ status: runs.status }).from(runs).where(
|
|
8513
|
+
const current = db.select({ status: runs.status }).from(runs).where(eq15(runs.id, runId)).get();
|
|
8162
8514
|
if (current?.status === "cancelled") throw new SiteAuditCancelledError(`Site audit cancelled before publication: ${runId}`);
|
|
8163
8515
|
throw new Error(`Site audit run was no longer running before publication: ${runId}`);
|
|
8164
8516
|
}
|
|
@@ -8173,12 +8525,12 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8173
8525
|
error: message,
|
|
8174
8526
|
finishedAt,
|
|
8175
8527
|
updatedAt: finishedAt
|
|
8176
|
-
}).where(
|
|
8528
|
+
}).where(eq15(siteCrawlAttempts.id, attemptId)).run();
|
|
8177
8529
|
tx.update(runs).set({
|
|
8178
8530
|
status: cancelled ? "cancelled" : "failed",
|
|
8179
8531
|
error: message,
|
|
8180
8532
|
finishedAt
|
|
8181
|
-
}).where(
|
|
8533
|
+
}).where(and12(eq15(runs.id, runId), eq15(runs.status, "running"))).run();
|
|
8182
8534
|
});
|
|
8183
8535
|
log12.error("failed", { runId, projectId, cancelled, error: message });
|
|
8184
8536
|
throw error;
|
|
@@ -8186,7 +8538,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
8186
8538
|
}
|
|
8187
8539
|
|
|
8188
8540
|
// src/commands/backfill.ts
|
|
8189
|
-
import { and as
|
|
8541
|
+
import { and as and13, eq as eq16, inArray as inArray5, isNull as isNull2, sql as sql7 } from "drizzle-orm";
|
|
8190
8542
|
var SNAPSHOT_BATCH_SIZE = 500;
|
|
8191
8543
|
async function backfillAnswerVisibilityCommand(opts) {
|
|
8192
8544
|
const config = loadConfig();
|
|
@@ -8194,7 +8546,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
8194
8546
|
migrate(db);
|
|
8195
8547
|
const projectFilter = opts?.project?.trim();
|
|
8196
8548
|
const isDryRun = opts?.dryRun === true;
|
|
8197
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
8549
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq16(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
8198
8550
|
let examined = 0;
|
|
8199
8551
|
let updated = 0;
|
|
8200
8552
|
let wouldUpdate = 0;
|
|
@@ -8202,10 +8554,10 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
8202
8554
|
let reparsed = 0;
|
|
8203
8555
|
let providerErrors = 0;
|
|
8204
8556
|
if (scopedProjects.length > 0) {
|
|
8205
|
-
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
8206
|
-
|
|
8207
|
-
|
|
8208
|
-
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
8557
|
+
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and13(
|
|
8558
|
+
eq16(runs.kind, RunKinds["answer-visibility"]),
|
|
8559
|
+
inArray5(runs.projectId, scopedProjects.map((project) => project.id))
|
|
8560
|
+
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq16(runs.kind, RunKinds["answer-visibility"])).all();
|
|
8209
8561
|
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
8210
8562
|
for (const run of runRows) {
|
|
8211
8563
|
const existing = runIdsByProject.get(run.projectId);
|
|
@@ -8213,7 +8565,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
8213
8565
|
else runIdsByProject.set(run.projectId, [run.id]);
|
|
8214
8566
|
}
|
|
8215
8567
|
for (const project of scopedProjects) {
|
|
8216
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
8568
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq16(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
8217
8569
|
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
8218
8570
|
if (runIds.length === 0) continue;
|
|
8219
8571
|
const projectDomains = effectiveDomains({
|
|
@@ -8236,7 +8588,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
8236
8588
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
8237
8589
|
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
8238
8590
|
rawResponse: querySnapshots.rawResponse
|
|
8239
|
-
}).from(querySnapshots).where(
|
|
8591
|
+
}).from(querySnapshots).where(inArray5(querySnapshots.runId, batchRunIds)).all();
|
|
8240
8592
|
const pendingUpdates = [];
|
|
8241
8593
|
for (const snapshot of snapshotRows) {
|
|
8242
8594
|
examined++;
|
|
@@ -8306,7 +8658,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
8306
8658
|
} else {
|
|
8307
8659
|
db.transaction((tx) => {
|
|
8308
8660
|
for (const update of pendingUpdates) {
|
|
8309
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
8661
|
+
tx.update(querySnapshots).set(update.patch).where(eq16(querySnapshots.id, update.id)).run();
|
|
8310
8662
|
}
|
|
8311
8663
|
});
|
|
8312
8664
|
updated += pendingUpdates.length;
|
|
@@ -8355,13 +8707,13 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
8355
8707
|
function backfillNormalizedPaths(db, opts) {
|
|
8356
8708
|
const baseConditions = [];
|
|
8357
8709
|
if (opts?.projectId) {
|
|
8358
|
-
baseConditions.push(
|
|
8710
|
+
baseConditions.push(eq16(gaTrafficSnapshots.projectId, opts.projectId));
|
|
8359
8711
|
}
|
|
8360
8712
|
const rows = db.select({
|
|
8361
8713
|
id: gaTrafficSnapshots.id,
|
|
8362
8714
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
8363
8715
|
landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
|
|
8364
|
-
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ?
|
|
8716
|
+
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and13(...baseConditions) : void 0).all();
|
|
8365
8717
|
let updated = 0;
|
|
8366
8718
|
let unchanged = 0;
|
|
8367
8719
|
if (rows.length > 0) {
|
|
@@ -8376,7 +8728,7 @@ function backfillNormalizedPaths(db, opts) {
|
|
|
8376
8728
|
unchanged++;
|
|
8377
8729
|
continue;
|
|
8378
8730
|
}
|
|
8379
|
-
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(
|
|
8731
|
+
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq16(gaTrafficSnapshots.id, row.id)).run();
|
|
8380
8732
|
updated++;
|
|
8381
8733
|
}
|
|
8382
8734
|
});
|
|
@@ -8390,7 +8742,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
8390
8742
|
const projectFilter = opts?.project?.trim();
|
|
8391
8743
|
let projectId;
|
|
8392
8744
|
if (projectFilter) {
|
|
8393
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
8745
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq16(projects.name, projectFilter)).get();
|
|
8394
8746
|
if (!project) {
|
|
8395
8747
|
const result2 = {
|
|
8396
8748
|
project: projectFilter,
|
|
@@ -8427,13 +8779,13 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
8427
8779
|
function backfillAiReferralPaths(db, opts) {
|
|
8428
8780
|
const baseConditions = [];
|
|
8429
8781
|
if (opts?.projectId) {
|
|
8430
|
-
baseConditions.push(
|
|
8782
|
+
baseConditions.push(eq16(gaAiReferrals.projectId, opts.projectId));
|
|
8431
8783
|
}
|
|
8432
8784
|
const rows = db.select({
|
|
8433
8785
|
id: gaAiReferrals.id,
|
|
8434
8786
|
landingPage: gaAiReferrals.landingPage,
|
|
8435
8787
|
landingPageNormalized: gaAiReferrals.landingPageNormalized
|
|
8436
|
-
}).from(gaAiReferrals).where(baseConditions.length > 0 ?
|
|
8788
|
+
}).from(gaAiReferrals).where(baseConditions.length > 0 ? and13(...baseConditions) : void 0).all();
|
|
8437
8789
|
let updated = 0;
|
|
8438
8790
|
let unchanged = 0;
|
|
8439
8791
|
if (rows.length > 0) {
|
|
@@ -8448,7 +8800,7 @@ function backfillAiReferralPaths(db, opts) {
|
|
|
8448
8800
|
unchanged++;
|
|
8449
8801
|
continue;
|
|
8450
8802
|
}
|
|
8451
|
-
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(
|
|
8803
|
+
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq16(gaAiReferrals.id, row.id)).run();
|
|
8452
8804
|
updated++;
|
|
8453
8805
|
}
|
|
8454
8806
|
});
|
|
@@ -8462,7 +8814,7 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
8462
8814
|
const projectFilter = opts?.project?.trim();
|
|
8463
8815
|
let projectId;
|
|
8464
8816
|
if (projectFilter) {
|
|
8465
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
8817
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq16(projects.name, projectFilter)).get();
|
|
8466
8818
|
if (!project) {
|
|
8467
8819
|
const result2 = {
|
|
8468
8820
|
project: projectFilter,
|
|
@@ -8498,10 +8850,10 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
8498
8850
|
}
|
|
8499
8851
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
8500
8852
|
const isDryRun = opts?.dryRun === true;
|
|
8501
|
-
const project = db.select().from(projects).where(
|
|
8853
|
+
const project = db.select().from(projects).where(eq16(projects.id, projectId)).get();
|
|
8502
8854
|
if (!project) return { examined: 0, updated: 0, mentioned: 0 };
|
|
8503
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
8504
|
-
const runRows = db.select({ id: runs.id }).from(runs).where(
|
|
8855
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq16(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
8856
|
+
const runRows = db.select({ id: runs.id }).from(runs).where(and13(eq16(runs.kind, RunKinds["answer-visibility"]), eq16(runs.projectId, projectId))).all();
|
|
8505
8857
|
const runIds = runRows.map((r) => r.id);
|
|
8506
8858
|
let examined = 0;
|
|
8507
8859
|
let updated = 0;
|
|
@@ -8529,7 +8881,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
|
8529
8881
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
8530
8882
|
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
8531
8883
|
rawResponse: querySnapshots.rawResponse
|
|
8532
|
-
}).from(querySnapshots).where(
|
|
8884
|
+
}).from(querySnapshots).where(inArray5(querySnapshots.runId, batchRunIds)).all();
|
|
8533
8885
|
const pendingUpdates = [];
|
|
8534
8886
|
for (const snapshot of snapshotRows) {
|
|
8535
8887
|
examined++;
|
|
@@ -8575,7 +8927,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
|
8575
8927
|
} else {
|
|
8576
8928
|
db.transaction((tx) => {
|
|
8577
8929
|
for (const update of pendingUpdates) {
|
|
8578
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
8930
|
+
tx.update(querySnapshots).set(update.patch).where(eq16(querySnapshots.id, update.id)).run();
|
|
8579
8931
|
}
|
|
8580
8932
|
});
|
|
8581
8933
|
updated += pendingUpdates.length;
|
|
@@ -8590,7 +8942,7 @@ async function backfillAnswerMentionsCommand(opts) {
|
|
|
8590
8942
|
migrate(db);
|
|
8591
8943
|
const projectFilter = opts?.project?.trim();
|
|
8592
8944
|
const isDryRun = opts?.dryRun === true;
|
|
8593
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
8945
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq16(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
8594
8946
|
let examined = 0;
|
|
8595
8947
|
let updated = 0;
|
|
8596
8948
|
let wouldUpdate = 0;
|
|
@@ -8650,7 +9002,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
8650
9002
|
return result;
|
|
8651
9003
|
}
|
|
8652
9004
|
async function backfillInsightsCommand(project, opts) {
|
|
8653
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
9005
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-DHY5CYWX.js");
|
|
8654
9006
|
const config = loadConfig();
|
|
8655
9007
|
const db = createClient(config.database);
|
|
8656
9008
|
migrate(db);
|
|
@@ -8809,7 +9161,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
8809
9161
|
const config = loadConfig();
|
|
8810
9162
|
const db = createClient(config.database);
|
|
8811
9163
|
migrate(db);
|
|
8812
|
-
const project = db.select().from(projects).where(
|
|
9164
|
+
const project = db.select().from(projects).where(eq16(projects.name, opts.project)).get();
|
|
8813
9165
|
if (!project) {
|
|
8814
9166
|
throw new Error(`Project "${opts.project}" not found`);
|
|
8815
9167
|
}
|
|
@@ -8820,17 +9172,17 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
8820
9172
|
process.stderr.write(`Recovering orphan snapshot attribution for "${project.name}"${mode}...
|
|
8821
9173
|
`);
|
|
8822
9174
|
}
|
|
8823
|
-
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(
|
|
8824
|
-
|
|
8825
|
-
|
|
9175
|
+
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and13(
|
|
9176
|
+
eq16(auditLog.projectId, project.id),
|
|
9177
|
+
inArray5(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
|
|
8826
9178
|
)).orderBy(auditLog.createdAt).all();
|
|
8827
9179
|
const history = replayQueryAuditLog(events);
|
|
8828
9180
|
const orphanRuns = db.select({
|
|
8829
9181
|
runId: runs.id,
|
|
8830
9182
|
createdAt: runs.createdAt,
|
|
8831
9183
|
location: runs.location
|
|
8832
|
-
}).from(runs).innerJoin(querySnapshots,
|
|
8833
|
-
|
|
9184
|
+
}).from(runs).innerJoin(querySnapshots, eq16(querySnapshots.runId, runs.id)).where(and13(
|
|
9185
|
+
eq16(runs.projectId, project.id),
|
|
8834
9186
|
isNull2(querySnapshots.queryId),
|
|
8835
9187
|
isNull2(querySnapshots.queryText)
|
|
8836
9188
|
)).groupBy(runs.id).orderBy(runs.createdAt).all();
|
|
@@ -8852,8 +9204,8 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
8852
9204
|
provider: querySnapshots.provider,
|
|
8853
9205
|
createdAt: querySnapshots.createdAt,
|
|
8854
9206
|
answerText: querySnapshots.answerText
|
|
8855
|
-
}).from(querySnapshots).where(
|
|
8856
|
-
|
|
9207
|
+
}).from(querySnapshots).where(and13(
|
|
9208
|
+
eq16(querySnapshots.runId, run.runId),
|
|
8857
9209
|
isNull2(querySnapshots.queryId),
|
|
8858
9210
|
isNull2(querySnapshots.queryText)
|
|
8859
9211
|
)).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
|
|
@@ -8919,7 +9271,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
8919
9271
|
if (!isDryRun && updates.length > 0) {
|
|
8920
9272
|
db.transaction((tx) => {
|
|
8921
9273
|
for (const u of updates) {
|
|
8922
|
-
tx.update(querySnapshots).set({ queryText: u.queryText }).where(
|
|
9274
|
+
tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq16(querySnapshots.id, u.id)).run();
|
|
8923
9275
|
}
|
|
8924
9276
|
});
|
|
8925
9277
|
}
|
|
@@ -8993,7 +9345,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
8993
9345
|
const projectFilter = opts?.project?.trim();
|
|
8994
9346
|
const isDryRun = opts?.dryRun === true;
|
|
8995
9347
|
const isJson = isMachineFormat(opts?.format);
|
|
8996
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
9348
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq16(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
8997
9349
|
if (scopedProjects.length === 0) {
|
|
8998
9350
|
if (projectFilter && !isJson) {
|
|
8999
9351
|
process.stderr.write(`No project named "${projectFilter}".
|
|
@@ -9018,9 +9370,9 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
9018
9370
|
dryRun: isDryRun,
|
|
9019
9371
|
byBot: {}
|
|
9020
9372
|
};
|
|
9021
|
-
const unknownCountRow = db.select({ n: sql7`count(*)` }).from(rawEventSamples).where(
|
|
9022
|
-
|
|
9023
|
-
|
|
9373
|
+
const unknownCountRow = db.select({ n: sql7`count(*)` }).from(rawEventSamples).where(and13(
|
|
9374
|
+
eq16(rawEventSamples.eventType, "unknown"),
|
|
9375
|
+
inArray5(rawEventSamples.projectId, projectIds)
|
|
9024
9376
|
)).get();
|
|
9025
9377
|
result.unknownBefore = Number(unknownCountRow?.n ?? 0);
|
|
9026
9378
|
const unknownSamples = db.select({
|
|
@@ -9031,9 +9383,9 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
9031
9383
|
userAgent: rawEventSamples.userAgent,
|
|
9032
9384
|
pathNormalized: rawEventSamples.pathNormalized,
|
|
9033
9385
|
status: rawEventSamples.status
|
|
9034
|
-
}).from(rawEventSamples).where(
|
|
9035
|
-
|
|
9036
|
-
|
|
9386
|
+
}).from(rawEventSamples).where(and13(
|
|
9387
|
+
eq16(rawEventSamples.eventType, "unknown"),
|
|
9388
|
+
inArray5(rawEventSamples.projectId, projectIds)
|
|
9037
9389
|
)).all();
|
|
9038
9390
|
result.examined = unknownSamples.length;
|
|
9039
9391
|
if (unknownSamples.length === 0) {
|
|
@@ -9071,7 +9423,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
9071
9423
|
result.reclassified++;
|
|
9072
9424
|
result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
|
|
9073
9425
|
if (isDryRun) continue;
|
|
9074
|
-
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(
|
|
9426
|
+
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq16(rawEventSamples.id, snap.id)).run();
|
|
9075
9427
|
const tsHour = new Date(snap.ts);
|
|
9076
9428
|
tsHour.setUTCMinutes(0, 0, 0);
|
|
9077
9429
|
if (userFetch) {
|
|
@@ -9135,9 +9487,9 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
9135
9487
|
}
|
|
9136
9488
|
}
|
|
9137
9489
|
if (!isDryRun) {
|
|
9138
|
-
const afterRow = db.select({ n: sql7`count(*)` }).from(rawEventSamples).where(
|
|
9139
|
-
|
|
9140
|
-
|
|
9490
|
+
const afterRow = db.select({ n: sql7`count(*)` }).from(rawEventSamples).where(and13(
|
|
9491
|
+
eq16(rawEventSamples.eventType, "unknown"),
|
|
9492
|
+
inArray5(rawEventSamples.projectId, projectIds)
|
|
9141
9493
|
)).get();
|
|
9142
9494
|
result.unknownAfter = Number(afterRow?.n ?? 0);
|
|
9143
9495
|
} else {
|
|
@@ -9570,7 +9922,7 @@ var ProviderRegistry = class {
|
|
|
9570
9922
|
// src/scheduler.ts
|
|
9571
9923
|
import crypto19 from "crypto";
|
|
9572
9924
|
import cron from "node-cron";
|
|
9573
|
-
import { and as
|
|
9925
|
+
import { and as and14, eq as eq17, inArray as inArray6, notExists, sql as sql8 } from "drizzle-orm";
|
|
9574
9926
|
var log13 = createLogger("Scheduler");
|
|
9575
9927
|
var DEFAULT_HEALTH_CRON = "0 */6 * * *";
|
|
9576
9928
|
function taskKey(projectId, kind) {
|
|
@@ -9602,9 +9954,9 @@ var Scheduler = class {
|
|
|
9602
9954
|
*/
|
|
9603
9955
|
ensureHealthSchedules() {
|
|
9604
9956
|
const projectsWithoutHealth = this.db.select({ id: projects.id }).from(projects).where(notExists(
|
|
9605
|
-
this.db.select({ one: sql8`1` }).from(schedules).where(
|
|
9606
|
-
|
|
9607
|
-
|
|
9957
|
+
this.db.select({ one: sql8`1` }).from(schedules).where(and14(
|
|
9958
|
+
eq17(schedules.projectId, projects.id),
|
|
9959
|
+
eq17(schedules.kind, SchedulableRunKinds.doctor)
|
|
9608
9960
|
))
|
|
9609
9961
|
)).all();
|
|
9610
9962
|
if (projectsWithoutHealth.length === 0) return;
|
|
@@ -9655,7 +10007,7 @@ var Scheduler = class {
|
|
|
9655
10007
|
start() {
|
|
9656
10008
|
this.ensureHealthSchedules();
|
|
9657
10009
|
this.ensureQueryBaskets();
|
|
9658
|
-
const allSchedules = this.db.select().from(schedules).where(
|
|
10010
|
+
const allSchedules = this.db.select().from(schedules).where(eq17(schedules.enabled, true)).all();
|
|
9659
10011
|
for (const schedule of allSchedules) {
|
|
9660
10012
|
const missedRunAt = schedule.nextRunAt;
|
|
9661
10013
|
this.registerCronTask(schedule);
|
|
@@ -9685,7 +10037,7 @@ var Scheduler = class {
|
|
|
9685
10037
|
this.stopTask(key, existing, "Stopped");
|
|
9686
10038
|
this.tasks.delete(key);
|
|
9687
10039
|
}
|
|
9688
|
-
const schedule = this.db.select().from(schedules).where(
|
|
10040
|
+
const schedule = this.db.select().from(schedules).where(and14(eq17(schedules.projectId, projectId), eq17(schedules.kind, kind))).get();
|
|
9689
10041
|
if (schedule && schedule.enabled) {
|
|
9690
10042
|
this.registerCronTask(schedule);
|
|
9691
10043
|
}
|
|
@@ -9726,21 +10078,21 @@ var Scheduler = class {
|
|
|
9726
10078
|
this.db.update(schedules).set({
|
|
9727
10079
|
nextRunAt: nextRunFromCron(cronExpr, timezone),
|
|
9728
10080
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9729
|
-
}).where(
|
|
10081
|
+
}).where(eq17(schedules.id, scheduleId)).run();
|
|
9730
10082
|
const label = schedule.preset ?? cronExpr;
|
|
9731
10083
|
log13.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
9732
10084
|
}
|
|
9733
10085
|
triggerRun(scheduleId, projectId, kind) {
|
|
9734
10086
|
try {
|
|
9735
10087
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9736
|
-
const currentSchedule = this.db.select().from(schedules).where(
|
|
10088
|
+
const currentSchedule = this.db.select().from(schedules).where(eq17(schedules.id, scheduleId)).get();
|
|
9737
10089
|
if (!currentSchedule || !currentSchedule.enabled) {
|
|
9738
10090
|
log13.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
9739
10091
|
this.remove(projectId, kind);
|
|
9740
10092
|
return;
|
|
9741
10093
|
}
|
|
9742
10094
|
const nextRunAt = nextRunFromCron(currentSchedule.cronExpr, currentSchedule.timezone);
|
|
9743
|
-
const project = this.db.select().from(projects).where(
|
|
10095
|
+
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
9744
10096
|
if (!project) {
|
|
9745
10097
|
log13.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
9746
10098
|
this.remove(projectId, kind);
|
|
@@ -9760,7 +10112,7 @@ var Scheduler = class {
|
|
|
9760
10112
|
lastRunAt: now,
|
|
9761
10113
|
nextRunAt,
|
|
9762
10114
|
updatedAt: now
|
|
9763
|
-
}).where(
|
|
10115
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9764
10116
|
log13.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
9765
10117
|
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
9766
10118
|
return;
|
|
@@ -9783,7 +10135,7 @@ var Scheduler = class {
|
|
|
9783
10135
|
lastRunAt: now,
|
|
9784
10136
|
nextRunAt,
|
|
9785
10137
|
updatedAt: now
|
|
9786
|
-
}).where(
|
|
10138
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9787
10139
|
log13.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
|
|
9788
10140
|
this.callbacks.onGbpSyncRequested(runId2, projectId);
|
|
9789
10141
|
return;
|
|
@@ -9793,14 +10145,14 @@ var Scheduler = class {
|
|
|
9793
10145
|
log13.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
|
|
9794
10146
|
return;
|
|
9795
10147
|
}
|
|
9796
|
-
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(
|
|
9797
|
-
|
|
9798
|
-
|
|
9799
|
-
|
|
10148
|
+
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and14(
|
|
10149
|
+
eq17(runs.projectId, projectId),
|
|
10150
|
+
eq17(runs.kind, RunKinds["ads-sync"]),
|
|
10151
|
+
inArray6(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
9800
10152
|
)).get();
|
|
9801
10153
|
if (activeAdsRun) {
|
|
9802
10154
|
log13.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
|
|
9803
|
-
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(
|
|
10155
|
+
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9804
10156
|
return;
|
|
9805
10157
|
}
|
|
9806
10158
|
const runId2 = crypto19.randomUUID();
|
|
@@ -9816,7 +10168,7 @@ var Scheduler = class {
|
|
|
9816
10168
|
lastRunAt: now,
|
|
9817
10169
|
nextRunAt,
|
|
9818
10170
|
updatedAt: now
|
|
9819
|
-
}).where(
|
|
10171
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9820
10172
|
log13.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
|
|
9821
10173
|
this.callbacks.onAdsSyncRequested(runId2, projectId);
|
|
9822
10174
|
return;
|
|
@@ -9830,7 +10182,7 @@ var Scheduler = class {
|
|
|
9830
10182
|
lastRunAt: now,
|
|
9831
10183
|
nextRunAt,
|
|
9832
10184
|
updatedAt: now
|
|
9833
|
-
}).where(
|
|
10185
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9834
10186
|
log13.info("data-refresh.triggered", { projectName: project.name });
|
|
9835
10187
|
this.callbacks.onDataRefreshRequested(project.name);
|
|
9836
10188
|
return;
|
|
@@ -9844,7 +10196,7 @@ var Scheduler = class {
|
|
|
9844
10196
|
lastRunAt: now,
|
|
9845
10197
|
nextRunAt,
|
|
9846
10198
|
updatedAt: now
|
|
9847
|
-
}).where(
|
|
10199
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9848
10200
|
log13.info("doctor.triggered", { projectName: project.name });
|
|
9849
10201
|
this.callbacks.onDoctorRequested(project.name);
|
|
9850
10202
|
return;
|
|
@@ -9858,7 +10210,7 @@ var Scheduler = class {
|
|
|
9858
10210
|
lastRunAt: now,
|
|
9859
10211
|
nextRunAt,
|
|
9860
10212
|
updatedAt: now
|
|
9861
|
-
}).where(
|
|
10213
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9862
10214
|
log13.info("backlinks-sync.triggered", { projectName: project.name });
|
|
9863
10215
|
this.callbacks.onBacklinksSyncRequested(project.name);
|
|
9864
10216
|
return;
|
|
@@ -9868,14 +10220,14 @@ var Scheduler = class {
|
|
|
9868
10220
|
log13.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
9869
10221
|
return;
|
|
9870
10222
|
}
|
|
9871
|
-
const active = this.db.select({ id: runs.id }).from(runs).where(
|
|
9872
|
-
|
|
9873
|
-
|
|
9874
|
-
|
|
10223
|
+
const active = this.db.select({ id: runs.id }).from(runs).where(and14(
|
|
10224
|
+
eq17(runs.projectId, projectId),
|
|
10225
|
+
eq17(runs.kind, RunKinds["site-audit"]),
|
|
10226
|
+
inArray6(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
9875
10227
|
)).get();
|
|
9876
10228
|
if (active) {
|
|
9877
10229
|
log13.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
|
|
9878
|
-
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(
|
|
10230
|
+
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9879
10231
|
return;
|
|
9880
10232
|
}
|
|
9881
10233
|
const runId2 = crypto19.randomUUID();
|
|
@@ -9901,7 +10253,7 @@ var Scheduler = class {
|
|
|
9901
10253
|
lastRunAt: now,
|
|
9902
10254
|
nextRunAt,
|
|
9903
10255
|
updatedAt: now
|
|
9904
|
-
}).where(
|
|
10256
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9905
10257
|
log13.info("site-audit.triggered", { runId: runId2, projectName: project.name });
|
|
9906
10258
|
this.callbacks.onSiteAuditRequested(runId2, projectId);
|
|
9907
10259
|
return;
|
|
@@ -9934,7 +10286,7 @@ var Scheduler = class {
|
|
|
9934
10286
|
this.db.update(schedules).set({
|
|
9935
10287
|
nextRunAt,
|
|
9936
10288
|
updatedAt: now
|
|
9937
|
-
}).where(
|
|
10289
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9938
10290
|
return;
|
|
9939
10291
|
}
|
|
9940
10292
|
const runId = queueResult.runId;
|
|
@@ -9942,7 +10294,7 @@ var Scheduler = class {
|
|
|
9942
10294
|
lastRunAt: now,
|
|
9943
10295
|
nextRunAt,
|
|
9944
10296
|
updatedAt: now
|
|
9945
|
-
}).where(
|
|
10297
|
+
}).where(eq17(schedules.id, currentSchedule.id)).run();
|
|
9946
10298
|
log13.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
9947
10299
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
9948
10300
|
} catch (err) {
|
|
@@ -9975,7 +10327,7 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
9975
10327
|
}
|
|
9976
10328
|
|
|
9977
10329
|
// src/notifier.ts
|
|
9978
|
-
import { eq as
|
|
10330
|
+
import { eq as eq18, desc as desc7, and as and15, inArray as inArray7, or as or2 } from "drizzle-orm";
|
|
9979
10331
|
import crypto20 from "crypto";
|
|
9980
10332
|
var log15 = createLogger("Notifier");
|
|
9981
10333
|
var Notifier = class {
|
|
@@ -9988,18 +10340,18 @@ var Notifier = class {
|
|
|
9988
10340
|
/** Called after a run completes (success, partial, or failed). */
|
|
9989
10341
|
async onRunCompleted(runId, projectId) {
|
|
9990
10342
|
log15.info("run.completed", { runId, projectId });
|
|
9991
|
-
const notifs = this.db.select().from(notifications).where(
|
|
10343
|
+
const notifs = this.db.select().from(notifications).where(eq18(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
9992
10344
|
if (notifs.length === 0) {
|
|
9993
10345
|
log15.info("notifications.none-enabled", { projectId });
|
|
9994
10346
|
return;
|
|
9995
10347
|
}
|
|
9996
10348
|
log15.info("notifications.found", { projectId, count: notifs.length });
|
|
9997
|
-
const run = this.db.select().from(runs).where(
|
|
10349
|
+
const run = this.db.select().from(runs).where(eq18(runs.id, runId)).get();
|
|
9998
10350
|
if (!run) {
|
|
9999
10351
|
log15.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
10000
10352
|
return;
|
|
10001
10353
|
}
|
|
10002
|
-
const project = this.db.select().from(projects).where(
|
|
10354
|
+
const project = this.db.select().from(projects).where(eq18(projects.id, projectId)).get();
|
|
10003
10355
|
if (!project) {
|
|
10004
10356
|
log15.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
10005
10357
|
return;
|
|
@@ -10052,7 +10404,7 @@ var Notifier = class {
|
|
|
10052
10404
|
* inferred from the absence of a webhook.
|
|
10053
10405
|
*/
|
|
10054
10406
|
async onHealthChecked(projectId, report) {
|
|
10055
|
-
const project = this.db.select().from(projects).where(
|
|
10407
|
+
const project = this.db.select().from(projects).where(eq18(projects.id, projectId)).get();
|
|
10056
10408
|
if (!project) {
|
|
10057
10409
|
log15.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
10058
10410
|
return null;
|
|
@@ -10083,7 +10435,7 @@ var Notifier = class {
|
|
|
10083
10435
|
const status = noSignal ? "warn" : worst ? worst.status : "ok";
|
|
10084
10436
|
const code = noSignal ? "health.no-signal" : worst?.code ?? "health.ok";
|
|
10085
10437
|
const summary = noSignal ? `No health check produced a result (${report.checks.length} skipped) \u2014 health is unknown, not confirmed.` : worst?.summary ?? `All ${graded.length} health check(s) passing.`;
|
|
10086
|
-
const previous = this.db.select().from(doctorHealthState).where(
|
|
10438
|
+
const previous = this.db.select().from(doctorHealthState).where(eq18(doctorHealthState.projectId, projectId)).get();
|
|
10087
10439
|
const previousStatus = previous?.status ?? null;
|
|
10088
10440
|
let event = null;
|
|
10089
10441
|
if (previous === void 0) {
|
|
@@ -10098,13 +10450,13 @@ var Notifier = class {
|
|
|
10098
10450
|
if (previous === void 0) {
|
|
10099
10451
|
this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
|
|
10100
10452
|
} else {
|
|
10101
|
-
this.db.update(doctorHealthState).set(observation).where(
|
|
10453
|
+
this.db.update(doctorHealthState).set(observation).where(eq18(doctorHealthState.projectId, projectId)).run();
|
|
10102
10454
|
}
|
|
10103
10455
|
if (!event) {
|
|
10104
10456
|
log15.info("health.unchanged", { projectId, status, code });
|
|
10105
10457
|
return null;
|
|
10106
10458
|
}
|
|
10107
|
-
const notifs = this.db.select().from(notifications).where(
|
|
10459
|
+
const notifs = this.db.select().from(notifications).where(eq18(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
10108
10460
|
const payload = {
|
|
10109
10461
|
source: "canonry",
|
|
10110
10462
|
event,
|
|
@@ -10128,7 +10480,7 @@ var Notifier = class {
|
|
|
10128
10480
|
delivered += 1;
|
|
10129
10481
|
}
|
|
10130
10482
|
if (delivered > 0) {
|
|
10131
|
-
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(
|
|
10483
|
+
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(eq18(doctorHealthState.projectId, projectId)).run();
|
|
10132
10484
|
}
|
|
10133
10485
|
log15.info("health.notified", { projectId, event, status, code, subscribers: notifs.length, delivered });
|
|
10134
10486
|
return event;
|
|
@@ -10141,11 +10493,11 @@ var Notifier = class {
|
|
|
10141
10493
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
10142
10494
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
10143
10495
|
if (insightEvents.length === 0) return;
|
|
10144
|
-
const notifs = this.db.select().from(notifications).where(
|
|
10496
|
+
const notifs = this.db.select().from(notifications).where(eq18(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
10145
10497
|
if (notifs.length === 0) return;
|
|
10146
|
-
const run = this.db.select().from(runs).where(
|
|
10498
|
+
const run = this.db.select().from(runs).where(eq18(runs.id, runId)).get();
|
|
10147
10499
|
if (!run) return;
|
|
10148
|
-
const project = this.db.select().from(projects).where(
|
|
10500
|
+
const project = this.db.select().from(projects).where(eq18(projects.id, projectId)).get();
|
|
10149
10501
|
if (!project) return;
|
|
10150
10502
|
for (const notif of notifs) {
|
|
10151
10503
|
const config = notif.config;
|
|
@@ -10175,7 +10527,7 @@ var Notifier = class {
|
|
|
10175
10527
|
}
|
|
10176
10528
|
}
|
|
10177
10529
|
computeTransitions(runId, projectId) {
|
|
10178
|
-
const thisRun = this.db.select().from(runs).where(
|
|
10530
|
+
const thisRun = this.db.select().from(runs).where(eq18(runs.id, runId)).get();
|
|
10179
10531
|
if (!thisRun) return [];
|
|
10180
10532
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
10181
10533
|
if (completeness.planned && !completeness.complete) {
|
|
@@ -10186,10 +10538,10 @@ var Notifier = class {
|
|
|
10186
10538
|
});
|
|
10187
10539
|
return [];
|
|
10188
10540
|
}
|
|
10189
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10541
|
+
const groupSiblings = this.db.select().from(runs).where(and15(
|
|
10542
|
+
eq18(runs.projectId, projectId),
|
|
10543
|
+
eq18(runs.kind, thisRun.kind),
|
|
10544
|
+
eq18(runs.createdAt, thisRun.createdAt)
|
|
10193
10545
|
)).all();
|
|
10194
10546
|
const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
|
|
10195
10547
|
if (stillPending) return [];
|
|
@@ -10205,17 +10557,17 @@ var Notifier = class {
|
|
|
10205
10557
|
return candidate.id > best.id ? candidate : best;
|
|
10206
10558
|
});
|
|
10207
10559
|
if (winner.id !== runId) return [];
|
|
10208
|
-
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
10560
|
+
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq18(projects.id, projectId)).get();
|
|
10209
10561
|
const locationCount = Math.max(
|
|
10210
10562
|
1,
|
|
10211
10563
|
(projectLocations?.locations ?? []).length
|
|
10212
10564
|
);
|
|
10213
10565
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
10214
10566
|
const recentRuns = this.db.select().from(runs).where(
|
|
10215
|
-
|
|
10216
|
-
|
|
10217
|
-
|
|
10218
|
-
or2(
|
|
10567
|
+
and15(
|
|
10568
|
+
eq18(runs.projectId, projectId),
|
|
10569
|
+
eq18(runs.kind, thisRun.kind),
|
|
10570
|
+
or2(eq18(runs.status, "completed"), eq18(runs.status, "partial"))
|
|
10219
10571
|
)
|
|
10220
10572
|
).orderBy(desc7(runs.createdAt), desc7(runs.id)).limit(RECENT_FETCH_LIMIT).all();
|
|
10221
10573
|
const groups = groupRunsByCreatedAt(recentRuns);
|
|
@@ -10232,13 +10584,13 @@ var Notifier = class {
|
|
|
10232
10584
|
provider: querySnapshots.provider,
|
|
10233
10585
|
location: querySnapshots.location,
|
|
10234
10586
|
citationState: querySnapshots.citationState
|
|
10235
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
10587
|
+
}).from(querySnapshots).leftJoin(queries, eq18(querySnapshots.queryId, queries.id)).where(inArray7(querySnapshots.runId, currentRunIds)).all();
|
|
10236
10588
|
const previousSnapshots = this.db.select({
|
|
10237
10589
|
queryId: querySnapshots.queryId,
|
|
10238
10590
|
provider: querySnapshots.provider,
|
|
10239
10591
|
location: querySnapshots.location,
|
|
10240
10592
|
citationState: querySnapshots.citationState
|
|
10241
|
-
}).from(querySnapshots).where(
|
|
10593
|
+
}).from(querySnapshots).where(inArray7(querySnapshots.runId, previousRunIds)).all();
|
|
10242
10594
|
const prevMap = /* @__PURE__ */ new Map();
|
|
10243
10595
|
for (const s of previousSnapshots) {
|
|
10244
10596
|
if (s.queryId == null) continue;
|
|
@@ -10315,7 +10667,7 @@ var Notifier = class {
|
|
|
10315
10667
|
};
|
|
10316
10668
|
|
|
10317
10669
|
// src/run-coordinator.ts
|
|
10318
|
-
import { eq as
|
|
10670
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
10319
10671
|
var log16 = createLogger("RunCoordinator");
|
|
10320
10672
|
var RunCoordinator = class {
|
|
10321
10673
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
@@ -10331,7 +10683,7 @@ var RunCoordinator = class {
|
|
|
10331
10683
|
onInsightsGenerated;
|
|
10332
10684
|
onAeroEvent;
|
|
10333
10685
|
async onRunCompleted(runId, projectId) {
|
|
10334
|
-
const runRow = this.db.select().from(runs).where(
|
|
10686
|
+
const runRow = this.db.select().from(runs).where(eq19(runs.id, runId)).get();
|
|
10335
10687
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
10336
10688
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
10337
10689
|
log16.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
@@ -10422,7 +10774,7 @@ var RunCoordinator = class {
|
|
|
10422
10774
|
* so the Aero queue is never starved of a follow-up.
|
|
10423
10775
|
*/
|
|
10424
10776
|
buildDiscoveryAeroContext(runId, projectId, status, error) {
|
|
10425
|
-
const session = this.db.select().from(discoverySessions).where(
|
|
10777
|
+
const session = this.db.select().from(discoverySessions).where(eq19(discoverySessions.runId, runId)).get();
|
|
10426
10778
|
const competitorMap = session ? session.competitorMap : [];
|
|
10427
10779
|
return {
|
|
10428
10780
|
kind: RunKinds["aeo-discover-probe"],
|
|
@@ -10465,7 +10817,7 @@ function analysisResultFromInsights(insights2) {
|
|
|
10465
10817
|
|
|
10466
10818
|
// src/agent/session-registry.ts
|
|
10467
10819
|
import crypto24 from "crypto";
|
|
10468
|
-
import { eq as
|
|
10820
|
+
import { eq as eq21 } from "drizzle-orm";
|
|
10469
10821
|
|
|
10470
10822
|
// src/agent/session.ts
|
|
10471
10823
|
import fs7 from "fs";
|
|
@@ -11594,7 +11946,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
11594
11946
|
|
|
11595
11947
|
// src/agent/memory-store.ts
|
|
11596
11948
|
import crypto23 from "crypto";
|
|
11597
|
-
import { and as
|
|
11949
|
+
import { and as and16, desc as desc8, eq as eq20, like, sql as sql9 } from "drizzle-orm";
|
|
11598
11950
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
11599
11951
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
11600
11952
|
function rowToDto(row) {
|
|
@@ -11608,7 +11960,7 @@ function rowToDto(row) {
|
|
|
11608
11960
|
};
|
|
11609
11961
|
}
|
|
11610
11962
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
11611
|
-
const query = db.select().from(agentMemory).where(
|
|
11963
|
+
const query = db.select().from(agentMemory).where(eq20(agentMemory.projectId, projectId)).orderBy(desc8(agentMemory.updatedAt));
|
|
11612
11964
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
11613
11965
|
return rows.map(rowToDto);
|
|
11614
11966
|
}
|
|
@@ -11639,12 +11991,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
11639
11991
|
updatedAt: now
|
|
11640
11992
|
}
|
|
11641
11993
|
}).run();
|
|
11642
|
-
const row = db.select().from(agentMemory).where(
|
|
11994
|
+
const row = db.select().from(agentMemory).where(and16(eq20(agentMemory.projectId, args.projectId), eq20(agentMemory.key, args.key))).get();
|
|
11643
11995
|
if (!row) throw new Error("memory upsert produced no row");
|
|
11644
11996
|
return rowToDto(row);
|
|
11645
11997
|
}
|
|
11646
11998
|
function deleteMemoryEntry(db, projectId, key) {
|
|
11647
|
-
const result = db.delete(agentMemory).where(
|
|
11999
|
+
const result = db.delete(agentMemory).where(and16(eq20(agentMemory.projectId, projectId), eq20(agentMemory.key, key))).run();
|
|
11648
12000
|
const changes = result.changes ?? 0;
|
|
11649
12001
|
return changes > 0;
|
|
11650
12002
|
}
|
|
@@ -11673,8 +12025,8 @@ function writeCompactionNote(db, args) {
|
|
|
11673
12025
|
}).run();
|
|
11674
12026
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
11675
12027
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
11676
|
-
|
|
11677
|
-
|
|
12028
|
+
and16(
|
|
12029
|
+
eq20(agentMemory.projectId, args.projectId),
|
|
11678
12030
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
11679
12031
|
)
|
|
11680
12032
|
).orderBy(desc8(agentMemory.updatedAt)).all();
|
|
@@ -11682,7 +12034,7 @@ function writeCompactionNote(db, args) {
|
|
|
11682
12034
|
if (stale.length > 0) {
|
|
11683
12035
|
tx.delete(agentMemory).where(sql9`${agentMemory.id} IN (${sql9.join(stale.map((s) => sql9`${s}`), sql9`, `)})`).run();
|
|
11684
12036
|
}
|
|
11685
|
-
const row = tx.select().from(agentMemory).where(
|
|
12037
|
+
const row = tx.select().from(agentMemory).where(and16(eq20(agentMemory.projectId, args.projectId), eq20(agentMemory.key, key))).get();
|
|
11686
12038
|
if (row) inserted = rowToDto(row);
|
|
11687
12039
|
});
|
|
11688
12040
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -11909,7 +12261,7 @@ var SessionRegistry = class {
|
|
|
11909
12261
|
modelProvider: effectiveProvider,
|
|
11910
12262
|
modelId: effectiveModelId,
|
|
11911
12263
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11912
|
-
}).where(
|
|
12264
|
+
}).where(eq21(agentSessions.projectId, projectId)).run();
|
|
11913
12265
|
}
|
|
11914
12266
|
const agent2 = createAeroSession({
|
|
11915
12267
|
projectName,
|
|
@@ -12143,7 +12495,7 @@ ${lines.join("\n")}
|
|
|
12143
12495
|
modelProvider: nextProvider,
|
|
12144
12496
|
modelId: nextModelId,
|
|
12145
12497
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12146
|
-
}).where(
|
|
12498
|
+
}).where(eq21(agentSessions.projectId, projectId)).run();
|
|
12147
12499
|
}
|
|
12148
12500
|
/** Persist a session's transcript back to the DB. Call after any run settles. */
|
|
12149
12501
|
save(projectName) {
|
|
@@ -12307,11 +12659,11 @@ ${lines.join("\n")}
|
|
|
12307
12659
|
return id;
|
|
12308
12660
|
}
|
|
12309
12661
|
tryResolveProjectId(projectName) {
|
|
12310
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
12662
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq21(projects.name, projectName)).get();
|
|
12311
12663
|
return row?.id;
|
|
12312
12664
|
}
|
|
12313
12665
|
loadRow(projectId) {
|
|
12314
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
12666
|
+
const row = this.opts.db.select().from(agentSessions).where(eq21(agentSessions.projectId, projectId)).get();
|
|
12315
12667
|
return row ?? null;
|
|
12316
12668
|
}
|
|
12317
12669
|
insertRow(params) {
|
|
@@ -12330,14 +12682,14 @@ ${lines.join("\n")}
|
|
|
12330
12682
|
}
|
|
12331
12683
|
updateRow(projectId, patch) {
|
|
12332
12684
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12333
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
12685
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq21(agentSessions.projectId, projectId)).run();
|
|
12334
12686
|
}
|
|
12335
12687
|
};
|
|
12336
12688
|
|
|
12337
12689
|
// src/agent/agent-routes.ts
|
|
12338
|
-
import { eq as
|
|
12690
|
+
import { eq as eq22 } from "drizzle-orm";
|
|
12339
12691
|
function resolveProject(db, name) {
|
|
12340
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
12692
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq22(projects.name, name)).get();
|
|
12341
12693
|
if (!row) throw notFound("project", name);
|
|
12342
12694
|
return row;
|
|
12343
12695
|
}
|
|
@@ -12346,7 +12698,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
12346
12698
|
"/projects/:name/agent/transcript",
|
|
12347
12699
|
async (request) => {
|
|
12348
12700
|
const project = resolveProject(opts.db, request.params.name);
|
|
12349
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
12701
|
+
const row = opts.db.select().from(agentSessions).where(eq22(agentSessions.projectId, project.id)).get();
|
|
12350
12702
|
if (!row) {
|
|
12351
12703
|
return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
12352
12704
|
}
|
|
@@ -12370,7 +12722,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
12370
12722
|
async (request) => {
|
|
12371
12723
|
const project = resolveProject(opts.db, request.params.name);
|
|
12372
12724
|
opts.sessionRegistry.reset(project.name);
|
|
12373
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
12725
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(agentSessions.projectId, project.id)).run();
|
|
12374
12726
|
return { status: "reset" };
|
|
12375
12727
|
}
|
|
12376
12728
|
);
|
|
@@ -12697,16 +13049,16 @@ Your previous reply was not valid JSON. Return ONLY a JSON object with exactly t
|
|
|
12697
13049
|
import { runAeoAudit } from "@canonry/aeo-audit";
|
|
12698
13050
|
|
|
12699
13051
|
// src/site-fetch.ts
|
|
12700
|
-
import
|
|
13052
|
+
import https2 from "https";
|
|
12701
13053
|
var FETCH_TIMEOUT_MS2 = 1e4;
|
|
12702
13054
|
var MAX_TEXT_LENGTH = 4e3;
|
|
12703
13055
|
var MAX_BODY_BYTES = 512e3;
|
|
12704
|
-
var
|
|
13056
|
+
var USER_AGENT2 = "Canonry/1.0 (site-analysis)";
|
|
12705
13057
|
function fetchWithPinnedAddress(target) {
|
|
12706
13058
|
return new Promise((resolve) => {
|
|
12707
13059
|
const port = target.url.port ? Number(target.url.port) : 443;
|
|
12708
13060
|
const path10 = target.url.pathname + target.url.search;
|
|
12709
|
-
const req =
|
|
13061
|
+
const req = https2.request(
|
|
12710
13062
|
{
|
|
12711
13063
|
hostname: target.address,
|
|
12712
13064
|
family: target.family,
|
|
@@ -12718,7 +13070,7 @@ function fetchWithPinnedAddress(target) {
|
|
|
12718
13070
|
// SNI for TLS
|
|
12719
13071
|
headers: {
|
|
12720
13072
|
Host: target.url.host,
|
|
12721
|
-
"User-Agent":
|
|
13073
|
+
"User-Agent": USER_AGENT2,
|
|
12722
13074
|
Accept: "text/html"
|
|
12723
13075
|
}
|
|
12724
13076
|
},
|
|
@@ -13313,20 +13665,20 @@ function clipText(value, length) {
|
|
|
13313
13665
|
}
|
|
13314
13666
|
|
|
13315
13667
|
// src/research-runner.ts
|
|
13316
|
-
import { and as
|
|
13668
|
+
import { and as and17, eq as eq23, inArray as inArray8, sql as sql10 } from "drizzle-orm";
|
|
13317
13669
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
13318
13670
|
function finalResearchRunStatus(completed, failed) {
|
|
13319
13671
|
if (failed === 0) return ResearchRunStatuses.completed;
|
|
13320
13672
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
13321
13673
|
}
|
|
13322
13674
|
async function executeResearchRun(db, registry, runId, projectId) {
|
|
13323
|
-
const run = db.select().from(researchRuns).where(
|
|
13675
|
+
const run = db.select().from(researchRuns).where(and17(eq23(researchRuns.id, runId), eq23(researchRuns.projectId, projectId))).get();
|
|
13324
13676
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
13325
|
-
const project = db.select().from(projects).where(
|
|
13677
|
+
const project = db.select().from(projects).where(eq23(projects.id, projectId)).get();
|
|
13326
13678
|
if (!project) return;
|
|
13327
13679
|
const provider = registry.get(run.provider);
|
|
13328
13680
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13329
|
-
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(
|
|
13681
|
+
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and17(eq23(researchRuns.id, runId), eq23(researchRuns.status, ResearchRunStatuses.queued))).run();
|
|
13330
13682
|
if (claim.changes !== 1) return;
|
|
13331
13683
|
let reserved = 0;
|
|
13332
13684
|
let dispatched = 0;
|
|
@@ -13350,8 +13702,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
13350
13702
|
}
|
|
13351
13703
|
reserved = run.totalQueries;
|
|
13352
13704
|
reservation = { scope, period };
|
|
13353
|
-
const rows = db.select().from(researchRunQueries).where(
|
|
13354
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
13705
|
+
const rows = db.select().from(researchRunQueries).where(eq23(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
|
|
13706
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq23(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
13355
13707
|
const domains = effectiveDomains(project);
|
|
13356
13708
|
const brands = effectiveBrandNames(project);
|
|
13357
13709
|
const config = { ...provider.config, model: run.resolvedModel };
|
|
@@ -13359,7 +13711,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
13359
13711
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
13360
13712
|
try {
|
|
13361
13713
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
13362
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(
|
|
13714
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and17(eq23(researchRunQueries.id, row.id), eq23(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
|
|
13363
13715
|
const raw = await gate.run(async () => {
|
|
13364
13716
|
dispatched++;
|
|
13365
13717
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -13386,7 +13738,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
13386
13738
|
citationState: determineCitationState(normalized, domains),
|
|
13387
13739
|
rawResponse: raw.rawResponse,
|
|
13388
13740
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13389
|
-
}).where(
|
|
13741
|
+
}).where(and17(eq23(researchRunQueries.id, row.id), eq23(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
13390
13742
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
13391
13743
|
} catch (error) {
|
|
13392
13744
|
try {
|
|
@@ -13410,18 +13762,18 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
13410
13762
|
}
|
|
13411
13763
|
}
|
|
13412
13764
|
function incrementResearchProgress(db, runId, column) {
|
|
13413
|
-
db.update(researchRuns).set({ [column]: sql10`${researchRuns[column]} + 1` }).where(
|
|
13765
|
+
db.update(researchRuns).set({ [column]: sql10`${researchRuns[column]} + 1` }).where(eq23(researchRuns.id, runId)).run();
|
|
13414
13766
|
}
|
|
13415
13767
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
13416
13768
|
const message = error instanceof Error ? error.message : String(error);
|
|
13417
|
-
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
13769
|
+
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and17(eq23(researchRunQueries.id, queryId), inArray8(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
13418
13770
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
13419
13771
|
}
|
|
13420
13772
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
13421
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
13773
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and17(eq23(researchRunQueries.researchRunId, runId), inArray8(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
13422
13774
|
}
|
|
13423
13775
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
13424
|
-
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(
|
|
13776
|
+
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq23(researchRunQueries.researchRunId, runId)).all();
|
|
13425
13777
|
const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
|
|
13426
13778
|
const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
|
|
13427
13779
|
db.update(researchRuns).set({
|
|
@@ -13430,7 +13782,7 @@ function finalizeResearchRun(db, runId, fatalError) {
|
|
|
13430
13782
|
failedQueries: failed,
|
|
13431
13783
|
error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
|
|
13432
13784
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13433
|
-
}).where(
|
|
13785
|
+
}).where(eq23(researchRuns.id, runId)).run();
|
|
13434
13786
|
}
|
|
13435
13787
|
|
|
13436
13788
|
// src/server.ts
|
|
@@ -13468,6 +13820,13 @@ function resolveDashboardShowResourceLinks(env, config) {
|
|
|
13468
13820
|
function resolveDashboardShowUpdateNotification(env, config) {
|
|
13469
13821
|
return parseBooleanEnv(env.CANONRY_DASHBOARD_SHOW_UPDATE_NOTIFICATION) ?? config.dashboard?.showUpdateNotification ?? true;
|
|
13470
13822
|
}
|
|
13823
|
+
function parseDashboardOnboardingMode(value) {
|
|
13824
|
+
const normalized = value?.trim().toLowerCase();
|
|
13825
|
+
return normalized === "legacy" || normalized === "platform" || normalized === "auto" ? normalized : void 0;
|
|
13826
|
+
}
|
|
13827
|
+
function resolveDashboardOnboardingMode(env, config) {
|
|
13828
|
+
return parseDashboardOnboardingMode(env.CANONRY_ONBOARDING_MODE) ?? config.dashboard?.onboardingMode;
|
|
13829
|
+
}
|
|
13471
13830
|
var API_ADAPTERS = [
|
|
13472
13831
|
geminiAdapter,
|
|
13473
13832
|
openaiAdapter,
|
|
@@ -13745,7 +14104,7 @@ async function createServer(opts) {
|
|
|
13745
14104
|
(runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
|
|
13746
14105
|
async (ctx) => {
|
|
13747
14106
|
if (!sessionRegistry) return;
|
|
13748
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
14107
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq24(projects.id, ctx.projectId)).get();
|
|
13749
14108
|
if (!project) return;
|
|
13750
14109
|
let content;
|
|
13751
14110
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -14095,7 +14454,7 @@ async function createServer(opts) {
|
|
|
14095
14454
|
void (async () => {
|
|
14096
14455
|
try {
|
|
14097
14456
|
const report = await aeroClient.runDoctor({ project: projectName });
|
|
14098
|
-
const project = opts.db.select().from(projects).where(
|
|
14457
|
+
const project = opts.db.select().from(projects).where(eq24(projects.name, projectName)).get();
|
|
14099
14458
|
if (!project) {
|
|
14100
14459
|
app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
|
|
14101
14460
|
return;
|
|
@@ -14123,9 +14482,9 @@ async function createServer(opts) {
|
|
|
14123
14482
|
});
|
|
14124
14483
|
if (!probed) return;
|
|
14125
14484
|
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
|
|
14485
|
+
and18(
|
|
14486
|
+
eq24(ccReleaseSyncs.release, probed.release),
|
|
14487
|
+
eq24(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
14129
14488
|
)
|
|
14130
14489
|
).limit(1).get();
|
|
14131
14490
|
if (alreadySynced) {
|
|
@@ -14365,6 +14724,7 @@ async function createServer(opts) {
|
|
|
14365
14724
|
const dashboardRequirePassword = resolveDashboardRequirePassword(process.env, opts.config);
|
|
14366
14725
|
const dashboardShowResourceLinks = resolveDashboardShowResourceLinks(process.env, opts.config);
|
|
14367
14726
|
const dashboardShowUpdateNotification = resolveDashboardShowUpdateNotification(process.env, opts.config);
|
|
14727
|
+
const dashboardOnboardingMode = resolveDashboardOnboardingMode(process.env, opts.config);
|
|
14368
14728
|
app.log.info(
|
|
14369
14729
|
{ dashboardRequirePassword },
|
|
14370
14730
|
"Dashboard password gate resolved"
|
|
@@ -14373,7 +14733,7 @@ async function createServer(opts) {
|
|
|
14373
14733
|
const googlePublicUrl = resolveGooglePublicUrl(opts.config, basePath);
|
|
14374
14734
|
if (opts.config.apiKey) {
|
|
14375
14735
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
14376
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
14736
|
+
const existing = opts.db.select().from(apiKeys).where(eq24(apiKeys.keyHash, keyHash)).get();
|
|
14377
14737
|
if (!existing) {
|
|
14378
14738
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
14379
14739
|
opts.db.insert(apiKeys).values({
|
|
@@ -14425,7 +14785,7 @@ async function createServer(opts) {
|
|
|
14425
14785
|
};
|
|
14426
14786
|
const getDefaultApiKey = () => {
|
|
14427
14787
|
if (!opts.config.apiKey) return void 0;
|
|
14428
|
-
return opts.db.select().from(apiKeys).where(
|
|
14788
|
+
return opts.db.select().from(apiKeys).where(eq24(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
14429
14789
|
};
|
|
14430
14790
|
const createPasswordSession = (reply) => {
|
|
14431
14791
|
const key = getDefaultApiKey();
|
|
@@ -14449,7 +14809,7 @@ async function createServer(opts) {
|
|
|
14449
14809
|
if (!header) return false;
|
|
14450
14810
|
const parts = header.split(" ");
|
|
14451
14811
|
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
14452
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
14812
|
+
const key = opts.db.select().from(apiKeys).where(eq24(apiKeys.keyHash, hashApiKey(parts[1]))).get();
|
|
14453
14813
|
return Boolean(key && !key.revokedAt);
|
|
14454
14814
|
};
|
|
14455
14815
|
const namedAccountsInUse = () => anyUsersExist(opts.db);
|
|
@@ -14539,12 +14899,12 @@ async function createServer(opts) {
|
|
|
14539
14899
|
return reply.send({ authenticated: true });
|
|
14540
14900
|
}
|
|
14541
14901
|
if (apiKey) {
|
|
14542
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
14902
|
+
const key = opts.db.select().from(apiKeys).where(eq24(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
14543
14903
|
if (!key || key.revokedAt) {
|
|
14544
14904
|
const err2 = authInvalid();
|
|
14545
14905
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
14546
14906
|
}
|
|
14547
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
14907
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(apiKeys.id, key.id)).run();
|
|
14548
14908
|
const sessionId = createSession(key.id);
|
|
14549
14909
|
reply.header(
|
|
14550
14910
|
"set-cookie",
|
|
@@ -14886,7 +15246,7 @@ async function createServer(opts) {
|
|
|
14886
15246
|
...inspectOpts,
|
|
14887
15247
|
config: opts.config
|
|
14888
15248
|
}).then(() => {
|
|
14889
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
15249
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq24(runs.id, runId)).get();
|
|
14890
15250
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
14891
15251
|
return maybeRefreshGscCoverage(
|
|
14892
15252
|
opts.db,
|
|
@@ -15163,6 +15523,9 @@ async function createServer(opts) {
|
|
|
15163
15523
|
const clientConfig = {};
|
|
15164
15524
|
if (basePath) clientConfig.basePath = basePath;
|
|
15165
15525
|
const dashboardConfig = {};
|
|
15526
|
+
if (dashboardOnboardingMode) {
|
|
15527
|
+
dashboardConfig.onboardingMode = dashboardOnboardingMode;
|
|
15528
|
+
}
|
|
15166
15529
|
if (!dashboardShowResourceLinks) {
|
|
15167
15530
|
dashboardConfig.showResourceLinks = false;
|
|
15168
15531
|
}
|
|
@@ -15264,7 +15627,7 @@ async function createServer(opts) {
|
|
|
15264
15627
|
}
|
|
15265
15628
|
checkLatestVersionForServer();
|
|
15266
15629
|
scheduler.start();
|
|
15267
|
-
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(
|
|
15630
|
+
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq24(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
15268
15631
|
dispatchResearchRun(run.id, run.projectId);
|
|
15269
15632
|
}
|
|
15270
15633
|
app.addHook("onClose", async () => {
|