@canonry/canonry 5.18.0 → 5.19.1

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 (34) hide show
  1. package/assets/agent-workspace/skills/aero/SKILL.md +13 -8
  2. package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +27 -10
  3. package/assets/agent-workspace/skills/canonry/references/canonry-cli.md +4 -1
  4. package/assets/assets/{AuditHistoryPanel-Di5c6xSa.js → AuditHistoryPanel-ddq5tdlB.js} +1 -1
  5. package/assets/assets/{BacklinksPage-CZi3sZ-7.js → BacklinksPage-g57_xzTX.js} +1 -1
  6. package/assets/assets/{HistoryPage-BY7HiZWU.js → HistoryPage-C-TdCUQ2.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-D3AdBHpI.js → MeasurementPropertyPage-CAADmNfw.js} +1 -1
  8. package/assets/assets/{ProjectPage-NyUl1dkF.js → ProjectPage-CoBwpHmA.js} +1 -1
  9. package/assets/assets/{RunRow-cRjiHQbt.js → RunRow-BpG0Zy-x.js} +1 -1
  10. package/assets/assets/{RunsPage-DZP_kqJs.js → RunsPage-DK3pb1LH.js} +1 -1
  11. package/assets/assets/{SettingsPage-BqJXYCDc.js → SettingsPage-BruiY8Zh.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-Brr0C56X.js → SiteHealthSection-De4U11Gv.js} +3 -3
  13. package/assets/assets/{TrafficPage-k5NPSDur.js → TrafficPage-BVolLXT2.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-BKNzWnxA.js → TrafficSourceDetailPage-CFDb6lPD.js} +1 -1
  15. package/assets/assets/{extract-error-message-Cnvelll0.js → extract-error-message-Bk1RTjzo.js} +1 -1
  16. package/assets/assets/index-B--AA1mG.js +86 -0
  17. package/assets/assets/{react-sigma_core.esm.min-CthGjQKl.js → react-sigma_core.esm.min--QXVK1JS.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-q2o2MzmG.js → v2-overview-adapter-CpjJPZsC.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-Y5IDGH5B.js → chunk-3IAIW5KH.js} +534 -136
  21. package/dist/chunk-4WROKFN6.js +3991 -0
  22. package/dist/{chunk-TMTO2JHU.js → chunk-EJXDABGU.js} +21 -3970
  23. package/dist/{chunk-HIFBIDOW.js → chunk-EJZFTNZG.js} +444 -185
  24. package/dist/{chunk-X3GFCKUJ.js → chunk-J3JXJIST.js} +3 -3
  25. package/dist/{chunk-HAABFHXB.js → chunk-PGOID3LE.js} +2 -2
  26. package/dist/{chunk-77VHT3JD.js → chunk-SOCYTKTF.js} +223 -23
  27. package/dist/cli.js +84 -27
  28. package/dist/{demo-server-JBGYZKAI.js → demo-server-BLDYKCFS.js} +1071 -187
  29. package/dist/index.js +6 -6
  30. package/dist/{intelligence-service-EORTVFDF.js → intelligence-service-3MHYLKMS.js} +2 -2
  31. package/dist/mcp.js +4 -4
  32. package/package.json +9 -9
  33. package/assets/assets/index-Cd_1GknE.js +0 -86
  34. package/dist/chunk-AOJU5LKX.js +0 -8
@@ -3,29 +3,29 @@ import {
3
3
  CANONRY_MCP_TIERS,
4
4
  CANONRY_MCP_TOOLKITS,
5
5
  CANONRY_MCP_TOOLKIT_NAMES,
6
- CanonryMcpToolNames,
7
- CliError,
8
- canonryMcpTools,
9
6
  configExists,
10
7
  createCanonryMcpServer,
11
8
  dashboardManagedRunKindsSchema,
12
9
  getBundledSkillSnapshots,
13
10
  getConfigPath,
14
- isMachineFormat,
15
11
  loadConfig,
16
12
  loadConfigRaw,
17
13
  resolveOperatorApiKeyIds,
18
14
  runWithUsageTags,
19
15
  saveConfigPatch
20
- } from "./chunk-TMTO2JHU.js";
16
+ } from "./chunk-EJXDABGU.js";
21
17
  import {
22
18
  deleteSiteCrawlGraphLayout,
23
19
  persistSiteCrawlGraphLayout,
24
20
  prepareSiteCrawlGraphLayout
25
- } from "./chunk-HAABFHXB.js";
21
+ } from "./chunk-PGOID3LE.js";
26
22
  import {
27
- PACKAGE_VERSION
28
- } from "./chunk-AOJU5LKX.js";
23
+ CanonryMcpToolNames,
24
+ CliError,
25
+ PACKAGE_VERSION,
26
+ canonryMcpTools,
27
+ isMachineFormat
28
+ } from "./chunk-4WROKFN6.js";
29
29
  import {
30
30
  CC_CACHE_DIR,
31
31
  DUCKDB_SPEC,
@@ -134,6 +134,8 @@ import {
134
134
  llmUsageEvents,
135
135
  loadDuckdb,
136
136
  markSessionFailed,
137
+ measurementPlanVersions,
138
+ measurementPlans,
137
139
  measurementRunCompleteness,
138
140
  measurementRunSlotState,
139
141
  measurementSlotKey,
@@ -184,7 +186,7 @@ import {
184
186
  siteLivenessState,
185
187
  toAlertView,
186
188
  usageCounters
187
- } from "./chunk-Y5IDGH5B.js";
189
+ } from "./chunk-3IAIW5KH.js";
188
190
  import {
189
191
  AGENT_MEMORY_VALUE_MAX_BYTES,
190
192
  AGENT_NONE,
@@ -213,6 +215,7 @@ import {
213
215
  GtmSnapshotKinds,
214
216
  LLM_CAPABILITIES,
215
217
  LlmCapabilities,
218
+ MEASUREMENT_PLAN_V2_SCHEMA_VERSION,
216
219
  MemorySources,
217
220
  ONBOARDING_FLOW_VERSION,
218
221
  ProviderNames,
@@ -347,7 +350,7 @@ import {
347
350
  visibilityReportRequestSchema,
348
351
  winnabilityClassLabel,
349
352
  withRetry
350
- } from "./chunk-77VHT3JD.js";
353
+ } from "./chunk-SOCYTKTF.js";
351
354
 
352
355
  // src/runtime-context.ts
353
356
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -3566,7 +3569,7 @@ import crypto27 from "crypto";
3566
3569
  import fs8 from "fs";
3567
3570
  import path8 from "path";
3568
3571
  import { fileURLToPath as fileURLToPath3 } from "url";
3569
- import { and as and20, eq as eq26 } from "drizzle-orm";
3572
+ import { and as and21, eq as eq27 } from "drizzle-orm";
3570
3573
  import Fastify from "fastify";
3571
3574
  import os4 from "os";
3572
3575
 
@@ -6498,7 +6501,7 @@ function nonBlank(value) {
6498
6501
  return trimmed ? trimmed : void 0;
6499
6502
  }
6500
6503
  function resolveBuildCommit(env = process.env) {
6501
- const embedded = true ? "508d40ec00ff12e4604267c10940212dd3b750b9" : void 0;
6504
+ const embedded = true ? "38f4809c78aa8ea4e0b4ab4eb9dbe5e3894ca3ac" : void 0;
6502
6505
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6503
6506
  }
6504
6507
  function resolveInstanceIdentity(env = process.env) {
@@ -7444,15 +7447,15 @@ var JobRunner = class {
7444
7447
  const dispatchable = [];
7445
7448
  for (const registered of activeProviders) {
7446
7449
  const name = registered.adapter.name;
7447
- const count = unitsByProvider.get(name)?.length ?? 0;
7450
+ const count2 = unitsByProvider.get(name)?.length ?? 0;
7448
7451
  const scope = `${projectId}:${name}`;
7449
7452
  const limit = registered.config.quotaPolicy.maxRequestsPerDay;
7450
- const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count, limit });
7453
+ const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count: count2, limit });
7451
7454
  if (!quota.reserved) {
7452
- providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count} more.`);
7455
+ providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count2} more.`);
7453
7456
  continue;
7454
7457
  }
7455
- providerReservations.set(name, { scope, period: todayPeriod, reserved: count });
7458
+ providerReservations.set(name, { scope, period: todayPeriod, reserved: count2 });
7456
7459
  dispatchable.push(registered);
7457
7460
  }
7458
7461
  const executionGates = /* @__PURE__ */ new Map();
@@ -7565,8 +7568,8 @@ var JobRunner = class {
7565
7568
  const remaining = /* @__PURE__ */ new Map();
7566
7569
  for (const slot of state.missing) remaining.set(slot.provider, (remaining.get(slot.provider) ?? 0) + 1);
7567
7570
  const providers = {};
7568
- for (const [provider, count] of remaining) {
7569
- providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${count} expected measurement(s) have not run.` };
7571
+ for (const [provider, count2] of remaining) {
7572
+ providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${count2} expected measurement(s) have not run.` };
7570
7573
  }
7571
7574
  txDb.update(runs).set({ error: serializeRunError({ providers }) }).where(and2(eq2(runs.id, input.runId), eq2(runs.status, "partial"))).run();
7572
7575
  }
@@ -7735,7 +7738,7 @@ var JobRunner = class {
7735
7738
  ctx.fill?.onOutcome(providerName, false);
7736
7739
  }
7737
7740
  }
7738
- incrementUsage(scope, metric, count) {
7741
+ incrementUsage(scope, metric, count2) {
7739
7742
  const now = (/* @__PURE__ */ new Date()).toISOString();
7740
7743
  const period = now.slice(0, 10);
7741
7744
  this.db.insert(usageCounters).values({
@@ -7743,11 +7746,11 @@ var JobRunner = class {
7743
7746
  scope,
7744
7747
  period,
7745
7748
  metric,
7746
- count,
7749
+ count: count2,
7747
7750
  updatedAt: now
7748
7751
  }).onConflictDoUpdate({
7749
7752
  target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
7750
- set: { count: sql2`${usageCounters.count} + ${count}`, updatedAt: now }
7753
+ set: { count: sql2`${usageCounters.count} + ${count2}`, updatedAt: now }
7751
7754
  }).run();
7752
7755
  }
7753
7756
  flushProviderUsage(providerDispatchCounts, providerReservations) {
@@ -12074,7 +12077,7 @@ function readStoredGroundingSources(rawResponse) {
12074
12077
  return result2;
12075
12078
  }
12076
12079
  async function backfillInsightsCommand(project, opts) {
12077
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-EORTVFDF.js");
12080
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-3MHYLKMS.js");
12078
12081
  const config = loadConfig();
12079
12082
  const db = createClient(config.database);
12080
12083
  migrate(db);
@@ -12588,8 +12591,8 @@ Traffic classification ${result2.dryRun ? "preview" : "recovery"} complete.`);
12588
12591
  if (result2.reclassified > 0) {
12589
12592
  console.log(` By bot:`);
12590
12593
  const sorted = Object.entries(result2.byBot).sort(([, a], [, b]) => b - a);
12591
- for (const [bot, count] of sorted) {
12592
- console.log(` ${count.toString().padStart(5)} ${bot}`);
12594
+ for (const [bot, count2] of sorted) {
12595
+ console.log(` ${count2.toString().padStart(5)} ${bot}`);
12593
12596
  }
12594
12597
  }
12595
12598
  if (result2.dryRun) {
@@ -13928,7 +13931,7 @@ function analysisResultFromInsights(insights2) {
13928
13931
 
13929
13932
  // src/agent/session-registry.ts
13930
13933
  import crypto24 from "crypto";
13931
- import { eq as eq20 } from "drizzle-orm";
13934
+ import { eq as eq21 } from "drizzle-orm";
13932
13935
 
13933
13936
  // src/agent/session.ts
13934
13937
  import fs7 from "fs";
@@ -14301,12 +14304,26 @@ import { Type as Type3 } from "@sinclair/typebox";
14301
14304
  import { Type as Type2 } from "@sinclair/typebox";
14302
14305
  import { randomUUID } from "crypto";
14303
14306
  var MAX_TOOL_RESULT_CHARS = 2e4;
14304
- var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
14307
+ var TRUNCATION_NOTE = "... (truncated, result too large)";
14308
+ var TRUNCATION_SUMMARY_KEY = "__truncation";
14305
14309
  var MAX_STRUCTURED_TRUNCATION_CHARS = 2e6;
14306
- var MAX_TRUNCATION_PASSES = 32;
14310
+ var TRUNCATION_PASS_BUDGET_CHARS = 64e6;
14311
+ var MIN_TRUNCATION_PASSES = 32;
14312
+ var MAX_TRUNCATION_PASSES = 200;
14313
+ var MAX_SUMMARY_DEPTH = 5;
14314
+ var MAX_SUMMARY_KEYS = 30;
14315
+ var MAX_SUMMARY_SEGMENT_CHARS = 60;
14307
14316
  function serializeResult(value) {
14308
14317
  return JSON.stringify(value, null, 2);
14309
14318
  }
14319
+ function keptSummary(counts) {
14320
+ const summary = { droppedKeys: [], keptItems: {} };
14321
+ for (const [path9, { kept, total }] of counts) {
14322
+ if (kept === 0) summary.droppedKeys.push(path9);
14323
+ summary.keptItems[path9] = `${kept} of ${total}`;
14324
+ }
14325
+ return summary;
14326
+ }
14310
14327
  function largestArrayKey(obj) {
14311
14328
  let best;
14312
14329
  let bestLen = -1;
@@ -14320,117 +14337,278 @@ function largestArrayKey(obj) {
14320
14337
  }
14321
14338
  return best;
14322
14339
  }
14323
- function trimRowsToFit(rows, render) {
14324
- if (rows.length === 0) return [];
14325
- const all = rows.slice();
14326
- if (render(all).length <= MAX_TOOL_RESULT_CHARS) return all;
14340
+ function fitsCap(text) {
14341
+ return text.length <= MAX_TOOL_RESULT_CHARS;
14342
+ }
14343
+ function largestFitting(max, fits) {
14327
14344
  let low = 0;
14328
- let high = rows.length - 1;
14345
+ let high = max;
14329
14346
  while (low < high) {
14330
14347
  const middle = Math.ceil((low + high) / 2);
14331
- if (render(rows.slice(0, middle)).length <= MAX_TOOL_RESULT_CHARS) low = middle;
14348
+ if (fits(middle)) low = middle;
14332
14349
  else high = middle - 1;
14333
14350
  }
14334
- return rows.slice(0, low);
14351
+ return low;
14335
14352
  }
14336
- function nestedArrayCandidates(root) {
14337
- const candidates = [];
14338
- const visit = (object) => {
14353
+ function trimRowsToFit(rows, render) {
14354
+ if (rows.length === 0) return [];
14355
+ const all = rows.slice();
14356
+ if (fitsCap(render(all))) return all;
14357
+ return rows.slice(0, largestFitting(rows.length - 1, (count2) => fitsCap(render(rows.slice(0, count2)))));
14358
+ }
14359
+ function nestedArrayGroups(root) {
14360
+ const groups = /* @__PURE__ */ new Map();
14361
+ const visit = (object, path9) => {
14339
14362
  let containsRecordCollection = false;
14340
14363
  for (const [key, value] of Object.entries(object)) {
14364
+ if (object === root && key === TRUNCATION_SUMMARY_KEY) continue;
14365
+ const valuePath = path9 ? `${path9}.${key}` : key;
14341
14366
  if (Array.isArray(value)) {
14342
14367
  let hasNestedRecords = false;
14343
14368
  let hasRecordRows = false;
14344
14369
  for (const row of value) {
14345
14370
  if (row && typeof row === "object" && !Array.isArray(row)) {
14346
14371
  hasRecordRows = true;
14347
- hasNestedRecords = visit(row) || hasNestedRecords;
14372
+ hasNestedRecords = visit(row, `${valuePath}[]`) || hasNestedRecords;
14348
14373
  }
14349
14374
  }
14350
14375
  if (value.length > 0 && !hasNestedRecords) {
14351
- candidates.push({ owner: object, key, rows: value, size: serializeResult(value).length, hasRecordRows });
14376
+ const id = `${Number(hasRecordRows)}:${valuePath}`;
14377
+ const group = groups.get(id) ?? { path: valuePath, members: [], size: 0, hasRecordRows };
14378
+ group.members.push({ owner: object, key, rows: value });
14379
+ group.size += serializeResult(value).length;
14380
+ groups.set(id, group);
14352
14381
  }
14353
14382
  containsRecordCollection = hasRecordRows || hasNestedRecords || containsRecordCollection;
14354
14383
  } else if (value && typeof value === "object") {
14355
- containsRecordCollection = visit(value) || containsRecordCollection;
14384
+ containsRecordCollection = visit(value, valuePath) || containsRecordCollection;
14356
14385
  }
14357
14386
  }
14358
14387
  return containsRecordCollection;
14359
14388
  };
14360
- visit(root);
14361
- return candidates.sort(
14389
+ visit(root, "");
14390
+ return [...groups.values()].sort(
14362
14391
  (left, right) => Number(right.hasRecordRows) - Number(left.hasRecordRows) || right.size - left.size
14363
14392
  );
14364
14393
  }
14394
+ function memberTrimmer({ owner, key, rows }) {
14395
+ const hadTruncated = Object.hasOwn(owner, "__truncated");
14396
+ const priorTruncated = owner.__truncated;
14397
+ const previous = owner.__omittedRowsByField;
14398
+ const priorOmitted = previous && typeof previous === "object" && !Array.isArray(previous) ? previous : void 0;
14399
+ const priorValue = priorOmitted?.[key];
14400
+ const priorCount = typeof priorValue === "number" ? priorValue : 0;
14401
+ return (count2) => {
14402
+ const kept = Math.min(count2, rows.length);
14403
+ owner[key] = rows.slice(0, kept);
14404
+ const omitted = priorCount + rows.length - kept;
14405
+ if (omitted > 0) {
14406
+ owner.__truncated = true;
14407
+ owner.__omittedRowsByField = { ...priorOmitted, [key]: omitted };
14408
+ return kept;
14409
+ }
14410
+ if (hadTruncated) owner.__truncated = priorTruncated;
14411
+ else delete owner.__truncated;
14412
+ if (previous === void 0) delete owner.__omittedRowsByField;
14413
+ else owner.__omittedRowsByField = previous;
14414
+ return kept;
14415
+ };
14416
+ }
14417
+ function emptiedFloorChars(root) {
14418
+ return JSON.stringify(root, (_key, value) => Array.isArray(value) ? [] : value, 2).length;
14419
+ }
14365
14420
  function trimNestedArrays(full) {
14366
14421
  const parsed = JSON.parse(full);
14367
14422
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14368
14423
  const copy = parsed;
14424
+ const counts = /* @__PURE__ */ new Map();
14369
14425
  copy.__truncated = true;
14426
+ copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
14427
+ if (emptiedFloorChars(copy) > MAX_TOOL_RESULT_CHARS) return void 0;
14370
14428
  let out = serializeResult(copy);
14429
+ const passLimit = Math.min(
14430
+ MAX_TRUNCATION_PASSES,
14431
+ Math.max(MIN_TRUNCATION_PASSES, Math.floor(TRUNCATION_PASS_BUDGET_CHARS / full.length))
14432
+ );
14371
14433
  let passes = 0;
14372
- while (out.length > MAX_TOOL_RESULT_CHARS) {
14373
- if (++passes > MAX_TRUNCATION_PASSES) return void 0;
14374
- const candidate = nestedArrayCandidates(copy).at(0);
14375
- if (!candidate) return void 0;
14376
- const { owner, key, rows } = candidate;
14377
- const previous = owner.__omittedRowsByField;
14378
- const omitted = previous && typeof previous === "object" && !Array.isArray(previous) ? { ...previous } : {};
14379
- const priorCount = typeof omitted[key] === "number" ? omitted[key] : 0;
14380
- owner.__truncated = true;
14381
- owner.__omittedRowsByField = omitted;
14382
- const keep = (count) => {
14383
- owner[key] = rows.slice(0, count);
14384
- omitted[key] = priorCount + rows.length - count;
14434
+ while (!fitsCap(out)) {
14435
+ if (++passes > passLimit) return void 0;
14436
+ const group = nestedArrayGroups(copy).at(0);
14437
+ if (!group) return void 0;
14438
+ const trimmers = group.members.map(memberTrimmer);
14439
+ const total = group.members.reduce((sum, member) => sum + member.rows.length, 0);
14440
+ const longest = group.members.reduce((max, member) => Math.max(max, member.rows.length), 0);
14441
+ const prior = counts.get(group.path) ?? { kept: 0, total: 0 };
14442
+ const keep = (count3, extra2 = 0) => {
14443
+ const kept = trimmers.reduce((sum, trim, index) => sum + trim(index < extra2 ? count3 + 1 : count3), 0);
14444
+ counts.set(group.path, { kept: prior.kept + kept, total: prior.total + total });
14445
+ copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
14385
14446
  return serializeResult(copy);
14386
14447
  };
14387
14448
  out = keep(0);
14388
- if (out.length > MAX_TOOL_RESULT_CHARS) continue;
14389
- let low = 0;
14390
- let high = rows.length - 1;
14391
- while (low < high) {
14392
- const middle = Math.ceil((low + high) / 2);
14393
- if (keep(middle).length <= MAX_TOOL_RESULT_CHARS) low = middle;
14394
- else high = middle - 1;
14395
- }
14396
- out = keep(low);
14449
+ if (!fitsCap(out)) continue;
14450
+ const count2 = largestFitting(longest - 1, (n) => fitsCap(keep(n)));
14451
+ const extra = trimmers.length > 1 ? largestFitting(trimmers.length - 1, (n) => fitsCap(keep(count2, n))) : 0;
14452
+ out = keep(count2, extra);
14397
14453
  }
14398
14454
  return out;
14399
14455
  }
14456
+ function shortSegment(key) {
14457
+ return key.length > MAX_SUMMARY_SEGMENT_CHARS ? `${key.slice(0, MAX_SUMMARY_SEGMENT_CHARS)}...` : key;
14458
+ }
14459
+ function childPath(container) {
14460
+ if (container.kind === "array") return `${container.path}[${container.complete}]`;
14461
+ const key = shortSegment(container.key ?? "");
14462
+ return container.path ? `${container.path}.${key}` : key;
14463
+ }
14464
+ function serializedKeys(value) {
14465
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
14466
+ const record = value;
14467
+ if (typeof record.toJSON === "function") return void 0;
14468
+ return Object.keys(record).filter((key) => {
14469
+ const child = record[key];
14470
+ return child !== void 0 && typeof child !== "function" && typeof child !== "symbol";
14471
+ });
14472
+ }
14473
+ function openContainersAt(shown, details) {
14474
+ const stack = [];
14475
+ let inString = false;
14476
+ let escaped = false;
14477
+ let stringStart = 0;
14478
+ for (let i = 0; i < shown.length; i++) {
14479
+ const ch = shown[i];
14480
+ const top = stack.at(-1);
14481
+ if (inString) {
14482
+ if (escaped) escaped = false;
14483
+ else if (ch === "\\") escaped = true;
14484
+ else if (ch === '"') {
14485
+ inString = false;
14486
+ if (top?.kind === "object" && top.expectKey) {
14487
+ top.key = JSON.parse(shown.slice(stringStart, i + 1));
14488
+ top.expectKey = false;
14489
+ }
14490
+ }
14491
+ continue;
14492
+ }
14493
+ if (ch === '"') {
14494
+ inString = true;
14495
+ stringStart = i;
14496
+ } else if (ch === "{" || ch === "[") {
14497
+ let value = details;
14498
+ if (top) {
14499
+ const parent = top.value;
14500
+ value = top.kind === "array" ? Array.isArray(parent) ? parent[top.complete] : void 0 : parent && typeof parent === "object" ? parent[top.key ?? ""] : void 0;
14501
+ }
14502
+ stack.push({
14503
+ kind: ch === "{" ? "object" : "array",
14504
+ path: top ? childPath(top) : "",
14505
+ value,
14506
+ complete: 0,
14507
+ childClosed: false,
14508
+ expectKey: ch === "{"
14509
+ });
14510
+ } else if (ch === "}" || ch === "]") {
14511
+ stack.pop();
14512
+ const parent = stack.at(-1);
14513
+ if (parent) parent.childClosed = true;
14514
+ } else if (ch === "," && top) {
14515
+ top.complete++;
14516
+ top.childClosed = false;
14517
+ top.expectKey = top.kind === "object";
14518
+ }
14519
+ }
14520
+ return stack;
14521
+ }
14522
+ function sliceSummaryLine(shown, details) {
14523
+ if (!shown.startsWith("{") && !shown.startsWith("[")) return "";
14524
+ const stack = openContainersAt(shown, details);
14525
+ const root = stack.at(0);
14526
+ const deepest = stack.at(-1);
14527
+ if (!root || !deepest) return "";
14528
+ const summary = {
14529
+ // Between two children the cut is named by their container.
14530
+ cutAt: deepest.childClosed || deepest.expectKey ? deepest.path : childPath(deepest),
14531
+ droppedKeys: [],
14532
+ keptItems: {}
14533
+ };
14534
+ for (const container of stack.slice(0, MAX_SUMMARY_DEPTH)) {
14535
+ const total = container.kind === "array" ? Array.isArray(container.value) ? container.value.length : void 0 : serializedKeys(container.value)?.length;
14536
+ if (total === void 0) break;
14537
+ if (container === root && container.kind === "object") continue;
14538
+ const shownCount = container.complete + (container.childClosed ? 1 : 0);
14539
+ summary.keptItems[container.path || "(root)"] = container.kind === "array" ? `${shownCount} of ${total}` : `${shownCount} of ${total} keys`;
14540
+ }
14541
+ const rootKeys = root.kind === "object" ? serializedKeys(root.value) : void 0;
14542
+ if (rootKeys) {
14543
+ const inProgress = root.expectKey || root.childClosed ? 0 : 1;
14544
+ const dropped = rootKeys.slice(root.complete + (root.childClosed ? 1 : 0) + inProgress);
14545
+ const record = root.value;
14546
+ for (const key of dropped.slice(0, MAX_SUMMARY_KEYS)) {
14547
+ const label = shortSegment(key);
14548
+ summary.droppedKeys.push(label);
14549
+ const value = record[key];
14550
+ if (Array.isArray(value)) summary.keptItems[label] = `0 of ${value.length}`;
14551
+ }
14552
+ if (dropped.length > MAX_SUMMARY_KEYS) summary.moreDroppedKeys = dropped.length - MAX_SUMMARY_KEYS;
14553
+ }
14554
+ return `${TRUNCATION_SUMMARY_KEY}: ${JSON.stringify(summary)}`;
14555
+ }
14556
+ function markedSlice(full, details) {
14557
+ let cut = MAX_TOOL_RESULT_CHARS;
14558
+ for (let attempt = 0; attempt < 4; attempt++) {
14559
+ const line = sliceSummaryLine(full.slice(0, cut), details);
14560
+ const cost = line ? line.length + 1 : 0;
14561
+ if (cut + cost <= MAX_TOOL_RESULT_CHARS) {
14562
+ return full.slice(0, cut) + "\n" + (line ? line + "\n" : "") + TRUNCATION_NOTE;
14563
+ }
14564
+ cut = MAX_TOOL_RESULT_CHARS - cost - attempt * 16;
14565
+ }
14566
+ return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
14567
+ }
14400
14568
  function truncateToolResult(details) {
14401
14569
  const full = serializeResult(details);
14402
14570
  if (full.length <= MAX_TOOL_RESULT_CHARS) return full;
14403
- if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS) {
14404
- return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
14405
- }
14571
+ if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS) return markedSlice(full, details);
14406
14572
  if (Array.isArray(details)) {
14407
- const kept = trimRowsToFit(
14408
- details,
14409
- (rows) => serializeResult({ items: rows, __truncated: true, __omittedRows: details.length - rows.length })
14410
- );
14411
- return serializeResult({ items: kept, __truncated: true, __omittedRows: details.length - kept.length });
14573
+ const render = (rows) => serializeResult({
14574
+ items: rows,
14575
+ __truncated: true,
14576
+ __omittedRows: details.length - rows.length,
14577
+ [TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([["items", { kept: rows.length, total: details.length }]]))
14578
+ });
14579
+ return render(trimRowsToFit(details, render));
14412
14580
  }
14413
14581
  if (details && typeof details === "object") {
14414
14582
  const obj = details;
14415
14583
  const arrayKey = largestArrayKey(obj);
14416
14584
  if (arrayKey) {
14417
14585
  const rows = obj[arrayKey];
14418
- const kept = trimRowsToFit(
14419
- rows,
14420
- (r) => serializeResult({ ...obj, [arrayKey]: r, __truncated: true, __omittedRows: rows.length - r.length })
14421
- );
14422
- const out = serializeResult({
14586
+ const render = (kept) => serializeResult({
14423
14587
  ...obj,
14424
14588
  [arrayKey]: kept,
14425
14589
  __truncated: true,
14426
- __omittedRows: rows.length - kept.length
14590
+ __omittedRows: rows.length - kept.length,
14591
+ [TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([[arrayKey, { kept: kept.length, total: rows.length }]]))
14427
14592
  });
14428
- if (out.length <= MAX_TOOL_RESULT_CHARS) return out;
14593
+ const out = render(trimRowsToFit(rows, render));
14594
+ const otherLists = Object.entries(obj).some(([key, value]) => key !== arrayKey && Array.isArray(value) && value.length > 0);
14595
+ const nested2 = otherLists ? trimNestedArrays(full) : void 0;
14596
+ if (fitsCap(out) && nested2 !== void 0 && keptRows(nested2, arrayKey) > keptRows(out, arrayKey)) return nested2;
14597
+ if (fitsCap(out)) return out;
14598
+ if (nested2 !== void 0) return nested2;
14429
14599
  }
14430
14600
  const nested = trimNestedArrays(full);
14431
14601
  if (nested !== void 0) return nested;
14432
14602
  }
14433
- return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
14603
+ return markedSlice(full, details);
14604
+ }
14605
+ function keptRows(serialized, key) {
14606
+ try {
14607
+ const parsed = JSON.parse(serialized);
14608
+ return Array.isArray(parsed[key]) ? parsed[key].length : 0;
14609
+ } catch {
14610
+ return 0;
14611
+ }
14434
14612
  }
14435
14613
  function textResult2(details) {
14436
14614
  return {
@@ -14887,6 +15065,7 @@ function splitAeroAnthropicSystemCachePayload(payload, model) {
14887
15065
  import { z } from "zod";
14888
15066
  import { Type as Type4 } from "@sinclair/typebox";
14889
15067
  var DISCOVER = "aero_list_toolkits";
15068
+ var MAX_VISIBLE_TOOLS = 128;
14890
15069
  var LOAD = "aero_load_toolkit";
14891
15070
  var metadata = new Map(canonryMcpTools.map((tool) => [tool.name, tool]));
14892
15071
  var runtimes = /* @__PURE__ */ new WeakMap();
@@ -14897,7 +15076,7 @@ function visibleTools(runtime) {
14897
15076
  if (!runtime.progressive) return runtime.allowed;
14898
15077
  const visible = runtime.allowed.filter((tool) => {
14899
15078
  const entry = metadata.get(tool.name);
14900
- return !entry || entry.tier === "core" || runtime.loaded.has(entry.tier);
15079
+ return !entry || entry.tier === "core" || runtime.pinned.has(tool.name) || runtime.loaded.has(entry.tier);
14901
15080
  });
14902
15081
  const available = CANONRY_MCP_TOOLKITS.filter((kit) => runtime.allowed.some((tool) => metadata.get(tool.name)?.tier === kit.name));
14903
15082
  return [...visible, {
@@ -14914,15 +15093,39 @@ function visibleTools(runtime) {
14914
15093
  execute: async (_id, input) => {
14915
15094
  const args = z.object({ toolkit: z.string() }).parse(input);
14916
15095
  if (!available.some((kit) => kit.name === args.toolkit)) throw new Error("Toolkit is unavailable for this turn.");
15096
+ runtime.loaded.delete(args.toolkit);
14917
15097
  runtime.loaded.add(args.toolkit);
14918
- return result({ tools: runtime.allowed.filter((tool) => metadata.get(tool.name)?.tier === args.toolkit).map((tool) => ({ name: tool.name, description: tool.description })) });
15098
+ const unloaded = [];
15099
+ for (const older of [...runtime.loaded]) {
15100
+ if (visibleTools(runtime).length <= MAX_VISIBLE_TOOLS || older === args.toolkit) continue;
15101
+ runtime.loaded.delete(older);
15102
+ unloaded.push(older);
15103
+ }
15104
+ return result({
15105
+ tools: runtime.allowed.filter((tool) => metadata.get(tool.name)?.tier === args.toolkit).map((tool) => ({ name: tool.name, description: tool.description })),
15106
+ ...unloaded.length > 0 ? { unloaded, note: `Unloaded ${unloaded.join(", ")} to stay within the tool limit; load again if needed.` } : {}
15107
+ });
14919
15108
  }
14920
15109
  }];
14921
15110
  }
14922
- function configureAeroRuntime(agent, allowed, limits, progressive = true) {
15111
+ function explainMissingTool(runtime, message) {
15112
+ if (!message.isError || !Array.isArray(message.content)) return;
15113
+ const first = message.content[0];
15114
+ const name = first?.type === "text" ? /^Tool (\S+) not found$/.exec(first.text ?? "")?.[1] : void 0;
15115
+ if (!name) return;
15116
+ let text;
15117
+ if (!runtime.progressive) {
15118
+ text = `${name} is not available in this conversation. Use only the tools listed for you.`;
15119
+ } else {
15120
+ const toolkit = metadata.get(name)?.tier;
15121
+ text = runtime.allowed.some((tool) => tool.name === name) && toolkit && toolkit !== "core" ? `${name} is not loaded yet. Call ${LOAD} with toolkit "${toolkit}", then call ${name} again.` : `${name} is not available in this conversation. Use ${DISCOVER} to see the tools you can load.`;
15122
+ }
15123
+ message.content = [{ type: "text", text }];
15124
+ }
15125
+ function configureAeroRuntime(agent, allowed, limits, progressive = true, pinned = []) {
14923
15126
  let runtime = runtimes.get(agent);
14924
15127
  if (!runtime) {
14925
- runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive };
15128
+ runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive, pinned: new Set(pinned) };
14926
15129
  runtimes.set(agent, runtime);
14927
15130
  const state = runtime;
14928
15131
  const stream = agent.streamFn;
@@ -14959,6 +15162,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
14959
15162
  }
14960
15163
  if (event.type === "message_end" && event.message.role === "toolResult") {
14961
15164
  const message = event.message;
15165
+ explainMissingTool(state, message);
14962
15166
  const startedAt = toolStarts.get(message.toolCallId);
14963
15167
  Object.assign(message, {
14964
15168
  aeroToolLabel: state.allowed.find((tool) => tool.name === message.toolName)?.label,
@@ -14990,6 +15194,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
14990
15194
  runtime.loaded = /* @__PURE__ */ new Set();
14991
15195
  runtime.limits = agentTurnLimitsSchema.parse(limits ?? {});
14992
15196
  runtime.progressive = progressive;
15197
+ runtime.pinned = new Set(pinned);
14993
15198
  agent.state.tools = visibleTools(runtime);
14994
15199
  }
14995
15200
  function aeroTurnStatus(agent) {
@@ -15036,7 +15241,7 @@ function buildAeroViewTool(options, validatedEvidence) {
15036
15241
  }
15037
15242
  };
15038
15243
  }
15039
- var AERO_RUNTIME_PROMPT = "\n\nNative Aero tools: when present, use aero_list_toolkits and aero_load_toolkit to discover and load task-specific tools before calling them. Otherwise use the exposed catalog. Start view-relative analysis with aero_inspect_view when available. Cite the returned source.href beside claims. Name the measurement date, class, numerator/denominator, and comparison limitations. Separate observations from possible explanations; do not infer causation. Never turn missing, incomplete, or truncated evidence into zero. Without current view context, resolve ambiguous references before making scoped claims.";
15244
+ var AERO_RUNTIME_PROMPT = "\n\nNative Aero tools: when present, use aero_list_toolkits and aero_load_toolkit to discover and load task-specific tools before calling them. Otherwise use the exposed catalog. Start view-relative analysis with aero_inspect_view when available. Cite the returned source.href beside claims. Name the measurement date, class, numerator/denominator, and comparison limitations. Separate observations from possible explanations; do not infer causation. Never turn missing, incomplete, or truncated evidence into zero. A tool result that says it was truncated is partial: never list, rank, count or group items you did not see; say what was cut. Without current view context, resolve ambiguous references before making scoped claims.";
15040
15245
  function aeroViewPrompt(context) {
15041
15246
  return context ? `
15042
15247
 
@@ -15460,6 +15665,59 @@ function writeCompactionNote(db, args) {
15460
15665
  return inserted;
15461
15666
  }
15462
15667
 
15668
+ // src/agent/project-shape.ts
15669
+ import { and as and17, count, eq as eq20 } from "drizzle-orm";
15670
+ var ADVANCED_TOOLS = [
15671
+ "canonry_measurement_portfolio_summary",
15672
+ "canonry_measurement_overview",
15673
+ "canonry_measurement_property_evidence",
15674
+ "canonry_measurement_property_competitors",
15675
+ "canonry_measurement_changes",
15676
+ "canonry_visibility_report",
15677
+ "canonry_analytics_sources"
15678
+ ];
15679
+ var LEGACY_TOOLS = ["canonry_measurement_overview", "canonry_measurement_plan_get"];
15680
+ var SIMPLE_TOOLS = ["canonry_visibility_report"];
15681
+ function aeroProjectShape(db, projectId) {
15682
+ try {
15683
+ const pointer = db.select().from(measurementPlans).where(eq20(measurementPlans.projectId, projectId)).get();
15684
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and17(
15685
+ eq20(measurementPlanVersions.projectId, projectId),
15686
+ eq20(measurementPlanVersions.id, pointer.activeVersionId)
15687
+ )).get() : void 0;
15688
+ if (version) {
15689
+ const plan = JSON.parse(version.canonicalJson);
15690
+ const groups = plan.groups ?? [];
15691
+ const topLevel = groups.filter((group) => !group.parentGroupKey).length;
15692
+ const size = `${plan.targets?.length ?? 0} Properties in ${groups.length} groups (${topLevel} top-level, ${groups.length - topLevel} nested)`;
15693
+ if ((plan.schemaVersion ?? version.schemaVersion) !== MEASUREMENT_PLAN_V2_SCHEMA_VERSION) {
15694
+ return {
15695
+ prompt: `
15696
+
15697
+ Project shape: an Advanced Measurement plan on the legacy schema v1 (plan revision ${version.revision}) with ${size}. Its queries are not classified as branded or non-brand, so do not report class splits for it. Read it with canonry_measurement_overview and canonry_measurement_plan_get; the portfolio ranking, Property evidence and the advanced visibility report need a schema-v2 plan and will refuse this one.`,
15698
+ pinned: LEGACY_TOOLS
15699
+ };
15700
+ }
15701
+ const queriesIn = (queryClass) => new Set((plan.assignments ?? []).filter((assignment) => assignment.queryClass === queryClass).map((assignment) => assignment.queryId)).size;
15702
+ return {
15703
+ prompt: `
15704
+
15705
+ Project shape: an Advanced Measurement portfolio (plan revision ${version.revision}) with ${size}, ${queriesIn("branded")} branded and ${queriesIn("non-brand")} non-brand queries. Measure per Property, group or market, never pool branded and non-brand, and a query shared by several Properties is one answer scored for each. Start portfolio questions with canonry_measurement_portfolio_summary (weakest-first ranking with each Property's metro, the names answers wrote instead, and the domains they cited) or canonry_measurement_overview; drill into one Property with canonry_measurement_property_evidence and canonry_measurement_property_competitors; compare sweeps with canonry_measurement_changes. Project-wide tools such as canonry_visibility_report describe the whole project, not one Property. Group Properties only by the metro and submarkets the tools return, never by name. Names given instead were written in the answer text, not cited. Denominators count answers (queries x engines), not queries. Properties tied at the weakest rate are not ranked against each other. For sources, use each Property's citedDomains in the portfolio summary, or canonry_analytics_sources with queryClass and runId set; without them it pools both classes and every sweep. canonry_measurement_plan_get is plan structure with no metrics; do not read it for analysis.`,
15706
+ pinned: ADVANCED_TOOLS
15707
+ };
15708
+ }
15709
+ const tracked = db.select({ total: count() }).from(queries).where(eq20(queries.projectId, projectId)).get()?.total ?? 0;
15710
+ return {
15711
+ prompt: `
15712
+
15713
+ Project shape: a Simple project with ${tracked} tracked queries and no measurement plan (no Properties or markets). Start with canonry_project_overview, then canonry_visibility_report for mention and citation rates by query and engine.`,
15714
+ pinned: SIMPLE_TOOLS
15715
+ };
15716
+ } catch {
15717
+ return { prompt: "", pinned: [] };
15718
+ }
15719
+ }
15720
+
15463
15721
  // src/agent/compaction.ts
15464
15722
  import { complete } from "@mariozechner/pi-ai";
15465
15723
 
@@ -15702,7 +15960,7 @@ var SessionRegistry = class {
15702
15960
  modelProvider: effectiveProvider,
15703
15961
  modelId: effectiveModelId,
15704
15962
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15705
- }).where(eq20(agentSessions.projectId, projectId)).run();
15963
+ }).where(eq21(agentSessions.projectId, projectId)).run();
15706
15964
  }
15707
15965
  const agent2 = createAeroSession({
15708
15966
  projectName,
@@ -15869,9 +16127,10 @@ ${lines.join("\n")}
15869
16127
  preferences?.signal?.throwIfAborted();
15870
16128
  await this.maybeCompact(projectName, agent);
15871
16129
  preferences?.signal?.throwIfAborted();
15872
- agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + aeroViewPrompt(preferences?.context);
15873
16130
  const progressive = (preferences?.toolProfile ?? AeroToolProfiles.default) === AeroToolProfiles.default;
15874
- configureAeroRuntime(agent, [...agent.state.tools, ...progressive ? [buildAeroViewTool(view, evidence)] : []], preferences?.limits, progressive);
16131
+ const shape = aeroProjectShape(this.opts.db, projectId);
16132
+ agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + shape.prompt + aeroViewPrompt(preferences?.context);
16133
+ configureAeroRuntime(agent, [...agent.state.tools, ...progressive ? [buildAeroViewTool(view, evidence)] : []], preferences?.limits, progressive, shape.pinned);
15875
16134
  return agent;
15876
16135
  } finally {
15877
16136
  this.acquisitions.delete(projectName);
@@ -15997,7 +16256,7 @@ ${lines.join("\n")}
15997
16256
  modelProvider: nextProvider,
15998
16257
  modelId: nextModelId,
15999
16258
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16000
- }).where(eq20(agentSessions.projectId, projectId)).run();
16259
+ }).where(eq21(agentSessions.projectId, projectId)).run();
16001
16260
  }
16002
16261
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
16003
16262
  prepareConversationChange(projectName) {
@@ -16171,11 +16430,11 @@ ${lines.join("\n")}
16171
16430
  return id;
16172
16431
  }
16173
16432
  tryResolveProjectId(projectName) {
16174
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq20(projects.name, projectName)).get();
16433
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq21(projects.name, projectName)).get();
16175
16434
  return row?.id;
16176
16435
  }
16177
16436
  loadRow(projectId) {
16178
- const row = this.opts.db.select().from(agentSessions).where(eq20(agentSessions.projectId, projectId)).get();
16437
+ const row = this.opts.db.select().from(agentSessions).where(eq21(agentSessions.projectId, projectId)).get();
16179
16438
  return row ?? null;
16180
16439
  }
16181
16440
  insertRow(params) {
@@ -16194,7 +16453,7 @@ ${lines.join("\n")}
16194
16453
  }
16195
16454
  updateRow(projectId, patch) {
16196
16455
  const now = (/* @__PURE__ */ new Date()).toISOString();
16197
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq20(agentSessions.projectId, projectId)).run();
16456
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq21(agentSessions.projectId, projectId)).run();
16198
16457
  }
16199
16458
  /**
16200
16459
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -16203,13 +16462,13 @@ ${lines.join("\n")}
16203
16462
  * recently used right after an upgrade.
16204
16463
  */
16205
16464
  persistPromptSnapshot(projectId, systemPrompt) {
16206
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq20(agentSessions.projectId, projectId)).run();
16465
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq21(agentSessions.projectId, projectId)).run();
16207
16466
  }
16208
16467
  };
16209
16468
 
16210
16469
  // src/agent/viewer-sessions.ts
16211
16470
  import crypto25 from "crypto";
16212
- import { and as and17, eq as eq21, isNotNull, like as like2 } from "drizzle-orm";
16471
+ import { and as and18, eq as eq22, isNotNull, like as like2 } from "drizzle-orm";
16213
16472
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
16214
16473
  CanonryMcpToolNames.canonry_ads_account,
16215
16474
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -16239,7 +16498,6 @@ var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
16239
16498
  var OPERATOR_ONLY_TOOL_NAMES = new Set(
16240
16499
  canonryMcpTools.filter((tool) => tool.tier === "agent" || tool.requiresOperator).map((tool) => tool.name)
16241
16500
  );
16242
- var VIEWER_AERO_TURN_LIMITS = { maxToolCalls: 12, timeoutMs: 12e4 };
16243
16501
  var VIEWER_AERO_MAX_PROMPT_CHARS = 4e3;
16244
16502
  var VIEWER_AERO_DAILY_TURN_LIMIT = 50;
16245
16503
  function viewerAeroDailyLimitExceeded(limit) {
@@ -16323,8 +16581,8 @@ var ViewerAeroSessions = class {
16323
16581
  if (this.isBusy(project.name, userId)) throw agentBusy(project.name);
16324
16582
  const today = new Date(this.now()).toISOString().slice(0, 10);
16325
16583
  const used = this.turnsToday.get(key);
16326
- const count = used?.date === today ? used.count : 0;
16327
- if (count >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
16584
+ const count2 = used?.date === today ? used.count : 0;
16585
+ if (count2 >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
16328
16586
  const minted = mintViewerAeroKey(this.opts.db, userId, new Date(this.now()));
16329
16587
  this.acquisitions.add(key);
16330
16588
  let handedOff = false;
@@ -16335,10 +16593,11 @@ var ViewerAeroSessions = class {
16335
16593
  const view = { client, projectName: project.name, basePath: this.opts.config.basePath, context: preferences.context };
16336
16594
  const evidence = preferences.context ? await readAeroViewEvidence(view) : void 0;
16337
16595
  preferences.signal?.throwIfAborted();
16338
- agent.state.systemPrompt = loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + aeroViewPrompt(preferences.context);
16339
- configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], VIEWER_AERO_TURN_LIMITS, true);
16596
+ const shape = aeroProjectShape(this.opts.db, project.id);
16597
+ agent.state.systemPrompt = loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + shape.prompt + aeroViewPrompt(preferences.context);
16598
+ configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], void 0, true, shape.pinned);
16340
16599
  this.sessions.set(key, { agent, lastUsedAt: this.now(), updatedAt: this.sessions.get(key)?.updatedAt ?? null });
16341
- this.turnsToday.set(key, { date: today, count: count + 1 });
16600
+ this.turnsToday.set(key, { date: today, count: count2 + 1 });
16342
16601
  handedOff = true;
16343
16602
  let released = false;
16344
16603
  return {
@@ -16385,10 +16644,10 @@ var ViewerAeroSessions = class {
16385
16644
  */
16386
16645
  removeKey(id) {
16387
16646
  try {
16388
- this.opts.db.delete(apiKeys).where(eq21(apiKeys.id, id)).run();
16647
+ this.opts.db.delete(apiKeys).where(eq22(apiKeys.id, id)).run();
16389
16648
  } catch {
16390
16649
  try {
16391
- this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq21(apiKeys.id, id)).run();
16650
+ this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq22(apiKeys.id, id)).run();
16392
16651
  } catch {
16393
16652
  }
16394
16653
  }
@@ -16400,7 +16659,7 @@ var ViewerAeroSessions = class {
16400
16659
  */
16401
16660
  revokeOrphanedKeys() {
16402
16661
  try {
16403
- this.opts.db.delete(apiKeys).where(and17(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
16662
+ this.opts.db.delete(apiKeys).where(and18(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
16404
16663
  } catch {
16405
16664
  }
16406
16665
  }
@@ -16416,7 +16675,7 @@ var ViewerAeroSessions = class {
16416
16675
 
16417
16676
  // src/mcp-http.ts
16418
16677
  import crypto26 from "crypto";
16419
- import { eq as eq22 } from "drizzle-orm";
16678
+ import { eq as eq23 } from "drizzle-orm";
16420
16679
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16421
16680
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
16422
16681
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -16437,7 +16696,7 @@ function mintSessionKey(db, scopes, userId) {
16437
16696
  }
16438
16697
  function revokeSessionKey(db, id) {
16439
16698
  try {
16440
- db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(apiKeys.id, id)).run();
16699
+ db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(apiKeys.id, id)).run();
16441
16700
  } catch {
16442
16701
  }
16443
16702
  }
@@ -16620,7 +16879,7 @@ function registerMcpHttpRoutes(scope, opts) {
16620
16879
  }
16621
16880
 
16622
16881
  // src/agent/agent-routes.ts
16623
- import { eq as eq23 } from "drizzle-orm";
16882
+ import { eq as eq24 } from "drizzle-orm";
16624
16883
  function viewerAeroCaller(request, opts) {
16625
16884
  if (!opts.viewerSessions) return null;
16626
16885
  const principal = request.principal;
@@ -16650,7 +16909,7 @@ function viewerTranscript(opts, projectName, userId) {
16650
16909
  };
16651
16910
  }
16652
16911
  function resolveProject(db, name) {
16653
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq23(projects.name, name)).get();
16912
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq24(projects.name, name)).get();
16654
16913
  if (!row) throw notFound("project", name);
16655
16914
  return row;
16656
16915
  }
@@ -16758,7 +17017,7 @@ function registerAgentRoutes(app, opts) {
16758
17017
  }
16759
17018
  requireInstanceAdministrator(request);
16760
17019
  const project = resolveProject(opts.db, request.params.name);
16761
- const row = opts.db.select().from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
17020
+ const row = opts.db.select().from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
16762
17021
  if (!row) {
16763
17022
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, updatedAt: null };
16764
17023
  }
@@ -16798,7 +17057,7 @@ function registerAgentRoutes(app, opts) {
16798
17057
  const project = resolveProject(opts.db, request.params.name);
16799
17058
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
16800
17059
  opts.sessionRegistry.reset(project.name);
16801
- opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(agentSessions.projectId, project.id)).run();
17060
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(agentSessions.projectId, project.id)).run();
16802
17061
  return { status: "reset" };
16803
17062
  }
16804
17063
  );
@@ -16822,7 +17081,7 @@ function registerAgentRoutes(app, opts) {
16822
17081
  return streamViewerTurn(opts, project, viewerId, body, reply);
16823
17082
  }
16824
17083
  if (body.conversationId !== void 0) {
16825
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
17084
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
16826
17085
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
16827
17086
  }
16828
17087
  const promptText = body.prompt;
@@ -17625,7 +17884,7 @@ function buildSnapshotSummary(companyName, queries2, providers, queryResults, au
17625
17884
  competitorCounts.set(competitor, (competitorCounts.get(competitor) ?? 0) + 1);
17626
17885
  }
17627
17886
  }
17628
- const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name, count]) => ({ name, count }));
17887
+ const topCompetitors = [...competitorCounts.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, 10).map(([name, count2]) => ({ name, count: count2 }));
17629
17888
  const defaultMeaning = totalComparisons > 0 ? `${companyName} was mentioned in ${mentionCount}/${totalComparisons} successful provider-query response${totalComparisons === 1 ? "" : "s"} across ${queries2.length} category queries.` : `No successful provider responses were returned across ${queries2.length} category queries.`;
17630
17889
  const failureNote = failedComparisons > 0 ? [
17631
17890
  `${failedComparisons} provider response${failedComparisons === 1 ? "" : "s"} failed and ${failedComparisons === 1 ? "was" : "were"} excluded from visibility totals.`
@@ -17760,20 +18019,20 @@ function clipText(value, length) {
17760
18019
  }
17761
18020
 
17762
18021
  // src/research-runner.ts
17763
- import { and as and18, eq as eq24, inArray as inArray9, sql as sql9 } from "drizzle-orm";
18022
+ import { and as and19, eq as eq25, inArray as inArray9, sql as sql9 } from "drizzle-orm";
17764
18023
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
17765
18024
  function finalResearchRunStatus(completed, failed) {
17766
18025
  if (failed === 0) return ResearchRunStatuses.completed;
17767
18026
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
17768
18027
  }
17769
18028
  async function executeResearchRun(db, registry, runId, projectId) {
17770
- const run = db.select().from(researchRuns).where(and18(eq24(researchRuns.id, runId), eq24(researchRuns.projectId, projectId))).get();
18029
+ const run = db.select().from(researchRuns).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.projectId, projectId))).get();
17771
18030
  if (!run || run.status !== ResearchRunStatuses.queued) return;
17772
- const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
18031
+ const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
17773
18032
  if (!project) return;
17774
18033
  const provider = registry.get(run.provider);
17775
18034
  const now = (/* @__PURE__ */ new Date()).toISOString();
17776
- const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and18(eq24(researchRuns.id, runId), eq24(researchRuns.status, ResearchRunStatuses.queued))).run();
18035
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.status, ResearchRunStatuses.queued))).run();
17777
18036
  if (claim.changes !== 1) return;
17778
18037
  let reserved = 0;
17779
18038
  let dispatched = 0;
@@ -17797,8 +18056,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
17797
18056
  }
17798
18057
  reserved = run.totalQueries;
17799
18058
  reservation = { scope, period };
17800
- const rows = db.select().from(researchRunQueries).where(eq24(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
17801
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq24(competitors.projectId, projectId)).all().map((row) => row.domain);
18059
+ const rows = db.select().from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
18060
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq25(competitors.projectId, projectId)).all().map((row) => row.domain);
17802
18061
  const domains = effectiveDomains(project);
17803
18062
  const brands = effectiveBrandNames(project);
17804
18063
  const config = { ...provider.config, model: run.resolvedModel };
@@ -17806,7 +18065,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
17806
18065
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
17807
18066
  try {
17808
18067
  const startedAt = (/* @__PURE__ */ new Date()).toISOString();
17809
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and18(eq24(researchRunQueries.id, row.id), eq24(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
18068
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
17810
18069
  const raw = await gate.run(async () => {
17811
18070
  dispatched++;
17812
18071
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -17833,7 +18092,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
17833
18092
  citationState: determineCitationState(normalized, domains),
17834
18093
  rawResponse: raw.rawResponse,
17835
18094
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
17836
- }).where(and18(eq24(researchRunQueries.id, row.id), eq24(researchRunQueries.status, ResearchQueryStatuses.running))).run();
18095
+ }).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.running))).run();
17837
18096
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
17838
18097
  } catch (error) {
17839
18098
  try {
@@ -17857,18 +18116,18 @@ async function executeResearchRun(db, registry, runId, projectId) {
17857
18116
  }
17858
18117
  }
17859
18118
  function incrementResearchProgress(db, runId, column) {
17860
- db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq24(researchRuns.id, runId)).run();
18119
+ db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq25(researchRuns.id, runId)).run();
17861
18120
  }
17862
18121
  function markResearchQueryFailed(db, runId, queryId, error) {
17863
18122
  const message = describeError(error);
17864
- const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and18(eq24(researchRunQueries.id, queryId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
18123
+ const failed = db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error: message, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and19(eq25(researchRunQueries.id, queryId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
17865
18124
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
17866
18125
  }
17867
18126
  function markUnfinishedResearchQueriesFailed(db, runId, error) {
17868
- db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and18(eq24(researchRunQueries.researchRunId, runId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
18127
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.failed, error, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(and19(eq25(researchRunQueries.researchRunId, runId), inArray9(researchRunQueries.status, unfinishedResearchQueryStatuses))).run();
17869
18128
  }
17870
18129
  function finalizeResearchRun(db, runId, fatalError) {
17871
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq24(researchRunQueries.researchRunId, runId)).all();
18130
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).all();
17872
18131
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
17873
18132
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
17874
18133
  db.update(researchRuns).set({
@@ -17877,11 +18136,11 @@ function finalizeResearchRun(db, runId, fatalError) {
17877
18136
  failedQueries: failed,
17878
18137
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
17879
18138
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
17880
- }).where(eq24(researchRuns.id, runId)).run();
18139
+ }).where(eq25(researchRuns.id, runId)).run();
17881
18140
  }
17882
18141
 
17883
18142
  // src/google-marketing-sync.ts
17884
- import { and as and19, eq as eq25, isNull as isNull2 } from "drizzle-orm";
18143
+ import { and as and20, eq as eq26, isNull as isNull2 } from "drizzle-orm";
17885
18144
  function safeErrorMessage(error) {
17886
18145
  const raw = describeError(error);
17887
18146
  return raw.replace(/(["']?authorization["']?\s*[:=]\s*["']?bearer\s+)[^\s,"'}]+/gi, "$1[redacted]").replace(/(["']?(?:developer|access|refresh|client)[_-]?(?:token|secret)["']?\s*[:=]\s*["']?)[^\s,"'}]+/gi, "$1[redacted]").replace(/[\r\n\t]+/g, " ").slice(0, 1e3);
@@ -17904,10 +18163,10 @@ function assertSnapshotContext(metadata2, expected) {
17904
18163
  }
17905
18164
  }
17906
18165
  function requireSyncRun(db, runId, projectId, kind) {
17907
- const run = db.select().from(runs).where(and19(
17908
- eq25(runs.id, runId),
17909
- eq25(runs.projectId, projectId),
17910
- eq25(runs.kind, kind)
18166
+ const run = db.select().from(runs).where(and20(
18167
+ eq26(runs.id, runId),
18168
+ eq26(runs.projectId, projectId),
18169
+ eq26(runs.kind, kind)
17911
18170
  )).get();
17912
18171
  if (!run) throw new Error(`Run ${runId} is not a ${kind} run for this project.`);
17913
18172
  if (run.status !== RunStatuses.queued) {
@@ -17920,19 +18179,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
17920
18179
  status: RunStatuses.failed,
17921
18180
  error: serializeRunError({ message: safeErrorMessage(error) }),
17922
18181
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
17923
- }).where(and19(
17924
- eq25(runs.id, runId),
17925
- eq25(runs.projectId, projectId),
17926
- eq25(runs.kind, kind),
17927
- eq25(runs.status, expectedStatus)
18182
+ }).where(and20(
18183
+ eq26(runs.id, runId),
18184
+ eq26(runs.projectId, projectId),
18185
+ eq26(runs.kind, kind),
18186
+ eq26(runs.status, expectedStatus)
17928
18187
  )).run();
17929
18188
  }
17930
18189
  function claimSyncRun(db, runId, projectId, kind, startedAt) {
17931
- const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and19(
17932
- eq25(runs.id, runId),
17933
- eq25(runs.projectId, projectId),
17934
- eq25(runs.kind, kind),
17935
- eq25(runs.status, RunStatuses.queued)
18190
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and20(
18191
+ eq26(runs.id, runId),
18192
+ eq26(runs.projectId, projectId),
18193
+ eq26(runs.kind, kind),
18194
+ eq26(runs.status, RunStatuses.queued)
17936
18195
  )).run();
17937
18196
  return claim.changes === 1;
17938
18197
  }
@@ -17940,9 +18199,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
17940
18199
  let claimed = false;
17941
18200
  try {
17942
18201
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
17943
- const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
18202
+ const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
17944
18203
  if (!project) throw new Error(`Project ${projectId} was not found.`);
17945
- const connection = db.select().from(googleAdsConnections).where(eq25(googleAdsConnections.projectId, projectId)).get();
18204
+ const connection = db.select().from(googleAdsConnections).where(eq26(googleAdsConnections.projectId, projectId)).get();
17946
18205
  if (!connection?.selectedCustomerId) {
17947
18206
  throw new Error("Google Ads needs a selected customer before it can sync.");
17948
18207
  }
@@ -17993,11 +18252,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
17993
18252
  status: RunStatuses.completed,
17994
18253
  finishedAt,
17995
18254
  error: null
17996
- }).where(and19(
17997
- eq25(runs.id, runId),
17998
- eq25(runs.projectId, projectId),
17999
- eq25(runs.kind, RunKinds["google-ads-sync"]),
18000
- eq25(runs.status, RunStatuses.running)
18255
+ }).where(and20(
18256
+ eq26(runs.id, runId),
18257
+ eq26(runs.projectId, projectId),
18258
+ eq26(runs.kind, RunKinds["google-ads-sync"]),
18259
+ eq26(runs.status, RunStatuses.running)
18001
18260
  )).run();
18002
18261
  if (terminalClaim.changes !== 1) {
18003
18262
  throw new Error(`Run ${runId} was no longer running when Google Ads evidence was ready.`);
@@ -18014,12 +18273,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
18014
18273
  lastMetricsSnapshotAt: result2.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
18015
18274
  lastMetricsSnapshotId: result2.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
18016
18275
  updatedAt: finishedAt
18017
- }).where(and19(
18018
- eq25(googleAdsConnections.id, connection.id),
18019
- eq25(googleAdsConnections.projectId, projectId),
18020
- eq25(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
18021
- connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) : eq25(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
18022
- eq25(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
18276
+ }).where(and20(
18277
+ eq26(googleAdsConnections.id, connection.id),
18278
+ eq26(googleAdsConnections.projectId, projectId),
18279
+ eq26(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
18280
+ connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) : eq26(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
18281
+ eq26(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
18023
18282
  )).run();
18024
18283
  if (connectionClaim.changes !== 1) {
18025
18284
  throw new Error("Google Ads selection changed while its evidence was being collected.");
@@ -18046,9 +18305,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18046
18305
  let claimed = false;
18047
18306
  try {
18048
18307
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
18049
- const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
18308
+ const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
18050
18309
  if (!project) throw new Error(`Project ${projectId} was not found.`);
18051
- const connection = db.select().from(gtmConnections).where(eq25(gtmConnections.projectId, projectId)).get();
18310
+ const connection = db.select().from(gtmConnections).where(eq26(gtmConnections.projectId, projectId)).get();
18052
18311
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
18053
18312
  throw new Error("GTM needs a selected account and container before it can sync.");
18054
18313
  }
@@ -18083,11 +18342,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18083
18342
  status: RunStatuses.completed,
18084
18343
  finishedAt,
18085
18344
  error: null
18086
- }).where(and19(
18087
- eq25(runs.id, runId),
18088
- eq25(runs.projectId, projectId),
18089
- eq25(runs.kind, RunKinds["gtm-sync"]),
18090
- eq25(runs.status, RunStatuses.running)
18345
+ }).where(and20(
18346
+ eq26(runs.id, runId),
18347
+ eq26(runs.projectId, projectId),
18348
+ eq26(runs.kind, RunKinds["gtm-sync"]),
18349
+ eq26(runs.status, RunStatuses.running)
18091
18350
  )).run();
18092
18351
  if (terminalClaim.changes !== 1) {
18093
18352
  throw new Error(`Run ${runId} was no longer running when GTM evidence was ready.`);
@@ -18101,13 +18360,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18101
18360
  lastSnapshotAt: snapshot.metadata.capturedAt,
18102
18361
  lastSnapshotId: snapshot.metadata.id,
18103
18362
  updatedAt: finishedAt
18104
- }).where(and19(
18105
- eq25(gtmConnections.id, connection.id),
18106
- eq25(gtmConnections.projectId, projectId),
18107
- eq25(gtmConnections.selectedAccountId, connection.selectedAccountId),
18108
- eq25(gtmConnections.selectedContainerId, connection.selectedContainerId),
18109
- connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) : eq25(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
18110
- eq25(gtmConnections.selectionGeneration, connection.selectionGeneration)
18363
+ }).where(and20(
18364
+ eq26(gtmConnections.id, connection.id),
18365
+ eq26(gtmConnections.projectId, projectId),
18366
+ eq26(gtmConnections.selectedAccountId, connection.selectedAccountId),
18367
+ eq26(gtmConnections.selectedContainerId, connection.selectedContainerId),
18368
+ connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) : eq26(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
18369
+ eq26(gtmConnections.selectionGeneration, connection.selectionGeneration)
18111
18370
  )).run();
18112
18371
  if (connectionClaim.changes !== 1) {
18113
18372
  throw new Error("GTM selection changed while its evidence was being collected.");
@@ -18611,7 +18870,7 @@ async function createServer(opts) {
18611
18870
  async (ctx) => {
18612
18871
  if (!sessionRegistry) return;
18613
18872
  if (!agentProactive) return;
18614
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq26(projects.id, ctx.projectId)).get();
18873
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq27(projects.id, ctx.projectId)).get();
18615
18874
  if (!project) return;
18616
18875
  let content;
18617
18876
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -19068,7 +19327,7 @@ async function createServer(opts) {
19068
19327
  void (async () => {
19069
19328
  try {
19070
19329
  const report = await schedulerClient.runDoctor({ project: projectName });
19071
- const project = opts.db.select().from(projects).where(eq26(projects.name, projectName)).get();
19330
+ const project = opts.db.select().from(projects).where(eq27(projects.name, projectName)).get();
19072
19331
  if (!project) {
19073
19332
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
19074
19333
  return;
@@ -19096,9 +19355,9 @@ async function createServer(opts) {
19096
19355
  });
19097
19356
  if (!probed) return;
19098
19357
  const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
19099
- and20(
19100
- eq26(ccReleaseSyncs.release, probed.release),
19101
- eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
19358
+ and21(
19359
+ eq27(ccReleaseSyncs.release, probed.release),
19360
+ eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
19102
19361
  )
19103
19362
  ).limit(1).get();
19104
19363
  if (alreadySynced) {
@@ -19369,7 +19628,7 @@ async function createServer(opts) {
19369
19628
  })();
19370
19629
  if (opts.config.apiKey) {
19371
19630
  const keyHash = hashApiKey2(opts.config.apiKey);
19372
- const existing = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, keyHash)).get();
19631
+ const existing = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, keyHash)).get();
19373
19632
  if (!existing) {
19374
19633
  const prefix = opts.config.apiKey.slice(0, 12);
19375
19634
  opts.db.insert(apiKeys).values({
@@ -19421,7 +19680,7 @@ async function createServer(opts) {
19421
19680
  };
19422
19681
  const getDefaultApiKey = () => {
19423
19682
  if (!opts.config.apiKey) return void 0;
19424
- return opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
19683
+ return opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
19425
19684
  };
19426
19685
  const createPasswordSession = (reply) => {
19427
19686
  const key = getDefaultApiKey();
@@ -19445,7 +19704,7 @@ async function createServer(opts) {
19445
19704
  if (!header) return false;
19446
19705
  const parts = header.split(" ");
19447
19706
  if (parts.length !== 2 || parts[0] !== "Bearer") return false;
19448
- const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(parts[1]))).get();
19707
+ const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(parts[1]))).get();
19449
19708
  return Boolean(key && !key.revokedAt);
19450
19709
  };
19451
19710
  const namedAccountsInUse = () => anyUsersExist(opts.db);
@@ -19535,12 +19794,12 @@ async function createServer(opts) {
19535
19794
  return reply.send({ authenticated: true });
19536
19795
  }
19537
19796
  if (apiKey) {
19538
- const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(apiKey))).get();
19797
+ const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(apiKey))).get();
19539
19798
  if (!key || key.revokedAt) {
19540
19799
  const err2 = authInvalid();
19541
19800
  return reply.status(err2.statusCode).send(err2.toJSON());
19542
19801
  }
19543
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(apiKeys.id, key.id)).run();
19802
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq27(apiKeys.id, key.id)).run();
19544
19803
  const sessionId = createSession(key.id);
19545
19804
  reply.header(
19546
19805
  "set-cookie",
@@ -19703,8 +19962,8 @@ async function createServer(opts) {
19703
19962
  getAgentPin: () => describeAgentPin(opts.config),
19704
19963
  getGoogleMarketingDoctorInput: (ctx) => {
19705
19964
  if (!ctx.project) return null;
19706
- const googleAds = opts.db.select().from(googleAdsConnections).where(eq26(googleAdsConnections.projectId, ctx.project.id)).get();
19707
- const gtm = opts.db.select().from(gtmConnections).where(eq26(gtmConnections.projectId, ctx.project.id)).get();
19965
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq27(googleAdsConnections.projectId, ctx.project.id)).get();
19966
+ const gtm = opts.db.select().from(gtmConnections).where(eq27(gtmConnections.projectId, ctx.project.id)).get();
19708
19967
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
19709
19968
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
19710
19969
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -19955,7 +20214,7 @@ async function createServer(opts) {
19955
20214
  ...inspectOpts,
19956
20215
  config: opts.config
19957
20216
  }).then(() => {
19958
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq26(runs.id, runId)).get();
20217
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq27(runs.id, runId)).get();
19959
20218
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
19960
20219
  return maybeRefreshGscCoverage(
19961
20220
  opts.db,
@@ -20221,7 +20480,7 @@ async function createServer(opts) {
20221
20480
  }
20222
20481
  ];
20223
20482
  },
20224
- onGenerateQueries: async (providerName, count, project) => {
20483
+ onGenerateQueries: async (providerName, count2, project) => {
20225
20484
  const provider = registry.get(providerName);
20226
20485
  if (!provider)
20227
20486
  throw new Error(`Provider "${providerName}" is not configured`);
@@ -20233,10 +20492,10 @@ async function createServer(opts) {
20233
20492
  language: project.language,
20234
20493
  existingQueries: project.existingQueries,
20235
20494
  siteText,
20236
- count
20495
+ count: count2
20237
20496
  });
20238
20497
  const raw = await provider.adapter.generateText(prompt, provider.config);
20239
- return parseQueryResponse(raw, count);
20498
+ return parseQueryResponse(raw, count2);
20240
20499
  },
20241
20500
  onSnapshotRequested: async (input) => {
20242
20501
  return snapshotService.createReport(input);
@@ -20431,7 +20690,7 @@ async function createServer(opts) {
20431
20690
  },
20432
20691
  notify: (projectId, result2) => notifier.onSiteLivenessChecked(projectId, result2)
20433
20692
  });
20434
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq26(researchRuns.status, ResearchRunStatuses.queued)).all()) {
20693
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq27(researchRuns.status, ResearchRunStatuses.queued)).all()) {
20435
20694
  dispatchResearchRun(run.id, run.projectId);
20436
20695
  }
20437
20696
  runtimeStartupSettled = true;
@@ -20486,7 +20745,7 @@ function buildQueryGenerationPrompt(ctx) {
20486
20745
  );
20487
20746
  return lines.join("\n");
20488
20747
  }
20489
- function parseQueryResponse(raw, count) {
20748
+ function parseQueryResponse(raw, count2) {
20490
20749
  const seen = /* @__PURE__ */ new Set();
20491
20750
  const results = [];
20492
20751
  for (const line of raw.split("\n")) {
@@ -20502,7 +20761,7 @@ function parseQueryResponse(raw, count) {
20502
20761
  if (seen.has(key)) continue;
20503
20762
  seen.add(key);
20504
20763
  results.push(cleaned);
20505
- if (results.length >= count) break;
20764
+ if (results.length >= count2) break;
20506
20765
  }
20507
20766
  return results;
20508
20767
  }