@ainyc/canonry 2.4.5 → 2.5.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.
@@ -346,11 +346,11 @@ function printCliError(err, format) {
346
346
 
347
347
  // src/server.ts
348
348
  import { createRequire as createRequire3 } from "module";
349
- import crypto27 from "crypto";
349
+ import crypto28 from "crypto";
350
350
  import fs13 from "fs";
351
351
  import path15 from "path";
352
352
  import { fileURLToPath as fileURLToPath2 } from "url";
353
- import { eq as eq29 } from "drizzle-orm";
353
+ import { eq as eq30 } from "drizzle-orm";
354
354
  import Fastify from "fastify";
355
355
 
356
356
  // ../contracts/src/config-schema.ts
@@ -957,6 +957,7 @@ var runKindSchema = z8.enum([
957
957
  "inspect-sitemap",
958
958
  "ga-sync",
959
959
  "bing-inspect",
960
+ "bing-inspect-sitemap",
960
961
  "backlink-extract"
961
962
  ]);
962
963
  var RunKinds = runKindSchema.enum;
@@ -5072,6 +5073,31 @@ var routeCatalog = [
5072
5073
  404: { description: "Project or connection not found." }
5073
5074
  }
5074
5075
  },
5076
+ {
5077
+ method: "post",
5078
+ path: "/api/v1/projects/{name}/bing/inspect-sitemap",
5079
+ summary: "Inspect every URL in a sitemap through Bing Webmaster Tools",
5080
+ tags: ["bing"],
5081
+ parameters: [nameParameter],
5082
+ requestBody: {
5083
+ required: false,
5084
+ content: {
5085
+ "application/json": {
5086
+ schema: {
5087
+ type: "object",
5088
+ properties: {
5089
+ sitemapUrl: stringSchema
5090
+ }
5091
+ }
5092
+ }
5093
+ }
5094
+ },
5095
+ responses: {
5096
+ 200: { description: "Sitemap inspection run queued." },
5097
+ 400: { description: "Bing is not configured for this project." },
5098
+ 404: { description: "Project not found." }
5099
+ }
5100
+ },
5075
5101
  {
5076
5102
  method: "post",
5077
5103
  path: "/api/v1/projects/{name}/bing/request-indexing",
@@ -8509,6 +8535,32 @@ async function bingRoutes(app, opts) {
8509
8535
  throw e;
8510
8536
  }
8511
8537
  });
8538
+ app.post("/projects/:name/bing/inspect-sitemap", async (request) => {
8539
+ const store = requireConnectionStore();
8540
+ const project = resolveProject(app.db, request.params.name);
8541
+ const conn = requireConnection(store, project.canonicalDomain);
8542
+ if (!conn.siteUrl) {
8543
+ throw validationError('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
8544
+ }
8545
+ const now = (/* @__PURE__ */ new Date()).toISOString();
8546
+ const runId = crypto15.randomUUID();
8547
+ app.db.insert(runs).values({
8548
+ id: runId,
8549
+ projectId: project.id,
8550
+ kind: RunKinds["bing-inspect-sitemap"],
8551
+ status: RunStatuses.queued,
8552
+ trigger: RunTriggers.manual,
8553
+ createdAt: now
8554
+ }).run();
8555
+ const { sitemapUrl } = request.body ?? {};
8556
+ if (opts.onInspectSitemapRequested) {
8557
+ opts.onInspectSitemapRequested(runId, project.id, { sitemapUrl: sitemapUrl ?? void 0 });
8558
+ } else {
8559
+ bingLog("warn", "inspect-sitemap.no-callback", { domain: project.canonicalDomain, runId });
8560
+ }
8561
+ const run = app.db.select().from(runs).where(eq15(runs.id, runId)).get();
8562
+ return run;
8563
+ });
8512
8564
  app.post("/projects/:name/bing/request-indexing", async (request) => {
8513
8565
  const store = requireConnectionStore();
8514
8566
  const project = resolveProject(app.db, request.params.name);
@@ -11693,7 +11745,8 @@ async function apiRoutes(app, opts) {
11693
11745
  setTelemetryEnabled: opts.setTelemetryEnabled
11694
11746
  });
11695
11747
  await api.register(bingRoutes, {
11696
- bingConnectionStore: opts.bingConnectionStore
11748
+ bingConnectionStore: opts.bingConnectionStore,
11749
+ onInspectSitemapRequested: opts.onBingInspectSitemapRequested
11697
11750
  });
11698
11751
  await api.register(googleRoutes, {
11699
11752
  getGoogleAuthConfig: opts.getGoogleAuthConfig,
@@ -15128,11 +15181,197 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
15128
15181
  }
15129
15182
  }
15130
15183
 
15131
- // src/commoncrawl-sync.ts
15184
+ // src/bing-inspect-sitemap.ts
15132
15185
  import crypto22 from "crypto";
15186
+ import { eq as eq22, desc as desc9 } from "drizzle-orm";
15187
+ var log4 = createLogger("BingInspectSitemap");
15188
+ function parseBingDate2(value) {
15189
+ if (!value) return null;
15190
+ const match = /\/Date\((-?\d+)[^)]*\)\//.exec(value);
15191
+ if (!match) return null;
15192
+ const ms = parseInt(match[1], 10);
15193
+ if (ms <= 0) return null;
15194
+ return new Date(ms).toISOString();
15195
+ }
15196
+ function isBlockingIssueType2(issueType) {
15197
+ if (!issueType) return true;
15198
+ const trimmed = issueType.trim();
15199
+ if (!trimmed) return true;
15200
+ return trimmed.split(/\s+/).some((flag) => !/^(None|Seo(Issues|Concerns))$/i.test(flag));
15201
+ }
15202
+ async function executeBingInspectSitemap(db, runId, projectId, opts) {
15203
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
15204
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq22(runs.id, runId)).run();
15205
+ try {
15206
+ const project = db.select().from(projects).where(eq22(projects.id, projectId)).get();
15207
+ if (!project) {
15208
+ throw new Error(`Project not found: ${projectId}`);
15209
+ }
15210
+ const conn = opts.config.bing?.connections?.find((c) => c.domain === project.canonicalDomain);
15211
+ if (!conn) {
15212
+ throw new Error('No Bing connection found for this project. Run "canonry bing connect <project>" first.');
15213
+ }
15214
+ if (!conn.siteUrl) {
15215
+ throw new Error('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
15216
+ }
15217
+ const sitemapUrl = opts.sitemapUrl ?? `https://${project.canonicalDomain}/sitemap.xml`;
15218
+ log4.info("sitemap.fetch", { runId, projectId, sitemapUrl });
15219
+ const sitemapUrls = await fetchAndParseSitemap(sitemapUrl);
15220
+ log4.info("sitemap.parsed", { runId, projectId, urlCount: sitemapUrls.length, sitemapUrl });
15221
+ if (sitemapUrls.length === 0) {
15222
+ throw new Error("No URLs found in sitemap");
15223
+ }
15224
+ const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, projectId)).all();
15225
+ const trackedUrls = new Set(trackedRows.map((r) => r.url));
15226
+ const discovered = sitemapUrls.filter((u) => !trackedUrls.has(u));
15227
+ log4.info("sitemap.diff", {
15228
+ runId,
15229
+ projectId,
15230
+ sitemapTotal: sitemapUrls.length,
15231
+ alreadyTracked: sitemapUrls.length - discovered.length,
15232
+ newlyDiscovered: discovered.length
15233
+ });
15234
+ let blockedUrls = /* @__PURE__ */ new Set();
15235
+ try {
15236
+ const issues = await getCrawlIssues(conn.apiKey, conn.siteUrl);
15237
+ for (const issue of issues) {
15238
+ if (issue.Url && isBlockingIssueType2(issue.IssueType ?? null)) {
15239
+ blockedUrls.add(issue.Url);
15240
+ }
15241
+ }
15242
+ log4.info("crawl-issues.loaded", { runId, projectId, blockedCount: blockedUrls.size });
15243
+ } catch (err) {
15244
+ log4.warn("crawl-issues.lookup-failed", {
15245
+ runId,
15246
+ projectId,
15247
+ error: err instanceof Error ? err.message : String(err)
15248
+ });
15249
+ blockedUrls = /* @__PURE__ */ new Set();
15250
+ }
15251
+ let inspected = 0;
15252
+ let errors = 0;
15253
+ for (const pageUrl of sitemapUrls) {
15254
+ try {
15255
+ const result = await getUrlInfo(conn.apiKey, conn.siteUrl, pageUrl);
15256
+ const inspectedAt = (/* @__PURE__ */ new Date()).toISOString();
15257
+ const httpCode = result.HttpStatus ?? result.HttpCode ?? null;
15258
+ const lastCrawledDate = parseBingDate2(result.LastCrawledDate);
15259
+ const inIndexDate = parseBingDate2(result.InIndexDate);
15260
+ const discoveryDate = parseBingDate2(result.DiscoveryDate);
15261
+ let derivedInIndex = null;
15262
+ if (result.DocumentSize != null && result.DocumentSize > 0) {
15263
+ derivedInIndex = true;
15264
+ } else if (lastCrawledDate != null) {
15265
+ derivedInIndex = httpCode != null && httpCode >= 400 ? false : true;
15266
+ } else if (discoveryDate != null) {
15267
+ derivedInIndex = false;
15268
+ }
15269
+ if (derivedInIndex === true && blockedUrls.has(pageUrl)) {
15270
+ derivedInIndex = false;
15271
+ }
15272
+ db.insert(bingUrlInspections).values({
15273
+ id: crypto22.randomUUID(),
15274
+ projectId,
15275
+ url: pageUrl,
15276
+ httpCode,
15277
+ inIndex: derivedInIndex === true ? 1 : derivedInIndex === false ? 0 : null,
15278
+ lastCrawledDate,
15279
+ inIndexDate,
15280
+ inspectedAt,
15281
+ syncRunId: runId,
15282
+ createdAt: inspectedAt,
15283
+ documentSize: result.DocumentSize ?? null,
15284
+ anchorCount: result.AnchorCount ?? null,
15285
+ discoveryDate
15286
+ }).run();
15287
+ inspected++;
15288
+ log4.info("inspect.url-done", {
15289
+ runId,
15290
+ projectId,
15291
+ url: pageUrl,
15292
+ progress: `${inspected}/${sitemapUrls.length}`
15293
+ });
15294
+ } catch (err) {
15295
+ errors++;
15296
+ log4.error("inspect.url-failed", {
15297
+ runId,
15298
+ projectId,
15299
+ url: pageUrl,
15300
+ error: err instanceof Error ? err.message : String(err)
15301
+ });
15302
+ }
15303
+ if (inspected + errors < sitemapUrls.length) {
15304
+ await new Promise((r) => setTimeout(r, 1e3));
15305
+ }
15306
+ }
15307
+ const allInspections = db.select().from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, projectId)).orderBy(desc9(bingUrlInspections.inspectedAt)).all();
15308
+ const latestByUrl = /* @__PURE__ */ new Map();
15309
+ const definitiveByUrl = /* @__PURE__ */ new Map();
15310
+ for (const row of allInspections) {
15311
+ if (!latestByUrl.has(row.url)) latestByUrl.set(row.url, row);
15312
+ if (!definitiveByUrl.has(row.url) && row.inIndex != null) definitiveByUrl.set(row.url, row);
15313
+ }
15314
+ for (const [url, latest] of latestByUrl) {
15315
+ if (latest.inIndex == null) {
15316
+ const def = definitiveByUrl.get(url);
15317
+ if (def) latestByUrl.set(url, def);
15318
+ }
15319
+ }
15320
+ let snapIndexed = 0;
15321
+ let snapNotIndexed = 0;
15322
+ let snapUnknown = 0;
15323
+ for (const [, row] of latestByUrl) {
15324
+ if (row.inIndex === 1) snapIndexed++;
15325
+ else if (row.inIndex === 0) snapNotIndexed++;
15326
+ else snapUnknown++;
15327
+ }
15328
+ const snapshotDate = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
15329
+ const snapNow = (/* @__PURE__ */ new Date()).toISOString();
15330
+ db.insert(bingCoverageSnapshots).values({
15331
+ id: crypto22.randomUUID(),
15332
+ projectId,
15333
+ syncRunId: runId,
15334
+ date: snapshotDate,
15335
+ indexed: snapIndexed,
15336
+ notIndexed: snapNotIndexed,
15337
+ unknown: snapUnknown,
15338
+ createdAt: snapNow
15339
+ }).onConflictDoUpdate({
15340
+ target: [bingCoverageSnapshots.projectId, bingCoverageSnapshots.date],
15341
+ set: {
15342
+ indexed: snapIndexed,
15343
+ notIndexed: snapNotIndexed,
15344
+ unknown: snapUnknown,
15345
+ createdAt: snapNow,
15346
+ syncRunId: runId
15347
+ }
15348
+ }).run();
15349
+ const status = errors === sitemapUrls.length ? RunStatuses.failed : errors > 0 ? RunStatuses.partial : RunStatuses.completed;
15350
+ db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
15351
+ log4.info("inspect.completed", {
15352
+ runId,
15353
+ projectId,
15354
+ inspected,
15355
+ errors,
15356
+ total: sitemapUrls.length,
15357
+ newlyDiscovered: discovered.length,
15358
+ indexed: snapIndexed,
15359
+ notIndexed: snapNotIndexed,
15360
+ unknown: snapUnknown
15361
+ });
15362
+ } catch (err) {
15363
+ const errorMsg = err instanceof Error ? err.message : String(err);
15364
+ db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
15365
+ log4.error("inspect.failed", { runId, projectId, error: errorMsg });
15366
+ throw err;
15367
+ }
15368
+ }
15369
+
15370
+ // src/commoncrawl-sync.ts
15371
+ import crypto23 from "crypto";
15133
15372
  import path11 from "path";
15134
- import { and as and11, eq as eq22, sql as sql8 } from "drizzle-orm";
15135
- var log4 = createLogger("CommonCrawlSync");
15373
+ import { and as and11, eq as eq23, sql as sql8 } from "drizzle-orm";
15374
+ var log5 = createLogger("CommonCrawlSync");
15136
15375
  var INSERT_CHUNK_SIZE = 1e4;
15137
15376
  function defaultDeps() {
15138
15377
  return {
@@ -15157,7 +15396,7 @@ async function executeReleaseSync(db, syncId, opts) {
15157
15396
  phaseDetail: "downloading vertices + edges",
15158
15397
  updatedAt: downloadStartedAt,
15159
15398
  error: null
15160
- }).where(eq22(ccReleaseSyncs.id, syncId)).run();
15399
+ }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15161
15400
  const paths = ccReleasePaths(release);
15162
15401
  const releaseCacheDir = path11.join(deps.cacheDir, release);
15163
15402
  const vertexPath = path11.join(releaseCacheDir, paths.vertexFilename);
@@ -15180,7 +15419,7 @@ async function executeReleaseSync(db, syncId, opts) {
15180
15419
  vertexSha256: vertex.sha256,
15181
15420
  edgesSha256: edges.sha256,
15182
15421
  updatedAt: downloadFinishedAt
15183
- }).where(eq22(ccReleaseSyncs.id, syncId)).run();
15422
+ }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15184
15423
  const allProjects = db.select().from(projects).all();
15185
15424
  const targets = Array.from(new Set(allProjects.map((p) => p.canonicalDomain)));
15186
15425
  let rows = [];
@@ -15196,15 +15435,15 @@ async function executeReleaseSync(db, syncId, opts) {
15196
15435
  }
15197
15436
  const queriedAt = deps.now().toISOString();
15198
15437
  db.transaction((tx) => {
15199
- tx.delete(backlinkDomains).where(eq22(backlinkDomains.releaseSyncId, syncId)).run();
15200
- tx.delete(backlinkSummaries).where(eq22(backlinkSummaries.releaseSyncId, syncId)).run();
15438
+ tx.delete(backlinkDomains).where(eq23(backlinkDomains.releaseSyncId, syncId)).run();
15439
+ tx.delete(backlinkSummaries).where(eq23(backlinkSummaries.releaseSyncId, syncId)).run();
15201
15440
  const expanded = [];
15202
15441
  for (const r of rows) {
15203
15442
  const projectIds = projectsByDomain.get(r.targetDomain);
15204
15443
  if (!projectIds) continue;
15205
15444
  for (const projectId of projectIds) {
15206
15445
  expanded.push({
15207
- id: crypto22.randomUUID(),
15446
+ id: crypto23.randomUUID(),
15208
15447
  projectId,
15209
15448
  releaseSyncId: syncId,
15210
15449
  release,
@@ -15224,7 +15463,7 @@ async function executeReleaseSync(db, syncId, opts) {
15224
15463
  const projectRows = rowsByProject.get(p.id) ?? [];
15225
15464
  const summary = computeSummary(projectRows);
15226
15465
  tx.insert(backlinkSummaries).values({
15227
- id: crypto22.randomUUID(),
15466
+ id: crypto23.randomUUID(),
15228
15467
  projectId: p.id,
15229
15468
  releaseSyncId: syncId,
15230
15469
  release,
@@ -15256,8 +15495,8 @@ async function executeReleaseSync(db, syncId, opts) {
15256
15495
  domainsDiscovered: rows.length,
15257
15496
  updatedAt: finishedAt,
15258
15497
  error: null
15259
- }).where(eq22(ccReleaseSyncs.id, syncId)).run();
15260
- log4.info("sync.completed", {
15498
+ }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15499
+ log5.info("sync.completed", {
15261
15500
  syncId,
15262
15501
  release,
15263
15502
  projectsProcessed: allProjects.length,
@@ -15269,7 +15508,7 @@ async function executeReleaseSync(db, syncId, opts) {
15269
15508
  try {
15270
15509
  deps.enqueueAutoExtract({ projectId: p.id, release });
15271
15510
  } catch (err) {
15272
- log4.error("auto-extract.enqueue-failed", {
15511
+ log5.error("auto-extract.enqueue-failed", {
15273
15512
  syncId,
15274
15513
  release,
15275
15514
  projectId: p.id,
@@ -15286,8 +15525,8 @@ async function executeReleaseSync(db, syncId, opts) {
15286
15525
  error: errorMsg,
15287
15526
  phaseDetail: null,
15288
15527
  updatedAt: finishedAt
15289
- }).where(eq22(ccReleaseSyncs.id, syncId)).run();
15290
- log4.error("sync.failed", { syncId, release, error: errorMsg });
15528
+ }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15529
+ log5.error("sync.failed", { syncId, release, error: errorMsg });
15291
15530
  throw err;
15292
15531
  }
15293
15532
  }
@@ -15320,10 +15559,10 @@ function computeSummary(rows) {
15320
15559
  }
15321
15560
 
15322
15561
  // src/backlink-extract.ts
15323
- import crypto23 from "crypto";
15562
+ import crypto24 from "crypto";
15324
15563
  import fs9 from "fs";
15325
- import { and as and12, desc as desc9, eq as eq23 } from "drizzle-orm";
15326
- var log5 = createLogger("BacklinkExtract");
15564
+ import { and as and12, desc as desc10, eq as eq24 } from "drizzle-orm";
15565
+ var log6 = createLogger("BacklinkExtract");
15327
15566
  function defaultDeps2() {
15328
15567
  return {
15329
15568
  queryBacklinks,
@@ -15334,13 +15573,13 @@ function defaultDeps2() {
15334
15573
  async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15335
15574
  const deps = { ...defaultDeps2(), ...opts.deps };
15336
15575
  const startedAt = deps.now().toISOString();
15337
- db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq23(runs.id, runId)).run();
15576
+ db.update(runs).set({ status: RunStatuses.running, startedAt }).where(eq24(runs.id, runId)).run();
15338
15577
  try {
15339
- const project = db.select().from(projects).where(eq23(projects.id, projectId)).get();
15578
+ const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
15340
15579
  if (!project) {
15341
15580
  throw new Error(`Project not found: ${projectId}`);
15342
15581
  }
15343
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq23(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq23(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc9(ccReleaseSyncs.createdAt)).limit(1).get();
15582
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq24(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc10(ccReleaseSyncs.createdAt)).limit(1).get();
15344
15583
  if (!sync) {
15345
15584
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
15346
15585
  }
@@ -15368,11 +15607,11 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15368
15607
  const targetDomain = project.canonicalDomain;
15369
15608
  db.transaction((tx) => {
15370
15609
  tx.delete(backlinkDomains).where(
15371
- and12(eq23(backlinkDomains.projectId, projectId), eq23(backlinkDomains.release, release))
15610
+ and12(eq24(backlinkDomains.projectId, projectId), eq24(backlinkDomains.release, release))
15372
15611
  ).run();
15373
15612
  if (rows.length > 0) {
15374
15613
  const values = rows.map((r) => ({
15375
- id: crypto23.randomUUID(),
15614
+ id: crypto24.randomUUID(),
15376
15615
  projectId,
15377
15616
  releaseSyncId: syncId,
15378
15617
  release,
@@ -15385,7 +15624,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15385
15624
  }
15386
15625
  const summary = computeSummary2(rows);
15387
15626
  tx.insert(backlinkSummaries).values({
15388
- id: crypto23.randomUUID(),
15627
+ id: crypto24.randomUUID(),
15389
15628
  projectId,
15390
15629
  releaseSyncId: syncId,
15391
15630
  release,
@@ -15408,8 +15647,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15408
15647
  }).run();
15409
15648
  });
15410
15649
  const finishedAt = deps.now().toISOString();
15411
- db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq23(runs.id, runId)).run();
15412
- log5.info("extract.completed", { runId, projectId, release, rows: rows.length });
15650
+ db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq24(runs.id, runId)).run();
15651
+ log6.info("extract.completed", { runId, projectId, release, rows: rows.length });
15413
15652
  } catch (err) {
15414
15653
  const errorMsg = err instanceof Error ? err.message : String(err);
15415
15654
  const finishedAt = deps.now().toISOString();
@@ -15417,8 +15656,8 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15417
15656
  status: RunStatuses.failed,
15418
15657
  error: errorMsg,
15419
15658
  finishedAt
15420
- }).where(eq23(runs.id, runId)).run();
15421
- log5.error("extract.failed", { runId, projectId, error: errorMsg });
15659
+ }).where(eq24(runs.id, runId)).run();
15660
+ log6.error("extract.failed", { runId, projectId, error: errorMsg });
15422
15661
  throw err;
15423
15662
  }
15424
15663
  }
@@ -15490,8 +15729,8 @@ var ProviderRegistry = class {
15490
15729
 
15491
15730
  // src/scheduler.ts
15492
15731
  import cron from "node-cron";
15493
- import { eq as eq24 } from "drizzle-orm";
15494
- var log6 = createLogger("Scheduler");
15732
+ import { eq as eq25 } from "drizzle-orm";
15733
+ var log7 = createLogger("Scheduler");
15495
15734
  var Scheduler = class {
15496
15735
  db;
15497
15736
  callbacks;
@@ -15502,16 +15741,16 @@ var Scheduler = class {
15502
15741
  }
15503
15742
  /** Load all enabled schedules from DB and register cron jobs. */
15504
15743
  start() {
15505
- const allSchedules = this.db.select().from(schedules).where(eq24(schedules.enabled, 1)).all();
15744
+ const allSchedules = this.db.select().from(schedules).where(eq25(schedules.enabled, 1)).all();
15506
15745
  for (const schedule of allSchedules) {
15507
15746
  const missedRunAt = schedule.nextRunAt;
15508
15747
  this.registerCronTask(schedule);
15509
15748
  if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
15510
- log6.info("run.catch-up", { projectId: schedule.projectId, missedRunAt });
15749
+ log7.info("run.catch-up", { projectId: schedule.projectId, missedRunAt });
15511
15750
  this.triggerRun(schedule.id, schedule.projectId);
15512
15751
  }
15513
15752
  }
15514
- log6.info("started", { scheduleCount: allSchedules.length });
15753
+ log7.info("started", { scheduleCount: allSchedules.length });
15515
15754
  }
15516
15755
  /** Stop all cron tasks for graceful shutdown. */
15517
15756
  stop() {
@@ -15527,7 +15766,7 @@ var Scheduler = class {
15527
15766
  this.stopTask(projectId, existing, "Stopped");
15528
15767
  this.tasks.delete(projectId);
15529
15768
  }
15530
- const schedule = this.db.select().from(schedules).where(eq24(schedules.projectId, projectId)).get();
15769
+ const schedule = this.db.select().from(schedules).where(eq25(schedules.projectId, projectId)).get();
15531
15770
  if (schedule && schedule.enabled === 1) {
15532
15771
  this.registerCronTask(schedule);
15533
15772
  }
@@ -15543,12 +15782,12 @@ var Scheduler = class {
15543
15782
  stopTask(projectId, task, verb) {
15544
15783
  task.stop();
15545
15784
  task.destroy();
15546
- log6.info(`task.${verb.toLowerCase()}`, { projectId });
15785
+ log7.info(`task.${verb.toLowerCase()}`, { projectId });
15547
15786
  }
15548
15787
  registerCronTask(schedule) {
15549
15788
  const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
15550
15789
  if (!cron.validate(cronExpr)) {
15551
- log6.error("cron.invalid", { projectId, cronExpr });
15790
+ log7.error("cron.invalid", { projectId, cronExpr });
15552
15791
  return;
15553
15792
  }
15554
15793
  const task = cron.schedule(cronExpr, () => {
@@ -15560,24 +15799,24 @@ var Scheduler = class {
15560
15799
  this.db.update(schedules).set({
15561
15800
  nextRunAt: task.getNextRun()?.toISOString() ?? null,
15562
15801
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15563
- }).where(eq24(schedules.id, scheduleId)).run();
15802
+ }).where(eq25(schedules.id, scheduleId)).run();
15564
15803
  const label = schedule.preset ?? cronExpr;
15565
- log6.info("cron.registered", { projectId, schedule: label, timezone });
15804
+ log7.info("cron.registered", { projectId, schedule: label, timezone });
15566
15805
  }
15567
15806
  triggerRun(scheduleId, projectId) {
15568
15807
  try {
15569
15808
  const now = (/* @__PURE__ */ new Date()).toISOString();
15570
- const currentSchedule = this.db.select().from(schedules).where(eq24(schedules.id, scheduleId)).get();
15809
+ const currentSchedule = this.db.select().from(schedules).where(eq25(schedules.id, scheduleId)).get();
15571
15810
  if (!currentSchedule || currentSchedule.enabled !== 1) {
15572
- log6.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
15811
+ log7.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
15573
15812
  this.remove(projectId);
15574
15813
  return;
15575
15814
  }
15576
15815
  const task = this.tasks.get(projectId);
15577
15816
  const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
15578
- const project = this.db.select().from(projects).where(eq24(projects.id, projectId)).get();
15817
+ const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15579
15818
  if (!project) {
15580
- log6.error("project.not-found", { projectId, msg: "skipping scheduled run" });
15819
+ log7.error("project.not-found", { projectId, msg: "skipping scheduled run" });
15581
15820
  this.remove(projectId);
15582
15821
  return;
15583
15822
  }
@@ -15586,7 +15825,7 @@ var Scheduler = class {
15586
15825
  if (project.defaultLocation) {
15587
15826
  const loc = projectLocations.find((l) => l.label === project.defaultLocation);
15588
15827
  if (!loc) {
15589
- log6.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
15828
+ log7.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
15590
15829
  return;
15591
15830
  }
15592
15831
  resolvedLocation = loc;
@@ -15600,11 +15839,11 @@ var Scheduler = class {
15600
15839
  location: locationLabel
15601
15840
  });
15602
15841
  if (queueResult.conflict) {
15603
- log6.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
15842
+ log7.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
15604
15843
  this.db.update(schedules).set({
15605
15844
  nextRunAt,
15606
15845
  updatedAt: now
15607
- }).where(eq24(schedules.id, currentSchedule.id)).run();
15846
+ }).where(eq25(schedules.id, currentSchedule.id)).run();
15608
15847
  return;
15609
15848
  }
15610
15849
  const runId = queueResult.runId;
@@ -15612,21 +15851,21 @@ var Scheduler = class {
15612
15851
  lastRunAt: now,
15613
15852
  nextRunAt,
15614
15853
  updatedAt: now
15615
- }).where(eq24(schedules.id, currentSchedule.id)).run();
15854
+ }).where(eq25(schedules.id, currentSchedule.id)).run();
15616
15855
  const scheduleProviders = parseJsonColumn(currentSchedule.providers, []);
15617
15856
  const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
15618
- log6.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
15857
+ log7.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
15619
15858
  this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
15620
15859
  } catch (err) {
15621
- log6.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
15860
+ log7.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
15622
15861
  }
15623
15862
  }
15624
15863
  };
15625
15864
 
15626
15865
  // src/notifier.ts
15627
- import { eq as eq25, desc as desc10, and as and13, or as or2 } from "drizzle-orm";
15628
- import crypto24 from "crypto";
15629
- var log7 = createLogger("Notifier");
15866
+ import { eq as eq26, desc as desc11, and as and13, or as or2 } from "drizzle-orm";
15867
+ import crypto25 from "crypto";
15868
+ var log8 = createLogger("Notifier");
15630
15869
  var Notifier = class {
15631
15870
  db;
15632
15871
  serverUrl;
@@ -15636,26 +15875,26 @@ var Notifier = class {
15636
15875
  }
15637
15876
  /** Called after a run completes (success, partial, or failed). */
15638
15877
  async onRunCompleted(runId, projectId) {
15639
- log7.info("run.completed", { runId, projectId });
15640
- const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15878
+ log8.info("run.completed", { runId, projectId });
15879
+ const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15641
15880
  if (notifs.length === 0) {
15642
- log7.info("notifications.none-enabled", { projectId });
15881
+ log8.info("notifications.none-enabled", { projectId });
15643
15882
  return;
15644
15883
  }
15645
- log7.info("notifications.found", { projectId, count: notifs.length });
15646
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15884
+ log8.info("notifications.found", { projectId, count: notifs.length });
15885
+ const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15647
15886
  if (!run) {
15648
- log7.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15887
+ log8.error("run.not-found", { runId, msg: "skipping notification dispatch" });
15649
15888
  return;
15650
15889
  }
15651
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15890
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15652
15891
  if (!project) {
15653
- log7.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15892
+ log8.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
15654
15893
  return;
15655
15894
  }
15656
15895
  const transitions = this.computeTransitions(runId, projectId);
15657
15896
  const events = [];
15658
- log7.info("run.status", { runId: run.id, status: run.status, projectId });
15897
+ log8.info("run.status", { runId: run.id, status: run.status, projectId });
15659
15898
  if (run.status === "completed" || run.status === "partial") {
15660
15899
  events.push("run.completed");
15661
15900
  }
@@ -15671,7 +15910,7 @@ var Notifier = class {
15671
15910
  if (!config.url) continue;
15672
15911
  const subscribedEvents = config.events;
15673
15912
  const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
15674
- log7.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
15913
+ log8.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
15675
15914
  if (matchingEvents.length === 0) continue;
15676
15915
  for (const event of matchingEvents) {
15677
15916
  const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
@@ -15695,11 +15934,11 @@ var Notifier = class {
15695
15934
  if (criticalInsights.length > 0) insightEvents.push("insight.critical");
15696
15935
  if (highInsights.length > 0) insightEvents.push("insight.high");
15697
15936
  if (insightEvents.length === 0) return;
15698
- const notifs = this.db.select().from(notifications).where(eq25(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15937
+ const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
15699
15938
  if (notifs.length === 0) return;
15700
- const run = this.db.select().from(runs).where(eq25(runs.id, runId)).get();
15939
+ const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
15701
15940
  if (!run) return;
15702
- const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
15941
+ const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
15703
15942
  if (!project) return;
15704
15943
  for (const notif of notifs) {
15705
15944
  const config = parseJsonColumn(notif.config, { url: "", events: [] });
@@ -15731,10 +15970,10 @@ var Notifier = class {
15731
15970
  computeTransitions(runId, projectId) {
15732
15971
  const recentRuns = this.db.select().from(runs).where(
15733
15972
  and13(
15734
- eq25(runs.projectId, projectId),
15735
- or2(eq25(runs.status, "completed"), eq25(runs.status, "partial"))
15973
+ eq26(runs.projectId, projectId),
15974
+ or2(eq26(runs.status, "completed"), eq26(runs.status, "partial"))
15736
15975
  )
15737
- ).orderBy(desc10(runs.createdAt)).limit(2).all();
15976
+ ).orderBy(desc11(runs.createdAt)).limit(2).all();
15738
15977
  if (recentRuns.length < 2) return [];
15739
15978
  const currentRunId = recentRuns[0].id;
15740
15979
  const previousRunId = recentRuns[1].id;
@@ -15744,12 +15983,12 @@ var Notifier = class {
15744
15983
  keyword: keywords.keyword,
15745
15984
  provider: querySnapshots.provider,
15746
15985
  citationState: querySnapshots.citationState
15747
- }).from(querySnapshots).leftJoin(keywords, eq25(querySnapshots.keywordId, keywords.id)).where(eq25(querySnapshots.runId, currentRunId)).all();
15986
+ }).from(querySnapshots).leftJoin(keywords, eq26(querySnapshots.keywordId, keywords.id)).where(eq26(querySnapshots.runId, currentRunId)).all();
15748
15987
  const previousSnapshots = this.db.select({
15749
15988
  keywordId: querySnapshots.keywordId,
15750
15989
  provider: querySnapshots.provider,
15751
15990
  citationState: querySnapshots.citationState
15752
- }).from(querySnapshots).where(eq25(querySnapshots.runId, previousRunId)).all();
15991
+ }).from(querySnapshots).where(eq26(querySnapshots.runId, previousRunId)).all();
15753
15992
  const prevMap = /* @__PURE__ */ new Map();
15754
15993
  for (const s of previousSnapshots) {
15755
15994
  prevMap.set(`${s.keywordId}:${s.provider}`, s.citationState);
@@ -15773,23 +16012,23 @@ var Notifier = class {
15773
16012
  const targetLabel = redactNotificationUrl(url).urlDisplay;
15774
16013
  const targetCheck = await resolveWebhookTarget(url);
15775
16014
  if (!targetCheck.ok) {
15776
- log7.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
16015
+ log8.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
15777
16016
  this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
15778
16017
  return;
15779
16018
  }
15780
- log7.info("webhook.send", { event: payload.event, url: targetLabel });
16019
+ log8.info("webhook.send", { event: payload.event, url: targetLabel });
15781
16020
  const maxRetries = 3;
15782
16021
  const delays = [1e3, 4e3, 16e3];
15783
16022
  for (let attempt = 0; attempt < maxRetries; attempt++) {
15784
16023
  try {
15785
16024
  const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
15786
16025
  if (response.status >= 200 && response.status < 300) {
15787
- log7.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
16026
+ log8.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
15788
16027
  this.logDelivery(projectId, notificationId, payload.event, "sent", null);
15789
16028
  return;
15790
16029
  }
15791
16030
  const errorDetail = response.error ?? `HTTP ${response.status}`;
15792
- log7.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
16031
+ log8.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
15793
16032
  if (attempt === maxRetries - 1) {
15794
16033
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
15795
16034
  }
@@ -15797,7 +16036,7 @@ var Notifier = class {
15797
16036
  const errorDetail = err instanceof Error ? err.message : String(err);
15798
16037
  if (attempt === maxRetries - 1) {
15799
16038
  this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
15800
- log7.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
16039
+ log8.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
15801
16040
  }
15802
16041
  }
15803
16042
  if (attempt < maxRetries - 1) {
@@ -15807,7 +16046,7 @@ var Notifier = class {
15807
16046
  }
15808
16047
  logDelivery(projectId, notificationId, event, status, error) {
15809
16048
  this.db.insert(auditLog).values({
15810
- id: crypto24.randomUUID(),
16049
+ id: crypto25.randomUUID(),
15811
16050
  projectId,
15812
16051
  actor: "scheduler",
15813
16052
  action: `notification.${status}`,
@@ -15820,7 +16059,7 @@ var Notifier = class {
15820
16059
  };
15821
16060
 
15822
16061
  // src/run-coordinator.ts
15823
- var log8 = createLogger("RunCoordinator");
16062
+ var log9 = createLogger("RunCoordinator");
15824
16063
  var RunCoordinator = class {
15825
16064
  constructor(notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
15826
16065
  this.notifier = notifier;
@@ -15842,31 +16081,31 @@ var RunCoordinator = class {
15842
16081
  try {
15843
16082
  await this.onInsightsGenerated(runId, projectId, result);
15844
16083
  } catch (err) {
15845
- log8.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
16084
+ log9.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
15846
16085
  }
15847
16086
  }
15848
16087
  }
15849
16088
  } catch (err) {
15850
- log8.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
16089
+ log9.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
15851
16090
  }
15852
16091
  try {
15853
16092
  await this.notifier.onRunCompleted(runId, projectId);
15854
16093
  } catch (err) {
15855
- log8.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
16094
+ log9.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
15856
16095
  }
15857
16096
  if (this.onAeroEvent) {
15858
16097
  try {
15859
16098
  await this.onAeroEvent({ runId, projectId, insightCount, criticalOrHigh });
15860
16099
  } catch (err) {
15861
- log8.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
16100
+ log9.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
15862
16101
  }
15863
16102
  }
15864
16103
  }
15865
16104
  };
15866
16105
 
15867
16106
  // src/agent/session-registry.ts
15868
- import crypto26 from "crypto";
15869
- import { eq as eq27 } from "drizzle-orm";
16107
+ import crypto27 from "crypto";
16108
+ import { eq as eq28 } from "drizzle-orm";
15870
16109
 
15871
16110
  // src/agent/session.ts
15872
16111
  import fs12 from "fs";
@@ -16085,8 +16324,8 @@ function buildSkillDocTools() {
16085
16324
  import { Type as Type2 } from "@sinclair/typebox";
16086
16325
 
16087
16326
  // src/agent/memory-store.ts
16088
- import crypto25 from "crypto";
16089
- import { and as and14, desc as desc11, eq as eq26, like, sql as sql9 } from "drizzle-orm";
16327
+ import crypto26 from "crypto";
16328
+ import { and as and14, desc as desc12, eq as eq27, like, sql as sql9 } from "drizzle-orm";
16090
16329
  var COMPACTION_KEY_PREFIX = "compaction:";
16091
16330
  var COMPACTION_NOTES_PER_SESSION = 3;
16092
16331
  function rowToDto(row) {
@@ -16100,7 +16339,7 @@ function rowToDto(row) {
16100
16339
  };
16101
16340
  }
16102
16341
  function listMemoryEntries(db, projectId, opts = {}) {
16103
- const query = db.select().from(agentMemory).where(eq26(agentMemory.projectId, projectId)).orderBy(desc11(agentMemory.updatedAt));
16342
+ const query = db.select().from(agentMemory).where(eq27(agentMemory.projectId, projectId)).orderBy(desc12(agentMemory.updatedAt));
16104
16343
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
16105
16344
  return rows.map(rowToDto);
16106
16345
  }
@@ -16114,7 +16353,7 @@ function upsertMemoryEntry(db, args) {
16114
16353
  throw new Error(`memory key prefix "${COMPACTION_KEY_PREFIX}" is reserved for compaction notes`);
16115
16354
  }
16116
16355
  const now = (/* @__PURE__ */ new Date()).toISOString();
16117
- const id = crypto25.randomUUID();
16356
+ const id = crypto26.randomUUID();
16118
16357
  db.insert(agentMemory).values({
16119
16358
  id,
16120
16359
  projectId: args.projectId,
@@ -16131,12 +16370,12 @@ function upsertMemoryEntry(db, args) {
16131
16370
  updatedAt: now
16132
16371
  }
16133
16372
  }).run();
16134
- const row = db.select().from(agentMemory).where(and14(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, args.key))).get();
16373
+ const row = db.select().from(agentMemory).where(and14(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, args.key))).get();
16135
16374
  if (!row) throw new Error("memory upsert produced no row");
16136
16375
  return rowToDto(row);
16137
16376
  }
16138
16377
  function deleteMemoryEntry(db, projectId, key) {
16139
- const result = db.delete(agentMemory).where(and14(eq26(agentMemory.projectId, projectId), eq26(agentMemory.key, key))).run();
16378
+ const result = db.delete(agentMemory).where(and14(eq27(agentMemory.projectId, projectId), eq27(agentMemory.key, key))).run();
16140
16379
  const changes = result.changes ?? 0;
16141
16380
  return changes > 0;
16142
16381
  }
@@ -16151,7 +16390,7 @@ function writeCompactionNote(db, args) {
16151
16390
  }
16152
16391
  const now = (/* @__PURE__ */ new Date()).toISOString();
16153
16392
  const key = `${COMPACTION_KEY_PREFIX}${args.sessionId}:${now}`;
16154
- const id = crypto25.randomUUID();
16393
+ const id = crypto26.randomUUID();
16155
16394
  let inserted;
16156
16395
  db.transaction((tx) => {
16157
16396
  tx.insert(agentMemory).values({
@@ -16166,15 +16405,15 @@ function writeCompactionNote(db, args) {
16166
16405
  const sessionPrefix = `${COMPACTION_KEY_PREFIX}${args.sessionId}:`;
16167
16406
  const existing = tx.select({ id: agentMemory.id, updatedAt: agentMemory.updatedAt }).from(agentMemory).where(
16168
16407
  and14(
16169
- eq26(agentMemory.projectId, args.projectId),
16408
+ eq27(agentMemory.projectId, args.projectId),
16170
16409
  like(agentMemory.key, `${sessionPrefix}%`)
16171
16410
  )
16172
- ).orderBy(desc11(agentMemory.updatedAt)).all();
16411
+ ).orderBy(desc12(agentMemory.updatedAt)).all();
16173
16412
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
16174
16413
  if (stale.length > 0) {
16175
16414
  tx.delete(agentMemory).where(sql9`${agentMemory.id} IN (${sql9.join(stale.map((s) => sql9`${s}`), sql9`, `)})`).run();
16176
16415
  }
16177
- const row = tx.select().from(agentMemory).where(and14(eq26(agentMemory.projectId, args.projectId), eq26(agentMemory.key, key))).get();
16416
+ const row = tx.select().from(agentMemory).where(and14(eq27(agentMemory.projectId, args.projectId), eq27(agentMemory.key, key))).get();
16178
16417
  if (row) inserted = rowToDto(row);
16179
16418
  });
16180
16419
  if (!inserted) throw new Error("compaction note write produced no row");
@@ -16802,7 +17041,7 @@ async function compactMessages(args) {
16802
17041
  }
16803
17042
 
16804
17043
  // src/agent/session-registry.ts
16805
- var log9 = createLogger("SessionRegistry");
17044
+ var log10 = createLogger("SessionRegistry");
16806
17045
  var MAX_HYDRATE_NOTES = 20;
16807
17046
  var MAX_HYDRATE_BYTES = 32 * 1024;
16808
17047
  function escapeMemoryFragment(value) {
@@ -16851,7 +17090,7 @@ var SessionRegistry = class {
16851
17090
  modelProvider: effectiveProvider,
16852
17091
  modelId: effectiveModelId,
16853
17092
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16854
- }).where(eq27(agentSessions.projectId, projectId)).run();
17093
+ }).where(eq28(agentSessions.projectId, projectId)).run();
16855
17094
  }
16856
17095
  const agent2 = createAeroSession({
16857
17096
  projectName,
@@ -17033,13 +17272,13 @@ ${lines.join("\n")}
17033
17272
  agent.state.messages = result.messages;
17034
17273
  agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
17035
17274
  this.save(projectName);
17036
- log9.info("compaction.completed", {
17275
+ log10.info("compaction.completed", {
17037
17276
  projectName,
17038
17277
  removedCount: result.removedCount,
17039
17278
  summaryBytes: Buffer.byteLength(result.summary, "utf8")
17040
17279
  });
17041
17280
  } catch (err) {
17042
- log9.error("compaction.failed", {
17281
+ log10.error("compaction.failed", {
17043
17282
  projectName,
17044
17283
  error: err instanceof Error ? err.message : String(err)
17045
17284
  });
@@ -17069,7 +17308,7 @@ ${lines.join("\n")}
17069
17308
  modelProvider: nextProvider,
17070
17309
  modelId: nextModelId,
17071
17310
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
17072
- }).where(eq27(agentSessions.projectId, projectId)).run();
17311
+ }).where(eq28(agentSessions.projectId, projectId)).run();
17073
17312
  }
17074
17313
  /** Persist a session's transcript back to the DB. Call after any run settles. */
17075
17314
  save(projectName) {
@@ -17136,7 +17375,7 @@ ${lines.join("\n")}
17136
17375
  await agent.prompt(msgs);
17137
17376
  this.save(projectName);
17138
17377
  } catch (err) {
17139
- log9.error("drain.failed", {
17378
+ log10.error("drain.failed", {
17140
17379
  projectName,
17141
17380
  error: err instanceof Error ? err.message : String(err)
17142
17381
  });
@@ -17231,17 +17470,17 @@ ${lines.join("\n")}
17231
17470
  return id;
17232
17471
  }
17233
17472
  tryResolveProjectId(projectName) {
17234
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq27(projects.name, projectName)).get();
17473
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq28(projects.name, projectName)).get();
17235
17474
  return row?.id;
17236
17475
  }
17237
17476
  loadRow(projectId) {
17238
- const row = this.opts.db.select().from(agentSessions).where(eq27(agentSessions.projectId, projectId)).get();
17477
+ const row = this.opts.db.select().from(agentSessions).where(eq28(agentSessions.projectId, projectId)).get();
17239
17478
  return row ?? null;
17240
17479
  }
17241
17480
  insertRow(params) {
17242
17481
  const now = (/* @__PURE__ */ new Date()).toISOString();
17243
17482
  this.opts.db.insert(agentSessions).values({
17244
- id: crypto26.randomUUID(),
17483
+ id: crypto27.randomUUID(),
17245
17484
  projectId: params.projectId,
17246
17485
  systemPrompt: params.systemPrompt,
17247
17486
  modelProvider: params.provider ?? params.modelProvider ?? AgentProviderIds.claude,
@@ -17254,14 +17493,14 @@ ${lines.join("\n")}
17254
17493
  }
17255
17494
  updateRow(projectId, patch) {
17256
17495
  const now = (/* @__PURE__ */ new Date()).toISOString();
17257
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq27(agentSessions.projectId, projectId)).run();
17496
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq28(agentSessions.projectId, projectId)).run();
17258
17497
  }
17259
17498
  };
17260
17499
 
17261
17500
  // src/agent/agent-routes.ts
17262
- import { eq as eq28 } from "drizzle-orm";
17501
+ import { eq as eq29 } from "drizzle-orm";
17263
17502
  function resolveProject2(db, name) {
17264
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq28(projects.name, name)).get();
17503
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq29(projects.name, name)).get();
17265
17504
  if (!row) throw notFound("project", name);
17266
17505
  return row;
17267
17506
  }
@@ -17270,7 +17509,7 @@ function registerAgentRoutes(app, opts) {
17270
17509
  "/projects/:name/agent/transcript",
17271
17510
  async (request) => {
17272
17511
  const project = resolveProject2(opts.db, request.params.name);
17273
- const row = opts.db.select().from(agentSessions).where(eq28(agentSessions.projectId, project.id)).get();
17512
+ const row = opts.db.select().from(agentSessions).where(eq29(agentSessions.projectId, project.id)).get();
17274
17513
  if (!row) {
17275
17514
  return { messages: [], modelProvider: null, modelId: null, updatedAt: null };
17276
17515
  }
@@ -17294,7 +17533,7 @@ function registerAgentRoutes(app, opts) {
17294
17533
  async (request) => {
17295
17534
  const project = resolveProject2(opts.db, request.params.name);
17296
17535
  opts.sessionRegistry.reset(project.name);
17297
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq28(agentSessions.projectId, project.id)).run();
17536
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq29(agentSessions.projectId, project.id)).run();
17298
17537
  return { status: "reset" };
17299
17538
  }
17300
17539
  );
@@ -17805,6 +18044,9 @@ var ApiClient = class {
17805
18044
  async bingInspectUrl(project, url) {
17806
18045
  return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-url`, { url });
17807
18046
  }
18047
+ async bingInspectSitemap(project, body) {
18048
+ return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-sitemap`, body ?? {});
18049
+ }
17808
18050
  async bingRequestIndexing(project, body) {
17809
18051
  return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/request-indexing`, body);
17810
18052
  }
@@ -18111,7 +18353,7 @@ function formatAuditFactorScore(factor) {
18111
18353
  }
18112
18354
 
18113
18355
  // src/snapshot-service.ts
18114
- var log10 = createLogger("Snapshot");
18356
+ var log11 = createLogger("Snapshot");
18115
18357
  var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
18116
18358
  var SNAPSHOT_QUERY_COUNT = 6;
18117
18359
  var ProviderExecutionGate2 = class {
@@ -18254,7 +18496,7 @@ var SnapshotService = class {
18254
18496
  return mapAuditReport(report);
18255
18497
  } catch (err) {
18256
18498
  const message = err instanceof Error ? err.message : String(err);
18257
- log10.warn("audit.failed", { homepageUrl, error: message });
18499
+ log11.warn("audit.failed", { homepageUrl, error: message });
18258
18500
  return {
18259
18501
  url: homepageUrl,
18260
18502
  finalUrl: homepageUrl,
@@ -18284,7 +18526,7 @@ var SnapshotService = class {
18284
18526
  phrases: parsedPhrases
18285
18527
  };
18286
18528
  } catch (err) {
18287
- log10.warn("profile.generation-failed", {
18529
+ log11.warn("profile.generation-failed", {
18288
18530
  domain: ctx.domain,
18289
18531
  provider: ctx.analysisProvider.adapter.name,
18290
18532
  error: err instanceof Error ? err.message : String(err)
@@ -18426,7 +18668,7 @@ var SnapshotService = class {
18426
18668
  recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
18427
18669
  };
18428
18670
  } catch (err) {
18429
- log10.warn("response.analysis-failed", {
18671
+ log11.warn("response.analysis-failed", {
18430
18672
  provider: ctx.analysisProvider.adapter.name,
18431
18673
  error: err instanceof Error ? err.message : String(err)
18432
18674
  });
@@ -18711,7 +18953,7 @@ function clipText(value, length) {
18711
18953
  // src/server.ts
18712
18954
  var _require2 = createRequire3(import.meta.url);
18713
18955
  var { version: PKG_VERSION } = _require2("../package.json");
18714
- var log11 = createLogger("Server");
18956
+ var log12 = createLogger("Server");
18715
18957
  var DEFAULT_QUOTA = {
18716
18958
  maxConcurrency: 2,
18717
18959
  maxRequestsPerMinute: 10,
@@ -18742,7 +18984,7 @@ function summarizeProviderConfig(provider, config) {
18742
18984
  };
18743
18985
  }
18744
18986
  function hashApiKey(key) {
18745
- return crypto27.createHash("sha256").update(key).digest("hex");
18987
+ return crypto28.createHash("sha256").update(key).digest("hex");
18746
18988
  }
18747
18989
  function parseCookies2(header) {
18748
18990
  if (!header) return {};
@@ -18798,7 +19040,7 @@ function applyLegacyCredentials(rows, config) {
18798
19040
  }
18799
19041
  if (migratedGoogle > 0) {
18800
19042
  saveConfigPatch({ google: config.google });
18801
- log11.info("credentials.migrated", { type: "google", count: migratedGoogle });
19043
+ log12.info("credentials.migrated", { type: "google", count: migratedGoogle });
18802
19044
  }
18803
19045
  let migratedGa4 = 0;
18804
19046
  for (const row of rows.ga4) {
@@ -18816,7 +19058,7 @@ function applyLegacyCredentials(rows, config) {
18816
19058
  }
18817
19059
  if (migratedGa4 > 0) {
18818
19060
  saveConfigPatch({ ga4: config.ga4 });
18819
- log11.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
19061
+ log12.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
18820
19062
  }
18821
19063
  }
18822
19064
  async function createServer(opts) {
@@ -18848,11 +19090,11 @@ async function createServer(opts) {
18848
19090
  applyLegacyCredentials(legacyRows, opts.config);
18849
19091
  dropLegacyCredentialColumns(opts.db);
18850
19092
  } catch (err) {
18851
- log11.warn("credentials.migration.failed", {
19093
+ log12.warn("credentials.migration.failed", {
18852
19094
  error: err instanceof Error ? err.message : String(err)
18853
19095
  });
18854
19096
  }
18855
- log11.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
19097
+ log12.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
18856
19098
  const p = providers[k];
18857
19099
  return p?.apiKey || p?.baseUrl || p?.vertexProject;
18858
19100
  }) });
@@ -18900,7 +19142,7 @@ async function createServer(opts) {
18900
19142
  intelligenceService,
18901
19143
  (runId, projectId, result) => notifier.dispatchInsightWebhooks(runId, projectId, result),
18902
19144
  async ({ runId, projectId, insightCount, criticalOrHigh }) => {
18903
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq29(projects.id, projectId)).get();
19145
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq30(projects.id, projectId)).get();
18904
19146
  if (!project) return;
18905
19147
  sessionRegistry.queueFollowUp(project.name, {
18906
19148
  role: "user",
@@ -18994,7 +19236,7 @@ async function createServer(opts) {
18994
19236
  return removed;
18995
19237
  }
18996
19238
  };
18997
- const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto27.randomBytes(32).toString("hex");
19239
+ const googleStateSecret = process.env.GOOGLE_STATE_SECRET ?? crypto28.randomBytes(32).toString("hex");
18998
19240
  const googleConnectionStore = {
18999
19241
  listConnections: (domain) => listGoogleConnections(opts.config, domain),
19000
19242
  getConnection: (domain, connectionType) => getGoogleConnection(opts.config, domain, connectionType),
@@ -19040,11 +19282,11 @@ async function createServer(opts) {
19040
19282
  const apiPrefix = basePath ? `${basePath}api/v1` : "/api/v1";
19041
19283
  if (opts.config.apiKey) {
19042
19284
  const keyHash = hashApiKey(opts.config.apiKey);
19043
- const existing = opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, keyHash)).get();
19285
+ const existing = opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, keyHash)).get();
19044
19286
  if (!existing) {
19045
19287
  const prefix = opts.config.apiKey.slice(0, 12);
19046
19288
  opts.db.insert(apiKeys).values({
19047
- id: `key_${crypto27.randomBytes(8).toString("hex")}`,
19289
+ id: `key_${crypto28.randomBytes(8).toString("hex")}`,
19048
19290
  name: "default",
19049
19291
  keyHash,
19050
19292
  keyPrefix: prefix,
@@ -19068,7 +19310,7 @@ async function createServer(opts) {
19068
19310
  };
19069
19311
  const createSession = (apiKeyId) => {
19070
19312
  pruneExpiredSessions();
19071
- const sessionId = crypto27.randomBytes(32).toString("hex");
19313
+ const sessionId = crypto28.randomBytes(32).toString("hex");
19072
19314
  sessions.set(sessionId, {
19073
19315
  apiKeyId,
19074
19316
  expiresAt: Date.now() + SESSION_TTL_MS
@@ -19092,7 +19334,7 @@ async function createServer(opts) {
19092
19334
  };
19093
19335
  const getDefaultApiKey = () => {
19094
19336
  if (!opts.config.apiKey) return void 0;
19095
- return opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
19337
+ return opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, hashApiKey(opts.config.apiKey))).get();
19096
19338
  };
19097
19339
  const createPasswordSession = (reply) => {
19098
19340
  const key = getDefaultApiKey();
@@ -19149,12 +19391,12 @@ async function createServer(opts) {
19149
19391
  return reply.send({ authenticated: true });
19150
19392
  }
19151
19393
  if (apiKey) {
19152
- const key = opts.db.select().from(apiKeys).where(eq29(apiKeys.keyHash, hashApiKey(apiKey))).get();
19394
+ const key = opts.db.select().from(apiKeys).where(eq30(apiKeys.keyHash, hashApiKey(apiKey))).get();
19153
19395
  if (!key || key.revokedAt) {
19154
19396
  const err2 = authInvalid();
19155
19397
  return reply.status(err2.statusCode).send(err2.toJSON());
19156
19398
  }
19157
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq29(apiKeys.id, key.id)).run();
19399
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq30(apiKeys.id, key.id)).run();
19158
19400
  const sessionId = createSession(key.id);
19159
19401
  reply.header("set-cookie", serializeSessionCookie({
19160
19402
  name: SESSION_COOKIE_NAME,
@@ -19242,7 +19484,7 @@ async function createServer(opts) {
19242
19484
  deps: {
19243
19485
  enqueueAutoExtract: ({ projectId, release: r }) => {
19244
19486
  const now = (/* @__PURE__ */ new Date()).toISOString();
19245
- const runId = crypto27.randomUUID();
19487
+ const runId = crypto28.randomUUID();
19246
19488
  opts.db.insert(runs).values({
19247
19489
  id: runId,
19248
19490
  projectId,
@@ -19304,6 +19546,14 @@ async function createServer(opts) {
19304
19546
  googleSettingsSummary,
19305
19547
  bingSettingsSummary,
19306
19548
  bingConnectionStore,
19549
+ onBingInspectSitemapRequested: (runId, projectId, inspectOpts) => {
19550
+ executeBingInspectSitemap(opts.db, runId, projectId, {
19551
+ ...inspectOpts,
19552
+ config: opts.config
19553
+ }).catch((err) => {
19554
+ app.log.error({ runId, err }, "Bing inspect sitemap failed");
19555
+ });
19556
+ },
19307
19557
  wordpressConnectionStore,
19308
19558
  ga4CredentialStore,
19309
19559
  onRunCreated: (runId, projectId, providers2, location) => {
@@ -19368,7 +19618,7 @@ async function createServer(opts) {
19368
19618
  const targetProjectIds = affectedProjectIds.length > 0 ? affectedProjectIds : [null];
19369
19619
  const createdAt = (/* @__PURE__ */ new Date()).toISOString();
19370
19620
  opts.db.insert(auditLog).values(targetProjectIds.map((projectId) => ({
19371
- id: crypto27.randomUUID(),
19621
+ id: crypto28.randomUUID(),
19372
19622
  projectId,
19373
19623
  actor: "api",
19374
19624
  action: existing ? "provider.updated" : "provider.created",