@canonry/canonry 5.19.0 → 5.20.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 (32) 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 +7 -2
  4. package/assets/assets/{AuditHistoryPanel-C_p8jX7Q.js → AuditHistoryPanel-BSh3Oezj.js} +1 -1
  5. package/assets/assets/{BacklinksPage-jD4nOGw1.js → BacklinksPage-DQ59dhCx.js} +1 -1
  6. package/assets/assets/{HistoryPage-DJGBbtBY.js → HistoryPage-BBvgid3o.js} +1 -1
  7. package/assets/assets/{MeasurementPropertyPage-Dmr3TVlo.js → MeasurementPropertyPage-Dmb18JbT.js} +1 -1
  8. package/assets/assets/{ProjectPage-B_haCKiA.js → ProjectPage-CPg0q2Dy.js} +1 -1
  9. package/assets/assets/{RunRow-BMbwR2yk.js → RunRow-BK8sZCID.js} +1 -1
  10. package/assets/assets/{RunsPage-D8hHakq2.js → RunsPage-CIcVBTdP.js} +1 -1
  11. package/assets/assets/{SettingsPage-QKnRcFA9.js → SettingsPage-Bc3aR-gz.js} +1 -1
  12. package/assets/assets/{SiteHealthSection-CDkUXqmb.js → SiteHealthSection-FF14aIXT.js} +3 -3
  13. package/assets/assets/{TrafficPage-CMif8RlM.js → TrafficPage-C_qoeGDl.js} +1 -1
  14. package/assets/assets/{TrafficSourceDetailPage-DOCTP_4-.js → TrafficSourceDetailPage-B3i_B2MI.js} +1 -1
  15. package/assets/assets/{extract-error-message-Dm8RvYE5.js → extract-error-message-BisUn6nN.js} +1 -1
  16. package/assets/assets/{index-lj0fuFl4.js → index-Dht7JNX5.js} +44 -44
  17. package/assets/assets/{react-sigma_core.esm.min-DOPuwLtN.js → react-sigma_core.esm.min-tWhS09SS.js} +1 -1
  18. package/assets/assets/{v2-overview-adapter-C19tUcDS.js → v2-overview-adapter-CAoeWKQJ.js} +1 -1
  19. package/assets/index.html +1 -1
  20. package/dist/{chunk-QJWIOCEO.js → chunk-4PENJKRN.js} +2 -2
  21. package/dist/{chunk-SPCEXW3B.js → chunk-5FKR2K76.js} +11 -4
  22. package/dist/{chunk-GUJY3RIM.js → chunk-DRZWMFRV.js} +627 -263
  23. package/dist/{chunk-WLL7Q7JD.js → chunk-QF7SY7AF.js} +222 -29
  24. package/dist/{chunk-ABUO27BV.js → chunk-TSFKNQ5X.js} +3 -3
  25. package/dist/{chunk-P2EFPLIO.js → chunk-WL27HO5C.js} +565 -139
  26. package/dist/{chunk-YHKKCXGY.js → chunk-XBGPHZKA.js} +24 -8
  27. package/dist/cli.js +73 -17
  28. package/dist/{demo-server-BOIBS74F.js → demo-server-OCBV5OMS.js} +4 -4
  29. package/dist/index.js +6 -6
  30. package/dist/{intelligence-service-IWR2H6OW.js → intelligence-service-MKHINDHN.js} +2 -2
  31. package/dist/mcp.js +4 -4
  32. package/package.json +14 -14
@@ -13,19 +13,19 @@ import {
13
13
  resolveOperatorApiKeyIds,
14
14
  runWithUsageTags,
15
15
  saveConfigPatch
16
- } from "./chunk-SPCEXW3B.js";
16
+ } from "./chunk-5FKR2K76.js";
17
17
  import {
18
18
  deleteSiteCrawlGraphLayout,
19
19
  persistSiteCrawlGraphLayout,
20
20
  prepareSiteCrawlGraphLayout
21
- } from "./chunk-QJWIOCEO.js";
21
+ } from "./chunk-4PENJKRN.js";
22
22
  import {
23
23
  CanonryMcpToolNames,
24
24
  CliError,
25
25
  PACKAGE_VERSION,
26
26
  canonryMcpTools,
27
27
  isMachineFormat
28
- } from "./chunk-YHKKCXGY.js";
28
+ } from "./chunk-XBGPHZKA.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-P2EFPLIO.js";
189
+ } from "./chunk-WL27HO5C.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,
@@ -326,6 +329,7 @@ import {
326
329
  providerError,
327
330
  providerSupportsLocationContext,
328
331
  registrableDomain,
332
+ resolveProviderModel,
329
333
  resolveSnapshotRequestQueries,
330
334
  retryAfterDelayMs,
331
335
  serializeForInlineScript,
@@ -347,7 +351,7 @@ import {
347
351
  visibilityReportRequestSchema,
348
352
  winnabilityClassLabel,
349
353
  withRetry
350
- } from "./chunk-WLL7Q7JD.js";
354
+ } from "./chunk-QF7SY7AF.js";
351
355
 
352
356
  // src/runtime-context.ts
353
357
  function cliRuntimeContext(env = process.env, stdio = process) {
@@ -3566,7 +3570,7 @@ import crypto27 from "crypto";
3566
3570
  import fs8 from "fs";
3567
3571
  import path8 from "path";
3568
3572
  import { fileURLToPath as fileURLToPath3 } from "url";
3569
- import { and as and20, eq as eq26 } from "drizzle-orm";
3573
+ import { and as and21, eq as eq27 } from "drizzle-orm";
3570
3574
  import Fastify from "fastify";
3571
3575
  import os4 from "os";
3572
3576
 
@@ -5713,8 +5717,36 @@ async function withRetry6(fn, options = {}) {
5713
5717
  }
5714
5718
 
5715
5719
  // ../provider-perplexity/src/normalize.ts
5716
- var DEFAULT_MODEL5 = "sonar";
5717
- var BASE_URL = "https://api.perplexity.ai";
5720
+ var DEFAULT_MODEL5 = "fast";
5721
+ var BASE_URL = "https://api.perplexity.ai/v1";
5722
+ var AGENT_PATH = "/agent";
5723
+ var PERPLEXITY_RETRIEVAL_CONTRACT = "search-required-v1";
5724
+ var RETRIEVAL_OUTPUT_TYPES = /* @__PURE__ */ new Set(["search_results", "fetch_url_results"]);
5725
+ function resolveModel3(model) {
5726
+ const trimmed = model?.trim();
5727
+ return resolveProviderModel("perplexity", trimmed ? trimmed : DEFAULT_MODEL5);
5728
+ }
5729
+ var ANTHROPIC_MAX_OUTPUT_TOKENS = 4096;
5730
+ function agentSelection(model) {
5731
+ if (!model.includes("/")) return { preset: model };
5732
+ return model.startsWith("anthropic/") ? { model, max_output_tokens: ANTHROPIC_MAX_OUTPUT_TOKENS } : { model };
5733
+ }
5734
+ function buildAgentRequest(query, model, location) {
5735
+ const webSearch = { type: "web_search" };
5736
+ if (location) {
5737
+ webSearch.user_location = {
5738
+ city: location.city,
5739
+ region: location.region,
5740
+ country: location.country
5741
+ };
5742
+ }
5743
+ return {
5744
+ ...agentSelection(model),
5745
+ input: query,
5746
+ tools: [webSearch],
5747
+ tool_choice: { type: "web_search" }
5748
+ };
5749
+ }
5718
5750
  function validateConfig5(config) {
5719
5751
  if (!config.apiKey || config.apiKey.length === 0) {
5720
5752
  return { ok: false, provider: "perplexity", message: "missing api key" };
@@ -5723,48 +5755,42 @@ function validateConfig5(config) {
5723
5755
  ok: true,
5724
5756
  provider: "perplexity",
5725
5757
  message: "config valid",
5726
- model: config.model ?? DEFAULT_MODEL5
5758
+ model: resolveModel3(config.model)
5727
5759
  };
5728
5760
  }
5729
5761
  async function healthcheck5(config) {
5730
5762
  const validation = validateConfig5(config);
5731
5763
  if (!validation.ok) return validation;
5764
+ const model = resolveModel3(config.model);
5732
5765
  try {
5733
- const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
5766
+ const client = createClient4(config.apiKey);
5734
5767
  const response = await withRetry6(
5735
- () => client.chat.completions.create({
5736
- model: config.model ?? DEFAULT_MODEL5,
5737
- messages: [{ role: "user", content: 'Say "ok"' }]
5738
- })
5768
+ () => postAgent(client, { ...agentSelection(model), input: 'Say "ok"' })
5739
5769
  );
5740
- const text = response.choices[0]?.message?.content ?? "";
5770
+ assertUsableResponse(response);
5771
+ const text = extractAgentAnswerText(response);
5741
5772
  return {
5742
5773
  ok: text.length > 0,
5743
5774
  provider: "perplexity",
5744
5775
  message: text.length > 0 ? "perplexity api key verified" : "empty response from perplexity",
5745
- model: config.model ?? DEFAULT_MODEL5
5776
+ model
5746
5777
  };
5747
5778
  } catch (err) {
5748
5779
  return {
5749
5780
  ok: false,
5750
5781
  provider: "perplexity",
5751
5782
  message: describeError(err),
5752
- model: config.model ?? DEFAULT_MODEL5
5783
+ model
5753
5784
  };
5754
5785
  }
5755
5786
  }
5756
5787
  async function executeTrackedQuery5(input) {
5757
- const model = input.config.model ?? DEFAULT_MODEL5;
5758
- const client = new OpenAI4({ apiKey: input.config.apiKey, baseURL: BASE_URL });
5759
- const prompt = buildPrompt4(input.query, input.location);
5788
+ const model = resolveModel3(input.config.model);
5789
+ const client = createClient4(input.config.apiKey);
5790
+ const request = buildAgentRequest(input.query, model, input.location);
5760
5791
  try {
5761
- const response = await withRetry6(
5762
- () => client.chat.completions.create({
5763
- model,
5764
- messages: [{ role: "user", content: prompt }]
5765
- })
5766
- );
5767
- const rawResponse = responseToRecord5(response);
5792
+ const rawResponse = await withRetry6(() => postAgent(client, request));
5793
+ assertUsableResponse(rawResponse);
5768
5794
  const parsed = reparseStoredResult4(rawResponse);
5769
5795
  return {
5770
5796
  provider: "perplexity",
@@ -5772,7 +5798,8 @@ async function executeTrackedQuery5(input) {
5772
5798
  model,
5773
5799
  servedModel: extractServedModel5(rawResponse),
5774
5800
  groundingSources: parsed.groundingSources,
5775
- searchQueries: parsed.searchQueries
5801
+ searchQueries: parsed.searchQueries,
5802
+ retrievalStatus: parsed.retrievalStatus
5776
5803
  };
5777
5804
  } catch (err) {
5778
5805
  const msg = describeError(err);
@@ -5790,10 +5817,13 @@ function normalizeResult6(raw) {
5790
5817
  answerText: parsed.answerText,
5791
5818
  citedDomains,
5792
5819
  groundingSources,
5793
- searchQueries
5820
+ searchQueries,
5821
+ retrievalStatus: useParsed ? parsed.retrievalStatus : raw.retrievalStatus ?? "unknown"
5794
5822
  };
5795
5823
  }
5796
5824
  function hasParsedResponseContent4(rawResponse) {
5825
+ const agent = agentResponseOf(rawResponse);
5826
+ if (agent) return agentOutput(agent).length > 0;
5797
5827
  if (Array.isArray(rawResponse.choices) && rawResponse.choices.length > 0) return true;
5798
5828
  if (Array.isArray(rawResponse.search_results) && rawResponse.search_results.length > 0) return true;
5799
5829
  if (Array.isArray(rawResponse.citations) && rawResponse.citations.length > 0) return true;
@@ -5805,24 +5835,113 @@ function extractServedModel5(rawResponse) {
5805
5835
  return normalizeServedModel(rawResponse.model);
5806
5836
  }
5807
5837
  function reparseStoredResult4(rawResponse) {
5838
+ const agent = agentResponseOf(rawResponse);
5839
+ if (agent) return parseAgentResponse(agent);
5840
+ return parseSonarResponse(rawResponse);
5841
+ }
5842
+ function createClient4(apiKey) {
5843
+ return new OpenAI4({ apiKey, baseURL: BASE_URL });
5844
+ }
5845
+ function postAgent(client, body) {
5846
+ return client.post(AGENT_PATH, { body });
5847
+ }
5848
+ function assertUsableResponse(response) {
5849
+ const status = response.status;
5850
+ if (status === void 0 || status === "completed") return;
5851
+ if (status === "incomplete") {
5852
+ if (extractAgentAnswerText(response).trim().length > 0) return;
5853
+ const details = isRecord(response.incomplete_details) ? response.incomplete_details : void 0;
5854
+ const reason = typeof details?.reason === "string" && details.reason.length > 0 ? details.reason : "no reason given";
5855
+ throw new Error(`agent response incomplete with no answer: ${reason}`);
5856
+ }
5857
+ const error = isRecord(response.error) ? response.error : void 0;
5858
+ const message = typeof error?.message === "string" && error.message.length > 0 ? error.message : "no error detail";
5859
+ const type = typeof error?.type === "string" && error.type.length > 0 ? ` (${error.type})` : "";
5860
+ throw new Error(`agent response ${typeof status === "string" ? status : describeError(status)}: ${message}${type}`);
5861
+ }
5862
+ function agentResponseOf(rawResponse) {
5863
+ if (Array.isArray(rawResponse.output)) return rawResponse;
5864
+ const nested = extractNestedApiResponse(rawResponse);
5865
+ return nested && Array.isArray(nested.output) ? nested : null;
5866
+ }
5867
+ function agentOutput(response) {
5868
+ return Array.isArray(response.output) ? response.output.filter(isRecord) : [];
5869
+ }
5870
+ function parseAgentResponse(response) {
5871
+ const groundingSources = extractAgentGroundingSources(response);
5872
+ return {
5873
+ provider: "perplexity",
5874
+ answerText: extractAgentAnswerText(response),
5875
+ citedDomains: extractCitedDomains2(groundingSources),
5876
+ groundingSources,
5877
+ searchQueries: extractAgentSearchQueries(response),
5878
+ retrievalStatus: extractAgentRetrievalStatus(response)
5879
+ };
5880
+ }
5881
+ function extractAgentAnswerText(response) {
5882
+ const parts = [];
5883
+ for (const item of agentOutput(response)) {
5884
+ if (item.type !== "message" || !Array.isArray(item.content)) continue;
5885
+ for (const part of item.content) {
5886
+ if (isRecord(part) && part.type === "output_text" && typeof part.text === "string") parts.push(part.text);
5887
+ }
5888
+ }
5889
+ return parts.join("");
5890
+ }
5891
+ function extractAgentGroundingSources(response) {
5892
+ const sources = [];
5893
+ const seen = /* @__PURE__ */ new Set();
5894
+ const add = (entry) => {
5895
+ if (!isRecord(entry) || typeof entry.url !== "string" || entry.url.length === 0) return;
5896
+ if (seen.has(entry.url)) return;
5897
+ seen.add(entry.url);
5898
+ sources.push({ uri: entry.url, title: typeof entry.title === "string" ? entry.title : "" });
5899
+ };
5900
+ for (const item of agentOutput(response)) {
5901
+ if (item.type === "search_results" && Array.isArray(item.results)) {
5902
+ item.results.forEach(add);
5903
+ } else if (item.type === "fetch_url_results" && Array.isArray(item.contents)) {
5904
+ item.contents.forEach(add);
5905
+ } else if (item.type === "message" && Array.isArray(item.content)) {
5906
+ for (const part of item.content) {
5907
+ if (isRecord(part) && Array.isArray(part.annotations)) part.annotations.forEach(add);
5908
+ }
5909
+ }
5910
+ }
5911
+ return sources;
5912
+ }
5913
+ function extractAgentSearchQueries(response) {
5914
+ const queries2 = /* @__PURE__ */ new Set();
5915
+ for (const item of agentOutput(response)) {
5916
+ if (item.type !== "search_results" || !Array.isArray(item.queries)) continue;
5917
+ for (const query of item.queries) {
5918
+ if (typeof query === "string" && query.trim().length > 0) queries2.add(query);
5919
+ }
5920
+ }
5921
+ return [...queries2];
5922
+ }
5923
+ function extractAgentRetrievalStatus(response) {
5924
+ const output = agentOutput(response);
5925
+ if (output.some((item) => typeof item.type === "string" && RETRIEVAL_OUTPUT_TYPES.has(item.type))) return "used";
5926
+ if (output.some((item) => item.type === "message")) return "not-used";
5927
+ return "unknown";
5928
+ }
5929
+ function parseSonarResponse(rawResponse) {
5808
5930
  const groundingSources = extractGroundingSources(rawResponse);
5809
5931
  return {
5810
5932
  provider: "perplexity",
5811
5933
  answerText: extractAnswerText3(rawResponse),
5812
5934
  citedDomains: extractCitedDomains2(groundingSources),
5813
5935
  groundingSources,
5814
- // Perplexity documents `search_results` and `citations` on the response structure but
5815
- // does not document returned search-query telemetry, so Canonry does not synthesize it.
5936
+ // Sonar documented `search_results` and `citations` on the response but no
5937
+ // returned search-query telemetry, so Canonry does not synthesize it.
5816
5938
  // Docs: https://docs.perplexity.ai/docs/sonar/openai-compatibility
5817
- searchQueries: []
5939
+ searchQueries: [],
5940
+ // Sonar's only retrieval marker was present on every stored row, so it
5941
+ // never discriminated a non-retrieving answer. `unknown` is what we know.
5942
+ retrievalStatus: "unknown"
5818
5943
  };
5819
5944
  }
5820
- function buildPrompt4(query, location) {
5821
- if (location) {
5822
- return `${query} (searching from ${location.city}, ${location.region}, ${location.country})`;
5823
- }
5824
- return query;
5825
- }
5826
5945
  function extractCitations(rawResponse) {
5827
5946
  if (Array.isArray(rawResponse.citations)) {
5828
5947
  return rawResponse.citations.filter((c) => typeof c === "string");
@@ -5865,9 +5984,7 @@ function extractSearchResults(rawResponse) {
5865
5984
  function parseSearchResultsArray(value) {
5866
5985
  if (!Array.isArray(value)) return [];
5867
5986
  return value.flatMap((result2) => {
5868
- if (result2 === null || typeof result2 !== "object" || Array.isArray(result2)) {
5869
- return [];
5870
- }
5987
+ if (!isRecord(result2)) return [];
5871
5988
  const url = result2.url;
5872
5989
  if (typeof url !== "string" || url.length === 0) {
5873
5990
  return [];
@@ -5894,11 +6011,10 @@ function extractAnswerText3(rawResponse) {
5894
6011
  }
5895
6012
  }
5896
6013
  function extractNestedApiResponse(rawResponse) {
5897
- const apiResponse = rawResponse.apiResponse;
5898
- if (apiResponse !== null && typeof apiResponse === "object" && !Array.isArray(apiResponse)) {
5899
- return apiResponse;
5900
- }
5901
- return null;
6014
+ return isRecord(rawResponse.apiResponse) ? rawResponse.apiResponse : null;
6015
+ }
6016
+ function isRecord(value) {
6017
+ return value !== null && typeof value === "object" && !Array.isArray(value);
5902
6018
  }
5903
6019
  function extractCitedDomains2(groundingSources) {
5904
6020
  const domains = /* @__PURE__ */ new Set();
@@ -5914,22 +6030,11 @@ function extractDomainFromUri4(uri) {
5914
6030
  return hostname;
5915
6031
  }
5916
6032
  async function generateText5(prompt, config) {
5917
- const model = config.model ?? DEFAULT_MODEL5;
5918
- const client = new OpenAI4({ apiKey: config.apiKey, baseURL: BASE_URL });
5919
- const response = await withRetry6(
5920
- () => client.chat.completions.create({
5921
- model,
5922
- messages: [{ role: "user", content: prompt }]
5923
- })
5924
- );
5925
- return response.choices[0]?.message?.content ?? "";
5926
- }
5927
- function responseToRecord5(response) {
5928
- try {
5929
- return JSON.parse(JSON.stringify(response));
5930
- } catch {
5931
- return { error: "failed to serialize response" };
5932
- }
6033
+ const model = resolveModel3(config.model);
6034
+ const client = createClient4(config.apiKey);
6035
+ const response = await withRetry6(() => postAgent(client, { ...agentSelection(model), input: prompt }));
6036
+ assertUsableResponse(response);
6037
+ return extractAgentAnswerText(response);
5933
6038
  }
5934
6039
 
5935
6040
  // ../provider-perplexity/src/adapter.ts
@@ -5944,19 +6049,22 @@ var perplexityAdapter = {
5944
6049
  name: "perplexity",
5945
6050
  displayName: "Perplexity",
5946
6051
  mode: "api",
5947
- // normalize.ts folds the location into the prompt the model receives.
6052
+ // normalize.ts sends the location as `user_location` on the web_search tool.
5948
6053
  supportsLocationContext: true,
5949
6054
  keyUrl: "https://www.perplexity.ai/settings/api",
5950
- // Upstream model list: https://docs.perplexity.ai/guides/model-cards
6055
+ // Agent API presets: https://docs.perplexity.ai/docs/agent-api/presets
6056
+ // Model slugs (GET https://api.perplexity.ai/v1/models): provider-prefixed, e.g. perplexity/sonar.
5951
6057
  modelRegistry: {
5952
- defaultModel: "sonar",
5953
- validationPattern: /^sonar/,
5954
- validationHint: "expected a sonar model (e.g. sonar, sonar-pro, sonar-reasoning)",
6058
+ defaultModel: DEFAULT_MODEL5,
6059
+ // Presets, `vendor/model` slugs, and the retired Sonar names and previous
6060
+ // preset names that PROVIDER_MODEL_ALIASES resolves (`sonar` → `fast`).
6061
+ validationPattern: /^(?:fast|low|medium|high|xhigh|fast-search|pro-search|deep-research|advanced-deep-research|sonar(?:-pro|-reasoning|-reasoning-pro|-deep-research)?|[a-z0-9][a-z0-9-]*\/[A-Za-z0-9][\w.:-]*)$/,
6062
+ validationHint: "expected an Agent API preset (fast, low, medium, high, xhigh) or a provider/model slug (e.g. perplexity/sonar)",
5955
6063
  knownModels: [
5956
- { id: "sonar", displayName: "Sonar", tier: "standard" },
5957
- { id: "sonar-pro", displayName: "Sonar Pro", tier: "flagship" },
5958
- { id: "sonar-reasoning", displayName: "Sonar Reasoning", tier: "flagship" },
5959
- { id: "sonar-reasoning-pro", displayName: "Sonar Reasoning Pro", tier: "flagship" }
6064
+ { id: "fast", displayName: "Fast preset", tier: "standard" },
6065
+ { id: "low", displayName: "Low preset (pro search)", tier: "flagship" },
6066
+ { id: "medium", displayName: "Medium preset (deep research)", tier: "flagship" },
6067
+ { id: "perplexity/sonar", displayName: "Sonar model + web search", tier: "fast" }
5960
6068
  ]
5961
6069
  },
5962
6070
  validateConfig(config) {
@@ -5992,13 +6100,10 @@ var perplexityAdapter = {
5992
6100
  servedModel: raw.servedModel,
5993
6101
  groundingSources: raw.groundingSources,
5994
6102
  searchQueries: raw.searchQueries,
5995
- // Retrieval detection is not implemented for this provider. Its candidate
5996
- // marker is present on 100% of stored rows, so it has never been shown to
5997
- // discriminate a non-retrieving answer and wiring it up would hardcode
5998
- // `used`. `unknown` states what we actually know. The contract is a
5999
- // declaration about how we build the request, so it is always knowable.
6000
- retrievalStatus: "unknown",
6001
- retrievalContract: "native-auto-v1"
6103
+ // Read off the Agent API output: a `search_results` item is the retrieval
6104
+ // call. The contract is how we built the request, so it is always known.
6105
+ retrievalStatus: raw.retrievalStatus ?? "unknown",
6106
+ retrievalContract: PERPLEXITY_RETRIEVAL_CONTRACT
6002
6107
  };
6003
6108
  },
6004
6109
  normalizeResult(raw) {
@@ -6007,7 +6112,8 @@ var perplexityAdapter = {
6007
6112
  rawResponse: raw.rawResponse,
6008
6113
  model: raw.model,
6009
6114
  groundingSources: raw.groundingSources,
6010
- searchQueries: raw.searchQueries
6115
+ searchQueries: raw.searchQueries,
6116
+ retrievalStatus: raw.retrievalStatus
6011
6117
  };
6012
6118
  const normalized = normalizeResult6(perplexityRaw);
6013
6119
  return {
@@ -6016,7 +6122,7 @@ var perplexityAdapter = {
6016
6122
  citedDomains: normalized.citedDomains,
6017
6123
  groundingSources: normalized.groundingSources,
6018
6124
  searchQueries: normalized.searchQueries,
6019
- retrievalStatus: "unknown"
6125
+ retrievalStatus: normalized.retrievalStatus
6020
6126
  };
6021
6127
  },
6022
6128
  async generateText(prompt, config) {
@@ -6498,7 +6604,7 @@ function nonBlank(value) {
6498
6604
  return trimmed ? trimmed : void 0;
6499
6605
  }
6500
6606
  function resolveBuildCommit(env = process.env) {
6501
- const embedded = true ? "622523aabfe6bf086a3086ac1e520f57215853a9" : void 0;
6607
+ const embedded = true ? "b5366056cfff6f0e275fe651252cfa1961bf40a9" : void 0;
6502
6608
  return nonBlank(embedded) ?? nonBlank(env.CANONRY_COMMIT);
6503
6609
  }
6504
6610
  function resolveInstanceIdentity(env = process.env) {
@@ -6964,7 +7070,8 @@ var JobRunner = class {
6964
7070
  } else {
6965
7071
  const projectProviders = providerOverride ?? project.providers;
6966
7072
  activeProviders = this.registry.getForProject(projectProviders).map((entry) => {
6967
- const model = project.providerModels[entry.adapter.name];
7073
+ const override = project.providerModels[entry.adapter.name];
7074
+ const model = override === void 0 ? void 0 : resolveProviderModel(entry.adapter.name, override);
6968
7075
  return model === void 0 ? entry : { ...entry, config: { ...entry.config, model } };
6969
7076
  });
6970
7077
  }
@@ -7444,15 +7551,15 @@ var JobRunner = class {
7444
7551
  const dispatchable = [];
7445
7552
  for (const registered of activeProviders) {
7446
7553
  const name = registered.adapter.name;
7447
- const count = unitsByProvider.get(name)?.length ?? 0;
7554
+ const count2 = unitsByProvider.get(name)?.length ?? 0;
7448
7555
  const scope = `${projectId}:${name}`;
7449
7556
  const limit = registered.config.quotaPolicy.maxRequestsPerDay;
7450
- const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count, limit });
7557
+ const quota = reserveDailyQueryQuota(this.db, { scope, period: todayPeriod, count: count2, limit });
7451
7558
  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.`);
7559
+ providerErrors.set(name, `Daily quota exceeded for ${name}: ${quota.used} queries used today, limit is ${limit}. This fill needs ${count2} more.`);
7453
7560
  continue;
7454
7561
  }
7455
- providerReservations.set(name, { scope, period: todayPeriod, reserved: count });
7562
+ providerReservations.set(name, { scope, period: todayPeriod, reserved: count2 });
7456
7563
  dispatchable.push(registered);
7457
7564
  }
7458
7565
  const executionGates = /* @__PURE__ */ new Map();
@@ -7565,8 +7672,8 @@ var JobRunner = class {
7565
7672
  const remaining = /* @__PURE__ */ new Map();
7566
7673
  for (const slot of state.missing) remaining.set(slot.provider, (remaining.get(slot.provider) ?? 0) + 1);
7567
7674
  const providers = {};
7568
- for (const [provider, count] of remaining) {
7569
- providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${count} expected measurement(s) have not run.` };
7675
+ for (const [provider, count2] of remaining) {
7676
+ providers[provider] = fresh[provider] ?? previous[provider] ?? { message: `${count2} expected measurement(s) have not run.` };
7570
7677
  }
7571
7678
  txDb.update(runs).set({ error: serializeRunError({ providers }) }).where(and2(eq2(runs.id, input.runId), eq2(runs.status, "partial"))).run();
7572
7679
  }
@@ -7735,7 +7842,7 @@ var JobRunner = class {
7735
7842
  ctx.fill?.onOutcome(providerName, false);
7736
7843
  }
7737
7844
  }
7738
- incrementUsage(scope, metric, count) {
7845
+ incrementUsage(scope, metric, count2) {
7739
7846
  const now = (/* @__PURE__ */ new Date()).toISOString();
7740
7847
  const period = now.slice(0, 10);
7741
7848
  this.db.insert(usageCounters).values({
@@ -7743,11 +7850,11 @@ var JobRunner = class {
7743
7850
  scope,
7744
7851
  period,
7745
7852
  metric,
7746
- count,
7853
+ count: count2,
7747
7854
  updatedAt: now
7748
7855
  }).onConflictDoUpdate({
7749
7856
  target: [usageCounters.scope, usageCounters.period, usageCounters.metric],
7750
- set: { count: sql2`${usageCounters.count} + ${count}`, updatedAt: now }
7857
+ set: { count: sql2`${usageCounters.count} + ${count2}`, updatedAt: now }
7751
7858
  }).run();
7752
7859
  }
7753
7860
  flushProviderUsage(providerDispatchCounts, providerReservations) {
@@ -8471,11 +8578,11 @@ function validateGeoSearchLimit(limit) {
8471
8578
  );
8472
8579
  }
8473
8580
  }
8474
- function isRecord(value) {
8581
+ function isRecord2(value) {
8475
8582
  return typeof value === "object" && value !== null && !Array.isArray(value);
8476
8583
  }
8477
8584
  function validateRequestObject(value, label) {
8478
- if (!isRecord(value)) {
8585
+ if (!isRecord2(value)) {
8479
8586
  throw new OpenAiAdsApiError(`${label} must be a JSON object`, 400);
8480
8587
  }
8481
8588
  }
@@ -8634,7 +8741,7 @@ function validateLandingPageConfiguration(value, label) {
8634
8741
  }
8635
8742
  }
8636
8743
  function parseLandingPageQueryStringTemplate(value) {
8637
- if (!isRecord(value)) return null;
8744
+ if (!isRecord2(value)) return null;
8638
8745
  const template = value.query_string_template;
8639
8746
  return typeof template === "string" && template.trim().length > 0 ? template : null;
8640
8747
  }
@@ -12074,7 +12181,7 @@ function readStoredGroundingSources(rawResponse) {
12074
12181
  return result2;
12075
12182
  }
12076
12183
  async function backfillInsightsCommand(project, opts) {
12077
- const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-IWR2H6OW.js");
12184
+ const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-MKHINDHN.js");
12078
12185
  const config = loadConfig();
12079
12186
  const db = createClient(config.database);
12080
12187
  migrate(db);
@@ -12588,8 +12695,8 @@ Traffic classification ${result2.dryRun ? "preview" : "recovery"} complete.`);
12588
12695
  if (result2.reclassified > 0) {
12589
12696
  console.log(` By bot:`);
12590
12697
  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}`);
12698
+ for (const [bot, count2] of sorted) {
12699
+ console.log(` ${count2.toString().padStart(5)} ${bot}`);
12593
12700
  }
12594
12701
  }
12595
12702
  if (result2.dryRun) {
@@ -12602,7 +12709,8 @@ No DB writes performed. Re-run without --dry-run to apply.`);
12602
12709
  var ProviderRegistry = class {
12603
12710
  providers = /* @__PURE__ */ new Map();
12604
12711
  register(adapter, config) {
12605
- this.providers.set(adapter.name, { adapter, config });
12712
+ const resolved = config.model === void 0 ? config : { ...config, model: resolveProviderModel(adapter.name, config.model) };
12713
+ this.providers.set(adapter.name, { adapter, config: resolved });
12606
12714
  }
12607
12715
  get(name) {
12608
12716
  return this.providers.get(name);
@@ -13928,7 +14036,7 @@ function analysisResultFromInsights(insights2) {
13928
14036
 
13929
14037
  // src/agent/session-registry.ts
13930
14038
  import crypto24 from "crypto";
13931
- import { eq as eq20 } from "drizzle-orm";
14039
+ import { eq as eq21 } from "drizzle-orm";
13932
14040
 
13933
14041
  // src/agent/session.ts
13934
14042
  import fs7 from "fs";
@@ -14301,12 +14409,26 @@ import { Type as Type3 } from "@sinclair/typebox";
14301
14409
  import { Type as Type2 } from "@sinclair/typebox";
14302
14410
  import { randomUUID } from "crypto";
14303
14411
  var MAX_TOOL_RESULT_CHARS = 2e4;
14304
- var TRUNCATION_NOTE = "... (truncated \u2014 result too large)";
14412
+ var TRUNCATION_NOTE = "... (truncated, result too large)";
14413
+ var TRUNCATION_SUMMARY_KEY = "__truncation";
14305
14414
  var MAX_STRUCTURED_TRUNCATION_CHARS = 2e6;
14306
- var MAX_TRUNCATION_PASSES = 32;
14415
+ var TRUNCATION_PASS_BUDGET_CHARS = 64e6;
14416
+ var MIN_TRUNCATION_PASSES = 32;
14417
+ var MAX_TRUNCATION_PASSES = 200;
14418
+ var MAX_SUMMARY_DEPTH = 5;
14419
+ var MAX_SUMMARY_KEYS = 30;
14420
+ var MAX_SUMMARY_SEGMENT_CHARS = 60;
14307
14421
  function serializeResult(value) {
14308
14422
  return JSON.stringify(value, null, 2);
14309
14423
  }
14424
+ function keptSummary(counts) {
14425
+ const summary = { droppedKeys: [], keptItems: {} };
14426
+ for (const [path9, { kept, total }] of counts) {
14427
+ if (kept === 0) summary.droppedKeys.push(path9);
14428
+ summary.keptItems[path9] = `${kept} of ${total}`;
14429
+ }
14430
+ return summary;
14431
+ }
14310
14432
  function largestArrayKey(obj) {
14311
14433
  let best;
14312
14434
  let bestLen = -1;
@@ -14320,117 +14442,278 @@ function largestArrayKey(obj) {
14320
14442
  }
14321
14443
  return best;
14322
14444
  }
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;
14445
+ function fitsCap(text) {
14446
+ return text.length <= MAX_TOOL_RESULT_CHARS;
14447
+ }
14448
+ function largestFitting(max, fits) {
14327
14449
  let low = 0;
14328
- let high = rows.length - 1;
14450
+ let high = max;
14329
14451
  while (low < high) {
14330
14452
  const middle = Math.ceil((low + high) / 2);
14331
- if (render(rows.slice(0, middle)).length <= MAX_TOOL_RESULT_CHARS) low = middle;
14453
+ if (fits(middle)) low = middle;
14332
14454
  else high = middle - 1;
14333
14455
  }
14334
- return rows.slice(0, low);
14456
+ return low;
14335
14457
  }
14336
- function nestedArrayCandidates(root) {
14337
- const candidates = [];
14338
- const visit = (object) => {
14458
+ function trimRowsToFit(rows, render) {
14459
+ if (rows.length === 0) return [];
14460
+ const all = rows.slice();
14461
+ if (fitsCap(render(all))) return all;
14462
+ return rows.slice(0, largestFitting(rows.length - 1, (count2) => fitsCap(render(rows.slice(0, count2)))));
14463
+ }
14464
+ function nestedArrayGroups(root) {
14465
+ const groups = /* @__PURE__ */ new Map();
14466
+ const visit = (object, path9) => {
14339
14467
  let containsRecordCollection = false;
14340
14468
  for (const [key, value] of Object.entries(object)) {
14469
+ if (object === root && key === TRUNCATION_SUMMARY_KEY) continue;
14470
+ const valuePath = path9 ? `${path9}.${key}` : key;
14341
14471
  if (Array.isArray(value)) {
14342
14472
  let hasNestedRecords = false;
14343
14473
  let hasRecordRows = false;
14344
14474
  for (const row of value) {
14345
14475
  if (row && typeof row === "object" && !Array.isArray(row)) {
14346
14476
  hasRecordRows = true;
14347
- hasNestedRecords = visit(row) || hasNestedRecords;
14477
+ hasNestedRecords = visit(row, `${valuePath}[]`) || hasNestedRecords;
14348
14478
  }
14349
14479
  }
14350
14480
  if (value.length > 0 && !hasNestedRecords) {
14351
- candidates.push({ owner: object, key, rows: value, size: serializeResult(value).length, hasRecordRows });
14481
+ const id = `${Number(hasRecordRows)}:${valuePath}`;
14482
+ const group = groups.get(id) ?? { path: valuePath, members: [], size: 0, hasRecordRows };
14483
+ group.members.push({ owner: object, key, rows: value });
14484
+ group.size += serializeResult(value).length;
14485
+ groups.set(id, group);
14352
14486
  }
14353
14487
  containsRecordCollection = hasRecordRows || hasNestedRecords || containsRecordCollection;
14354
14488
  } else if (value && typeof value === "object") {
14355
- containsRecordCollection = visit(value) || containsRecordCollection;
14489
+ containsRecordCollection = visit(value, valuePath) || containsRecordCollection;
14356
14490
  }
14357
14491
  }
14358
14492
  return containsRecordCollection;
14359
14493
  };
14360
- visit(root);
14361
- return candidates.sort(
14494
+ visit(root, "");
14495
+ return [...groups.values()].sort(
14362
14496
  (left, right) => Number(right.hasRecordRows) - Number(left.hasRecordRows) || right.size - left.size
14363
14497
  );
14364
14498
  }
14499
+ function memberTrimmer({ owner, key, rows }) {
14500
+ const hadTruncated = Object.hasOwn(owner, "__truncated");
14501
+ const priorTruncated = owner.__truncated;
14502
+ const previous = owner.__omittedRowsByField;
14503
+ const priorOmitted = previous && typeof previous === "object" && !Array.isArray(previous) ? previous : void 0;
14504
+ const priorValue = priorOmitted?.[key];
14505
+ const priorCount = typeof priorValue === "number" ? priorValue : 0;
14506
+ return (count2) => {
14507
+ const kept = Math.min(count2, rows.length);
14508
+ owner[key] = rows.slice(0, kept);
14509
+ const omitted = priorCount + rows.length - kept;
14510
+ if (omitted > 0) {
14511
+ owner.__truncated = true;
14512
+ owner.__omittedRowsByField = { ...priorOmitted, [key]: omitted };
14513
+ return kept;
14514
+ }
14515
+ if (hadTruncated) owner.__truncated = priorTruncated;
14516
+ else delete owner.__truncated;
14517
+ if (previous === void 0) delete owner.__omittedRowsByField;
14518
+ else owner.__omittedRowsByField = previous;
14519
+ return kept;
14520
+ };
14521
+ }
14522
+ function emptiedFloorChars(root) {
14523
+ return JSON.stringify(root, (_key, value) => Array.isArray(value) ? [] : value, 2).length;
14524
+ }
14365
14525
  function trimNestedArrays(full) {
14366
14526
  const parsed = JSON.parse(full);
14367
14527
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
14368
14528
  const copy = parsed;
14529
+ const counts = /* @__PURE__ */ new Map();
14369
14530
  copy.__truncated = true;
14531
+ copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
14532
+ if (emptiedFloorChars(copy) > MAX_TOOL_RESULT_CHARS) return void 0;
14370
14533
  let out = serializeResult(copy);
14534
+ const passLimit = Math.min(
14535
+ MAX_TRUNCATION_PASSES,
14536
+ Math.max(MIN_TRUNCATION_PASSES, Math.floor(TRUNCATION_PASS_BUDGET_CHARS / full.length))
14537
+ );
14371
14538
  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;
14539
+ while (!fitsCap(out)) {
14540
+ if (++passes > passLimit) return void 0;
14541
+ const group = nestedArrayGroups(copy).at(0);
14542
+ if (!group) return void 0;
14543
+ const trimmers = group.members.map(memberTrimmer);
14544
+ const total = group.members.reduce((sum, member) => sum + member.rows.length, 0);
14545
+ const longest = group.members.reduce((max, member) => Math.max(max, member.rows.length), 0);
14546
+ const prior = counts.get(group.path) ?? { kept: 0, total: 0 };
14547
+ const keep = (count3, extra2 = 0) => {
14548
+ const kept = trimmers.reduce((sum, trim, index) => sum + trim(index < extra2 ? count3 + 1 : count3), 0);
14549
+ counts.set(group.path, { kept: prior.kept + kept, total: prior.total + total });
14550
+ copy[TRUNCATION_SUMMARY_KEY] = keptSummary(counts);
14385
14551
  return serializeResult(copy);
14386
14552
  };
14387
14553
  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);
14554
+ if (!fitsCap(out)) continue;
14555
+ const count2 = largestFitting(longest - 1, (n) => fitsCap(keep(n)));
14556
+ const extra = trimmers.length > 1 ? largestFitting(trimmers.length - 1, (n) => fitsCap(keep(count2, n))) : 0;
14557
+ out = keep(count2, extra);
14397
14558
  }
14398
14559
  return out;
14399
14560
  }
14561
+ function shortSegment(key) {
14562
+ return key.length > MAX_SUMMARY_SEGMENT_CHARS ? `${key.slice(0, MAX_SUMMARY_SEGMENT_CHARS)}...` : key;
14563
+ }
14564
+ function childPath(container) {
14565
+ if (container.kind === "array") return `${container.path}[${container.complete}]`;
14566
+ const key = shortSegment(container.key ?? "");
14567
+ return container.path ? `${container.path}.${key}` : key;
14568
+ }
14569
+ function serializedKeys(value) {
14570
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
14571
+ const record = value;
14572
+ if (typeof record.toJSON === "function") return void 0;
14573
+ return Object.keys(record).filter((key) => {
14574
+ const child = record[key];
14575
+ return child !== void 0 && typeof child !== "function" && typeof child !== "symbol";
14576
+ });
14577
+ }
14578
+ function openContainersAt(shown, details) {
14579
+ const stack = [];
14580
+ let inString = false;
14581
+ let escaped = false;
14582
+ let stringStart = 0;
14583
+ for (let i = 0; i < shown.length; i++) {
14584
+ const ch = shown[i];
14585
+ const top = stack.at(-1);
14586
+ if (inString) {
14587
+ if (escaped) escaped = false;
14588
+ else if (ch === "\\") escaped = true;
14589
+ else if (ch === '"') {
14590
+ inString = false;
14591
+ if (top?.kind === "object" && top.expectKey) {
14592
+ top.key = JSON.parse(shown.slice(stringStart, i + 1));
14593
+ top.expectKey = false;
14594
+ }
14595
+ }
14596
+ continue;
14597
+ }
14598
+ if (ch === '"') {
14599
+ inString = true;
14600
+ stringStart = i;
14601
+ } else if (ch === "{" || ch === "[") {
14602
+ let value = details;
14603
+ if (top) {
14604
+ const parent = top.value;
14605
+ value = top.kind === "array" ? Array.isArray(parent) ? parent[top.complete] : void 0 : parent && typeof parent === "object" ? parent[top.key ?? ""] : void 0;
14606
+ }
14607
+ stack.push({
14608
+ kind: ch === "{" ? "object" : "array",
14609
+ path: top ? childPath(top) : "",
14610
+ value,
14611
+ complete: 0,
14612
+ childClosed: false,
14613
+ expectKey: ch === "{"
14614
+ });
14615
+ } else if (ch === "}" || ch === "]") {
14616
+ stack.pop();
14617
+ const parent = stack.at(-1);
14618
+ if (parent) parent.childClosed = true;
14619
+ } else if (ch === "," && top) {
14620
+ top.complete++;
14621
+ top.childClosed = false;
14622
+ top.expectKey = top.kind === "object";
14623
+ }
14624
+ }
14625
+ return stack;
14626
+ }
14627
+ function sliceSummaryLine(shown, details) {
14628
+ if (!shown.startsWith("{") && !shown.startsWith("[")) return "";
14629
+ const stack = openContainersAt(shown, details);
14630
+ const root = stack.at(0);
14631
+ const deepest = stack.at(-1);
14632
+ if (!root || !deepest) return "";
14633
+ const summary = {
14634
+ // Between two children the cut is named by their container.
14635
+ cutAt: deepest.childClosed || deepest.expectKey ? deepest.path : childPath(deepest),
14636
+ droppedKeys: [],
14637
+ keptItems: {}
14638
+ };
14639
+ for (const container of stack.slice(0, MAX_SUMMARY_DEPTH)) {
14640
+ const total = container.kind === "array" ? Array.isArray(container.value) ? container.value.length : void 0 : serializedKeys(container.value)?.length;
14641
+ if (total === void 0) break;
14642
+ if (container === root && container.kind === "object") continue;
14643
+ const shownCount = container.complete + (container.childClosed ? 1 : 0);
14644
+ summary.keptItems[container.path || "(root)"] = container.kind === "array" ? `${shownCount} of ${total}` : `${shownCount} of ${total} keys`;
14645
+ }
14646
+ const rootKeys = root.kind === "object" ? serializedKeys(root.value) : void 0;
14647
+ if (rootKeys) {
14648
+ const inProgress = root.expectKey || root.childClosed ? 0 : 1;
14649
+ const dropped = rootKeys.slice(root.complete + (root.childClosed ? 1 : 0) + inProgress);
14650
+ const record = root.value;
14651
+ for (const key of dropped.slice(0, MAX_SUMMARY_KEYS)) {
14652
+ const label = shortSegment(key);
14653
+ summary.droppedKeys.push(label);
14654
+ const value = record[key];
14655
+ if (Array.isArray(value)) summary.keptItems[label] = `0 of ${value.length}`;
14656
+ }
14657
+ if (dropped.length > MAX_SUMMARY_KEYS) summary.moreDroppedKeys = dropped.length - MAX_SUMMARY_KEYS;
14658
+ }
14659
+ return `${TRUNCATION_SUMMARY_KEY}: ${JSON.stringify(summary)}`;
14660
+ }
14661
+ function markedSlice(full, details) {
14662
+ let cut = MAX_TOOL_RESULT_CHARS;
14663
+ for (let attempt = 0; attempt < 4; attempt++) {
14664
+ const line = sliceSummaryLine(full.slice(0, cut), details);
14665
+ const cost = line ? line.length + 1 : 0;
14666
+ if (cut + cost <= MAX_TOOL_RESULT_CHARS) {
14667
+ return full.slice(0, cut) + "\n" + (line ? line + "\n" : "") + TRUNCATION_NOTE;
14668
+ }
14669
+ cut = MAX_TOOL_RESULT_CHARS - cost - attempt * 16;
14670
+ }
14671
+ return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
14672
+ }
14400
14673
  function truncateToolResult(details) {
14401
14674
  const full = serializeResult(details);
14402
14675
  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
- }
14676
+ if (full.length > MAX_STRUCTURED_TRUNCATION_CHARS) return markedSlice(full, details);
14406
14677
  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 });
14678
+ const render = (rows) => serializeResult({
14679
+ items: rows,
14680
+ __truncated: true,
14681
+ __omittedRows: details.length - rows.length,
14682
+ [TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([["items", { kept: rows.length, total: details.length }]]))
14683
+ });
14684
+ return render(trimRowsToFit(details, render));
14412
14685
  }
14413
14686
  if (details && typeof details === "object") {
14414
14687
  const obj = details;
14415
14688
  const arrayKey = largestArrayKey(obj);
14416
14689
  if (arrayKey) {
14417
14690
  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({
14691
+ const render = (kept) => serializeResult({
14423
14692
  ...obj,
14424
14693
  [arrayKey]: kept,
14425
14694
  __truncated: true,
14426
- __omittedRows: rows.length - kept.length
14695
+ __omittedRows: rows.length - kept.length,
14696
+ [TRUNCATION_SUMMARY_KEY]: keptSummary(/* @__PURE__ */ new Map([[arrayKey, { kept: kept.length, total: rows.length }]]))
14427
14697
  });
14428
- if (out.length <= MAX_TOOL_RESULT_CHARS) return out;
14698
+ const out = render(trimRowsToFit(rows, render));
14699
+ const otherLists = Object.entries(obj).some(([key, value]) => key !== arrayKey && Array.isArray(value) && value.length > 0);
14700
+ const nested2 = otherLists ? trimNestedArrays(full) : void 0;
14701
+ if (fitsCap(out) && nested2 !== void 0 && keptRows(nested2, arrayKey) > keptRows(out, arrayKey)) return nested2;
14702
+ if (fitsCap(out)) return out;
14703
+ if (nested2 !== void 0) return nested2;
14429
14704
  }
14430
14705
  const nested = trimNestedArrays(full);
14431
14706
  if (nested !== void 0) return nested;
14432
14707
  }
14433
- return full.slice(0, MAX_TOOL_RESULT_CHARS) + "\n" + TRUNCATION_NOTE;
14708
+ return markedSlice(full, details);
14709
+ }
14710
+ function keptRows(serialized, key) {
14711
+ try {
14712
+ const parsed = JSON.parse(serialized);
14713
+ return Array.isArray(parsed[key]) ? parsed[key].length : 0;
14714
+ } catch {
14715
+ return 0;
14716
+ }
14434
14717
  }
14435
14718
  function textResult2(details) {
14436
14719
  return {
@@ -14835,11 +15118,11 @@ function recordLlmUsageEvent(args) {
14835
15118
  var AERO_MEMORY_SEPARATOR = "\n\n---\n\n";
14836
15119
  var AERO_MEMORY_BLOCK_PREFIX = `${AERO_MEMORY_SEPARATOR}<memory>
14837
15120
  `;
14838
- function isRecord2(value) {
15121
+ function isRecord3(value) {
14839
15122
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
14840
15123
  }
14841
15124
  function isTextSystemBlock(value) {
14842
- return isRecord2(value) && value.type === "text" && typeof value.text === "string";
15125
+ return isRecord3(value) && value.type === "text" && typeof value.text === "string";
14843
15126
  }
14844
15127
  function splitAeroHydratedSystemPrompt(systemPrompt) {
14845
15128
  const idx = systemPrompt.lastIndexOf(AERO_MEMORY_BLOCK_PREFIX);
@@ -14851,7 +15134,7 @@ function splitAeroHydratedSystemPrompt(systemPrompt) {
14851
15134
  }
14852
15135
  function splitAeroAnthropicSystemCachePayload(payload, model) {
14853
15136
  if (model.api !== "anthropic-messages") return void 0;
14854
- if (!isRecord2(payload) || !Array.isArray(payload.system)) return void 0;
15137
+ if (!isRecord3(payload) || !Array.isArray(payload.system)) return void 0;
14855
15138
  let changed = false;
14856
15139
  const nextSystem = [];
14857
15140
  for (const block of payload.system) {
@@ -14887,6 +15170,7 @@ function splitAeroAnthropicSystemCachePayload(payload, model) {
14887
15170
  import { z } from "zod";
14888
15171
  import { Type as Type4 } from "@sinclair/typebox";
14889
15172
  var DISCOVER = "aero_list_toolkits";
15173
+ var MAX_VISIBLE_TOOLS = 128;
14890
15174
  var LOAD = "aero_load_toolkit";
14891
15175
  var metadata = new Map(canonryMcpTools.map((tool) => [tool.name, tool]));
14892
15176
  var runtimes = /* @__PURE__ */ new WeakMap();
@@ -14897,7 +15181,7 @@ function visibleTools(runtime) {
14897
15181
  if (!runtime.progressive) return runtime.allowed;
14898
15182
  const visible = runtime.allowed.filter((tool) => {
14899
15183
  const entry = metadata.get(tool.name);
14900
- return !entry || entry.tier === "core" || runtime.loaded.has(entry.tier);
15184
+ return !entry || entry.tier === "core" || runtime.pinned.has(tool.name) || runtime.loaded.has(entry.tier);
14901
15185
  });
14902
15186
  const available = CANONRY_MCP_TOOLKITS.filter((kit) => runtime.allowed.some((tool) => metadata.get(tool.name)?.tier === kit.name));
14903
15187
  return [...visible, {
@@ -14914,15 +15198,39 @@ function visibleTools(runtime) {
14914
15198
  execute: async (_id, input) => {
14915
15199
  const args = z.object({ toolkit: z.string() }).parse(input);
14916
15200
  if (!available.some((kit) => kit.name === args.toolkit)) throw new Error("Toolkit is unavailable for this turn.");
15201
+ runtime.loaded.delete(args.toolkit);
14917
15202
  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 })) });
15203
+ const unloaded = [];
15204
+ for (const older of [...runtime.loaded]) {
15205
+ if (visibleTools(runtime).length <= MAX_VISIBLE_TOOLS || older === args.toolkit) continue;
15206
+ runtime.loaded.delete(older);
15207
+ unloaded.push(older);
15208
+ }
15209
+ return result({
15210
+ tools: runtime.allowed.filter((tool) => metadata.get(tool.name)?.tier === args.toolkit).map((tool) => ({ name: tool.name, description: tool.description })),
15211
+ ...unloaded.length > 0 ? { unloaded, note: `Unloaded ${unloaded.join(", ")} to stay within the tool limit; load again if needed.` } : {}
15212
+ });
14919
15213
  }
14920
15214
  }];
14921
15215
  }
14922
- function configureAeroRuntime(agent, allowed, limits, progressive = true) {
15216
+ function explainMissingTool(runtime, message) {
15217
+ if (!message.isError || !Array.isArray(message.content)) return;
15218
+ const first = message.content[0];
15219
+ const name = first?.type === "text" ? /^Tool (\S+) not found$/.exec(first.text ?? "")?.[1] : void 0;
15220
+ if (!name) return;
15221
+ let text;
15222
+ if (!runtime.progressive) {
15223
+ text = `${name} is not available in this conversation. Use only the tools listed for you.`;
15224
+ } else {
15225
+ const toolkit = metadata.get(name)?.tier;
15226
+ 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.`;
15227
+ }
15228
+ message.content = [{ type: "text", text }];
15229
+ }
15230
+ function configureAeroRuntime(agent, allowed, limits, progressive = true, pinned = []) {
14923
15231
  let runtime = runtimes.get(agent);
14924
15232
  if (!runtime) {
14925
- runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive };
15233
+ runtime = { allowed, loaded: /* @__PURE__ */ new Set(), limits: agentTurnLimitsSchema.parse(limits ?? {}), calls: 0, rounds: 0, startedAt: 0, reason: "completed", progressive, pinned: new Set(pinned) };
14926
15234
  runtimes.set(agent, runtime);
14927
15235
  const state = runtime;
14928
15236
  const stream = agent.streamFn;
@@ -14959,6 +15267,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
14959
15267
  }
14960
15268
  if (event.type === "message_end" && event.message.role === "toolResult") {
14961
15269
  const message = event.message;
15270
+ explainMissingTool(state, message);
14962
15271
  const startedAt = toolStarts.get(message.toolCallId);
14963
15272
  Object.assign(message, {
14964
15273
  aeroToolLabel: state.allowed.find((tool) => tool.name === message.toolName)?.label,
@@ -14990,6 +15299,7 @@ function configureAeroRuntime(agent, allowed, limits, progressive = true) {
14990
15299
  runtime.loaded = /* @__PURE__ */ new Set();
14991
15300
  runtime.limits = agentTurnLimitsSchema.parse(limits ?? {});
14992
15301
  runtime.progressive = progressive;
15302
+ runtime.pinned = new Set(pinned);
14993
15303
  agent.state.tools = visibleTools(runtime);
14994
15304
  }
14995
15305
  function aeroTurnStatus(agent) {
@@ -15036,7 +15346,7 @@ function buildAeroViewTool(options, validatedEvidence) {
15036
15346
  }
15037
15347
  };
15038
15348
  }
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.";
15349
+ 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
15350
  function aeroViewPrompt(context) {
15041
15351
  return context ? `
15042
15352
 
@@ -15460,6 +15770,59 @@ function writeCompactionNote(db, args) {
15460
15770
  return inserted;
15461
15771
  }
15462
15772
 
15773
+ // src/agent/project-shape.ts
15774
+ import { and as and17, count, eq as eq20 } from "drizzle-orm";
15775
+ var ADVANCED_TOOLS = [
15776
+ "canonry_measurement_portfolio_summary",
15777
+ "canonry_measurement_overview",
15778
+ "canonry_measurement_property_evidence",
15779
+ "canonry_measurement_property_competitors",
15780
+ "canonry_measurement_changes",
15781
+ "canonry_visibility_report",
15782
+ "canonry_analytics_sources"
15783
+ ];
15784
+ var LEGACY_TOOLS = ["canonry_measurement_overview", "canonry_measurement_plan_get"];
15785
+ var SIMPLE_TOOLS = ["canonry_visibility_report"];
15786
+ function aeroProjectShape(db, projectId) {
15787
+ try {
15788
+ const pointer = db.select().from(measurementPlans).where(eq20(measurementPlans.projectId, projectId)).get();
15789
+ const version = pointer ? db.select().from(measurementPlanVersions).where(and17(
15790
+ eq20(measurementPlanVersions.projectId, projectId),
15791
+ eq20(measurementPlanVersions.id, pointer.activeVersionId)
15792
+ )).get() : void 0;
15793
+ if (version) {
15794
+ const plan = JSON.parse(version.canonicalJson);
15795
+ const groups = plan.groups ?? [];
15796
+ const topLevel = groups.filter((group) => !group.parentGroupKey).length;
15797
+ const size = `${plan.targets?.length ?? 0} Properties in ${groups.length} groups (${topLevel} top-level, ${groups.length - topLevel} nested)`;
15798
+ if ((plan.schemaVersion ?? version.schemaVersion) !== MEASUREMENT_PLAN_V2_SCHEMA_VERSION) {
15799
+ return {
15800
+ prompt: `
15801
+
15802
+ 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.`,
15803
+ pinned: LEGACY_TOOLS
15804
+ };
15805
+ }
15806
+ const queriesIn = (queryClass) => new Set((plan.assignments ?? []).filter((assignment) => assignment.queryClass === queryClass).map((assignment) => assignment.queryId)).size;
15807
+ return {
15808
+ prompt: `
15809
+
15810
+ 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.`,
15811
+ pinned: ADVANCED_TOOLS
15812
+ };
15813
+ }
15814
+ const tracked = db.select({ total: count() }).from(queries).where(eq20(queries.projectId, projectId)).get()?.total ?? 0;
15815
+ return {
15816
+ prompt: `
15817
+
15818
+ 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.`,
15819
+ pinned: SIMPLE_TOOLS
15820
+ };
15821
+ } catch {
15822
+ return { prompt: "", pinned: [] };
15823
+ }
15824
+ }
15825
+
15463
15826
  // src/agent/compaction.ts
15464
15827
  import { complete } from "@mariozechner/pi-ai";
15465
15828
 
@@ -15702,7 +16065,7 @@ var SessionRegistry = class {
15702
16065
  modelProvider: effectiveProvider,
15703
16066
  modelId: effectiveModelId,
15704
16067
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
15705
- }).where(eq20(agentSessions.projectId, projectId)).run();
16068
+ }).where(eq21(agentSessions.projectId, projectId)).run();
15706
16069
  }
15707
16070
  const agent2 = createAeroSession({
15708
16071
  projectName,
@@ -15869,9 +16232,10 @@ ${lines.join("\n")}
15869
16232
  preferences?.signal?.throwIfAborted();
15870
16233
  await this.maybeCompact(projectName, agent);
15871
16234
  preferences?.signal?.throwIfAborted();
15872
- agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + aeroViewPrompt(preferences?.context);
15873
16235
  const progressive = (preferences?.toolProfile ?? AeroToolProfiles.default) === AeroToolProfiles.default;
15874
- configureAeroRuntime(agent, [...agent.state.tools, ...progressive ? [buildAeroViewTool(view, evidence)] : []], preferences?.limits, progressive);
16236
+ const shape = aeroProjectShape(this.opts.db, projectId);
16237
+ agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, systemPrompt) + shape.prompt + aeroViewPrompt(preferences?.context);
16238
+ configureAeroRuntime(agent, [...agent.state.tools, ...progressive ? [buildAeroViewTool(view, evidence)] : []], preferences?.limits, progressive, shape.pinned);
15875
16239
  return agent;
15876
16240
  } finally {
15877
16241
  this.acquisitions.delete(projectName);
@@ -15997,7 +16361,7 @@ ${lines.join("\n")}
15997
16361
  modelProvider: nextProvider,
15998
16362
  modelId: nextModelId,
15999
16363
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
16000
- }).where(eq20(agentSessions.projectId, projectId)).run();
16364
+ }).where(eq21(agentSessions.projectId, projectId)).run();
16001
16365
  }
16002
16366
  /** Snapshot pending work without consuming it before an atomic conversation switch. */
16003
16367
  prepareConversationChange(projectName) {
@@ -16171,11 +16535,11 @@ ${lines.join("\n")}
16171
16535
  return id;
16172
16536
  }
16173
16537
  tryResolveProjectId(projectName) {
16174
- const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq20(projects.name, projectName)).get();
16538
+ const row = this.opts.db.select({ id: projects.id }).from(projects).where(eq21(projects.name, projectName)).get();
16175
16539
  return row?.id;
16176
16540
  }
16177
16541
  loadRow(projectId) {
16178
- const row = this.opts.db.select().from(agentSessions).where(eq20(agentSessions.projectId, projectId)).get();
16542
+ const row = this.opts.db.select().from(agentSessions).where(eq21(agentSessions.projectId, projectId)).get();
16179
16543
  return row ?? null;
16180
16544
  }
16181
16545
  insertRow(params) {
@@ -16194,7 +16558,7 @@ ${lines.join("\n")}
16194
16558
  }
16195
16559
  updateRow(projectId, patch) {
16196
16560
  const now = (/* @__PURE__ */ new Date()).toISOString();
16197
- this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq20(agentSessions.projectId, projectId)).run();
16561
+ this.opts.db.update(agentSessions).set({ ...patch, updatedAt: now }).where(eq21(agentSessions.projectId, projectId)).run();
16198
16562
  }
16199
16563
  /**
16200
16564
  * Store a refreshed prompt snapshot without touching `updatedAt`, which the
@@ -16203,13 +16567,13 @@ ${lines.join("\n")}
16203
16567
  * recently used right after an upgrade.
16204
16568
  */
16205
16569
  persistPromptSnapshot(projectId, systemPrompt) {
16206
- this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq20(agentSessions.projectId, projectId)).run();
16570
+ this.opts.db.update(agentSessions).set({ systemPrompt }).where(eq21(agentSessions.projectId, projectId)).run();
16207
16571
  }
16208
16572
  };
16209
16573
 
16210
16574
  // src/agent/viewer-sessions.ts
16211
16575
  import crypto25 from "crypto";
16212
- import { and as and17, eq as eq21, isNotNull, like as like2 } from "drizzle-orm";
16576
+ import { and as and18, eq as eq22, isNotNull, like as like2 } from "drizzle-orm";
16213
16577
  var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
16214
16578
  CanonryMcpToolNames.canonry_ads_account,
16215
16579
  CanonryMcpToolNames.canonry_ads_geo_search,
@@ -16239,7 +16603,6 @@ var AERO_VIEWER_EXCLUDED_MCP_TOOLS = /* @__PURE__ */ new Set([
16239
16603
  var OPERATOR_ONLY_TOOL_NAMES = new Set(
16240
16604
  canonryMcpTools.filter((tool) => tool.tier === "agent" || tool.requiresOperator).map((tool) => tool.name)
16241
16605
  );
16242
- var VIEWER_AERO_TURN_LIMITS = { maxToolCalls: 12, timeoutMs: 12e4 };
16243
16606
  var VIEWER_AERO_MAX_PROMPT_CHARS = 4e3;
16244
16607
  var VIEWER_AERO_DAILY_TURN_LIMIT = 50;
16245
16608
  function viewerAeroDailyLimitExceeded(limit) {
@@ -16323,8 +16686,8 @@ var ViewerAeroSessions = class {
16323
16686
  if (this.isBusy(project.name, userId)) throw agentBusy(project.name);
16324
16687
  const today = new Date(this.now()).toISOString().slice(0, 10);
16325
16688
  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);
16689
+ const count2 = used?.date === today ? used.count : 0;
16690
+ if (count2 >= VIEWER_AERO_DAILY_TURN_LIMIT) throw viewerAeroDailyLimitExceeded(VIEWER_AERO_DAILY_TURN_LIMIT);
16328
16691
  const minted = mintViewerAeroKey(this.opts.db, userId, new Date(this.now()));
16329
16692
  this.acquisitions.add(key);
16330
16693
  let handedOff = false;
@@ -16335,10 +16698,11 @@ var ViewerAeroSessions = class {
16335
16698
  const view = { client, projectName: project.name, basePath: this.opts.config.basePath, context: preferences.context };
16336
16699
  const evidence = preferences.context ? await readAeroViewEvidence(view) : void 0;
16337
16700
  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);
16701
+ const shape = aeroProjectShape(this.opts.db, project.id);
16702
+ agent.state.systemPrompt = loadAeroSystemPrompt(void 0, { extras: false }) + VIEWER_AERO_PROMPT + shape.prompt + aeroViewPrompt(preferences.context);
16703
+ configureAeroRuntime(agent, [...buildViewerAeroTools(client, project.name, this.opts.managedSweeps), buildAeroViewTool(view, evidence)], void 0, true, shape.pinned);
16340
16704
  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 });
16705
+ this.turnsToday.set(key, { date: today, count: count2 + 1 });
16342
16706
  handedOff = true;
16343
16707
  let released = false;
16344
16708
  return {
@@ -16385,10 +16749,10 @@ var ViewerAeroSessions = class {
16385
16749
  */
16386
16750
  removeKey(id) {
16387
16751
  try {
16388
- this.opts.db.delete(apiKeys).where(eq21(apiKeys.id, id)).run();
16752
+ this.opts.db.delete(apiKeys).where(eq22(apiKeys.id, id)).run();
16389
16753
  } catch {
16390
16754
  try {
16391
- this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq21(apiKeys.id, id)).run();
16755
+ this.opts.db.update(apiKeys).set({ revokedAt: new Date(this.now()).toISOString() }).where(eq22(apiKeys.id, id)).run();
16392
16756
  } catch {
16393
16757
  }
16394
16758
  }
@@ -16400,7 +16764,7 @@ var ViewerAeroSessions = class {
16400
16764
  */
16401
16765
  revokeOrphanedKeys() {
16402
16766
  try {
16403
- this.opts.db.delete(apiKeys).where(and17(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
16767
+ this.opts.db.delete(apiKeys).where(and18(like2(apiKeys.name, `${VIEWER_AERO_KEY_PREFIX}%`), isNotNull(apiKeys.delegatedUserId))).run();
16404
16768
  } catch {
16405
16769
  }
16406
16770
  }
@@ -16416,7 +16780,7 @@ var ViewerAeroSessions = class {
16416
16780
 
16417
16781
  // src/mcp-http.ts
16418
16782
  import crypto26 from "crypto";
16419
- import { eq as eq22 } from "drizzle-orm";
16783
+ import { eq as eq23 } from "drizzle-orm";
16420
16784
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
16421
16785
  import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
16422
16786
  var SESSION_IDLE_MS = 10 * 60 * 1e3;
@@ -16437,7 +16801,7 @@ function mintSessionKey(db, scopes, userId) {
16437
16801
  }
16438
16802
  function revokeSessionKey(db, id) {
16439
16803
  try {
16440
- db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(apiKeys.id, id)).run();
16804
+ db.update(apiKeys).set({ revokedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq23(apiKeys.id, id)).run();
16441
16805
  } catch {
16442
16806
  }
16443
16807
  }
@@ -16620,7 +16984,7 @@ function registerMcpHttpRoutes(scope, opts) {
16620
16984
  }
16621
16985
 
16622
16986
  // src/agent/agent-routes.ts
16623
- import { eq as eq23 } from "drizzle-orm";
16987
+ import { eq as eq24 } from "drizzle-orm";
16624
16988
  function viewerAeroCaller(request, opts) {
16625
16989
  if (!opts.viewerSessions) return null;
16626
16990
  const principal = request.principal;
@@ -16650,7 +17014,7 @@ function viewerTranscript(opts, projectName, userId) {
16650
17014
  };
16651
17015
  }
16652
17016
  function resolveProject(db, name) {
16653
- const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq23(projects.name, name)).get();
17017
+ const row = db.select({ id: projects.id, name: projects.name }).from(projects).where(eq24(projects.name, name)).get();
16654
17018
  if (!row) throw notFound("project", name);
16655
17019
  return row;
16656
17020
  }
@@ -16758,7 +17122,7 @@ function registerAgentRoutes(app, opts) {
16758
17122
  }
16759
17123
  requireInstanceAdministrator(request);
16760
17124
  const project = resolveProject(opts.db, request.params.name);
16761
- const row = opts.db.select().from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
17125
+ const row = opts.db.select().from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
16762
17126
  if (!row) {
16763
17127
  return { conversationId: null, messages: [], modelProvider: null, modelId: null, updatedAt: null };
16764
17128
  }
@@ -16798,7 +17162,7 @@ function registerAgentRoutes(app, opts) {
16798
17162
  const project = resolveProject(opts.db, request.params.name);
16799
17163
  if (opts.sessionRegistry.isBusy(project.name)) throw agentBusy(project.name);
16800
17164
  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();
17165
+ opts.db.update(agentSessions).set({ messages: "[]", followUpQueue: "[]", updatedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq24(agentSessions.projectId, project.id)).run();
16802
17166
  return { status: "reset" };
16803
17167
  }
16804
17168
  );
@@ -16822,7 +17186,7 @@ function registerAgentRoutes(app, opts) {
16822
17186
  return streamViewerTurn(opts, project, viewerId, body, reply);
16823
17187
  }
16824
17188
  if (body.conversationId !== void 0) {
16825
- const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq23(agentSessions.projectId, project.id)).get();
17189
+ const current = opts.db.select({ id: agentSessions.id }).from(agentSessions).where(eq24(agentSessions.projectId, project.id)).get();
16826
17190
  if ((current?.id ?? null) !== body.conversationId) throw validationError("The active conversation changed. Reload the conversation before sending.");
16827
17191
  }
16828
17192
  const promptText = body.prompt;
@@ -17625,7 +17989,7 @@ function buildSnapshotSummary(companyName, queries2, providers, queryResults, au
17625
17989
  competitorCounts.set(competitor, (competitorCounts.get(competitor) ?? 0) + 1);
17626
17990
  }
17627
17991
  }
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 }));
17992
+ 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
17993
  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
17994
  const failureNote = failedComparisons > 0 ? [
17631
17995
  `${failedComparisons} provider response${failedComparisons === 1 ? "" : "s"} failed and ${failedComparisons === 1 ? "was" : "were"} excluded from visibility totals.`
@@ -17760,20 +18124,20 @@ function clipText(value, length) {
17760
18124
  }
17761
18125
 
17762
18126
  // src/research-runner.ts
17763
- import { and as and18, eq as eq24, inArray as inArray9, sql as sql9 } from "drizzle-orm";
18127
+ import { and as and19, eq as eq25, inArray as inArray9, sql as sql9 } from "drizzle-orm";
17764
18128
  var unfinishedResearchQueryStatuses = [ResearchQueryStatuses.queued, ResearchQueryStatuses.running];
17765
18129
  function finalResearchRunStatus(completed, failed) {
17766
18130
  if (failed === 0) return ResearchRunStatuses.completed;
17767
18131
  return completed > 0 ? ResearchRunStatuses.partial : ResearchRunStatuses.failed;
17768
18132
  }
17769
18133
  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();
18134
+ const run = db.select().from(researchRuns).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.projectId, projectId))).get();
17771
18135
  if (!run || run.status !== ResearchRunStatuses.queued) return;
17772
- const project = db.select().from(projects).where(eq24(projects.id, projectId)).get();
18136
+ const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
17773
18137
  if (!project) return;
17774
18138
  const provider = registry.get(run.provider);
17775
18139
  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();
18140
+ const claim = db.update(researchRuns).set({ status: ResearchRunStatuses.running, startedAt: now }).where(and19(eq25(researchRuns.id, runId), eq25(researchRuns.status, ResearchRunStatuses.queued))).run();
17777
18141
  if (claim.changes !== 1) return;
17778
18142
  let reserved = 0;
17779
18143
  let dispatched = 0;
@@ -17797,8 +18161,8 @@ async function executeResearchRun(db, registry, runId, projectId) {
17797
18161
  }
17798
18162
  reserved = run.totalQueries;
17799
18163
  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);
18164
+ const rows = db.select().from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).orderBy(researchRunQueries.position).all();
18165
+ const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq25(competitors.projectId, projectId)).all().map((row) => row.domain);
17802
18166
  const domains = effectiveDomains(project);
17803
18167
  const brands = effectiveBrandNames(project);
17804
18168
  const config = { ...provider.config, model: run.resolvedModel };
@@ -17806,7 +18170,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
17806
18170
  await mapWithConcurrency(rows, Math.max(1, provider.config.quotaPolicy.maxConcurrency), async (row) => {
17807
18171
  try {
17808
18172
  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();
18173
+ db.update(researchRunQueries).set({ status: ResearchQueryStatuses.running, startedAt }).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.queued))).run();
17810
18174
  const raw = await gate.run(async () => {
17811
18175
  dispatched++;
17812
18176
  return provider.adapter.executeTrackedQuery({ query: row.queryText, canonicalDomains: domains, competitorDomains, ...run.location ? { location: run.location } : {} }, config);
@@ -17833,7 +18197,7 @@ async function executeResearchRun(db, registry, runId, projectId) {
17833
18197
  citationState: determineCitationState(normalized, domains),
17834
18198
  rawResponse: raw.rawResponse,
17835
18199
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
17836
- }).where(and18(eq24(researchRunQueries.id, row.id), eq24(researchRunQueries.status, ResearchQueryStatuses.running))).run();
18200
+ }).where(and19(eq25(researchRunQueries.id, row.id), eq25(researchRunQueries.status, ResearchQueryStatuses.running))).run();
17837
18201
  if (completed.changes === 1) incrementResearchProgress(db, runId, "completedQueries");
17838
18202
  } catch (error) {
17839
18203
  try {
@@ -17857,18 +18221,18 @@ async function executeResearchRun(db, registry, runId, projectId) {
17857
18221
  }
17858
18222
  }
17859
18223
  function incrementResearchProgress(db, runId, column) {
17860
- db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq24(researchRuns.id, runId)).run();
18224
+ db.update(researchRuns).set({ [column]: sql9`${researchRuns[column]} + 1` }).where(eq25(researchRuns.id, runId)).run();
17861
18225
  }
17862
18226
  function markResearchQueryFailed(db, runId, queryId, error) {
17863
18227
  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();
18228
+ 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
18229
  if (failed.changes === 1) incrementResearchProgress(db, runId, "failedQueries");
17866
18230
  }
17867
18231
  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();
18232
+ 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
18233
  }
17870
18234
  function finalizeResearchRun(db, runId, fatalError) {
17871
- const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq24(researchRunQueries.researchRunId, runId)).all();
18235
+ const rows = db.select({ status: researchRunQueries.status }).from(researchRunQueries).where(eq25(researchRunQueries.researchRunId, runId)).all();
17872
18236
  const completed = rows.filter((row) => row.status === ResearchQueryStatuses.completed).length;
17873
18237
  const failed = rows.filter((row) => row.status === ResearchQueryStatuses.failed).length;
17874
18238
  db.update(researchRuns).set({
@@ -17877,11 +18241,11 @@ function finalizeResearchRun(db, runId, fatalError) {
17877
18241
  failedQueries: failed,
17878
18242
  error: fatalError ?? (failed === rows.length ? "Every research query failed." : null),
17879
18243
  finishedAt: (/* @__PURE__ */ new Date()).toISOString()
17880
- }).where(eq24(researchRuns.id, runId)).run();
18244
+ }).where(eq25(researchRuns.id, runId)).run();
17881
18245
  }
17882
18246
 
17883
18247
  // src/google-marketing-sync.ts
17884
- import { and as and19, eq as eq25, isNull as isNull2 } from "drizzle-orm";
18248
+ import { and as and20, eq as eq26, isNull as isNull2 } from "drizzle-orm";
17885
18249
  function safeErrorMessage(error) {
17886
18250
  const raw = describeError(error);
17887
18251
  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 +18268,10 @@ function assertSnapshotContext(metadata2, expected) {
17904
18268
  }
17905
18269
  }
17906
18270
  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)
18271
+ const run = db.select().from(runs).where(and20(
18272
+ eq26(runs.id, runId),
18273
+ eq26(runs.projectId, projectId),
18274
+ eq26(runs.kind, kind)
17911
18275
  )).get();
17912
18276
  if (!run) throw new Error(`Run ${runId} is not a ${kind} run for this project.`);
17913
18277
  if (run.status !== RunStatuses.queued) {
@@ -17920,19 +18284,19 @@ function markFailed(db, runId, projectId, kind, expectedStatus, error) {
17920
18284
  status: RunStatuses.failed,
17921
18285
  error: serializeRunError({ message: safeErrorMessage(error) }),
17922
18286
  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)
18287
+ }).where(and20(
18288
+ eq26(runs.id, runId),
18289
+ eq26(runs.projectId, projectId),
18290
+ eq26(runs.kind, kind),
18291
+ eq26(runs.status, expectedStatus)
17928
18292
  )).run();
17929
18293
  }
17930
18294
  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)
18295
+ const claim = db.update(runs).set({ status: RunStatuses.running, startedAt, error: null }).where(and20(
18296
+ eq26(runs.id, runId),
18297
+ eq26(runs.projectId, projectId),
18298
+ eq26(runs.kind, kind),
18299
+ eq26(runs.status, RunStatuses.queued)
17936
18300
  )).run();
17937
18301
  return claim.changes === 1;
17938
18302
  }
@@ -17940,9 +18304,9 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
17940
18304
  let claimed = false;
17941
18305
  try {
17942
18306
  requireSyncRun(db, runId, projectId, RunKinds["google-ads-sync"]);
17943
- const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
18307
+ const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
17944
18308
  if (!project) throw new Error(`Project ${projectId} was not found.`);
17945
- const connection = db.select().from(googleAdsConnections).where(eq25(googleAdsConnections.projectId, projectId)).get();
18309
+ const connection = db.select().from(googleAdsConnections).where(eq26(googleAdsConnections.projectId, projectId)).get();
17946
18310
  if (!connection?.selectedCustomerId) {
17947
18311
  throw new Error("Google Ads needs a selected customer before it can sync.");
17948
18312
  }
@@ -17993,11 +18357,11 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
17993
18357
  status: RunStatuses.completed,
17994
18358
  finishedAt,
17995
18359
  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)
18360
+ }).where(and20(
18361
+ eq26(runs.id, runId),
18362
+ eq26(runs.projectId, projectId),
18363
+ eq26(runs.kind, RunKinds["google-ads-sync"]),
18364
+ eq26(runs.status, RunStatuses.running)
18001
18365
  )).run();
18002
18366
  if (terminalClaim.changes !== 1) {
18003
18367
  throw new Error(`Run ${runId} was no longer running when Google Ads evidence was ready.`);
@@ -18014,12 +18378,12 @@ async function executeGoogleAdsMarketingSync(db, runtime, runId, projectId) {
18014
18378
  lastMetricsSnapshotAt: result2.metrics?.metadata.capturedAt ?? connection.lastMetricsSnapshotAt,
18015
18379
  lastMetricsSnapshotId: result2.metrics?.metadata.id ?? connection.lastMetricsSnapshotId,
18016
18380
  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)
18381
+ }).where(and20(
18382
+ eq26(googleAdsConnections.id, connection.id),
18383
+ eq26(googleAdsConnections.projectId, projectId),
18384
+ eq26(googleAdsConnections.selectedCustomerId, connection.selectedCustomerId),
18385
+ connection.selectedLoginCustomerId === null ? isNull2(googleAdsConnections.selectedLoginCustomerId) : eq26(googleAdsConnections.selectedLoginCustomerId, connection.selectedLoginCustomerId),
18386
+ eq26(googleAdsConnections.selectionGeneration, connection.selectionGeneration)
18023
18387
  )).run();
18024
18388
  if (connectionClaim.changes !== 1) {
18025
18389
  throw new Error("Google Ads selection changed while its evidence was being collected.");
@@ -18046,9 +18410,9 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18046
18410
  let claimed = false;
18047
18411
  try {
18048
18412
  requireSyncRun(db, runId, projectId, RunKinds["gtm-sync"]);
18049
- const project = db.select().from(projects).where(eq25(projects.id, projectId)).get();
18413
+ const project = db.select().from(projects).where(eq26(projects.id, projectId)).get();
18050
18414
  if (!project) throw new Error(`Project ${projectId} was not found.`);
18051
- const connection = db.select().from(gtmConnections).where(eq25(gtmConnections.projectId, projectId)).get();
18415
+ const connection = db.select().from(gtmConnections).where(eq26(gtmConnections.projectId, projectId)).get();
18052
18416
  if (!connection?.selectedAccountId || !connection.selectedContainerId) {
18053
18417
  throw new Error("GTM needs a selected account and container before it can sync.");
18054
18418
  }
@@ -18083,11 +18447,11 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18083
18447
  status: RunStatuses.completed,
18084
18448
  finishedAt,
18085
18449
  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)
18450
+ }).where(and20(
18451
+ eq26(runs.id, runId),
18452
+ eq26(runs.projectId, projectId),
18453
+ eq26(runs.kind, RunKinds["gtm-sync"]),
18454
+ eq26(runs.status, RunStatuses.running)
18091
18455
  )).run();
18092
18456
  if (terminalClaim.changes !== 1) {
18093
18457
  throw new Error(`Run ${runId} was no longer running when GTM evidence was ready.`);
@@ -18101,13 +18465,13 @@ async function executeGtmMarketingSync(db, runtime, runId, projectId) {
18101
18465
  lastSnapshotAt: snapshot.metadata.capturedAt,
18102
18466
  lastSnapshotId: snapshot.metadata.id,
18103
18467
  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)
18468
+ }).where(and20(
18469
+ eq26(gtmConnections.id, connection.id),
18470
+ eq26(gtmConnections.projectId, projectId),
18471
+ eq26(gtmConnections.selectedAccountId, connection.selectedAccountId),
18472
+ eq26(gtmConnections.selectedContainerId, connection.selectedContainerId),
18473
+ connection.selectedWorkspaceId === null ? isNull2(gtmConnections.selectedWorkspaceId) : eq26(gtmConnections.selectedWorkspaceId, connection.selectedWorkspaceId),
18474
+ eq26(gtmConnections.selectionGeneration, connection.selectionGeneration)
18111
18475
  )).run();
18112
18476
  if (connectionClaim.changes !== 1) {
18113
18477
  throw new Error("GTM selection changed while its evidence was being collected.");
@@ -18611,7 +18975,7 @@ async function createServer(opts) {
18611
18975
  async (ctx) => {
18612
18976
  if (!sessionRegistry) return;
18613
18977
  if (!agentProactive) return;
18614
- const project = opts.db.select({ name: projects.name }).from(projects).where(eq26(projects.id, ctx.projectId)).get();
18978
+ const project = opts.db.select({ name: projects.name }).from(projects).where(eq27(projects.id, ctx.projectId)).get();
18615
18979
  if (!project) return;
18616
18980
  let content;
18617
18981
  if (ctx.kind === RunKinds["aeo-discover-probe"]) {
@@ -19068,7 +19432,7 @@ async function createServer(opts) {
19068
19432
  void (async () => {
19069
19433
  try {
19070
19434
  const report = await schedulerClient.runDoctor({ project: projectName });
19071
- const project = opts.db.select().from(projects).where(eq26(projects.name, projectName)).get();
19435
+ const project = opts.db.select().from(projects).where(eq27(projects.name, projectName)).get();
19072
19436
  if (!project) {
19073
19437
  app.log.warn({ projectName }, "doctor schedule fired for an unknown project");
19074
19438
  return;
@@ -19096,9 +19460,9 @@ async function createServer(opts) {
19096
19460
  });
19097
19461
  if (!probed) return;
19098
19462
  const alreadySynced = opts.db.select().from(ccReleaseSyncs).where(
19099
- and20(
19100
- eq26(ccReleaseSyncs.release, probed.release),
19101
- eq26(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
19463
+ and21(
19464
+ eq27(ccReleaseSyncs.release, probed.release),
19465
+ eq27(ccReleaseSyncs.status, CcReleaseSyncStatuses.ready)
19102
19466
  )
19103
19467
  ).limit(1).get();
19104
19468
  if (alreadySynced) {
@@ -19369,7 +19733,7 @@ async function createServer(opts) {
19369
19733
  })();
19370
19734
  if (opts.config.apiKey) {
19371
19735
  const keyHash = hashApiKey2(opts.config.apiKey);
19372
- const existing = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, keyHash)).get();
19736
+ const existing = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, keyHash)).get();
19373
19737
  if (!existing) {
19374
19738
  const prefix = opts.config.apiKey.slice(0, 12);
19375
19739
  opts.db.insert(apiKeys).values({
@@ -19421,7 +19785,7 @@ async function createServer(opts) {
19421
19785
  };
19422
19786
  const getDefaultApiKey = () => {
19423
19787
  if (!opts.config.apiKey) return void 0;
19424
- return opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
19788
+ return opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(opts.config.apiKey))).get();
19425
19789
  };
19426
19790
  const createPasswordSession = (reply) => {
19427
19791
  const key = getDefaultApiKey();
@@ -19445,7 +19809,7 @@ async function createServer(opts) {
19445
19809
  if (!header) return false;
19446
19810
  const parts = header.split(" ");
19447
19811
  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();
19812
+ const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(parts[1]))).get();
19449
19813
  return Boolean(key && !key.revokedAt);
19450
19814
  };
19451
19815
  const namedAccountsInUse = () => anyUsersExist(opts.db);
@@ -19535,12 +19899,12 @@ async function createServer(opts) {
19535
19899
  return reply.send({ authenticated: true });
19536
19900
  }
19537
19901
  if (apiKey) {
19538
- const key = opts.db.select().from(apiKeys).where(eq26(apiKeys.keyHash, hashApiKey2(apiKey))).get();
19902
+ const key = opts.db.select().from(apiKeys).where(eq27(apiKeys.keyHash, hashApiKey2(apiKey))).get();
19539
19903
  if (!key || key.revokedAt) {
19540
19904
  const err2 = authInvalid();
19541
19905
  return reply.status(err2.statusCode).send(err2.toJSON());
19542
19906
  }
19543
- opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq26(apiKeys.id, key.id)).run();
19907
+ opts.db.update(apiKeys).set({ lastUsedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq27(apiKeys.id, key.id)).run();
19544
19908
  const sessionId = createSession(key.id);
19545
19909
  reply.header(
19546
19910
  "set-cookie",
@@ -19703,8 +20067,8 @@ async function createServer(opts) {
19703
20067
  getAgentPin: () => describeAgentPin(opts.config),
19704
20068
  getGoogleMarketingDoctorInput: (ctx) => {
19705
20069
  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();
20070
+ const googleAds = opts.db.select().from(googleAdsConnections).where(eq27(googleAdsConnections.projectId, ctx.project.id)).get();
20071
+ const gtm = opts.db.select().from(gtmConnections).where(eq27(gtmConnections.projectId, ctx.project.id)).get();
19708
20072
  const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
19709
20073
  const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
19710
20074
  const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
@@ -19955,7 +20319,7 @@ async function createServer(opts) {
19955
20319
  ...inspectOpts,
19956
20320
  config: opts.config
19957
20321
  }).then(() => {
19958
- const finished = opts.db.select({ status: runs.status }).from(runs).where(eq26(runs.id, runId)).get();
20322
+ const finished = opts.db.select({ status: runs.status }).from(runs).where(eq27(runs.id, runId)).get();
19959
20323
  if (finished?.status === RunStatuses.completed || finished?.status === RunStatuses.partial) {
19960
20324
  return maybeRefreshGscCoverage(
19961
20325
  opts.db,
@@ -20053,7 +20417,7 @@ async function createServer(opts) {
20053
20417
  const entry = providerSummary.find((p) => p.name === name);
20054
20418
  if (entry) {
20055
20419
  entry.configured = true;
20056
- entry.model = model || registry.get(name)?.config.model;
20420
+ entry.model = registry.get(name)?.config.model;
20057
20421
  entry.quota = quota;
20058
20422
  if (name === "gemini") {
20059
20423
  entry.vertexConfigured = !!opts.config.providers?.[name]?.vertexProject;
@@ -20221,7 +20585,7 @@ async function createServer(opts) {
20221
20585
  }
20222
20586
  ];
20223
20587
  },
20224
- onGenerateQueries: async (providerName, count, project) => {
20588
+ onGenerateQueries: async (providerName, count2, project) => {
20225
20589
  const provider = registry.get(providerName);
20226
20590
  if (!provider)
20227
20591
  throw new Error(`Provider "${providerName}" is not configured`);
@@ -20233,10 +20597,10 @@ async function createServer(opts) {
20233
20597
  language: project.language,
20234
20598
  existingQueries: project.existingQueries,
20235
20599
  siteText,
20236
- count
20600
+ count: count2
20237
20601
  });
20238
20602
  const raw = await provider.adapter.generateText(prompt, provider.config);
20239
- return parseQueryResponse(raw, count);
20603
+ return parseQueryResponse(raw, count2);
20240
20604
  },
20241
20605
  onSnapshotRequested: async (input) => {
20242
20606
  return snapshotService.createReport(input);
@@ -20431,7 +20795,7 @@ async function createServer(opts) {
20431
20795
  },
20432
20796
  notify: (projectId, result2) => notifier.onSiteLivenessChecked(projectId, result2)
20433
20797
  });
20434
- for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq26(researchRuns.status, ResearchRunStatuses.queued)).all()) {
20798
+ for (const run of opts.db.select({ id: researchRuns.id, projectId: researchRuns.projectId }).from(researchRuns).where(eq27(researchRuns.status, ResearchRunStatuses.queued)).all()) {
20435
20799
  dispatchResearchRun(run.id, run.projectId);
20436
20800
  }
20437
20801
  runtimeStartupSettled = true;
@@ -20486,7 +20850,7 @@ function buildQueryGenerationPrompt(ctx) {
20486
20850
  );
20487
20851
  return lines.join("\n");
20488
20852
  }
20489
- function parseQueryResponse(raw, count) {
20853
+ function parseQueryResponse(raw, count2) {
20490
20854
  const seen = /* @__PURE__ */ new Set();
20491
20855
  const results = [];
20492
20856
  for (const line of raw.split("\n")) {
@@ -20502,7 +20866,7 @@ function parseQueryResponse(raw, count) {
20502
20866
  if (seen.has(key)) continue;
20503
20867
  seen.add(key);
20504
20868
  results.push(cleaned);
20505
- if (results.length >= count) break;
20869
+ if (results.length >= count2) break;
20506
20870
  }
20507
20871
  return results;
20508
20872
  }