@ainyc/canonry 5.2.1 → 5.3.1
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/skills/canonry/references/canonry-cli.md +1 -0
- package/assets/assets/{AuditHistoryPanel-CkBLHz2H.js → AuditHistoryPanel-D5cOFMKE.js} +1 -1
- package/assets/assets/{BacklinksPage-D2YWJUf9.js → BacklinksPage-BiOj8ztV.js} +1 -1
- package/assets/assets/{HistoryPage-C9j3RzMN.js → HistoryPage-DDBCwooU.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-CMoU2yA_.js +1 -0
- package/assets/assets/ProjectPage-CrFybBls.js +8 -0
- package/assets/assets/{RunRow-DTJuJ8mb.js → RunRow-BLBAwIot.js} +1 -1
- package/assets/assets/{RunsPage-CdKorpOj.js → RunsPage-BVgBbE1M.js} +1 -1
- package/assets/assets/{SettingsPage-BOjZh2vW.js → SettingsPage-DYtN-1_-.js} +1 -1
- package/assets/assets/{SiteHealthSection-D7sPztpW.js → SiteHealthSection-ElUwcAS4.js} +3 -3
- package/assets/assets/{TrafficPage-BCodKdgN.js → TrafficPage-D6QT_2hU.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-D_zueOK5.js → TrafficSourceDetailPage-BnLzF82w.js} +1 -1
- package/assets/assets/{extract-error-message-BZYGBapN.js → extract-error-message-DIzjGZxl.js} +1 -1
- package/assets/assets/{index-aZ69fB-u.js → index-CoQVOsWw.js} +3 -3
- package/assets/assets/{react-sigma_core.esm.min-m72qsd73.js → react-sigma_core.esm.min-DTbIcdn4.js} +1 -1
- package/assets/assets/v2-overview-adapter-B_N4ilQV.js +1 -0
- package/assets/assets/{vendor-lucide-D_WBzb76.js → vendor-lucide-nVpYViBH.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-5VONCLVQ.js → chunk-3COTRCK3.js} +1 -1
- package/dist/{chunk-YQNKULAD.js → chunk-IACO5R7A.js} +837 -367
- package/dist/{chunk-AGUCTX7L.js → chunk-TF6HFT3Q.js} +198 -57
- package/dist/cli.js +6 -6
- package/dist/{demo-server-BIPT53EY.js → demo-server-TXW3UX3P.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-H34EOB6K.js} +1 -1
- package/package.json +13 -13
- package/assets/assets/MeasurementPropertyPage-Ci3CUr7Q.js +0 -1
- package/assets/assets/ProjectPage-Bdq_KTXX.js +0 -8
- package/assets/assets/v2-overview-adapter-BgsdlByS.js +0 -1
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
deleteSiteCrawlGraphLayout,
|
|
22
22
|
persistSiteCrawlGraphLayout,
|
|
23
23
|
prepareSiteCrawlGraphLayout
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-3COTRCK3.js";
|
|
25
25
|
import {
|
|
26
26
|
PACKAGE_VERSION
|
|
27
27
|
} from "./chunk-AOJU5LKX.js";
|
|
@@ -34,6 +34,8 @@ import {
|
|
|
34
34
|
IntelligenceService,
|
|
35
35
|
OperationalLogStore,
|
|
36
36
|
PLUGIN_DIR,
|
|
37
|
+
SITE_REACHABILITY_CHECKS,
|
|
38
|
+
SITE_REACHABILITY_CHECK_ID,
|
|
37
39
|
USER_SESSION_COOKIE_NAME,
|
|
38
40
|
addLogListener,
|
|
39
41
|
adsAdGroups,
|
|
@@ -158,6 +160,7 @@ import {
|
|
|
158
160
|
resolveUserSession,
|
|
159
161
|
resolveVercelSyncDeadlineMs,
|
|
160
162
|
resolveWebhookTarget,
|
|
163
|
+
runChecks,
|
|
161
164
|
runs,
|
|
162
165
|
schedules,
|
|
163
166
|
serializeUserSessionCookie,
|
|
@@ -170,9 +173,10 @@ import {
|
|
|
170
173
|
siteCrawlPages,
|
|
171
174
|
siteCrawlRunRequests,
|
|
172
175
|
siteCrawlSnapshots,
|
|
176
|
+
siteLivenessState,
|
|
173
177
|
toAlertView,
|
|
174
178
|
usageCounters
|
|
175
|
-
} from "./chunk-
|
|
179
|
+
} from "./chunk-IACO5R7A.js";
|
|
176
180
|
import {
|
|
177
181
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
178
182
|
AGENT_NONE,
|
|
@@ -6457,7 +6461,7 @@ function nonBlank(value) {
|
|
|
6457
6461
|
return trimmed ? trimmed : void 0;
|
|
6458
6462
|
}
|
|
6459
6463
|
function resolveBuildCommit(env = process.env) {
|
|
6460
|
-
const embedded = true ? "
|
|
6464
|
+
const embedded = true ? "faea7a16b83966f055c85e798a03c0a4108c1377" : void 0;
|
|
6461
6465
|
return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
|
|
6462
6466
|
}
|
|
6463
6467
|
function resolveInstanceIdentity(env = process.env) {
|
|
@@ -11751,7 +11755,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
11751
11755
|
return result;
|
|
11752
11756
|
}
|
|
11753
11757
|
async function backfillInsightsCommand(project, opts) {
|
|
11754
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
11758
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-H34EOB6K.js");
|
|
11755
11759
|
const config = loadConfig();
|
|
11756
11760
|
const db = createClient(config.database);
|
|
11757
11761
|
migrate(db);
|
|
@@ -12901,8 +12905,48 @@ var Scheduler = class {
|
|
|
12901
12905
|
}
|
|
12902
12906
|
};
|
|
12903
12907
|
|
|
12908
|
+
// src/site-liveness-loop.ts
|
|
12909
|
+
var log15 = createLogger("SiteLiveness");
|
|
12910
|
+
var SITE_LIVENESS_INTERVAL_MS = 10 * 6e4;
|
|
12911
|
+
async function runSiteLivenessPass(deps) {
|
|
12912
|
+
const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
12913
|
+
const rows = deps.db.select({ id: projects.id, name: projects.name, canonicalDomain: projects.canonicalDomain, displayName: projects.displayName }).from(projects).all();
|
|
12914
|
+
let checked = 0;
|
|
12915
|
+
let events = 0;
|
|
12916
|
+
for (const project of rows) {
|
|
12917
|
+
try {
|
|
12918
|
+
const check = await deps.probe(project);
|
|
12919
|
+
if (!check) continue;
|
|
12920
|
+
checked += 1;
|
|
12921
|
+
const event = await deps.notify(project.id, { check, checkedAt: now() });
|
|
12922
|
+
if (event) {
|
|
12923
|
+
events += 1;
|
|
12924
|
+
log15.info("site-liveness.notified", { projectName: project.name, event, code: check.code });
|
|
12925
|
+
}
|
|
12926
|
+
} catch (err) {
|
|
12927
|
+
log15.warn("site-liveness.project-failed", { projectName: project.name, err: String(err) });
|
|
12928
|
+
}
|
|
12929
|
+
}
|
|
12930
|
+
return { checked, events };
|
|
12931
|
+
}
|
|
12932
|
+
function startSiteLivenessLoop(deps, intervalMs = SITE_LIVENESS_INTERVAL_MS) {
|
|
12933
|
+
let running = false;
|
|
12934
|
+
const timer = setInterval(() => {
|
|
12935
|
+
if (running) {
|
|
12936
|
+
log15.warn("site-liveness.pass-still-running", { intervalMs });
|
|
12937
|
+
return;
|
|
12938
|
+
}
|
|
12939
|
+
running = true;
|
|
12940
|
+
void runSiteLivenessPass(deps).catch((err) => log15.warn("site-liveness.pass-failed", { err: String(err) })).finally(() => {
|
|
12941
|
+
running = false;
|
|
12942
|
+
});
|
|
12943
|
+
}, intervalMs);
|
|
12944
|
+
timer.unref?.();
|
|
12945
|
+
return () => clearInterval(timer);
|
|
12946
|
+
}
|
|
12947
|
+
|
|
12904
12948
|
// src/data-refresh.ts
|
|
12905
|
-
var
|
|
12949
|
+
var log16 = createLogger("DataRefresh");
|
|
12906
12950
|
async function refreshAllIntegrations(client, projectName) {
|
|
12907
12951
|
const integrations = [
|
|
12908
12952
|
{ name: "gsc", run: () => client.gscSync(projectName, {}) },
|
|
@@ -12919,11 +12963,11 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
12919
12963
|
results.forEach((result, idx) => {
|
|
12920
12964
|
const integration = integrations[idx].name;
|
|
12921
12965
|
if (result.status === "fulfilled") {
|
|
12922
|
-
|
|
12966
|
+
log16.info("integration.refreshed", { projectName, integration });
|
|
12923
12967
|
} else {
|
|
12924
12968
|
const reason = result.reason;
|
|
12925
12969
|
const message = describeError(reason);
|
|
12926
|
-
|
|
12970
|
+
log16.warn("integration.refresh-failed", { projectName, integration, error: message });
|
|
12927
12971
|
}
|
|
12928
12972
|
});
|
|
12929
12973
|
}
|
|
@@ -12931,7 +12975,7 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
12931
12975
|
// src/notifier.ts
|
|
12932
12976
|
import { eq as eq17, desc as desc6, and as and15, inArray as inArray8, or } from "drizzle-orm";
|
|
12933
12977
|
import crypto20 from "crypto";
|
|
12934
|
-
var
|
|
12978
|
+
var log17 = createLogger("Notifier");
|
|
12935
12979
|
var MATERIAL_MAGNITUDE_DELTA = 20;
|
|
12936
12980
|
var SEVERITY_RANK = { low: 0, medium: 1, high: 2, critical: 3 };
|
|
12937
12981
|
function severityRank(severity) {
|
|
@@ -12947,6 +12991,9 @@ function insightMagnitude(insight) {
|
|
|
12947
12991
|
function insightNotifyKey(projectId, insight) {
|
|
12948
12992
|
return `${projectId}:${insight.type}:${insight.query}:${insightFingerprint(insight)}`;
|
|
12949
12993
|
}
|
|
12994
|
+
var SITE_LIVENESS_FAILURES_TO_PAGE = 2;
|
|
12995
|
+
var SITE_LIVENESS_MIN_PASS_GAP_MS = 4 * 6e4;
|
|
12996
|
+
var SITE_LIVENESS_MAX_PASS_GAP_MS = 45 * 6e4;
|
|
12950
12997
|
var Notifier = class {
|
|
12951
12998
|
db;
|
|
12952
12999
|
serverUrl;
|
|
@@ -12956,26 +13003,26 @@ var Notifier = class {
|
|
|
12956
13003
|
}
|
|
12957
13004
|
/** Called after a run completes (success, partial, or failed). */
|
|
12958
13005
|
async onRunCompleted(runId, projectId) {
|
|
12959
|
-
|
|
13006
|
+
log17.info("run.completed", { runId, projectId });
|
|
12960
13007
|
const notifs = this.db.select().from(notifications).where(eq17(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
12961
13008
|
if (notifs.length === 0) {
|
|
12962
|
-
|
|
13009
|
+
log17.info("notifications.none-enabled", { projectId });
|
|
12963
13010
|
return;
|
|
12964
13011
|
}
|
|
12965
|
-
|
|
13012
|
+
log17.info("notifications.found", { projectId, count: notifs.length });
|
|
12966
13013
|
const run = this.db.select().from(runs).where(eq17(runs.id, runId)).get();
|
|
12967
13014
|
if (!run) {
|
|
12968
|
-
|
|
13015
|
+
log17.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
12969
13016
|
return;
|
|
12970
13017
|
}
|
|
12971
13018
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
12972
13019
|
if (!project) {
|
|
12973
|
-
|
|
13020
|
+
log17.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
12974
13021
|
return;
|
|
12975
13022
|
}
|
|
12976
13023
|
const transitions = this.computeTransitions(runId, projectId);
|
|
12977
13024
|
const events = [];
|
|
12978
|
-
|
|
13025
|
+
log17.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
12979
13026
|
if (run.status === "completed" || run.status === "partial") {
|
|
12980
13027
|
events.push("run.completed");
|
|
12981
13028
|
}
|
|
@@ -12991,7 +13038,7 @@ var Notifier = class {
|
|
|
12991
13038
|
if (!config.url) continue;
|
|
12992
13039
|
const subscribedEvents = config.events;
|
|
12993
13040
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
12994
|
-
|
|
13041
|
+
log17.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
12995
13042
|
if (matchingEvents.length === 0) continue;
|
|
12996
13043
|
for (const event of matchingEvents) {
|
|
12997
13044
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -13023,7 +13070,7 @@ var Notifier = class {
|
|
|
13023
13070
|
async onHealthChecked(projectId, report) {
|
|
13024
13071
|
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
13025
13072
|
if (!project) {
|
|
13026
|
-
|
|
13073
|
+
log17.error("project.not-found", { projectId, msg: "skipping health notification" });
|
|
13027
13074
|
return null;
|
|
13028
13075
|
}
|
|
13029
13076
|
const rank = (status2) => status2 === "fail" ? 2 : status2 === "warn" ? 1 : 0;
|
|
@@ -13054,6 +13101,7 @@ var Notifier = class {
|
|
|
13054
13101
|
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.`;
|
|
13055
13102
|
const previous = this.db.select().from(doctorHealthState).where(eq17(doctorHealthState.projectId, projectId)).get();
|
|
13056
13103
|
const previousStatus = previous?.status ?? null;
|
|
13104
|
+
const failingSignature = failing.map((c) => `${c.status}:${c.code}`).join(",");
|
|
13057
13105
|
let event = null;
|
|
13058
13106
|
if (previous === void 0) {
|
|
13059
13107
|
if (status !== "ok") event = "health.degraded";
|
|
@@ -13061,16 +13109,18 @@ var Notifier = class {
|
|
|
13061
13109
|
event = "health.recovered";
|
|
13062
13110
|
} else if (status !== "ok" && (previousStatus === "ok" || previous.code !== code)) {
|
|
13063
13111
|
event = "health.degraded";
|
|
13112
|
+
} else if (status !== "ok" && previous.failingSignature !== null && previous.failingSignature !== failingSignature) {
|
|
13113
|
+
event = "health.degraded";
|
|
13064
13114
|
}
|
|
13065
13115
|
const now = report.checkedAt;
|
|
13066
|
-
const observation = { projectId, status, code, summary, checkedAt: now };
|
|
13116
|
+
const observation = { projectId, status, code, summary, checkedAt: now, failingSignature };
|
|
13067
13117
|
if (previous === void 0) {
|
|
13068
13118
|
this.db.insert(doctorHealthState).values({ ...observation, notifiedAt: null }).run();
|
|
13069
13119
|
} else {
|
|
13070
13120
|
this.db.update(doctorHealthState).set(observation).where(eq17(doctorHealthState.projectId, projectId)).run();
|
|
13071
13121
|
}
|
|
13072
13122
|
if (!event) {
|
|
13073
|
-
|
|
13123
|
+
log17.info("health.unchanged", { projectId, status, code });
|
|
13074
13124
|
return null;
|
|
13075
13125
|
}
|
|
13076
13126
|
const notifs = this.db.select().from(notifications).where(eq17(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
@@ -13093,13 +13143,87 @@ var Notifier = class {
|
|
|
13093
13143
|
for (const notif of notifs) {
|
|
13094
13144
|
const config = notif.config;
|
|
13095
13145
|
if (!config.url) continue;
|
|
13096
|
-
await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null);
|
|
13097
|
-
delivered += 1;
|
|
13146
|
+
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
13098
13147
|
}
|
|
13099
13148
|
if (delivered > 0) {
|
|
13100
13149
|
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(eq17(doctorHealthState.projectId, projectId)).run();
|
|
13101
13150
|
}
|
|
13102
|
-
|
|
13151
|
+
log17.info("health.notified", { projectId, event, status, code, subscribers: notifs.length, delivered });
|
|
13152
|
+
return event;
|
|
13153
|
+
}
|
|
13154
|
+
/**
|
|
13155
|
+
* Record a website liveness probe and notify on a confirmed change.
|
|
13156
|
+
*
|
|
13157
|
+
* Separate from onHealthChecked: that state grades the whole 6h doctor pass,
|
|
13158
|
+
* and this pass sees one check. Sharing its row would let "site is up" clear
|
|
13159
|
+
* an unrelated GA outage and send a false recovery.
|
|
13160
|
+
*
|
|
13161
|
+
* Two failed passes in a row before paging. The probe already retries once
|
|
13162
|
+
* inside a pass, so an alert means four failed requests spread over at least
|
|
13163
|
+
* one schedule interval, not a blip or a host restart. A recovery is sent only
|
|
13164
|
+
* for an outage that was paged, so a blip that never alerted stays silent in
|
|
13165
|
+
* both directions.
|
|
13166
|
+
*/
|
|
13167
|
+
async onSiteLivenessChecked(projectId, result) {
|
|
13168
|
+
const { check, checkedAt } = result;
|
|
13169
|
+
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
13170
|
+
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
13171
|
+
if (!project) {
|
|
13172
|
+
log17.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
13173
|
+
return null;
|
|
13174
|
+
}
|
|
13175
|
+
const previous = this.db.select().from(siteLivenessState).where(eq17(siteLivenessState.projectId, projectId)).get();
|
|
13176
|
+
const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
|
|
13177
|
+
const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
|
|
13178
|
+
const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
|
|
13179
|
+
const outagePaged = previous?.status === "fail" && Boolean(previous.notifiedAt);
|
|
13180
|
+
let event = null;
|
|
13181
|
+
if (check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE && !outagePaged) event = "health.degraded";
|
|
13182
|
+
if (check.status === "ok" && outagePaged) event = "health.recovered";
|
|
13183
|
+
const observation = {
|
|
13184
|
+
projectId,
|
|
13185
|
+
status: check.status,
|
|
13186
|
+
code: check.code,
|
|
13187
|
+
summary: check.summary,
|
|
13188
|
+
consecutiveFailures: failures,
|
|
13189
|
+
checkedAt,
|
|
13190
|
+
notifiedAt: check.status === "ok" ? null : previous?.notifiedAt ?? null
|
|
13191
|
+
};
|
|
13192
|
+
if (previous === void 0) {
|
|
13193
|
+
this.db.insert(siteLivenessState).values(observation).run();
|
|
13194
|
+
} else {
|
|
13195
|
+
this.db.update(siteLivenessState).set(observation).where(eq17(siteLivenessState.projectId, projectId)).run();
|
|
13196
|
+
}
|
|
13197
|
+
if (!event) return null;
|
|
13198
|
+
const notifs = this.db.select().from(notifications).where(eq17(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
13199
|
+
const payload = {
|
|
13200
|
+
source: "canonry",
|
|
13201
|
+
event,
|
|
13202
|
+
project: { name: project.name, canonicalDomain: project.canonicalDomain },
|
|
13203
|
+
health: {
|
|
13204
|
+
status: check.status,
|
|
13205
|
+
code: check.code,
|
|
13206
|
+
summary: check.summary,
|
|
13207
|
+
remediation: check.remediation ?? null,
|
|
13208
|
+
checkedAt,
|
|
13209
|
+
// The counter resets on every ok, so a page always follows a site that was
|
|
13210
|
+
// answering. Echoing the row the first failed pass wrote would report
|
|
13211
|
+
// "fail -> fail" and show the operator no transition at all.
|
|
13212
|
+
previousStatus: event === "health.degraded" ? "ok" : "fail",
|
|
13213
|
+
failing: event === "health.degraded" ? [{ id: check.id, status: check.status, code: check.code, summary: check.summary }] : []
|
|
13214
|
+
},
|
|
13215
|
+
dashboardUrl: `${this.serverUrl}/projects/${project.name}`
|
|
13216
|
+
};
|
|
13217
|
+
let delivered = 0;
|
|
13218
|
+
for (const notif of notifs) {
|
|
13219
|
+
const config = notif.config;
|
|
13220
|
+
if (!config.url) continue;
|
|
13221
|
+
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
13222
|
+
}
|
|
13223
|
+
if (event === "health.degraded" && delivered > 0) {
|
|
13224
|
+
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq17(siteLivenessState.projectId, projectId)).run();
|
|
13225
|
+
}
|
|
13226
|
+
log17.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
13103
13227
|
return event;
|
|
13104
13228
|
}
|
|
13105
13229
|
/**
|
|
@@ -13197,7 +13321,7 @@ var Notifier = class {
|
|
|
13197
13321
|
if (!thisRun) return [];
|
|
13198
13322
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
13199
13323
|
if (completeness.planned && !completeness.complete) {
|
|
13200
|
-
|
|
13324
|
+
log17.info("transitions.skipped-incomplete", {
|
|
13201
13325
|
runId,
|
|
13202
13326
|
executed: completeness.executed,
|
|
13203
13327
|
expected: completeness.expected
|
|
@@ -13279,6 +13403,7 @@ var Notifier = class {
|
|
|
13279
13403
|
}
|
|
13280
13404
|
return transitions;
|
|
13281
13405
|
}
|
|
13406
|
+
/** True only when the destination accepted the payload. Callers that record "already said" must not count an attempt. */
|
|
13282
13407
|
async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
|
|
13283
13408
|
const destination = resolveDestination(url);
|
|
13284
13409
|
const body = destination.render ? destination.render(toAlertView(payload)) : payload;
|
|
@@ -13286,23 +13411,23 @@ var Notifier = class {
|
|
|
13286
13411
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
13287
13412
|
const targetCheck = await resolveWebhookTarget(url);
|
|
13288
13413
|
if (!targetCheck.ok) {
|
|
13289
|
-
|
|
13414
|
+
log17.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
13290
13415
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
13291
|
-
return;
|
|
13416
|
+
return false;
|
|
13292
13417
|
}
|
|
13293
|
-
|
|
13418
|
+
log17.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
13294
13419
|
const maxRetries = 3;
|
|
13295
13420
|
const delays = [1e3, 4e3, 16e3];
|
|
13296
13421
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
13297
13422
|
try {
|
|
13298
13423
|
const response = await deliverWebhook(targetCheck.target, body, signingSecret);
|
|
13299
13424
|
if (response.status >= 200 && response.status < 300) {
|
|
13300
|
-
|
|
13425
|
+
log17.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
13301
13426
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
13302
|
-
return;
|
|
13427
|
+
return true;
|
|
13303
13428
|
}
|
|
13304
13429
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
13305
|
-
|
|
13430
|
+
log17.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
13306
13431
|
if (attempt === maxRetries - 1) {
|
|
13307
13432
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
13308
13433
|
}
|
|
@@ -13310,13 +13435,14 @@ var Notifier = class {
|
|
|
13310
13435
|
const errorDetail = describeError(err);
|
|
13311
13436
|
if (attempt === maxRetries - 1) {
|
|
13312
13437
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
13313
|
-
|
|
13438
|
+
log17.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
13314
13439
|
}
|
|
13315
13440
|
}
|
|
13316
13441
|
if (attempt < maxRetries - 1) {
|
|
13317
13442
|
await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
|
|
13318
13443
|
}
|
|
13319
13444
|
}
|
|
13445
|
+
return false;
|
|
13320
13446
|
}
|
|
13321
13447
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
13322
13448
|
this.db.insert(auditLog).values({
|
|
@@ -13334,7 +13460,7 @@ var Notifier = class {
|
|
|
13334
13460
|
|
|
13335
13461
|
// src/run-coordinator.ts
|
|
13336
13462
|
import { eq as eq18 } from "drizzle-orm";
|
|
13337
|
-
var
|
|
13463
|
+
var log18 = createLogger("RunCoordinator");
|
|
13338
13464
|
var RunCoordinator = class {
|
|
13339
13465
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
13340
13466
|
this.db = db;
|
|
@@ -13352,12 +13478,12 @@ var RunCoordinator = class {
|
|
|
13352
13478
|
const runRow = this.db.select().from(runs).where(eq18(runs.id, runId)).get();
|
|
13353
13479
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
13354
13480
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
13355
|
-
|
|
13481
|
+
log18.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
13356
13482
|
return;
|
|
13357
13483
|
}
|
|
13358
13484
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
13359
13485
|
if (completeness.planned && !completeness.complete) {
|
|
13360
|
-
|
|
13486
|
+
log18.info("measurement.incomplete-skip-derived", {
|
|
13361
13487
|
runId,
|
|
13362
13488
|
projectId,
|
|
13363
13489
|
executed: completeness.executed,
|
|
@@ -13366,7 +13492,7 @@ var RunCoordinator = class {
|
|
|
13366
13492
|
try {
|
|
13367
13493
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
13368
13494
|
} catch (err) {
|
|
13369
|
-
|
|
13495
|
+
log18.error("notifier.failed", { runId, error: describeError(err) });
|
|
13370
13496
|
}
|
|
13371
13497
|
return;
|
|
13372
13498
|
}
|
|
@@ -13384,12 +13510,12 @@ var RunCoordinator = class {
|
|
|
13384
13510
|
try {
|
|
13385
13511
|
await this.onInsightsGenerated(runId, projectId, result);
|
|
13386
13512
|
} catch (err) {
|
|
13387
|
-
|
|
13513
|
+
log18.error("insight-webhook.failed", { runId, error: describeError(err) });
|
|
13388
13514
|
}
|
|
13389
13515
|
}
|
|
13390
13516
|
}
|
|
13391
13517
|
} catch (err) {
|
|
13392
|
-
|
|
13518
|
+
log18.error("intelligence.failed", { runId, error: describeError(err) });
|
|
13393
13519
|
}
|
|
13394
13520
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
13395
13521
|
try {
|
|
@@ -13402,17 +13528,17 @@ var RunCoordinator = class {
|
|
|
13402
13528
|
try {
|
|
13403
13529
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
13404
13530
|
} catch (err) {
|
|
13405
|
-
|
|
13531
|
+
log18.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
|
|
13406
13532
|
}
|
|
13407
13533
|
}
|
|
13408
13534
|
} catch (err) {
|
|
13409
|
-
|
|
13535
|
+
log18.error("gbp-intelligence.failed", { runId, error: describeError(err) });
|
|
13410
13536
|
}
|
|
13411
13537
|
}
|
|
13412
13538
|
try {
|
|
13413
13539
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
13414
13540
|
} catch (err) {
|
|
13415
|
-
|
|
13541
|
+
log18.error("notifier.failed", { runId, error: describeError(err) });
|
|
13416
13542
|
}
|
|
13417
13543
|
if (this.onAeroEvent) {
|
|
13418
13544
|
try {
|
|
@@ -13425,7 +13551,7 @@ var RunCoordinator = class {
|
|
|
13425
13551
|
};
|
|
13426
13552
|
await this.onAeroEvent(ctx);
|
|
13427
13553
|
} catch (err) {
|
|
13428
|
-
|
|
13554
|
+
log18.error("aero.failed", { runId, error: describeError(err) });
|
|
13429
13555
|
}
|
|
13430
13556
|
}
|
|
13431
13557
|
}
|
|
@@ -14593,7 +14719,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
14593
14719
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
14594
14720
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
14595
14721
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
14596
|
-
var
|
|
14722
|
+
var log19 = createLogger("RemoteMcp");
|
|
14597
14723
|
var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
|
|
14598
14724
|
var MAX_TOOL_RESULT_CHARS2 = 2e4;
|
|
14599
14725
|
function truncate(text) {
|
|
@@ -14646,7 +14772,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14646
14772
|
try {
|
|
14647
14773
|
client = await connect(server);
|
|
14648
14774
|
} catch (err) {
|
|
14649
|
-
|
|
14775
|
+
log19.error("external-mcp.connect-failed", {
|
|
14650
14776
|
label,
|
|
14651
14777
|
error: describeError(err)
|
|
14652
14778
|
});
|
|
@@ -14656,7 +14782,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14656
14782
|
try {
|
|
14657
14783
|
listed = await client.listTools();
|
|
14658
14784
|
} catch (err) {
|
|
14659
|
-
|
|
14785
|
+
log19.error("external-mcp.list-failed", {
|
|
14660
14786
|
label,
|
|
14661
14787
|
error: describeError(err)
|
|
14662
14788
|
});
|
|
@@ -14667,7 +14793,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14667
14793
|
if (!isReadOnly(tool)) continue;
|
|
14668
14794
|
tools.push(adaptRemoteTool(client, tool));
|
|
14669
14795
|
}
|
|
14670
|
-
|
|
14796
|
+
log19.info("external-mcp.loaded", {
|
|
14671
14797
|
label,
|
|
14672
14798
|
discovered: listed.tools.length,
|
|
14673
14799
|
adopted: tools.length
|
|
@@ -14899,7 +15025,7 @@ async function compactMessages(args) {
|
|
|
14899
15025
|
}
|
|
14900
15026
|
|
|
14901
15027
|
// src/agent/session-registry.ts
|
|
14902
|
-
var
|
|
15028
|
+
var log20 = createLogger("SessionRegistry");
|
|
14903
15029
|
var MAX_HYDRATE_NOTES = 20;
|
|
14904
15030
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
14905
15031
|
function escapeMemoryFragment(value) {
|
|
@@ -14943,7 +15069,7 @@ var SessionRegistry = class {
|
|
|
14943
15069
|
if (!this.externalToolsPromise) {
|
|
14944
15070
|
this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
|
|
14945
15071
|
(err) => {
|
|
14946
|
-
|
|
15072
|
+
log20.error("external-mcp.load-failed", {
|
|
14947
15073
|
error: describeError(err)
|
|
14948
15074
|
});
|
|
14949
15075
|
return [];
|
|
@@ -15198,13 +15324,13 @@ ${lines.join("\n")}
|
|
|
15198
15324
|
agent.state.messages = result.messages;
|
|
15199
15325
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
15200
15326
|
this.save(projectName);
|
|
15201
|
-
|
|
15327
|
+
log20.info("compaction.completed", {
|
|
15202
15328
|
projectName,
|
|
15203
15329
|
removedCount: result.removedCount,
|
|
15204
15330
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
15205
15331
|
});
|
|
15206
15332
|
} catch (err) {
|
|
15207
|
-
|
|
15333
|
+
log20.error("compaction.failed", {
|
|
15208
15334
|
projectName,
|
|
15209
15335
|
error: describeError(err)
|
|
15210
15336
|
});
|
|
@@ -15305,7 +15431,7 @@ ${lines.join("\n")}
|
|
|
15305
15431
|
await agent.prompt(msgs);
|
|
15306
15432
|
this.save(projectName);
|
|
15307
15433
|
} catch (err) {
|
|
15308
|
-
|
|
15434
|
+
log20.error("drain.failed", {
|
|
15309
15435
|
projectName,
|
|
15310
15436
|
error: describeError(err)
|
|
15311
15437
|
});
|
|
@@ -16112,7 +16238,7 @@ function formatAuditFactorScore(factor) {
|
|
|
16112
16238
|
}
|
|
16113
16239
|
|
|
16114
16240
|
// src/snapshot-service.ts
|
|
16115
|
-
var
|
|
16241
|
+
var log21 = createLogger("Snapshot");
|
|
16116
16242
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
16117
16243
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
16118
16244
|
var SnapshotService = class {
|
|
@@ -16209,7 +16335,7 @@ var SnapshotService = class {
|
|
|
16209
16335
|
return mapAuditReport(report);
|
|
16210
16336
|
} catch (err) {
|
|
16211
16337
|
const message = describeError(err);
|
|
16212
|
-
|
|
16338
|
+
log21.warn("audit.failed", { homepageUrl, error: message });
|
|
16213
16339
|
return {
|
|
16214
16340
|
url: homepageUrl,
|
|
16215
16341
|
finalUrl: homepageUrl,
|
|
@@ -16238,7 +16364,7 @@ var SnapshotService = class {
|
|
|
16238
16364
|
queries: parsedQueries
|
|
16239
16365
|
};
|
|
16240
16366
|
} catch (err) {
|
|
16241
|
-
|
|
16367
|
+
log21.warn("profile.generation-failed", {
|
|
16242
16368
|
domain: ctx.domain,
|
|
16243
16369
|
provider: ctx.analysisProvider.adapter.name,
|
|
16244
16370
|
error: describeError(err)
|
|
@@ -16381,7 +16507,7 @@ var SnapshotService = class {
|
|
|
16381
16507
|
recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
|
|
16382
16508
|
};
|
|
16383
16509
|
} catch (err) {
|
|
16384
|
-
|
|
16510
|
+
log21.warn("response.analysis-failed", {
|
|
16385
16511
|
provider: ctx.analysisProvider.adapter.name,
|
|
16386
16512
|
error: describeError(err)
|
|
16387
16513
|
});
|
|
@@ -17006,7 +17132,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
17006
17132
|
// src/server.ts
|
|
17007
17133
|
var _require3 = createRequire3(import.meta.url);
|
|
17008
17134
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
17009
|
-
var
|
|
17135
|
+
var log22 = createLogger("Server");
|
|
17010
17136
|
var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
|
|
17011
17137
|
function waitForServerRuntimeStartup(app) {
|
|
17012
17138
|
const startup = runtimeStartupByServer.get(app);
|
|
@@ -17211,7 +17337,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
17211
17337
|
}
|
|
17212
17338
|
if (migratedGoogle > 0) {
|
|
17213
17339
|
saveConfigPatch({ google: config.google });
|
|
17214
|
-
|
|
17340
|
+
log22.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
17215
17341
|
}
|
|
17216
17342
|
let migratedGa4 = 0;
|
|
17217
17343
|
for (const row of rows.ga4) {
|
|
@@ -17229,7 +17355,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
17229
17355
|
}
|
|
17230
17356
|
if (migratedGa4 > 0) {
|
|
17231
17357
|
saveConfigPatch({ ga4: config.ga4 });
|
|
17232
|
-
|
|
17358
|
+
log22.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
17233
17359
|
}
|
|
17234
17360
|
}
|
|
17235
17361
|
function isLoopbackBindHost(host) {
|
|
@@ -17404,11 +17530,11 @@ async function createServer(opts) {
|
|
|
17404
17530
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
17405
17531
|
dropLegacyCredentialColumns(opts.db);
|
|
17406
17532
|
} catch (err) {
|
|
17407
|
-
|
|
17533
|
+
log22.warn("credentials.migration.failed", {
|
|
17408
17534
|
error: describeError(err)
|
|
17409
17535
|
});
|
|
17410
17536
|
}
|
|
17411
|
-
|
|
17537
|
+
log22.info("providers.configured", {
|
|
17412
17538
|
providers: Object.keys(providers).filter((k) => {
|
|
17413
17539
|
const p = providers[k];
|
|
17414
17540
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
@@ -17890,6 +18016,7 @@ async function createServer(opts) {
|
|
|
17890
18016
|
AD_GROUP_IN_PROGRESS_INSIGHT_FIELDS
|
|
17891
18017
|
))
|
|
17892
18018
|
};
|
|
18019
|
+
let stopSiteLiveness = null;
|
|
17893
18020
|
const scheduler = new Scheduler(opts.db, {
|
|
17894
18021
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
17895
18022
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -19261,6 +19388,18 @@ async function createServer(opts) {
|
|
|
19261
19388
|
if (runtimeStartupSettled) return;
|
|
19262
19389
|
try {
|
|
19263
19390
|
scheduler.start();
|
|
19391
|
+
stopSiteLiveness = startSiteLivenessLoop({
|
|
19392
|
+
db: opts.db,
|
|
19393
|
+
// In-process, so a pass writes no HTTP request logs, and the check is
|
|
19394
|
+
// named explicitly because it is opt-in and never runs in a default pass.
|
|
19395
|
+
probe: async (project) => {
|
|
19396
|
+
const report = await runChecks({ db: opts.db, project }, SITE_REACHABILITY_CHECKS, {
|
|
19397
|
+
checkIds: [SITE_REACHABILITY_CHECK_ID]
|
|
19398
|
+
});
|
|
19399
|
+
return report.checks[0] ?? null;
|
|
19400
|
+
},
|
|
19401
|
+
notify: (projectId, result) => notifier.onSiteLivenessChecked(projectId, result)
|
|
19402
|
+
});
|
|
19264
19403
|
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq25(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
19265
19404
|
dispatchResearchRun(run.id, run.projectId);
|
|
19266
19405
|
}
|
|
@@ -19274,6 +19413,8 @@ async function createServer(opts) {
|
|
|
19274
19413
|
const stopLogCapture = addLogListener((entry) => operationalLogs.append(entry));
|
|
19275
19414
|
app.addHook("onClose", async () => {
|
|
19276
19415
|
stopLogCapture();
|
|
19416
|
+
stopSiteLiveness?.();
|
|
19417
|
+
stopSiteLiveness = null;
|
|
19277
19418
|
scheduler.stop();
|
|
19278
19419
|
});
|
|
19279
19420
|
return app;
|
package/dist/cli.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
trackCliCommandFinished,
|
|
34
34
|
trackEvent,
|
|
35
35
|
waitForServerRuntimeStartup
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-TF6HFT3Q.js";
|
|
37
37
|
import {
|
|
38
38
|
autoSyncSkills,
|
|
39
39
|
formatAutoSyncNotice
|
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
trustedProxiesProblem,
|
|
70
70
|
trustedProxyProblem
|
|
71
71
|
} from "./chunk-RB2F4D3Z.js";
|
|
72
|
-
import "./chunk-
|
|
72
|
+
import "./chunk-3COTRCK3.js";
|
|
73
73
|
import "./chunk-AOJU5LKX.js";
|
|
74
74
|
import {
|
|
75
75
|
CLOUDFLARE_WORKER_BINDINGS,
|
|
@@ -84,7 +84,7 @@ import {
|
|
|
84
84
|
projects,
|
|
85
85
|
queries,
|
|
86
86
|
renderReportHtml
|
|
87
|
-
} from "./chunk-
|
|
87
|
+
} from "./chunk-IACO5R7A.js";
|
|
88
88
|
import {
|
|
89
89
|
AdsDeliverySnapshotStatuses,
|
|
90
90
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -428,7 +428,7 @@ function parseDemoTrustedProxies(values) {
|
|
|
428
428
|
async function demoCommand(options) {
|
|
429
429
|
const { host, port } = parseDemoListenOptions(options);
|
|
430
430
|
const trustProxy = parseDemoTrustedProxies(options.trustProxy);
|
|
431
|
-
const { createDemoServer } = await import("./demo-server-
|
|
431
|
+
const { createDemoServer } = await import("./demo-server-TXW3UX3P.js");
|
|
432
432
|
const app = await createDemoServer({ trustProxy });
|
|
433
433
|
let stopping = false;
|
|
434
434
|
const stop = () => {
|
|
@@ -11021,8 +11021,8 @@ var EVENT_DESCRIPTIONS = {
|
|
|
11021
11021
|
"run.failed": "An AEO sweep failed",
|
|
11022
11022
|
"insight.critical": "A critical-severity insight was generated",
|
|
11023
11023
|
"insight.high": "A high-severity insight was generated",
|
|
11024
|
-
"health.degraded": "A scheduled health check found the measurement
|
|
11025
|
-
"health.recovered": "A
|
|
11024
|
+
"health.degraded": "A scheduled health check found the measurement degraded, daily data stopped arriving, or the website stopped answering (sent on change, not every pass)",
|
|
11025
|
+
"health.recovered": "A degraded health check is passing again, or a website that was paged as down answers again"
|
|
11026
11026
|
};
|
|
11027
11027
|
function listEvents(format, target = "local") {
|
|
11028
11028
|
if (target === "server") return getClient16().listNotificationEvents().then((events) => printEvents(events, format));
|