@canonry/canonry 6.1.5 → 6.2.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.
Files changed (29) hide show
  1. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +1 -1
  2. package/assets/assets/{AuditHistoryPanel-D-W_kwEY.js → AuditHistoryPanel-CuEMfkaA.js} +1 -1
  3. package/assets/assets/{BacklinksPage-BeqCFRO3.js → BacklinksPage-CftadZ1-.js} +1 -1
  4. package/assets/assets/{HistoryPage-wAPbY0z4.js → HistoryPage-DouRgshN.js} +1 -1
  5. package/assets/assets/{MeasurementPropertyPage-BSOrkHCB.js → MeasurementPropertyPage-qfqv2qs9.js} +1 -1
  6. package/assets/assets/{ProjectPage-v0C7CwyA.js → ProjectPage-DqEVwe4t.js} +1 -1
  7. package/assets/assets/{RunsPage-CxolaIps.js → RunsPage-CUkMs0gB.js} +1 -1
  8. package/assets/assets/{SettingsPage-BQqJz0Ms.js → SettingsPage-LLQ40tSh.js} +1 -1
  9. package/assets/assets/{SiteHealthSection-C56kNcQQ.js → SiteHealthSection-Ba7foDiU.js} +3 -3
  10. package/assets/assets/{TrafficPage-BDmWdYWf.js → TrafficPage-Cu_ZR3--.js} +1 -1
  11. package/assets/assets/{TrafficSourceDetailPage-DZl3YNBs.js → TrafficSourceDetailPage-NKZq7jf9.js} +1 -1
  12. package/assets/assets/{extract-error-message-D0mBVEGn.js → extract-error-message-BdQYrz9N.js} +1 -1
  13. package/assets/assets/{index-DZ__RzGa.js → index-HOeLz1ZL.js} +20 -20
  14. package/assets/assets/{react-sigma_core.esm.min-BQNp33-g.js → react-sigma_core.esm.min-CZaoE5cU.js} +1 -1
  15. package/assets/assets/{v2-overview-adapter-XZbD7swG.js → v2-overview-adapter-BVx4nI49.js} +1 -1
  16. package/assets/index.html +1 -1
  17. package/dist/{chunk-4UEX44C3.js → chunk-2RFHQ6XT.js} +204 -75
  18. package/dist/{chunk-MSFBNFTI.js → chunk-4EKOKWQT.js} +2 -2
  19. package/dist/{chunk-J7LV4CS2.js → chunk-6EPN4XY5.js} +11 -0
  20. package/dist/{chunk-MTEVU6KG.js → chunk-ENI3GOWW.js} +2 -1
  21. package/dist/{chunk-FRBKYQN7.js → chunk-V6JUBTNL.js} +3 -3
  22. package/dist/{chunk-5QH72CTN.js → chunk-W3GDZZNF.js} +1 -1
  23. package/dist/{chunk-PAYC6TQ2.js → chunk-Y2B7JRSB.js} +3 -3
  24. package/dist/cli.js +10 -10
  25. package/dist/{demo-server-UGILQVEC.js → demo-server-CH7MOBXW.js} +4 -4
  26. package/dist/index.js +6 -6
  27. package/dist/{intelligence-service-JYGP6LME.js → intelligence-service-4IONMOFB.js} +2 -2
  28. package/dist/mcp.js +4 -4
  29. package/package.json +12 -12
@@ -19,21 +19,21 @@ import {
19
19
  runWithUsageTags,
20
20
  saveConfigPatch,
21
21
  sentimentInstallReadiness
22
- } from "./chunk-FRBKYQN7.js";
22
+ } from "./chunk-V6JUBTNL.js";
23
23
  import {
24
24
  computeFactorAverages,
25
25
  deleteSiteCrawlGraphLayout,
26
26
  persistSiteCrawlGraphLayout,
27
27
  prepareSiteCrawlGraphLayout,
28
28
  recordedSharePct
29
- } from "./chunk-MSFBNFTI.js";
29
+ } from "./chunk-4EKOKWQT.js";
30
30
  import {
31
31
  CanonryMcpToolNames,
32
32
  CliError,
33
33
  PACKAGE_VERSION,
34
34
  canonryMcpTools,
35
35
  isMachineFormat
36
- } from "./chunk-5QH72CTN.js";
36
+ } from "./chunk-W3GDZZNF.js";
37
37
  import {
38
38
  CC_CACHE_DIR,
39
39
  DUCKDB_SPEC,
@@ -214,7 +214,7 @@ import {
214
214
  siteLivenessState,
215
215
  toAlertView,
216
216
  usageCounters
217
- } from "./chunk-MTEVU6KG.js";
217
+ } from "./chunk-ENI3GOWW.js";
218
218
  import {
219
219
  AGENT_MEMORY_VALUE_MAX_BYTES,
220
220
  AGENT_NONE,
@@ -304,6 +304,7 @@ import {
304
304
  canonicalizeGtmAccountId,
305
305
  canonicalizeGtmContainerId,
306
306
  canonicalizeGtmResourceSelection,
307
+ classifyProviderErrorMessage,
307
308
  classifyProviderErrorMessages,
308
309
  classifyTemplateLinkEdge,
309
310
  cloudflareQueueNameSchema,
@@ -327,6 +328,7 @@ import {
327
328
  effectiveBrandNames,
328
329
  effectiveDomains,
329
330
  embedClientConfigForRequest,
331
+ extractProviderHttpStatus,
330
332
  factorStatusFromScore,
331
333
  filterCapturedCitedUrls,
332
334
  forbidden,
@@ -414,7 +416,7 @@ import {
414
416
  visibilityReportRequestSchema,
415
417
  winnabilityClassLabel,
416
418
  withRetry
417
- } from "./chunk-J7LV4CS2.js";
419
+ } from "./chunk-6EPN4XY5.js";
418
420
 
419
421
  // src/runtime-context.ts
420
422
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -4053,6 +4055,19 @@ import os4 from "os";
4053
4055
  import { GoogleGenAI } from "@google/genai";
4054
4056
 
4055
4057
  // ../provider-gemini/src/utils.ts
4058
+ var GEMINI_MAX_RATE_LIMIT_WAIT_MS = 65e3;
4059
+ function geminiRetryDelayMs(err) {
4060
+ const match = /"retryDelay"\s*:\s*"(\d+(?:\.\d+)?)s"/.exec(describeError(err));
4061
+ return match ? Math.round(Number(match[1]) * 1e3) : null;
4062
+ }
4063
+ function isGeminiDailyQuotaError(err) {
4064
+ return /"quotaId"\s*:\s*"[^"]*PerDay/i.test(describeError(err));
4065
+ }
4066
+ function isGeminiRetryable(err) {
4067
+ if (!isRetryableHttpError(err) || isGeminiDailyQuotaError(err)) return false;
4068
+ const asked = geminiRetryDelayMs(err);
4069
+ return asked === null || asked <= GEMINI_MAX_RATE_LIMIT_WAIT_MS;
4070
+ }
4056
4071
  async function withRetry2(fn, options = {}) {
4057
4072
  return withRetry(fn, {
4058
4073
  maxRetries: options.maxRetries ?? 3,
@@ -4061,7 +4076,12 @@ async function withRetry2(fn, options = {}) {
4061
4076
  // provider has shipped to date (1000, 2000, 4000ms). Enable per-caller
4062
4077
  // if you need it.
4063
4078
  jitter: false,
4064
- isRetryable: isRetryableHttpError,
4079
+ isRetryable: isGeminiRetryable,
4080
+ computeDelayMs: (_attempt, err, defaultMs) => {
4081
+ const asked = geminiRetryDelayMs(err);
4082
+ return asked === null ? defaultMs : asked + 500;
4083
+ },
4084
+ ...options.sleep ? { sleep: options.sleep } : {},
4065
4085
  onRetry: ({ attempt, err, delayMs }) => {
4066
4086
  console.warn(
4067
4087
  `[provider] Attempt ${attempt + 1} failed, retrying in ${delayMs}ms...`,
@@ -4072,7 +4092,7 @@ async function withRetry2(fn, options = {}) {
4072
4092
  }
4073
4093
 
4074
4094
  // ../provider-gemini/src/normalize.ts
4075
- var DEFAULT_MODEL = "gemini-2.5-flash";
4095
+ var DEFAULT_MODEL = "gemini-flash-latest";
4076
4096
  function isVertexConfig(config) {
4077
4097
  return !!config.vertexProject;
4078
4098
  }
@@ -4515,10 +4535,20 @@ var geminiAdapter = {
4515
4535
  keyUrl: "https://aistudio.google.com/apikey",
4516
4536
  // Upstream model list: https://ai.google.dev/gemini-api/docs/models
4517
4537
  modelRegistry: {
4518
- defaultModel: "gemini-2.5-flash",
4538
+ defaultModel: "gemini-flash-latest",
4519
4539
  validationPattern: /./,
4520
- validationHint: "any valid Google model name (e.g. gemini-2.5-flash, learnlm-1.5-pro-experimental)",
4540
+ validationHint: "any valid Google model name (e.g. gemini-flash-latest, gemini-3.8-flash)",
4521
4541
  knownModels: [
4542
+ // Google's moving aliases: always the newest Flash / Flash-Lite, with
4543
+ // 2 weeks' email notice before a breaking change. The model actually
4544
+ // served is recorded per snapshot (`servedModel`), so trends still show
4545
+ // when the model behind an alias changed.
4546
+ { id: "gemini-flash-latest", displayName: "Gemini Flash (latest)", tier: "standard" },
4547
+ { id: "gemini-flash-lite-latest", displayName: "Gemini Flash-Lite (latest)", tier: "economy" },
4548
+ { id: "gemini-3.8-flash", displayName: "Gemini 3.8 Flash", tier: "standard" },
4549
+ { id: "gemini-3.5-flash-lite", displayName: "Gemini 3.5 Flash-Lite", tier: "economy" },
4550
+ // Google limits the 2.5 models to projects that already used them, so a
4551
+ // new key may be refused. Listed for installs that still pin them.
4522
4552
  { id: "gemini-2.5-pro", displayName: "Gemini 2.5 Pro", tier: "flagship" },
4523
4553
  { id: "gemini-2.5-flash", displayName: "Gemini 2.5 Flash", tier: "standard" },
4524
4554
  { id: "gemini-2.5-flash-lite", displayName: "Gemini 2.5 Flash-Lite", tier: "economy" },
@@ -7830,7 +7860,7 @@ function nonBlank(value) {
7830
7860
  return trimmed ? trimmed : void 0;
7831
7861
  }
7832
7862
  function resolveBuildCommit(env = process.env) {
7833
- const embedded = true ? "07710f04d6bdd8ec8b4374a93f0f3c3d1b56f498" : void 0;
7863
+ const embedded = true ? "633bd63832cf6af1da610e63f376dfc872975b49" : void 0;
7834
7864
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
7835
7865
  }
7836
7866
  function resolveInstanceIdentity(env = process.env) {
@@ -7846,7 +7876,7 @@ import fs4 from "fs";
7846
7876
  import path3 from "path";
7847
7877
  import os3 from "os";
7848
7878
  import { setImmediate as yieldToEventLoop } from "timers/promises";
7849
- import { and as and3, asc as asc2, count, eq as eq3, inArray, isNotNull, isNull, ne, sql as sql2 } from "drizzle-orm";
7879
+ import { and as and3, asc as asc2, count, desc, eq as eq3, inArray, isNotNull, isNull, lt, ne, sql as sql2 } from "drizzle-orm";
7850
7880
 
7851
7881
  // src/run-telemetry.ts
7852
7882
  import crypto4 from "crypto";
@@ -7911,6 +7941,51 @@ function buildSiteAuditCompletedProps(input) {
7911
7941
  if (crawl.checkDeadLinks) props.deadLinksFound = crawl.deadLinksFound;
7912
7942
  return props;
7913
7943
  }
7944
+ var MAX_NESTED_KEYS = 12;
7945
+ function buildProviderOutcomeProps(providers, providerErrors) {
7946
+ if (providerErrors.size === 0) return {};
7947
+ const names = [.../* @__PURE__ */ new Set([...providers, ...providerErrors.keys()])].slice(0, MAX_NESTED_KEYS);
7948
+ const providerOutcomes = {};
7949
+ const providerHttpStatus = {};
7950
+ for (const name of names) {
7951
+ const message = providerErrors.get(name);
7952
+ if (message === void 0) {
7953
+ providerOutcomes[name] = "ok";
7954
+ continue;
7955
+ }
7956
+ providerOutcomes[name] = classifyProviderErrorMessage(message);
7957
+ const status = extractProviderHttpStatus(message);
7958
+ if (status !== void 0) providerHttpStatus[name] = status;
7959
+ }
7960
+ return Object.keys(providerHttpStatus).length > 0 ? { providerOutcomes, providerHttpStatus } : { providerOutcomes };
7961
+ }
7962
+ function runFailureSite(providerCallStart, providerCallEnd) {
7963
+ if (providerCallStart === void 0) return "setup";
7964
+ return providerCallEnd === void 0 ? "provider_call" : "finalize";
7965
+ }
7966
+ var ERROR_NAME_RE = /^[a-z_$][\w$]{0,39}$/i;
7967
+ var ERROR_SYS_CODE_RE = /^[A-Z][A-Z0-9_]{1,39}$/;
7968
+ function describeRunFailure(err, site) {
7969
+ const props = site ? { errorSite: site } : {};
7970
+ const name = err instanceof Error ? err.name : typeof err;
7971
+ if (ERROR_NAME_RE.test(name)) props.errorName = name;
7972
+ const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
7973
+ if (typeof code === "string" && ERROR_SYS_CODE_RE.test(code)) props.errorSysCode = code;
7974
+ return props;
7975
+ }
7976
+ var FAILURE_STREAK_SAMPLE_AFTER = 5;
7977
+ var FAILURE_STREAK_SAMPLE_RATE = 20;
7978
+ function failureStreakSampling(status, priorStreak, runId) {
7979
+ if (status !== "failed" && status !== "partial") return { report: true, props: {} };
7980
+ if (priorStreak < FAILURE_STREAK_SAMPLE_AFTER) {
7981
+ return { report: true, props: { failureStreak: priorStreak } };
7982
+ }
7983
+ const bucket = crypto4.createHash("sha256").update(runId).digest().readUInt32BE(0) % FAILURE_STREAK_SAMPLE_RATE;
7984
+ return {
7985
+ report: bucket === 0,
7986
+ props: { failureStreak: priorStreak, sampleRate: FAILURE_STREAK_SAMPLE_RATE }
7987
+ };
7988
+ }
7914
7989
 
7915
7990
  // src/provider-execution-gate.ts
7916
7991
  var ProviderExecutionGate = class {
@@ -8259,6 +8334,7 @@ function classifyRunAbortReason(message) {
8259
8334
  if (/^Run [^ ]+ is not executable\b/.test(message)) return "run_not_executable";
8260
8335
  return void 0;
8261
8336
  }
8337
+ var FAILURE_STREAK_LOOKBACK = 50;
8262
8338
  function classifyProviderErrors(errors) {
8263
8339
  return classifyProviderErrorMessages(errors.values());
8264
8340
  }
@@ -8853,21 +8929,28 @@ var JobRunner = class {
8853
8929
  ...executionContext.location ? { location: executionContext.location } : {}
8854
8930
  });
8855
8931
  } else {
8856
- trackEvent(
8857
- "run.completed",
8858
- buildRunCompletedProps({
8859
- status: "failed",
8860
- providerCount: executionContext.providerCount,
8861
- providers: executionContext.providers,
8862
- queryCount: executionContext.queryCount,
8863
- startTime,
8864
- trigger: executionContext.trigger,
8865
- canonicalDomain: executionContext.canonicalDomain,
8866
- phases,
8867
- location: executionContext.location
8868
- }),
8869
- { errorCode: "UNKNOWN" }
8870
- );
8932
+ const sampling = failureStreakSampling("failed", this.priorFailureStreak(projectId, runId), runId);
8933
+ if (sampling.report) {
8934
+ trackEvent(
8935
+ "run.completed",
8936
+ {
8937
+ ...buildRunCompletedProps({
8938
+ status: "failed",
8939
+ providerCount: executionContext.providerCount,
8940
+ providers: executionContext.providers,
8941
+ queryCount: executionContext.queryCount,
8942
+ startTime,
8943
+ trigger: executionContext.trigger,
8944
+ canonicalDomain: executionContext.canonicalDomain,
8945
+ phases,
8946
+ location: executionContext.location
8947
+ }),
8948
+ ...describeRunFailure(err, runFailureSite(providerCallStart, providerCallEnd)),
8949
+ ...sampling.props
8950
+ },
8951
+ { errorCode: "INTERNAL" }
8952
+ );
8953
+ }
8871
8954
  }
8872
8955
  if (this.onRunCompleted) {
8873
8956
  this.onRunCompleted(runId, projectId).catch((notifErr) => {
@@ -8931,21 +9014,28 @@ var JobRunner = class {
8931
9014
  reportFinalizedRun(input, finalStatus) {
8932
9015
  const { runId, projectId, providerErrors, executionContext } = input;
8933
9016
  const failureCode = providerErrors.size > 0 ? classifyProviderErrors(providerErrors) : void 0;
8934
- trackEvent(
8935
- "run.completed",
8936
- buildRunCompletedProps({
8937
- status: finalStatus,
8938
- providerCount: executionContext.providerCount,
8939
- providers: executionContext.providers,
8940
- queryCount: executionContext.queryCount,
8941
- startTime: input.startTime,
8942
- trigger: executionContext.trigger,
8943
- canonicalDomain: executionContext.canonicalDomain,
8944
- phases: input.phases,
8945
- location: executionContext.location
8946
- }),
8947
- failureCode ? { errorCode: failureCode } : void 0
8948
- );
9017
+ const sampling = failureStreakSampling(finalStatus, this.priorFailureStreak(projectId, runId), runId);
9018
+ if (sampling.report) {
9019
+ trackEvent(
9020
+ "run.completed",
9021
+ {
9022
+ ...buildRunCompletedProps({
9023
+ status: finalStatus,
9024
+ providerCount: executionContext.providerCount,
9025
+ providers: executionContext.providers,
9026
+ queryCount: executionContext.queryCount,
9027
+ startTime: input.startTime,
9028
+ trigger: executionContext.trigger,
9029
+ canonicalDomain: executionContext.canonicalDomain,
9030
+ phases: input.phases,
9031
+ location: executionContext.location
9032
+ }),
9033
+ ...buildProviderOutcomeProps(executionContext.providers, providerErrors),
9034
+ ...sampling.props
9035
+ },
9036
+ failureCode ? { errorCode: failureCode } : void 0
9037
+ );
9038
+ }
8949
9039
  if (input.kind === RunKinds["answer-visibility"] && executionContext.trigger !== RunTriggers.probe && input.inserted > 0 && !this.hasPriorActivation(projectId, runId)) {
8950
9040
  trackEvent("activation.completed", {
8951
9041
  flowVersion: ONBOARDING_FLOW_VERSION,
@@ -10008,6 +10098,36 @@ var JobRunner = class {
10008
10098
  }
10009
10099
  providerReservations.clear();
10010
10100
  }
10101
+ /**
10102
+ * How many runs of the same kind, created before this one, failed or came
10103
+ * back partial in a row. Telemetry only: it feeds `failureStreak` and the
10104
+ * sampling of runs stuck failing the same way. Probes do not count, other run
10105
+ * kinds (syncs, audits) neither break nor extend a streak, and a run aborted
10106
+ * before any provider was called (`run.aborted`, never a failed
10107
+ * `run.completed`) is skipped, so every failure counted was also reported.
10108
+ */
10109
+ priorFailureStreak(projectId, currentRunId) {
10110
+ try {
10111
+ const current = this.db.select({ kind: runs.kind, createdAt: runs.createdAt }).from(runs).where(eq3(runs.id, currentRunId)).get();
10112
+ if (!current) return 0;
10113
+ const recent = this.db.select({ status: runs.status, error: runs.error }).from(runs).where(and3(
10114
+ eq3(runs.projectId, projectId),
10115
+ eq3(runs.kind, current.kind),
10116
+ lt(runs.createdAt, current.createdAt),
10117
+ ne(runs.trigger, RunTriggers.probe),
10118
+ inArray(runs.status, ["completed", "partial", "failed"])
10119
+ )).orderBy(desc(runs.createdAt)).limit(FAILURE_STREAK_LOOKBACK).all();
10120
+ let streak = 0;
10121
+ for (const row of recent) {
10122
+ if (row.status === "completed") break;
10123
+ if (row.status === "failed" && row.error && classifyRunAbortReason(row.error)) continue;
10124
+ streak++;
10125
+ }
10126
+ return streak;
10127
+ } catch {
10128
+ return 0;
10129
+ }
10130
+ }
10011
10131
  hasPriorActivation(projectId, currentRunId) {
10012
10132
  return this.db.select({ id: querySnapshots.id }).from(querySnapshots).innerJoin(runs, eq3(querySnapshots.runId, runs.id)).where(and3(
10013
10133
  eq3(runs.projectId, projectId),
@@ -10099,7 +10219,7 @@ import { eq as eq5, and as and5, sql as sql3 } from "drizzle-orm";
10099
10219
 
10100
10220
  // src/gsc-coverage-snapshot.ts
10101
10221
  import crypto7 from "crypto";
10102
- import { and as and4, desc, eq as eq4, gte } from "drizzle-orm";
10222
+ import { and as and4, desc as desc2, eq as eq4, gte } from "drizzle-orm";
10103
10223
  var COVERAGE_IMPRESSION_WINDOW_DAYS = 30;
10104
10224
  function pageKey(raw) {
10105
10225
  if (!raw) return null;
@@ -10123,9 +10243,9 @@ function writeCoverageSnapshot(db, projectId, runId, opts = {}) {
10123
10243
  eq4(siteCrawlSnapshots.projectId, projectId),
10124
10244
  eq4(siteCrawlSnapshots.complete, true),
10125
10245
  eq4(runs.status, "completed")
10126
- )).orderBy(desc(siteCrawlSnapshots.createdAt)).limit(1).get();
10246
+ )).orderBy(desc2(siteCrawlSnapshots.createdAt)).limit(1).get();
10127
10247
  const hasCrawlHistory = Boolean(db.select({ id: siteCrawlSnapshots.id }).from(siteCrawlSnapshots).where(eq4(siteCrawlSnapshots.projectId, projectId)).limit(1).get());
10128
- const latestAudit = db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).where(eq4(siteAuditSnapshots.projectId, projectId)).orderBy(desc(siteAuditSnapshots.createdAt)).limit(1).get();
10248
+ const latestAudit = db.select({ runId: siteAuditSnapshots.runId }).from(siteAuditSnapshots).where(eq4(siteAuditSnapshots.projectId, projectId)).orderBy(desc2(siteAuditSnapshots.createdAt)).limit(1).get();
10129
10249
  const sitemapUrls = latestCrawl?.attemptId ? db.select({ url: siteCrawlPages.url }).from(siteCrawlPages).where(and4(
10130
10250
  eq4(siteCrawlPages.projectId, projectId),
10131
10251
  eq4(siteCrawlPages.runId, latestCrawl.runId),
@@ -10346,11 +10466,11 @@ async function executeGscSync(db, runId, projectId, opts) {
10346
10466
 
10347
10467
  // src/gbp-sync.ts
10348
10468
  import crypto10 from "crypto";
10349
- import { eq as eq7, and as and7, desc as desc3, inArray as inArray3, lt } from "drizzle-orm";
10469
+ import { eq as eq7, and as and7, desc as desc4, inArray as inArray3, lt as lt2 } from "drizzle-orm";
10350
10470
 
10351
10471
  // src/gbp-reviews.ts
10352
10472
  import crypto9 from "crypto";
10353
- import { and as and6, desc as desc2, eq as eq6, inArray as inArray2, sql as sql4 } from "drizzle-orm";
10473
+ import { and as and6, desc as desc3, eq as eq6, inArray as inArray2, sql as sql4 } from "drizzle-orm";
10354
10474
  var log4 = createLogger("GbpReviews");
10355
10475
  var MS_PER_DAY = 864e5;
10356
10476
  var REVIEW_ALERT_MAX_AGE_DAYS = 30;
@@ -10382,7 +10502,7 @@ function placesReviewsDue(db, projectId, locationName, now) {
10382
10502
  eq6(gbpReviewRatings.projectId, projectId),
10383
10503
  eq6(gbpReviewRatings.locationName, locationName),
10384
10504
  eq6(gbpReviewRatings.origin, "places")
10385
- )).orderBy(desc2(gbpReviewRatings.observedAt)).limit(1).get();
10505
+ )).orderBy(desc3(gbpReviewRatings.observedAt)).limit(1).get();
10386
10506
  if (!latest) return true;
10387
10507
  return now.getTime() - Date.parse(latest.observedAt) >= PLACES_REVIEW_MIN_INTERVAL_HOURS * 36e5;
10388
10508
  }
@@ -10463,7 +10583,7 @@ function persistReviewObservation(tx, projectId, runId, obs, ctx) {
10463
10583
  eq6(gbpReviewRatings.projectId, projectId),
10464
10584
  eq6(gbpReviewRatings.locationName, obs.locationName),
10465
10585
  eq6(gbpReviewRatings.origin, obs.origin)
10466
- )).orderBy(desc2(gbpReviewRatings.observedAt)).limit(1).get();
10586
+ )).orderBy(desc3(gbpReviewRatings.observedAt)).limit(1).get();
10467
10587
  const baseline = latestRating === void 0;
10468
10588
  let queuedRatingDrop = false;
10469
10589
  if (!latestRating) {
@@ -10669,15 +10789,15 @@ async function executeGbpSync(db, runId, projectId, opts) {
10669
10789
  fetchLocationReviews(db, projectId, loc, reviewContext)
10670
10790
  ]);
10671
10791
  const lodgingHash = lodging ? hashLodging(lodging) : null;
10672
- const latestLodging = lodging ? db.select().from(gbpLodgingSnapshots).where(and7(eq7(gbpLodgingSnapshots.projectId, projectId), eq7(gbpLodgingSnapshots.locationName, loc.locationName))).orderBy(desc3(gbpLodgingSnapshots.syncedAt)).limit(1).get() : void 0;
10792
+ const latestLodging = lodging ? db.select().from(gbpLodgingSnapshots).where(and7(eq7(gbpLodgingSnapshots.projectId, projectId), eq7(gbpLodgingSnapshots.locationName, loc.locationName))).orderBy(desc4(gbpLodgingSnapshots.syncedAt)).limit(1).get() : void 0;
10673
10793
  const lodgingChanged = lodging !== null && latestLodging?.contentHash !== lodgingHash;
10674
10794
  const attributesHash = hashAttributes(attributes);
10675
- const latestAttributes = db.select().from(gbpAttributesSnapshots).where(and7(eq7(gbpAttributesSnapshots.projectId, projectId), eq7(gbpAttributesSnapshots.locationName, loc.locationName))).orderBy(desc3(gbpAttributesSnapshots.syncedAt)).limit(1).get();
10795
+ const latestAttributes = db.select().from(gbpAttributesSnapshots).where(and7(eq7(gbpAttributesSnapshots.projectId, projectId), eq7(gbpAttributesSnapshots.locationName, loc.locationName))).orderBy(desc4(gbpAttributesSnapshots.syncedAt)).limit(1).get();
10676
10796
  const attributesChanged = latestAttributes?.contentHash !== attributesHash;
10677
10797
  let placeToWrite = null;
10678
10798
  let placeToTouch = null;
10679
10799
  if (placesTier !== "off" && placesApiKey && lodging !== null && loc.placeId) {
10680
- const latestPlace = db.select().from(gbpPlaceDetails).where(and7(eq7(gbpPlaceDetails.projectId, projectId), eq7(gbpPlaceDetails.locationName, loc.locationName))).orderBy(desc3(gbpPlaceDetails.syncedAt)).limit(1).get();
10800
+ const latestPlace = db.select().from(gbpPlaceDetails).where(and7(eq7(gbpPlaceDetails.projectId, projectId), eq7(gbpPlaceDetails.locationName, loc.locationName))).orderBy(desc4(gbpPlaceDetails.syncedAt)).limit(1).get();
10681
10801
  const ageDays = latestPlace ? (Date.now() - new Date(latestPlace.syncedAt).getTime()) / MS_PER_DAY2 : Infinity;
10682
10802
  if (ageDays >= placesRefreshDays) {
10683
10803
  try {
@@ -10747,7 +10867,7 @@ async function executeGbpSync(db, runId, projectId, opts) {
10747
10867
  tx.delete(gbpKeywordMonthly).where(and7(
10748
10868
  eq7(gbpKeywordMonthly.projectId, projectId),
10749
10869
  eq7(gbpKeywordMonthly.locationName, loc.locationName),
10750
- lt(gbpKeywordMonthly.month, keywordRetentionCutoff)
10870
+ lt2(gbpKeywordMonthly.month, keywordRetentionCutoff)
10751
10871
  )).run();
10752
10872
  }
10753
10873
  tx.delete(gbpPlaceActions).where(and7(eq7(gbpPlaceActions.projectId, projectId), eq7(gbpPlaceActions.locationName, loc.locationName))).run();
@@ -12075,7 +12195,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
12075
12195
 
12076
12196
  // src/bing-inspect-sitemap.ts
12077
12197
  import crypto14 from "crypto";
12078
- import { eq as eq10, desc as desc4 } from "drizzle-orm";
12198
+ import { eq as eq10, desc as desc5 } from "drizzle-orm";
12079
12199
  var log9 = createLogger("BingInspectSitemap");
12080
12200
  var BING_INSPECT_SPACING_MS = 2e3;
12081
12201
  function parseBingDate(value) {
@@ -12239,7 +12359,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12239
12359
  `Bing inspection failed for every URL attempted (${errors} of ${targetUrls.length}); coverage was left unchanged rather than re-dated from stored inspections. Last error: ${detail}`
12240
12360
  );
12241
12361
  }
12242
- const allInspections = db.select().from(bingUrlInspections).where(eq10(bingUrlInspections.projectId, projectId)).orderBy(desc4(bingUrlInspections.inspectedAt)).all();
12362
+ const allInspections = db.select().from(bingUrlInspections).where(eq10(bingUrlInspections.projectId, projectId)).orderBy(desc5(bingUrlInspections.inspectedAt)).all();
12243
12363
  const latestByUrl = /* @__PURE__ */ new Map();
12244
12364
  const definitiveByUrl = /* @__PURE__ */ new Map();
12245
12365
  for (const row of allInspections) {
@@ -12305,7 +12425,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
12305
12425
 
12306
12426
  // src/coverage-refresh.ts
12307
12427
  import crypto15 from "crypto";
12308
- import { and as and9, desc as desc5, eq as eq11, inArray as inArray5 } from "drizzle-orm";
12428
+ import { and as and9, desc as desc6, eq as eq11, inArray as inArray5 } from "drizzle-orm";
12309
12429
  var log10 = createLogger("CoverageRefresh");
12310
12430
  var COVERAGE_REFRESH_MIN_INTERVAL_MS = 60 * 60 * 1e3;
12311
12431
  var COVERAGE_REFRESH_MANUAL_MIN_INTERVAL_MS = 2 * 60 * 1e3;
@@ -12334,7 +12454,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12334
12454
  eq11(runs.kind, RunKinds["inspect-sitemap"]),
12335
12455
  inArray5(runs.status, IN_FLIGHT_STATUSES)
12336
12456
  )
12337
- ).orderBy(desc5(runs.createdAt)).limit(1).get();
12457
+ ).orderBy(desc6(runs.createdAt)).limit(1).get();
12338
12458
  if (inFlight2) {
12339
12459
  log10.info("skip.in-flight", { projectId });
12340
12460
  return null;
@@ -12346,7 +12466,7 @@ async function maybeRefreshGscCoverage(db, config, projectId, deps = defaultDeps
12346
12466
  eq11(runs.kind, RunKinds["inspect-sitemap"]),
12347
12467
  inArray5(runs.status, ACTIVE_OR_DONE_STATUSES)
12348
12468
  )
12349
- ).orderBy(desc5(runs.createdAt)).limit(1).get();
12469
+ ).orderBy(desc6(runs.createdAt)).limit(1).get();
12350
12470
  if (recent) {
12351
12471
  const ageMs = nowMs - Date.parse(recent.createdAt);
12352
12472
  if (Number.isFinite(ageMs) && ageMs < windowMs) {
@@ -12571,7 +12691,7 @@ function computeSummary(rows) {
12571
12691
  // src/backlink-extract.ts
12572
12692
  import crypto17 from "crypto";
12573
12693
  import fs5 from "fs";
12574
- import { and as and11, desc as desc6, eq as eq13 } from "drizzle-orm";
12694
+ import { and as and11, desc as desc7, eq as eq13 } from "drizzle-orm";
12575
12695
  var log12 = createLogger("BacklinkExtract");
12576
12696
  function defaultDeps3() {
12577
12697
  return {
@@ -12589,7 +12709,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
12589
12709
  if (!project) {
12590
12710
  throw new Error(`Project not found: ${projectId}`);
12591
12711
  }
12592
- const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc6(ccReleaseSyncs.createdAt)).limit(1).get();
12712
+ const sync = opts.release ? db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.release, opts.release)).get() : db.select().from(ccReleaseSyncs).where(eq13(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)).orderBy(desc7(ccReleaseSyncs.createdAt)).limit(1).get();
12593
12713
  if (!sync) {
12594
12714
  throw new Error("No ready release sync available \u2014 run `canonry backlinks sync` first");
12595
12715
  }
@@ -13493,10 +13613,13 @@ function deadLinkCheckedCount(edges, pages) {
13493
13613
  }
13494
13614
  function trackSiteAuditOutcome(db, input) {
13495
13615
  try {
13496
- const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "UNKNOWN" : void 0;
13616
+ const errorCode = input.status === "cancelled" ? "RUN_CANCELLED" : input.status === "failed" ? "INTERNAL" : void 0;
13497
13617
  trackEvent(
13498
13618
  "site_audit.completed",
13499
- buildSiteAuditCompletedProps(input),
13619
+ {
13620
+ ...buildSiteAuditCompletedProps(input),
13621
+ ...input.status === "failed" ? describeRunFailure(input.error) : {}
13622
+ },
13500
13623
  errorCode ? { errorCode } : void 0
13501
13624
  );
13502
13625
  const pagesAudited = input.crawl?.pagesAudited ?? 0;
@@ -14063,7 +14186,8 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
14063
14186
  status: cancelled ? "cancelled" : "failed",
14064
14187
  startTime,
14065
14188
  trigger,
14066
- canonicalDomain
14189
+ canonicalDomain,
14190
+ error
14067
14191
  });
14068
14192
  throw error;
14069
14193
  }
@@ -14071,7 +14195,7 @@ async function executeSiteAudit(db, runId, projectId, opts = {}) {
14071
14195
 
14072
14196
  // src/commands/backfill.ts
14073
14197
  import { setImmediate as yieldToEventLoop2 } from "timers/promises";
14074
- import { and as and15, asc as asc4, desc as desc7, eq as eq17, gt as gt3, inArray as inArray7, isNull as isNull2, lte as lte2, sql as sql7 } from "drizzle-orm";
14198
+ import { and as and15, asc as asc4, desc as desc8, eq as eq17, gt as gt3, inArray as inArray7, isNull as isNull2, lte as lte2, sql as sql7 } from "drizzle-orm";
14075
14199
  var SNAPSHOT_BATCH_SIZE = 500;
14076
14200
  var PATH_REPAIR_PAGE_SIZE = 128;
14077
14201
  async function backfillAnswerVisibilityCommand(opts) {
@@ -14259,7 +14383,7 @@ function backfillNormalizedPaths(db, opts) {
14259
14383
  async function repairStoredPaths(db, table, opts) {
14260
14384
  const pageSize = opts?.pageSize ?? PATH_REPAIR_PAGE_SIZE;
14261
14385
  const projectScope = opts?.projectId ? sql7`+${table.projectId} = ${opts.projectId}` : void 0;
14262
- const lastId = db.select({ id: table.id }).from(table).where(projectScope).orderBy(desc7(table.id)).limit(1).get()?.id;
14386
+ const lastId = db.select({ id: table.id }).from(table).where(projectScope).orderBy(desc8(table.id)).limit(1).get()?.id;
14263
14387
  if (lastId === void 0) return { examined: 0, updated: 0, unchanged: 0 };
14264
14388
  let cursor;
14265
14389
  let examined = 0;
@@ -14549,7 +14673,7 @@ function readStoredGroundingSources(rawResponse) {
14549
14673
  return result2;
14550
14674
  }
14551
14675
  async function backfillInsightsCommand(project, opts) {
14552
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-JYGP6LME.js");
14676
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-4IONMOFB.js");
14553
14677
  const config = loadConfig();
14554
14678
  const db = createClient(config.database);
14555
14679
  migrate(db);
@@ -15966,7 +16090,7 @@ async function refreshAllIntegrations(client, projectName, now = /* @__PURE__ */
15966
16090
  }
15967
16091
 
15968
16092
  // src/notifier.ts
15969
- import { eq as eq20, desc as desc8, and as and18, inArray as inArray10, or } from "drizzle-orm";
16093
+ import { eq as eq20, desc as desc9, and as and18, inArray as inArray10, or } from "drizzle-orm";
15970
16094
  import crypto21 from "crypto";
15971
16095
  var log19 = createLogger("Notifier");
15972
16096
  var MATERIAL_MAGNITUDE_DELTA = 20;
@@ -16507,7 +16631,7 @@ var Notifier = class {
16507
16631
  eq20(runs.kind, thisRun.kind),
16508
16632
  or(eq20(runs.status, "completed"), eq20(runs.status, "partial"))
16509
16633
  )
16510
- ).orderBy(desc8(runs.createdAt), desc8(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16634
+ ).orderBy(desc9(runs.createdAt), desc9(runs.id)).limit(RECENT_FETCH_LIMIT).all();
16511
16635
  const groups = groupRunsByCreatedAt(recentRuns);
16512
16636
  const currentGroupIdx = groups.findIndex((g) => g[0]?.createdAt === thisRun.createdAt);
16513
16637
  if (currentGroupIdx < 0) return [];
@@ -16828,12 +16952,17 @@ var PROVIDER_MODELS = {
16828
16952
  [LlmCapabilities.classify]: "gpt-5-nano"
16829
16953
  },
16830
16954
  [AgentProviderIds.gemini]: {
16831
- // Gemini's 2.5-flash is already cheap + capable; flash-lite is the
16955
+ // Gemini's Flash is already cheap + capable; Flash-Lite is the
16832
16956
  // dedicated micro tier for classify. Analyze stays on flash because
16833
16957
  // flash-lite drops too much quality for structured synthesis.
16834
- [LlmCapabilities.agent]: "gemini-2.5-flash",
16835
- [LlmCapabilities.analyze]: "gemini-2.5-flash",
16836
- [LlmCapabilities.classify]: "gemini-2.5-flash-lite"
16958
+ // Exact IDs, never Google's `-latest` aliases: pi-ai derives request
16959
+ // options (thinking level) from the catalog entry for the ID it is given,
16960
+ // and an alias entry can lag the model it serves. `gemini-flash-latest`
16961
+ // allows MINIMAL thinking while the 3.8 Flash it resolves to rejects it,
16962
+ // so every Aero turn failed. Sweeps can use the alias; Aero cannot.
16963
+ [LlmCapabilities.agent]: "gemini-3.8-flash",
16964
+ [LlmCapabilities.analyze]: "gemini-3.8-flash",
16965
+ [LlmCapabilities.classify]: "gemini-3.5-flash-lite"
16837
16966
  },
16838
16967
  [AgentProviderIds.zai]: {
16839
16968
  // GLM lineage: 5.2 replaced 5.1, which pi-ai 0.87 dropped from its
@@ -18726,7 +18855,7 @@ async function loadExternalMcpTools(servers, opts = {}) {
18726
18855
 
18727
18856
  // src/agent/memory-store.ts
18728
18857
  import crypto24 from "crypto";
18729
- import { and as and19, desc as desc9, eq as eq22, like, sql as sql10, or as or2 } from "drizzle-orm";
18858
+ import { and as and19, desc as desc10, eq as eq22, like, sql as sql10, or as or2 } from "drizzle-orm";
18730
18859
  var COMPACTION_KEY_PREFIX = "compaction:";
18731
18860
  var COMPACTION_NOTES_PER_SESSION = 3;
18732
18861
  function rowToDto(row) {
@@ -18740,7 +18869,7 @@ function rowToDto(row) {
18740
18869
  };
18741
18870
  }
18742
18871
  function listMemoryEntries(db, projectId, opts = {}) {
18743
- const query = db.select().from(agentMemory).where(eq22(agentMemory.projectId, projectId)).orderBy(desc9(agentMemory.updatedAt));
18872
+ const query = db.select().from(agentMemory).where(eq22(agentMemory.projectId, projectId)).orderBy(desc10(agentMemory.updatedAt));
18744
18873
  const rows = opts.limit === void 0 ? query.all() : query.limit(opts.limit).all();
18745
18874
  return rows.map(rowToDto);
18746
18875
  }
@@ -18786,7 +18915,7 @@ function loadRecentForHydrate(db, projectId, limit, sessionId) {
18786
18915
  return db.select().from(agentMemory).where(and19(
18787
18916
  eq22(agentMemory.projectId, projectId),
18788
18917
  or2(sql10`substr(${agentMemory.key}, 1, ${COMPACTION_KEY_PREFIX.length}) <> ${COMPACTION_KEY_PREFIX}`, sessionId ? sql10`substr(${agentMemory.key}, 1, ${sessionPrefix.length}) = ${sessionPrefix}` : void 0)
18789
- )).orderBy(desc9(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
18918
+ )).orderBy(desc10(agentMemory.updatedAt)).limit(limit).all().map(rowToDto);
18790
18919
  }
18791
18920
  function writeCompactionNote(db, args) {
18792
18921
  if (Buffer.byteLength(args.summary, "utf8") > AGENT_MEMORY_VALUE_MAX_BYTES) {
@@ -18814,7 +18943,7 @@ function writeCompactionNote(db, args) {
18814
18943
  eq22(agentMemory.projectId, args.projectId),
18815
18944
  like(agentMemory.key, `${sessionPrefix}%`)
18816
18945
  )
18817
- ).orderBy(desc9(agentMemory.updatedAt)).all();
18946
+ ).orderBy(desc10(agentMemory.updatedAt)).all();
18818
18947
  const stale = existing.slice(COMPACTION_NOTES_PER_SESSION).map((r) => r.id);
18819
18948
  if (stale.length > 0) {
18820
18949
  tx.delete(agentMemory).where(sql10`${agentMemory.id} IN (${sql10.join(stale.map((s) => sql10`${s}`), sql10`, `)})`).run();
@@ -5,7 +5,7 @@ import {
5
5
  siteCrawlGraphNodes,
6
6
  siteCrawlPages,
7
7
  siteCrawlSnapshots
8
- } from "./chunk-MTEVU6KG.js";
8
+ } from "./chunk-ENI3GOWW.js";
9
9
  import {
10
10
  SITE_CRAWL_GRAPH_MAX_EDGES,
11
11
  SITE_CRAWL_GRAPH_MAX_NODES,
@@ -13,7 +13,7 @@ import {
13
13
  TEMPLATE_LINK_RATIO_THRESHOLD,
14
14
  factorStatusFromScore,
15
15
  roundPreservingTotal
16
- } from "./chunk-J7LV4CS2.js";
16
+ } from "./chunk-6EPN4XY5.js";
17
17
 
18
18
  // src/site-audit-factors.ts
19
19
  function recordedSharePct(factor) {
@@ -19859,6 +19859,7 @@ function serializeForInlineScript(value) {
19859
19859
  var PROVIDER_ERROR_PRIORITY = [
19860
19860
  "PROVIDER_AUTH",
19861
19861
  "RATE_LIMITED",
19862
+ "PROVIDER_UNAVAILABLE",
19862
19863
  "TIMEOUT",
19863
19864
  "NETWORK",
19864
19865
  "PARSE_ERROR",
@@ -19871,6 +19872,10 @@ function classifyProviderErrorMessage(message) {
19871
19872
  if (/\b429\b|rate[_ -]?limit|too many requests|quota[_ -]?exceeded/i.test(message)) {
19872
19873
  return "RATE_LIMITED";
19873
19874
  }
19875
+ const status = extractProviderHttpStatus(message);
19876
+ if (status !== void 0 && status >= 500 || /overloaded|service unavailable|bad gateway|internal server error/i.test(message)) {
19877
+ return "PROVIDER_UNAVAILABLE";
19878
+ }
19874
19879
  if (/timeout|timed out|ETIMEDOUT/i.test(message)) {
19875
19880
  return "TIMEOUT";
19876
19881
  }
@@ -19892,6 +19897,11 @@ function classifyProviderErrorMessages(messages) {
19892
19897
  }
19893
19898
  return "UNKNOWN";
19894
19899
  }
19900
+ function extractProviderHttpStatus(message) {
19901
+ const text = message.replace(/^(?:\[[^\]]+\]\s*)+/, "");
19902
+ const match = /^([1-5]\d\d)\b/.exec(text) ?? /\b(?:status(?: code)?|http)[:=]?\s*([1-5]\d\d)\b/i.exec(text) ?? /"(?:code|status)"\s*:\s*([1-5]\d\d)\b/.exec(text);
19903
+ return match ? Number(match[1]) : void 0;
19904
+ }
19895
19905
 
19896
19906
  // ../contracts/src/telemetry.ts
19897
19907
  import { z as z65 } from "zod";
@@ -22203,6 +22213,7 @@ export {
22203
22213
  serializeForInlineScript,
22204
22214
  classifyProviderErrorMessage,
22205
22215
  classifyProviderErrorMessages,
22216
+ extractProviderHttpStatus,
22206
22217
  telemetryStatusDtoSchema,
22207
22218
  normalizeTelemetryStatus,
22208
22219
  maskTelemetryAnonymousId,
@@ -869,7 +869,7 @@ import {
869
869
  wordpressSchemaDeployResultDtoSchema,
870
870
  wordpressSchemaStatusResultDtoSchema,
871
871
  wordpressStatusDtoSchema
872
- } from "./chunk-J7LV4CS2.js";
872
+ } from "./chunk-6EPN4XY5.js";
873
873
 
874
874
  // src/intelligence-service.ts
875
875
  import { eq as eq82, desc as desc36, asc as asc15, and as and69, ne as ne14, or as or20, inArray as inArray32, gte as gte22, lte as lte20, isNull as isNull16, sql as sql31, exists } from "drizzle-orm";
@@ -17678,6 +17678,7 @@ function generationFailure(err, fallbackMessage) {
17678
17678
  return providerAuthError(message);
17679
17679
  case "RATE_LIMITED":
17680
17680
  return quotaExceeded("provider requests", { message });
17681
+ case "PROVIDER_UNAVAILABLE":
17681
17682
  case "NETWORK":
17682
17683
  case "TIMEOUT":
17683
17684
  return providerError(message);