@ainyc/canonry 5.2.1 → 5.3.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/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-AGUCTX7L.js → chunk-HOOU4NS2.js} +194 -56
- package/dist/{chunk-5VONCLVQ.js → chunk-MNUFMEAX.js} +1 -1
- package/dist/{chunk-YQNKULAD.js → chunk-QAGZWYPI.js} +815 -366
- package/dist/cli.js +6 -6
- package/dist/{demo-server-BIPT53EY.js → demo-server-Y7YWLPU7.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-SJO5E7WN.js → intelligence-service-PR6T6JKY.js} +1 -1
- package/package.json +8 -8
- 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-MNUFMEAX.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-QAGZWYPI.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 ? "caba5d386b2538f0fb2e4b23c1275a1c8c652fbd" : 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-PR6T6JKY.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;
|
|
@@ -13070,7 +13117,7 @@ var Notifier = class {
|
|
|
13070
13117
|
this.db.update(doctorHealthState).set(observation).where(eq17(doctorHealthState.projectId, projectId)).run();
|
|
13071
13118
|
}
|
|
13072
13119
|
if (!event) {
|
|
13073
|
-
|
|
13120
|
+
log17.info("health.unchanged", { projectId, status, code });
|
|
13074
13121
|
return null;
|
|
13075
13122
|
}
|
|
13076
13123
|
const notifs = this.db.select().from(notifications).where(eq17(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
@@ -13093,13 +13140,87 @@ var Notifier = class {
|
|
|
13093
13140
|
for (const notif of notifs) {
|
|
13094
13141
|
const config = notif.config;
|
|
13095
13142
|
if (!config.url) continue;
|
|
13096
|
-
await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null);
|
|
13097
|
-
delivered += 1;
|
|
13143
|
+
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
13098
13144
|
}
|
|
13099
13145
|
if (delivered > 0) {
|
|
13100
13146
|
this.db.update(doctorHealthState).set({ notifiedAt: now }).where(eq17(doctorHealthState.projectId, projectId)).run();
|
|
13101
13147
|
}
|
|
13102
|
-
|
|
13148
|
+
log17.info("health.notified", { projectId, event, status, code, subscribers: notifs.length, delivered });
|
|
13149
|
+
return event;
|
|
13150
|
+
}
|
|
13151
|
+
/**
|
|
13152
|
+
* Record a website liveness probe and notify on a confirmed change.
|
|
13153
|
+
*
|
|
13154
|
+
* Separate from onHealthChecked: that state grades the whole 6h doctor pass,
|
|
13155
|
+
* and this pass sees one check. Sharing its row would let "site is up" clear
|
|
13156
|
+
* an unrelated GA outage and send a false recovery.
|
|
13157
|
+
*
|
|
13158
|
+
* Two failed passes in a row before paging. The probe already retries once
|
|
13159
|
+
* inside a pass, so an alert means four failed requests spread over at least
|
|
13160
|
+
* one schedule interval, not a blip or a host restart. A recovery is sent only
|
|
13161
|
+
* for an outage that was paged, so a blip that never alerted stays silent in
|
|
13162
|
+
* both directions.
|
|
13163
|
+
*/
|
|
13164
|
+
async onSiteLivenessChecked(projectId, result) {
|
|
13165
|
+
const { check, checkedAt } = result;
|
|
13166
|
+
if (check.status !== "ok" && check.status !== "fail") return null;
|
|
13167
|
+
const project = this.db.select().from(projects).where(eq17(projects.id, projectId)).get();
|
|
13168
|
+
if (!project) {
|
|
13169
|
+
log17.error("project.not-found", { projectId, msg: "skipping site liveness notification" });
|
|
13170
|
+
return null;
|
|
13171
|
+
}
|
|
13172
|
+
const previous = this.db.select().from(siteLivenessState).where(eq17(siteLivenessState.projectId, projectId)).get();
|
|
13173
|
+
const sincePrevious = previous ? Date.parse(checkedAt) - Date.parse(previous.checkedAt) : Number.NaN;
|
|
13174
|
+
const countsAsRepeat = Number.isFinite(sincePrevious) && sincePrevious >= SITE_LIVENESS_MIN_PASS_GAP_MS && sincePrevious <= SITE_LIVENESS_MAX_PASS_GAP_MS;
|
|
13175
|
+
const failures = check.status === "fail" ? countsAsRepeat ? (previous?.consecutiveFailures ?? 0) + 1 : 1 : 0;
|
|
13176
|
+
const outagePaged = previous?.status === "fail" && Boolean(previous.notifiedAt);
|
|
13177
|
+
let event = null;
|
|
13178
|
+
if (check.status === "fail" && failures >= SITE_LIVENESS_FAILURES_TO_PAGE && !outagePaged) event = "health.degraded";
|
|
13179
|
+
if (check.status === "ok" && outagePaged) event = "health.recovered";
|
|
13180
|
+
const observation = {
|
|
13181
|
+
projectId,
|
|
13182
|
+
status: check.status,
|
|
13183
|
+
code: check.code,
|
|
13184
|
+
summary: check.summary,
|
|
13185
|
+
consecutiveFailures: failures,
|
|
13186
|
+
checkedAt,
|
|
13187
|
+
notifiedAt: check.status === "ok" ? null : previous?.notifiedAt ?? null
|
|
13188
|
+
};
|
|
13189
|
+
if (previous === void 0) {
|
|
13190
|
+
this.db.insert(siteLivenessState).values(observation).run();
|
|
13191
|
+
} else {
|
|
13192
|
+
this.db.update(siteLivenessState).set(observation).where(eq17(siteLivenessState.projectId, projectId)).run();
|
|
13193
|
+
}
|
|
13194
|
+
if (!event) return null;
|
|
13195
|
+
const notifs = this.db.select().from(notifications).where(eq17(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
13196
|
+
const payload = {
|
|
13197
|
+
source: "canonry",
|
|
13198
|
+
event,
|
|
13199
|
+
project: { name: project.name, canonicalDomain: project.canonicalDomain },
|
|
13200
|
+
health: {
|
|
13201
|
+
status: check.status,
|
|
13202
|
+
code: check.code,
|
|
13203
|
+
summary: check.summary,
|
|
13204
|
+
remediation: check.remediation ?? null,
|
|
13205
|
+
checkedAt,
|
|
13206
|
+
// The counter resets on every ok, so a page always follows a site that was
|
|
13207
|
+
// answering. Echoing the row the first failed pass wrote would report
|
|
13208
|
+
// "fail -> fail" and show the operator no transition at all.
|
|
13209
|
+
previousStatus: event === "health.degraded" ? "ok" : "fail",
|
|
13210
|
+
failing: event === "health.degraded" ? [{ id: check.id, status: check.status, code: check.code, summary: check.summary }] : []
|
|
13211
|
+
},
|
|
13212
|
+
dashboardUrl: `${this.serverUrl}/projects/${project.name}`
|
|
13213
|
+
};
|
|
13214
|
+
let delivered = 0;
|
|
13215
|
+
for (const notif of notifs) {
|
|
13216
|
+
const config = notif.config;
|
|
13217
|
+
if (!config.url) continue;
|
|
13218
|
+
if (await this.sendWebhook(config.url, payload, notif.id, projectId, notif.webhookSecret ?? null)) delivered += 1;
|
|
13219
|
+
}
|
|
13220
|
+
if (event === "health.degraded" && delivered > 0) {
|
|
13221
|
+
this.db.update(siteLivenessState).set({ notifiedAt: checkedAt }).where(eq17(siteLivenessState.projectId, projectId)).run();
|
|
13222
|
+
}
|
|
13223
|
+
log17.info("site-liveness.notified", { projectId, event, code: check.code, subscribers: notifs.length, delivered });
|
|
13103
13224
|
return event;
|
|
13104
13225
|
}
|
|
13105
13226
|
/**
|
|
@@ -13197,7 +13318,7 @@ var Notifier = class {
|
|
|
13197
13318
|
if (!thisRun) return [];
|
|
13198
13319
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
13199
13320
|
if (completeness.planned && !completeness.complete) {
|
|
13200
|
-
|
|
13321
|
+
log17.info("transitions.skipped-incomplete", {
|
|
13201
13322
|
runId,
|
|
13202
13323
|
executed: completeness.executed,
|
|
13203
13324
|
expected: completeness.expected
|
|
@@ -13279,6 +13400,7 @@ var Notifier = class {
|
|
|
13279
13400
|
}
|
|
13280
13401
|
return transitions;
|
|
13281
13402
|
}
|
|
13403
|
+
/** True only when the destination accepted the payload. Callers that record "already said" must not count an attempt. */
|
|
13282
13404
|
async sendWebhook(url, payload, notificationId, projectId, webhookSecret) {
|
|
13283
13405
|
const destination = resolveDestination(url);
|
|
13284
13406
|
const body = destination.render ? destination.render(toAlertView(payload)) : payload;
|
|
@@ -13286,23 +13408,23 @@ var Notifier = class {
|
|
|
13286
13408
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
13287
13409
|
const targetCheck = await resolveWebhookTarget(url);
|
|
13288
13410
|
if (!targetCheck.ok) {
|
|
13289
|
-
|
|
13411
|
+
log17.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
13290
13412
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
13291
|
-
return;
|
|
13413
|
+
return false;
|
|
13292
13414
|
}
|
|
13293
|
-
|
|
13415
|
+
log17.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
13294
13416
|
const maxRetries = 3;
|
|
13295
13417
|
const delays = [1e3, 4e3, 16e3];
|
|
13296
13418
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
13297
13419
|
try {
|
|
13298
13420
|
const response = await deliverWebhook(targetCheck.target, body, signingSecret);
|
|
13299
13421
|
if (response.status >= 200 && response.status < 300) {
|
|
13300
|
-
|
|
13422
|
+
log17.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
13301
13423
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
13302
|
-
return;
|
|
13424
|
+
return true;
|
|
13303
13425
|
}
|
|
13304
13426
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
13305
|
-
|
|
13427
|
+
log17.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
13306
13428
|
if (attempt === maxRetries - 1) {
|
|
13307
13429
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
13308
13430
|
}
|
|
@@ -13310,13 +13432,14 @@ var Notifier = class {
|
|
|
13310
13432
|
const errorDetail = describeError(err);
|
|
13311
13433
|
if (attempt === maxRetries - 1) {
|
|
13312
13434
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
13313
|
-
|
|
13435
|
+
log17.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
13314
13436
|
}
|
|
13315
13437
|
}
|
|
13316
13438
|
if (attempt < maxRetries - 1) {
|
|
13317
13439
|
await new Promise((resolve) => setTimeout(resolve, delays[attempt]));
|
|
13318
13440
|
}
|
|
13319
13441
|
}
|
|
13442
|
+
return false;
|
|
13320
13443
|
}
|
|
13321
13444
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
13322
13445
|
this.db.insert(auditLog).values({
|
|
@@ -13334,7 +13457,7 @@ var Notifier = class {
|
|
|
13334
13457
|
|
|
13335
13458
|
// src/run-coordinator.ts
|
|
13336
13459
|
import { eq as eq18 } from "drizzle-orm";
|
|
13337
|
-
var
|
|
13460
|
+
var log18 = createLogger("RunCoordinator");
|
|
13338
13461
|
var RunCoordinator = class {
|
|
13339
13462
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
13340
13463
|
this.db = db;
|
|
@@ -13352,12 +13475,12 @@ var RunCoordinator = class {
|
|
|
13352
13475
|
const runRow = this.db.select().from(runs).where(eq18(runs.id, runId)).get();
|
|
13353
13476
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
13354
13477
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
13355
|
-
|
|
13478
|
+
log18.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
13356
13479
|
return;
|
|
13357
13480
|
}
|
|
13358
13481
|
const completeness = measurementRunCompleteness(this.db, runId);
|
|
13359
13482
|
if (completeness.planned && !completeness.complete) {
|
|
13360
|
-
|
|
13483
|
+
log18.info("measurement.incomplete-skip-derived", {
|
|
13361
13484
|
runId,
|
|
13362
13485
|
projectId,
|
|
13363
13486
|
executed: completeness.executed,
|
|
@@ -13366,7 +13489,7 @@ var RunCoordinator = class {
|
|
|
13366
13489
|
try {
|
|
13367
13490
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
13368
13491
|
} catch (err) {
|
|
13369
|
-
|
|
13492
|
+
log18.error("notifier.failed", { runId, error: describeError(err) });
|
|
13370
13493
|
}
|
|
13371
13494
|
return;
|
|
13372
13495
|
}
|
|
@@ -13384,12 +13507,12 @@ var RunCoordinator = class {
|
|
|
13384
13507
|
try {
|
|
13385
13508
|
await this.onInsightsGenerated(runId, projectId, result);
|
|
13386
13509
|
} catch (err) {
|
|
13387
|
-
|
|
13510
|
+
log18.error("insight-webhook.failed", { runId, error: describeError(err) });
|
|
13388
13511
|
}
|
|
13389
13512
|
}
|
|
13390
13513
|
}
|
|
13391
13514
|
} catch (err) {
|
|
13392
|
-
|
|
13515
|
+
log18.error("intelligence.failed", { runId, error: describeError(err) });
|
|
13393
13516
|
}
|
|
13394
13517
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
13395
13518
|
try {
|
|
@@ -13402,17 +13525,17 @@ var RunCoordinator = class {
|
|
|
13402
13525
|
try {
|
|
13403
13526
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
13404
13527
|
} catch (err) {
|
|
13405
|
-
|
|
13528
|
+
log18.error("gbp-insight-webhook.failed", { runId, error: describeError(err) });
|
|
13406
13529
|
}
|
|
13407
13530
|
}
|
|
13408
13531
|
} catch (err) {
|
|
13409
|
-
|
|
13532
|
+
log18.error("gbp-intelligence.failed", { runId, error: describeError(err) });
|
|
13410
13533
|
}
|
|
13411
13534
|
}
|
|
13412
13535
|
try {
|
|
13413
13536
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
13414
13537
|
} catch (err) {
|
|
13415
|
-
|
|
13538
|
+
log18.error("notifier.failed", { runId, error: describeError(err) });
|
|
13416
13539
|
}
|
|
13417
13540
|
if (this.onAeroEvent) {
|
|
13418
13541
|
try {
|
|
@@ -13425,7 +13548,7 @@ var RunCoordinator = class {
|
|
|
13425
13548
|
};
|
|
13426
13549
|
await this.onAeroEvent(ctx);
|
|
13427
13550
|
} catch (err) {
|
|
13428
|
-
|
|
13551
|
+
log18.error("aero.failed", { runId, error: describeError(err) });
|
|
13429
13552
|
}
|
|
13430
13553
|
}
|
|
13431
13554
|
}
|
|
@@ -14593,7 +14716,7 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
14593
14716
|
import { Type as Type4 } from "@sinclair/typebox";
|
|
14594
14717
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
14595
14718
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
14596
|
-
var
|
|
14719
|
+
var log19 = createLogger("RemoteMcp");
|
|
14597
14720
|
var AERO_EXCLUDED_MCP_TOOLS2 = /* @__PURE__ */ new Set([]);
|
|
14598
14721
|
var MAX_TOOL_RESULT_CHARS2 = 2e4;
|
|
14599
14722
|
function truncate(text) {
|
|
@@ -14646,7 +14769,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14646
14769
|
try {
|
|
14647
14770
|
client = await connect(server);
|
|
14648
14771
|
} catch (err) {
|
|
14649
|
-
|
|
14772
|
+
log19.error("external-mcp.connect-failed", {
|
|
14650
14773
|
label,
|
|
14651
14774
|
error: describeError(err)
|
|
14652
14775
|
});
|
|
@@ -14656,7 +14779,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14656
14779
|
try {
|
|
14657
14780
|
listed = await client.listTools();
|
|
14658
14781
|
} catch (err) {
|
|
14659
|
-
|
|
14782
|
+
log19.error("external-mcp.list-failed", {
|
|
14660
14783
|
label,
|
|
14661
14784
|
error: describeError(err)
|
|
14662
14785
|
});
|
|
@@ -14667,7 +14790,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
|
|
|
14667
14790
|
if (!isReadOnly(tool)) continue;
|
|
14668
14791
|
tools.push(adaptRemoteTool(client, tool));
|
|
14669
14792
|
}
|
|
14670
|
-
|
|
14793
|
+
log19.info("external-mcp.loaded", {
|
|
14671
14794
|
label,
|
|
14672
14795
|
discovered: listed.tools.length,
|
|
14673
14796
|
adopted: tools.length
|
|
@@ -14899,7 +15022,7 @@ async function compactMessages(args) {
|
|
|
14899
15022
|
}
|
|
14900
15023
|
|
|
14901
15024
|
// src/agent/session-registry.ts
|
|
14902
|
-
var
|
|
15025
|
+
var log20 = createLogger("SessionRegistry");
|
|
14903
15026
|
var MAX_HYDRATE_NOTES = 20;
|
|
14904
15027
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
14905
15028
|
function escapeMemoryFragment(value) {
|
|
@@ -14943,7 +15066,7 @@ var SessionRegistry = class {
|
|
|
14943
15066
|
if (!this.externalToolsPromise) {
|
|
14944
15067
|
this.externalToolsPromise = loadExternalMcpTools(this.opts.config.externalMcpServers).catch(
|
|
14945
15068
|
(err) => {
|
|
14946
|
-
|
|
15069
|
+
log20.error("external-mcp.load-failed", {
|
|
14947
15070
|
error: describeError(err)
|
|
14948
15071
|
});
|
|
14949
15072
|
return [];
|
|
@@ -15198,13 +15321,13 @@ ${lines.join("\n")}
|
|
|
15198
15321
|
agent.state.messages = result.messages;
|
|
15199
15322
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
15200
15323
|
this.save(projectName);
|
|
15201
|
-
|
|
15324
|
+
log20.info("compaction.completed", {
|
|
15202
15325
|
projectName,
|
|
15203
15326
|
removedCount: result.removedCount,
|
|
15204
15327
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
15205
15328
|
});
|
|
15206
15329
|
} catch (err) {
|
|
15207
|
-
|
|
15330
|
+
log20.error("compaction.failed", {
|
|
15208
15331
|
projectName,
|
|
15209
15332
|
error: describeError(err)
|
|
15210
15333
|
});
|
|
@@ -15305,7 +15428,7 @@ ${lines.join("\n")}
|
|
|
15305
15428
|
await agent.prompt(msgs);
|
|
15306
15429
|
this.save(projectName);
|
|
15307
15430
|
} catch (err) {
|
|
15308
|
-
|
|
15431
|
+
log20.error("drain.failed", {
|
|
15309
15432
|
projectName,
|
|
15310
15433
|
error: describeError(err)
|
|
15311
15434
|
});
|
|
@@ -16112,7 +16235,7 @@ function formatAuditFactorScore(factor) {
|
|
|
16112
16235
|
}
|
|
16113
16236
|
|
|
16114
16237
|
// src/snapshot-service.ts
|
|
16115
|
-
var
|
|
16238
|
+
var log21 = createLogger("Snapshot");
|
|
16116
16239
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
16117
16240
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
16118
16241
|
var SnapshotService = class {
|
|
@@ -16209,7 +16332,7 @@ var SnapshotService = class {
|
|
|
16209
16332
|
return mapAuditReport(report);
|
|
16210
16333
|
} catch (err) {
|
|
16211
16334
|
const message = describeError(err);
|
|
16212
|
-
|
|
16335
|
+
log21.warn("audit.failed", { homepageUrl, error: message });
|
|
16213
16336
|
return {
|
|
16214
16337
|
url: homepageUrl,
|
|
16215
16338
|
finalUrl: homepageUrl,
|
|
@@ -16238,7 +16361,7 @@ var SnapshotService = class {
|
|
|
16238
16361
|
queries: parsedQueries
|
|
16239
16362
|
};
|
|
16240
16363
|
} catch (err) {
|
|
16241
|
-
|
|
16364
|
+
log21.warn("profile.generation-failed", {
|
|
16242
16365
|
domain: ctx.domain,
|
|
16243
16366
|
provider: ctx.analysisProvider.adapter.name,
|
|
16244
16367
|
error: describeError(err)
|
|
@@ -16381,7 +16504,7 @@ var SnapshotService = class {
|
|
|
16381
16504
|
recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
|
|
16382
16505
|
};
|
|
16383
16506
|
} catch (err) {
|
|
16384
|
-
|
|
16507
|
+
log21.warn("response.analysis-failed", {
|
|
16385
16508
|
provider: ctx.analysisProvider.adapter.name,
|
|
16386
16509
|
error: describeError(err)
|
|
16387
16510
|
});
|
|
@@ -17006,7 +17129,7 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
|
|
|
17006
17129
|
// src/server.ts
|
|
17007
17130
|
var _require3 = createRequire3(import.meta.url);
|
|
17008
17131
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
17009
|
-
var
|
|
17132
|
+
var log22 = createLogger("Server");
|
|
17010
17133
|
var runtimeStartupByServer = /* @__PURE__ */ new WeakMap();
|
|
17011
17134
|
function waitForServerRuntimeStartup(app) {
|
|
17012
17135
|
const startup = runtimeStartupByServer.get(app);
|
|
@@ -17211,7 +17334,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
17211
17334
|
}
|
|
17212
17335
|
if (migratedGoogle > 0) {
|
|
17213
17336
|
saveConfigPatch({ google: config.google });
|
|
17214
|
-
|
|
17337
|
+
log22.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
17215
17338
|
}
|
|
17216
17339
|
let migratedGa4 = 0;
|
|
17217
17340
|
for (const row of rows.ga4) {
|
|
@@ -17229,7 +17352,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
17229
17352
|
}
|
|
17230
17353
|
if (migratedGa4 > 0) {
|
|
17231
17354
|
saveConfigPatch({ ga4: config.ga4 });
|
|
17232
|
-
|
|
17355
|
+
log22.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
17233
17356
|
}
|
|
17234
17357
|
}
|
|
17235
17358
|
function isLoopbackBindHost(host) {
|
|
@@ -17404,11 +17527,11 @@ async function createServer(opts) {
|
|
|
17404
17527
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
17405
17528
|
dropLegacyCredentialColumns(opts.db);
|
|
17406
17529
|
} catch (err) {
|
|
17407
|
-
|
|
17530
|
+
log22.warn("credentials.migration.failed", {
|
|
17408
17531
|
error: describeError(err)
|
|
17409
17532
|
});
|
|
17410
17533
|
}
|
|
17411
|
-
|
|
17534
|
+
log22.info("providers.configured", {
|
|
17412
17535
|
providers: Object.keys(providers).filter((k) => {
|
|
17413
17536
|
const p = providers[k];
|
|
17414
17537
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
@@ -17890,6 +18013,7 @@ async function createServer(opts) {
|
|
|
17890
18013
|
AD_GROUP_IN_PROGRESS_INSIGHT_FIELDS
|
|
17891
18014
|
))
|
|
17892
18015
|
};
|
|
18016
|
+
let stopSiteLiveness = null;
|
|
17893
18017
|
const scheduler = new Scheduler(opts.db, {
|
|
17894
18018
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
17895
18019
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -19261,6 +19385,18 @@ async function createServer(opts) {
|
|
|
19261
19385
|
if (runtimeStartupSettled) return;
|
|
19262
19386
|
try {
|
|
19263
19387
|
scheduler.start();
|
|
19388
|
+
stopSiteLiveness = startSiteLivenessLoop({
|
|
19389
|
+
db: opts.db,
|
|
19390
|
+
// In-process, so a pass writes no HTTP request logs, and the check is
|
|
19391
|
+
// named explicitly because it is opt-in and never runs in a default pass.
|
|
19392
|
+
probe: async (project) => {
|
|
19393
|
+
const report = await runChecks({ db: opts.db, project }, SITE_REACHABILITY_CHECKS, {
|
|
19394
|
+
checkIds: [SITE_REACHABILITY_CHECK_ID]
|
|
19395
|
+
});
|
|
19396
|
+
return report.checks[0] ?? null;
|
|
19397
|
+
},
|
|
19398
|
+
notify: (projectId, result) => notifier.onSiteLivenessChecked(projectId, result)
|
|
19399
|
+
});
|
|
19264
19400
|
for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq25(researchRuns.status, ResearchRunStatuses.queued)).all()) {
|
|
19265
19401
|
dispatchResearchRun(run.id, run.projectId);
|
|
19266
19402
|
}
|
|
@@ -19274,6 +19410,8 @@ async function createServer(opts) {
|
|
|
19274
19410
|
const stopLogCapture = addLogListener((entry) => operationalLogs.append(entry));
|
|
19275
19411
|
app.addHook("onClose", async () => {
|
|
19276
19412
|
stopLogCapture();
|
|
19413
|
+
stopSiteLiveness?.();
|
|
19414
|
+
stopSiteLiveness = null;
|
|
19277
19415
|
scheduler.stop();
|
|
19278
19416
|
});
|
|
19279
19417
|
return app;
|