@canonry/canonry 4.154.0 → 4.155.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/assets/agent-workspace/AGENTS.md +2 -2
- package/assets/agent-workspace/skills/aero/references/orchestration.md +3 -3
- package/assets/agent-workspace/skills/canonry/SKILL.md +6 -21
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +14 -6
- package/assets/assets/{AuditHistoryPanel-WWT1n8jp.js → AuditHistoryPanel-BlbMBxOY.js} +1 -1
- package/assets/assets/BacklinksPage-DA9EaV9q.js +1 -0
- package/assets/assets/HistoryPage-BTrIHjMQ.js +1 -0
- package/assets/assets/MeasurementPropertyPage-CN6o5xFg.js +1 -0
- package/assets/assets/{ProjectPage-BR5ECwUP.js → ProjectPage-CsDbgp_0.js} +9 -9
- package/assets/assets/{RunRow-OE5tiuih.js → RunRow-ch_OlCPs.js} +1 -1
- package/assets/assets/RunsPage-T89Y_yS8.js +1 -0
- package/assets/assets/SettingsPage-DnmEq3F9.js +1 -0
- package/assets/assets/TrafficPage-CW1UhaJt.js +1 -0
- package/assets/assets/TrafficSourceDetailPage-r4Ji3wJm.js +1 -0
- package/assets/assets/extract-error-message-a8EErBoZ.js +1 -0
- package/assets/assets/index-BKq6k5Of.js +83 -0
- package/assets/assets/vendor-lucide-ByU7vBHt.js +1 -0
- package/assets/assets/vendor-yaml-GWJz5H2G.js +131 -0
- package/assets/index.html +3 -1
- package/dist/{chunk-ANSQ6JNO.js → chunk-OSHRDV36.js} +317 -4
- package/dist/{chunk-NNW7TJUZ.js → chunk-VIKPFI6F.js} +200 -5
- package/dist/{chunk-IFZS2TXV.js → chunk-VOL2BSM5.js} +1133 -104
- package/dist/{chunk-X5I7CAXH.js → chunk-YSESZNVT.js} +609 -165
- package/dist/cli.js +342 -7
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-B5AETQ5N.js → intelligence-service-G4NV3X36.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +9 -9
- package/assets/assets/BacklinksPage-DIjUnsVL.js +0 -1
- package/assets/assets/HistoryPage-CrIBhGuW.js +0 -1
- package/assets/assets/MeasurementPropertyPage-uN4BWigS.js +0 -1
- package/assets/assets/RunsPage-C6xBmjps.js +0 -1
- package/assets/assets/SettingsPage-CUjjr8U0.js +0 -1
- package/assets/assets/TrafficPage-DrEfsHiE.js +0 -1
- package/assets/assets/TrafficSourceDetailPage-B3DX5BEk.js +0 -1
- package/assets/assets/arrow-left-CLmqvCqU.js +0 -1
- package/assets/assets/extract-error-message-BEBd1a5v.js +0 -1
- package/assets/assets/index-CsM-Xxia.js +0 -213
- package/assets/assets/refresh-cw-DYSS2Wd-.js +0 -1
- package/assets/assets/trash-2-VoiF_ckU.js +0 -1
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
loadConfig,
|
|
11
11
|
loadConfigRaw,
|
|
12
12
|
saveConfigPatch
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-OSHRDV36.js";
|
|
14
14
|
import {
|
|
15
15
|
CC_CACHE_DIR,
|
|
16
16
|
DUCKDB_SPEC,
|
|
@@ -124,9 +124,16 @@ import {
|
|
|
124
124
|
schedules,
|
|
125
125
|
siteAuditPages,
|
|
126
126
|
siteAuditSnapshots,
|
|
127
|
+
siteCrawlAttempts,
|
|
128
|
+
siteCrawlEdges,
|
|
129
|
+
siteCrawlEventReceipts,
|
|
130
|
+
siteCrawlFindings,
|
|
131
|
+
siteCrawlPages,
|
|
132
|
+
siteCrawlRunRequests,
|
|
133
|
+
siteCrawlSnapshots,
|
|
127
134
|
toAlertView,
|
|
128
135
|
usageCounters
|
|
129
|
-
} from "./chunk-
|
|
136
|
+
} from "./chunk-VOL2BSM5.js";
|
|
130
137
|
import {
|
|
131
138
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
132
139
|
AGENT_PROVIDER_IDS,
|
|
@@ -151,6 +158,10 @@ import {
|
|
|
151
158
|
RunKinds,
|
|
152
159
|
RunStatuses,
|
|
153
160
|
RunTriggers,
|
|
161
|
+
SITE_AUDIT_DEFAULT_EDGE_LIMIT,
|
|
162
|
+
SITE_AUDIT_DEFAULT_PAGE_LIMIT,
|
|
163
|
+
SITE_AUDIT_MAX_EDGE_LIMIT,
|
|
164
|
+
SITE_AUDIT_MAX_PAGE_LIMIT,
|
|
154
165
|
SKILL_MANIFEST_FILENAME,
|
|
155
166
|
SchedulableRunKinds,
|
|
156
167
|
SkillsClients,
|
|
@@ -200,6 +211,7 @@ import {
|
|
|
200
211
|
normalizeIdTokens,
|
|
201
212
|
normalizeMeasurementExecutionQueryText,
|
|
202
213
|
normalizeServedModel,
|
|
214
|
+
normalizeSiteAuditRunRequest,
|
|
203
215
|
normalizeUrlPath,
|
|
204
216
|
notFound,
|
|
205
217
|
parseMeasurementRunManifestV1,
|
|
@@ -210,6 +222,7 @@ import {
|
|
|
210
222
|
resolveSnapshotRequestQueries,
|
|
211
223
|
serializeForInlineScript,
|
|
212
224
|
serializeRunError,
|
|
225
|
+
siteAuditRequestIdentity,
|
|
213
226
|
skillsClientSchema,
|
|
214
227
|
splitList,
|
|
215
228
|
startOfDayHourInTimeZone,
|
|
@@ -218,7 +231,7 @@ import {
|
|
|
218
231
|
validationError,
|
|
219
232
|
winnabilityClassLabel,
|
|
220
233
|
withRetry
|
|
221
|
-
} from "./chunk-
|
|
234
|
+
} from "./chunk-VIKPFI6F.js";
|
|
222
235
|
|
|
223
236
|
// src/telemetry.ts
|
|
224
237
|
import crypto from "crypto";
|
|
@@ -553,7 +566,7 @@ import crypto24 from "crypto";
|
|
|
553
566
|
import fs8 from "fs";
|
|
554
567
|
import path9 from "path";
|
|
555
568
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
556
|
-
import { and as
|
|
569
|
+
import { and as and16, eq as eq22 } from "drizzle-orm";
|
|
557
570
|
import Fastify from "fastify";
|
|
558
571
|
import os5 from "os";
|
|
559
572
|
|
|
@@ -3547,11 +3560,23 @@ var JobRunner = class {
|
|
|
3547
3560
|
this.registry = registry;
|
|
3548
3561
|
}
|
|
3549
3562
|
recoverStaleRuns() {
|
|
3550
|
-
const stale = this.db.select({ id: runs.id, status: runs.status }).from(runs).where(inArray(runs.status, ["running", "queued"])).all();
|
|
3563
|
+
const stale = this.db.select({ id: runs.id, projectId: runs.projectId, kind: runs.kind, status: runs.status }).from(runs).where(inArray(runs.status, ["running", "queued"])).all();
|
|
3551
3564
|
if (stale.length === 0) return;
|
|
3552
3565
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3553
3566
|
for (const run of stale) {
|
|
3554
|
-
this.db.
|
|
3567
|
+
const recovered = this.db.transaction((tx) => {
|
|
3568
|
+
const claim = tx.update(runs).set({ status: "failed", finishedAt: now, error: "Server restarted while run was in progress" }).where(and2(eq2(runs.id, run.id), eq2(runs.status, run.status))).run();
|
|
3569
|
+
if (claim.changes === 0) return false;
|
|
3570
|
+
if (run.kind === RunKinds["site-audit"]) {
|
|
3571
|
+
tx.update(siteCrawlAttempts).set({ state: "failed", finishedAt: now, updatedAt: now, error: "Server restarted while run was in progress" }).where(and2(
|
|
3572
|
+
eq2(siteCrawlAttempts.projectId, run.projectId),
|
|
3573
|
+
eq2(siteCrawlAttempts.runId, run.id),
|
|
3574
|
+
inArray(siteCrawlAttempts.state, ["queued", "running"])
|
|
3575
|
+
)).run();
|
|
3576
|
+
}
|
|
3577
|
+
return true;
|
|
3578
|
+
});
|
|
3579
|
+
if (!recovered) continue;
|
|
3555
3580
|
log.warn("run.recovered-stale", { runId: run.id, previousStatus: run.status });
|
|
3556
3581
|
}
|
|
3557
3582
|
}
|
|
@@ -4250,8 +4275,19 @@ function writeCoverageSnapshot(db, projectId, runId, opts = {}) {
|
|
|
4250
4275
|
const since = isoDaysAgo(windowDays);
|
|
4251
4276
|
const pageRows = db.select({ page: gscSearchData.page, impressions: gscSearchData.impressions }).from(gscSearchData).where(and3(eq3(gscSearchData.projectId, projectId), gte(gscSearchData.date, since))).all();
|
|
4252
4277
|
const allInspections = db.select().from(gscUrlInspections).where(eq3(gscUrlInspections.projectId, projectId)).all();
|
|
4278
|
+
const latestCrawl = db.select({ runId: siteCrawlSnapshots.runId, attemptId: siteCrawlSnapshots.attemptId }).from(siteCrawlSnapshots).innerJoin(runs, eq3(siteCrawlSnapshots.runId, runs.id)).where(and3(
|
|
4279
|
+
eq3(siteCrawlSnapshots.projectId, projectId),
|
|
4280
|
+
eq3(siteCrawlSnapshots.complete, true),
|
|
4281
|
+
eq3(runs.status, "completed")
|
|
4282
|
+
)).orderBy(desc(siteCrawlSnapshots.createdAt)).limit(1).get();
|
|
4283
|
+
const hasCrawlHistory = Boolean(db.select({ id: siteCrawlSnapshots.id }).from(siteCrawlSnapshots).where(eq3(siteCrawlSnapshots.projectId, projectId)).limit(1).get());
|
|
4253
4284
|
const latestAudit = db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).where(eq3(siteAuditSnapshots.projectId, projectId)).orderBy(desc(siteAuditSnapshots.createdAt)).limit(1).get();
|
|
4254
|
-
const sitemapUrls =
|
|
4285
|
+
const sitemapUrls = latestCrawl?.attemptId ? db.select({ url: siteCrawlPages.url }).from(siteCrawlPages).where(and3(
|
|
4286
|
+
eq3(siteCrawlPages.projectId, projectId),
|
|
4287
|
+
eq3(siteCrawlPages.runId, latestCrawl.runId),
|
|
4288
|
+
eq3(siteCrawlPages.attemptId, latestCrawl.attemptId),
|
|
4289
|
+
eq3(siteCrawlPages.inventoryEligible, true)
|
|
4290
|
+
)).all() : !hasCrawlHistory && latestAudit ? db.select({ url: siteAuditPages.url }).from(siteAuditPages).where(eq3(siteAuditPages.runId, latestAudit.runId)).all() : [];
|
|
4255
4291
|
const impressionsByKey = /* @__PURE__ */ new Map();
|
|
4256
4292
|
for (const row of pageRows) {
|
|
4257
4293
|
const key = pageKey(row.page);
|
|
@@ -6895,164 +6931,549 @@ function buildDiscoveryInsightTitle(input) {
|
|
|
6895
6931
|
|
|
6896
6932
|
// src/execute-site-audit.ts
|
|
6897
6933
|
import crypto16 from "crypto";
|
|
6898
|
-
import { eq as eq13 } from "drizzle-orm";
|
|
6899
|
-
import {
|
|
6934
|
+
import { and as and10, eq as eq13, sql as sql5 } from "drizzle-orm";
|
|
6935
|
+
import { runSiteCrawl } from "@canonry/aeo-audit";
|
|
6900
6936
|
var log12 = createLogger("SiteAudit");
|
|
6901
|
-
var
|
|
6902
|
-
|
|
6937
|
+
var SiteAuditCancelledError = class extends Error {
|
|
6938
|
+
name = "SiteAuditCancelledError";
|
|
6939
|
+
};
|
|
6903
6940
|
function toHomepageUrl(canonicalDomain) {
|
|
6904
6941
|
const trimmed = canonicalDomain.trim().replace(/\/+$/, "");
|
|
6905
6942
|
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
6906
6943
|
}
|
|
6907
6944
|
async function assertSiteAuditUrlAllowed(rawUrl, field) {
|
|
6908
6945
|
const check = await resolveWebhookTarget(rawUrl);
|
|
6909
|
-
if (!check.ok) {
|
|
6910
|
-
throw new Error(`${field} ${check.message.replace(/^"url" /, "")}`);
|
|
6911
|
-
}
|
|
6946
|
+
if (!check.ok) throw new Error(`${field} ${check.message.replace(/^"url" /, "")}`);
|
|
6912
6947
|
}
|
|
6913
6948
|
function clampSiteAuditLimit(limit) {
|
|
6914
6949
|
if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_PAGE_LIMIT;
|
|
6915
6950
|
return Math.max(1, Math.min(SITE_AUDIT_MAX_PAGE_LIMIT, Math.floor(limit)));
|
|
6916
6951
|
}
|
|
6952
|
+
function clampSiteAuditEdgeLimit(limit) {
|
|
6953
|
+
if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_EDGE_LIMIT;
|
|
6954
|
+
return Math.max(1, Math.min(SITE_AUDIT_MAX_EDGE_LIMIT, Math.floor(limit)));
|
|
6955
|
+
}
|
|
6917
6956
|
function toPageFactor(factor) {
|
|
6918
|
-
return {
|
|
6919
|
-
id: factor.id,
|
|
6920
|
-
name: factor.name,
|
|
6921
|
-
weight: factor.weight,
|
|
6922
|
-
score: factor.score
|
|
6923
|
-
};
|
|
6957
|
+
return { id: factor.id, name: factor.name, weight: factor.weight, score: factor.score };
|
|
6924
6958
|
}
|
|
6925
6959
|
function computeFactorAverages(pages) {
|
|
6926
6960
|
const byId = /* @__PURE__ */ new Map();
|
|
6927
6961
|
for (const page of pages) {
|
|
6928
|
-
if (
|
|
6929
|
-
for (const factor of page.factors) {
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
byId.set(factor.id, entry);
|
|
6934
|
-
}
|
|
6935
|
-
entry.scores.push(factor.score);
|
|
6962
|
+
if (!page.audit) continue;
|
|
6963
|
+
for (const factor of page.audit.factors) {
|
|
6964
|
+
const current = byId.get(factor.id) ?? { name: factor.name, weight: factor.weight, scores: [], pass: 0, partial: 0, fail: 0 };
|
|
6965
|
+
byId.set(factor.id, current);
|
|
6966
|
+
current.scores.push(factor.score);
|
|
6936
6967
|
const status = factorStatusFromScore(factor.score);
|
|
6937
|
-
if (status === "pass")
|
|
6938
|
-
else if (status === "partial")
|
|
6939
|
-
else
|
|
6968
|
+
if (status === "pass") current.pass++;
|
|
6969
|
+
else if (status === "partial") current.partial++;
|
|
6970
|
+
else current.fail++;
|
|
6940
6971
|
}
|
|
6941
6972
|
}
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
summaries.push({
|
|
6973
|
+
return [...byId.entries()].map(([id, value]) => {
|
|
6974
|
+
const avgScore = Math.round(value.scores.reduce((sum, score) => sum + score, 0) / value.scores.length);
|
|
6975
|
+
return {
|
|
6946
6976
|
id,
|
|
6947
|
-
name:
|
|
6948
|
-
weight:
|
|
6977
|
+
name: value.name,
|
|
6978
|
+
weight: value.weight,
|
|
6949
6979
|
avgScore,
|
|
6950
6980
|
status: factorStatusFromScore(avgScore),
|
|
6951
|
-
pagesPassing:
|
|
6952
|
-
pagesPartial:
|
|
6953
|
-
pagesFailing:
|
|
6954
|
-
}
|
|
6981
|
+
pagesPassing: value.pass,
|
|
6982
|
+
pagesPartial: value.partial,
|
|
6983
|
+
pagesFailing: value.fail
|
|
6984
|
+
};
|
|
6985
|
+
}).sort((a, b) => b.weight - a.weight || a.name.localeCompare(b.name));
|
|
6986
|
+
}
|
|
6987
|
+
function pagePath(page) {
|
|
6988
|
+
if (page.path) return page.path;
|
|
6989
|
+
try {
|
|
6990
|
+
return new URL(page.finalUrl ?? page.requestedUrl).pathname || "/";
|
|
6991
|
+
} catch {
|
|
6992
|
+
return "/";
|
|
6955
6993
|
}
|
|
6956
|
-
summaries.sort((a, b) => b.weight - a.weight || a.name.localeCompare(b.name));
|
|
6957
|
-
return summaries;
|
|
6958
6994
|
}
|
|
6959
|
-
|
|
6960
|
-
const
|
|
6961
|
-
|
|
6995
|
+
function parentPath(page) {
|
|
6996
|
+
const path10 = pagePath(page);
|
|
6997
|
+
if (path10 === "/") return "/";
|
|
6998
|
+
const trimmed = path10.endsWith("/") ? path10.slice(0, -1) : path10;
|
|
6999
|
+
const slash = trimmed.lastIndexOf("/");
|
|
7000
|
+
return slash <= 0 ? "/" : trimmed.slice(0, slash) || "/";
|
|
7001
|
+
}
|
|
7002
|
+
function pageDiscoverySource(page) {
|
|
7003
|
+
if (page.provenance.root) return "root";
|
|
7004
|
+
if (page.provenance.sitemapSources.length > 0) return "sitemap";
|
|
7005
|
+
return "link";
|
|
7006
|
+
}
|
|
7007
|
+
function pageAuditState(page) {
|
|
7008
|
+
if (page.audit) return "success";
|
|
7009
|
+
if (page.state === "fetch-error") return "error";
|
|
7010
|
+
return "not-applicable";
|
|
7011
|
+
}
|
|
7012
|
+
function isInventoryEligible(page) {
|
|
7013
|
+
return page.state === "html" && page.indexability.state === "indexable";
|
|
7014
|
+
}
|
|
7015
|
+
function isCancelled(error, signal) {
|
|
7016
|
+
if (error instanceof SiteAuditCancelledError) return true;
|
|
7017
|
+
if (signal?.aborted && signal.reason !== void 0 && error === signal.reason) return true;
|
|
7018
|
+
return error instanceof DOMException && error.name === "AbortError";
|
|
7019
|
+
}
|
|
7020
|
+
function isLegacyScorecardPage(page) {
|
|
7021
|
+
return page.audit != null || page.state === "fetch-error";
|
|
7022
|
+
}
|
|
7023
|
+
function emptyCompleteCrawlError(rootUrl, finalRootUrl, pagesObserved) {
|
|
7024
|
+
if (!finalRootUrl) return new Error(`Site audit could not successfully audit any of ${pagesObserved} observed page(s).`);
|
|
6962
7025
|
try {
|
|
6963
|
-
const
|
|
6964
|
-
|
|
6965
|
-
|
|
7026
|
+
const rootHost = new URL(rootUrl).hostname;
|
|
7027
|
+
const finalRootHost = new URL(finalRootUrl).hostname;
|
|
7028
|
+
if (rootHost !== finalRootHost) {
|
|
7029
|
+
return new Error(
|
|
7030
|
+
`Site audit could not successfully audit any of ${pagesObserved} observed page(s): root URL redirected off-host from ${rootHost} to ${finalRootHost} (${finalRootUrl}).`
|
|
7031
|
+
);
|
|
6966
7032
|
}
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
7033
|
+
} catch {
|
|
7034
|
+
}
|
|
7035
|
+
return new Error(`Site audit could not successfully audit any of ${pagesObserved} observed page(s).`);
|
|
7036
|
+
}
|
|
7037
|
+
function toLegacyIssue(summary, totalPages) {
|
|
7038
|
+
const affected = summary.pagesPartial + summary.pagesFailing;
|
|
7039
|
+
if (affected === 0 || summary.avgScore >= 70) return null;
|
|
7040
|
+
return {
|
|
7041
|
+
factorId: summary.id,
|
|
7042
|
+
factorName: summary.name,
|
|
7043
|
+
avgScore: summary.avgScore,
|
|
7044
|
+
affectedPages: affected,
|
|
7045
|
+
totalPages,
|
|
7046
|
+
affectedPct: totalPages > 0 ? Math.round(affected / totalPages * 100) : 0,
|
|
7047
|
+
topRecommendations: []
|
|
7048
|
+
};
|
|
7049
|
+
}
|
|
7050
|
+
function observedErrorCount(pages) {
|
|
7051
|
+
let total = 0;
|
|
7052
|
+
for (const page of pages) if (page.state === "fetch-error") total++;
|
|
7053
|
+
return total;
|
|
7054
|
+
}
|
|
7055
|
+
function deadLinkCheckedCount(edges, pages) {
|
|
7056
|
+
const attemptedUrls = /* @__PURE__ */ new Set();
|
|
7057
|
+
for (const page of pages) {
|
|
7058
|
+
if (page.state === "discovered" || page.state === "robots-blocked") continue;
|
|
7059
|
+
attemptedUrls.add(page.requestedUrl);
|
|
7060
|
+
if (page.finalUrl) attemptedUrls.add(page.finalUrl);
|
|
7061
|
+
}
|
|
7062
|
+
return new Set([...edges].filter((edge) => edge.type === "anchor" && edge.classification === "internal" && attemptedUrls.has(edge.to)).map((edge) => edge.to)).size;
|
|
7063
|
+
}
|
|
7064
|
+
async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
7065
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7066
|
+
const claim = db.update(runs).set({ status: "running", startedAt }).where(and10(eq13(runs.id, runId), eq13(runs.projectId, projectId), eq13(runs.status, "queued"))).run();
|
|
7067
|
+
if (claim.changes === 0) return;
|
|
7068
|
+
const attemptNumber = (db.select({ value: sql5`coalesce(max(${siteCrawlAttempts.attemptNumber}), 0)` }).from(siteCrawlAttempts).where(eq13(siteCrawlAttempts.runId, runId)).get()?.value ?? 0) + 1;
|
|
7069
|
+
const attemptId = crypto16.randomUUID();
|
|
7070
|
+
db.insert(siteCrawlAttempts).values({
|
|
7071
|
+
id: attemptId,
|
|
7072
|
+
projectId,
|
|
7073
|
+
runId,
|
|
7074
|
+
attemptNumber,
|
|
7075
|
+
state: "running",
|
|
7076
|
+
startedAt,
|
|
7077
|
+
createdAt: startedAt,
|
|
7078
|
+
updatedAt: startedAt
|
|
7079
|
+
}).run();
|
|
7080
|
+
const observedPages = /* @__PURE__ */ new Map();
|
|
7081
|
+
const observedEdges = /* @__PURE__ */ new Map();
|
|
7082
|
+
const nodeKeyByUrl = /* @__PURE__ */ new Map();
|
|
7083
|
+
const nodePriorityByUrl = /* @__PURE__ */ new Map();
|
|
7084
|
+
const registerNode = (url, nodeKey, priority, changedUrls) => {
|
|
7085
|
+
const currentPriority = nodePriorityByUrl.get(url);
|
|
7086
|
+
const currentNodeKey = nodeKeyByUrl.get(url);
|
|
7087
|
+
if (currentPriority == null || priority > currentPriority || priority === currentPriority && currentNodeKey != null && nodeKey.localeCompare(currentNodeKey) < 0) {
|
|
7088
|
+
nodePriorityByUrl.set(url, priority);
|
|
7089
|
+
nodeKeyByUrl.set(url, nodeKey);
|
|
7090
|
+
changedUrls.add(url);
|
|
6985
7091
|
}
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
7092
|
+
};
|
|
7093
|
+
const registerPageNode = (page, changedUrls) => {
|
|
7094
|
+
observedPages.set(page.key, page);
|
|
7095
|
+
registerNode(page.requestedUrl, page.key, 2, changedUrls);
|
|
7096
|
+
if (page.finalUrl && page.finalUrl !== page.requestedUrl) registerNode(page.finalUrl, page.key, 1, changedUrls);
|
|
7097
|
+
};
|
|
7098
|
+
const bindReferences = (tx, changedUrls, now) => {
|
|
7099
|
+
for (const url of [...new Set(changedUrls)].sort()) {
|
|
7100
|
+
const nodeKey = nodeKeyByUrl.get(url);
|
|
7101
|
+
if (!nodeKey) continue;
|
|
7102
|
+
const scope = [
|
|
7103
|
+
eq13(siteCrawlEdges.projectId, projectId),
|
|
7104
|
+
eq13(siteCrawlEdges.runId, runId),
|
|
7105
|
+
eq13(siteCrawlEdges.attemptId, attemptId)
|
|
7106
|
+
];
|
|
7107
|
+
tx.update(siteCrawlEdges).set({ sourceNodeKey: nodeKey, updatedAt: now }).where(and10(
|
|
7108
|
+
...scope,
|
|
7109
|
+
eq13(siteCrawlEdges.sourceUrl, url)
|
|
7110
|
+
)).run();
|
|
7111
|
+
tx.update(siteCrawlEdges).set({ targetNodeKey: nodeKey, updatedAt: now }).where(and10(
|
|
7112
|
+
...scope,
|
|
7113
|
+
eq13(siteCrawlEdges.targetUrl, url)
|
|
7114
|
+
)).run();
|
|
7115
|
+
tx.update(siteCrawlPages).set({ canonicalNodeKey: nodeKey, updatedAt: now }).where(and10(
|
|
7116
|
+
eq13(siteCrawlPages.projectId, projectId),
|
|
7117
|
+
eq13(siteCrawlPages.runId, runId),
|
|
7118
|
+
eq13(siteCrawlPages.attemptId, attemptId),
|
|
7119
|
+
eq13(siteCrawlPages.canonicalUrl, url)
|
|
7120
|
+
)).run();
|
|
6990
7121
|
}
|
|
6991
|
-
|
|
6992
|
-
|
|
6993
|
-
const
|
|
7122
|
+
};
|
|
7123
|
+
const persistPage = (tx, page, now) => {
|
|
7124
|
+
const audit = page.audit;
|
|
7125
|
+
tx.insert(siteCrawlPages).values({
|
|
7126
|
+
id: crypto16.randomUUID(),
|
|
7127
|
+
projectId,
|
|
7128
|
+
runId,
|
|
7129
|
+
attemptId,
|
|
7130
|
+
nodeKey: page.key,
|
|
7131
|
+
url: page.requestedUrl,
|
|
7132
|
+
path: pagePath(page),
|
|
7133
|
+
parentPath: parentPath(page),
|
|
7134
|
+
discoverySource: pageDiscoverySource(page),
|
|
7135
|
+
discoveryProvenance: [{
|
|
7136
|
+
discoveredFrom: page.provenance.discoveredFrom,
|
|
7137
|
+
sitemapSources: page.provenance.sitemapSources,
|
|
7138
|
+
root: page.provenance.root
|
|
7139
|
+
}],
|
|
7140
|
+
sitemapMetadata: { sources: page.provenance.sitemapSources },
|
|
7141
|
+
fetchState: page.state,
|
|
7142
|
+
fetchedAt: page.state === "discovered" || page.state === "robots-blocked" ? null : now,
|
|
7143
|
+
httpStatus: page.statusCode,
|
|
7144
|
+
contentType: page.contentType,
|
|
7145
|
+
finalUrl: page.finalUrl,
|
|
7146
|
+
redirectChain: page.redirectChain.map((hop) => hop.to),
|
|
7147
|
+
directives: { metaRobots: page.metaRobots, xRobots: page.xRobots },
|
|
7148
|
+
canonicalUrl: page.canonicalUrl,
|
|
7149
|
+
canonicalNodeKey: page.canonicalUrl ? nodeKeyByUrl.get(page.canonicalUrl) ?? null : null,
|
|
7150
|
+
indexabilityState: page.indexability.state,
|
|
7151
|
+
indexabilityReasons: page.indexability.reasons,
|
|
7152
|
+
auditState: pageAuditState(page),
|
|
7153
|
+
auditScore: audit?.overallScore ?? null,
|
|
7154
|
+
auditFields: audit ? { factors: audit.factors.map(toPageFactor) } : {},
|
|
7155
|
+
inventoryEligible: isInventoryEligible(page),
|
|
7156
|
+
depth: page.depth,
|
|
7157
|
+
inboundUniqueEdges: page.metrics.inbound.uniqueEdges,
|
|
7158
|
+
outboundUniqueEdges: page.metrics.outbound.uniqueEdges,
|
|
7159
|
+
inboundOccurrences: page.metrics.inbound.totalOccurrences,
|
|
7160
|
+
outboundOccurrences: page.metrics.outbound.totalOccurrences,
|
|
7161
|
+
linkScoreRaw: page.metrics.linkScoreRaw,
|
|
7162
|
+
linkScoreNormalized: page.metrics.linkScore,
|
|
7163
|
+
createdAt: now,
|
|
7164
|
+
updatedAt: now
|
|
7165
|
+
}).onConflictDoUpdate({
|
|
7166
|
+
target: [siteCrawlPages.projectId, siteCrawlPages.runId, siteCrawlPages.attemptId, siteCrawlPages.nodeKey],
|
|
7167
|
+
set: {
|
|
7168
|
+
url: page.requestedUrl,
|
|
7169
|
+
path: pagePath(page),
|
|
7170
|
+
parentPath: parentPath(page),
|
|
7171
|
+
discoverySource: pageDiscoverySource(page),
|
|
7172
|
+
discoveryProvenance: [{ discoveredFrom: page.provenance.discoveredFrom, sitemapSources: page.provenance.sitemapSources, root: page.provenance.root }],
|
|
7173
|
+
sitemapMetadata: { sources: page.provenance.sitemapSources },
|
|
7174
|
+
fetchState: page.state,
|
|
7175
|
+
fetchedAt: page.state === "discovered" || page.state === "robots-blocked" ? null : sql5`coalesce(${siteCrawlPages.fetchedAt}, ${now})`,
|
|
7176
|
+
httpStatus: page.statusCode,
|
|
7177
|
+
contentType: page.contentType,
|
|
7178
|
+
finalUrl: page.finalUrl,
|
|
7179
|
+
redirectChain: page.redirectChain.map((hop) => hop.to),
|
|
7180
|
+
directives: { metaRobots: page.metaRobots, xRobots: page.xRobots },
|
|
7181
|
+
canonicalUrl: page.canonicalUrl,
|
|
7182
|
+
canonicalNodeKey: page.canonicalUrl ? nodeKeyByUrl.get(page.canonicalUrl) ?? null : null,
|
|
7183
|
+
indexabilityState: page.indexability.state,
|
|
7184
|
+
indexabilityReasons: page.indexability.reasons,
|
|
7185
|
+
auditState: pageAuditState(page),
|
|
7186
|
+
auditScore: audit?.overallScore ?? null,
|
|
7187
|
+
auditFields: audit ? { factors: audit.factors.map(toPageFactor) } : {},
|
|
7188
|
+
inventoryEligible: isInventoryEligible(page),
|
|
7189
|
+
depth: page.depth,
|
|
7190
|
+
inboundUniqueEdges: page.metrics.inbound.uniqueEdges,
|
|
7191
|
+
outboundUniqueEdges: page.metrics.outbound.uniqueEdges,
|
|
7192
|
+
inboundOccurrences: page.metrics.inbound.totalOccurrences,
|
|
7193
|
+
outboundOccurrences: page.metrics.outbound.totalOccurrences,
|
|
7194
|
+
linkScoreRaw: page.metrics.linkScoreRaw,
|
|
7195
|
+
linkScoreNormalized: page.metrics.linkScore,
|
|
7196
|
+
updatedAt: now
|
|
7197
|
+
}
|
|
7198
|
+
}).run();
|
|
7199
|
+
};
|
|
7200
|
+
const persistPages = (tx, pages, now) => {
|
|
7201
|
+
const changedUrls = /* @__PURE__ */ new Set();
|
|
7202
|
+
for (const page of pages) registerPageNode(page, changedUrls);
|
|
7203
|
+
for (const page of pages) persistPage(tx, page, now);
|
|
7204
|
+
bindReferences(tx, changedUrls, now);
|
|
7205
|
+
};
|
|
7206
|
+
const persistEdge = (tx, edge, now) => {
|
|
7207
|
+
observedEdges.set(edge.key, edge);
|
|
7208
|
+
tx.insert(siteCrawlEdges).values({
|
|
7209
|
+
id: crypto16.randomUUID(),
|
|
7210
|
+
projectId,
|
|
7211
|
+
runId,
|
|
7212
|
+
attemptId,
|
|
7213
|
+
edgeKey: edge.key,
|
|
7214
|
+
sourceNodeKey: nodeKeyByUrl.get(edge.from) ?? edge.from,
|
|
7215
|
+
sourceUrl: edge.from,
|
|
7216
|
+
targetNodeKey: nodeKeyByUrl.get(edge.to) ?? null,
|
|
7217
|
+
targetUrl: edge.to,
|
|
7218
|
+
relation: edge.type,
|
|
7219
|
+
internal: edge.classification === "internal",
|
|
7220
|
+
followable: edge.followableOccurrences > 0,
|
|
7221
|
+
occurrences: edge.totalOccurrences,
|
|
7222
|
+
followableOccurrences: edge.followableOccurrences,
|
|
7223
|
+
nofollowOccurrences: edge.nofollowOccurrences,
|
|
7224
|
+
anchors: edge.anchorSummaries.map((anchor) => anchor.text),
|
|
7225
|
+
createdAt: now,
|
|
7226
|
+
updatedAt: now
|
|
7227
|
+
}).onConflictDoUpdate({
|
|
7228
|
+
target: [siteCrawlEdges.projectId, siteCrawlEdges.runId, siteCrawlEdges.attemptId, siteCrawlEdges.edgeKey],
|
|
7229
|
+
set: {
|
|
7230
|
+
sourceNodeKey: nodeKeyByUrl.get(edge.from) ?? edge.from,
|
|
7231
|
+
sourceUrl: edge.from,
|
|
7232
|
+
targetNodeKey: nodeKeyByUrl.get(edge.to) ?? null,
|
|
7233
|
+
targetUrl: edge.to,
|
|
7234
|
+
relation: edge.type,
|
|
7235
|
+
internal: edge.classification === "internal",
|
|
7236
|
+
followable: edge.followableOccurrences > 0,
|
|
7237
|
+
occurrences: edge.totalOccurrences,
|
|
7238
|
+
followableOccurrences: edge.followableOccurrences,
|
|
7239
|
+
nofollowOccurrences: edge.nofollowOccurrences,
|
|
7240
|
+
anchors: edge.anchorSummaries.map((anchor) => anchor.text),
|
|
7241
|
+
updatedAt: now
|
|
7242
|
+
}
|
|
7243
|
+
}).run();
|
|
7244
|
+
};
|
|
7245
|
+
const persistMetrics = (tx, key, metrics, now) => {
|
|
7246
|
+
tx.update(siteCrawlPages).set({
|
|
7247
|
+
inboundUniqueEdges: metrics.inbound.uniqueEdges,
|
|
7248
|
+
outboundUniqueEdges: metrics.outbound.uniqueEdges,
|
|
7249
|
+
inboundOccurrences: metrics.inbound.totalOccurrences,
|
|
7250
|
+
outboundOccurrences: metrics.outbound.totalOccurrences,
|
|
7251
|
+
depth: metrics.shortestFollowableAnchorDepth,
|
|
7252
|
+
linkScoreRaw: metrics.linkScoreRaw,
|
|
7253
|
+
linkScoreNormalized: metrics.linkScore,
|
|
7254
|
+
updatedAt: now
|
|
7255
|
+
}).where(and10(
|
|
7256
|
+
eq13(siteCrawlPages.projectId, projectId),
|
|
7257
|
+
eq13(siteCrawlPages.runId, runId),
|
|
7258
|
+
eq13(siteCrawlPages.attemptId, attemptId),
|
|
7259
|
+
eq13(siteCrawlPages.nodeKey, key)
|
|
7260
|
+
)).run();
|
|
7261
|
+
};
|
|
7262
|
+
const persistEvent = async (event) => {
|
|
7263
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6994
7264
|
db.transaction((tx) => {
|
|
6995
|
-
tx.
|
|
7265
|
+
const receipt = tx.select({ checksum: siteCrawlEventReceipts.checksum }).from(siteCrawlEventReceipts).where(and10(
|
|
7266
|
+
eq13(siteCrawlEventReceipts.attemptId, attemptId),
|
|
7267
|
+
eq13(siteCrawlEventReceipts.sequence, event.sequence),
|
|
7268
|
+
eq13(siteCrawlEventReceipts.batchId, event.batchId)
|
|
7269
|
+
)).get();
|
|
7270
|
+
if (receipt) {
|
|
7271
|
+
if (receipt.checksum !== event.checksum) {
|
|
7272
|
+
throw new Error(`Crawl checkpoint checksum mismatch for ${event.batchId} (${event.sequence})`);
|
|
7273
|
+
}
|
|
7274
|
+
return;
|
|
7275
|
+
}
|
|
7276
|
+
tx.insert(siteCrawlEventReceipts).values({
|
|
6996
7277
|
id: crypto16.randomUUID(),
|
|
6997
7278
|
projectId,
|
|
6998
7279
|
runId,
|
|
6999
|
-
|
|
7000
|
-
|
|
7001
|
-
|
|
7002
|
-
|
|
7003
|
-
|
|
7004
|
-
|
|
7005
|
-
pagesErrored,
|
|
7006
|
-
factorAverages,
|
|
7007
|
-
// aeo-audit v3 enriches these (topIssues, avgGrade-free); keep only the
|
|
7008
|
-
// fields our DTO exposes so the stored JSON stays lean.
|
|
7009
|
-
crossCuttingIssues: report.crossCuttingIssues.map((issue) => ({
|
|
7010
|
-
factorId: issue.factorId,
|
|
7011
|
-
factorName: issue.factorName,
|
|
7012
|
-
avgScore: issue.avgScore,
|
|
7013
|
-
affectedPages: issue.affectedPages,
|
|
7014
|
-
totalPages: issue.totalPages,
|
|
7015
|
-
affectedPct: issue.totalPages > 0 ? Math.round(issue.affectedPages / issue.totalPages * 100) : 0,
|
|
7016
|
-
topRecommendations: issue.topRecommendations
|
|
7017
|
-
})),
|
|
7018
|
-
// v3 prioritizedFixes are structured PrioritizedFix objects; persist the
|
|
7019
|
-
// ready-to-display one-line summary to keep the DTO a string list.
|
|
7020
|
-
prioritizedFixes: report.prioritizedFixes.map((fix) => fix.summary),
|
|
7021
|
-
createdAt: finishedAt
|
|
7280
|
+
attemptId,
|
|
7281
|
+
sequence: event.sequence,
|
|
7282
|
+
batchId: event.batchId,
|
|
7283
|
+
checksum: event.checksum,
|
|
7284
|
+
receipt: { type: event.type },
|
|
7285
|
+
createdAt: now
|
|
7022
7286
|
}).run();
|
|
7023
|
-
|
|
7024
|
-
|
|
7287
|
+
if (event.type === "pages") persistPages(tx, event.rows, now);
|
|
7288
|
+
if (event.type === "edges") for (const edge of event.rows) persistEdge(tx, edge, now);
|
|
7289
|
+
if (event.type === "metrics") for (const metric of event.rows) persistMetrics(tx, metric.key, metric.metrics, now);
|
|
7290
|
+
const progress = event.type === "progress" ? event.progress : event.type === "summary" ? event.summary : null;
|
|
7291
|
+
if (progress) {
|
|
7292
|
+
tx.update(siteCrawlAttempts).set({
|
|
7293
|
+
lastEventSequence: event.sequence,
|
|
7294
|
+
lastEventChecksum: event.checksum,
|
|
7295
|
+
pagesDiscovered: progress.pagesDiscovered,
|
|
7296
|
+
pagesFetched: progress.pagesFetched,
|
|
7297
|
+
pagesEligible: event.type === "summary" ? observedPages.size === 0 ? 0 : [...observedPages.values()].filter(isInventoryEligible).length : sql5`${siteCrawlAttempts.pagesEligible}`,
|
|
7298
|
+
pagesErrored: event.type === "summary" ? observedErrorCount(observedPages.values()) : sql5`${siteCrawlAttempts.pagesErrored}`,
|
|
7299
|
+
edgesDiscovered: progress.edgesObserved,
|
|
7300
|
+
updatedAt: now
|
|
7301
|
+
}).where(eq13(siteCrawlAttempts.id, attemptId)).run();
|
|
7302
|
+
} else {
|
|
7303
|
+
tx.update(siteCrawlAttempts).set({
|
|
7304
|
+
lastEventSequence: event.sequence,
|
|
7305
|
+
lastEventChecksum: event.checksum,
|
|
7306
|
+
updatedAt: now
|
|
7307
|
+
}).where(eq13(siteCrawlAttempts.id, attemptId)).run();
|
|
7308
|
+
}
|
|
7309
|
+
});
|
|
7310
|
+
};
|
|
7311
|
+
try {
|
|
7312
|
+
const project = db.select().from(projects).where(eq13(projects.id, projectId)).get();
|
|
7313
|
+
if (!project) throw new Error(`Project not found: ${projectId}`);
|
|
7314
|
+
const homepageUrl = toHomepageUrl(project.canonicalDomain);
|
|
7315
|
+
const maxPages = clampSiteAuditLimit(opts.maxPages ?? opts.limit);
|
|
7316
|
+
const maxEdges = clampSiteAuditEdgeLimit(opts.maxEdges);
|
|
7317
|
+
await assertSiteAuditUrlAllowed(homepageUrl, "canonicalDomain");
|
|
7318
|
+
if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
|
|
7319
|
+
log12.info("start", { runId, projectId, homepageUrl, maxPages, maxEdges, maxDepth: opts.maxDepth ?? null });
|
|
7320
|
+
const report = await runSiteCrawl(homepageUrl, {
|
|
7321
|
+
mode: "summary",
|
|
7322
|
+
sitemapUrl: opts.sitemapUrl,
|
|
7323
|
+
maxPages,
|
|
7324
|
+
maxEdges,
|
|
7325
|
+
maxDepth: opts.maxDepth,
|
|
7326
|
+
checkDeadLinks: opts.checkDeadLinks ?? false,
|
|
7327
|
+
signal: opts.signal,
|
|
7328
|
+
onEvent: persistEvent
|
|
7329
|
+
});
|
|
7330
|
+
const crawlSummary = report.summary;
|
|
7331
|
+
const hasAuditedPages = crawlSummary.auditRollup.auditedPages > 0;
|
|
7332
|
+
if (crawlSummary.complete && !hasAuditedPages) {
|
|
7333
|
+
throw emptyCompleteCrawlError(crawlSummary.rootUrl, crawlSummary.finalRootUrl, crawlSummary.pagesObserved);
|
|
7334
|
+
}
|
|
7335
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7336
|
+
const factors = computeFactorAverages([...observedPages.values()]);
|
|
7337
|
+
const errorCount = observedErrorCount(observedPages.values());
|
|
7338
|
+
const terminalStatus = crawlSummary.complete ? "completed" : "partial";
|
|
7339
|
+
const deadLinksChecked = opts.checkDeadLinks ? deadLinkCheckedCount(observedEdges.values(), observedPages.values()) : 0;
|
|
7340
|
+
const deadLinksFound = report.deadLinks.findings.length;
|
|
7341
|
+
const legacyIssues = factors.map((factor) => toLegacyIssue(factor, crawlSummary.auditRollup.auditedPages)).filter((issue) => issue !== null);
|
|
7342
|
+
const published = db.transaction((tx) => {
|
|
7343
|
+
const claim2 = tx.update(runs).set({ status: terminalStatus, finishedAt }).where(and10(eq13(runs.id, runId), eq13(runs.status, "running"))).run();
|
|
7344
|
+
if (claim2.changes === 0) return false;
|
|
7345
|
+
if (hasAuditedPages) {
|
|
7346
|
+
tx.insert(siteAuditSnapshots).values({
|
|
7025
7347
|
id: crypto16.randomUUID(),
|
|
7026
7348
|
projectId,
|
|
7027
7349
|
runId,
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7350
|
+
sitemapUrl: opts.sitemapUrl ?? `${homepageUrl}/sitemap.xml`,
|
|
7351
|
+
auditedAt: crawlSummary.completedAt,
|
|
7352
|
+
aggregateScore: crawlSummary.auditRollup.aggregateScore ?? 0,
|
|
7353
|
+
pagesDiscovered: crawlSummary.pagesDiscovered,
|
|
7354
|
+
pagesAudited: crawlSummary.auditRollup.auditedPages,
|
|
7355
|
+
pagesSkipped: Math.max(0, crawlSummary.pagesDiscovered - crawlSummary.pagesObserved),
|
|
7356
|
+
pagesErrored: errorCount,
|
|
7357
|
+
factorAverages: factors,
|
|
7358
|
+
crossCuttingIssues: legacyIssues,
|
|
7359
|
+
prioritizedFixes: legacyIssues.map((issue) => `Improve ${issue.factorName}`),
|
|
7033
7360
|
createdAt: finishedAt
|
|
7034
7361
|
}).run();
|
|
7362
|
+
for (const page of observedPages.values()) {
|
|
7363
|
+
if (!isLegacyScorecardPage(page)) continue;
|
|
7364
|
+
tx.insert(siteAuditPages).values({
|
|
7365
|
+
id: crypto16.randomUUID(),
|
|
7366
|
+
projectId,
|
|
7367
|
+
runId,
|
|
7368
|
+
url: page.finalUrl ?? page.requestedUrl,
|
|
7369
|
+
overallScore: page.audit?.overallScore ?? 0,
|
|
7370
|
+
status: page.audit ? "success" : "error",
|
|
7371
|
+
error: page.error,
|
|
7372
|
+
factors: page.audit?.factors.map(toPageFactor) ?? [],
|
|
7373
|
+
createdAt: finishedAt
|
|
7374
|
+
}).run();
|
|
7375
|
+
}
|
|
7035
7376
|
}
|
|
7036
|
-
tx.
|
|
7377
|
+
tx.insert(siteCrawlSnapshots).values({
|
|
7378
|
+
id: crypto16.randomUUID(),
|
|
7379
|
+
projectId,
|
|
7380
|
+
runId,
|
|
7381
|
+
attemptId,
|
|
7382
|
+
rootUrl: crawlSummary.rootUrl,
|
|
7383
|
+
crawlSchemaVersion: crawlSummary.crawlSchemaVersion,
|
|
7384
|
+
engineVersion: crawlSummary.engineVersion,
|
|
7385
|
+
normalizationVersion: crawlSummary.urlNormalizationVersion,
|
|
7386
|
+
indexabilityVersion: crawlSummary.indexabilityRulesetVersion,
|
|
7387
|
+
linkScoreVersion: crawlSummary.linkScoreAlgorithmVersion,
|
|
7388
|
+
effectiveOptions: {
|
|
7389
|
+
mode: "summary",
|
|
7390
|
+
sitemapUrl: opts.sitemapUrl ?? null,
|
|
7391
|
+
maxPages,
|
|
7392
|
+
maxEdges,
|
|
7393
|
+
maxDepth: opts.maxDepth ?? null,
|
|
7394
|
+
checkDeadLinks: opts.checkDeadLinks ?? false
|
|
7395
|
+
},
|
|
7396
|
+
pageBudget: maxPages,
|
|
7397
|
+
edgeBudget: maxEdges,
|
|
7398
|
+
maxDepth: opts.maxDepth ?? null,
|
|
7399
|
+
checkDeadLinks: opts.checkDeadLinks ?? false,
|
|
7400
|
+
complete: crawlSummary.complete,
|
|
7401
|
+
termination: crawlSummary.terminationReason ?? "complete",
|
|
7402
|
+
detailsAvailable: true,
|
|
7403
|
+
pagesDiscovered: crawlSummary.pagesDiscovered,
|
|
7404
|
+
pagesFetched: crawlSummary.pagesFetched,
|
|
7405
|
+
pagesEligible: [...observedPages.values()].filter(isInventoryEligible).length,
|
|
7406
|
+
pagesErrored: errorCount,
|
|
7407
|
+
edgesDiscovered: crawlSummary.edgesObserved,
|
|
7408
|
+
findingsCount: deadLinksFound,
|
|
7409
|
+
deadLinkState: report.deadLinks.state,
|
|
7410
|
+
deadLinksChecked,
|
|
7411
|
+
deadLinksFound,
|
|
7412
|
+
createdAt: finishedAt,
|
|
7413
|
+
updatedAt: finishedAt
|
|
7414
|
+
}).run();
|
|
7415
|
+
if (opts.checkDeadLinks) {
|
|
7416
|
+
for (const finding of report.deadLinks.findings) {
|
|
7417
|
+
tx.insert(siteCrawlFindings).values({
|
|
7418
|
+
id: crypto16.randomUUID(),
|
|
7419
|
+
projectId,
|
|
7420
|
+
runId,
|
|
7421
|
+
attemptId,
|
|
7422
|
+
findingKey: finding.key,
|
|
7423
|
+
findingType: "dead-link",
|
|
7424
|
+
severity: "error",
|
|
7425
|
+
sourceNodeKey: nodeKeyByUrl.get(finding.from) ?? null,
|
|
7426
|
+
sourceUrl: finding.from,
|
|
7427
|
+
targetNodeKey: nodeKeyByUrl.get(finding.to) ?? null,
|
|
7428
|
+
targetUrl: finding.to,
|
|
7429
|
+
evidence: { statusCode: finding.statusCode, reason: finding.reason },
|
|
7430
|
+
createdAt: finishedAt,
|
|
7431
|
+
updatedAt: finishedAt
|
|
7432
|
+
}).run();
|
|
7433
|
+
}
|
|
7434
|
+
}
|
|
7435
|
+
tx.update(siteCrawlAttempts).set({
|
|
7436
|
+
state: terminalStatus,
|
|
7437
|
+
pagesDiscovered: crawlSummary.pagesDiscovered,
|
|
7438
|
+
pagesFetched: crawlSummary.pagesFetched,
|
|
7439
|
+
pagesEligible: [...observedPages.values()].filter(isInventoryEligible).length,
|
|
7440
|
+
pagesErrored: errorCount,
|
|
7441
|
+
edgesDiscovered: crawlSummary.edgesObserved,
|
|
7442
|
+
finishedAt,
|
|
7443
|
+
updatedAt: finishedAt
|
|
7444
|
+
}).where(eq13(siteCrawlAttempts.id, attemptId)).run();
|
|
7445
|
+
return true;
|
|
7037
7446
|
});
|
|
7038
|
-
|
|
7039
|
-
runId
|
|
7040
|
-
|
|
7041
|
-
|
|
7042
|
-
|
|
7043
|
-
|
|
7044
|
-
|
|
7447
|
+
if (!published) {
|
|
7448
|
+
const current = db.select({ status: runs.status }).from(runs).where(eq13(runs.id, runId)).get();
|
|
7449
|
+
if (current?.status === "cancelled") throw new SiteAuditCancelledError(`Site audit cancelled before publication: ${runId}`);
|
|
7450
|
+
throw new Error(`Site audit run was no longer running before publication: ${runId}`);
|
|
7451
|
+
}
|
|
7452
|
+
log12.info("completed", { runId, projectId, status: terminalStatus, complete: crawlSummary.complete, pages: crawlSummary.pagesObserved });
|
|
7453
|
+
} catch (error) {
|
|
7454
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7455
|
+
const cancelled = isCancelled(error, opts.signal);
|
|
7456
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7457
|
+
db.transaction((tx) => {
|
|
7458
|
+
tx.update(siteCrawlAttempts).set({
|
|
7459
|
+
state: cancelled ? "cancelled" : "failed",
|
|
7460
|
+
error: message,
|
|
7461
|
+
finishedAt,
|
|
7462
|
+
updatedAt: finishedAt
|
|
7463
|
+
}).where(eq13(siteCrawlAttempts.id, attemptId)).run();
|
|
7464
|
+
tx.update(runs).set({
|
|
7465
|
+
status: cancelled ? "cancelled" : "failed",
|
|
7466
|
+
error: message,
|
|
7467
|
+
finishedAt
|
|
7468
|
+
}).where(and10(eq13(runs.id, runId), eq13(runs.status, "running"))).run();
|
|
7045
7469
|
});
|
|
7046
|
-
|
|
7047
|
-
|
|
7048
|
-
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq13(runs.id, runId)).run();
|
|
7049
|
-
log12.error("failed", { runId, projectId, error: errorMsg });
|
|
7050
|
-
throw err;
|
|
7470
|
+
log12.error("failed", { runId, projectId, cancelled, error: message });
|
|
7471
|
+
throw error;
|
|
7051
7472
|
}
|
|
7052
7473
|
}
|
|
7053
7474
|
|
|
7054
7475
|
// src/commands/backfill.ts
|
|
7055
|
-
import { and as
|
|
7476
|
+
import { and as and11, eq as eq14, inArray as inArray4, isNull, sql as sql6 } from "drizzle-orm";
|
|
7056
7477
|
var SNAPSHOT_BATCH_SIZE = 500;
|
|
7057
7478
|
async function backfillAnswerVisibilityCommand(opts) {
|
|
7058
7479
|
const config = loadConfig();
|
|
@@ -7068,7 +7489,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
7068
7489
|
let reparsed = 0;
|
|
7069
7490
|
let providerErrors = 0;
|
|
7070
7491
|
if (scopedProjects.length > 0) {
|
|
7071
|
-
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
7492
|
+
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and11(
|
|
7072
7493
|
eq14(runs.kind, RunKinds["answer-visibility"]),
|
|
7073
7494
|
inArray4(runs.projectId, scopedProjects.map((project) => project.id))
|
|
7074
7495
|
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq14(runs.kind, RunKinds["answer-visibility"])).all();
|
|
@@ -7227,7 +7648,7 @@ function backfillNormalizedPaths(db, opts) {
|
|
|
7227
7648
|
id: gaTrafficSnapshots.id,
|
|
7228
7649
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
7229
7650
|
landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
|
|
7230
|
-
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ?
|
|
7651
|
+
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and11(...baseConditions) : void 0).all();
|
|
7231
7652
|
let updated = 0;
|
|
7232
7653
|
let unchanged = 0;
|
|
7233
7654
|
if (rows.length > 0) {
|
|
@@ -7299,7 +7720,7 @@ function backfillAiReferralPaths(db, opts) {
|
|
|
7299
7720
|
id: gaAiReferrals.id,
|
|
7300
7721
|
landingPage: gaAiReferrals.landingPage,
|
|
7301
7722
|
landingPageNormalized: gaAiReferrals.landingPageNormalized
|
|
7302
|
-
}).from(gaAiReferrals).where(baseConditions.length > 0 ?
|
|
7723
|
+
}).from(gaAiReferrals).where(baseConditions.length > 0 ? and11(...baseConditions) : void 0).all();
|
|
7303
7724
|
let updated = 0;
|
|
7304
7725
|
let unchanged = 0;
|
|
7305
7726
|
if (rows.length > 0) {
|
|
@@ -7367,7 +7788,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
|
7367
7788
|
const project = db.select().from(projects).where(eq14(projects.id, projectId)).get();
|
|
7368
7789
|
if (!project) return { examined: 0, updated: 0, mentioned: 0 };
|
|
7369
7790
|
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq14(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
7370
|
-
const runRows = db.select({ id: runs.id }).from(runs).where(
|
|
7791
|
+
const runRows = db.select({ id: runs.id }).from(runs).where(and11(eq14(runs.kind, RunKinds["answer-visibility"]), eq14(runs.projectId, projectId))).all();
|
|
7371
7792
|
const runIds = runRows.map((r) => r.id);
|
|
7372
7793
|
let examined = 0;
|
|
7373
7794
|
let updated = 0;
|
|
@@ -7516,7 +7937,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
7516
7937
|
return result;
|
|
7517
7938
|
}
|
|
7518
7939
|
async function backfillInsightsCommand(project, opts) {
|
|
7519
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
7940
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-G4NV3X36.js");
|
|
7520
7941
|
const config = loadConfig();
|
|
7521
7942
|
const db = createClient(config.database);
|
|
7522
7943
|
migrate(db);
|
|
@@ -7686,7 +8107,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
7686
8107
|
process.stderr.write(`Recovering orphan snapshot attribution for "${project.name}"${mode}...
|
|
7687
8108
|
`);
|
|
7688
8109
|
}
|
|
7689
|
-
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(
|
|
8110
|
+
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and11(
|
|
7690
8111
|
eq14(auditLog.projectId, project.id),
|
|
7691
8112
|
inArray4(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
|
|
7692
8113
|
)).orderBy(auditLog.createdAt).all();
|
|
@@ -7695,7 +8116,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
7695
8116
|
runId: runs.id,
|
|
7696
8117
|
createdAt: runs.createdAt,
|
|
7697
8118
|
location: runs.location
|
|
7698
|
-
}).from(runs).innerJoin(querySnapshots, eq14(querySnapshots.runId, runs.id)).where(
|
|
8119
|
+
}).from(runs).innerJoin(querySnapshots, eq14(querySnapshots.runId, runs.id)).where(and11(
|
|
7699
8120
|
eq14(runs.projectId, project.id),
|
|
7700
8121
|
isNull(querySnapshots.queryId),
|
|
7701
8122
|
isNull(querySnapshots.queryText)
|
|
@@ -7718,7 +8139,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
7718
8139
|
provider: querySnapshots.provider,
|
|
7719
8140
|
createdAt: querySnapshots.createdAt,
|
|
7720
8141
|
answerText: querySnapshots.answerText
|
|
7721
|
-
}).from(querySnapshots).where(
|
|
8142
|
+
}).from(querySnapshots).where(and11(
|
|
7722
8143
|
eq14(querySnapshots.runId, run.runId),
|
|
7723
8144
|
isNull(querySnapshots.queryId),
|
|
7724
8145
|
isNull(querySnapshots.queryText)
|
|
@@ -7884,7 +8305,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
7884
8305
|
dryRun: isDryRun,
|
|
7885
8306
|
byBot: {}
|
|
7886
8307
|
};
|
|
7887
|
-
const unknownCountRow = db.select({ n:
|
|
8308
|
+
const unknownCountRow = db.select({ n: sql6`count(*)` }).from(rawEventSamples).where(and11(
|
|
7888
8309
|
eq14(rawEventSamples.eventType, "unknown"),
|
|
7889
8310
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
7890
8311
|
)).get();
|
|
@@ -7897,7 +8318,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
7897
8318
|
userAgent: rawEventSamples.userAgent,
|
|
7898
8319
|
pathNormalized: rawEventSamples.pathNormalized,
|
|
7899
8320
|
status: rawEventSamples.status
|
|
7900
|
-
}).from(rawEventSamples).where(
|
|
8321
|
+
}).from(rawEventSamples).where(and11(
|
|
7901
8322
|
eq14(rawEventSamples.eventType, "unknown"),
|
|
7902
8323
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
7903
8324
|
)).all();
|
|
@@ -7965,7 +8386,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
7965
8386
|
aiUserFetchEventsHourly.status
|
|
7966
8387
|
],
|
|
7967
8388
|
set: {
|
|
7968
|
-
hits:
|
|
8389
|
+
hits: sql6`${aiUserFetchEventsHourly.hits} + 1`,
|
|
7969
8390
|
updatedAt: now
|
|
7970
8391
|
}
|
|
7971
8392
|
}).run();
|
|
@@ -7994,14 +8415,14 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
7994
8415
|
crawlerEventsHourly.status
|
|
7995
8416
|
],
|
|
7996
8417
|
set: {
|
|
7997
|
-
hits:
|
|
8418
|
+
hits: sql6`${crawlerEventsHourly.hits} + 1`,
|
|
7998
8419
|
updatedAt: now
|
|
7999
8420
|
}
|
|
8000
8421
|
}).run();
|
|
8001
8422
|
}
|
|
8002
8423
|
}
|
|
8003
8424
|
if (!isDryRun) {
|
|
8004
|
-
const afterRow = db.select({ n:
|
|
8425
|
+
const afterRow = db.select({ n: sql6`count(*)` }).from(rawEventSamples).where(and11(
|
|
8005
8426
|
eq14(rawEventSamples.eventType, "unknown"),
|
|
8006
8427
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
8007
8428
|
)).get();
|
|
@@ -8436,7 +8857,7 @@ var ProviderRegistry = class {
|
|
|
8436
8857
|
// src/scheduler.ts
|
|
8437
8858
|
import crypto18 from "crypto";
|
|
8438
8859
|
import cron from "node-cron";
|
|
8439
|
-
import { and as
|
|
8860
|
+
import { and as and12, eq as eq15, inArray as inArray5, notExists, sql as sql7 } from "drizzle-orm";
|
|
8440
8861
|
var log13 = createLogger("Scheduler");
|
|
8441
8862
|
var DEFAULT_HEALTH_CRON = "0 */6 * * *";
|
|
8442
8863
|
function taskKey(projectId, kind) {
|
|
@@ -8468,7 +8889,7 @@ var Scheduler = class {
|
|
|
8468
8889
|
*/
|
|
8469
8890
|
ensureHealthSchedules() {
|
|
8470
8891
|
const projectsWithoutHealth = this.db.select({ id: projects.id }).from(projects).where(notExists(
|
|
8471
|
-
this.db.select({ one:
|
|
8892
|
+
this.db.select({ one: sql7`1` }).from(schedules).where(and12(
|
|
8472
8893
|
eq15(schedules.projectId, projects.id),
|
|
8473
8894
|
eq15(schedules.kind, SchedulableRunKinds.doctor)
|
|
8474
8895
|
))
|
|
@@ -8551,7 +8972,7 @@ var Scheduler = class {
|
|
|
8551
8972
|
this.stopTask(key, existing, "Stopped");
|
|
8552
8973
|
this.tasks.delete(key);
|
|
8553
8974
|
}
|
|
8554
|
-
const schedule = this.db.select().from(schedules).where(
|
|
8975
|
+
const schedule = this.db.select().from(schedules).where(and12(eq15(schedules.projectId, projectId), eq15(schedules.kind, kind))).get();
|
|
8555
8976
|
if (schedule && schedule.enabled) {
|
|
8556
8977
|
this.registerCronTask(schedule);
|
|
8557
8978
|
}
|
|
@@ -8659,7 +9080,7 @@ var Scheduler = class {
|
|
|
8659
9080
|
log13.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
|
|
8660
9081
|
return;
|
|
8661
9082
|
}
|
|
8662
|
-
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(
|
|
9083
|
+
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and12(
|
|
8663
9084
|
eq15(runs.projectId, projectId),
|
|
8664
9085
|
eq15(runs.kind, RunKinds["ads-sync"]),
|
|
8665
9086
|
inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
@@ -8734,7 +9155,7 @@ var Scheduler = class {
|
|
|
8734
9155
|
log13.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
8735
9156
|
return;
|
|
8736
9157
|
}
|
|
8737
|
-
const active = this.db.select({ id: runs.id }).from(runs).where(
|
|
9158
|
+
const active = this.db.select({ id: runs.id }).from(runs).where(and12(
|
|
8738
9159
|
eq15(runs.projectId, projectId),
|
|
8739
9160
|
eq15(runs.kind, RunKinds["site-audit"]),
|
|
8740
9161
|
inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
@@ -8745,14 +9166,24 @@ var Scheduler = class {
|
|
|
8745
9166
|
return;
|
|
8746
9167
|
}
|
|
8747
9168
|
const runId2 = crypto18.randomUUID();
|
|
8748
|
-
|
|
8749
|
-
|
|
8750
|
-
|
|
8751
|
-
|
|
8752
|
-
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
9169
|
+
const effectiveRequest = normalizeSiteAuditRunRequest({});
|
|
9170
|
+
this.db.transaction((tx) => {
|
|
9171
|
+
tx.insert(runs).values({
|
|
9172
|
+
id: runId2,
|
|
9173
|
+
projectId,
|
|
9174
|
+
kind: RunKinds["site-audit"],
|
|
9175
|
+
status: RunStatuses.queued,
|
|
9176
|
+
trigger: RunTriggers.scheduled,
|
|
9177
|
+
createdAt: now
|
|
9178
|
+
}).run();
|
|
9179
|
+
tx.insert(siteCrawlRunRequests).values({
|
|
9180
|
+
runId: runId2,
|
|
9181
|
+
projectId,
|
|
9182
|
+
identityKey: siteAuditRequestIdentity(effectiveRequest),
|
|
9183
|
+
effectiveOptions: effectiveRequest,
|
|
9184
|
+
createdAt: now
|
|
9185
|
+
}).run();
|
|
9186
|
+
});
|
|
8756
9187
|
this.db.update(schedules).set({
|
|
8757
9188
|
lastRunAt: now,
|
|
8758
9189
|
nextRunAt,
|
|
@@ -8831,7 +9262,7 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
8831
9262
|
}
|
|
8832
9263
|
|
|
8833
9264
|
// src/notifier.ts
|
|
8834
|
-
import { eq as eq16, desc as desc6, and as
|
|
9265
|
+
import { eq as eq16, desc as desc6, and as and13, inArray as inArray6, or } from "drizzle-orm";
|
|
8835
9266
|
import crypto19 from "crypto";
|
|
8836
9267
|
var log15 = createLogger("Notifier");
|
|
8837
9268
|
var Notifier = class {
|
|
@@ -9042,7 +9473,7 @@ var Notifier = class {
|
|
|
9042
9473
|
});
|
|
9043
9474
|
return [];
|
|
9044
9475
|
}
|
|
9045
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
9476
|
+
const groupSiblings = this.db.select().from(runs).where(and13(
|
|
9046
9477
|
eq16(runs.projectId, projectId),
|
|
9047
9478
|
eq16(runs.kind, thisRun.kind),
|
|
9048
9479
|
eq16(runs.createdAt, thisRun.createdAt)
|
|
@@ -9068,7 +9499,7 @@ var Notifier = class {
|
|
|
9068
9499
|
);
|
|
9069
9500
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
9070
9501
|
const recentRuns = this.db.select().from(runs).where(
|
|
9071
|
-
|
|
9502
|
+
and13(
|
|
9072
9503
|
eq16(runs.projectId, projectId),
|
|
9073
9504
|
eq16(runs.kind, thisRun.kind),
|
|
9074
9505
|
or(eq16(runs.status, "completed"), eq16(runs.status, "partial"))
|
|
@@ -10450,7 +10881,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
10450
10881
|
|
|
10451
10882
|
// src/agent/memory-store.ts
|
|
10452
10883
|
import crypto22 from "crypto";
|
|
10453
|
-
import { and as
|
|
10884
|
+
import { and as and14, desc as desc7, eq as eq18, like, sql as sql8 } from "drizzle-orm";
|
|
10454
10885
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
10455
10886
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
10456
10887
|
function rowToDto(row) {
|
|
@@ -10495,12 +10926,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
10495
10926
|
updatedAt: now
|
|
10496
10927
|
}
|
|
10497
10928
|
}).run();
|
|
10498
|
-
const row = db.select().from(agentMemory).where(
|
|
10929
|
+
const row = db.select().from(agentMemory).where(and14(eq18(agentMemory.projectId, args.projectId), eq18(agentMemory.key, args.key))).get();
|
|
10499
10930
|
if (!row) throw new Error("memory upsert produced no row");
|
|
10500
10931
|
return rowToDto(row);
|
|
10501
10932
|
}
|
|
10502
10933
|
function deleteMemoryEntry(db, projectId, key) {
|
|
10503
|
-
const result = db.delete(agentMemory).where(
|
|
10934
|
+
const result = db.delete(agentMemory).where(and14(eq18(agentMemory.projectId, projectId), eq18(agentMemory.key, key))).run();
|
|
10504
10935
|
const changes = result.changes ?? 0;
|
|
10505
10936
|
return changes > 0;
|
|
10506
10937
|
}
|
|
@@ -10529,16 +10960,16 @@ function writeCompactionNote(db, args) {
|
|
|
10529
10960
|
}).run();
|
|
10530
10961
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
10531
10962
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
10532
|
-
|
|
10963
|
+
and14(
|
|
10533
10964
|
eq18(agentMemory.projectId, args.projectId),
|
|
10534
10965
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
10535
10966
|
)
|
|
10536
10967
|
).orderBy(desc7(agentMemory.updatedAt)).all();
|
|
10537
10968
|
const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
|
|
10538
10969
|
if (stale.length > 0) {
|
|
10539
|
-
tx.delete(agentMemory).where(
|
|
10970
|
+
tx.delete(agentMemory).where(sql8`${agentMemory.id} IN (${sql8.join(stale.map((s) => sql8`${s}`), sql8`, `)})`).run();
|
|
10540
10971
|
}
|
|
10541
|
-
const row = tx.select().from(agentMemory).where(
|
|
10972
|
+
const row = tx.select().from(agentMemory).where(and14(eq18(agentMemory.projectId, args.projectId), eq18(agentMemory.key, key))).get();
|
|
10542
10973
|
if (row) inserted = rowToDto(row);
|
|
10543
10974
|
});
|
|
10544
10975
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -11550,7 +11981,7 @@ Your previous reply was not valid JSON. Return ONLY a JSON object with exactly t
|
|
|
11550
11981
|
}
|
|
11551
11982
|
|
|
11552
11983
|
// src/snapshot-service.ts
|
|
11553
|
-
import { runAeoAudit } from "@
|
|
11984
|
+
import { runAeoAudit } from "@canonry/aeo-audit";
|
|
11554
11985
|
|
|
11555
11986
|
// src/site-fetch.ts
|
|
11556
11987
|
import https from "https";
|
|
@@ -12169,20 +12600,20 @@ function clipText(value, length) {
|
|
|
12169
12600
|
}
|
|
12170
12601
|
|
|
12171
12602
|
// src/research-runner.ts
|
|
12172
|
-
import { and as
|
|
12603
|
+
import { and as and15, eq as eq21, inArray as inArray7, sql as sql9 } from "drizzle-orm";
|
|
12173
12604
|
var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
|
|
12174
12605
|
function finalResearchRunStatus(completed, failed) {
|
|
12175
12606
|
if (failed === 0) return ResearchRunStatuses.completed;
|
|
12176
12607
|
return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
|
|
12177
12608
|
}
|
|
12178
12609
|
async function executeResearchRun(db, registry, runId, projectId) {
|
|
12179
|
-
const run = db.select().from(researchRuns).where(
|
|
12610
|
+
const run = db.select().from(researchRuns).where(and15(eq21(researchRuns.id, runId), eq21(researchRuns.projectId, projectId))).get();
|
|
12180
12611
|
if (!run || run.status !== ResearchRunStatuses.queued) return;
|
|
12181
12612
|
const project = db.select().from(projects).where(eq21(projects.id, projectId)).get();
|
|
12182
12613
|
if (!project) return;
|
|
12183
12614
|
const provider = registry.get(run.provider);
|
|
12184
12615
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12185
|
-
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(
|
|
12616
|
+
const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and15(eq21(researchRuns.id, runId), eq21(researchRuns.status, ResearchRunStatuses.queued))).run();
|
|
12186
12617
|
if (claim.changes !== 1) return;
|
|
12187
12618
|
let reserved = 0;
|
|
12188
12619
|
let dispatched = 0;
|
|
@@ -12215,7 +12646,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
12215
12646
|
await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
|
|
12216
12647
|
try {
|
|
12217
12648
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12218
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(
|
|
12649
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and15(eq21(researchRunQueries.id, row.id), eq21(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
|
|
12219
12650
|
const raw = await gate.run(async () => {
|
|
12220
12651
|
dispatched++;
|
|
12221
12652
|
return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
|
|
@@ -12242,7 +12673,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
12242
12673
|
citationState: determineCitationState(normalized, domains),
|
|
12243
12674
|
rawResponse: raw.rawResponse,
|
|
12244
12675
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12245
|
-
}).where(
|
|
12676
|
+
}).where(and15(eq21(researchRunQueries.id, row.id), eq21(researchRunQueries.status, ResearchQueryStatuses.running))).run();
|
|
12246
12677
|
if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
|
|
12247
12678
|
} catch (error) {
|
|
12248
12679
|
try {
|
|
@@ -12266,15 +12697,15 @@ async function executeResearchRun(db, registry, runId, projectId) {
|
|
|
12266
12697
|
}
|
|
12267
12698
|
}
|
|
12268
12699
|
function incrementResearchProgress(db, runId, column) {
|
|
12269
|
-
db.update(researchRuns).set({ [column]:
|
|
12700
|
+
db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq21(researchRuns.id, runId)).run();
|
|
12270
12701
|
}
|
|
12271
12702
|
function markResearchQueryFailed(db, runId, queryId, error) {
|
|
12272
12703
|
const message = error instanceof Error ? error.message : String(error);
|
|
12273
|
-
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
12704
|
+
const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and15(eq21(researchRunQueries.id, queryId), inArray7(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
12274
12705
|
if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
|
|
12275
12706
|
}
|
|
12276
12707
|
function markUnfinishedResearchQueriesFailed(db, runId, error) {
|
|
12277
|
-
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
12708
|
+
db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and15(eq21(researchRunQueries.researchRunId, runId), inArray7(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
|
|
12278
12709
|
}
|
|
12279
12710
|
function finalizeResearchRun(db, runId, fatalError) {
|
|
12280
12711
|
const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq21(researchRunQueries.researchRunId, runId)).all();
|
|
@@ -12651,9 +13082,16 @@ async function createServer(opts) {
|
|
|
12651
13082
|
app.log.error({ runId, err }, "Ads sync failed");
|
|
12652
13083
|
});
|
|
12653
13084
|
};
|
|
13085
|
+
const siteAuditAbortControllers = /* @__PURE__ */ new Map();
|
|
12654
13086
|
const runSiteAudit = (runId, projectId, auditOpts) => {
|
|
12655
|
-
|
|
13087
|
+
const controller = new AbortController();
|
|
13088
|
+
siteAuditAbortControllers.set(runId, controller);
|
|
13089
|
+
executeSiteAudit(opts.db, runId, projectId, { ...auditOpts ?? {}, signal: controller.signal }).then(() => runCoordinator.onRunCompleted(runId, projectId)).catch((err) => {
|
|
12656
13090
|
app.log.error({ runId, err }, "Site audit failed");
|
|
13091
|
+
}).finally(() => {
|
|
13092
|
+
if (siteAuditAbortControllers.get(runId) === controller) {
|
|
13093
|
+
siteAuditAbortControllers.delete(runId);
|
|
13094
|
+
}
|
|
12657
13095
|
});
|
|
12658
13096
|
};
|
|
12659
13097
|
const adsCredentialStore = {
|
|
@@ -12972,7 +13410,7 @@ async function createServer(opts) {
|
|
|
12972
13410
|
});
|
|
12973
13411
|
if (!probed) return;
|
|
12974
13412
|
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
12975
|
-
|
|
13413
|
+
and16(
|
|
12976
13414
|
eq22(ccReleaseSyncs.release, probed.release),
|
|
12977
13415
|
eq22(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
12978
13416
|
)
|
|
@@ -13759,6 +14197,12 @@ async function createServer(opts) {
|
|
|
13759
14197
|
app.log.error({ runId, err }, "Job runner failed");
|
|
13760
14198
|
});
|
|
13761
14199
|
},
|
|
14200
|
+
onRunCancelled: (runId) => {
|
|
14201
|
+
const controller = siteAuditAbortControllers.get(runId);
|
|
14202
|
+
if (controller && !controller.signal.aborted) {
|
|
14203
|
+
controller.abort(new Error("Cancelled by user"));
|
|
14204
|
+
}
|
|
14205
|
+
},
|
|
13762
14206
|
getRunnableProviderNames: () => registry.getAll().map((provider) => provider.adapter.name),
|
|
13763
14207
|
getEffectiveProviderModels: () => effectiveProviderModels(registry),
|
|
13764
14208
|
onProviderUpdate: (providerName, apiKey, model, baseUrl, incomingQuota) => {
|