@ainyc/canonry 4.80.0 → 4.82.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/assets/agent-workspace/skills/canonry/references/canonry-cli.md +24 -12
- package/assets/assets/{BacklinksPage-dRc62jAY.js → BacklinksPage-CHclt-pq.js} +1 -1
- package/assets/assets/{ChartPrimitives-D2_IvTkk.js → ChartPrimitives-2Ub4vNWe.js} +1 -1
- package/assets/assets/ProjectPage-UPmHfuxR.js +6 -0
- package/assets/assets/{RunRow-C0MA3yuQ.js → RunRow-rUL1UeA3.js} +1 -1
- package/assets/assets/{RunsPage-4uxTYgGy.js → RunsPage-BQpHfUJf.js} +1 -1
- package/assets/assets/{SettingsPage-3-SLhcJ7.js → SettingsPage-DjTJlr_1.js} +1 -1
- package/assets/assets/{TrafficPage-DZ50qwme.js → TrafficPage-D7rv3BrH.js} +1 -1
- package/assets/assets/TrafficSourceDetailPage-BysyuH2H.js +1 -0
- package/assets/assets/{arrow-left-BaZIkAXX.js → arrow-left-CR_FGlkE.js} +1 -1
- package/assets/assets/{extract-error-message-cpvfuFqW.js → extract-error-message-BKkAbWNp.js} +1 -1
- package/assets/assets/{index-EnY_OBRd.js → index-DzzTt20n.js} +87 -87
- package/assets/assets/{trash-2-JpcztiS5.js → trash-2-uSttujvh.js} +1 -1
- package/assets/index.html +1 -1
- package/dist/{chunk-CXIGHPBE.js → chunk-IEUTAQUF.js} +471 -124
- package/dist/{chunk-2QBSRHSN.js → chunk-JLAD6CYH.js} +88 -8
- package/dist/{chunk-AVN6Q6LM.js → chunk-KPSFRSS7.js} +96 -3
- package/dist/{chunk-LCABGFYN.js → chunk-NSZ3D3MM.js} +404 -242
- package/dist/cli.js +145 -18
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-ZWW3I3NL.js → intelligence-service-2UUJ3YGI.js} +2 -2
- package/dist/mcp.js +23 -4
- package/package.json +8 -8
- package/assets/assets/ProjectPage-DSuvRUIf.js +0 -6
- package/assets/assets/TrafficSourceDetailPage-CzK5TMFp.js +0 -1
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JLAD6CYH.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -62,9 +62,11 @@ import {
|
|
|
62
62
|
gbpPlaceActions,
|
|
63
63
|
gbpPlaceDetails,
|
|
64
64
|
getCrawlIssues,
|
|
65
|
+
getLinkCounts,
|
|
65
66
|
getLodging,
|
|
66
67
|
getPlaceDetails,
|
|
67
68
|
getUrlInfo,
|
|
69
|
+
getUrlLinks,
|
|
68
70
|
groupRunsByCreatedAt,
|
|
69
71
|
gscCoverageSnapshots,
|
|
70
72
|
gscSearchData,
|
|
@@ -102,13 +104,14 @@ import {
|
|
|
102
104
|
siteAuditPages,
|
|
103
105
|
siteAuditSnapshots,
|
|
104
106
|
usageCounters
|
|
105
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-IEUTAQUF.js";
|
|
106
108
|
import {
|
|
107
109
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
108
110
|
AGENT_PROVIDER_IDS,
|
|
109
111
|
AI_ENGINE_DOMAINS,
|
|
110
112
|
AI_ENGINE_SELF_DOMAINS,
|
|
111
113
|
AgentProviderIds,
|
|
114
|
+
BacklinkSources,
|
|
112
115
|
CcReleaseSyncStatuses,
|
|
113
116
|
CodingAgents,
|
|
114
117
|
DiscoveryCompetitorTypes,
|
|
@@ -136,6 +139,7 @@ import {
|
|
|
136
139
|
buildRunErrorFromMessages,
|
|
137
140
|
classifySkillFile,
|
|
138
141
|
coerceSkillManifest,
|
|
142
|
+
computeBacklinkSummaryMetrics,
|
|
139
143
|
contentActionLabel,
|
|
140
144
|
contentBriefDtoSchema,
|
|
141
145
|
determineAnswerMentioned,
|
|
@@ -143,6 +147,7 @@ import {
|
|
|
143
147
|
effectiveBrandNames,
|
|
144
148
|
effectiveDomains,
|
|
145
149
|
factorStatusFromScore,
|
|
150
|
+
hostOf,
|
|
146
151
|
isAgentProviderId,
|
|
147
152
|
isBrowserProvider,
|
|
148
153
|
isRetryableHttpError,
|
|
@@ -155,7 +160,7 @@ import {
|
|
|
155
160
|
validationError,
|
|
156
161
|
winnabilityClassLabel,
|
|
157
162
|
withRetry
|
|
158
|
-
} from "./chunk-
|
|
163
|
+
} from "./chunk-KPSFRSS7.js";
|
|
159
164
|
|
|
160
165
|
// src/telemetry.ts
|
|
161
166
|
import crypto from "crypto";
|
|
@@ -443,11 +448,11 @@ function checkLatestVersionForServer(opts) {
|
|
|
443
448
|
|
|
444
449
|
// src/server.ts
|
|
445
450
|
import { createRequire as createRequire3 } from "module";
|
|
446
|
-
import
|
|
451
|
+
import crypto20 from "crypto";
|
|
447
452
|
import fs8 from "fs";
|
|
448
453
|
import path9 from "path";
|
|
449
454
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
450
|
-
import { and as
|
|
455
|
+
import { and as and14, eq as eq20 } from "drizzle-orm";
|
|
451
456
|
import Fastify from "fastify";
|
|
452
457
|
import os5 from "os";
|
|
453
458
|
|
|
@@ -5003,6 +5008,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
5003
5008
|
id: crypto10.randomUUID(),
|
|
5004
5009
|
projectId: p.id,
|
|
5005
5010
|
releaseSyncId: syncId,
|
|
5011
|
+
source: BacklinkSources.commoncrawl,
|
|
5006
5012
|
release,
|
|
5007
5013
|
targetDomain: p.canonicalDomain,
|
|
5008
5014
|
totalLinkingDomains: summary.totalLinkingDomains,
|
|
@@ -5011,7 +5017,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
5011
5017
|
queriedAt,
|
|
5012
5018
|
createdAt: queriedAt
|
|
5013
5019
|
}).onConflictDoUpdate({
|
|
5014
|
-
target: [backlinkSummaries.projectId, backlinkSummaries.release],
|
|
5020
|
+
target: [backlinkSummaries.projectId, backlinkSummaries.source, backlinkSummaries.release],
|
|
5015
5021
|
set: {
|
|
5016
5022
|
releaseSyncId: syncId,
|
|
5017
5023
|
targetDomain: p.canonicalDomain,
|
|
@@ -5144,13 +5150,18 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
5144
5150
|
const targetDomain = project.canonicalDomain;
|
|
5145
5151
|
db.transaction((tx) => {
|
|
5146
5152
|
tx.delete(backlinkDomains).where(
|
|
5147
|
-
and7(
|
|
5153
|
+
and7(
|
|
5154
|
+
eq9(backlinkDomains.projectId, projectId),
|
|
5155
|
+
eq9(backlinkDomains.source, BacklinkSources.commoncrawl),
|
|
5156
|
+
eq9(backlinkDomains.release, release)
|
|
5157
|
+
)
|
|
5148
5158
|
).run();
|
|
5149
5159
|
if (rows.length > 0) {
|
|
5150
5160
|
const values = rows.map((r) => ({
|
|
5151
5161
|
id: crypto11.randomUUID(),
|
|
5152
5162
|
projectId,
|
|
5153
5163
|
releaseSyncId: syncId,
|
|
5164
|
+
source: BacklinkSources.commoncrawl,
|
|
5154
5165
|
release,
|
|
5155
5166
|
targetDomain,
|
|
5156
5167
|
linkingDomain: r.linkingDomain,
|
|
@@ -5164,6 +5175,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
5164
5175
|
id: crypto11.randomUUID(),
|
|
5165
5176
|
projectId,
|
|
5166
5177
|
releaseSyncId: syncId,
|
|
5178
|
+
source: BacklinkSources.commoncrawl,
|
|
5167
5179
|
release,
|
|
5168
5180
|
targetDomain,
|
|
5169
5181
|
totalLinkingDomains: summary.totalLinkingDomains,
|
|
@@ -5172,7 +5184,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
5172
5184
|
queriedAt,
|
|
5173
5185
|
createdAt: queriedAt
|
|
5174
5186
|
}).onConflictDoUpdate({
|
|
5175
|
-
target: [backlinkSummaries.projectId, backlinkSummaries.release],
|
|
5187
|
+
target: [backlinkSummaries.projectId, backlinkSummaries.source, backlinkSummaries.release],
|
|
5176
5188
|
set: {
|
|
5177
5189
|
releaseSyncId: syncId,
|
|
5178
5190
|
targetDomain,
|
|
@@ -5199,33 +5211,167 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
5199
5211
|
}
|
|
5200
5212
|
}
|
|
5201
5213
|
function computeSummary2(rows) {
|
|
5202
|
-
|
|
5203
|
-
return { totalLinkingDomains: 0, totalHosts: 0, top10HostsShare: "0" };
|
|
5204
|
-
}
|
|
5205
|
-
const sorted = [...rows].sort((a, b) => b.numHosts - a.numHosts);
|
|
5206
|
-
const totalHosts = sorted.reduce((acc, r) => acc + r.numHosts, 0);
|
|
5207
|
-
const top10Hosts = sorted.slice(0, 10).reduce((acc, r) => acc + r.numHosts, 0);
|
|
5208
|
-
const share = totalHosts > 0 ? top10Hosts / totalHosts : 0;
|
|
5209
|
-
return {
|
|
5210
|
-
totalLinkingDomains: rows.length,
|
|
5211
|
-
totalHosts,
|
|
5212
|
-
top10HostsShare: share.toFixed(6)
|
|
5213
|
-
};
|
|
5214
|
+
return computeBacklinkSummaryMetrics(rows);
|
|
5214
5215
|
}
|
|
5215
5216
|
|
|
5216
|
-
// src/
|
|
5217
|
+
// src/bing-backlinks-sync.ts
|
|
5217
5218
|
import crypto12 from "crypto";
|
|
5218
5219
|
import { and as and8, eq as eq10 } from "drizzle-orm";
|
|
5219
|
-
var log11 = createLogger("
|
|
5220
|
+
var log11 = createLogger("BingBacklinkSync");
|
|
5221
|
+
var DEFAULT_MAX_PAGES = 200;
|
|
5222
|
+
var LINK_COUNTS_MAX_PAGES = 50;
|
|
5223
|
+
var URL_LINKS_MAX_PAGES = 20;
|
|
5224
|
+
function bingReleaseId(date) {
|
|
5225
|
+
return `bing-${date.toISOString().slice(0, 10)}`;
|
|
5226
|
+
}
|
|
5227
|
+
function aggregateInboundLinksByDomain(links, targetDomain) {
|
|
5228
|
+
const target = hostOf(targetDomain);
|
|
5229
|
+
const byHost = /* @__PURE__ */ new Map();
|
|
5230
|
+
for (const link of links) {
|
|
5231
|
+
const host = hostOf(link.Url);
|
|
5232
|
+
if (!host) continue;
|
|
5233
|
+
if (target && (host === target || host.endsWith(`.${target}`))) continue;
|
|
5234
|
+
let urls = byHost.get(host);
|
|
5235
|
+
if (!urls) {
|
|
5236
|
+
urls = /* @__PURE__ */ new Set();
|
|
5237
|
+
byHost.set(host, urls);
|
|
5238
|
+
}
|
|
5239
|
+
urls.add(link.Url);
|
|
5240
|
+
}
|
|
5241
|
+
return [...byHost.entries()].map(([linkingDomain, urls]) => ({ linkingDomain, numHosts: urls.size })).sort((a, b) => b.numHosts - a.numHosts || a.linkingDomain.localeCompare(b.linkingDomain));
|
|
5242
|
+
}
|
|
5243
|
+
function computeBingSummary(rows) {
|
|
5244
|
+
return computeBacklinkSummaryMetrics(rows);
|
|
5245
|
+
}
|
|
5246
|
+
function defaultDeps4() {
|
|
5247
|
+
return { getLinkCounts, getUrlLinks, now: () => /* @__PURE__ */ new Date() };
|
|
5248
|
+
}
|
|
5249
|
+
async function executeBingBacklinkSync(db, runId, projectId, opts) {
|
|
5250
|
+
const deps = { ...defaultDeps4(), ...opts.deps };
|
|
5251
|
+
const startedAt = deps.now().toISOString();
|
|
5252
|
+
db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq10(runs.id, runId)).run();
|
|
5253
|
+
try {
|
|
5254
|
+
const project = db.select().from(projects).where(eq10(projects.id, projectId)).get();
|
|
5255
|
+
if (!project) throw new Error(`Project not found: ${projectId}`);
|
|
5256
|
+
const conn = opts.resolveConnection(project.canonicalDomain);
|
|
5257
|
+
if (!conn) throw new Error(`No Bing Webmaster connection for ${project.canonicalDomain}`);
|
|
5258
|
+
const siteUrl = conn.siteUrl;
|
|
5259
|
+
if (!siteUrl) {
|
|
5260
|
+
throw new Error(`Bing connection for ${project.canonicalDomain} has no verified site selected`);
|
|
5261
|
+
}
|
|
5262
|
+
const pages = await deps.getLinkCounts(conn.apiKey, siteUrl, { maxPages: LINK_COUNTS_MAX_PAGES });
|
|
5263
|
+
const maxPages = Math.max(1, opts.maxPages ?? DEFAULT_MAX_PAGES);
|
|
5264
|
+
const targetPages = [...pages].sort((a, b) => b.Count - a.Count).slice(0, maxPages);
|
|
5265
|
+
if (pages.length > targetPages.length) {
|
|
5266
|
+
log11.info("bing-sync.pages-capped", {
|
|
5267
|
+
runId,
|
|
5268
|
+
projectId,
|
|
5269
|
+
sitePagesFound: pages.length,
|
|
5270
|
+
sitePagesPulled: targetPages.length
|
|
5271
|
+
});
|
|
5272
|
+
}
|
|
5273
|
+
const allLinks = [];
|
|
5274
|
+
let pageFailures = 0;
|
|
5275
|
+
for (const page of targetPages) {
|
|
5276
|
+
try {
|
|
5277
|
+
const links = await deps.getUrlLinks(conn.apiKey, siteUrl, page.Url, { maxPages: URL_LINKS_MAX_PAGES });
|
|
5278
|
+
allLinks.push(...links);
|
|
5279
|
+
} catch (err) {
|
|
5280
|
+
pageFailures++;
|
|
5281
|
+
log11.warn("bing-sync.page-failed", {
|
|
5282
|
+
runId,
|
|
5283
|
+
projectId,
|
|
5284
|
+
page: page.Url,
|
|
5285
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5286
|
+
});
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
if (targetPages.length > 0 && pageFailures === targetPages.length) {
|
|
5290
|
+
throw new Error(`All ${targetPages.length} Bing inbound-link page fetch(es) failed`);
|
|
5291
|
+
}
|
|
5292
|
+
const rows = aggregateInboundLinksByDomain(allLinks, project.canonicalDomain);
|
|
5293
|
+
const summary = computeBingSummary(rows);
|
|
5294
|
+
const queriedAt = deps.now().toISOString();
|
|
5295
|
+
const release = bingReleaseId(deps.now());
|
|
5296
|
+
const targetDomain = project.canonicalDomain;
|
|
5297
|
+
const source = BacklinkSources["bing-webmaster"];
|
|
5298
|
+
const existing = db.select({ id: backlinkSummaries.id }).from(backlinkSummaries).where(and8(
|
|
5299
|
+
eq10(backlinkSummaries.projectId, projectId),
|
|
5300
|
+
eq10(backlinkSummaries.source, source),
|
|
5301
|
+
eq10(backlinkSummaries.release, release)
|
|
5302
|
+
)).get();
|
|
5303
|
+
const preserveExisting = pageFailures > 0 && !!existing;
|
|
5304
|
+
if (!preserveExisting) {
|
|
5305
|
+
db.transaction((tx) => {
|
|
5306
|
+
tx.delete(backlinkDomains).where(and8(
|
|
5307
|
+
eq10(backlinkDomains.projectId, projectId),
|
|
5308
|
+
eq10(backlinkDomains.source, source),
|
|
5309
|
+
eq10(backlinkDomains.release, release)
|
|
5310
|
+
)).run();
|
|
5311
|
+
if (rows.length > 0) {
|
|
5312
|
+
tx.insert(backlinkDomains).values(rows.map((r) => ({
|
|
5313
|
+
id: crypto12.randomUUID(),
|
|
5314
|
+
projectId,
|
|
5315
|
+
releaseSyncId: null,
|
|
5316
|
+
source,
|
|
5317
|
+
release,
|
|
5318
|
+
targetDomain,
|
|
5319
|
+
linkingDomain: r.linkingDomain,
|
|
5320
|
+
numHosts: r.numHosts,
|
|
5321
|
+
createdAt: queriedAt
|
|
5322
|
+
}))).run();
|
|
5323
|
+
}
|
|
5324
|
+
tx.insert(backlinkSummaries).values({
|
|
5325
|
+
id: crypto12.randomUUID(),
|
|
5326
|
+
projectId,
|
|
5327
|
+
releaseSyncId: null,
|
|
5328
|
+
source,
|
|
5329
|
+
release,
|
|
5330
|
+
targetDomain,
|
|
5331
|
+
totalLinkingDomains: summary.totalLinkingDomains,
|
|
5332
|
+
totalHosts: summary.totalHosts,
|
|
5333
|
+
top10HostsShare: summary.top10HostsShare,
|
|
5334
|
+
queriedAt,
|
|
5335
|
+
createdAt: queriedAt
|
|
5336
|
+
}).onConflictDoUpdate({
|
|
5337
|
+
target: [backlinkSummaries.projectId, backlinkSummaries.source, backlinkSummaries.release],
|
|
5338
|
+
set: {
|
|
5339
|
+
targetDomain,
|
|
5340
|
+
totalLinkingDomains: summary.totalLinkingDomains,
|
|
5341
|
+
totalHosts: summary.totalHosts,
|
|
5342
|
+
top10HostsShare: summary.top10HostsShare,
|
|
5343
|
+
queriedAt
|
|
5344
|
+
}
|
|
5345
|
+
}).run();
|
|
5346
|
+
});
|
|
5347
|
+
}
|
|
5348
|
+
const finishedAt = deps.now().toISOString();
|
|
5349
|
+
const status = pageFailures > 0 ? RunStatuses.partial : RunStatuses.completed;
|
|
5350
|
+
const error = preserveExisting ? `Kept existing ${release} snapshot; ${pageFailures} of ${targetPages.length} inbound-link page fetches failed` : pageFailures > 0 ? `${pageFailures} of ${targetPages.length} inbound-link page fetches failed` : null;
|
|
5351
|
+
db.update(runs).set({ status, error, finishedAt }).where(eq10(runs.id, runId)).run();
|
|
5352
|
+
log11.info("bing-sync.completed", { runId, projectId, release, rows: rows.length, status, pageFailures, preserveExisting });
|
|
5353
|
+
} catch (err) {
|
|
5354
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
5355
|
+
const finishedAt = deps.now().toISOString();
|
|
5356
|
+
db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt }).where(eq10(runs.id, runId)).run();
|
|
5357
|
+
log11.error("bing-sync.failed", { runId, projectId, error: errorMsg });
|
|
5358
|
+
throw err;
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
|
|
5362
|
+
// src/discovery-run.ts
|
|
5363
|
+
import crypto13 from "crypto";
|
|
5364
|
+
import { and as and9, eq as eq11 } from "drizzle-orm";
|
|
5365
|
+
var log12 = createLogger("DiscoveryRun");
|
|
5220
5366
|
var DEFAULT_SEED_COUNT = 30;
|
|
5221
5367
|
var QUERIES_PER_INTENT_BUCKET = 6;
|
|
5222
5368
|
async function executeDiscoveryRun(opts) {
|
|
5223
5369
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5224
|
-
opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(
|
|
5370
|
+
opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq11(runs.id, opts.runId)).run();
|
|
5225
5371
|
try {
|
|
5226
|
-
const projectRow = opts.db.select().from(projects).where(
|
|
5372
|
+
const projectRow = opts.db.select().from(projects).where(eq11(projects.id, opts.projectId)).get();
|
|
5227
5373
|
if (!projectRow) throw new Error(`Project ${opts.projectId} not found`);
|
|
5228
|
-
const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(
|
|
5374
|
+
const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq11(competitors.projectId, opts.projectId)).all().map((r) => r.domain.toLowerCase());
|
|
5229
5375
|
const canonicalDomains = effectiveDomains({
|
|
5230
5376
|
canonicalDomain: projectRow.canonicalDomain,
|
|
5231
5377
|
ownedDomains: projectRow.ownedDomains
|
|
@@ -5255,8 +5401,8 @@ async function executeDiscoveryRun(opts) {
|
|
|
5255
5401
|
seedProvider: result.seedProvider,
|
|
5256
5402
|
result
|
|
5257
5403
|
});
|
|
5258
|
-
opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
5259
|
-
|
|
5404
|
+
opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq11(runs.id, opts.runId)).run();
|
|
5405
|
+
log12.info("discovery.completed", {
|
|
5260
5406
|
runId: opts.runId,
|
|
5261
5407
|
sessionId: opts.sessionId,
|
|
5262
5408
|
buckets: result.buckets,
|
|
@@ -5264,13 +5410,13 @@ async function executeDiscoveryRun(opts) {
|
|
|
5264
5410
|
});
|
|
5265
5411
|
} catch (err) {
|
|
5266
5412
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
5267
|
-
|
|
5413
|
+
log12.error("discovery.failed", { runId: opts.runId, sessionId: opts.sessionId, error: errorMsg });
|
|
5268
5414
|
markSessionFailed(opts.db, opts.sessionId, errorMsg);
|
|
5269
5415
|
opts.db.update(runs).set({
|
|
5270
5416
|
status: RunStatuses.failed,
|
|
5271
5417
|
finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5272
5418
|
error: errorMsg
|
|
5273
|
-
}).where(
|
|
5419
|
+
}).where(eq11(runs.id, opts.runId)).run();
|
|
5274
5420
|
}
|
|
5275
5421
|
}
|
|
5276
5422
|
function buildDefaultDeps(registry) {
|
|
@@ -5475,13 +5621,13 @@ function writeDiscoveryInsight(db, input) {
|
|
|
5475
5621
|
totalProbes
|
|
5476
5622
|
});
|
|
5477
5623
|
db.transaction((tx) => {
|
|
5478
|
-
tx.update(insights).set({ dismissed: true }).where(
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5624
|
+
tx.update(insights).set({ dismissed: true }).where(and9(
|
|
5625
|
+
eq11(insights.projectId, input.projectId),
|
|
5626
|
+
eq11(insights.type, "discovery.basket-divergence"),
|
|
5627
|
+
eq11(insights.dismissed, false)
|
|
5482
5628
|
)).run();
|
|
5483
5629
|
tx.insert(insights).values({
|
|
5484
|
-
id:
|
|
5630
|
+
id: crypto13.randomUUID(),
|
|
5485
5631
|
projectId: input.projectId,
|
|
5486
5632
|
runId: input.runId,
|
|
5487
5633
|
type: "discovery.basket-divergence",
|
|
@@ -5517,10 +5663,10 @@ function buildDiscoveryInsightTitle(input) {
|
|
|
5517
5663
|
}
|
|
5518
5664
|
|
|
5519
5665
|
// src/execute-site-audit.ts
|
|
5520
|
-
import
|
|
5521
|
-
import { eq as
|
|
5666
|
+
import crypto14 from "crypto";
|
|
5667
|
+
import { eq as eq12 } from "drizzle-orm";
|
|
5522
5668
|
import { runSitemapAudit } from "@ainyc/aeo-audit";
|
|
5523
|
-
var
|
|
5669
|
+
var log13 = createLogger("SiteAudit");
|
|
5524
5670
|
var SITE_AUDIT_DEFAULT_PAGE_LIMIT = 500;
|
|
5525
5671
|
var SITE_AUDIT_MAX_PAGE_LIMIT = 2e3;
|
|
5526
5672
|
function toHomepageUrl(canonicalDomain) {
|
|
@@ -5581,15 +5727,15 @@ function computeFactorAverages(pages) {
|
|
|
5581
5727
|
}
|
|
5582
5728
|
async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
5583
5729
|
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5584
|
-
db.update(runs).set({ status: "running", startedAt }).where(
|
|
5730
|
+
db.update(runs).set({ status: "running", startedAt }).where(eq12(runs.id, runId)).run();
|
|
5585
5731
|
try {
|
|
5586
|
-
const project = db.select().from(projects).where(
|
|
5732
|
+
const project = db.select().from(projects).where(eq12(projects.id, projectId)).get();
|
|
5587
5733
|
if (!project) {
|
|
5588
5734
|
throw new Error(`Project not found: ${projectId}`);
|
|
5589
5735
|
}
|
|
5590
5736
|
const homepageUrl = toHomepageUrl(project.canonicalDomain);
|
|
5591
5737
|
const limit = clampSiteAuditLimit(opts.limit);
|
|
5592
|
-
|
|
5738
|
+
log13.info("start", { runId, projectId, homepageUrl, sitemapUrl: opts.sitemapUrl ?? null, limit });
|
|
5593
5739
|
await assertSiteAuditUrlAllowed(homepageUrl, "canonicalDomain");
|
|
5594
5740
|
if (opts.sitemapUrl) await assertSiteAuditUrlAllowed(opts.sitemapUrl, "sitemapUrl");
|
|
5595
5741
|
const report = await runSitemapAudit(homepageUrl, { sitemapUrl: opts.sitemapUrl, limit });
|
|
@@ -5597,7 +5743,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
5597
5743
|
const pagesErrored = report.pages.filter((page) => page.status === "error").length;
|
|
5598
5744
|
const auditable = report.pagesDiscovered - report.pagesSkipped;
|
|
5599
5745
|
if (auditable > report.pagesAudited) {
|
|
5600
|
-
|
|
5746
|
+
log13.info("truncated", {
|
|
5601
5747
|
runId,
|
|
5602
5748
|
projectId,
|
|
5603
5749
|
auditable,
|
|
@@ -5616,7 +5762,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
5616
5762
|
const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
5617
5763
|
db.transaction((tx) => {
|
|
5618
5764
|
tx.insert(siteAuditSnapshots).values({
|
|
5619
|
-
id:
|
|
5765
|
+
id: crypto14.randomUUID(),
|
|
5620
5766
|
projectId,
|
|
5621
5767
|
runId,
|
|
5622
5768
|
sitemapUrl: report.sitemapUrl,
|
|
@@ -5645,7 +5791,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
5645
5791
|
}).run();
|
|
5646
5792
|
for (const page of report.pages) {
|
|
5647
5793
|
tx.insert(siteAuditPages).values({
|
|
5648
|
-
id:
|
|
5794
|
+
id: crypto14.randomUUID(),
|
|
5649
5795
|
projectId,
|
|
5650
5796
|
runId,
|
|
5651
5797
|
url: page.url,
|
|
@@ -5656,9 +5802,9 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
5656
5802
|
createdAt: finishedAt
|
|
5657
5803
|
}).run();
|
|
5658
5804
|
}
|
|
5659
|
-
tx.update(runs).set({ status, finishedAt }).where(
|
|
5805
|
+
tx.update(runs).set({ status, finishedAt }).where(eq12(runs.id, runId)).run();
|
|
5660
5806
|
});
|
|
5661
|
-
|
|
5807
|
+
log13.info("completed", {
|
|
5662
5808
|
runId,
|
|
5663
5809
|
projectId,
|
|
5664
5810
|
status,
|
|
@@ -5668,14 +5814,14 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
|
|
|
5668
5814
|
});
|
|
5669
5815
|
} catch (err) {
|
|
5670
5816
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
5671
|
-
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
5672
|
-
|
|
5817
|
+
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq12(runs.id, runId)).run();
|
|
5818
|
+
log13.error("failed", { runId, projectId, error: errorMsg });
|
|
5673
5819
|
throw err;
|
|
5674
5820
|
}
|
|
5675
5821
|
}
|
|
5676
5822
|
|
|
5677
5823
|
// src/commands/backfill.ts
|
|
5678
|
-
import { and as
|
|
5824
|
+
import { and as and10, eq as eq13, inArray as inArray4, isNull, sql as sql4 } from "drizzle-orm";
|
|
5679
5825
|
var SNAPSHOT_BATCH_SIZE = 500;
|
|
5680
5826
|
async function backfillAnswerVisibilityCommand(opts) {
|
|
5681
5827
|
const config = loadConfig();
|
|
@@ -5683,7 +5829,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5683
5829
|
migrate(db);
|
|
5684
5830
|
const projectFilter = opts?.project?.trim();
|
|
5685
5831
|
const isDryRun = opts?.dryRun === true;
|
|
5686
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
5832
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq13(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
5687
5833
|
let examined = 0;
|
|
5688
5834
|
let updated = 0;
|
|
5689
5835
|
let wouldUpdate = 0;
|
|
@@ -5691,10 +5837,10 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5691
5837
|
let reparsed = 0;
|
|
5692
5838
|
let providerErrors = 0;
|
|
5693
5839
|
if (scopedProjects.length > 0) {
|
|
5694
|
-
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
5695
|
-
|
|
5840
|
+
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and10(
|
|
5841
|
+
eq13(runs.kind, RunKinds["answer-visibility"]),
|
|
5696
5842
|
inArray4(runs.projectId, scopedProjects.map((project) => project.id))
|
|
5697
|
-
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(
|
|
5843
|
+
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq13(runs.kind, RunKinds["answer-visibility"])).all();
|
|
5698
5844
|
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
5699
5845
|
for (const run of runRows) {
|
|
5700
5846
|
const existing = runIdsByProject.get(run.projectId);
|
|
@@ -5702,7 +5848,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5702
5848
|
else runIdsByProject.set(run.projectId, [run.id]);
|
|
5703
5849
|
}
|
|
5704
5850
|
for (const project of scopedProjects) {
|
|
5705
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
5851
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq13(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
5706
5852
|
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
5707
5853
|
if (runIds.length === 0) continue;
|
|
5708
5854
|
const projectDomains = effectiveDomains({
|
|
@@ -5790,7 +5936,7 @@ async function backfillAnswerVisibilityCommand(opts) {
|
|
|
5790
5936
|
} else {
|
|
5791
5937
|
db.transaction((tx) => {
|
|
5792
5938
|
for (const update of pendingUpdates) {
|
|
5793
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
5939
|
+
tx.update(querySnapshots).set(update.patch).where(eq13(querySnapshots.id, update.id)).run();
|
|
5794
5940
|
}
|
|
5795
5941
|
});
|
|
5796
5942
|
updated += pendingUpdates.length;
|
|
@@ -5839,13 +5985,13 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
5839
5985
|
function backfillNormalizedPaths(db, opts) {
|
|
5840
5986
|
const baseConditions = [];
|
|
5841
5987
|
if (opts?.projectId) {
|
|
5842
|
-
baseConditions.push(
|
|
5988
|
+
baseConditions.push(eq13(gaTrafficSnapshots.projectId, opts.projectId));
|
|
5843
5989
|
}
|
|
5844
5990
|
const rows = db.select({
|
|
5845
5991
|
id: gaTrafficSnapshots.id,
|
|
5846
5992
|
landingPage: gaTrafficSnapshots.landingPage,
|
|
5847
5993
|
landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
|
|
5848
|
-
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ?
|
|
5994
|
+
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and10(...baseConditions) : void 0).all();
|
|
5849
5995
|
let updated = 0;
|
|
5850
5996
|
let unchanged = 0;
|
|
5851
5997
|
if (rows.length > 0) {
|
|
@@ -5860,7 +6006,7 @@ function backfillNormalizedPaths(db, opts) {
|
|
|
5860
6006
|
unchanged++;
|
|
5861
6007
|
continue;
|
|
5862
6008
|
}
|
|
5863
|
-
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(
|
|
6009
|
+
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq13(gaTrafficSnapshots.id, row.id)).run();
|
|
5864
6010
|
updated++;
|
|
5865
6011
|
}
|
|
5866
6012
|
});
|
|
@@ -5874,7 +6020,7 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5874
6020
|
const projectFilter = opts?.project?.trim();
|
|
5875
6021
|
let projectId;
|
|
5876
6022
|
if (projectFilter) {
|
|
5877
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
6023
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq13(projects.name, projectFilter)).get();
|
|
5878
6024
|
if (!project) {
|
|
5879
6025
|
const result2 = {
|
|
5880
6026
|
project: projectFilter,
|
|
@@ -5911,13 +6057,13 @@ async function backfillNormalizedPathsCommand(opts) {
|
|
|
5911
6057
|
function backfillAiReferralPaths(db, opts) {
|
|
5912
6058
|
const baseConditions = [];
|
|
5913
6059
|
if (opts?.projectId) {
|
|
5914
|
-
baseConditions.push(
|
|
6060
|
+
baseConditions.push(eq13(gaAiReferrals.projectId, opts.projectId));
|
|
5915
6061
|
}
|
|
5916
6062
|
const rows = db.select({
|
|
5917
6063
|
id: gaAiReferrals.id,
|
|
5918
6064
|
landingPage: gaAiReferrals.landingPage,
|
|
5919
6065
|
landingPageNormalized: gaAiReferrals.landingPageNormalized
|
|
5920
|
-
}).from(gaAiReferrals).where(baseConditions.length > 0 ?
|
|
6066
|
+
}).from(gaAiReferrals).where(baseConditions.length > 0 ? and10(...baseConditions) : void 0).all();
|
|
5921
6067
|
let updated = 0;
|
|
5922
6068
|
let unchanged = 0;
|
|
5923
6069
|
if (rows.length > 0) {
|
|
@@ -5932,7 +6078,7 @@ function backfillAiReferralPaths(db, opts) {
|
|
|
5932
6078
|
unchanged++;
|
|
5933
6079
|
continue;
|
|
5934
6080
|
}
|
|
5935
|
-
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(
|
|
6081
|
+
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq13(gaAiReferrals.id, row.id)).run();
|
|
5936
6082
|
updated++;
|
|
5937
6083
|
}
|
|
5938
6084
|
});
|
|
@@ -5946,7 +6092,7 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5946
6092
|
const projectFilter = opts?.project?.trim();
|
|
5947
6093
|
let projectId;
|
|
5948
6094
|
if (projectFilter) {
|
|
5949
|
-
const project = db.select({ id: projects.id }).from(projects).where(
|
|
6095
|
+
const project = db.select({ id: projects.id }).from(projects).where(eq13(projects.name, projectFilter)).get();
|
|
5950
6096
|
if (!project) {
|
|
5951
6097
|
const result2 = {
|
|
5952
6098
|
project: projectFilter,
|
|
@@ -5982,10 +6128,10 @@ async function backfillAiReferralPathsCommand(opts) {
|
|
|
5982
6128
|
}
|
|
5983
6129
|
function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
5984
6130
|
const isDryRun = opts?.dryRun === true;
|
|
5985
|
-
const project = db.select().from(projects).where(
|
|
6131
|
+
const project = db.select().from(projects).where(eq13(projects.id, projectId)).get();
|
|
5986
6132
|
if (!project) return { examined: 0, updated: 0, mentioned: 0 };
|
|
5987
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(
|
|
5988
|
-
const runRows = db.select({ id: runs.id }).from(runs).where(
|
|
6133
|
+
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq13(competitors.projectId, projectId)).all().map((row) => row.domain);
|
|
6134
|
+
const runRows = db.select({ id: runs.id }).from(runs).where(and10(eq13(runs.kind, RunKinds["answer-visibility"]), eq13(runs.projectId, projectId))).all();
|
|
5989
6135
|
const runIds = runRows.map((r) => r.id);
|
|
5990
6136
|
let examined = 0;
|
|
5991
6137
|
let updated = 0;
|
|
@@ -6057,7 +6203,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
|
|
|
6057
6203
|
} else {
|
|
6058
6204
|
db.transaction((tx) => {
|
|
6059
6205
|
for (const update of pendingUpdates) {
|
|
6060
|
-
tx.update(querySnapshots).set(update.patch).where(
|
|
6206
|
+
tx.update(querySnapshots).set(update.patch).where(eq13(querySnapshots.id, update.id)).run();
|
|
6061
6207
|
}
|
|
6062
6208
|
});
|
|
6063
6209
|
updated += pendingUpdates.length;
|
|
@@ -6072,7 +6218,7 @@ async function backfillAnswerMentionsCommand(opts) {
|
|
|
6072
6218
|
migrate(db);
|
|
6073
6219
|
const projectFilter = opts?.project?.trim();
|
|
6074
6220
|
const isDryRun = opts?.dryRun === true;
|
|
6075
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
6221
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq13(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
6076
6222
|
let examined = 0;
|
|
6077
6223
|
let updated = 0;
|
|
6078
6224
|
let wouldUpdate = 0;
|
|
@@ -6132,7 +6278,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
6132
6278
|
return result;
|
|
6133
6279
|
}
|
|
6134
6280
|
async function backfillInsightsCommand(project, opts) {
|
|
6135
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
6281
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-2UUJ3YGI.js");
|
|
6136
6282
|
const config = loadConfig();
|
|
6137
6283
|
const db = createClient(config.database);
|
|
6138
6284
|
migrate(db);
|
|
@@ -6291,7 +6437,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
6291
6437
|
const config = loadConfig();
|
|
6292
6438
|
const db = createClient(config.database);
|
|
6293
6439
|
migrate(db);
|
|
6294
|
-
const project = db.select().from(projects).where(
|
|
6440
|
+
const project = db.select().from(projects).where(eq13(projects.name, opts.project)).get();
|
|
6295
6441
|
if (!project) {
|
|
6296
6442
|
throw new Error(`Project "${opts.project}" not found`);
|
|
6297
6443
|
}
|
|
@@ -6302,8 +6448,8 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
6302
6448
|
process.stderr.write(`Recovering orphan snapshot attribution for "${project.name}"${mode}...
|
|
6303
6449
|
`);
|
|
6304
6450
|
}
|
|
6305
|
-
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(
|
|
6306
|
-
|
|
6451
|
+
const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and10(
|
|
6452
|
+
eq13(auditLog.projectId, project.id),
|
|
6307
6453
|
inArray4(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
|
|
6308
6454
|
)).orderBy(auditLog.createdAt).all();
|
|
6309
6455
|
const history = replayQueryAuditLog(events);
|
|
@@ -6311,8 +6457,8 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
6311
6457
|
runId: runs.id,
|
|
6312
6458
|
createdAt: runs.createdAt,
|
|
6313
6459
|
location: runs.location
|
|
6314
|
-
}).from(runs).innerJoin(querySnapshots,
|
|
6315
|
-
|
|
6460
|
+
}).from(runs).innerJoin(querySnapshots, eq13(querySnapshots.runId, runs.id)).where(and10(
|
|
6461
|
+
eq13(runs.projectId, project.id),
|
|
6316
6462
|
isNull(querySnapshots.queryId),
|
|
6317
6463
|
isNull(querySnapshots.queryText)
|
|
6318
6464
|
)).groupBy(runs.id).orderBy(runs.createdAt).all();
|
|
@@ -6334,8 +6480,8 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
6334
6480
|
provider: querySnapshots.provider,
|
|
6335
6481
|
createdAt: querySnapshots.createdAt,
|
|
6336
6482
|
answerText: querySnapshots.answerText
|
|
6337
|
-
}).from(querySnapshots).where(
|
|
6338
|
-
|
|
6483
|
+
}).from(querySnapshots).where(and10(
|
|
6484
|
+
eq13(querySnapshots.runId, run.runId),
|
|
6339
6485
|
isNull(querySnapshots.queryId),
|
|
6340
6486
|
isNull(querySnapshots.queryText)
|
|
6341
6487
|
)).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
|
|
@@ -6401,7 +6547,7 @@ async function backfillSnapshotAttributionCommand(opts) {
|
|
|
6401
6547
|
if (!isDryRun && updates.length > 0) {
|
|
6402
6548
|
db.transaction((tx) => {
|
|
6403
6549
|
for (const u of updates) {
|
|
6404
|
-
tx.update(querySnapshots).set({ queryText: u.queryText }).where(
|
|
6550
|
+
tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq13(querySnapshots.id, u.id)).run();
|
|
6405
6551
|
}
|
|
6406
6552
|
});
|
|
6407
6553
|
}
|
|
@@ -6475,7 +6621,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6475
6621
|
const projectFilter = opts?.project?.trim();
|
|
6476
6622
|
const isDryRun = opts?.dryRun === true;
|
|
6477
6623
|
const isJson = isMachineFormat(opts?.format);
|
|
6478
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(
|
|
6624
|
+
const scopedProjects = projectFilter ? db.select().from(projects).where(eq13(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
6479
6625
|
if (scopedProjects.length === 0) {
|
|
6480
6626
|
if (projectFilter && !isJson) {
|
|
6481
6627
|
process.stderr.write(`No project named "${projectFilter}".
|
|
@@ -6500,8 +6646,8 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6500
6646
|
dryRun: isDryRun,
|
|
6501
6647
|
byBot: {}
|
|
6502
6648
|
};
|
|
6503
|
-
const unknownCountRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(
|
|
6504
|
-
|
|
6649
|
+
const unknownCountRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and10(
|
|
6650
|
+
eq13(rawEventSamples.eventType, "unknown"),
|
|
6505
6651
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
6506
6652
|
)).get();
|
|
6507
6653
|
result.unknownBefore = Number(unknownCountRow?.n ?? 0);
|
|
@@ -6513,8 +6659,8 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6513
6659
|
userAgent: rawEventSamples.userAgent,
|
|
6514
6660
|
pathNormalized: rawEventSamples.pathNormalized,
|
|
6515
6661
|
status: rawEventSamples.status
|
|
6516
|
-
}).from(rawEventSamples).where(
|
|
6517
|
-
|
|
6662
|
+
}).from(rawEventSamples).where(and10(
|
|
6663
|
+
eq13(rawEventSamples.eventType, "unknown"),
|
|
6518
6664
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
6519
6665
|
)).all();
|
|
6520
6666
|
result.examined = unknownSamples.length;
|
|
@@ -6553,7 +6699,7 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6553
6699
|
result.reclassified++;
|
|
6554
6700
|
result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
|
|
6555
6701
|
if (isDryRun) continue;
|
|
6556
|
-
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(
|
|
6702
|
+
db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq13(rawEventSamples.id, snap.id)).run();
|
|
6557
6703
|
const tsHour = new Date(snap.ts);
|
|
6558
6704
|
tsHour.setUTCMinutes(0, 0, 0);
|
|
6559
6705
|
if (userFetch) {
|
|
@@ -6617,8 +6763,8 @@ async function backfillTrafficClassificationCommand(opts) {
|
|
|
6617
6763
|
}
|
|
6618
6764
|
}
|
|
6619
6765
|
if (!isDryRun) {
|
|
6620
|
-
const afterRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(
|
|
6621
|
-
|
|
6766
|
+
const afterRow = db.select({ n: sql4`count(*)` }).from(rawEventSamples).where(and10(
|
|
6767
|
+
eq13(rawEventSamples.eventType, "unknown"),
|
|
6622
6768
|
inArray4(rawEventSamples.projectId, projectIds)
|
|
6623
6769
|
)).get();
|
|
6624
6770
|
result.unknownAfter = Number(afterRow?.n ?? 0);
|
|
@@ -6653,7 +6799,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
6653
6799
|
}
|
|
6654
6800
|
|
|
6655
6801
|
// src/commands/skills.ts
|
|
6656
|
-
import
|
|
6802
|
+
import crypto15 from "crypto";
|
|
6657
6803
|
import fs4 from "fs";
|
|
6658
6804
|
import os4 from "os";
|
|
6659
6805
|
import path5 from "path";
|
|
@@ -6708,7 +6854,7 @@ function walkRelative(dir, prefix = "") {
|
|
|
6708
6854
|
return out.sort();
|
|
6709
6855
|
}
|
|
6710
6856
|
function sha256File(filePath) {
|
|
6711
|
-
return
|
|
6857
|
+
return crypto15.createHash("sha256").update(fs4.readFileSync(filePath)).digest("hex");
|
|
6712
6858
|
}
|
|
6713
6859
|
function readSkillManifest(skillDir) {
|
|
6714
6860
|
try {
|
|
@@ -7031,10 +7177,10 @@ var ProviderRegistry = class {
|
|
|
7031
7177
|
};
|
|
7032
7178
|
|
|
7033
7179
|
// src/scheduler.ts
|
|
7034
|
-
import
|
|
7180
|
+
import crypto16 from "crypto";
|
|
7035
7181
|
import cron from "node-cron";
|
|
7036
|
-
import { and as
|
|
7037
|
-
var
|
|
7182
|
+
import { and as and11, eq as eq14, inArray as inArray5 } from "drizzle-orm";
|
|
7183
|
+
var log14 = createLogger("Scheduler");
|
|
7038
7184
|
function taskKey(projectId, kind) {
|
|
7039
7185
|
return `${projectId}::${kind}`;
|
|
7040
7186
|
}
|
|
@@ -7048,16 +7194,16 @@ var Scheduler = class {
|
|
|
7048
7194
|
}
|
|
7049
7195
|
/** Load all enabled schedules from DB and register cron jobs. */
|
|
7050
7196
|
start() {
|
|
7051
|
-
const allSchedules = this.db.select().from(schedules).where(
|
|
7197
|
+
const allSchedules = this.db.select().from(schedules).where(eq14(schedules.enabled, true)).all();
|
|
7052
7198
|
for (const schedule of allSchedules) {
|
|
7053
7199
|
const missedRunAt = schedule.nextRunAt;
|
|
7054
7200
|
this.registerCronTask(schedule);
|
|
7055
7201
|
if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
7056
|
-
|
|
7202
|
+
log14.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
|
|
7057
7203
|
this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
|
|
7058
7204
|
}
|
|
7059
7205
|
}
|
|
7060
|
-
|
|
7206
|
+
log14.info("started", { scheduleCount: allSchedules.length });
|
|
7061
7207
|
}
|
|
7062
7208
|
/** Stop all cron tasks for graceful shutdown. */
|
|
7063
7209
|
stop() {
|
|
@@ -7078,7 +7224,7 @@ var Scheduler = class {
|
|
|
7078
7224
|
this.stopTask(key, existing, "Stopped");
|
|
7079
7225
|
this.tasks.delete(key);
|
|
7080
7226
|
}
|
|
7081
|
-
const schedule = this.db.select().from(schedules).where(
|
|
7227
|
+
const schedule = this.db.select().from(schedules).where(and11(eq14(schedules.projectId, projectId), eq14(schedules.kind, kind))).get();
|
|
7082
7228
|
if (schedule && schedule.enabled) {
|
|
7083
7229
|
this.registerCronTask(schedule);
|
|
7084
7230
|
}
|
|
@@ -7101,13 +7247,13 @@ var Scheduler = class {
|
|
|
7101
7247
|
stopTask(key, task, verb) {
|
|
7102
7248
|
void task.stop();
|
|
7103
7249
|
void task.destroy();
|
|
7104
|
-
|
|
7250
|
+
log14.info(`task.${verb.toLowerCase()}`, { key });
|
|
7105
7251
|
}
|
|
7106
7252
|
registerCronTask(schedule) {
|
|
7107
7253
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
7108
7254
|
const kind = schedule.kind;
|
|
7109
7255
|
if (!cron.validate(cronExpr)) {
|
|
7110
|
-
|
|
7256
|
+
log14.error("cron.invalid", { projectId, kind, cronExpr });
|
|
7111
7257
|
return;
|
|
7112
7258
|
}
|
|
7113
7259
|
const task = cron.schedule(cronExpr, () => {
|
|
@@ -7119,51 +7265,51 @@ var Scheduler = class {
|
|
|
7119
7265
|
this.db.update(schedules).set({
|
|
7120
7266
|
nextRunAt: nextRunFromCron(cronExpr, timezone),
|
|
7121
7267
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7122
|
-
}).where(
|
|
7268
|
+
}).where(eq14(schedules.id, scheduleId)).run();
|
|
7123
7269
|
const label = schedule.preset ?? cronExpr;
|
|
7124
|
-
|
|
7270
|
+
log14.info("cron.registered", { projectId, kind, schedule: label, timezone });
|
|
7125
7271
|
}
|
|
7126
7272
|
triggerRun(scheduleId, projectId, kind) {
|
|
7127
7273
|
try {
|
|
7128
7274
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
7129
|
-
const currentSchedule = this.db.select().from(schedules).where(
|
|
7275
|
+
const currentSchedule = this.db.select().from(schedules).where(eq14(schedules.id, scheduleId)).get();
|
|
7130
7276
|
if (!currentSchedule || !currentSchedule.enabled) {
|
|
7131
|
-
|
|
7277
|
+
log14.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
|
|
7132
7278
|
this.remove(projectId, kind);
|
|
7133
7279
|
return;
|
|
7134
7280
|
}
|
|
7135
7281
|
const nextRunAt = nextRunFromCron(currentSchedule.cronExpr, currentSchedule.timezone);
|
|
7136
|
-
const project = this.db.select().from(projects).where(
|
|
7282
|
+
const project = this.db.select().from(projects).where(eq14(projects.id, projectId)).get();
|
|
7137
7283
|
if (!project) {
|
|
7138
|
-
|
|
7284
|
+
log14.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
|
|
7139
7285
|
this.remove(projectId, kind);
|
|
7140
7286
|
return;
|
|
7141
7287
|
}
|
|
7142
7288
|
if (kind === SchedulableRunKinds["traffic-sync"]) {
|
|
7143
7289
|
const sourceId = currentSchedule.sourceId;
|
|
7144
7290
|
if (!sourceId) {
|
|
7145
|
-
|
|
7291
|
+
log14.warn("traffic-sync.missing-source", { scheduleId, projectId });
|
|
7146
7292
|
return;
|
|
7147
7293
|
}
|
|
7148
7294
|
if (!this.callbacks.onTrafficSyncRequested) {
|
|
7149
|
-
|
|
7295
|
+
log14.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
|
|
7150
7296
|
return;
|
|
7151
7297
|
}
|
|
7152
7298
|
this.db.update(schedules).set({
|
|
7153
7299
|
lastRunAt: now,
|
|
7154
7300
|
nextRunAt,
|
|
7155
7301
|
updatedAt: now
|
|
7156
|
-
}).where(
|
|
7157
|
-
|
|
7302
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7303
|
+
log14.info("traffic-sync.triggered", { projectName: project.name, sourceId });
|
|
7158
7304
|
this.callbacks.onTrafficSyncRequested(project.name, sourceId);
|
|
7159
7305
|
return;
|
|
7160
7306
|
}
|
|
7161
7307
|
if (kind === SchedulableRunKinds["gbp-sync"]) {
|
|
7162
7308
|
if (!this.callbacks.onGbpSyncRequested) {
|
|
7163
|
-
|
|
7309
|
+
log14.warn("gbp-sync.no-callback", { scheduleId, projectId, msg: "host did not register onGbpSyncRequested" });
|
|
7164
7310
|
return;
|
|
7165
7311
|
}
|
|
7166
|
-
const runId2 =
|
|
7312
|
+
const runId2 = crypto16.randomUUID();
|
|
7167
7313
|
this.db.insert(runs).values({
|
|
7168
7314
|
id: runId2,
|
|
7169
7315
|
projectId,
|
|
@@ -7176,27 +7322,27 @@ var Scheduler = class {
|
|
|
7176
7322
|
lastRunAt: now,
|
|
7177
7323
|
nextRunAt,
|
|
7178
7324
|
updatedAt: now
|
|
7179
|
-
}).where(
|
|
7180
|
-
|
|
7325
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7326
|
+
log14.info("gbp-sync.triggered", { runId: runId2, projectName: project.name });
|
|
7181
7327
|
this.callbacks.onGbpSyncRequested(runId2, projectId);
|
|
7182
7328
|
return;
|
|
7183
7329
|
}
|
|
7184
7330
|
if (kind === SchedulableRunKinds["ads-sync"]) {
|
|
7185
7331
|
if (!this.callbacks.onAdsSyncRequested) {
|
|
7186
|
-
|
|
7332
|
+
log14.warn("ads-sync.no-callback", { scheduleId, projectId, msg: "host did not register onAdsSyncRequested" });
|
|
7187
7333
|
return;
|
|
7188
7334
|
}
|
|
7189
|
-
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(
|
|
7190
|
-
|
|
7191
|
-
|
|
7335
|
+
const activeAdsRun = this.db.select({ id: runs.id }).from(runs).where(and11(
|
|
7336
|
+
eq14(runs.projectId, projectId),
|
|
7337
|
+
eq14(runs.kind, RunKinds["ads-sync"]),
|
|
7192
7338
|
inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
7193
7339
|
)).get();
|
|
7194
7340
|
if (activeAdsRun) {
|
|
7195
|
-
|
|
7196
|
-
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(
|
|
7341
|
+
log14.info("ads-sync.skipped-active", { projectName: project.name, activeRunId: activeAdsRun.id });
|
|
7342
|
+
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7197
7343
|
return;
|
|
7198
7344
|
}
|
|
7199
|
-
const runId2 =
|
|
7345
|
+
const runId2 = crypto16.randomUUID();
|
|
7200
7346
|
this.db.insert(runs).values({
|
|
7201
7347
|
id: runId2,
|
|
7202
7348
|
projectId,
|
|
@@ -7209,55 +7355,55 @@ var Scheduler = class {
|
|
|
7209
7355
|
lastRunAt: now,
|
|
7210
7356
|
nextRunAt,
|
|
7211
7357
|
updatedAt: now
|
|
7212
|
-
}).where(
|
|
7213
|
-
|
|
7358
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7359
|
+
log14.info("ads-sync.triggered", { runId: runId2, projectName: project.name });
|
|
7214
7360
|
this.callbacks.onAdsSyncRequested(runId2, projectId);
|
|
7215
7361
|
return;
|
|
7216
7362
|
}
|
|
7217
7363
|
if (kind === SchedulableRunKinds["data-refresh"]) {
|
|
7218
7364
|
if (!this.callbacks.onDataRefreshRequested) {
|
|
7219
|
-
|
|
7365
|
+
log14.warn("data-refresh.no-callback", { scheduleId, projectId, msg: "host did not register onDataRefreshRequested" });
|
|
7220
7366
|
return;
|
|
7221
7367
|
}
|
|
7222
7368
|
this.db.update(schedules).set({
|
|
7223
7369
|
lastRunAt: now,
|
|
7224
7370
|
nextRunAt,
|
|
7225
7371
|
updatedAt: now
|
|
7226
|
-
}).where(
|
|
7227
|
-
|
|
7372
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7373
|
+
log14.info("data-refresh.triggered", { projectName: project.name });
|
|
7228
7374
|
this.callbacks.onDataRefreshRequested(project.name);
|
|
7229
7375
|
return;
|
|
7230
7376
|
}
|
|
7231
7377
|
if (kind === SchedulableRunKinds["backlinks-sync"]) {
|
|
7232
7378
|
if (!this.callbacks.onBacklinksSyncRequested) {
|
|
7233
|
-
|
|
7379
|
+
log14.warn("backlinks-sync.no-callback", { scheduleId, projectId, msg: "host did not register onBacklinksSyncRequested" });
|
|
7234
7380
|
return;
|
|
7235
7381
|
}
|
|
7236
7382
|
this.db.update(schedules).set({
|
|
7237
7383
|
lastRunAt: now,
|
|
7238
7384
|
nextRunAt,
|
|
7239
7385
|
updatedAt: now
|
|
7240
|
-
}).where(
|
|
7241
|
-
|
|
7386
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7387
|
+
log14.info("backlinks-sync.triggered", { projectName: project.name });
|
|
7242
7388
|
this.callbacks.onBacklinksSyncRequested(project.name);
|
|
7243
7389
|
return;
|
|
7244
7390
|
}
|
|
7245
7391
|
if (kind === SchedulableRunKinds["site-audit"]) {
|
|
7246
7392
|
if (!this.callbacks.onSiteAuditRequested) {
|
|
7247
|
-
|
|
7393
|
+
log14.warn("site-audit.no-callback", { scheduleId, projectId, msg: "host did not register onSiteAuditRequested" });
|
|
7248
7394
|
return;
|
|
7249
7395
|
}
|
|
7250
|
-
const active = this.db.select({ id: runs.id }).from(runs).where(
|
|
7251
|
-
|
|
7252
|
-
|
|
7396
|
+
const active = this.db.select({ id: runs.id }).from(runs).where(and11(
|
|
7397
|
+
eq14(runs.projectId, projectId),
|
|
7398
|
+
eq14(runs.kind, RunKinds["site-audit"]),
|
|
7253
7399
|
inArray5(runs.status, [RunStatuses.queued, RunStatuses.running])
|
|
7254
7400
|
)).get();
|
|
7255
7401
|
if (active) {
|
|
7256
|
-
|
|
7257
|
-
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(
|
|
7402
|
+
log14.info("site-audit.skipped-active", { projectName: project.name, activeRunId: active.id });
|
|
7403
|
+
this.db.update(schedules).set({ nextRunAt, updatedAt: now }).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7258
7404
|
return;
|
|
7259
7405
|
}
|
|
7260
|
-
const runId2 =
|
|
7406
|
+
const runId2 = crypto16.randomUUID();
|
|
7261
7407
|
this.db.insert(runs).values({
|
|
7262
7408
|
id: runId2,
|
|
7263
7409
|
projectId,
|
|
@@ -7270,8 +7416,8 @@ var Scheduler = class {
|
|
|
7270
7416
|
lastRunAt: now,
|
|
7271
7417
|
nextRunAt,
|
|
7272
7418
|
updatedAt: now
|
|
7273
|
-
}).where(
|
|
7274
|
-
|
|
7419
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7420
|
+
log14.info("site-audit.triggered", { runId: runId2, projectName: project.name });
|
|
7275
7421
|
this.callbacks.onSiteAuditRequested(runId2, projectId);
|
|
7276
7422
|
return;
|
|
7277
7423
|
}
|
|
@@ -7280,7 +7426,7 @@ var Scheduler = class {
|
|
|
7280
7426
|
if (project.defaultLocation) {
|
|
7281
7427
|
const loc = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
7282
7428
|
if (!loc) {
|
|
7283
|
-
|
|
7429
|
+
log14.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
|
|
7284
7430
|
return;
|
|
7285
7431
|
}
|
|
7286
7432
|
resolvedLocation = loc;
|
|
@@ -7294,11 +7440,11 @@ var Scheduler = class {
|
|
|
7294
7440
|
location: locationLabel
|
|
7295
7441
|
});
|
|
7296
7442
|
if (queueResult.conflict) {
|
|
7297
|
-
|
|
7443
|
+
log14.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
7298
7444
|
this.db.update(schedules).set({
|
|
7299
7445
|
nextRunAt,
|
|
7300
7446
|
updatedAt: now
|
|
7301
|
-
}).where(
|
|
7447
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7302
7448
|
return;
|
|
7303
7449
|
}
|
|
7304
7450
|
const runId = queueResult.runId;
|
|
@@ -7306,19 +7452,19 @@ var Scheduler = class {
|
|
|
7306
7452
|
lastRunAt: now,
|
|
7307
7453
|
nextRunAt,
|
|
7308
7454
|
updatedAt: now
|
|
7309
|
-
}).where(
|
|
7455
|
+
}).where(eq14(schedules.id, currentSchedule.id)).run();
|
|
7310
7456
|
const scheduleProviders = currentSchedule.providers;
|
|
7311
7457
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
7312
|
-
|
|
7458
|
+
log14.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
7313
7459
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
7314
7460
|
} catch (err) {
|
|
7315
|
-
|
|
7461
|
+
log14.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
|
|
7316
7462
|
}
|
|
7317
7463
|
}
|
|
7318
7464
|
};
|
|
7319
7465
|
|
|
7320
7466
|
// src/data-refresh.ts
|
|
7321
|
-
var
|
|
7467
|
+
var log15 = createLogger("DataRefresh");
|
|
7322
7468
|
async function refreshAllIntegrations(client, projectName) {
|
|
7323
7469
|
const integrations = [
|
|
7324
7470
|
{ name: "gsc", run: () => client.gscSync(projectName, {}) },
|
|
@@ -7331,19 +7477,19 @@ async function refreshAllIntegrations(client, projectName) {
|
|
|
7331
7477
|
results.forEach((result, idx) => {
|
|
7332
7478
|
const integration = integrations[idx].name;
|
|
7333
7479
|
if (result.status === "fulfilled") {
|
|
7334
|
-
|
|
7480
|
+
log15.info("integration.refreshed", { projectName, integration });
|
|
7335
7481
|
} else {
|
|
7336
7482
|
const reason = result.reason;
|
|
7337
7483
|
const message = reason instanceof Error ? reason.message : String(reason);
|
|
7338
|
-
|
|
7484
|
+
log15.warn("integration.refresh-failed", { projectName, integration, error: message });
|
|
7339
7485
|
}
|
|
7340
7486
|
});
|
|
7341
7487
|
}
|
|
7342
7488
|
|
|
7343
7489
|
// src/notifier.ts
|
|
7344
|
-
import { eq as
|
|
7345
|
-
import
|
|
7346
|
-
var
|
|
7490
|
+
import { eq as eq15, desc as desc5, and as and12, inArray as inArray6, or } from "drizzle-orm";
|
|
7491
|
+
import crypto17 from "crypto";
|
|
7492
|
+
var log16 = createLogger("Notifier");
|
|
7347
7493
|
var Notifier = class {
|
|
7348
7494
|
db;
|
|
7349
7495
|
serverUrl;
|
|
@@ -7353,26 +7499,26 @@ var Notifier = class {
|
|
|
7353
7499
|
}
|
|
7354
7500
|
/** Called after a run completes (success, partial, or failed). */
|
|
7355
7501
|
async onRunCompleted(runId, projectId) {
|
|
7356
|
-
|
|
7357
|
-
const notifs = this.db.select().from(notifications).where(
|
|
7502
|
+
log16.info("run.completed", { runId, projectId });
|
|
7503
|
+
const notifs = this.db.select().from(notifications).where(eq15(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
7358
7504
|
if (notifs.length === 0) {
|
|
7359
|
-
|
|
7505
|
+
log16.info("notifications.none-enabled", { projectId });
|
|
7360
7506
|
return;
|
|
7361
7507
|
}
|
|
7362
|
-
|
|
7363
|
-
const run = this.db.select().from(runs).where(
|
|
7508
|
+
log16.info("notifications.found", { projectId, count: notifs.length });
|
|
7509
|
+
const run = this.db.select().from(runs).where(eq15(runs.id, runId)).get();
|
|
7364
7510
|
if (!run) {
|
|
7365
|
-
|
|
7511
|
+
log16.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
7366
7512
|
return;
|
|
7367
7513
|
}
|
|
7368
|
-
const project = this.db.select().from(projects).where(
|
|
7514
|
+
const project = this.db.select().from(projects).where(eq15(projects.id, projectId)).get();
|
|
7369
7515
|
if (!project) {
|
|
7370
|
-
|
|
7516
|
+
log16.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
7371
7517
|
return;
|
|
7372
7518
|
}
|
|
7373
7519
|
const transitions = this.computeTransitions(runId, projectId);
|
|
7374
7520
|
const events = [];
|
|
7375
|
-
|
|
7521
|
+
log16.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
7376
7522
|
if (run.status === "completed" || run.status === "partial") {
|
|
7377
7523
|
events.push("run.completed");
|
|
7378
7524
|
}
|
|
@@ -7388,7 +7534,7 @@ var Notifier = class {
|
|
|
7388
7534
|
if (!config.url) continue;
|
|
7389
7535
|
const subscribedEvents = config.events;
|
|
7390
7536
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
7391
|
-
|
|
7537
|
+
log16.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
7392
7538
|
if (matchingEvents.length === 0) continue;
|
|
7393
7539
|
for (const event of matchingEvents) {
|
|
7394
7540
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -7412,11 +7558,11 @@ var Notifier = class {
|
|
|
7412
7558
|
if (criticalInsights.length > 0) insightEvents.push("insight.critical");
|
|
7413
7559
|
if (highInsights.length > 0) insightEvents.push("insight.high");
|
|
7414
7560
|
if (insightEvents.length === 0) return;
|
|
7415
|
-
const notifs = this.db.select().from(notifications).where(
|
|
7561
|
+
const notifs = this.db.select().from(notifications).where(eq15(notifications.projectId, projectId)).all().filter((n) => n.enabled);
|
|
7416
7562
|
if (notifs.length === 0) return;
|
|
7417
|
-
const run = this.db.select().from(runs).where(
|
|
7563
|
+
const run = this.db.select().from(runs).where(eq15(runs.id, runId)).get();
|
|
7418
7564
|
if (!run) return;
|
|
7419
|
-
const project = this.db.select().from(projects).where(
|
|
7565
|
+
const project = this.db.select().from(projects).where(eq15(projects.id, projectId)).get();
|
|
7420
7566
|
if (!project) return;
|
|
7421
7567
|
for (const notif of notifs) {
|
|
7422
7568
|
const config = notif.config;
|
|
@@ -7446,12 +7592,12 @@ var Notifier = class {
|
|
|
7446
7592
|
}
|
|
7447
7593
|
}
|
|
7448
7594
|
computeTransitions(runId, projectId) {
|
|
7449
|
-
const thisRun = this.db.select().from(runs).where(
|
|
7595
|
+
const thisRun = this.db.select().from(runs).where(eq15(runs.id, runId)).get();
|
|
7450
7596
|
if (!thisRun) return [];
|
|
7451
|
-
const groupSiblings = this.db.select().from(runs).where(
|
|
7452
|
-
|
|
7453
|
-
|
|
7454
|
-
|
|
7597
|
+
const groupSiblings = this.db.select().from(runs).where(and12(
|
|
7598
|
+
eq15(runs.projectId, projectId),
|
|
7599
|
+
eq15(runs.kind, thisRun.kind),
|
|
7600
|
+
eq15(runs.createdAt, thisRun.createdAt)
|
|
7455
7601
|
)).all();
|
|
7456
7602
|
const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
|
|
7457
7603
|
if (stillPending) return [];
|
|
@@ -7467,17 +7613,17 @@ var Notifier = class {
|
|
|
7467
7613
|
return candidate.id > best.id ? candidate : best;
|
|
7468
7614
|
});
|
|
7469
7615
|
if (winner.id !== runId) return [];
|
|
7470
|
-
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(
|
|
7616
|
+
const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq15(projects.id, projectId)).get();
|
|
7471
7617
|
const locationCount = Math.max(
|
|
7472
7618
|
1,
|
|
7473
7619
|
(projectLocations?.locations ?? []).length
|
|
7474
7620
|
);
|
|
7475
7621
|
const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
|
|
7476
7622
|
const recentRuns = this.db.select().from(runs).where(
|
|
7477
|
-
|
|
7478
|
-
|
|
7479
|
-
|
|
7480
|
-
or(
|
|
7623
|
+
and12(
|
|
7624
|
+
eq15(runs.projectId, projectId),
|
|
7625
|
+
eq15(runs.kind, thisRun.kind),
|
|
7626
|
+
or(eq15(runs.status, "completed"), eq15(runs.status, "partial"))
|
|
7481
7627
|
)
|
|
7482
7628
|
).orderBy(desc5(runs.createdAt), desc5(runs.id)).limit(RECENT_FETCH_LIMIT).all();
|
|
7483
7629
|
const groups = groupRunsByCreatedAt(recentRuns);
|
|
@@ -7494,7 +7640,7 @@ var Notifier = class {
|
|
|
7494
7640
|
provider: querySnapshots.provider,
|
|
7495
7641
|
location: querySnapshots.location,
|
|
7496
7642
|
citationState: querySnapshots.citationState
|
|
7497
|
-
}).from(querySnapshots).leftJoin(queries,
|
|
7643
|
+
}).from(querySnapshots).leftJoin(queries, eq15(querySnapshots.queryId, queries.id)).where(inArray6(querySnapshots.runId, currentRunIds)).all();
|
|
7498
7644
|
const previousSnapshots = this.db.select({
|
|
7499
7645
|
queryId: querySnapshots.queryId,
|
|
7500
7646
|
provider: querySnapshots.provider,
|
|
@@ -7527,23 +7673,23 @@ var Notifier = class {
|
|
|
7527
7673
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
7528
7674
|
const targetCheck = await resolveWebhookTarget(url);
|
|
7529
7675
|
if (!targetCheck.ok) {
|
|
7530
|
-
|
|
7676
|
+
log16.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
7531
7677
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
7532
7678
|
return;
|
|
7533
7679
|
}
|
|
7534
|
-
|
|
7680
|
+
log16.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
7535
7681
|
const maxRetries = 3;
|
|
7536
7682
|
const delays = [1e3, 4e3, 16e3];
|
|
7537
7683
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
7538
7684
|
try {
|
|
7539
7685
|
const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
|
|
7540
7686
|
if (response.status >= 200 && response.status < 300) {
|
|
7541
|
-
|
|
7687
|
+
log16.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
7542
7688
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
7543
7689
|
return;
|
|
7544
7690
|
}
|
|
7545
7691
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
7546
|
-
|
|
7692
|
+
log16.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
7547
7693
|
if (attempt === maxRetries - 1) {
|
|
7548
7694
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
7549
7695
|
}
|
|
@@ -7551,7 +7697,7 @@ var Notifier = class {
|
|
|
7551
7697
|
const errorDetail = err instanceof Error ? err.message : String(err);
|
|
7552
7698
|
if (attempt === maxRetries - 1) {
|
|
7553
7699
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
7554
|
-
|
|
7700
|
+
log16.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
7555
7701
|
}
|
|
7556
7702
|
}
|
|
7557
7703
|
if (attempt < maxRetries - 1) {
|
|
@@ -7561,7 +7707,7 @@ var Notifier = class {
|
|
|
7561
7707
|
}
|
|
7562
7708
|
logDelivery(projectId, notificationId, event, status, error) {
|
|
7563
7709
|
this.db.insert(auditLog).values({
|
|
7564
|
-
id:
|
|
7710
|
+
id: crypto17.randomUUID(),
|
|
7565
7711
|
projectId,
|
|
7566
7712
|
actor: "scheduler",
|
|
7567
7713
|
action: `notification.${status}`,
|
|
@@ -7574,8 +7720,8 @@ var Notifier = class {
|
|
|
7574
7720
|
};
|
|
7575
7721
|
|
|
7576
7722
|
// src/run-coordinator.ts
|
|
7577
|
-
import { eq as
|
|
7578
|
-
var
|
|
7723
|
+
import { eq as eq16 } from "drizzle-orm";
|
|
7724
|
+
var log17 = createLogger("RunCoordinator");
|
|
7579
7725
|
var RunCoordinator = class {
|
|
7580
7726
|
constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
7581
7727
|
this.db = db;
|
|
@@ -7590,10 +7736,10 @@ var RunCoordinator = class {
|
|
|
7590
7736
|
onInsightsGenerated;
|
|
7591
7737
|
onAeroEvent;
|
|
7592
7738
|
async onRunCompleted(runId, projectId) {
|
|
7593
|
-
const runRow = this.db.select().from(runs).where(
|
|
7739
|
+
const runRow = this.db.select().from(runs).where(eq16(runs.id, runId)).get();
|
|
7594
7740
|
const kind = runRow?.kind ?? RunKinds["answer-visibility"];
|
|
7595
7741
|
if (runRow?.trigger === RunTriggers.probe) {
|
|
7596
|
-
|
|
7742
|
+
log17.info("probe.skip-side-effects", { runId, projectId, kind });
|
|
7597
7743
|
return;
|
|
7598
7744
|
}
|
|
7599
7745
|
let insightCount = 0;
|
|
@@ -7610,12 +7756,12 @@ var RunCoordinator = class {
|
|
|
7610
7756
|
try {
|
|
7611
7757
|
await this.onInsightsGenerated(runId, projectId, result);
|
|
7612
7758
|
} catch (err) {
|
|
7613
|
-
|
|
7759
|
+
log17.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7614
7760
|
}
|
|
7615
7761
|
}
|
|
7616
7762
|
}
|
|
7617
7763
|
} catch (err) {
|
|
7618
|
-
|
|
7764
|
+
log17.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7619
7765
|
}
|
|
7620
7766
|
} else if (kind === RunKinds["gbp-sync"]) {
|
|
7621
7767
|
try {
|
|
@@ -7628,17 +7774,17 @@ var RunCoordinator = class {
|
|
|
7628
7774
|
try {
|
|
7629
7775
|
await this.onInsightsGenerated(runId, projectId, analysisResultFromInsights(gbpInsights));
|
|
7630
7776
|
} catch (err) {
|
|
7631
|
-
|
|
7777
|
+
log17.error("gbp-insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7632
7778
|
}
|
|
7633
7779
|
}
|
|
7634
7780
|
} catch (err) {
|
|
7635
|
-
|
|
7781
|
+
log17.error("gbp-intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7636
7782
|
}
|
|
7637
7783
|
}
|
|
7638
7784
|
try {
|
|
7639
7785
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
7640
7786
|
} catch (err) {
|
|
7641
|
-
|
|
7787
|
+
log17.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7642
7788
|
}
|
|
7643
7789
|
if (this.onAeroEvent) {
|
|
7644
7790
|
try {
|
|
@@ -7651,7 +7797,7 @@ var RunCoordinator = class {
|
|
|
7651
7797
|
};
|
|
7652
7798
|
await this.onAeroEvent(ctx);
|
|
7653
7799
|
} catch (err) {
|
|
7654
|
-
|
|
7800
|
+
log17.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
7655
7801
|
}
|
|
7656
7802
|
}
|
|
7657
7803
|
}
|
|
@@ -7666,7 +7812,7 @@ var RunCoordinator = class {
|
|
|
7666
7812
|
* so the Aero queue is never starved of a follow-up.
|
|
7667
7813
|
*/
|
|
7668
7814
|
buildDiscoveryAeroContext(runId, projectId, status, error) {
|
|
7669
|
-
const session = this.db.select().from(discoverySessions).where(
|
|
7815
|
+
const session = this.db.select().from(discoverySessions).where(eq16(discoverySessions.runId, runId)).get();
|
|
7670
7816
|
const competitorMap = session ? session.competitorMap : [];
|
|
7671
7817
|
return {
|
|
7672
7818
|
kind: RunKinds["aeo-discover-probe"],
|
|
@@ -7706,8 +7852,8 @@ function analysisResultFromInsights(insights2) {
|
|
|
7706
7852
|
}
|
|
7707
7853
|
|
|
7708
7854
|
// src/agent/session-registry.ts
|
|
7709
|
-
import
|
|
7710
|
-
import { eq as
|
|
7855
|
+
import crypto19 from "crypto";
|
|
7856
|
+
import { eq as eq18 } from "drizzle-orm";
|
|
7711
7857
|
|
|
7712
7858
|
// src/agent/session.ts
|
|
7713
7859
|
import fs7 from "fs";
|
|
@@ -8095,8 +8241,8 @@ function resolveSessionProviderAndModel(config, opts) {
|
|
|
8095
8241
|
}
|
|
8096
8242
|
|
|
8097
8243
|
// src/agent/memory-store.ts
|
|
8098
|
-
import
|
|
8099
|
-
import { and as
|
|
8244
|
+
import crypto18 from "crypto";
|
|
8245
|
+
import { and as and13, desc as desc6, eq as eq17, like, sql as sql5 } from "drizzle-orm";
|
|
8100
8246
|
var COMPACTION_KEY_PREFIX = "compaction:";
|
|
8101
8247
|
var COMPACTION_NOTES_PER_SESSION = 3;
|
|
8102
8248
|
function rowToDto(row) {
|
|
@@ -8110,7 +8256,7 @@ function rowToDto(row) {
|
|
|
8110
8256
|
};
|
|
8111
8257
|
}
|
|
8112
8258
|
function listMemoryEntries(db, projectId, opts = {}) {
|
|
8113
|
-
const query = db.select().from(agentMemory).where(
|
|
8259
|
+
const query = db.select().from(agentMemory).where(eq17(agentMemory.projectId, projectId)).orderBy(desc6(agentMemory.updatedAt));
|
|
8114
8260
|
const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
|
|
8115
8261
|
return rows.map(rowToDto);
|
|
8116
8262
|
}
|
|
@@ -8124,7 +8270,7 @@ function upsertMemoryEntry(db, args) {
|
|
|
8124
8270
|
throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
|
|
8125
8271
|
}
|
|
8126
8272
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8127
|
-
const id =
|
|
8273
|
+
const id = crypto18.randomUUID();
|
|
8128
8274
|
db.insert(agentMemory).values({
|
|
8129
8275
|
id,
|
|
8130
8276
|
projectId: args.projectId,
|
|
@@ -8141,12 +8287,12 @@ function upsertMemoryEntry(db, args) {
|
|
|
8141
8287
|
updatedAt: now
|
|
8142
8288
|
}
|
|
8143
8289
|
}).run();
|
|
8144
|
-
const row = db.select().from(agentMemory).where(
|
|
8290
|
+
const row = db.select().from(agentMemory).where(and13(eq17(agentMemory.projectId, args.projectId), eq17(agentMemory.key, args.key))).get();
|
|
8145
8291
|
if (!row) throw new Error("memory upsert produced no row");
|
|
8146
8292
|
return rowToDto(row);
|
|
8147
8293
|
}
|
|
8148
8294
|
function deleteMemoryEntry(db, projectId, key) {
|
|
8149
|
-
const result = db.delete(agentMemory).where(
|
|
8295
|
+
const result = db.delete(agentMemory).where(and13(eq17(agentMemory.projectId, projectId), eq17(agentMemory.key, key))).run();
|
|
8150
8296
|
const changes = result.changes ?? 0;
|
|
8151
8297
|
return changes > 0;
|
|
8152
8298
|
}
|
|
@@ -8161,7 +8307,7 @@ function writeCompactionNote(db, args) {
|
|
|
8161
8307
|
}
|
|
8162
8308
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8163
8309
|
const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
|
|
8164
|
-
const id =
|
|
8310
|
+
const id = crypto18.randomUUID();
|
|
8165
8311
|
let inserted;
|
|
8166
8312
|
db.transaction((tx) => {
|
|
8167
8313
|
tx.insert(agentMemory).values({
|
|
@@ -8175,8 +8321,8 @@ function writeCompactionNote(db, args) {
|
|
|
8175
8321
|
}).run();
|
|
8176
8322
|
const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
|
|
8177
8323
|
const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
|
|
8178
|
-
|
|
8179
|
-
|
|
8324
|
+
and13(
|
|
8325
|
+
eq17(agentMemory.projectId, args.projectId),
|
|
8180
8326
|
like(agentMemory.key, `${sessionPrefix}%`)
|
|
8181
8327
|
)
|
|
8182
8328
|
).orderBy(desc6(agentMemory.updatedAt)).all();
|
|
@@ -8184,7 +8330,7 @@ function writeCompactionNote(db, args) {
|
|
|
8184
8330
|
if (stale.length > 0) {
|
|
8185
8331
|
tx.delete(agentMemory).where(sql5`${agentMemory.id} IN (${sql5.join(stale.map((s) => sql5`${s}`), sql5`, `)})`).run();
|
|
8186
8332
|
}
|
|
8187
|
-
const row = tx.select().from(agentMemory).where(
|
|
8333
|
+
const row = tx.select().from(agentMemory).where(and13(eq17(agentMemory.projectId, args.projectId), eq17(agentMemory.key, key))).get();
|
|
8188
8334
|
if (row) inserted = rowToDto(row);
|
|
8189
8335
|
});
|
|
8190
8336
|
if (!inserted) throw new Error("compaction note write produced no row");
|
|
@@ -8317,7 +8463,7 @@ async function compactMessages(args) {
|
|
|
8317
8463
|
}
|
|
8318
8464
|
|
|
8319
8465
|
// src/agent/session-registry.ts
|
|
8320
|
-
var
|
|
8466
|
+
var log18 = createLogger("SessionRegistry");
|
|
8321
8467
|
var MAX_HYDRATE_NOTES = 20;
|
|
8322
8468
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
8323
8469
|
function escapeMemoryFragment(value) {
|
|
@@ -8366,7 +8512,7 @@ var SessionRegistry = class {
|
|
|
8366
8512
|
modelProvider: effectiveProvider,
|
|
8367
8513
|
modelId: effectiveModelId,
|
|
8368
8514
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8369
|
-
}).where(
|
|
8515
|
+
}).where(eq18(agentSessions.projectId, projectId)).run();
|
|
8370
8516
|
}
|
|
8371
8517
|
const agent2 = createAeroSession({
|
|
8372
8518
|
projectName,
|
|
@@ -8544,13 +8690,13 @@ ${lines.join("\n")}
|
|
|
8544
8690
|
agent.state.messages = result.messages;
|
|
8545
8691
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
8546
8692
|
this.save(projectName);
|
|
8547
|
-
|
|
8693
|
+
log18.info("compaction.completed", {
|
|
8548
8694
|
projectName,
|
|
8549
8695
|
removedCount: result.removedCount,
|
|
8550
8696
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
8551
8697
|
});
|
|
8552
8698
|
} catch (err) {
|
|
8553
|
-
|
|
8699
|
+
log18.error("compaction.failed", {
|
|
8554
8700
|
projectName,
|
|
8555
8701
|
error: err instanceof Error ? err.message : String(err)
|
|
8556
8702
|
});
|
|
@@ -8580,7 +8726,7 @@ ${lines.join("\n")}
|
|
|
8580
8726
|
modelProvider: nextProvider,
|
|
8581
8727
|
modelId: nextModelId,
|
|
8582
8728
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8583
|
-
}).where(
|
|
8729
|
+
}).where(eq18(agentSessions.projectId, projectId)).run();
|
|
8584
8730
|
}
|
|
8585
8731
|
/** Persist a session's transcript back to the DB. Call after any run settles. */
|
|
8586
8732
|
save(projectName) {
|
|
@@ -8647,7 +8793,7 @@ ${lines.join("\n")}
|
|
|
8647
8793
|
await agent.prompt(msgs);
|
|
8648
8794
|
this.save(projectName);
|
|
8649
8795
|
} catch (err) {
|
|
8650
|
-
|
|
8796
|
+
log18.error("drain.failed", {
|
|
8651
8797
|
projectName,
|
|
8652
8798
|
error: err instanceof Error ? err.message : String(err)
|
|
8653
8799
|
});
|
|
@@ -8742,17 +8888,17 @@ ${lines.join("\n")}
|
|
|
8742
8888
|
return id;
|
|
8743
8889
|
}
|
|
8744
8890
|
tryResolveProjectId(projectName) {
|
|
8745
|
-
const row = this.opts.db.select({ id: projects.id }).from(projects).where(
|
|
8891
|
+
const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq18(projects.name, projectName)).get();
|
|
8746
8892
|
return row?.id;
|
|
8747
8893
|
}
|
|
8748
8894
|
loadRow(projectId) {
|
|
8749
|
-
const row = this.opts.db.select().from(agentSessions).where(
|
|
8895
|
+
const row = this.opts.db.select().from(agentSessions).where(eq18(agentSessions.projectId, projectId)).get();
|
|
8750
8896
|
return row ?? null;
|
|
8751
8897
|
}
|
|
8752
8898
|
insertRow(params) {
|
|
8753
8899
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8754
8900
|
this.opts.db.insert(agentSessions).values({
|
|
8755
|
-
id:
|
|
8901
|
+
id: crypto19.randomUUID(),
|
|
8756
8902
|
projectId: params.projectId,
|
|
8757
8903
|
systemPrompt: params.systemPrompt,
|
|
8758
8904
|
modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
|
|
@@ -8765,14 +8911,14 @@ ${lines.join("\n")}
|
|
|
8765
8911
|
}
|
|
8766
8912
|
updateRow(projectId, patch) {
|
|
8767
8913
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8768
|
-
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(
|
|
8914
|
+
this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq18(agentSessions.projectId, projectId)).run();
|
|
8769
8915
|
}
|
|
8770
8916
|
};
|
|
8771
8917
|
|
|
8772
8918
|
// src/agent/agent-routes.ts
|
|
8773
|
-
import { eq as
|
|
8919
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
8774
8920
|
function resolveProject(db, name) {
|
|
8775
|
-
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(
|
|
8921
|
+
const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq19(projects.name, name)).get();
|
|
8776
8922
|
if (!row) throw notFound("project", name);
|
|
8777
8923
|
return row;
|
|
8778
8924
|
}
|
|
@@ -8781,7 +8927,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
8781
8927
|
"/projects/:name/agent/transcript",
|
|
8782
8928
|
async (request) => {
|
|
8783
8929
|
const project = resolveProject(opts.db, request.params.name);
|
|
8784
|
-
const row = opts.db.select().from(agentSessions).where(
|
|
8930
|
+
const row = opts.db.select().from(agentSessions).where(eq19(agentSessions.projectId, project.id)).get();
|
|
8785
8931
|
if (!row) {
|
|
8786
8932
|
return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
|
|
8787
8933
|
}
|
|
@@ -8805,7 +8951,7 @@ function registerAgentRoutes(app, opts) {
|
|
|
8805
8951
|
async (request) => {
|
|
8806
8952
|
const project = resolveProject(opts.db, request.params.name);
|
|
8807
8953
|
opts.sessionRegistry.reset(project.name);
|
|
8808
|
-
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
8954
|
+
opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq19(agentSessions.projectId, project.id)).run();
|
|
8809
8955
|
return { status: "reset" };
|
|
8810
8956
|
}
|
|
8811
8957
|
);
|
|
@@ -9245,7 +9391,7 @@ function formatAuditFactorScore(factor) {
|
|
|
9245
9391
|
}
|
|
9246
9392
|
|
|
9247
9393
|
// src/snapshot-service.ts
|
|
9248
|
-
var
|
|
9394
|
+
var log19 = createLogger("Snapshot");
|
|
9249
9395
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
9250
9396
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
9251
9397
|
var ProviderExecutionGate2 = class {
|
|
@@ -9391,7 +9537,7 @@ var SnapshotService = class {
|
|
|
9391
9537
|
return mapAuditReport(report);
|
|
9392
9538
|
} catch (err) {
|
|
9393
9539
|
const message = err instanceof Error ? err.message : String(err);
|
|
9394
|
-
|
|
9540
|
+
log19.warn("audit.failed", { homepageUrl, error: message });
|
|
9395
9541
|
return {
|
|
9396
9542
|
url: homepageUrl,
|
|
9397
9543
|
finalUrl: homepageUrl,
|
|
@@ -9420,7 +9566,7 @@ var SnapshotService = class {
|
|
|
9420
9566
|
queries: parsedQueries
|
|
9421
9567
|
};
|
|
9422
9568
|
} catch (err) {
|
|
9423
|
-
|
|
9569
|
+
log19.warn("profile.generation-failed", {
|
|
9424
9570
|
domain: ctx.domain,
|
|
9425
9571
|
provider: ctx.analysisProvider.adapter.name,
|
|
9426
9572
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -9562,7 +9708,7 @@ var SnapshotService = class {
|
|
|
9562
9708
|
recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
|
|
9563
9709
|
};
|
|
9564
9710
|
} catch (err) {
|
|
9565
|
-
|
|
9711
|
+
log19.warn("response.analysis-failed", {
|
|
9566
9712
|
provider: ctx.analysisProvider.adapter.name,
|
|
9567
9713
|
error: err instanceof Error ? err.message : String(err)
|
|
9568
9714
|
});
|
|
@@ -9844,7 +9990,7 @@ function clipText(value, length) {
|
|
|
9844
9990
|
// src/server.ts
|
|
9845
9991
|
var _require3 = createRequire3(import.meta.url);
|
|
9846
9992
|
var { version: PKG_VERSION2 } = _require3("../package.json");
|
|
9847
|
-
var
|
|
9993
|
+
var log20 = createLogger("Server");
|
|
9848
9994
|
var DEFAULT_QUOTA = {
|
|
9849
9995
|
maxConcurrency: 2,
|
|
9850
9996
|
maxRequestsPerMinute: 10,
|
|
@@ -9879,14 +10025,14 @@ function summarizeProviderConfig(config) {
|
|
|
9879
10025
|
};
|
|
9880
10026
|
}
|
|
9881
10027
|
function hashApiKey(key) {
|
|
9882
|
-
return
|
|
10028
|
+
return crypto20.createHash("sha256").update(key).digest("hex");
|
|
9883
10029
|
}
|
|
9884
10030
|
var DASHBOARD_SCRYPT_KEYLEN = 64;
|
|
9885
10031
|
var DASHBOARD_SCRYPT_COST = 1 << 15;
|
|
9886
10032
|
var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
|
|
9887
10033
|
function hashDashboardPassword(password) {
|
|
9888
|
-
const salt =
|
|
9889
|
-
const derived =
|
|
10034
|
+
const salt = crypto20.randomBytes(16);
|
|
10035
|
+
const derived = crypto20.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
|
|
9890
10036
|
N: DASHBOARD_SCRYPT_COST,
|
|
9891
10037
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
9892
10038
|
});
|
|
@@ -9907,18 +10053,18 @@ function verifyDashboardPassword(password, storedHash) {
|
|
|
9907
10053
|
} catch {
|
|
9908
10054
|
return { ok: false, needsRehash: false };
|
|
9909
10055
|
}
|
|
9910
|
-
const derived =
|
|
10056
|
+
const derived = crypto20.scryptSync(password, salt, expected.length, {
|
|
9911
10057
|
N: DASHBOARD_SCRYPT_COST,
|
|
9912
10058
|
maxmem: DASHBOARD_SCRYPT_MAXMEM
|
|
9913
10059
|
});
|
|
9914
10060
|
if (derived.length !== expected.length) return { ok: false, needsRehash: false };
|
|
9915
|
-
return { ok:
|
|
10061
|
+
return { ok: crypto20.timingSafeEqual(derived, expected), needsRehash: false };
|
|
9916
10062
|
}
|
|
9917
10063
|
if (/^[a-f0-9]{64}$/i.test(storedHash)) {
|
|
9918
10064
|
const candidate = Buffer.from(hashApiKey(password), "hex");
|
|
9919
10065
|
const expected = Buffer.from(storedHash, "hex");
|
|
9920
10066
|
if (candidate.length !== expected.length) return { ok: false, needsRehash: false };
|
|
9921
|
-
const ok =
|
|
10067
|
+
const ok = crypto20.timingSafeEqual(candidate, expected);
|
|
9922
10068
|
return { ok, needsRehash: ok };
|
|
9923
10069
|
}
|
|
9924
10070
|
return { ok: false, needsRehash: false };
|
|
@@ -9977,7 +10123,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
9977
10123
|
}
|
|
9978
10124
|
if (migratedGoogle > 0) {
|
|
9979
10125
|
saveConfigPatch({ google: config.google });
|
|
9980
|
-
|
|
10126
|
+
log20.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
9981
10127
|
}
|
|
9982
10128
|
let migratedGa4 = 0;
|
|
9983
10129
|
for (const row of rows.ga4) {
|
|
@@ -9995,7 +10141,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
9995
10141
|
}
|
|
9996
10142
|
if (migratedGa4 > 0) {
|
|
9997
10143
|
saveConfigPatch({ ga4: config.ga4 });
|
|
9998
|
-
|
|
10144
|
+
log20.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
9999
10145
|
}
|
|
10000
10146
|
}
|
|
10001
10147
|
function isLoopbackBindHost(host) {
|
|
@@ -10034,11 +10180,11 @@ async function createServer(opts) {
|
|
|
10034
10180
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
10035
10181
|
dropLegacyCredentialColumns(opts.db);
|
|
10036
10182
|
} catch (err) {
|
|
10037
|
-
|
|
10183
|
+
log20.warn("credentials.migration.failed", {
|
|
10038
10184
|
error: err instanceof Error ? err.message : String(err)
|
|
10039
10185
|
});
|
|
10040
10186
|
}
|
|
10041
|
-
|
|
10187
|
+
log20.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
|
|
10042
10188
|
const p = providers[k];
|
|
10043
10189
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
10044
10190
|
}) });
|
|
@@ -10087,7 +10233,7 @@ async function createServer(opts) {
|
|
|
10087
10233
|
intelligenceService,
|
|
10088
10234
|
(runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
|
|
10089
10235
|
async (ctx) => {
|
|
10090
|
-
const project = opts.db.select({ name: projects.name }).from(projects).where(
|
|
10236
|
+
const project = opts.db.select({ name: projects.name }).from(projects).where(eq20(projects.id, ctx.projectId)).get();
|
|
10091
10237
|
if (!project) return;
|
|
10092
10238
|
let content;
|
|
10093
10239
|
if (ctx.kind === RunKinds["aeo-discover-probe"]) {
|
|
@@ -10192,9 +10338,9 @@ async function createServer(opts) {
|
|
|
10192
10338
|
return null;
|
|
10193
10339
|
});
|
|
10194
10340
|
if (!probed) return;
|
|
10195
|
-
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
|
|
10196
|
-
|
|
10197
|
-
|
|
10341
|
+
const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(and14(
|
|
10342
|
+
eq20(ccReleaseSyncs.release, probed.release),
|
|
10343
|
+
eq20(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
|
|
10198
10344
|
)).limit(1).get();
|
|
10199
10345
|
if (alreadySynced) {
|
|
10200
10346
|
app.log.info({ projectName, release: probed.release }, "Scheduled backlinks sync: already up to date, skipping");
|
|
@@ -10207,6 +10353,15 @@ async function createServer(opts) {
|
|
|
10207
10353
|
);
|
|
10208
10354
|
});
|
|
10209
10355
|
})();
|
|
10356
|
+
const project = opts.db.select().from(projects).where(eq20(projects.name, projectName)).get();
|
|
10357
|
+
if (project && bingConnectionStore.getConnection(project.canonicalDomain)) {
|
|
10358
|
+
aeroClient.backlinksBingSync(projectName).catch((err) => {
|
|
10359
|
+
app.log.error(
|
|
10360
|
+
{ projectName, err: err instanceof Error ? err.message : String(err) },
|
|
10361
|
+
"Scheduled Bing backlinks sync failed"
|
|
10362
|
+
);
|
|
10363
|
+
});
|
|
10364
|
+
}
|
|
10210
10365
|
},
|
|
10211
10366
|
onSiteAuditRequested: (runId, projectId) => {
|
|
10212
10367
|
runSiteAudit(runId, projectId);
|
|
@@ -10327,7 +10482,7 @@ async function createServer(opts) {
|
|
|
10327
10482
|
return removed;
|
|
10328
10483
|
}
|
|
10329
10484
|
};
|
|
10330
|
-
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ??
|
|
10485
|
+
const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto20.randomBytes(32).toString("hex");
|
|
10331
10486
|
const googleConnectionStore = {
|
|
10332
10487
|
listConnections: (domain) => listGoogleConnections(opts.config, domain),
|
|
10333
10488
|
getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
|
|
@@ -10373,11 +10528,11 @@ async function createServer(opts) {
|
|
|
10373
10528
|
const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
|
|
10374
10529
|
if (opts.config.apiKey) {
|
|
10375
10530
|
const keyHash = hashApiKey(opts.config.apiKey);
|
|
10376
|
-
const existing = opts.db.select().from(apiKeys).where(
|
|
10531
|
+
const existing = opts.db.select().from(apiKeys).where(eq20(apiKeys.keyHash, keyHash)).get();
|
|
10377
10532
|
if (!existing) {
|
|
10378
10533
|
const prefix = opts.config.apiKey.slice(0, 12);
|
|
10379
10534
|
opts.db.insert(apiKeys).values({
|
|
10380
|
-
id: `key_${
|
|
10535
|
+
id: `key_${crypto20.randomBytes(8).toString("hex")}`,
|
|
10381
10536
|
name: "default",
|
|
10382
10537
|
keyHash,
|
|
10383
10538
|
keyPrefix: prefix,
|
|
@@ -10401,7 +10556,7 @@ async function createServer(opts) {
|
|
|
10401
10556
|
};
|
|
10402
10557
|
const createSession = (apiKeyId) => {
|
|
10403
10558
|
pruneExpiredSessions();
|
|
10404
|
-
const sessionId =
|
|
10559
|
+
const sessionId = crypto20.randomBytes(32).toString("hex");
|
|
10405
10560
|
sessions.set(sessionId, {
|
|
10406
10561
|
apiKeyId,
|
|
10407
10562
|
expiresAt: Date.now() + SESSION_TTL_MS
|
|
@@ -10425,7 +10580,7 @@ async function createServer(opts) {
|
|
|
10425
10580
|
};
|
|
10426
10581
|
const getDefaultApiKey = () => {
|
|
10427
10582
|
if (!opts.config.apiKey) return void 0;
|
|
10428
|
-
return opts.db.select().from(apiKeys).where(
|
|
10583
|
+
return opts.db.select().from(apiKeys).where(eq20(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
|
|
10429
10584
|
};
|
|
10430
10585
|
const createPasswordSession = (reply) => {
|
|
10431
10586
|
const key = getDefaultApiKey();
|
|
@@ -10446,7 +10601,7 @@ async function createServer(opts) {
|
|
|
10446
10601
|
if (!header) return false;
|
|
10447
10602
|
const parts = header.split(" ");
|
|
10448
10603
|
if (parts.length !== 2 || parts[0] !== "Bearer") return false;
|
|
10449
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
10604
|
+
const key = opts.db.select().from(apiKeys).where(eq20(apiKeys.keyHash, hashApiKey(parts[1]))).get();
|
|
10450
10605
|
return Boolean(key && !key.revokedAt);
|
|
10451
10606
|
};
|
|
10452
10607
|
app.get(apiPrefix + "/session", async (request, reply) => {
|
|
@@ -10500,12 +10655,12 @@ async function createServer(opts) {
|
|
|
10500
10655
|
return reply.send({ authenticated: true });
|
|
10501
10656
|
}
|
|
10502
10657
|
if (apiKey) {
|
|
10503
|
-
const key = opts.db.select().from(apiKeys).where(
|
|
10658
|
+
const key = opts.db.select().from(apiKeys).where(eq20(apiKeys.keyHash, hashApiKey(apiKey))).get();
|
|
10504
10659
|
if (!key || key.revokedAt) {
|
|
10505
10660
|
const err2 = authInvalid();
|
|
10506
10661
|
return reply.status(err2.statusCode).send(err2.toJSON());
|
|
10507
10662
|
}
|
|
10508
|
-
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(
|
|
10663
|
+
opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq20(apiKeys.id, key.id)).run();
|
|
10509
10664
|
const sessionId = createSession(key.id);
|
|
10510
10665
|
reply.header("set-cookie", serializeSessionCookie({
|
|
10511
10666
|
name: SESSION_COOKIE_NAME,
|
|
@@ -10664,7 +10819,7 @@ async function createServer(opts) {
|
|
|
10664
10819
|
deps: {
|
|
10665
10820
|
enqueueAutoExtract: ({ projectId, release: r }) => {
|
|
10666
10821
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10667
|
-
const runId =
|
|
10822
|
+
const runId = crypto20.randomUUID();
|
|
10668
10823
|
opts.db.insert(runs).values({
|
|
10669
10824
|
id: runId,
|
|
10670
10825
|
projectId,
|
|
@@ -10687,6 +10842,13 @@ async function createServer(opts) {
|
|
|
10687
10842
|
app.log.error({ runId, err }, "Backlink extract failed");
|
|
10688
10843
|
});
|
|
10689
10844
|
},
|
|
10845
|
+
onBingBacklinkSyncRequested: (runId, projectId) => {
|
|
10846
|
+
executeBingBacklinkSync(opts.db, runId, projectId, {
|
|
10847
|
+
resolveConnection: (domain) => bingConnectionStore.getConnection(domain)
|
|
10848
|
+
}).catch((err) => {
|
|
10849
|
+
app.log.error({ runId, err }, "Bing backlink sync failed");
|
|
10850
|
+
});
|
|
10851
|
+
},
|
|
10690
10852
|
onDiscoveryRunRequested: (input) => {
|
|
10691
10853
|
executeDiscoveryRun({
|
|
10692
10854
|
db: opts.db,
|
|
@@ -10750,7 +10912,7 @@ async function createServer(opts) {
|
|
|
10750
10912
|
...inspectOpts,
|
|
10751
10913
|
config: opts.config
|
|
10752
10914
|
}).then(() => {
|
|
10753
|
-
const finished = opts.db.select({ status: runs.status }).from(runs).where(
|
|
10915
|
+
const finished = opts.db.select({ status: runs.status }).from(runs).where(eq20(runs.id, runId)).get();
|
|
10754
10916
|
if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
|
|
10755
10917
|
return maybeRefreshGscCoverage(opts.db, opts.config, projectId);
|
|
10756
10918
|
}
|
|
@@ -10838,7 +11000,7 @@ async function createServer(opts) {
|
|
|
10838
11000
|
const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
|
|
10839
11001
|
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10840
11002
|
opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
|
|
10841
|
-
id:
|
|
11003
|
+
id: crypto20.randomUUID(),
|
|
10842
11004
|
projectId,
|
|
10843
11005
|
actor: "api",
|
|
10844
11006
|
action: existing ? "provider.updated" : "provider.created",
|