@ainyc/canonry 4.72.4 → 4.75.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 +1 -1
- package/assets/agent-workspace/skills/aero/references/orchestration.md +1 -1
- package/assets/agent-workspace/skills/canonry/SKILL.md +1 -1
- package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +17 -0
- package/assets/assets/{BacklinksPage-CjfpwZEH.js → BacklinksPage-CwAplOLo.js} +1 -1
- package/assets/assets/{ChartPrimitives-Ckf2FrUy.js → ChartPrimitives-EGp5HFxn.js} +1 -1
- package/assets/assets/ProjectPage-C-zhkBKK.js +6 -0
- package/assets/assets/{RunRow-BuFyG0V_.js → RunRow-YFN2PwH-.js} +1 -1
- package/assets/assets/{RunsPage-D-pr000K.js → RunsPage-DlKS8zaS.js} +1 -1
- package/assets/assets/{SettingsPage-CiaapCYn.js → SettingsPage-Q0OZKjMD.js} +1 -1
- package/assets/assets/{TrafficPage-B40xytJD.js → TrafficPage-BbySUnhy.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-7hHem-gM.js → TrafficSourceDetailPage-BGzuvTYp.js} +1 -1
- package/assets/assets/{extract-error-message-3GkDsu1h.js → extract-error-message-Czt2jFxA.js} +1 -1
- package/assets/assets/index-CFVX11lK.css +1 -0
- package/assets/assets/{index-BVdH2O9w.js → index-DYsYdWV8.js} +118 -118
- package/assets/assets/{server-traffic-CsgPsudZ.js → server-traffic-BzIFKqGS.js} +1 -1
- package/assets/assets/{trash-2-B8Ipf9rI.js → trash-2-DKCkbZUb.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-JXFNERK4.js → chunk-JNAKRK77.js} +1103 -998
- package/dist/{chunk-HOKVBMOD.js → chunk-JUWU2DV6.js} +402 -81
- package/dist/{chunk-SRBO33HB.js → chunk-QY5WZWU4.js} +403 -202
- package/dist/{chunk-ZUBBADMR.js → chunk-WFMEK34V.js} +162 -1
- package/dist/cli.js +237 -31
- package/dist/index.d.ts +10 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-CSW4R4I7.js → intelligence-service-L2A5MFB4.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +10 -10
- package/assets/assets/ProjectPage-DZeplYeC.js +0 -6
- package/assets/assets/index-B3nENtU0.css +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-WFMEK34V.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -94,8 +94,10 @@ import {
|
|
|
94
94
|
resolveWebhookTarget,
|
|
95
95
|
runs,
|
|
96
96
|
schedules,
|
|
97
|
+
siteAuditPages,
|
|
98
|
+
siteAuditSnapshots,
|
|
97
99
|
usageCounters
|
|
98
|
-
} from "./chunk-
|
|
100
|
+
} from "./chunk-JUWU2DV6.js";
|
|
99
101
|
import {
|
|
100
102
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
101
103
|
AGENT_PROVIDER_IDS,
|
|
@@ -125,6 +127,7 @@ import {
|
|
|
125
127
|
agentMemoryDeleteRequestSchema,
|
|
126
128
|
agentMemoryUpsertRequestSchema,
|
|
127
129
|
authInvalid,
|
|
130
|
+
authRequired,
|
|
128
131
|
buildRunErrorFromMessages,
|
|
129
132
|
classifySkillFile,
|
|
130
133
|
coerceSkillManifest,
|
|
@@ -133,6 +136,7 @@ import {
|
|
|
133
136
|
determineAnswerMentioned,
|
|
134
137
|
effectiveBrandNames,
|
|
135
138
|
effectiveDomains,
|
|
139
|
+
factorStatusFromScore,
|
|
136
140
|
isAgentProviderId,
|
|
137
141
|
isBrowserProvider,
|
|
138
142
|
isRetryableHttpError,
|
|
@@ -145,7 +149,7 @@ import {
|
|
|
145
149
|
validationError,
|
|
146
150
|
winnabilityClassLabel,
|
|
147
151
|
withRetry
|
|
148
|
-
} from "./chunk-
|
|
152
|
+
} from "./chunk-JNAKRK77.js";
|
|
149
153
|
|
|
150
154
|
// src/telemetry.ts
|
|
151
155
|
import crypto from "crypto";
|
|
@@ -433,11 +437,11 @@ function checkLatestVersionForServer(opts) {
|
|
|
433
437
|
|
|
434
438
|
// src/server.ts
|
|
435
439
|
import { createRequire as createRequire3 } from "module";
|
|
436
|
-
import
|
|
440
|
+
import crypto18 from "crypto";
|
|
437
441
|
import fs8 from "fs";
|
|
438
442
|
import path9 from "path";
|
|
439
443
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
440
|
-
import { and as and13, eq as
|
|
444
|
+
import { and as and13, eq as eq18 } from "drizzle-orm";
|
|
441
445
|
import Fastify from "fastify";
|
|
442
446
|
import os5 from "os";
|
|
443
447
|
|
|
@@ -4065,31 +4069,10 @@ import { eq as eq4, and as and4 } from "drizzle-orm";
|
|
|
4065
4069
|
var log4 = createLogger("SitemapParser");
|
|
4066
4070
|
var LOC_REGEX = /<loc>([^<]+)<\/loc>/gi;
|
|
4067
4071
|
var SITEMAP_TAG_REGEX = /<sitemap>[\s\S]*?<\/sitemap>/gi;
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
// private class A
|
|
4073
|
-
/^172\.(1[6-9]|2\d|3[01])\./,
|
|
4074
|
-
// private class B
|
|
4075
|
-
/^192\.168\./
|
|
4076
|
-
// private class C
|
|
4077
|
-
];
|
|
4078
|
-
function validateSitemapUrl(url) {
|
|
4079
|
-
let parsed;
|
|
4080
|
-
try {
|
|
4081
|
-
parsed = new URL(url);
|
|
4082
|
-
} catch {
|
|
4083
|
-
throw new Error(`Invalid sitemap URL: ${url}`);
|
|
4084
|
-
}
|
|
4085
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4086
|
-
throw new Error(`Sitemap URL must use http or https protocol: ${url}`);
|
|
4087
|
-
}
|
|
4088
|
-
const host = parsed.hostname.toLowerCase();
|
|
4089
|
-
for (const pattern of PRIVATE_IP_PATTERNS) {
|
|
4090
|
-
if (pattern.test(host)) {
|
|
4091
|
-
throw new Error(`Sitemap URL points to a private or reserved IP range: ${url}`);
|
|
4092
|
-
}
|
|
4072
|
+
async function validateSitemapUrl(url) {
|
|
4073
|
+
const check = await resolveWebhookTarget(url, { allowLoopback: true });
|
|
4074
|
+
if (!check.ok) {
|
|
4075
|
+
throw new Error(`Sitemap URL rejected: ${check.message.replace(/^"url" /, "")} (${url})`);
|
|
4093
4076
|
}
|
|
4094
4077
|
}
|
|
4095
4078
|
async function readSitemapBody(res) {
|
|
@@ -4119,9 +4102,9 @@ async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
|
|
|
4119
4102
|
if (depth > 3) return;
|
|
4120
4103
|
if (visited.has(url)) return;
|
|
4121
4104
|
visited.add(url);
|
|
4122
|
-
validateSitemapUrl(url);
|
|
4123
4105
|
let res;
|
|
4124
4106
|
try {
|
|
4107
|
+
await validateSitemapUrl(url);
|
|
4125
4108
|
res = await fetch(url);
|
|
4126
4109
|
} catch (err) {
|
|
4127
4110
|
if (!isChild) throw err;
|
|
@@ -5162,8 +5145,166 @@ function buildDiscoveryInsightTitle(input) {
|
|
|
5162
5145
|
return parts.join(" \u2022 ");
|
|
5163
5146
|
}
|
|
5164
5147
|
|
|
5148
|
+
// src/execute-site-audit.ts
|
|
5149
|
+
import crypto12 from "crypto";
|
|
5150
|
+
import { eq as eq10 } from "drizzle-orm";
|
|
5151
|
+
import { runSitemapAudit } from "@ainyc/aeo-audit";
|
|
5152
|
+
var log11 = createLogger("SiteAudit");
|
|
5153
|
+
var SITE_AUDIT_DEFAULT_PAGE_LIMIT = 500;
|
|
5154
|
+
var SITE_AUDIT_MAX_PAGE_LIMIT = 2e3;
|
|
5155
|
+
function toHomepageUrl(canonicalDomain) {
|
|
5156
|
+
const trimmed = canonicalDomain.trim().replace(/\/+$/, "");
|
|
5157
|
+
return /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
5158
|
+
}
|
|
5159
|
+
async function assertSiteAuditUrlAllowed(rawUrl, field) {
|
|
5160
|
+
const check = await resolveWebhookTarget(rawUrl);
|
|
5161
|
+
if (!check.ok) {
|
|
5162
|
+
throw new Error(`${field} ${check.message.replace(/^"url" /, "")}`);
|
|
5163
|
+
}
|
|
5164
|
+
}
|
|
5165
|
+
function clampSiteAuditLimit(limit) {
|
|
5166
|
+
if (limit == null || !Number.isFinite(limit)) return SITE_AUDIT_DEFAULT_PAGE_LIMIT;
|
|
5167
|
+
return Math.max(1, Math.min(SITE_AUDIT_MAX_PAGE_LIMIT, Math.floor(limit)));
|
|
5168
|
+
}
|
|
5169
|
+
function toPageFactor(factor) {
|
|
5170
|
+
return {
|
|
5171
|
+
id: factor.id,
|
|
5172
|
+
name: factor.name,
|
|
5173
|
+
weight: factor.weight,
|
|
5174
|
+
score: factor.score
|
|
5175
|
+
};
|
|
5176
|
+
}
|
|
5177
|
+
function computeFactorAverages(pages) {
|
|
5178
|
+
const byId = /* @__PURE__ */ new Map();
|
|
5179
|
+
for (const page of pages) {
|
|
5180
|
+
if (page.status !== "success" || !page.factors) continue;
|
|
5181
|
+
for (const factor of page.factors) {
|
|
5182
|
+
let entry = byId.get(factor.id);
|
|
5183
|
+
if (!entry) {
|
|
5184
|
+
entry = { name: factor.name, weight: factor.weight, scores: [], pass: 0, partial: 0, fail: 0 };
|
|
5185
|
+
byId.set(factor.id, entry);
|
|
5186
|
+
}
|
|
5187
|
+
entry.scores.push(factor.score);
|
|
5188
|
+
const status = factorStatusFromScore(factor.score);
|
|
5189
|
+
if (status === "pass") entry.pass++;
|
|
5190
|
+
else if (status === "partial") entry.partial++;
|
|
5191
|
+
else entry.fail++;
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
const summaries = [];
|
|
5195
|
+
for (const [id, entry] of byId) {
|
|
5196
|
+
const avgScore = entry.scores.length ? Math.round(entry.scores.reduce((sum, score) => sum + score, 0) / entry.scores.length) : 0;
|
|
5197
|
+
summaries.push({
|
|
5198
|
+
id,
|
|
5199
|
+
name: entry.name,
|
|
5200
|
+
weight: entry.weight,
|
|
5201
|
+
avgScore,
|
|
5202
|
+
status: factorStatusFromScore(avgScore),
|
|
5203
|
+
pagesPassing: entry.pass,
|
|
5204
|
+
pagesPartial: entry.partial,
|
|
5205
|
+
pagesFailing: entry.fail
|
|
5206
|
+
});
|
|
5207
|
+
}
|
|
5208
|
+
summaries.sort((a, b) => b.weight - a.weight || a.name.localeCompare(b.name));
|
|
5209
|
+
return summaries;
|
|
5210
|
+
}
|
|
5211
|
+
async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
5212
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5213
|
+
db.update(runs).set({ status: "running", startedAt }).where(eq10(runs.id, runId)).run();
|
|
5214
|
+
try {
|
|
5215
|
+
const project = db.select().from(projects).where(eq10(projects.id, projectId)).get();
|
|
5216
|
+
if (!project) {
|
|
5217
|
+
throw new Error(`Project not found: ${projectId}`);
|
|
5218
|
+
}
|
|
5219
|
+
const homepageUrl = toHomepageUrl(project.canonicalDomain);
|
|
5220
|
+
const limit = clampSiteAuditLimit(opts.limit);
|
|
5221
|
+
log11.info("start", { runId, projectId, homepageUrl, sitemapUrl: opts.sitemapUrl ?? null, limit });
|
|
5222
|
+
await assertSiteAuditUrlAllowed(homepageUrl, "canonicalDomain");
|
|
5223
|
+
if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
|
|
5224
|
+
const report = await runSitemapAudit(homepageUrl, { sitemapUrl: opts.sitemapUrl, limit });
|
|
5225
|
+
const successCount = report.pages.filter((page) => page.status === "success").length;
|
|
5226
|
+
const pagesErrored = report.pages.filter((page) => page.status === "error").length;
|
|
5227
|
+
const auditable = report.pagesDiscovered - report.pagesSkipped;
|
|
5228
|
+
if (auditable > report.pagesAudited) {
|
|
5229
|
+
log11.info("truncated", {
|
|
5230
|
+
runId,
|
|
5231
|
+
projectId,
|
|
5232
|
+
auditable,
|
|
5233
|
+
audited: report.pagesAudited,
|
|
5234
|
+
dropped: auditable - report.pagesAudited,
|
|
5235
|
+
limit
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
5238
|
+
if (successCount === 0) {
|
|
5239
|
+
throw new Error(
|
|
5240
|
+
`Site audit could not successfully audit any of ${report.pagesAudited} page(s) from ${report.sitemapUrl}.`
|
|
5241
|
+
);
|
|
5242
|
+
}
|
|
5243
|
+
const factorAverages = computeFactorAverages(report.pages);
|
|
5244
|
+
const status = pagesErrored === 0 ? "completed" : "partial";
|
|
5245
|
+
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5246
|
+
db.transaction((tx) => {
|
|
5247
|
+
tx.insert(siteAuditSnapshots).values({
|
|
5248
|
+
id: crypto12.randomUUID(),
|
|
5249
|
+
projectId,
|
|
5250
|
+
runId,
|
|
5251
|
+
sitemapUrl: report.sitemapUrl,
|
|
5252
|
+
auditedAt: report.auditedAt,
|
|
5253
|
+
aggregateScore: report.aggregateScore,
|
|
5254
|
+
pagesDiscovered: report.pagesDiscovered,
|
|
5255
|
+
pagesAudited: report.pagesAudited,
|
|
5256
|
+
pagesSkipped: report.pagesSkipped,
|
|
5257
|
+
pagesErrored,
|
|
5258
|
+
factorAverages,
|
|
5259
|
+
// aeo-audit v3 enriches these (topIssues, avgGrade-free); keep only the
|
|
5260
|
+
// fields our DTO exposes so the stored JSON stays lean.
|
|
5261
|
+
crossCuttingIssues: report.crossCuttingIssues.map((issue) => ({
|
|
5262
|
+
factorId: issue.factorId,
|
|
5263
|
+
factorName: issue.factorName,
|
|
5264
|
+
avgScore: issue.avgScore,
|
|
5265
|
+
affectedPages: issue.affectedPages,
|
|
5266
|
+
totalPages: issue.totalPages,
|
|
5267
|
+
affectedPct: issue.totalPages > 0 ? Math.round(issue.affectedPages / issue.totalPages * 100) : 0,
|
|
5268
|
+
topRecommendations: issue.topRecommendations
|
|
5269
|
+
})),
|
|
5270
|
+
// v3 prioritizedFixes are structured PrioritizedFix objects; persist the
|
|
5271
|
+
// ready-to-display one-line summary to keep the DTO a string list.
|
|
5272
|
+
prioritizedFixes: report.prioritizedFixes.map((fix) => fix.summary),
|
|
5273
|
+
createdAt: finishedAt
|
|
5274
|
+
}).run();
|
|
5275
|
+
for (const page of report.pages) {
|
|
5276
|
+
tx.insert(siteAuditPages).values({
|
|
5277
|
+
id: crypto12.randomUUID(),
|
|
5278
|
+
projectId,
|
|
5279
|
+
runId,
|
|
5280
|
+
url: page.url,
|
|
5281
|
+
overallScore: page.overallScore,
|
|
5282
|
+
status: page.status,
|
|
5283
|
+
error: page.error ?? null,
|
|
5284
|
+
factors: (page.factors ?? []).map(toPageFactor),
|
|
5285
|
+
createdAt: finishedAt
|
|
5286
|
+
}).run();
|
|
5287
|
+
}
|
|
5288
|
+
tx.update(runs).set({ status, finishedAt }).where(eq10(runs.id, runId)).run();
|
|
5289
|
+
});
|
|
5290
|
+
log11.info("completed", {
|
|
5291
|
+
runId,
|
|
5292
|
+
projectId,
|
|
5293
|
+
status,
|
|
5294
|
+
score: report.aggregateScore,
|
|
5295
|
+
audited: report.pagesAudited,
|
|
5296
|
+
errored: pagesErrored
|
|
5297
|
+
});
|
|
5298
|
+
} catch (err) {
|
|
5299
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
5300
|
+
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq10(runs.id, runId)).run();
|
|
5301
|
+
log11.error("failed", { runId, projectId, error: errorMsg });
|
|
5302
|
+
throw err;
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
|
|
5165
5306
|
// src/commands/backfill.ts
|
|
5166
|
-
import { and as and9, eq as
|
|
5307
|
+
import { and as and9, eq as eq11, inArray as inArray4, isNull, sql as sql4 } from "drizzle-orm";
|
|
5167
5308
|
var SNAPSHOT_BATCH_SIZE = 500;
|
|
5168
5309
|
async function backfillAnswerVisibilityCommand(opts) {
|
|
5169
5310
|
const config = loadConfig();
|
|
@@ -5171,7 +5312,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5171
5312
|
migrate(db);
|
|
5172
5313
|
const projectFilter = opts?.project?.trim();
|
|
5173
5314
|
const isDryRun = opts?.dryRun === true;
|
|
5174
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
5315
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
5175
5316
|
let examined = 0;
|
|
5176
5317
|
let updated = 0;
|
|
5177
5318
|
let wouldUpdate = 0;
|
|
@@ -5180,9 +5321,9 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5180
5321
|
let providerErrors = 0;
|
|
5181
5322
|
if (scopedProjects.length > 0) {
|
|
5182
5323
|
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and9(
|
|
5183
|
-
|
|
5324
|
+
eq11(runs.kind, RunKinds["answer-visibility"]),
|
|
5184
5325
|
inArray4(runs.projectId, scopedProjects.map((project) => project.id))
|
|
5185
|
-
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
5326
|
+
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq11(runs.kind, RunKinds["answer-visibility"])).all();
|
|
5186
5327
|
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
5187
5328
|
for (const run of runRows) {
|
|
5188
5329
|
const existing = runIdsByProject.get(run.projectId);
|
|
@@ -5190,7 +5331,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5190
5331
|
else runIdsByProject.set(run.projectId, [run.id]);
|
|
5191
5332
|
}
|
|
5192
5333
|
for (const project of scopedProjects) {
|
|
5193
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
5334
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
5194
5335
|
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
5195
5336
|
if (runIds.length === 0) continue;
|
|
5196
5337
|
const projectDomains = effectiveDomains({
|
|
@@ -5278,7 +5419,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5278
5419
|
} else {
|
|
5279
5420
|
db.transaction((tx) => {
|
|
5280
5421
|
for (const update of pendingUpdates) {
|
|
5281
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
5422
|
+
tx.update(querySnapshots).set(update.patch).where(eq11(querySnapshots.id, update.id)).run();
|
|
5282
5423
|
}
|
|
5283
5424
|
});
|
|
5284
5425
|
updated += pendingUpdates.length;
|
|
@@ -5327,7 +5468,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
5327
5468
|
function backfillNormalizedPaths(db, opts) {
|
|
5328
5469
|
const baseConditions = [];
|
|
5329
5470
|
if (opts?.projectId) {
|
|
5330
|
-
baseConditions.push(
|
|
5471
|
+
baseConditions.push(eq11(gaTrafficSnapshots.projectId, opts.projectId));
|
|
5331
5472
|
}
|
|
5332
5473
|
const rows = db.select({
|
|
5333
5474
|
id: gaTrafficSnapshots.id,
|
|
@@ -5348,7 +5489,7 @@ function backfillNormalizedPaths(db, opts) {
|
|
|
5348
5489
|
unchanged++;
|
|
5349
5490
|
continue;
|
|
5350
5491
|
}
|
|
5351
|
-
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(
|
|
5492
|
+
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq11(gaTrafficSnapshots.id, row.id)).run();
|
|
5352
5493
|
updated++;
|
|
5353
5494
|
}
|
|
5354
5495
|
});
|
|
@@ -5362,7 +5503,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5362
5503
|
const projectFilter = opts?.project?.trim();
|
|
5363
5504
|
let projectId;
|
|
5364
5505
|
if (projectFilter) {
|
|
5365
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
5506
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq11(projects.name, projectFilter)).get();
|
|
5366
5507
|
if (!project) {
|
|
5367
5508
|
const result2 = {
|
|
5368
5509
|
project: projectFilter,
|
|
@@ -5399,7 +5540,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5399
5540
|
function backfillAiReferralPaths(db, opts) {
|
|
5400
5541
|
const baseConditions = [];
|
|
5401
5542
|
if (opts?.projectId) {
|
|
5402
|
-
baseConditions.push(
|
|
5543
|
+
baseConditions.push(eq11(gaAiReferrals.projectId, opts.projectId));
|
|
5403
5544
|
}
|
|
5404
5545
|
const rows = db.select({
|
|
5405
5546
|
id: gaAiReferrals.id,
|
|
@@ -5420,7 +5561,7 @@ function backfillAiReferralPaths(db, opts) {
|
|
|
5420
5561
|
unchanged++;
|
|
5421
5562
|
continue;
|
|
5422
5563
|
}
|
|
5423
|
-
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(
|
|
5564
|
+
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq11(gaAiReferrals.id, row.id)).run();
|
|
5424
5565
|
updated++;
|
|
5425
5566
|
}
|
|
5426
5567
|
});
|
|
@@ -5434,7 +5575,7 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5434
5575
|
const projectFilter = opts?.project?.trim();
|
|
5435
5576
|
let projectId;
|
|
5436
5577
|
if (projectFilter) {
|
|
5437
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
5578
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq11(projects.name, projectFilter)).get();
|
|
5438
5579
|
if (!project) {
|
|
5439
5580
|
const result2 = {
|
|
5440
5581
|
project: projectFilter,
|
|
@@ -5470,10 +5611,10 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5470
5611
|
}
|
|
5471
5612
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
5472
5613
|
const isDryRun = opts?.dryRun === true;
|
|
5473
|
-
const project = db.select().from(projects).where(
|
|
5614
|
+
const project = db.select().from(projects).where(eq11(projects.id, projectId)).get();
|
|
5474
5615
|
if (!project) return { examined: 0, updated: 0, mentioned: 0 };
|
|
5475
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
5476
|
-
const runRows = db.select({ id: runs.id }).from(runs).where(and9(
|
|
5616
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
5617
|
+
const runRows = db.select({ id: runs.id }).from(runs).where(and9(eq11(runs.kind, RunKinds["answer-visibility"]), eq11(runs.projectId, projectId))).all();
|
|
5477
5618
|
const runIds = runRows.map((r) => r.id);
|
|
5478
5619
|
let examined = 0;
|
|
5479
5620
|
let updated = 0;
|
|
@@ -5545,7 +5686,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
|
5545
5686
|
} else {
|
|
5546
5687
|
db.transaction((tx) => {
|
|
5547
5688
|
for (const update of pendingUpdates) {
|
|
5548
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
5689
|
+
tx.update(querySnapshots).set(update.patch).where(eq11(querySnapshots.id, update.id)).run();
|
|
5549
5690
|
}
|
|
5550
5691
|
});
|
|
5551
5692
|
updated += pendingUpdates.length;
|
|
@@ -5560,7 +5701,7 @@ async function backfillAnswerMentionsCommand(opts) {
|
|
|
5560
5701
|
migrate(db);
|
|
5561
5702
|
const projectFilter = opts?.project?.trim();
|
|
5562
5703
|
const isDryRun = opts?.dryRun === true;
|
|
5563
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
5704
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
5564
5705
|
let examined = 0;
|
|
5565
5706
|
let updated = 0;
|
|
5566
5707
|
let wouldUpdate = 0;
|
|
@@ -5620,7 +5761,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
5620
5761
|
return result;
|
|
5621
5762
|
}
|
|
5622
5763
|
async function backfillInsightsCommand(project, opts) {
|
|
5623
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
5764
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-L2A5MFB4.js");
|
|
5624
5765
|
const config = loadConfig();
|
|
5625
5766
|
const db = createClient(config.database);
|
|
5626
5767
|
migrate(db);
|
|
@@ -5779,7 +5920,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5779
5920
|
const config = loadConfig();
|
|
5780
5921
|
const db = createClient(config.database);
|
|
5781
5922
|
migrate(db);
|
|
5782
|
-
const project = db.select().from(projects).where(
|
|
5923
|
+
const project = db.select().from(projects).where(eq11(projects.name, opts.project)).get();
|
|
5783
5924
|
if (!project) {
|
|
5784
5925
|
throw new Error(`Project "${opts.project}" not found`);
|
|
5785
5926
|
}
|
|
@@ -5791,7 +5932,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5791
5932
|
`);
|
|
5792
5933
|
}
|
|
5793
5934
|
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and9(
|
|
5794
|
-
|
|
5935
|
+
eq11(auditLog.projectId, project.id),
|
|
5795
5936
|
inArray4(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
|
|
5796
5937
|
)).orderBy(auditLog.createdAt).all();
|
|
5797
5938
|
const history = replayQueryAuditLog(events);
|
|
@@ -5799,8 +5940,8 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5799
5940
|
runId: runs.id,
|
|
5800
5941
|
createdAt: runs.createdAt,
|
|
5801
5942
|
location: runs.location
|
|
5802
|
-
}).from(runs).innerJoin(querySnapshots,
|
|
5803
|
-
|
|
5943
|
+
}).from(runs).innerJoin(querySnapshots, eq11(querySnapshots.runId, runs.id)).where(and9(
|
|
5944
|
+
eq11(runs.projectId, project.id),
|
|
5804
5945
|
isNull(querySnapshots.queryId),
|
|
5805
5946
|
isNull(querySnapshots.queryText)
|
|
5806
5947
|
)).groupBy(runs.id).orderBy(runs.createdAt).all();
|
|
@@ -5823,7 +5964,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5823
5964
|
createdAt: querySnapshots.createdAt,
|
|
5824
5965
|
answerText: querySnapshots.answerText
|
|
5825
5966
|
}).from(querySnapshots).where(and9(
|
|
5826
|
-
|
|
5967
|
+
eq11(querySnapshots.runId, run.runId),
|
|
5827
5968
|
isNull(querySnapshots.queryId),
|
|
5828
5969
|
isNull(querySnapshots.queryText)
|
|
5829
5970
|
)).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
|
|
@@ -5889,7 +6030,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
5889
6030
|
if (!isDryRun && updates.length > 0) {
|
|
5890
6031
|
db.transaction((tx) => {
|
|
5891
6032
|
for (const u of updates) {
|
|
5892
|
-
tx.update(querySnapshots).set({ queryText: u.queryText }).where(
|
|
6033
|
+
tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq11(querySnapshots.id, u.id)).run();
|
|
5893
6034
|
}
|
|
5894
6035
|
});
|
|
5895
6036
|
}
|
|
@@ -5963,7 +6104,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
5963
6104
|
const projectFilter = opts?.project?.trim();
|
|
5964
6105
|
const isDryRun = opts?.dryRun === true;
|
|
5965
6106
|
const isJson = isMachineFormat(opts?.format);
|
|
5966
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
6107
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq11(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
5967
6108
|
if (scopedProjects.length === 0) {
|
|
5968
6109
|
if (projectFilter && !isJson) {
|
|
5969
6110
|
process.stderr.write(`No project named "${projectFilter}".
|
|
@@ -5989,7 +6130,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
5989
6130
|
byBot: {}
|
|
5990
6131
|
};
|
|
5991
6132
|
const unknownCountRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and9(
|
|
5992
|
-
|
|
6133
|
+
eq11(rawEventSamples.eventType, "unknown"),
|
|
5993
6134
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
5994
6135
|
)).get();
|
|
5995
6136
|
result.unknownBefore = Number(unknownCountRow?.n ?? 0);
|
|
@@ -6002,7 +6143,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6002
6143
|
pathNormalized: rawEventSamples.pathNormalized,
|
|
6003
6144
|
status: rawEventSamples.status
|
|
6004
6145
|
}).from(rawEventSamples).where(and9(
|
|
6005
|
-
|
|
6146
|
+
eq11(rawEventSamples.eventType, "unknown"),
|
|
6006
6147
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
6007
6148
|
)).all();
|
|
6008
6149
|
result.examined = unknownSamples.length;
|
|
@@ -6041,7 +6182,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6041
6182
|
result.reclassified++;
|
|
6042
6183
|
result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
|
|
6043
6184
|
if (isDryRun) continue;
|
|
6044
|
-
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(
|
|
6185
|
+
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq11(rawEventSamples.id, snap.id)).run();
|
|
6045
6186
|
const tsHour = new Date(snap.ts);
|
|
6046
6187
|
tsHour.setUTCMinutes(0, 0, 0);
|
|
6047
6188
|
if (userFetch) {
|
|
@@ -6106,7 +6247,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6106
6247
|
}
|
|
6107
6248
|
if (!isDryRun) {
|
|
6108
6249
|
const afterRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and9(
|
|
6109
|
-
|
|
6250
|
+
eq11(rawEventSamples.eventType, "unknown"),
|
|
6110
6251
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
6111
6252
|
)).get();
|
|
6112
6253
|
result.unknownAfter = Number(afterRow?.n ?? 0);
|
|
@@ -6141,7 +6282,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
6141
6282
|
}
|
|
6142
6283
|
|
|
6143
6284
|
// src/commands/skills.ts
|
|
6144
|
-
import
|
|
6285
|
+
import crypto13 from "crypto";
|
|
6145
6286
|
import fs4 from "fs";
|
|
6146
6287
|
import os4 from "os";
|
|
6147
6288
|
import path5 from "path";
|
|
@@ -6196,7 +6337,7 @@ function walkRelative(dir, prefix = "") {
|
|
|
6196
6337
|
return out.sort();
|
|
6197
6338
|
}
|
|
6198
6339
|
function sha256File(filePath) {
|
|
6199
|
-
return
|
|
6340
|
+
return crypto13.createHash("sha256").update(fs4.readFileSync(filePath)).digest("hex");
|
|
6200
6341
|
}
|
|
6201
6342
|
function readSkillManifest(skillDir) {
|
|
6202
6343
|
try {
|
|
@@ -6519,10 +6660,10 @@ var ProviderRegistry = class {
|
|
|
6519
6660
|
};
|
|
6520
6661
|
|
|
6521
6662
|
// src/scheduler.ts
|
|
6522
|
-
import
|
|
6663
|
+
import crypto14 from "crypto";
|
|
6523
6664
|
import cron from "node-cron";
|
|
6524
|
-
import { and as and10, eq as
|
|
6525
|
-
var
|
|
6665
|
+
import { and as and10, eq as eq12, inArray as inArray5 } from "drizzle-orm";
|
|
6666
|
+
var log12 = createLogger("Scheduler");
|
|
6526
6667
|
function taskKey(projectId, kind) {
|
|
6527
6668
|
return `${projectId}::${kind}`;
|
|
6528
6669
|
}
|
|
@@ -6536,16 +6677,16 @@ var Scheduler = class {
|
|
|
6536
6677
|
}
|
|
6537
6678
|
/** Load all enabled schedules from DB and register cron jobs. */
|
|
6538
6679
|
start() {
|
|
6539
|
-
const allSchedules = this.db.select().from(schedules).where(
|
|
6680
|
+
const allSchedules = this.db.select().from(schedules).where(eq12(schedules.enabled, true)).all();
|
|
6540
6681
|
for (const schedule of allSchedules) {
|
|
6541
6682
|
const missedRunAt = schedule.nextRunAt;
|
|
6542
6683
|
this.registerCronTask(schedule);
|
|
6543
6684
|
if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
6544
|
-
|
|
6685
|
+
log12.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
6545
6686
|
this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
|
|
6546
6687
|
}
|
|
6547
6688
|
}
|
|
6548
|
-
|
|
6689
|
+
log12.info("started", { scheduleCount: allSchedules.length });
|
|
6549
6690
|
}
|
|
6550
6691
|
/** Stop all cron tasks for graceful shutdown. */
|
|
6551
6692
|
stop() {
|
|
@@ -6566,7 +6707,7 @@ var Scheduler = class {
|
|
|
6566
6707
|
this.stopTask(key, existing, "Stopped");
|
|
6567
6708
|
this.tasks.delete(key);
|
|
6568
6709
|
}
|
|
6569
|
-
const schedule = this.db.select().from(schedules).where(and10(
|
|
6710
|
+
const schedule = this.db.select().from(schedules).where(and10(eq12(schedules.projectId, projectId), eq12(schedules.kind, kind))).get();
|
|
6570
6711
|
if (schedule && schedule.enabled) {
|
|
6571
6712
|
this.registerCronTask(schedule);
|
|
6572
6713
|
}
|
|
@@ -6589,13 +6730,13 @@ var Scheduler = class {
|
|
|
6589
6730
|
stopTask(key, task, verb) {
|
|
6590
6731
|
void task.stop();
|
|
6591
6732
|
void task.destroy();
|
|
6592
|
-
|
|
6733
|
+
log12.info(`task.${verb.toLowerCase()}`, { key });
|
|
6593
6734
|
}
|
|
6594
6735
|
registerCronTask(schedule) {
|
|
6595
6736
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
6596
6737
|
const kind = schedule.kind;
|
|
6597
6738
|
if (!cron.validate(cronExpr)) {
|
|
6598
|
-
|
|
6739
|
+
log12.error("cron.invalid", { projectId, kind, cronExpr });
|
|
6599
6740
|
return;
|
|
6600
6741
|
}
|
|
6601
6742
|
const task = cron.schedule(cronExpr, () => {
|
|
@@ -6607,51 +6748,51 @@ var Scheduler = class {
|
|
|
6607
6748
|
this.db.update(schedules).set({
|
|
6608
6749
|
nextRunAt: nextRunFromCron(cronExpr, timezone),
|
|
6609
6750
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6610
|
-
}).where(
|
|
6751
|
+
}).where(eq12(schedules.id, scheduleId)).run();
|
|
6611
6752
|
const label = schedule.preset ?? cronExpr;
|
|
6612
|
-
|
|
6753
|
+
log12.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
6613
6754
|
}
|
|
6614
6755
|
triggerRun(scheduleId, projectId, kind) {
|
|
6615
6756
|
try {
|
|
6616
6757
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6617
|
-
const currentSchedule = this.db.select().from(schedules).where(
|
|
6758
|
+
const currentSchedule = this.db.select().from(schedules).where(eq12(schedules.id, scheduleId)).get();
|
|
6618
6759
|
if (!currentSchedule || !currentSchedule.enabled) {
|
|
6619
|
-
|
|
6760
|
+
log12.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
6620
6761
|
this.remove(projectId, kind);
|
|
6621
6762
|
return;
|
|
6622
6763
|
}
|
|
6623
6764
|
const nextRunAt = nextRunFromCron(currentSchedule.cronExpr, currentSchedule.timezone);
|
|
6624
|
-
const project = this.db.select().from(projects).where(
|
|
6765
|
+
const project = this.db.select().from(projects).where(eq12(projects.id, projectId)).get();
|
|
6625
6766
|
if (!project) {
|
|
6626
|
-
|
|
6767
|
+
log12.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
6627
6768
|
this.remove(projectId, kind);
|
|
6628
6769
|
return;
|
|
6629
6770
|
}
|
|
6630
6771
|
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
6631
6772
|
const sourceId = currentSchedule.sourceId;
|
|
6632
6773
|
if (!sourceId) {
|
|
6633
|
-
|
|
6774
|
+
log12.warn("traffic-sync.missing-source", { scheduleId, projectId });
|
|
6634
6775
|
return;
|
|
6635
6776
|
}
|
|
6636
6777
|
if (!this.callbacks.onTrafficSyncRequested) {
|
|
6637
|
-
|
|
6778
|
+
log12.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
|
|
6638
6779
|
return;
|
|
6639
6780
|
}
|
|
6640
6781
|
this.db.update(schedules).set({
|
|
6641
6782
|
lastRunAt: now,
|
|
6642
6783
|
nextRunAt,
|
|
6643
6784
|
updatedAt: now
|
|
6644
|
-
}).where(
|
|
6645
|
-
|
|
6785
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6786
|
+
log12.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
6646
6787
|
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
6647
6788
|
return;
|
|
6648
6789
|
}
|
|
6649
6790
|
if (kind === SchedulableRunKinds["gbp-sync"]) {
|
|
6650
6791
|
if (!this.callbacks.onGbpSyncRequested) {
|
|
6651
|
-
|
|
6792
|
+
log12.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
|
|
6652
6793
|
return;
|
|
6653
6794
|
}
|
|
6654
|
-
const runId2 =
|
|
6795
|
+
const runId2 = crypto14.randomUUID();
|
|
6655
6796
|
this.db.insert(runs).values({
|
|
6656
6797
|
id: runId2,
|
|
6657
6798
|
projectId,
|
|
@@ -6664,45 +6805,78 @@ var Scheduler = class {
|
|
|
6664
6805
|
lastRunAt: now,
|
|
6665
6806
|
nextRunAt,
|
|
6666
6807
|
updatedAt: now
|
|
6667
|
-
}).where(
|
|
6668
|
-
|
|
6808
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6809
|
+
log12.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
|
|
6669
6810
|
this.callbacks.onGbpSyncRequested(runId2, projectId);
|
|
6670
6811
|
return;
|
|
6671
6812
|
}
|
|
6672
6813
|
if (kind === SchedulableRunKinds["data-refresh"]) {
|
|
6673
6814
|
if (!this.callbacks.onDataRefreshRequested) {
|
|
6674
|
-
|
|
6815
|
+
log12.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
|
|
6675
6816
|
return;
|
|
6676
6817
|
}
|
|
6677
6818
|
this.db.update(schedules).set({
|
|
6678
6819
|
lastRunAt: now,
|
|
6679
6820
|
nextRunAt,
|
|
6680
6821
|
updatedAt: now
|
|
6681
|
-
}).where(
|
|
6682
|
-
|
|
6822
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6823
|
+
log12.info("data-refresh.triggered", { projectName: project.name });
|
|
6683
6824
|
this.callbacks.onDataRefreshRequested(project.name);
|
|
6684
6825
|
return;
|
|
6685
6826
|
}
|
|
6686
6827
|
if (kind === SchedulableRunKinds["backlinks-sync"]) {
|
|
6687
6828
|
if (!this.callbacks.onBacklinksSyncRequested) {
|
|
6688
|
-
|
|
6829
|
+
log12.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
|
|
6689
6830
|
return;
|
|
6690
6831
|
}
|
|
6691
6832
|
this.db.update(schedules).set({
|
|
6692
6833
|
lastRunAt: now,
|
|
6693
6834
|
nextRunAt,
|
|
6694
6835
|
updatedAt: now
|
|
6695
|
-
}).where(
|
|
6696
|
-
|
|
6836
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6837
|
+
log12.info("backlinks-sync.triggered", { projectName: project.name });
|
|
6697
6838
|
this.callbacks.onBacklinksSyncRequested(project.name);
|
|
6698
6839
|
return;
|
|
6699
6840
|
}
|
|
6841
|
+
if (kind === SchedulableRunKinds["site-audit"]) {
|
|
6842
|
+
if (!this.callbacks.onSiteAuditRequested) {
|
|
6843
|
+
log12.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
6844
|
+
return;
|
|
6845
|
+
}
|
|
6846
|
+
const active = this.db.select({ id: runs.id }).from(runs).where(and10(
|
|
6847
|
+
eq12(runs.projectId, projectId),
|
|
6848
|
+
eq12(runs.kind, RunKinds["site-audit"]),
|
|
6849
|
+
inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
6850
|
+
)).get();
|
|
6851
|
+
if (active) {
|
|
6852
|
+
log12.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
|
|
6853
|
+
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6854
|
+
return;
|
|
6855
|
+
}
|
|
6856
|
+
const runId2 = crypto14.randomUUID();
|
|
6857
|
+
this.db.insert(runs).values({
|
|
6858
|
+
id: runId2,
|
|
6859
|
+
projectId,
|
|
6860
|
+
kind: RunKinds["site-audit"],
|
|
6861
|
+
status: RunStatuses.queued,
|
|
6862
|
+
trigger: RunTriggers.scheduled,
|
|
6863
|
+
createdAt: now
|
|
6864
|
+
}).run();
|
|
6865
|
+
this.db.update(schedules).set({
|
|
6866
|
+
lastRunAt: now,
|
|
6867
|
+
nextRunAt,
|
|
6868
|
+
updatedAt: now
|
|
6869
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6870
|
+
log12.info("site-audit.triggered", { runId: runId2, projectName: project.name });
|
|
6871
|
+
this.callbacks.onSiteAuditRequested(runId2, projectId);
|
|
6872
|
+
return;
|
|
6873
|
+
}
|
|
6700
6874
|
const projectLocations = project.locations;
|
|
6701
6875
|
let resolvedLocation;
|
|
6702
6876
|
if (project.defaultLocation) {
|
|
6703
6877
|
const loc = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
6704
6878
|
if (!loc) {
|
|
6705
|
-
|
|
6879
|
+
log12.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
|
|
6706
6880
|
return;
|
|
6707
6881
|
}
|
|
6708
6882
|
resolvedLocation = loc;
|
|
@@ -6716,11 +6890,11 @@ var Scheduler = class {
|
|
|
6716
6890
|
location: locationLabel
|
|
6717
6891
|
});
|
|
6718
6892
|
if (queueResult.conflict) {
|
|
6719
|
-
|
|
6893
|
+
log12.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
6720
6894
|
this.db.update(schedules).set({
|
|
6721
6895
|
nextRunAt,
|
|
6722
6896
|
updatedAt: now
|
|
6723
|
-
}).where(
|
|
6897
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6724
6898
|
return;
|
|
6725
6899
|
}
|
|
6726
6900
|
const runId = queueResult.runId;
|
|
@@ -6728,19 +6902,19 @@ var Scheduler = class {
|
|
|
6728
6902
|
lastRunAt: now,
|
|
6729
6903
|
nextRunAt,
|
|
6730
6904
|
updatedAt: now
|
|
6731
|
-
}).where(
|
|
6905
|
+
}).where(eq12(schedules.id, currentSchedule.id)).run();
|
|
6732
6906
|
const scheduleProviders = currentSchedule.providers;
|
|
6733
6907
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
6734
|
-
|
|
6908
|
+
log12.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
6735
6909
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
6736
6910
|
} catch (err) {
|
|
6737
|
-
|
|
6911
|
+
log12.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
|
|
6738
6912
|
}
|
|
6739
6913
|
}
|
|
6740
6914
|
};
|
|
6741
6915
|
|
|
6742
6916
|
// src/data-refresh.ts
|
|
6743
|
-
var
|
|
6917
|
+
var log13 = createLogger("DataRefresh");
|
|
6744
6918
|
async function refreshAllIntegrations(client, projectName) {
|
|
6745
6919
|
const integrations = [
|
|
6746
6920
|
{ name: "gsc", run: () => client.gscSync(projectName, {}) },
|
|
@@ -6752,19 +6926,19 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
6752
6926
|
results.forEach((result, idx) => {
|
|
6753
6927
|
const integration = integrations[idx].name;
|
|
6754
6928
|
if (result.status === "fulfilled") {
|
|
6755
|
-
|
|
6929
|
+
log13.info("integration.refreshed", { projectName, integration });
|
|
6756
6930
|
} else {
|
|
6757
6931
|
const reason = result.reason;
|
|
6758
6932
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
6759
|
-
|
|
6933
|
+
log13.warn("integration.refresh-failed", { projectName, integration, error: message });
|
|
6760
6934
|
}
|
|
6761
6935
|
});
|
|
6762
6936
|
}
|
|
6763
6937
|
|
|
6764
6938
|
// src/notifier.ts
|
|
6765
|
-
import { eq as
|
|
6766
|
-
import
|
|
6767
|
-
var
|
|
6939
|
+
import { eq as eq13, desc as desc5, and as and11, inArray as inArray6, or } from "drizzle-orm";
|
|
6940
|
+
import crypto15 from "crypto";
|
|
6941
|
+
var log14 = createLogger("Notifier");
|
|
6768
6942
|
var Notifier = class {
|
|
6769
6943
|
db;
|
|
6770
6944
|
serverUrl;
|
|
@@ -6774,26 +6948,26 @@ var Notifier = class {
|
|
|
6774
6948
|
}
|
|
6775
6949
|
/** Called after a run completes (success, partial, or failed). */
|
|
6776
6950
|
async onRunCompleted(runId, projectId) {
|
|
6777
|
-
|
|
6778
|
-
const notifs = this.db.select().from(notifications).where(
|
|
6951
|
+
log14.info("run.completed", { runId, projectId });
|
|
6952
|
+
const notifs = this.db.select().from(notifications).where(eq13(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
6779
6953
|
if (notifs.length === 0) {
|
|
6780
|
-
|
|
6954
|
+
log14.info("notifications.none-enabled", { projectId });
|
|
6781
6955
|
return;
|
|
6782
6956
|
}
|
|
6783
|
-
|
|
6784
|
-
const run = this.db.select().from(runs).where(
|
|
6957
|
+
log14.info("notifications.found", { projectId, count: notifs.length });
|
|
6958
|
+
const run = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
|
|
6785
6959
|
if (!run) {
|
|
6786
|
-
|
|
6960
|
+
log14.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
6787
6961
|
return;
|
|
6788
6962
|
}
|
|
6789
|
-
const project = this.db.select().from(projects).where(
|
|
6963
|
+
const project = this.db.select().from(projects).where(eq13(projects.id, projectId)).get();
|
|
6790
6964
|
if (!project) {
|
|
6791
|
-
|
|
6965
|
+
log14.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
6792
6966
|
return;
|
|
6793
6967
|
}
|
|
6794
6968
|
const transitions = this.computeTransitions(runId, projectId);
|
|
6795
6969
|
const events = [];
|
|
6796
|
-
|
|
6970
|
+
log14.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
6797
6971
|
if (run.status === "completed" || run.status === "partial") {
|
|
6798
6972
|
events.push("run.completed");
|
|
6799
6973
|
}
|
|
@@ -6809,7 +6983,7 @@ var Notifier = class {
|
|
|
6809
6983
|
if (!config.url) continue;
|
|
6810
6984
|
const subscribedEvents = config.events;
|
|
6811
6985
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
6812
|
-
|
|
6986
|
+
log14.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
6813
6987
|
if (matchingEvents.length === 0) continue;
|
|
6814
6988
|
for (const event of matchingEvents) {
|
|
6815
6989
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -6833,11 +7007,11 @@ var Notifier = class {
|
|
|
6833
7007
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
6834
7008
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
6835
7009
|
if (insightEvents.length === 0) return;
|
|
6836
|
-
const notifs = this.db.select().from(notifications).where(
|
|
7010
|
+
const notifs = this.db.select().from(notifications).where(eq13(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
6837
7011
|
if (notifs.length === 0) return;
|
|
6838
|
-
const run = this.db.select().from(runs).where(
|
|
7012
|
+
const run = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
|
|
6839
7013
|
if (!run) return;
|
|
6840
|
-
const project = this.db.select().from(projects).where(
|
|
7014
|
+
const project = this.db.select().from(projects).where(eq13(projects.id, projectId)).get();
|
|
6841
7015
|
if (!project) return;
|
|
6842
7016
|
for (const notif of notifs) {
|
|
6843
7017
|
const config = notif.config;
|
|
@@ -6867,12 +7041,12 @@ var Notifier = class {
|
|
|
6867
7041
|
}
|
|
6868
7042
|
}
|
|
6869
7043
|
computeTransitions(runId, projectId) {
|
|
6870
|
-
const thisRun = this.db.select().from(runs).where(
|
|
7044
|
+
const thisRun = this.db.select().from(runs).where(eq13(runs.id, runId)).get();
|
|
6871
7045
|
if (!thisRun) return [];
|
|
6872
7046
|
const groupSiblings = this.db.select().from(runs).where(and11(
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
7047
|
+
eq13(runs.projectId, projectId),
|
|
7048
|
+
eq13(runs.kind, thisRun.kind),
|
|
7049
|
+
eq13(runs.createdAt, thisRun.createdAt)
|
|
6876
7050
|
)).all();
|
|
6877
7051
|
const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
|
|
6878
7052
|
if (stillPending) return [];
|
|
@@ -6888,7 +7062,7 @@ var Notifier = class {
|
|
|
6888
7062
|
return candidate.id > best.id ? candidate : best;
|
|
6889
7063
|
});
|
|
6890
7064
|
if (winner.id !== runId) return [];
|
|
6891
|
-
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
7065
|
+
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq13(projects.id, projectId)).get();
|
|
6892
7066
|
const locationCount = Math.max(
|
|
6893
7067
|
1,
|
|
6894
7068
|
(projectLocations?.locations ?? []).length
|
|
@@ -6896,9 +7070,9 @@ var Notifier = class {
|
|
|
6896
7070
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
6897
7071
|
const recentRuns = this.db.select().from(runs).where(
|
|
6898
7072
|
and11(
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
or(
|
|
7073
|
+
eq13(runs.projectId, projectId),
|
|
7074
|
+
eq13(runs.kind, thisRun.kind),
|
|
7075
|
+
or(eq13(runs.status, "completed"), eq13(runs.status, "partial"))
|
|
6902
7076
|
)
|
|
6903
7077
|
).orderBy(desc5(runs.createdAt), desc5(runs.id)).limit(RECENT_FETCH_LIMIT).all();
|
|
6904
7078
|
const groups = groupRunsByCreatedAt(recentRuns);
|
|
@@ -6915,13 +7089,13 @@ var Notifier = class {
|
|
|
6915
7089
|
provider: querySnapshots.provider,
|
|
6916
7090
|
location: querySnapshots.location,
|
|
6917
7091
|
citationState: querySnapshots.citationState
|
|
6918
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
7092
|
+
}).from(querySnapshots).leftJoin(queries, eq13(querySnapshots.queryId, queries.id)).where(inArray6(querySnapshots.runId, currentRunIds)).all();
|
|
6919
7093
|
const previousSnapshots = this.db.select({
|
|
6920
7094
|
queryId: querySnapshots.queryId,
|
|
6921
7095
|
provider: querySnapshots.provider,
|
|
6922
7096
|
location: querySnapshots.location,
|
|
6923
7097
|
citationState: querySnapshots.citationState
|
|
6924
|
-
}).from(querySnapshots).where(
|
|
7098
|
+
}).from(querySnapshots).where(inArray6(querySnapshots.runId, previousRunIds)).all();
|
|
6925
7099
|
const prevMap = /* @__PURE__ */ new Map();
|
|
6926
7100
|
for (const s of previousSnapshots) {
|
|
6927
7101
|
if (s.queryId == null) continue;
|
|
@@ -6948,23 +7122,23 @@ var Notifier = class {
|
|
|
6948
7122
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
6949
7123
|
const targetCheck = await resolveWebhookTarget(url);
|
|
6950
7124
|
if (!targetCheck.ok) {
|
|
6951
|
-
|
|
7125
|
+
log14.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
6952
7126
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
6953
7127
|
return;
|
|
6954
7128
|
}
|
|
6955
|
-
|
|
7129
|
+
log14.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
6956
7130
|
const maxRetries = 3;
|
|
6957
7131
|
const delays = [1e3, 4e3, 16e3];
|
|
6958
7132
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
6959
7133
|
try {
|
|
6960
7134
|
const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
|
|
6961
7135
|
if (response.status >= 200 && response.status < 300) {
|
|
6962
|
-
|
|
7136
|
+
log14.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
6963
7137
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
6964
7138
|
return;
|
|
6965
7139
|
}
|
|
6966
7140
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
6967
|
-
|
|
7141
|
+
log14.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
6968
7142
|
if (attempt === maxRetries - 1) {
|
|
6969
7143
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
6970
7144
|
}
|
|
@@ -6972,7 +7146,7 @@ var Notifier = class {
|
|
|
6972
7146
|
const errorDetail = err instanceof Error ? err.message : String(err);
|
|
6973
7147
|
if (attempt === maxRetries - 1) {
|
|
6974
7148
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
6975
|
-
|
|
7149
|
+
log14.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
6976
7150
|
}
|
|
6977
7151
|
}
|
|
6978
7152
|
if (attempt < maxRetries - 1) {
|
|
@@ -6982,7 +7156,7 @@ var Notifier = class {
|
|
|
6982
7156
|
}
|
|
6983
7157
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
6984
7158
|
this.db.insert(auditLog).values({
|
|
6985
|
-
id:
|
|
7159
|
+
id: crypto15.randomUUID(),
|
|
6986
7160
|
projectId,
|
|
6987
7161
|
actor: "scheduler",
|
|
6988
7162
|
action: `notification.${status}`,
|
|
@@ -6995,8 +7169,8 @@ var Notifier = class {
|
|
|
6995
7169
|
};
|
|
6996
7170
|
|
|
6997
7171
|
// src/run-coordinator.ts
|
|
6998
|
-
import { eq as
|
|
6999
|
-
var
|
|
7172
|
+
import { eq as eq14 } from "drizzle-orm";
|
|
7173
|
+
var log15 = createLogger("RunCoordinator");
|
|
7000
7174
|
var RunCoordinator = class {
|
|
7001
7175
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
7002
7176
|
this.db = db;
|
|
@@ -7006,10 +7180,10 @@ var RunCoordinator = class {
|
|
|
7006
7180
|
this.onAeroEvent = onAeroEvent;
|
|
7007
7181
|
}
|
|
7008
7182
|
async onRunCompleted(runId, projectId) {
|
|
7009
|
-
const runRow = this.db.select().from(runs).where(
|
|
7183
|
+
const runRow = this.db.select().from(runs).where(eq14(runs.id, runId)).get();
|
|
7010
7184
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
7011
7185
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
7012
|
-
|
|
7186
|
+
log15.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
7013
7187
|
return;
|
|
7014
7188
|
}
|
|
7015
7189
|
let insightCount = 0;
|
|
@@ -7026,12 +7200,12 @@ var RunCoordinator = class {
|
|
|
7026
7200
|
try {
|
|
7027
7201
|
await this.onInsightsGenerated(runId, projectId, result);
|
|
7028
7202
|
} catch (err) {
|
|
7029
|
-
|
|
7203
|
+
log15.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7030
7204
|
}
|
|
7031
7205
|
}
|
|
7032
7206
|
}
|
|
7033
7207
|
} catch (err) {
|
|
7034
|
-
|
|
7208
|
+
log15.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7035
7209
|
}
|
|
7036
7210
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
7037
7211
|
try {
|
|
@@ -7044,17 +7218,17 @@ var RunCoordinator = class {
|
|
|
7044
7218
|
try {
|
|
7045
7219
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
7046
7220
|
} catch (err) {
|
|
7047
|
-
|
|
7221
|
+
log15.error("gbp-insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7048
7222
|
}
|
|
7049
7223
|
}
|
|
7050
7224
|
} catch (err) {
|
|
7051
|
-
|
|
7225
|
+
log15.error("gbp-intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7052
7226
|
}
|
|
7053
7227
|
}
|
|
7054
7228
|
try {
|
|
7055
7229
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
7056
7230
|
} catch (err) {
|
|
7057
|
-
|
|
7231
|
+
log15.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7058
7232
|
}
|
|
7059
7233
|
if (this.onAeroEvent) {
|
|
7060
7234
|
try {
|
|
@@ -7067,7 +7241,7 @@ var RunCoordinator = class {
|
|
|
7067
7241
|
};
|
|
7068
7242
|
await this.onAeroEvent(ctx);
|
|
7069
7243
|
} catch (err) {
|
|
7070
|
-
|
|
7244
|
+
log15.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7071
7245
|
}
|
|
7072
7246
|
}
|
|
7073
7247
|
}
|
|
@@ -7082,7 +7256,7 @@ var RunCoordinator = class {
|
|
|
7082
7256
|
* so the Aero queue is never starved of a follow-up.
|
|
7083
7257
|
*/
|
|
7084
7258
|
buildDiscoveryAeroContext(runId, projectId, status, error) {
|
|
7085
|
-
const session = this.db.select().from(discoverySessions).where(
|
|
7259
|
+
const session = this.db.select().from(discoverySessions).where(eq14(discoverySessions.runId, runId)).get();
|
|
7086
7260
|
const competitorMap = session ? session.competitorMap : [];
|
|
7087
7261
|
return {
|
|
7088
7262
|
kind: RunKinds["aeo-discover-probe"],
|
|
@@ -7122,8 +7296,8 @@ function analysisResultFromInsights(insights2) {
|
|
|
7122
7296
|
}
|
|
7123
7297
|
|
|
7124
7298
|
// src/agent/session-registry.ts
|
|
7125
|
-
import
|
|
7126
|
-
import { eq as
|
|
7299
|
+
import crypto17 from "crypto";
|
|
7300
|
+
import { eq as eq16 } from "drizzle-orm";
|
|
7127
7301
|
|
|
7128
7302
|
// src/agent/session.ts
|
|
7129
7303
|
import fs7 from "fs";
|
|
@@ -7511,8 +7685,8 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
7511
7685
|
}
|
|
7512
7686
|
|
|
7513
7687
|
// src/agent/memory-store.ts
|
|
7514
|
-
import
|
|
7515
|
-
import { and as and12, desc as desc6, eq as
|
|
7688
|
+
import crypto16 from "crypto";
|
|
7689
|
+
import { and as and12, desc as desc6, eq as eq15, like, sql as sql5 } from "drizzle-orm";
|
|
7516
7690
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
7517
7691
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
7518
7692
|
function rowToDto(row) {
|
|
@@ -7526,7 +7700,7 @@ function rowToDto(row) {
|
|
|
7526
7700
|
};
|
|
7527
7701
|
}
|
|
7528
7702
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
7529
|
-
const query = db.select().from(agentMemory).where(
|
|
7703
|
+
const query = db.select().from(agentMemory).where(eq15(agentMemory.projectId, projectId)).orderBy(desc6(agentMemory.updatedAt));
|
|
7530
7704
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
7531
7705
|
return rows.map(rowToDto);
|
|
7532
7706
|
}
|
|
@@ -7540,7 +7714,7 @@ function upsertMemoryEntry(db, args) {
|
|
|
7540
7714
|
throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
|
|
7541
7715
|
}
|
|
7542
7716
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7543
|
-
const id =
|
|
7717
|
+
const id = crypto16.randomUUID();
|
|
7544
7718
|
db.insert(agentMemory).values({
|
|
7545
7719
|
id,
|
|
7546
7720
|
projectId: args.projectId,
|
|
@@ -7557,12 +7731,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
7557
7731
|
updatedAt: now
|
|
7558
7732
|
}
|
|
7559
7733
|
}).run();
|
|
7560
|
-
const row = db.select().from(agentMemory).where(and12(
|
|
7734
|
+
const row = db.select().from(agentMemory).where(and12(eq15(agentMemory.projectId, args.projectId), eq15(agentMemory.key, args.key))).get();
|
|
7561
7735
|
if (!row) throw new Error("memory upsert produced no row");
|
|
7562
7736
|
return rowToDto(row);
|
|
7563
7737
|
}
|
|
7564
7738
|
function deleteMemoryEntry(db, projectId, key) {
|
|
7565
|
-
const result = db.delete(agentMemory).where(and12(
|
|
7739
|
+
const result = db.delete(agentMemory).where(and12(eq15(agentMemory.projectId, projectId), eq15(agentMemory.key, key))).run();
|
|
7566
7740
|
const changes = result.changes ?? 0;
|
|
7567
7741
|
return changes > 0;
|
|
7568
7742
|
}
|
|
@@ -7577,7 +7751,7 @@ function writeCompactionNote(db, args) {
|
|
|
7577
7751
|
}
|
|
7578
7752
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7579
7753
|
const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
|
|
7580
|
-
const id =
|
|
7754
|
+
const id = crypto16.randomUUID();
|
|
7581
7755
|
let inserted;
|
|
7582
7756
|
db.transaction((tx) => {
|
|
7583
7757
|
tx.insert(agentMemory).values({
|
|
@@ -7592,7 +7766,7 @@ function writeCompactionNote(db, args) {
|
|
|
7592
7766
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
7593
7767
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
7594
7768
|
and12(
|
|
7595
|
-
|
|
7769
|
+
eq15(agentMemory.projectId, args.projectId),
|
|
7596
7770
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
7597
7771
|
)
|
|
7598
7772
|
).orderBy(desc6(agentMemory.updatedAt)).all();
|
|
@@ -7600,7 +7774,7 @@ function writeCompactionNote(db, args) {
|
|
|
7600
7774
|
if (stale.length > 0) {
|
|
7601
7775
|
tx.delete(agentMemory).where(sql5`${agentMemory.id} IN (${sql5.join(stale.map((s) => sql5`${s}`), sql5`, `)})`).run();
|
|
7602
7776
|
}
|
|
7603
|
-
const row = tx.select().from(agentMemory).where(and12(
|
|
7777
|
+
const row = tx.select().from(agentMemory).where(and12(eq15(agentMemory.projectId, args.projectId), eq15(agentMemory.key, key))).get();
|
|
7604
7778
|
if (row) inserted = rowToDto(row);
|
|
7605
7779
|
});
|
|
7606
7780
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -7733,7 +7907,7 @@ async function compactMessages(args) {
|
|
|
7733
7907
|
}
|
|
7734
7908
|
|
|
7735
7909
|
// src/agent/session-registry.ts
|
|
7736
|
-
var
|
|
7910
|
+
var log16 = createLogger("SessionRegistry");
|
|
7737
7911
|
var MAX_HYDRATE_NOTES = 20;
|
|
7738
7912
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
7739
7913
|
function escapeMemoryFragment(value) {
|
|
@@ -7782,7 +7956,7 @@ var SessionRegistry = class {
|
|
|
7782
7956
|
modelProvider: effectiveProvider,
|
|
7783
7957
|
modelId: effectiveModelId,
|
|
7784
7958
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7785
|
-
}).where(
|
|
7959
|
+
}).where(eq16(agentSessions.projectId, projectId)).run();
|
|
7786
7960
|
}
|
|
7787
7961
|
const agent2 = createAeroSession({
|
|
7788
7962
|
projectName,
|
|
@@ -7960,13 +8134,13 @@ ${lines.join("\n")}
|
|
|
7960
8134
|
agent.state.messages = result.messages;
|
|
7961
8135
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
7962
8136
|
this.save(projectName);
|
|
7963
|
-
|
|
8137
|
+
log16.info("compaction.completed", {
|
|
7964
8138
|
projectName,
|
|
7965
8139
|
removedCount: result.removedCount,
|
|
7966
8140
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
7967
8141
|
});
|
|
7968
8142
|
} catch (err) {
|
|
7969
|
-
|
|
8143
|
+
log16.error("compaction.failed", {
|
|
7970
8144
|
projectName,
|
|
7971
8145
|
error: err instanceof Error ? err.message : String(err)
|
|
7972
8146
|
});
|
|
@@ -7996,7 +8170,7 @@ ${lines.join("\n")}
|
|
|
7996
8170
|
modelProvider: nextProvider,
|
|
7997
8171
|
modelId: nextModelId,
|
|
7998
8172
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7999
|
-
}).where(
|
|
8173
|
+
}).where(eq16(agentSessions.projectId, projectId)).run();
|
|
8000
8174
|
}
|
|
8001
8175
|
/** Persist a session's transcript back to the DB. Call after any run settles. */
|
|
8002
8176
|
save(projectName) {
|
|
@@ -8063,7 +8237,7 @@ ${lines.join("\n")}
|
|
|
8063
8237
|
await agent.prompt(msgs);
|
|
8064
8238
|
this.save(projectName);
|
|
8065
8239
|
} catch (err) {
|
|
8066
|
-
|
|
8240
|
+
log16.error("drain.failed", {
|
|
8067
8241
|
projectName,
|
|
8068
8242
|
error: err instanceof Error ? err.message : String(err)
|
|
8069
8243
|
});
|
|
@@ -8158,17 +8332,17 @@ ${lines.join("\n")}
|
|
|
8158
8332
|
return id;
|
|
8159
8333
|
}
|
|
8160
8334
|
tryResolveProjectId(projectName) {
|
|
8161
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
8335
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq16(projects.name, projectName)).get();
|
|
8162
8336
|
return row?.id;
|
|
8163
8337
|
}
|
|
8164
8338
|
loadRow(projectId) {
|
|
8165
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
8339
|
+
const row = this.opts.db.select().from(agentSessions).where(eq16(agentSessions.projectId, projectId)).get();
|
|
8166
8340
|
return row ?? null;
|
|
8167
8341
|
}
|
|
8168
8342
|
insertRow(params) {
|
|
8169
8343
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8170
8344
|
this.opts.db.insert(agentSessions).values({
|
|
8171
|
-
id:
|
|
8345
|
+
id: crypto17.randomUUID(),
|
|
8172
8346
|
projectId: params.projectId,
|
|
8173
8347
|
systemPrompt: params.systemPrompt,
|
|
8174
8348
|
modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
|
|
@@ -8181,14 +8355,14 @@ ${lines.join("\n")}
|
|
|
8181
8355
|
}
|
|
8182
8356
|
updateRow(projectId, patch) {
|
|
8183
8357
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8184
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
8358
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq16(agentSessions.projectId, projectId)).run();
|
|
8185
8359
|
}
|
|
8186
8360
|
};
|
|
8187
8361
|
|
|
8188
8362
|
// src/agent/agent-routes.ts
|
|
8189
|
-
import { eq as
|
|
8363
|
+
import { eq as eq17 } from "drizzle-orm";
|
|
8190
8364
|
function resolveProject(db, name) {
|
|
8191
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
8365
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq17(projects.name, name)).get();
|
|
8192
8366
|
if (!row) throw notFound("project", name);
|
|
8193
8367
|
return row;
|
|
8194
8368
|
}
|
|
@@ -8197,7 +8371,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
8197
8371
|
"/projects/:name/agent/transcript",
|
|
8198
8372
|
async (request) => {
|
|
8199
8373
|
const project = resolveProject(opts.db, request.params.name);
|
|
8200
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
8374
|
+
const row = opts.db.select().from(agentSessions).where(eq17(agentSessions.projectId, project.id)).get();
|
|
8201
8375
|
if (!row) {
|
|
8202
8376
|
return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
8203
8377
|
}
|
|
@@ -8221,7 +8395,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
8221
8395
|
async (request) => {
|
|
8222
8396
|
const project = resolveProject(opts.db, request.params.name);
|
|
8223
8397
|
opts.sessionRegistry.reset(project.name);
|
|
8224
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
8398
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq17(agentSessions.projectId, project.id)).run();
|
|
8225
8399
|
return { status: "reset" };
|
|
8226
8400
|
}
|
|
8227
8401
|
);
|
|
@@ -8661,7 +8835,7 @@ function formatAuditFactorScore(factor) {
|
|
|
8661
8835
|
}
|
|
8662
8836
|
|
|
8663
8837
|
// src/snapshot-service.ts
|
|
8664
|
-
var
|
|
8838
|
+
var log17 = createLogger("Snapshot");
|
|
8665
8839
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
8666
8840
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
8667
8841
|
var ProviderExecutionGate2 = class {
|
|
@@ -8804,13 +8978,12 @@ var SnapshotService = class {
|
|
|
8804
8978
|
return mapAuditReport(report);
|
|
8805
8979
|
} catch (err) {
|
|
8806
8980
|
const message = err instanceof Error ? err.message : String(err);
|
|
8807
|
-
|
|
8981
|
+
log17.warn("audit.failed", { homepageUrl, error: message });
|
|
8808
8982
|
return {
|
|
8809
8983
|
url: homepageUrl,
|
|
8810
8984
|
finalUrl: homepageUrl,
|
|
8811
8985
|
auditedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8812
8986
|
overallScore: 0,
|
|
8813
|
-
overallGrade: "N/A",
|
|
8814
8987
|
summary: `Technical audit unavailable: ${message}`,
|
|
8815
8988
|
factors: []
|
|
8816
8989
|
};
|
|
@@ -8834,7 +9007,7 @@ var SnapshotService = class {
|
|
|
8834
9007
|
queries: parsedQueries
|
|
8835
9008
|
};
|
|
8836
9009
|
} catch (err) {
|
|
8837
|
-
|
|
9010
|
+
log17.warn("profile.generation-failed", {
|
|
8838
9011
|
domain: ctx.domain,
|
|
8839
9012
|
provider: ctx.analysisProvider.adapter.name,
|
|
8840
9013
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -8976,7 +9149,7 @@ var SnapshotService = class {
|
|
|
8976
9149
|
recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
|
|
8977
9150
|
};
|
|
8978
9151
|
} catch (err) {
|
|
8979
|
-
|
|
9152
|
+
log17.warn("response.analysis-failed", {
|
|
8980
9153
|
provider: ctx.analysisProvider.adapter.name,
|
|
8981
9154
|
error: err instanceof Error ? err.message : String(err)
|
|
8982
9155
|
});
|
|
@@ -9036,7 +9209,7 @@ function buildBatchAnalysisPrompt(ctx) {
|
|
|
9036
9209
|
`Services: ${ctx.profile.services.join(", ") || "unknown"}`,
|
|
9037
9210
|
`Category terms: ${ctx.profile.categoryTerms.join(", ") || "unknown"}`,
|
|
9038
9211
|
`Manual competitor hints: ${ctx.manualCompetitors.join(", ") || "none"}`,
|
|
9039
|
-
`Technical audit: ${ctx.audit.overallScore}/100
|
|
9212
|
+
`Technical audit: ${ctx.audit.overallScore}/100 \u2014 ${ctx.audit.summary}`,
|
|
9040
9213
|
"",
|
|
9041
9214
|
"Responses JSON:",
|
|
9042
9215
|
JSON.stringify(ctx.responses, null, 2)
|
|
@@ -9047,7 +9220,7 @@ function buildFallbackBatchAssessment(companyName, audit) {
|
|
|
9047
9220
|
assessments: [],
|
|
9048
9221
|
whatThisMeans: [
|
|
9049
9222
|
`${companyName} needs category-level visibility, not just branded comprehension.`,
|
|
9050
|
-
`The technical baseline is ${audit.overallScore}/100
|
|
9223
|
+
`The technical baseline is ${audit.overallScore}/100, so weak site signals may be making AI systems prefer better-structured alternatives.`
|
|
9051
9224
|
],
|
|
9052
9225
|
recommendedActions: buildFallbackRecommendedActions(audit)
|
|
9053
9226
|
};
|
|
@@ -9191,7 +9364,6 @@ function mapAuditReport(report) {
|
|
|
9191
9364
|
finalUrl: report.finalUrl,
|
|
9192
9365
|
auditedAt: report.auditedAt,
|
|
9193
9366
|
overallScore: report.overallScore,
|
|
9194
|
-
overallGrade: report.overallGrade,
|
|
9195
9367
|
summary: report.summary,
|
|
9196
9368
|
factors: report.factors.map(mapAuditFactor)
|
|
9197
9369
|
};
|
|
@@ -9202,8 +9374,6 @@ function mapAuditFactor(factor) {
|
|
|
9202
9374
|
name: factor.name,
|
|
9203
9375
|
weight: factor.weight,
|
|
9204
9376
|
score: factor.score,
|
|
9205
|
-
grade: factor.grade,
|
|
9206
|
-
status: factor.status,
|
|
9207
9377
|
findings: factor.findings.map((finding) => ({
|
|
9208
9378
|
type: finding.type,
|
|
9209
9379
|
message: finding.message
|
|
@@ -9261,7 +9431,7 @@ function clipText(value, length) {
|
|
|
9261
9431
|
// src/server.ts
|
|
9262
9432
|
var _require3 = createRequire3(import.meta.url);
|
|
9263
9433
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
9264
|
-
var
|
|
9434
|
+
var log18 = createLogger("Server");
|
|
9265
9435
|
var DEFAULT_QUOTA = {
|
|
9266
9436
|
maxConcurrency: 2,
|
|
9267
9437
|
maxRequestsPerMinute: 10,
|
|
@@ -9291,14 +9461,14 @@ function summarizeProviderConfig(provider, config) {
|
|
|
9291
9461
|
};
|
|
9292
9462
|
}
|
|
9293
9463
|
function hashApiKey(key) {
|
|
9294
|
-
return
|
|
9464
|
+
return crypto18.createHash("sha256").update(key).digest("hex");
|
|
9295
9465
|
}
|
|
9296
9466
|
var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
9297
9467
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
9298
9468
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
9299
9469
|
function hashDashboardPassword(password) {
|
|
9300
|
-
const salt =
|
|
9301
|
-
const derived =
|
|
9470
|
+
const salt = crypto18.randomBytes(16);
|
|
9471
|
+
const derived = crypto18.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
|
|
9302
9472
|
N: DASHBOARD_SCRYPT_COST,
|
|
9303
9473
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
9304
9474
|
});
|
|
@@ -9319,18 +9489,18 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
9319
9489
|
} catch {
|
|
9320
9490
|
return { ok: false, needsRehash: false };
|
|
9321
9491
|
}
|
|
9322
|
-
const derived =
|
|
9492
|
+
const derived = crypto18.scryptSync(password, salt, expected.length, {
|
|
9323
9493
|
N: DASHBOARD_SCRYPT_COST,
|
|
9324
9494
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
9325
9495
|
});
|
|
9326
9496
|
if (derived.length !== expected.length) return { ok: false, needsRehash: false };
|
|
9327
|
-
return { ok:
|
|
9497
|
+
return { ok: crypto18.timingSafeEqual(derived, expected), needsRehash: false };
|
|
9328
9498
|
}
|
|
9329
9499
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
9330
9500
|
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
9331
9501
|
const expected = Buffer.from(storedHash, "hex");
|
|
9332
9502
|
if (candidate.length !== expected.length) return { ok: false, needsRehash: false };
|
|
9333
|
-
const ok =
|
|
9503
|
+
const ok = crypto18.timingSafeEqual(candidate, expected);
|
|
9334
9504
|
return { ok, needsRehash: ok };
|
|
9335
9505
|
}
|
|
9336
9506
|
return { ok: false, needsRehash: false };
|
|
@@ -9389,7 +9559,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
9389
9559
|
}
|
|
9390
9560
|
if (migratedGoogle > 0) {
|
|
9391
9561
|
saveConfigPatch({ google: config.google });
|
|
9392
|
-
|
|
9562
|
+
log18.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
9393
9563
|
}
|
|
9394
9564
|
let migratedGa4 = 0;
|
|
9395
9565
|
for (const row of rows.ga4) {
|
|
@@ -9407,9 +9577,16 @@ function applyLegacyCredentials(rows, config) {
|
|
|
9407
9577
|
}
|
|
9408
9578
|
if (migratedGa4 > 0) {
|
|
9409
9579
|
saveConfigPatch({ ga4: config.ga4 });
|
|
9410
|
-
|
|
9580
|
+
log18.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
9411
9581
|
}
|
|
9412
9582
|
}
|
|
9583
|
+
function isLoopbackBindHost(host) {
|
|
9584
|
+
if (host == null || host === "") return true;
|
|
9585
|
+
const normalized = host.trim().toLowerCase().replace(/^\[|\]$/g, "");
|
|
9586
|
+
if (normalized === "localhost" || normalized === "::1") return true;
|
|
9587
|
+
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(normalized)) return true;
|
|
9588
|
+
return false;
|
|
9589
|
+
}
|
|
9413
9590
|
async function createServer(opts) {
|
|
9414
9591
|
const logger = opts.logger === false ? false : process.stdout.isTTY ? {
|
|
9415
9592
|
transport: {
|
|
@@ -9439,11 +9616,11 @@ async function createServer(opts) {
|
|
|
9439
9616
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
9440
9617
|
dropLegacyCredentialColumns(opts.db);
|
|
9441
9618
|
} catch (err) {
|
|
9442
|
-
|
|
9619
|
+
log18.warn("credentials.migration.failed", {
|
|
9443
9620
|
error: err instanceof Error ? err.message : String(err)
|
|
9444
9621
|
});
|
|
9445
9622
|
}
|
|
9446
|
-
|
|
9623
|
+
log18.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
|
|
9447
9624
|
const p = providers[k];
|
|
9448
9625
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
9449
9626
|
}) });
|
|
@@ -9492,7 +9669,7 @@ async function createServer(opts) {
|
|
|
9492
9669
|
intelligenceService,
|
|
9493
9670
|
(runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
|
|
9494
9671
|
async (ctx) => {
|
|
9495
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
9672
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq18(projects.id, ctx.projectId)).get();
|
|
9496
9673
|
if (!project) return;
|
|
9497
9674
|
let content;
|
|
9498
9675
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -9535,6 +9712,11 @@ async function createServer(opts) {
|
|
|
9535
9712
|
app.log.error({ runId, err }, "GBP sync failed");
|
|
9536
9713
|
});
|
|
9537
9714
|
};
|
|
9715
|
+
const runSiteAudit = (runId, projectId, auditOpts) => {
|
|
9716
|
+
executeSiteAudit(opts.db, runId, projectId, auditOpts ?? {}).then(() => runCoordinator.onRunCompleted(runId, projectId)).catch((err) => {
|
|
9717
|
+
app.log.error({ runId, err }, "Site audit failed");
|
|
9718
|
+
});
|
|
9719
|
+
};
|
|
9538
9720
|
const scheduler = new Scheduler(opts.db, {
|
|
9539
9721
|
onRunCreated: (runId, projectId, providers2, location) => {
|
|
9540
9722
|
jobRunner.executeRun(runId, projectId, providers2, location).catch((err) => {
|
|
@@ -9560,8 +9742,8 @@ async function createServer(opts) {
|
|
|
9560
9742
|
});
|
|
9561
9743
|
if (!probed) return;
|
|
9562
9744
|
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(and13(
|
|
9563
|
-
|
|
9564
|
-
|
|
9745
|
+
eq18(ccReleaseSyncs.release, probed.release),
|
|
9746
|
+
eq18(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
9565
9747
|
)).limit(1).get();
|
|
9566
9748
|
if (alreadySynced) {
|
|
9567
9749
|
app.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
|
|
@@ -9574,6 +9756,9 @@ async function createServer(opts) {
|
|
|
9574
9756
|
);
|
|
9575
9757
|
});
|
|
9576
9758
|
})();
|
|
9759
|
+
},
|
|
9760
|
+
onSiteAuditRequested: (runId, projectId) => {
|
|
9761
|
+
runSiteAudit(runId, projectId);
|
|
9577
9762
|
}
|
|
9578
9763
|
});
|
|
9579
9764
|
const providerSummary = API_ADAPTERS.map((adapter) => ({
|
|
@@ -9691,7 +9876,7 @@ async function createServer(opts) {
|
|
|
9691
9876
|
return removed;
|
|
9692
9877
|
}
|
|
9693
9878
|
};
|
|
9694
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
9879
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto18.randomBytes(32).toString("hex");
|
|
9695
9880
|
const googleConnectionStore = {
|
|
9696
9881
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
9697
9882
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -9737,11 +9922,11 @@ async function createServer(opts) {
|
|
|
9737
9922
|
const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
|
|
9738
9923
|
if (opts.config.apiKey) {
|
|
9739
9924
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
9740
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
9925
|
+
const existing = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, keyHash)).get();
|
|
9741
9926
|
if (!existing) {
|
|
9742
9927
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
9743
9928
|
opts.db.insert(apiKeys).values({
|
|
9744
|
-
id: `key_${
|
|
9929
|
+
id: `key_${crypto18.randomBytes(8).toString("hex")}`,
|
|
9745
9930
|
name: "default",
|
|
9746
9931
|
keyHash,
|
|
9747
9932
|
keyPrefix: prefix,
|
|
@@ -9765,7 +9950,7 @@ async function createServer(opts) {
|
|
|
9765
9950
|
};
|
|
9766
9951
|
const createSession = (apiKeyId) => {
|
|
9767
9952
|
pruneExpiredSessions();
|
|
9768
|
-
const sessionId =
|
|
9953
|
+
const sessionId = crypto18.randomBytes(32).toString("hex");
|
|
9769
9954
|
sessions.set(sessionId, {
|
|
9770
9955
|
apiKeyId,
|
|
9771
9956
|
expiresAt: Date.now() + SESSION_TTL_MS
|
|
@@ -9789,7 +9974,7 @@ async function createServer(opts) {
|
|
|
9789
9974
|
};
|
|
9790
9975
|
const getDefaultApiKey = () => {
|
|
9791
9976
|
if (!opts.config.apiKey) return void 0;
|
|
9792
|
-
return opts.db.select().from(apiKeys).where(
|
|
9977
|
+
return opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
9793
9978
|
};
|
|
9794
9979
|
const createPasswordSession = (reply) => {
|
|
9795
9980
|
const key = getDefaultApiKey();
|
|
@@ -9804,6 +9989,15 @@ async function createServer(opts) {
|
|
|
9804
9989
|
}));
|
|
9805
9990
|
return true;
|
|
9806
9991
|
};
|
|
9992
|
+
const boundToLoopback = isLoopbackBindHost(opts.host);
|
|
9993
|
+
const requestHasValidApiKey = (request) => {
|
|
9994
|
+
const header = request.headers.authorization;
|
|
9995
|
+
if (!header) return false;
|
|
9996
|
+
const parts = header.split(" ");
|
|
9997
|
+
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
9998
|
+
const key = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(parts[1]))).get();
|
|
9999
|
+
return Boolean(key && !key.revokedAt);
|
|
10000
|
+
};
|
|
9807
10001
|
app.get(apiPrefix + "/session", async (request, reply) => {
|
|
9808
10002
|
const sessionId = parseCookies(request.headers.cookie)[SESSION_COOKIE_NAME];
|
|
9809
10003
|
return reply.send({
|
|
@@ -9812,6 +10006,10 @@ async function createServer(opts) {
|
|
|
9812
10006
|
});
|
|
9813
10007
|
});
|
|
9814
10008
|
app.post(apiPrefix + "/session/setup", async (request, reply) => {
|
|
10009
|
+
if (!boundToLoopback && !requestHasValidApiKey(request)) {
|
|
10010
|
+
const err = authRequired("This server is network-reachable; setting the dashboard password requires a valid API key.");
|
|
10011
|
+
return reply.status(err.statusCode).send(err.toJSON());
|
|
10012
|
+
}
|
|
9815
10013
|
if (opts.config.dashboardPasswordHash) {
|
|
9816
10014
|
const err = validationError("Dashboard password is already configured");
|
|
9817
10015
|
return reply.status(err.statusCode).send(err.toJSON());
|
|
@@ -9851,12 +10049,12 @@ async function createServer(opts) {
|
|
|
9851
10049
|
return reply.send({ authenticated: true });
|
|
9852
10050
|
}
|
|
9853
10051
|
if (apiKey) {
|
|
9854
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
10052
|
+
const key = opts.db.select().from(apiKeys).where(eq18(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
9855
10053
|
if (!key || key.revokedAt) {
|
|
9856
10054
|
const err2 = authInvalid();
|
|
9857
10055
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
9858
10056
|
}
|
|
9859
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
10057
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq18(apiKeys.id, key.id)).run();
|
|
9860
10058
|
const sessionId = createSession(key.id);
|
|
9861
10059
|
reply.header("set-cookie", serializeSessionCookie({
|
|
9862
10060
|
name: SESSION_COOKIE_NAME,
|
|
@@ -10010,7 +10208,7 @@ async function createServer(opts) {
|
|
|
10010
10208
|
deps: {
|
|
10011
10209
|
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
10012
10210
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10013
|
-
const runId =
|
|
10211
|
+
const runId = crypto18.randomUUID();
|
|
10014
10212
|
opts.db.insert(runs).values({
|
|
10015
10213
|
id: runId,
|
|
10016
10214
|
projectId,
|
|
@@ -10048,6 +10246,9 @@ async function createServer(opts) {
|
|
|
10048
10246
|
app.log.error({ runId: input.runId, err }, "Discovery run failed");
|
|
10049
10247
|
});
|
|
10050
10248
|
},
|
|
10249
|
+
onSiteAuditRequested: (runId, projectId, auditOpts) => {
|
|
10250
|
+
runSiteAudit(runId, projectId, auditOpts);
|
|
10251
|
+
},
|
|
10051
10252
|
onBacklinksPruneCache: (release) => {
|
|
10052
10253
|
try {
|
|
10053
10254
|
pruneCachedRelease(release);
|
|
@@ -10093,7 +10294,7 @@ async function createServer(opts) {
|
|
|
10093
10294
|
...inspectOpts,
|
|
10094
10295
|
config: opts.config
|
|
10095
10296
|
}).then(() => {
|
|
10096
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
10297
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq18(runs.id, runId)).get();
|
|
10097
10298
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
10098
10299
|
return maybeRefreshGscCoverage(opts.db, opts.config, projectId);
|
|
10099
10300
|
}
|
|
@@ -10181,7 +10382,7 @@ async function createServer(opts) {
|
|
|
10181
10382
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
10182
10383
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10183
10384
|
opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
|
|
10184
|
-
id:
|
|
10385
|
+
id: crypto18.randomUUID(),
|
|
10185
10386
|
projectId,
|
|
10186
10387
|
actor: "api",
|
|
10187
10388
|
action: existing ? "provider.updated" : "provider.created",
|