@ainyc/canonry 4.56.0 → 4.57.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.
@@ -577,11 +577,11 @@ function checkLatestVersionForServer(opts) {
577
577
 
578
578
  // src/server.ts
579
579
  import { createRequire as createRequire4 } from "module";
580
- import crypto35 from "crypto";
580
+ import crypto36 from "crypto";
581
581
  import fs15 from "fs";
582
582
  import path15 from "path";
583
583
  import { fileURLToPath as fileURLToPath2 } from "url";
584
- import { eq as eq42 } from "drizzle-orm";
584
+ import { eq as eq43 } from "drizzle-orm";
585
585
  import Fastify from "fastify";
586
586
 
587
587
  // ../api-routes/src/index.ts
@@ -29079,13 +29079,68 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
29079
29079
  }
29080
29080
  }
29081
29081
 
29082
- // src/commoncrawl-sync.ts
29082
+ // src/coverage-refresh.ts
29083
29083
  import crypto29 from "crypto";
29084
+ import { and as and25, desc as desc16, eq as eq32, inArray as inArray11 } from "drizzle-orm";
29085
+ var log6 = createLogger("CoverageRefresh");
29086
+ var COVERAGE_REFRESH_MIN_INTERVAL_MS = 60 * 60 * 1e3;
29087
+ var ACTIVE_OR_DONE_STATUSES = [
29088
+ RunStatuses.queued,
29089
+ RunStatuses.running,
29090
+ RunStatuses.completed,
29091
+ RunStatuses.partial
29092
+ ];
29093
+ var defaultDeps = { executeInspectSitemap };
29094
+ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps, nowMs = Date.now()) {
29095
+ const project = db.select({ canonicalDomain: projects.canonicalDomain }).from(projects).where(eq32(projects.id, projectId)).get();
29096
+ if (!project) return null;
29097
+ const { clientId, clientSecret } = getGoogleAuthConfig(config);
29098
+ if (!clientId || !clientSecret) return null;
29099
+ const conn = getGoogleConnection(config, project.canonicalDomain, "gsc");
29100
+ if (!conn?.refreshToken || !conn.propertyId) return null;
29101
+ const recent = db.select({ createdAt: runs.createdAt }).from(runs).where(
29102
+ and25(
29103
+ eq32(runs.projectId, projectId),
29104
+ eq32(runs.kind, RunKinds["inspect-sitemap"]),
29105
+ inArray11(runs.status, ACTIVE_OR_DONE_STATUSES)
29106
+ )
29107
+ ).orderBy(desc16(runs.createdAt)).limit(1).get();
29108
+ if (recent) {
29109
+ const ageMs = nowMs - Date.parse(recent.createdAt);
29110
+ if (Number.isFinite(ageMs) && ageMs < COVERAGE_REFRESH_MIN_INTERVAL_MS) {
29111
+ log6.info("skip.recent", { projectId, ageMs });
29112
+ return null;
29113
+ }
29114
+ }
29115
+ const runId = crypto29.randomUUID();
29116
+ db.insert(runs).values({
29117
+ id: runId,
29118
+ projectId,
29119
+ kind: RunKinds["inspect-sitemap"],
29120
+ status: RunStatuses.queued,
29121
+ trigger: RunTriggers.scheduled,
29122
+ createdAt: new Date(nowMs).toISOString()
29123
+ }).run();
29124
+ log6.info("refresh.start", { projectId, runId });
29125
+ try {
29126
+ await deps.executeInspectSitemap(db, runId, projectId, { config });
29127
+ } catch (err) {
29128
+ log6.error("refresh.failed", {
29129
+ projectId,
29130
+ runId,
29131
+ error: err instanceof Error ? err.message : String(err)
29132
+ });
29133
+ }
29134
+ return runId;
29135
+ }
29136
+
29137
+ // src/commoncrawl-sync.ts
29138
+ import crypto30 from "crypto";
29084
29139
  import path11 from "path";
29085
- import { and as and25, eq as eq32, sql as sql14 } from "drizzle-orm";
29086
- var log6 = createLogger("CommonCrawlSync");
29140
+ import { and as and26, eq as eq33, sql as sql14 } from "drizzle-orm";
29141
+ var log7 = createLogger("CommonCrawlSync");
29087
29142
  var INSERT_CHUNK_SIZE = 1e4;
29088
- function defaultDeps() {
29143
+ function defaultDeps2() {
29089
29144
  return {
29090
29145
  downloadFile,
29091
29146
  queryBacklinks,
@@ -29095,7 +29150,7 @@ function defaultDeps() {
29095
29150
  };
29096
29151
  }
29097
29152
  async function executeReleaseSync(db, syncId, opts) {
29098
- const deps = { ...defaultDeps(), ...opts.deps };
29153
+ const deps = { ...defaultDeps2(), ...opts.deps };
29099
29154
  const release = opts.release;
29100
29155
  try {
29101
29156
  if (!isValidReleaseId(release)) {
@@ -29108,7 +29163,7 @@ async function executeReleaseSync(db, syncId, opts) {
29108
29163
  phaseDetail: "downloading vertices + edges",
29109
29164
  updatedAt: downloadStartedAt,
29110
29165
  error: null
29111
- }).where(eq32(ccReleaseSyncs.id, syncId)).run();
29166
+ }).where(eq33(ccReleaseSyncs.id, syncId)).run();
29112
29167
  const paths = ccReleasePaths(release);
29113
29168
  const releaseCacheDir = path11.join(deps.cacheDir, release);
29114
29169
  const vertexPath = path11.join(releaseCacheDir, paths.vertexFilename);
@@ -29131,7 +29186,7 @@ async function executeReleaseSync(db, syncId, opts) {
29131
29186
  vertexSha256: vertex.sha256,
29132
29187
  edgesSha256: edges.sha256,
29133
29188
  updatedAt: downloadFinishedAt
29134
- }).where(eq32(ccReleaseSyncs.id, syncId)).run();
29189
+ }).where(eq33(ccReleaseSyncs.id, syncId)).run();
29135
29190
  const allProjects = db.select().from(projects).all();
29136
29191
  const targets = Array.from(new Set(allProjects.map((p) => p.canonicalDomain)));
29137
29192
  let rows = [];
@@ -29147,15 +29202,15 @@ async function executeReleaseSync(db, syncId, opts) {
29147
29202
  }
29148
29203
  const queriedAt = deps.now().toISOString();
29149
29204
  db.transaction((tx) => {
29150
- tx.delete(backlinkDomains).where(eq32(backlinkDomains.releaseSyncId, syncId)).run();
29151
- tx.delete(backlinkSummaries).where(eq32(backlinkSummaries.releaseSyncId, syncId)).run();
29205
+ tx.delete(backlinkDomains).where(eq33(backlinkDomains.releaseSyncId, syncId)).run();
29206
+ tx.delete(backlinkSummaries).where(eq33(backlinkSummaries.releaseSyncId, syncId)).run();
29152
29207
  const expanded = [];
29153
29208
  for (const r of rows) {
29154
29209
  const projectIds = projectsByDomain.get(r.targetDomain);
29155
29210
  if (!projectIds) continue;
29156
29211
  for (const projectId of projectIds) {
29157
29212
  expanded.push({
29158
- id: crypto29.randomUUID(),
29213
+ id: crypto30.randomUUID(),
29159
29214
  projectId,
29160
29215
  releaseSyncId: syncId,
29161
29216
  release,
@@ -29175,7 +29230,7 @@ async function executeReleaseSync(db, syncId, opts) {
29175
29230
  const projectRows = rowsByProject.get(p.id) ?? [];
29176
29231
  const summary = computeSummary(projectRows);
29177
29232
  tx.insert(backlinkSummaries).values({
29178
- id: crypto29.randomUUID(),
29233
+ id: crypto30.randomUUID(),
29179
29234
  projectId: p.id,
29180
29235
  releaseSyncId: syncId,
29181
29236
  release,
@@ -29207,8 +29262,8 @@ async function executeReleaseSync(db, syncId, opts) {
29207
29262
  domainsDiscovered: rows.length,
29208
29263
  updatedAt: finishedAt,
29209
29264
  error: null
29210
- }).where(eq32(ccReleaseSyncs.id, syncId)).run();
29211
- log6.info("sync.completed", {
29265
+ }).where(eq33(ccReleaseSyncs.id, syncId)).run();
29266
+ log7.info("sync.completed", {
29212
29267
  syncId,
29213
29268
  release,
29214
29269
  projectsProcessed: allProjects.length,
@@ -29220,7 +29275,7 @@ async function executeReleaseSync(db, syncId, opts) {
29220
29275
  try {
29221
29276
  deps.enqueueAutoExtract({ projectId: p.id, release });
29222
29277
  } catch (err) {
29223
- log6.error("auto-extract.enqueue-failed", {
29278
+ log7.error("auto-extract.enqueue-failed", {
29224
29279
  syncId,
29225
29280
  release,
29226
29281
  projectId: p.id,
@@ -29237,8 +29292,8 @@ async function executeReleaseSync(db, syncId, opts) {
29237
29292
  error: errorMsg,
29238
29293
  phaseDetail: null,
29239
29294
  updatedAt: finishedAt
29240
- }).where(eq32(ccReleaseSyncs.id, syncId)).run();
29241
- log6.error("sync.failed", { syncId, release, error: errorMsg });
29295
+ }).where(eq33(ccReleaseSyncs.id, syncId)).run();
29296
+ log7.error("sync.failed", { syncId, release, error: errorMsg });
29242
29297
  throw err;
29243
29298
  }
29244
29299
  }
@@ -29271,11 +29326,11 @@ function computeSummary(rows) {
29271
29326
  }
29272
29327
 
29273
29328
  // src/backlink-extract.ts
29274
- import crypto30 from "crypto";
29329
+ import crypto31 from "crypto";
29275
29330
  import fs11 from "fs";
29276
- import { and as and26, desc as desc16, eq as eq33 } from "drizzle-orm";
29277
- var log7 = createLogger("BacklinkExtract");
29278
- function defaultDeps2() {
29331
+ import { and as and27, desc as desc17, eq as eq34 } from "drizzle-orm";
29332
+ var log8 = createLogger("BacklinkExtract");
29333
+ function defaultDeps3() {
29279
29334
  return {
29280
29335
  queryBacklinks,
29281
29336
  loadDuckdb,
@@ -29283,15 +29338,15 @@ function defaultDeps2() {
29283
29338
  };
29284
29339
  }
29285
29340
  async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
29286
- const deps = { ...defaultDeps2(), ...opts.deps };
29341
+ const deps = { ...defaultDeps3(), ...opts.deps };
29287
29342
  const startedAt = deps.now().toISOString();
29288
- db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq33(runs.id, runId)).run();
29343
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq34(runs.id, runId)).run();
29289
29344
  try {
29290
- const project = db.select().from(projects).where(eq33(projects.id, projectId)).get();
29345
+ const project = db.select().from(projects).where(eq34(projects.id, projectId)).get();
29291
29346
  if (!project) {
29292
29347
  throw new Error(`Project not found: ${projectId}`);
29293
29348
  }
29294
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq33(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq33(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc16(ccReleaseSyncs.createdAt)).limit(1).get();
29349
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq34(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq34(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc17(ccReleaseSyncs.createdAt)).limit(1).get();
29295
29350
  if (!sync) {
29296
29351
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
29297
29352
  }
@@ -29319,11 +29374,11 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
29319
29374
  const targetDomain = project.canonicalDomain;
29320
29375
  db.transaction((tx) => {
29321
29376
  tx.delete(backlinkDomains).where(
29322
- and26(eq33(backlinkDomains.projectId, projectId), eq33(backlinkDomains.release, release))
29377
+ and27(eq34(backlinkDomains.projectId, projectId), eq34(backlinkDomains.release, release))
29323
29378
  ).run();
29324
29379
  if (rows.length > 0) {
29325
29380
  const values = rows.map((r) => ({
29326
- id: crypto30.randomUUID(),
29381
+ id: crypto31.randomUUID(),
29327
29382
  projectId,
29328
29383
  releaseSyncId: syncId,
29329
29384
  release,
@@ -29336,7 +29391,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
29336
29391
  }
29337
29392
  const summary = computeSummary2(rows);
29338
29393
  tx.insert(backlinkSummaries).values({
29339
- id: crypto30.randomUUID(),
29394
+ id: crypto31.randomUUID(),
29340
29395
  projectId,
29341
29396
  releaseSyncId: syncId,
29342
29397
  release,
@@ -29359,8 +29414,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
29359
29414
  }).run();
29360
29415
  });
29361
29416
  const finishedAt = deps.now().toISOString();
29362
- db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq33(runs.id, runId)).run();
29363
- log7.info("extract.completed", { runId, projectId, release, rows: rows.length });
29417
+ db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq34(runs.id, runId)).run();
29418
+ log8.info("extract.completed", { runId, projectId, release, rows: rows.length });
29364
29419
  } catch (err) {
29365
29420
  const errorMsg = err instanceof Error ? err.message : String(err);
29366
29421
  const finishedAt = deps.now().toISOString();
@@ -29368,8 +29423,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
29368
29423
  status: RunStatuses.failed,
29369
29424
  error: errorMsg,
29370
29425
  finishedAt
29371
- }).where(eq33(runs.id, runId)).run();
29372
- log7.error("extract.failed", { runId, projectId, error: errorMsg });
29426
+ }).where(eq34(runs.id, runId)).run();
29427
+ log8.error("extract.failed", { runId, projectId, error: errorMsg });
29373
29428
  throw err;
29374
29429
  }
29375
29430
  }
@@ -29389,18 +29444,18 @@ function computeSummary2(rows) {
29389
29444
  }
29390
29445
 
29391
29446
  // src/discovery-run.ts
29392
- import crypto31 from "crypto";
29393
- import { and as and27, eq as eq34 } from "drizzle-orm";
29394
- var log8 = createLogger("DiscoveryRun");
29447
+ import crypto32 from "crypto";
29448
+ import { and as and28, eq as eq35 } from "drizzle-orm";
29449
+ var log9 = createLogger("DiscoveryRun");
29395
29450
  var DEFAULT_SEED_COUNT = 30;
29396
29451
  var QUERIES_PER_INTENT_BUCKET = 6;
29397
29452
  async function executeDiscoveryRun(opts) {
29398
29453
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
29399
- opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq34(runs.id, opts.runId)).run();
29454
+ opts.db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq35(runs.id, opts.runId)).run();
29400
29455
  try {
29401
- const projectRow = opts.db.select().from(projects).where(eq34(projects.id, opts.projectId)).get();
29456
+ const projectRow = opts.db.select().from(projects).where(eq35(projects.id, opts.projectId)).get();
29402
29457
  if (!projectRow) throw new Error(`Project ${opts.projectId} not found`);
29403
- const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq34(competitors.projectId, opts.projectId)).all().map((r) => r.domain.toLowerCase());
29458
+ const projectCompetitors = opts.db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, opts.projectId)).all().map((r) => r.domain.toLowerCase());
29404
29459
  const canonicalDomains = effectiveDomains({
29405
29460
  canonicalDomain: projectRow.canonicalDomain,
29406
29461
  ownedDomains: projectRow.ownedDomains
@@ -29430,8 +29485,8 @@ async function executeDiscoveryRun(opts) {
29430
29485
  seedProvider: result.seedProvider,
29431
29486
  result
29432
29487
  });
29433
- opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq34(runs.id, opts.runId)).run();
29434
- log8.info("discovery.completed", {
29488
+ opts.db.update(runs).set({ status: RunStatuses.completed, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq35(runs.id, opts.runId)).run();
29489
+ log9.info("discovery.completed", {
29435
29490
  runId: opts.runId,
29436
29491
  sessionId: opts.sessionId,
29437
29492
  buckets: result.buckets,
@@ -29439,13 +29494,13 @@ async function executeDiscoveryRun(opts) {
29439
29494
  });
29440
29495
  } catch (err) {
29441
29496
  const errorMsg = err instanceof Error ? err.message : String(err);
29442
- log8.error("discovery.failed", { runId: opts.runId, sessionId: opts.sessionId, error: errorMsg });
29497
+ log9.error("discovery.failed", { runId: opts.runId, sessionId: opts.sessionId, error: errorMsg });
29443
29498
  markSessionFailed(opts.db, opts.sessionId, errorMsg);
29444
29499
  opts.db.update(runs).set({
29445
29500
  status: RunStatuses.failed,
29446
29501
  finishedAt: (/* @__PURE__ */ new Date()).toISOString(),
29447
29502
  error: errorMsg
29448
- }).where(eq34(runs.id, opts.runId)).run();
29503
+ }).where(eq35(runs.id, opts.runId)).run();
29449
29504
  }
29450
29505
  }
29451
29506
  function buildDefaultDeps(registry) {
@@ -29650,13 +29705,13 @@ function writeDiscoveryInsight(db, input) {
29650
29705
  totalProbes
29651
29706
  });
29652
29707
  db.transaction((tx) => {
29653
- tx.update(insights).set({ dismissed: true }).where(and27(
29654
- eq34(insights.projectId, input.projectId),
29655
- eq34(insights.type, "discovery.basket-divergence"),
29656
- eq34(insights.dismissed, false)
29708
+ tx.update(insights).set({ dismissed: true }).where(and28(
29709
+ eq35(insights.projectId, input.projectId),
29710
+ eq35(insights.type, "discovery.basket-divergence"),
29711
+ eq35(insights.dismissed, false)
29657
29712
  )).run();
29658
29713
  tx.insert(insights).values({
29659
- id: crypto31.randomUUID(),
29714
+ id: crypto32.randomUUID(),
29660
29715
  projectId: input.projectId,
29661
29716
  runId: input.runId,
29662
29717
  type: "discovery.basket-divergence",
@@ -29692,7 +29747,7 @@ function buildDiscoveryInsightTitle(input) {
29692
29747
  }
29693
29748
 
29694
29749
  // src/commands/backfill.ts
29695
- import { and as and28, eq as eq35, inArray as inArray11, isNull, sql as sql15 } from "drizzle-orm";
29750
+ import { and as and29, eq as eq36, inArray as inArray12, isNull, sql as sql15 } from "drizzle-orm";
29696
29751
  var SNAPSHOT_BATCH_SIZE = 500;
29697
29752
  async function backfillAnswerVisibilityCommand(opts) {
29698
29753
  const config = loadConfig();
@@ -29700,7 +29755,7 @@ async function backfillAnswerVisibilityCommand(opts) {
29700
29755
  migrate(db);
29701
29756
  const projectFilter = opts?.project?.trim();
29702
29757
  const isDryRun = opts?.dryRun === true;
29703
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq35(projects.name, projectFilter)).all() : db.select().from(projects).all();
29758
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq36(projects.name, projectFilter)).all() : db.select().from(projects).all();
29704
29759
  let examined = 0;
29705
29760
  let updated = 0;
29706
29761
  let wouldUpdate = 0;
@@ -29708,10 +29763,10 @@ async function backfillAnswerVisibilityCommand(opts) {
29708
29763
  let reparsed = 0;
29709
29764
  let providerErrors = 0;
29710
29765
  if (scopedProjects.length > 0) {
29711
- const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and28(
29712
- eq35(runs.kind, RunKinds["answer-visibility"]),
29713
- inArray11(runs.projectId, scopedProjects.map((project) => project.id))
29714
- )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq35(runs.kind, RunKinds["answer-visibility"])).all();
29766
+ const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and29(
29767
+ eq36(runs.kind, RunKinds["answer-visibility"]),
29768
+ inArray12(runs.projectId, scopedProjects.map((project) => project.id))
29769
+ )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq36(runs.kind, RunKinds["answer-visibility"])).all();
29715
29770
  const runIdsByProject = /* @__PURE__ */ new Map();
29716
29771
  for (const run of runRows) {
29717
29772
  const existing = runIdsByProject.get(run.projectId);
@@ -29719,7 +29774,7 @@ async function backfillAnswerVisibilityCommand(opts) {
29719
29774
  else runIdsByProject.set(run.projectId, [run.id]);
29720
29775
  }
29721
29776
  for (const project of scopedProjects) {
29722
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, project.id)).all().map((row) => row.domain);
29777
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, project.id)).all().map((row) => row.domain);
29723
29778
  const runIds = runIdsByProject.get(project.id) ?? [];
29724
29779
  if (runIds.length === 0) continue;
29725
29780
  const projectDomains = effectiveDomains({
@@ -29742,7 +29797,7 @@ async function backfillAnswerVisibilityCommand(opts) {
29742
29797
  competitorOverlap: querySnapshots.competitorOverlap,
29743
29798
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
29744
29799
  rawResponse: querySnapshots.rawResponse
29745
- }).from(querySnapshots).where(inArray11(querySnapshots.runId, batchRunIds)).all();
29800
+ }).from(querySnapshots).where(inArray12(querySnapshots.runId, batchRunIds)).all();
29746
29801
  const pendingUpdates = [];
29747
29802
  for (const snapshot of snapshotRows) {
29748
29803
  examined++;
@@ -29807,7 +29862,7 @@ async function backfillAnswerVisibilityCommand(opts) {
29807
29862
  } else {
29808
29863
  db.transaction((tx) => {
29809
29864
  for (const update of pendingUpdates) {
29810
- tx.update(querySnapshots).set(update.patch).where(eq35(querySnapshots.id, update.id)).run();
29865
+ tx.update(querySnapshots).set(update.patch).where(eq36(querySnapshots.id, update.id)).run();
29811
29866
  }
29812
29867
  });
29813
29868
  updated += pendingUpdates.length;
@@ -29856,13 +29911,13 @@ No DB writes performed. Re-run without --dry-run to apply.`);
29856
29911
  function backfillNormalizedPaths(db, opts) {
29857
29912
  const baseConditions = [];
29858
29913
  if (opts?.projectId) {
29859
- baseConditions.push(eq35(gaTrafficSnapshots.projectId, opts.projectId));
29914
+ baseConditions.push(eq36(gaTrafficSnapshots.projectId, opts.projectId));
29860
29915
  }
29861
29916
  const rows = db.select({
29862
29917
  id: gaTrafficSnapshots.id,
29863
29918
  landingPage: gaTrafficSnapshots.landingPage,
29864
29919
  landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
29865
- }).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and28(...baseConditions) : void 0).all();
29920
+ }).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and29(...baseConditions) : void 0).all();
29866
29921
  let updated = 0;
29867
29922
  let unchanged = 0;
29868
29923
  if (rows.length > 0) {
@@ -29877,7 +29932,7 @@ function backfillNormalizedPaths(db, opts) {
29877
29932
  unchanged++;
29878
29933
  continue;
29879
29934
  }
29880
- tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq35(gaTrafficSnapshots.id, row.id)).run();
29935
+ tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq36(gaTrafficSnapshots.id, row.id)).run();
29881
29936
  updated++;
29882
29937
  }
29883
29938
  });
@@ -29891,7 +29946,7 @@ async function backfillNormalizedPathsCommand(opts) {
29891
29946
  const projectFilter = opts?.project?.trim();
29892
29947
  let projectId;
29893
29948
  if (projectFilter) {
29894
- const project = db.select({ id: projects.id }).from(projects).where(eq35(projects.name, projectFilter)).get();
29949
+ const project = db.select({ id: projects.id }).from(projects).where(eq36(projects.name, projectFilter)).get();
29895
29950
  if (!project) {
29896
29951
  const result2 = {
29897
29952
  project: projectFilter,
@@ -29928,13 +29983,13 @@ async function backfillNormalizedPathsCommand(opts) {
29928
29983
  function backfillAiReferralPaths(db, opts) {
29929
29984
  const baseConditions = [];
29930
29985
  if (opts?.projectId) {
29931
- baseConditions.push(eq35(gaAiReferrals.projectId, opts.projectId));
29986
+ baseConditions.push(eq36(gaAiReferrals.projectId, opts.projectId));
29932
29987
  }
29933
29988
  const rows = db.select({
29934
29989
  id: gaAiReferrals.id,
29935
29990
  landingPage: gaAiReferrals.landingPage,
29936
29991
  landingPageNormalized: gaAiReferrals.landingPageNormalized
29937
- }).from(gaAiReferrals).where(baseConditions.length > 0 ? and28(...baseConditions) : void 0).all();
29992
+ }).from(gaAiReferrals).where(baseConditions.length > 0 ? and29(...baseConditions) : void 0).all();
29938
29993
  let updated = 0;
29939
29994
  let unchanged = 0;
29940
29995
  if (rows.length > 0) {
@@ -29949,7 +30004,7 @@ function backfillAiReferralPaths(db, opts) {
29949
30004
  unchanged++;
29950
30005
  continue;
29951
30006
  }
29952
- tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq35(gaAiReferrals.id, row.id)).run();
30007
+ tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq36(gaAiReferrals.id, row.id)).run();
29953
30008
  updated++;
29954
30009
  }
29955
30010
  });
@@ -29963,7 +30018,7 @@ async function backfillAiReferralPathsCommand(opts) {
29963
30018
  const projectFilter = opts?.project?.trim();
29964
30019
  let projectId;
29965
30020
  if (projectFilter) {
29966
- const project = db.select({ id: projects.id }).from(projects).where(eq35(projects.name, projectFilter)).get();
30021
+ const project = db.select({ id: projects.id }).from(projects).where(eq36(projects.name, projectFilter)).get();
29967
30022
  if (!project) {
29968
30023
  const result2 = {
29969
30024
  project: projectFilter,
@@ -29999,10 +30054,10 @@ async function backfillAiReferralPathsCommand(opts) {
29999
30054
  }
30000
30055
  function backfillProjectAnswerMentions(db, projectId, opts) {
30001
30056
  const isDryRun = opts?.dryRun === true;
30002
- const project = db.select().from(projects).where(eq35(projects.id, projectId)).get();
30057
+ const project = db.select().from(projects).where(eq36(projects.id, projectId)).get();
30003
30058
  if (!project) return { examined: 0, updated: 0, mentioned: 0 };
30004
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq35(competitors.projectId, projectId)).all().map((row) => row.domain);
30005
- const runRows = db.select({ id: runs.id }).from(runs).where(and28(eq35(runs.kind, RunKinds["answer-visibility"]), eq35(runs.projectId, projectId))).all();
30059
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq36(competitors.projectId, projectId)).all().map((row) => row.domain);
30060
+ const runRows = db.select({ id: runs.id }).from(runs).where(and29(eq36(runs.kind, RunKinds["answer-visibility"]), eq36(runs.projectId, projectId))).all();
30006
30061
  const runIds = runRows.map((r) => r.id);
30007
30062
  let examined = 0;
30008
30063
  let updated = 0;
@@ -30030,7 +30085,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
30030
30085
  competitorOverlap: querySnapshots.competitorOverlap,
30031
30086
  recommendedCompetitors: querySnapshots.recommendedCompetitors,
30032
30087
  rawResponse: querySnapshots.rawResponse
30033
- }).from(querySnapshots).where(inArray11(querySnapshots.runId, batchRunIds)).all();
30088
+ }).from(querySnapshots).where(inArray12(querySnapshots.runId, batchRunIds)).all();
30034
30089
  const pendingUpdates = [];
30035
30090
  for (const snapshot of snapshotRows) {
30036
30091
  examined++;
@@ -30074,7 +30129,7 @@ function backfillProjectAnswerMentions(db, projectId, opts) {
30074
30129
  } else {
30075
30130
  db.transaction((tx) => {
30076
30131
  for (const update of pendingUpdates) {
30077
- tx.update(querySnapshots).set(update.patch).where(eq35(querySnapshots.id, update.id)).run();
30132
+ tx.update(querySnapshots).set(update.patch).where(eq36(querySnapshots.id, update.id)).run();
30078
30133
  }
30079
30134
  });
30080
30135
  updated += pendingUpdates.length;
@@ -30089,7 +30144,7 @@ async function backfillAnswerMentionsCommand(opts) {
30089
30144
  migrate(db);
30090
30145
  const projectFilter = opts?.project?.trim();
30091
30146
  const isDryRun = opts?.dryRun === true;
30092
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq35(projects.name, projectFilter)).all() : db.select().from(projects).all();
30147
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq36(projects.name, projectFilter)).all() : db.select().from(projects).all();
30093
30148
  let examined = 0;
30094
30149
  let updated = 0;
30095
30150
  let wouldUpdate = 0;
@@ -30308,7 +30363,7 @@ async function backfillSnapshotAttributionCommand(opts) {
30308
30363
  const config = loadConfig();
30309
30364
  const db = createClient(config.database);
30310
30365
  migrate(db);
30311
- const project = db.select().from(projects).where(eq35(projects.name, opts.project)).get();
30366
+ const project = db.select().from(projects).where(eq36(projects.name, opts.project)).get();
30312
30367
  if (!project) {
30313
30368
  throw new Error(`Project "${opts.project}" not found`);
30314
30369
  }
@@ -30319,17 +30374,17 @@ async function backfillSnapshotAttributionCommand(opts) {
30319
30374
  process.stderr.write(`Recovering orphan snapshot attribution for "${project.name}"${mode}...
30320
30375
  `);
30321
30376
  }
30322
- const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and28(
30323
- eq35(auditLog.projectId, project.id),
30324
- inArray11(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
30377
+ const events = db.select({ createdAt: auditLog.createdAt, action: auditLog.action, diff: auditLog.diff }).from(auditLog).where(and29(
30378
+ eq36(auditLog.projectId, project.id),
30379
+ inArray12(auditLog.action, ["keywords.appended", "keywords.deleted", "queries.appended", "queries.deleted", "queries.replaced"])
30325
30380
  )).orderBy(auditLog.createdAt).all();
30326
30381
  const history = replayQueryAuditLog(events);
30327
30382
  const orphanRuns = db.select({
30328
30383
  runId: runs.id,
30329
30384
  createdAt: runs.createdAt,
30330
30385
  location: runs.location
30331
- }).from(runs).innerJoin(querySnapshots, eq35(querySnapshots.runId, runs.id)).where(and28(
30332
- eq35(runs.projectId, project.id),
30386
+ }).from(runs).innerJoin(querySnapshots, eq36(querySnapshots.runId, runs.id)).where(and29(
30387
+ eq36(runs.projectId, project.id),
30333
30388
  isNull(querySnapshots.queryId),
30334
30389
  isNull(querySnapshots.queryText)
30335
30390
  )).groupBy(runs.id).orderBy(runs.createdAt).all();
@@ -30351,8 +30406,8 @@ async function backfillSnapshotAttributionCommand(opts) {
30351
30406
  provider: querySnapshots.provider,
30352
30407
  createdAt: querySnapshots.createdAt,
30353
30408
  answerText: querySnapshots.answerText
30354
- }).from(querySnapshots).where(and28(
30355
- eq35(querySnapshots.runId, run.runId),
30409
+ }).from(querySnapshots).where(and29(
30410
+ eq36(querySnapshots.runId, run.runId),
30356
30411
  isNull(querySnapshots.queryId),
30357
30412
  isNull(querySnapshots.queryText)
30358
30413
  )).orderBy(querySnapshots.provider, querySnapshots.createdAt).all();
@@ -30418,7 +30473,7 @@ async function backfillSnapshotAttributionCommand(opts) {
30418
30473
  if (!isDryRun && updates.length > 0) {
30419
30474
  db.transaction((tx) => {
30420
30475
  for (const u of updates) {
30421
- tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq35(querySnapshots.id, u.id)).run();
30476
+ tx.update(querySnapshots).set({ queryText: u.queryText }).where(eq36(querySnapshots.id, u.id)).run();
30422
30477
  }
30423
30478
  });
30424
30479
  }
@@ -30492,7 +30547,7 @@ async function backfillTrafficClassificationCommand(opts) {
30492
30547
  const projectFilter = opts?.project?.trim();
30493
30548
  const isDryRun = opts?.dryRun === true;
30494
30549
  const isJson = opts?.format === "json";
30495
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq35(projects.name, projectFilter)).all() : db.select().from(projects).all();
30550
+ const scopedProjects = projectFilter ? db.select().from(projects).where(eq36(projects.name, projectFilter)).all() : db.select().from(projects).all();
30496
30551
  if (scopedProjects.length === 0) {
30497
30552
  if (projectFilter && !isJson) {
30498
30553
  process.stderr.write(`No project named "${projectFilter}".
@@ -30517,9 +30572,9 @@ async function backfillTrafficClassificationCommand(opts) {
30517
30572
  dryRun: isDryRun,
30518
30573
  byBot: {}
30519
30574
  };
30520
- const unknownCountRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and28(
30521
- eq35(rawEventSamples.eventType, "unknown"),
30522
- inArray11(rawEventSamples.projectId, projectIds)
30575
+ const unknownCountRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and29(
30576
+ eq36(rawEventSamples.eventType, "unknown"),
30577
+ inArray12(rawEventSamples.projectId, projectIds)
30523
30578
  )).get();
30524
30579
  result.unknownBefore = Number(unknownCountRow?.n ?? 0);
30525
30580
  const unknownSamples = db.select({
@@ -30530,9 +30585,9 @@ async function backfillTrafficClassificationCommand(opts) {
30530
30585
  userAgent: rawEventSamples.userAgent,
30531
30586
  pathNormalized: rawEventSamples.pathNormalized,
30532
30587
  status: rawEventSamples.status
30533
- }).from(rawEventSamples).where(and28(
30534
- eq35(rawEventSamples.eventType, "unknown"),
30535
- inArray11(rawEventSamples.projectId, projectIds)
30588
+ }).from(rawEventSamples).where(and29(
30589
+ eq36(rawEventSamples.eventType, "unknown"),
30590
+ inArray12(rawEventSamples.projectId, projectIds)
30536
30591
  )).all();
30537
30592
  result.examined = unknownSamples.length;
30538
30593
  if (unknownSamples.length === 0) {
@@ -30570,7 +30625,7 @@ async function backfillTrafficClassificationCommand(opts) {
30570
30625
  result.reclassified++;
30571
30626
  result.byBot[classified.botId] = (result.byBot[classified.botId] ?? 0) + 1;
30572
30627
  if (isDryRun) continue;
30573
- db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq35(rawEventSamples.id, snap.id)).run();
30628
+ db.update(rawEventSamples).set({ eventType: userFetch ? TrafficEventKinds["ai-user-fetch"] : TrafficEventKinds.crawler }).where(eq36(rawEventSamples.id, snap.id)).run();
30574
30629
  const tsHour = new Date(snap.ts);
30575
30630
  tsHour.setUTCMinutes(0, 0, 0);
30576
30631
  if (userFetch) {
@@ -30634,9 +30689,9 @@ async function backfillTrafficClassificationCommand(opts) {
30634
30689
  }
30635
30690
  }
30636
30691
  if (!isDryRun) {
30637
- const afterRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and28(
30638
- eq35(rawEventSamples.eventType, "unknown"),
30639
- inArray11(rawEventSamples.projectId, projectIds)
30692
+ const afterRow = db.select({ n: sql15`count(*)` }).from(rawEventSamples).where(and29(
30693
+ eq36(rawEventSamples.eventType, "unknown"),
30694
+ inArray12(rawEventSamples.projectId, projectIds)
30640
30695
  )).get();
30641
30696
  result.unknownAfter = Number(afterRow?.n ?? 0);
30642
30697
  } else {
@@ -30722,8 +30777,8 @@ var ProviderRegistry = class {
30722
30777
 
30723
30778
  // src/scheduler.ts
30724
30779
  import cron from "node-cron";
30725
- import { and as and29, eq as eq36 } from "drizzle-orm";
30726
- var log9 = createLogger("Scheduler");
30780
+ import { and as and30, eq as eq37 } from "drizzle-orm";
30781
+ var log10 = createLogger("Scheduler");
30727
30782
  function taskKey(projectId, kind) {
30728
30783
  return `${projectId}::${kind}`;
30729
30784
  }
@@ -30737,16 +30792,16 @@ var Scheduler = class {
30737
30792
  }
30738
30793
  /** Load all enabled schedules from DB and register cron jobs. */
30739
30794
  start() {
30740
- const allSchedules = this.db.select().from(schedules).where(eq36(schedules.enabled, true)).all();
30795
+ const allSchedules = this.db.select().from(schedules).where(eq37(schedules.enabled, true)).all();
30741
30796
  for (const schedule of allSchedules) {
30742
30797
  const missedRunAt = schedule.nextRunAt;
30743
30798
  this.registerCronTask(schedule);
30744
30799
  if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
30745
- log9.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
30800
+ log10.info("run.catch-up", { projectId: schedule.projectId, kind: schedule.kind, missedRunAt });
30746
30801
  this.triggerRun(schedule.id, schedule.projectId, schedule.kind);
30747
30802
  }
30748
30803
  }
30749
- log9.info("started", { scheduleCount: allSchedules.length });
30804
+ log10.info("started", { scheduleCount: allSchedules.length });
30750
30805
  }
30751
30806
  /** Stop all cron tasks for graceful shutdown. */
30752
30807
  stop() {
@@ -30767,7 +30822,7 @@ var Scheduler = class {
30767
30822
  this.stopTask(key, existing, "Stopped");
30768
30823
  this.tasks.delete(key);
30769
30824
  }
30770
- const schedule = this.db.select().from(schedules).where(and29(eq36(schedules.projectId, projectId), eq36(schedules.kind, kind))).get();
30825
+ const schedule = this.db.select().from(schedules).where(and30(eq37(schedules.projectId, projectId), eq37(schedules.kind, kind))).get();
30771
30826
  if (schedule && schedule.enabled) {
30772
30827
  this.registerCronTask(schedule);
30773
30828
  }
@@ -30790,13 +30845,13 @@ var Scheduler = class {
30790
30845
  stopTask(key, task, verb) {
30791
30846
  void task.stop();
30792
30847
  void task.destroy();
30793
- log9.info(`task.${verb.toLowerCase()}`, { key });
30848
+ log10.info(`task.${verb.toLowerCase()}`, { key });
30794
30849
  }
30795
30850
  registerCronTask(schedule) {
30796
30851
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
30797
30852
  const kind = schedule.kind;
30798
30853
  if (!cron.validate(cronExpr)) {
30799
- log9.error("cron.invalid", { projectId, kind, cronExpr });
30854
+ log10.error("cron.invalid", { projectId, kind, cronExpr });
30800
30855
  return;
30801
30856
  }
30802
30857
  const task = cron.schedule(cronExpr, () => {
@@ -30808,43 +30863,43 @@ var Scheduler = class {
30808
30863
  this.db.update(schedules).set({
30809
30864
  nextRunAt: task.getNextRun()?.toISOString() ?? null,
30810
30865
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30811
- }).where(eq36(schedules.id, scheduleId)).run();
30866
+ }).where(eq37(schedules.id, scheduleId)).run();
30812
30867
  const label = schedule.preset ?? cronExpr;
30813
- log9.info("cron.registered", { projectId, kind, schedule: label, timezone });
30868
+ log10.info("cron.registered", { projectId, kind, schedule: label, timezone });
30814
30869
  }
30815
30870
  triggerRun(scheduleId, projectId, kind) {
30816
30871
  try {
30817
30872
  const now = (/* @__PURE__ */ new Date()).toISOString();
30818
- const currentSchedule = this.db.select().from(schedules).where(eq36(schedules.id, scheduleId)).get();
30873
+ const currentSchedule = this.db.select().from(schedules).where(eq37(schedules.id, scheduleId)).get();
30819
30874
  if (!currentSchedule || !currentSchedule.enabled) {
30820
- log9.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
30875
+ log10.warn("schedule.stale", { scheduleId, projectId, kind, msg: "schedule no longer exists or is disabled" });
30821
30876
  this.remove(projectId, kind);
30822
30877
  return;
30823
30878
  }
30824
30879
  const task = this.tasks.get(taskKey(projectId, kind));
30825
30880
  const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
30826
- const project = this.db.select().from(projects).where(eq36(projects.id, projectId)).get();
30881
+ const project = this.db.select().from(projects).where(eq37(projects.id, projectId)).get();
30827
30882
  if (!project) {
30828
- log9.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
30883
+ log10.error("project.not-found", { projectId, kind, msg: "skipping scheduled run" });
30829
30884
  this.remove(projectId, kind);
30830
30885
  return;
30831
30886
  }
30832
30887
  if (kind === SchedulableRunKinds["traffic-sync"]) {
30833
30888
  const sourceId = currentSchedule.sourceId;
30834
30889
  if (!sourceId) {
30835
- log9.warn("traffic-sync.missing-source", { scheduleId, projectId });
30890
+ log10.warn("traffic-sync.missing-source", { scheduleId, projectId });
30836
30891
  return;
30837
30892
  }
30838
30893
  if (!this.callbacks.onTrafficSyncRequested) {
30839
- log9.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
30894
+ log10.warn("traffic-sync.no-callback", { scheduleId, projectId, msg: "host did not register onTrafficSyncRequested" });
30840
30895
  return;
30841
30896
  }
30842
30897
  this.db.update(schedules).set({
30843
30898
  lastRunAt: now,
30844
30899
  nextRunAt,
30845
30900
  updatedAt: now
30846
- }).where(eq36(schedules.id, currentSchedule.id)).run();
30847
- log9.info("traffic-sync.triggered", { projectName: project.name, sourceId });
30901
+ }).where(eq37(schedules.id, currentSchedule.id)).run();
30902
+ log10.info("traffic-sync.triggered", { projectName: project.name, sourceId });
30848
30903
  this.callbacks.onTrafficSyncRequested(project.name, sourceId);
30849
30904
  return;
30850
30905
  }
@@ -30853,7 +30908,7 @@ var Scheduler = class {
30853
30908
  if (project.defaultLocation) {
30854
30909
  const loc = projectLocations.find((l) => l.label === project.defaultLocation);
30855
30910
  if (!loc) {
30856
- log9.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
30911
+ log10.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
30857
30912
  return;
30858
30913
  }
30859
30914
  resolvedLocation = loc;
@@ -30867,11 +30922,11 @@ var Scheduler = class {
30867
30922
  location: locationLabel
30868
30923
  });
30869
30924
  if (queueResult.conflict) {
30870
- log9.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
30925
+ log10.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
30871
30926
  this.db.update(schedules).set({
30872
30927
  nextRunAt,
30873
30928
  updatedAt: now
30874
- }).where(eq36(schedules.id, currentSchedule.id)).run();
30929
+ }).where(eq37(schedules.id, currentSchedule.id)).run();
30875
30930
  return;
30876
30931
  }
30877
30932
  const runId = queueResult.runId;
@@ -30879,21 +30934,21 @@ var Scheduler = class {
30879
30934
  lastRunAt: now,
30880
30935
  nextRunAt,
30881
30936
  updatedAt: now
30882
- }).where(eq36(schedules.id, currentSchedule.id)).run();
30937
+ }).where(eq37(schedules.id, currentSchedule.id)).run();
30883
30938
  const scheduleProviders = currentSchedule.providers;
30884
30939
  const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
30885
- log9.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
30940
+ log10.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
30886
30941
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
30887
30942
  } catch (err) {
30888
- log9.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
30943
+ log10.error("trigger.error", { scheduleId, projectId, kind, error: err instanceof Error ? err.message : String(err) });
30889
30944
  }
30890
30945
  }
30891
30946
  };
30892
30947
 
30893
30948
  // src/notifier.ts
30894
- import { eq as eq37, desc as desc17, and as and30, inArray as inArray12, or as or5 } from "drizzle-orm";
30895
- import crypto32 from "crypto";
30896
- var log10 = createLogger("Notifier");
30949
+ import { eq as eq38, desc as desc18, and as and31, inArray as inArray13, or as or5 } from "drizzle-orm";
30950
+ import crypto33 from "crypto";
30951
+ var log11 = createLogger("Notifier");
30897
30952
  var Notifier = class {
30898
30953
  db;
30899
30954
  serverUrl;
@@ -30903,26 +30958,26 @@ var Notifier = class {
30903
30958
  }
30904
30959
  /** Called after a run completes (success, partial, or failed). */
30905
30960
  async onRunCompleted(runId, projectId) {
30906
- log10.info("run.completed", { runId, projectId });
30907
- const notifs = this.db.select().from(notifications).where(eq37(notifications.projectId, projectId)).all().filter((n) => n.enabled);
30961
+ log11.info("run.completed", { runId, projectId });
30962
+ const notifs = this.db.select().from(notifications).where(eq38(notifications.projectId, projectId)).all().filter((n) => n.enabled);
30908
30963
  if (notifs.length === 0) {
30909
- log10.info("notifications.none-enabled", { projectId });
30964
+ log11.info("notifications.none-enabled", { projectId });
30910
30965
  return;
30911
30966
  }
30912
- log10.info("notifications.found", { projectId, count: notifs.length });
30913
- const run = this.db.select().from(runs).where(eq37(runs.id, runId)).get();
30967
+ log11.info("notifications.found", { projectId, count: notifs.length });
30968
+ const run = this.db.select().from(runs).where(eq38(runs.id, runId)).get();
30914
30969
  if (!run) {
30915
- log10.error("run.not-found", { runId, msg: "skipping notification dispatch" });
30970
+ log11.error("run.not-found", { runId, msg: "skipping notification dispatch" });
30916
30971
  return;
30917
30972
  }
30918
- const project = this.db.select().from(projects).where(eq37(projects.id, projectId)).get();
30973
+ const project = this.db.select().from(projects).where(eq38(projects.id, projectId)).get();
30919
30974
  if (!project) {
30920
- log10.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
30975
+ log11.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
30921
30976
  return;
30922
30977
  }
30923
30978
  const transitions = this.computeTransitions(runId, projectId);
30924
30979
  const events = [];
30925
- log10.info("run.status", { runId: run.id, status: run.status, projectId });
30980
+ log11.info("run.status", { runId: run.id, status: run.status, projectId });
30926
30981
  if (run.status === "completed" || run.status === "partial") {
30927
30982
  events.push("run.completed");
30928
30983
  }
@@ -30938,7 +30993,7 @@ var Notifier = class {
30938
30993
  if (!config.url) continue;
30939
30994
  const subscribedEvents = config.events;
30940
30995
  const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
30941
- log10.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
30996
+ log11.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
30942
30997
  if (matchingEvents.length === 0) continue;
30943
30998
  for (const event of matchingEvents) {
30944
30999
  const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
@@ -30962,11 +31017,11 @@ var Notifier = class {
30962
31017
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
30963
31018
  if (highInsights.length > 0) insightEvents.push("insight.high");
30964
31019
  if (insightEvents.length === 0) return;
30965
- const notifs = this.db.select().from(notifications).where(eq37(notifications.projectId, projectId)).all().filter((n) => n.enabled);
31020
+ const notifs = this.db.select().from(notifications).where(eq38(notifications.projectId, projectId)).all().filter((n) => n.enabled);
30966
31021
  if (notifs.length === 0) return;
30967
- const run = this.db.select().from(runs).where(eq37(runs.id, runId)).get();
31022
+ const run = this.db.select().from(runs).where(eq38(runs.id, runId)).get();
30968
31023
  if (!run) return;
30969
- const project = this.db.select().from(projects).where(eq37(projects.id, projectId)).get();
31024
+ const project = this.db.select().from(projects).where(eq38(projects.id, projectId)).get();
30970
31025
  if (!project) return;
30971
31026
  for (const notif of notifs) {
30972
31027
  const config = notif.config;
@@ -30996,12 +31051,12 @@ var Notifier = class {
30996
31051
  }
30997
31052
  }
30998
31053
  computeTransitions(runId, projectId) {
30999
- const thisRun = this.db.select().from(runs).where(eq37(runs.id, runId)).get();
31054
+ const thisRun = this.db.select().from(runs).where(eq38(runs.id, runId)).get();
31000
31055
  if (!thisRun) return [];
31001
- const groupSiblings = this.db.select().from(runs).where(and30(
31002
- eq37(runs.projectId, projectId),
31003
- eq37(runs.kind, thisRun.kind),
31004
- eq37(runs.createdAt, thisRun.createdAt)
31056
+ const groupSiblings = this.db.select().from(runs).where(and31(
31057
+ eq38(runs.projectId, projectId),
31058
+ eq38(runs.kind, thisRun.kind),
31059
+ eq38(runs.createdAt, thisRun.createdAt)
31005
31060
  )).all();
31006
31061
  const stillPending = groupSiblings.some((r) => r.status === "queued" || r.status === "running");
31007
31062
  if (stillPending) return [];
@@ -31017,19 +31072,19 @@ var Notifier = class {
31017
31072
  return candidate.id > best.id ? candidate : best;
31018
31073
  });
31019
31074
  if (winner.id !== runId) return [];
31020
- const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq37(projects.id, projectId)).get();
31075
+ const projectLocations = this.db.select({ locations: projects.locations }).from(projects).where(eq38(projects.id, projectId)).get();
31021
31076
  const locationCount = Math.max(
31022
31077
  1,
31023
31078
  (projectLocations?.locations ?? []).length
31024
31079
  );
31025
31080
  const RECENT_FETCH_LIMIT = Math.max(8, locationCount * 4);
31026
31081
  const recentRuns = this.db.select().from(runs).where(
31027
- and30(
31028
- eq37(runs.projectId, projectId),
31029
- eq37(runs.kind, thisRun.kind),
31030
- or5(eq37(runs.status, "completed"), eq37(runs.status, "partial"))
31082
+ and31(
31083
+ eq38(runs.projectId, projectId),
31084
+ eq38(runs.kind, thisRun.kind),
31085
+ or5(eq38(runs.status, "completed"), eq38(runs.status, "partial"))
31031
31086
  )
31032
- ).orderBy(desc17(runs.createdAt), desc17(runs.id)).limit(RECENT_FETCH_LIMIT).all();
31087
+ ).orderBy(desc18(runs.createdAt), desc18(runs.id)).limit(RECENT_FETCH_LIMIT).all();
31033
31088
  const groups = groupRunsByCreatedAt(recentRuns);
31034
31089
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
31035
31090
  if (currentGroupIdx < 0) return [];
@@ -31044,13 +31099,13 @@ var Notifier = class {
31044
31099
  provider: querySnapshots.provider,
31045
31100
  location: querySnapshots.location,
31046
31101
  citationState: querySnapshots.citationState
31047
- }).from(querySnapshots).leftJoin(queries, eq37(querySnapshots.queryId, queries.id)).where(inArray12(querySnapshots.runId, currentRunIds)).all();
31102
+ }).from(querySnapshots).leftJoin(queries, eq38(querySnapshots.queryId, queries.id)).where(inArray13(querySnapshots.runId, currentRunIds)).all();
31048
31103
  const previousSnapshots = this.db.select({
31049
31104
  queryId: querySnapshots.queryId,
31050
31105
  provider: querySnapshots.provider,
31051
31106
  location: querySnapshots.location,
31052
31107
  citationState: querySnapshots.citationState
31053
- }).from(querySnapshots).where(inArray12(querySnapshots.runId, previousRunIds)).all();
31108
+ }).from(querySnapshots).where(inArray13(querySnapshots.runId, previousRunIds)).all();
31054
31109
  const prevMap = /* @__PURE__ */ new Map();
31055
31110
  for (const s of previousSnapshots) {
31056
31111
  if (s.queryId == null) continue;
@@ -31077,23 +31132,23 @@ var Notifier = class {
31077
31132
  const targetLabel = redactNotificationUrl(url).urlDisplay;
31078
31133
  const targetCheck = await resolveWebhookTarget(url);
31079
31134
  if (!targetCheck.ok) {
31080
- log10.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
31135
+ log11.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
31081
31136
  this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
31082
31137
  return;
31083
31138
  }
31084
- log10.info("webhook.send", { event: payload.event, url: targetLabel });
31139
+ log11.info("webhook.send", { event: payload.event, url: targetLabel });
31085
31140
  const maxRetries = 3;
31086
31141
  const delays = [1e3, 4e3, 16e3];
31087
31142
  for (let attempt = 0; attempt < maxRetries; attempt++) {
31088
31143
  try {
31089
31144
  const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
31090
31145
  if (response.status >= 200 && response.status < 300) {
31091
- log10.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
31146
+ log11.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
31092
31147
  this.logDelivery(projectId, notificationId, payload.event, "sent", null);
31093
31148
  return;
31094
31149
  }
31095
31150
  const errorDetail = response.error ?? `HTTP ${response.status}`;
31096
- log10.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
31151
+ log11.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
31097
31152
  if (attempt === maxRetries - 1) {
31098
31153
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
31099
31154
  }
@@ -31101,7 +31156,7 @@ var Notifier = class {
31101
31156
  const errorDetail = err instanceof Error ? err.message : String(err);
31102
31157
  if (attempt === maxRetries - 1) {
31103
31158
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
31104
- log10.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
31159
+ log11.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
31105
31160
  }
31106
31161
  }
31107
31162
  if (attempt < maxRetries - 1) {
@@ -31111,7 +31166,7 @@ var Notifier = class {
31111
31166
  }
31112
31167
  logDelivery(projectId, notificationId, event, status, error) {
31113
31168
  this.db.insert(auditLog).values({
31114
- id: crypto32.randomUUID(),
31169
+ id: crypto33.randomUUID(),
31115
31170
  projectId,
31116
31171
  actor: "scheduler",
31117
31172
  action: `notification.${status}`,
@@ -31124,8 +31179,8 @@ var Notifier = class {
31124
31179
  };
31125
31180
 
31126
31181
  // src/run-coordinator.ts
31127
- import { eq as eq38 } from "drizzle-orm";
31128
- var log11 = createLogger("RunCoordinator");
31182
+ import { eq as eq39 } from "drizzle-orm";
31183
+ var log12 = createLogger("RunCoordinator");
31129
31184
  var RunCoordinator = class {
31130
31185
  constructor(db, notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
31131
31186
  this.db = db;
@@ -31135,10 +31190,10 @@ var RunCoordinator = class {
31135
31190
  this.onAeroEvent = onAeroEvent;
31136
31191
  }
31137
31192
  async onRunCompleted(runId, projectId) {
31138
- const runRow = this.db.select().from(runs).where(eq38(runs.id, runId)).get();
31193
+ const runRow = this.db.select().from(runs).where(eq39(runs.id, runId)).get();
31139
31194
  const kind = runRow?.kind ?? RunKinds["answer-visibility"];
31140
31195
  if (runRow?.trigger === RunTriggers.probe) {
31141
- log11.info("probe.skip-side-effects", { runId, projectId, kind });
31196
+ log12.info("probe.skip-side-effects", { runId, projectId, kind });
31142
31197
  return;
31143
31198
  }
31144
31199
  let insightCount = 0;
@@ -31155,18 +31210,18 @@ var RunCoordinator = class {
31155
31210
  try {
31156
31211
  await this.onInsightsGenerated(runId, projectId, result);
31157
31212
  } catch (err) {
31158
- log11.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31213
+ log12.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31159
31214
  }
31160
31215
  }
31161
31216
  }
31162
31217
  } catch (err) {
31163
- log11.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31218
+ log12.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31164
31219
  }
31165
31220
  }
31166
31221
  try {
31167
31222
  await this.notifier.onRunCompleted(runId, projectId);
31168
31223
  } catch (err) {
31169
- log11.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31224
+ log12.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31170
31225
  }
31171
31226
  if (this.onAeroEvent) {
31172
31227
  try {
@@ -31179,7 +31234,7 @@ var RunCoordinator = class {
31179
31234
  };
31180
31235
  await this.onAeroEvent(ctx);
31181
31236
  } catch (err) {
31182
- log11.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31237
+ log12.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
31183
31238
  }
31184
31239
  }
31185
31240
  }
@@ -31194,7 +31249,7 @@ var RunCoordinator = class {
31194
31249
  * so the Aero queue is never starved of a follow-up.
31195
31250
  */
31196
31251
  buildDiscoveryAeroContext(runId, projectId, status, error) {
31197
- const session = this.db.select().from(discoverySessions).where(eq38(discoverySessions.runId, runId)).get();
31252
+ const session = this.db.select().from(discoverySessions).where(eq39(discoverySessions.runId, runId)).get();
31198
31253
  const competitorMap = session ? session.competitorMap : [];
31199
31254
  return {
31200
31255
  kind: RunKinds["aeo-discover-probe"],
@@ -31216,8 +31271,8 @@ var RunCoordinator = class {
31216
31271
  };
31217
31272
 
31218
31273
  // src/agent/session-registry.ts
31219
- import crypto34 from "crypto";
31220
- import { eq as eq40 } from "drizzle-orm";
31274
+ import crypto35 from "crypto";
31275
+ import { eq as eq41 } from "drizzle-orm";
31221
31276
 
31222
31277
  // src/agent/session.ts
31223
31278
  import fs14 from "fs";
@@ -31605,8 +31660,8 @@ function resolveSessionProviderAndModel(config, opts) {
31605
31660
  }
31606
31661
 
31607
31662
  // src/agent/memory-store.ts
31608
- import crypto33 from "crypto";
31609
- import { and as and31, desc as desc18, eq as eq39, like as like2, sql as sql16 } from "drizzle-orm";
31663
+ import crypto34 from "crypto";
31664
+ import { and as and32, desc as desc19, eq as eq40, like as like2, sql as sql16 } from "drizzle-orm";
31610
31665
  var COMPACTION_KEY_PREFIX = "compaction:";
31611
31666
  var COMPACTION_NOTES_PER_SESSION = 3;
31612
31667
  function rowToDto2(row) {
@@ -31620,7 +31675,7 @@ function rowToDto2(row) {
31620
31675
  };
31621
31676
  }
31622
31677
  function listMemoryEntries(db, projectId, opts = {}) {
31623
- const query = db.select().from(agentMemory).where(eq39(agentMemory.projectId, projectId)).orderBy(desc18(agentMemory.updatedAt));
31678
+ const query = db.select().from(agentMemory).where(eq40(agentMemory.projectId, projectId)).orderBy(desc19(agentMemory.updatedAt));
31624
31679
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
31625
31680
  return rows.map(rowToDto2);
31626
31681
  }
@@ -31634,7 +31689,7 @@ function upsertMemoryEntry(db, args) {
31634
31689
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
31635
31690
  }
31636
31691
  const now = (/* @__PURE__ */ new Date()).toISOString();
31637
- const id = crypto33.randomUUID();
31692
+ const id = crypto34.randomUUID();
31638
31693
  db.insert(agentMemory).values({
31639
31694
  id,
31640
31695
  projectId: args.projectId,
@@ -31651,12 +31706,12 @@ function upsertMemoryEntry(db, args) {
31651
31706
  updatedAt: now
31652
31707
  }
31653
31708
  }).run();
31654
- const row = db.select().from(agentMemory).where(and31(eq39(agentMemory.projectId, args.projectId), eq39(agentMemory.key, args.key))).get();
31709
+ const row = db.select().from(agentMemory).where(and32(eq40(agentMemory.projectId, args.projectId), eq40(agentMemory.key, args.key))).get();
31655
31710
  if (!row) throw new Error("memory upsert produced no row");
31656
31711
  return rowToDto2(row);
31657
31712
  }
31658
31713
  function deleteMemoryEntry(db, projectId, key) {
31659
- const result = db.delete(agentMemory).where(and31(eq39(agentMemory.projectId, projectId), eq39(agentMemory.key, key))).run();
31714
+ const result = db.delete(agentMemory).where(and32(eq40(agentMemory.projectId, projectId), eq40(agentMemory.key, key))).run();
31660
31715
  const changes = result.changes ?? 0;
31661
31716
  return changes > 0;
31662
31717
  }
@@ -31671,7 +31726,7 @@ function writeCompactionNote(db, args) {
31671
31726
  }
31672
31727
  const now = (/* @__PURE__ */ new Date()).toISOString();
31673
31728
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
31674
- const id = crypto33.randomUUID();
31729
+ const id = crypto34.randomUUID();
31675
31730
  let inserted;
31676
31731
  db.transaction((tx) => {
31677
31732
  tx.insert(agentMemory).values({
@@ -31685,16 +31740,16 @@ function writeCompactionNote(db, args) {
31685
31740
  }).run();
31686
31741
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
31687
31742
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
31688
- and31(
31689
- eq39(agentMemory.projectId, args.projectId),
31743
+ and32(
31744
+ eq40(agentMemory.projectId, args.projectId),
31690
31745
  like2(agentMemory.key, `${sessionPrefix}%`)
31691
31746
  )
31692
- ).orderBy(desc18(agentMemory.updatedAt)).all();
31747
+ ).orderBy(desc19(agentMemory.updatedAt)).all();
31693
31748
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
31694
31749
  if (stale.length > 0) {
31695
31750
  tx.delete(agentMemory).where(sql16`${agentMemory.id} IN (${sql16.join(stale.map((s) => sql16`${s}`), sql16`, `)})`).run();
31696
31751
  }
31697
- const row = tx.select().from(agentMemory).where(and31(eq39(agentMemory.projectId, args.projectId), eq39(agentMemory.key, key))).get();
31752
+ const row = tx.select().from(agentMemory).where(and32(eq40(agentMemory.projectId, args.projectId), eq40(agentMemory.key, key))).get();
31698
31753
  if (row) inserted = rowToDto2(row);
31699
31754
  });
31700
31755
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -31827,7 +31882,7 @@ async function compactMessages(args) {
31827
31882
  }
31828
31883
 
31829
31884
  // src/agent/session-registry.ts
31830
- var log12 = createLogger("SessionRegistry");
31885
+ var log13 = createLogger("SessionRegistry");
31831
31886
  var MAX_HYDRATE_NOTES = 20;
31832
31887
  var MAX_HYDRATE_BYTES = 32 * 1024;
31833
31888
  function escapeMemoryFragment(value) {
@@ -31876,7 +31931,7 @@ var SessionRegistry = class {
31876
31931
  modelProvider: effectiveProvider,
31877
31932
  modelId: effectiveModelId,
31878
31933
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
31879
- }).where(eq40(agentSessions.projectId, projectId)).run();
31934
+ }).where(eq41(agentSessions.projectId, projectId)).run();
31880
31935
  }
31881
31936
  const agent2 = createAeroSession({
31882
31937
  projectName,
@@ -32054,13 +32109,13 @@ ${lines.join("\n")}
32054
32109
  agent.state.messages = result.messages;
32055
32110
  agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
32056
32111
  this.save(projectName);
32057
- log12.info("compaction.completed", {
32112
+ log13.info("compaction.completed", {
32058
32113
  projectName,
32059
32114
  removedCount: result.removedCount,
32060
32115
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
32061
32116
  });
32062
32117
  } catch (err) {
32063
- log12.error("compaction.failed", {
32118
+ log13.error("compaction.failed", {
32064
32119
  projectName,
32065
32120
  error: err instanceof Error ? err.message : String(err)
32066
32121
  });
@@ -32090,7 +32145,7 @@ ${lines.join("\n")}
32090
32145
  modelProvider: nextProvider,
32091
32146
  modelId: nextModelId,
32092
32147
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
32093
- }).where(eq40(agentSessions.projectId, projectId)).run();
32148
+ }).where(eq41(agentSessions.projectId, projectId)).run();
32094
32149
  }
32095
32150
  /** Persist a session's transcript back to the DB. Call after any run settles. */
32096
32151
  save(projectName) {
@@ -32157,7 +32212,7 @@ ${lines.join("\n")}
32157
32212
  await agent.prompt(msgs);
32158
32213
  this.save(projectName);
32159
32214
  } catch (err) {
32160
- log12.error("drain.failed", {
32215
+ log13.error("drain.failed", {
32161
32216
  projectName,
32162
32217
  error: err instanceof Error ? err.message : String(err)
32163
32218
  });
@@ -32252,17 +32307,17 @@ ${lines.join("\n")}
32252
32307
  return id;
32253
32308
  }
32254
32309
  tryResolveProjectId(projectName) {
32255
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq40(projects.name, projectName)).get();
32310
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq41(projects.name, projectName)).get();
32256
32311
  return row?.id;
32257
32312
  }
32258
32313
  loadRow(projectId) {
32259
- const row = this.opts.db.select().from(agentSessions).where(eq40(agentSessions.projectId, projectId)).get();
32314
+ const row = this.opts.db.select().from(agentSessions).where(eq41(agentSessions.projectId, projectId)).get();
32260
32315
  return row ?? null;
32261
32316
  }
32262
32317
  insertRow(params) {
32263
32318
  const now = (/* @__PURE__ */ new Date()).toISOString();
32264
32319
  this.opts.db.insert(agentSessions).values({
32265
- id: crypto34.randomUUID(),
32320
+ id: crypto35.randomUUID(),
32266
32321
  projectId: params.projectId,
32267
32322
  systemPrompt: params.systemPrompt,
32268
32323
  modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
@@ -32275,14 +32330,14 @@ ${lines.join("\n")}
32275
32330
  }
32276
32331
  updateRow(projectId, patch) {
32277
32332
  const now = (/* @__PURE__ */ new Date()).toISOString();
32278
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq40(agentSessions.projectId, projectId)).run();
32333
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq41(agentSessions.projectId, projectId)).run();
32279
32334
  }
32280
32335
  };
32281
32336
 
32282
32337
  // src/agent/agent-routes.ts
32283
- import { eq as eq41 } from "drizzle-orm";
32338
+ import { eq as eq42 } from "drizzle-orm";
32284
32339
  function resolveProject2(db, name) {
32285
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq41(projects.name, name)).get();
32340
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq42(projects.name, name)).get();
32286
32341
  if (!row) throw notFound("project", name);
32287
32342
  return row;
32288
32343
  }
@@ -32291,7 +32346,7 @@ function registerAgentRoutes(app, opts) {
32291
32346
  "/projects/:name/agent/transcript",
32292
32347
  async (request) => {
32293
32348
  const project = resolveProject2(opts.db, request.params.name);
32294
- const row = opts.db.select().from(agentSessions).where(eq41(agentSessions.projectId, project.id)).get();
32349
+ const row = opts.db.select().from(agentSessions).where(eq42(agentSessions.projectId, project.id)).get();
32295
32350
  if (!row) {
32296
32351
  return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
32297
32352
  }
@@ -32315,7 +32370,7 @@ function registerAgentRoutes(app, opts) {
32315
32370
  async (request) => {
32316
32371
  const project = resolveProject2(opts.db, request.params.name);
32317
32372
  opts.sessionRegistry.reset(project.name);
32318
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq41(agentSessions.projectId, project.id)).run();
32373
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq42(agentSessions.projectId, project.id)).run();
32319
32374
  return { status: "reset" };
32320
32375
  }
32321
32376
  );
@@ -32672,7 +32727,7 @@ function formatAuditFactorScore(factor) {
32672
32727
  }
32673
32728
 
32674
32729
  // src/snapshot-service.ts
32675
- var log13 = createLogger("Snapshot");
32730
+ var log14 = createLogger("Snapshot");
32676
32731
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
32677
32732
  var SNAPSHOT_QUERY_COUNT = 6;
32678
32733
  var ProviderExecutionGate2 = class {
@@ -32815,7 +32870,7 @@ var SnapshotService = class {
32815
32870
  return mapAuditReport(report);
32816
32871
  } catch (err) {
32817
32872
  const message = err instanceof Error ? err.message : String(err);
32818
- log13.warn("audit.failed", { homepageUrl, error: message });
32873
+ log14.warn("audit.failed", { homepageUrl, error: message });
32819
32874
  return {
32820
32875
  url: homepageUrl,
32821
32876
  finalUrl: homepageUrl,
@@ -32845,7 +32900,7 @@ var SnapshotService = class {
32845
32900
  queries: parsedQueries
32846
32901
  };
32847
32902
  } catch (err) {
32848
- log13.warn("profile.generation-failed", {
32903
+ log14.warn("profile.generation-failed", {
32849
32904
  domain: ctx.domain,
32850
32905
  provider: ctx.analysisProvider.adapter.name,
32851
32906
  error: err instanceof Error ? err.message : String(err)
@@ -32987,7 +33042,7 @@ var SnapshotService = class {
32987
33042
  recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
32988
33043
  };
32989
33044
  } catch (err) {
32990
- log13.warn("response.analysis-failed", {
33045
+ log14.warn("response.analysis-failed", {
32991
33046
  provider: ctx.analysisProvider.adapter.name,
32992
33047
  error: err instanceof Error ? err.message : String(err)
32993
33048
  });
@@ -33272,7 +33327,7 @@ function clipText(value, length) {
33272
33327
  // src/server.ts
33273
33328
  var _require3 = createRequire4(import.meta.url);
33274
33329
  var { version: PKG_VERSION2 } = _require3("../package.json");
33275
- var log14 = createLogger("Server");
33330
+ var log15 = createLogger("Server");
33276
33331
  var DEFAULT_QUOTA = {
33277
33332
  maxConcurrency: 2,
33278
33333
  maxRequestsPerMinute: 10,
@@ -33302,14 +33357,14 @@ function summarizeProviderConfig(provider, config) {
33302
33357
  };
33303
33358
  }
33304
33359
  function hashApiKey(key) {
33305
- return crypto35.createHash("sha256").update(key).digest("hex");
33360
+ return crypto36.createHash("sha256").update(key).digest("hex");
33306
33361
  }
33307
33362
  var DASHBOARD_SCRYPT_KEYLEN = 64;
33308
33363
  var DASHBOARD_SCRYPT_COST = 1 << 15;
33309
33364
  var DASHBOARD_SCRYPT_MAXMEM = 64 * 1024 * 1024;
33310
33365
  function hashDashboardPassword(password) {
33311
- const salt = crypto35.randomBytes(16);
33312
- const derived = crypto35.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
33366
+ const salt = crypto36.randomBytes(16);
33367
+ const derived = crypto36.scryptSync(password, salt, DASHBOARD_SCRYPT_KEYLEN, {
33313
33368
  N: DASHBOARD_SCRYPT_COST,
33314
33369
  maxmem: DASHBOARD_SCRYPT_MAXMEM
33315
33370
  });
@@ -33330,18 +33385,18 @@ function verifyDashboardPassword(password, storedHash) {
33330
33385
  } catch {
33331
33386
  return { ok: false, needsRehash: false };
33332
33387
  }
33333
- const derived = crypto35.scryptSync(password, salt, expected.length, {
33388
+ const derived = crypto36.scryptSync(password, salt, expected.length, {
33334
33389
  N: DASHBOARD_SCRYPT_COST,
33335
33390
  maxmem: DASHBOARD_SCRYPT_MAXMEM
33336
33391
  });
33337
33392
  if (derived.length !== expected.length) return { ok: false, needsRehash: false };
33338
- return { ok: crypto35.timingSafeEqual(derived, expected), needsRehash: false };
33393
+ return { ok: crypto36.timingSafeEqual(derived, expected), needsRehash: false };
33339
33394
  }
33340
33395
  if (/^[a-f0-9]{64}$/i.test(storedHash)) {
33341
33396
  const candidate = Buffer.from(hashApiKey(password), "hex");
33342
33397
  const expected = Buffer.from(storedHash, "hex");
33343
33398
  if (candidate.length !== expected.length) return { ok: false, needsRehash: false };
33344
- const ok = crypto35.timingSafeEqual(candidate, expected);
33399
+ const ok = crypto36.timingSafeEqual(candidate, expected);
33345
33400
  return { ok, needsRehash: ok };
33346
33401
  }
33347
33402
  return { ok: false, needsRehash: false };
@@ -33400,7 +33455,7 @@ function applyLegacyCredentials(rows, config) {
33400
33455
  }
33401
33456
  if (migratedGoogle > 0) {
33402
33457
  saveConfigPatch({ google: config.google });
33403
- log14.info("credentials.migrated", { type: "google", count: migratedGoogle });
33458
+ log15.info("credentials.migrated", { type: "google", count: migratedGoogle });
33404
33459
  }
33405
33460
  let migratedGa4 = 0;
33406
33461
  for (const row of rows.ga4) {
@@ -33418,7 +33473,7 @@ function applyLegacyCredentials(rows, config) {
33418
33473
  }
33419
33474
  if (migratedGa4 > 0) {
33420
33475
  saveConfigPatch({ ga4: config.ga4 });
33421
- log14.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
33476
+ log15.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
33422
33477
  }
33423
33478
  }
33424
33479
  async function createServer(opts) {
@@ -33450,11 +33505,11 @@ async function createServer(opts) {
33450
33505
  applyLegacyCredentials(legacyRows, opts.config);
33451
33506
  dropLegacyCredentialColumns(opts.db);
33452
33507
  } catch (err) {
33453
- log14.warn("credentials.migration.failed", {
33508
+ log15.warn("credentials.migration.failed", {
33454
33509
  error: err instanceof Error ? err.message : String(err)
33455
33510
  });
33456
33511
  }
33457
- log14.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
33512
+ log15.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
33458
33513
  const p = providers[k];
33459
33514
  return p?.apiKey || p?.baseUrl || p?.vertexProject;
33460
33515
  }) });
@@ -33503,7 +33558,7 @@ async function createServer(opts) {
33503
33558
  intelligenceService,
33504
33559
  (runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
33505
33560
  async (ctx) => {
33506
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq42(projects.id, ctx.projectId)).get();
33561
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq43(projects.id, ctx.projectId)).get();
33507
33562
  if (!project) return;
33508
33563
  let content;
33509
33564
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -33660,7 +33715,7 @@ async function createServer(opts) {
33660
33715
  return removed;
33661
33716
  }
33662
33717
  };
33663
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto35.randomBytes(32).toString("hex");
33718
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto36.randomBytes(32).toString("hex");
33664
33719
  const googleConnectionStore = {
33665
33720
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
33666
33721
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -33706,11 +33761,11 @@ async function createServer(opts) {
33706
33761
  const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
33707
33762
  if (opts.config.apiKey) {
33708
33763
  const keyHash = hashApiKey(opts.config.apiKey);
33709
- const existing = opts.db.select().from(apiKeys).where(eq42(apiKeys.keyHash, keyHash)).get();
33764
+ const existing = opts.db.select().from(apiKeys).where(eq43(apiKeys.keyHash, keyHash)).get();
33710
33765
  if (!existing) {
33711
33766
  const prefix = opts.config.apiKey.slice(0, 12);
33712
33767
  opts.db.insert(apiKeys).values({
33713
- id: `key_${crypto35.randomBytes(8).toString("hex")}`,
33768
+ id: `key_${crypto36.randomBytes(8).toString("hex")}`,
33714
33769
  name: "default",
33715
33770
  keyHash,
33716
33771
  keyPrefix: prefix,
@@ -33734,7 +33789,7 @@ async function createServer(opts) {
33734
33789
  };
33735
33790
  const createSession = (apiKeyId) => {
33736
33791
  pruneExpiredSessions();
33737
- const sessionId = crypto35.randomBytes(32).toString("hex");
33792
+ const sessionId = crypto36.randomBytes(32).toString("hex");
33738
33793
  sessions.set(sessionId, {
33739
33794
  apiKeyId,
33740
33795
  expiresAt: Date.now() + SESSION_TTL_MS
@@ -33758,7 +33813,7 @@ async function createServer(opts) {
33758
33813
  };
33759
33814
  const getDefaultApiKey = () => {
33760
33815
  if (!opts.config.apiKey) return void 0;
33761
- return opts.db.select().from(apiKeys).where(eq42(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
33816
+ return opts.db.select().from(apiKeys).where(eq43(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
33762
33817
  };
33763
33818
  const createPasswordSession = (reply) => {
33764
33819
  const key = getDefaultApiKey();
@@ -33820,12 +33875,12 @@ async function createServer(opts) {
33820
33875
  return reply.send({ authenticated: true });
33821
33876
  }
33822
33877
  if (apiKey) {
33823
- const key = opts.db.select().from(apiKeys).where(eq42(apiKeys.keyHash, hashApiKey(apiKey))).get();
33878
+ const key = opts.db.select().from(apiKeys).where(eq43(apiKeys.keyHash, hashApiKey(apiKey))).get();
33824
33879
  if (!key || key.revokedAt) {
33825
33880
  const err2 = authInvalid();
33826
33881
  return reply.status(err2.statusCode).send(err2.toJSON());
33827
33882
  }
33828
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq42(apiKeys.id, key.id)).run();
33883
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq43(apiKeys.id, key.id)).run();
33829
33884
  const sessionId = createSession(key.id);
33830
33885
  reply.header("set-cookie", serializeSessionCookie({
33831
33886
  name: SESSION_COOKIE_NAME,
@@ -33923,7 +33978,7 @@ async function createServer(opts) {
33923
33978
  executeGscSync(opts.db, runId, projectId, {
33924
33979
  ...syncOpts,
33925
33980
  config: opts.config
33926
- }).catch((err) => {
33981
+ }).then(() => maybeRefreshGscCoverage(opts.db, opts.config, projectId)).catch((err) => {
33927
33982
  app.log.error({ runId, err }, "GSC sync failed");
33928
33983
  });
33929
33984
  },
@@ -33961,7 +34016,7 @@ async function createServer(opts) {
33961
34016
  deps: {
33962
34017
  enqueueAutoExtract: ({ projectId, release: r }) => {
33963
34018
  const now = (/* @__PURE__ */ new Date()).toISOString();
33964
- const runId = crypto35.randomUUID();
34019
+ const runId = crypto36.randomUUID();
33965
34020
  opts.db.insert(runs).values({
33966
34021
  id: runId,
33967
34022
  projectId,
@@ -34043,6 +34098,12 @@ async function createServer(opts) {
34043
34098
  executeBingInspectSitemap(opts.db, runId, projectId, {
34044
34099
  ...inspectOpts,
34045
34100
  config: opts.config
34101
+ }).then(() => {
34102
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq43(runs.id, runId)).get();
34103
+ if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
34104
+ return maybeRefreshGscCoverage(opts.db, opts.config, projectId);
34105
+ }
34106
+ return null;
34046
34107
  }).catch((err) => {
34047
34108
  app.log.error({ runId, err }, "Bing inspect sitemap failed");
34048
34109
  });
@@ -34125,7 +34186,7 @@ async function createServer(opts) {
34125
34186
  const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
34126
34187
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
34127
34188
  opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
34128
- id: crypto35.randomUUID(),
34189
+ id: crypto36.randomUUID(),
34129
34190
  projectId,
34130
34191
  actor: "api",
34131
34192
  action: existing ? "provider.updated" : "provider.created",